Initial commit - Lost and Found Revisi

This commit is contained in:
bambang-code1
2025-12-20 00:01:08 +07:00
commit f4c838471c
210 changed files with 30706 additions and 0 deletions
+266
View File
@@ -0,0 +1,266 @@
// ==========================================
// UPDATE MyClaimsTab.js untuk pass currentUserId
// ==========================================
const MyClaimsTab = ({ state, handlers }) => {
const { myLostItems, myClaims, loading, user } = state; // ⭐ Ambil user dari state
const lostItemsWithClaims = myLostItems.filter(
(item) =>
item.direct_claim_id &&
item.direct_claim_status &&
item.status !== "completed" &&
item.direct_claim_status !== "completed"
);
const regularClaims = myClaims || [];
const getStatusBadge = (status) => {
const badges = {
active: { color: "bg-blue-500", text: "🔍 Masih Hilang", emoji: "😢" },
claimed: {
color: "bg-yellow-500",
text: "⏳ Menunggu Persetujuan Anda",
emoji: "🤔",
},
found: { color: "bg-green-500", text: "✅ Ditemukan", emoji: "🎉" },
completed: {
color: "bg-purple-500",
text: "✅ Selesai - Barang Diterima",
emoji: "🎉",
},
pending: {
color: "bg-yellow-500",
text: "⏳ Menunggu Verifikasi",
emoji: "⏳",
},
approved: { color: "bg-green-500", text: "✅ Disetujui", emoji: "✅" },
rejected: { color: "bg-red-500", text: "❌ Ditolak", emoji: "❌" },
waiting_owner: {
color: "bg-orange-500",
text: "⏳ Menunggu Anda",
emoji: "👤",
},
verified: {
color: "bg-green-600",
text: "✅ Terverifikasi",
emoji: "✅",
},
};
return (
badges[status] || {
color: "bg-gray-500",
text: status,
emoji: "❓",
}
);
};
if (loading) {
return (
<div className="text-center py-12 text-slate-400">
<div className="animate-spin text-4xl mb-4">⏳</div>
<p>Memuat data klaim...</p>
</div>
);
}
return (
<div className="space-y-8">
{lostItemsWithClaims.length > 0 && (
<div className="bg-slate-800/50 backdrop-blur-sm rounded-2xl border border-slate-700 p-6">
<h3 className="text-2xl font-bold text-white mb-4 flex items-center gap-3">
<span className="text-3xl">😢</span>
Barang Hilang Saya
<span className="bg-orange-500/20 text-orange-400 text-sm px-3 py-1 rounded-full border border-orange-500/30">
{lostItemsWithClaims.length} item dengan klaim pending
</span>
</h3>
<p className="text-slate-400 mb-6">
Ada yang menemukan barang Anda! Silakan approve atau reject klaim
mereka.
</p>
<div className="space-y-4">
{lostItemsWithClaims.map((lostItem) => {
const statusBadge = getStatusBadge(
lostItem.direct_claim_status || lostItem.status
);
const isWaitingOwner =
lostItem.direct_claim_status === "waiting_owner";
const isVerified =
lostItem.direct_claim_status === "verified" ||
lostItem.status === "found";
return (
<div
key={lostItem.id}
className={`bg-slate-900/80 rounded-xl border ${
isWaitingOwner
? "border-orange-500/50 shadow-lg shadow-orange-500/20"
: "border-slate-700"
} p-6 hover:shadow-xl transition-all`}
>
<div className="flex justify-between items-start mb-4">
<div className="flex-1">
<div className="flex items-center gap-3 mb-2">
<h4 className="text-xl font-bold text-white">
{lostItem.name}
</h4>
<span
className={`${statusBadge.color} text-white text-xs px-3 py-1 rounded-full font-medium`}
>
{statusBadge.emoji} {statusBadge.text}
</span>
</div>
<div className="space-y-1 text-sm text-slate-400">
<p>
<span className="font-medium">📦 Kategori:</span>{" "}
{lostItem.category}
</p>
{lostItem.color && (
<p>
<span className="font-medium">🎨 Warna:</span>{" "}
{lostItem.color}
</p>
)}
<p>
<span className="font-medium">📍 Lokasi Hilang:</span>{" "}
{lostItem.location}
</p>
<p>
<span className="font-medium">
📅 Tanggal Hilang:
</span>{" "}
{new Date(lostItem.date_lost).toLocaleDateString(
"id-ID"
)}
</p>
<p className="mt-2">
<span className="font-medium">📝 Deskripsi:</span>{" "}
{lostItem.description}
</p>
</div>
</div>
</div>
{/* ⭐ Display claim card with proper context */}
{lostItem.direct_claim && (
<div className="mt-4">
<ClaimCard
claim={{
...lostItem.direct_claim,
lost_item_user_id: lostItem.user_id, // ⭐ Pass owner ID
}}
currentUserId={user?.id} // ⭐ Pass current user ID
onUserRespond={handlers.handleUserRespondClaim}
/>
</div>
)}
{/* Fallback untuk direct claim tanpa detail */}
{!lostItem.direct_claim && isWaitingOwner && (
<div className="bg-orange-900/30 border border-orange-500/30 rounded-lg p-4 mt-4">
<p className="text-orange-300 font-semibold mb-3 flex items-center gap-2">
<span className="text-2xl">🔔</span>
Ada yang mengaku menemukan barang ini!
</p>
<p className="text-sm text-slate-300 mb-4">
Seseorang telah mengajukan klaim untuk barang ini.
Silakan putuskan apakah ini benar barang Anda.
</p>
<div className="flex gap-3">
<button
onClick={() =>
handlers.handleUserRespondClaim(
lostItem.direct_claim_id,
"approve"
)
}
className="flex-1 bg-gradient-to-r from-green-600 to-green-700 text-white px-4 py-3 rounded-lg font-semibold hover:from-green-700 hover:to-green-800 transition shadow-lg shadow-green-500/30"
>
✅ Approve (Ini barang saya!)
</button>
<button
onClick={() =>
handlers.handleUserRespondClaim(
lostItem.direct_claim_id,
"reject"
)
}
className="flex-1 bg-gradient-to-r from-red-600 to-red-700 text-white px-4 py-3 rounded-lg font-semibold hover:from-red-700 hover:to-red-800 transition shadow-lg shadow-red-500/30"
>
❌ Reject (Bukan barang saya)
</button>
</div>
</div>
)}
{isVerified && (
<div className="bg-green-900/30 border border-green-500/30 rounded-lg p-4 mt-4">
<p className="text-green-300 font-semibold mb-3 flex items-center gap-2">
<span className="text-2xl">✅</span>
Klaim Disetujui!
</p>
<p className="text-sm text-slate-300 mb-4">
Anda telah menyetujui bahwa penemu menemukan barang
Anda. Koordinasikan pengambilan barang, lalu konfirmasi
setelah menerima barang.
</p>
<button
onClick={() =>
handlers.handleUserCompleteCase(
lostItem.direct_claim_id
)
}
className="w-full bg-gradient-to-r from-purple-600 to-purple-700 text-white px-4 py-3 rounded-lg font-semibold hover:from-purple-700 hover:to-purple-800 transition shadow-lg shadow-purple-500/30"
>
🎉 Konfirmasi: Saya Sudah Terima Barang
</button>
</div>
)}
</div>
);
})}
</div>
</div>
)}
{regularClaims.length > 0 && (
<div className="bg-slate-800/50 backdrop-blur-sm rounded-2xl border border-slate-700 p-6">
<h3 className="text-2xl font-bold text-white mb-4 flex items-center gap-3">
<span className="text-3xl">🤝</span>
Klaim Barang yang Saya Ajukan
<span className="bg-blue-500/20 text-blue-400 text-sm px-3 py-1 rounded-full border border-blue-500/30">
{regularClaims.length} klaim
</span>
</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{regularClaims.map((claim) => (
<ClaimCard
key={claim.id}
claim={{
...claim,
user_id: claim.user_id, // Penemu
lost_item_user_id: claim.lost_item?.user_id, // Pemilik
}}
currentUserId={user?.id} // ⭐ Pass current user ID
/>
))}
</div>
</div>
)}
{lostItemsWithClaims.length === 0 && regularClaims.length === 0 && (
<div className="text-center py-16 bg-slate-800/30 rounded-2xl border border-slate-700">
<div className="text-6xl mb-4">🔭</div>
<h3 className="text-xl font-bold text-white mb-2">Belum Ada Klaim</h3>
<p className="text-slate-400">
Anda belum memiliki klaim atau barang hilang dengan klaim pending.
</p>
</div>
)}
</div>
);
};
window.MyClaimsTab = MyClaimsTab;
+308
View File
@@ -0,0 +1,308 @@
const MyFoundItemsTab = ({ state, handlers }) => {
const { myFoundItems, loading, user } = state;
const { showToast, loadData } = handlers;
const handleApproveItem = async (item) => {
if (!item.direct_claim_id) {
showToast("⚠️ Tidak ada klaim untuk item ini", "error");
return;
}
const confirmMsg = `✅ Konfirmasi Penerimaan Barang
Apakah Anda yakin barang "${item.name}" ini adalah milik Anda?
Jika Ya:
- Status akan berubah menjadi "verified"
- Anda bisa koordinasi dengan penemu untuk pengambilan
- Kasus akan dilanjutkan
⚠️ Pastikan ini benar-benar barang Anda!`;
if (!confirm(confirmMsg)) return;
try {
state.setLoading(true);
// ✅ ENDPOINT YANG BENAR - menggunakan claim ID
await ApiUtils.post(`/api/user/claims/${item.direct_claim_id}/respond`, {
action: "approve",
});
showToast(
"✅ Berhasil! Silakan koordinasi pengambilan dengan penemu.",
"success"
);
loadData();
} catch (error) {
console.error("Error approving item:", error);
showToast("Gagal menerima: " + error.message, "error");
} finally {
state.setLoading(false);
}
};
const handleRejectItem = async (item) => {
if (!item.direct_claim_id) {
showToast("⚠️ Tidak ada klaim untuk item ini", "error");
return;
}
const reason = prompt(
"❌ Alasan Penolakan (opsional):\n\nContoh: Bukan barang saya, warna berbeda, dll."
);
if (reason === null) return; // User cancelled
const confirmMsg = `❌ Konfirmasi Penolakan
Yakin menolak barang "${item.name}"?
${reason ? `Alasan: ${reason}` : "(Tanpa alasan)"}
Barang akan kembali dalam status pencarian.`;
if (!confirm(confirmMsg)) return;
try {
state.setLoading(true);
// ✅ ENDPOINT YANG BENAR - menggunakan claim ID
await ApiUtils.post(`/api/user/claims/${item.direct_claim_id}/respond`, {
action: "reject",
});
showToast(
"❌ Barang ditolak. Status kembali dalam pencarian.",
"success"
);
loadData();
} catch (error) {
console.error("Error rejecting item:", error);
showToast("Gagal menolak: " + error.message, "error");
} finally {
state.setLoading(false);
}
};
return (
<div className="bg-gradient-to-br from-slate-800 to-slate-900 rounded-xl p-6 shadow-xl border border-slate-700">
<div className="flex justify-between items-center mb-6">
<div>
<h2 className="text-xl font-semibold text-white">
📦 Barang yang Dilaporkan Orang Lain Menemukan
</h2>
<p className="text-slate-400 text-sm mt-1">
Barang hilang Anda yang dilaporkan orang lain menemukan - butuh
konfirmasi Anda
</p>
</div>
<button
onClick={() => loadData()}
className="px-4 py-2 bg-gradient-to-r from-blue-600 to-blue-700 text-white rounded-lg font-semibold hover:from-blue-700 hover:to-blue-800 transition shadow-lg"
>
🔄 Refresh
</button>
</div>
{/* Stats Cards */}
<div className="grid grid-cols-3 gap-4 mb-6">
<div className="bg-yellow-500/10 border border-yellow-500/30 p-4 rounded-lg text-center">
<div className="text-yellow-400 font-bold text-2xl">
{
myFoundItems.filter((i) => i.status === "pending_verification")
.length
}
</div>
<div className="text-slate-400 text-sm">Menunggu Konfirmasi</div>
</div>
<div className="bg-green-500/10 border border-green-500/30 p-4 rounded-lg text-center">
<div className="text-green-400 font-bold text-2xl">
{myFoundItems.filter((i) => i.status === "verified").length}
</div>
<div className="text-slate-400 text-sm">Diverifikasi</div>
</div>
<div className="bg-slate-500/10 border border-slate-500/30 p-4 rounded-lg text-center">
<div className="text-slate-400 font-bold text-2xl">
{myFoundItems.length}
</div>
<div className="text-slate-400 text-sm">Total</div>
</div>
</div>
{loading ? (
<div className="text-center py-12 text-slate-400">
<div className="text-6xl mb-4">⏳</div>
<p>Memuat data...</p>
</div>
) : myFoundItems.length > 0 ? (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{myFoundItems.map((item) => {
const isPendingVerification =
item.status === "pending_verification";
const isVerified = item.status === "verified";
return (
<div
key={item.id}
className={`bg-gradient-to-br from-slate-700 to-slate-800 border-2 rounded-xl p-5 hover:shadow-xl transition-all ${
isPendingVerification
? "border-yellow-500/50 hover:border-yellow-500 shadow-yellow-500/20"
: isVerified
? "border-green-500/50 hover:border-green-500"
: "border-slate-600 hover:border-blue-500"
}`}
>
{/* Header */}
<div className="flex justify-between items-start mb-4 border-b border-slate-600 pb-3">
<div className="flex-1">
<h3 className="text-lg font-bold text-white">
{item.name}
</h3>
{item.category_name && (
<p className="text-xs text-slate-400 mt-1">
🏷️ {item.category_name}
</p>
)}
</div>
<span
className={`px-3 py-1 rounded-full text-xs font-bold uppercase tracking-wider ${
isPendingVerification
? "bg-yellow-500/20 text-yellow-400 border border-yellow-500/50 animate-pulse"
: isVerified
? "bg-green-500/20 text-green-400 border border-green-500/50"
: Helpers.getStatusBadgeClass(item.status)
}`}
>
{isPendingVerification
? "⏳ Menunggu"
: isVerified
? "✅ Verified"
: item.status}
</span>
</div>
{/* Item Details */}
<div className="space-y-2 mb-4">
{item.color && (
<div className="flex items-center gap-2 text-sm text-slate-300">
<span className="text-blue-400">🎨</span>
<span>Warna: {item.color}</span>
</div>
)}
{item.location && (
<div className="flex items-center gap-2 text-sm text-slate-300">
<span className="text-blue-400">📍</span>
<span>{item.location}</span>
</div>
)}
{item.date_found && (
<div className="flex items-center gap-2 text-sm text-slate-300">
<span className="text-blue-400">📅</span>
<span>{Helpers.formatDate(item.date_found)}</span>
</div>
)}
{item.reporter_name && (
<div className="flex items-center gap-2 text-sm text-slate-300">
<span className="text-blue-400">👤</span>
<span>Ditemukan oleh: {item.reporter_name}</span>
</div>
)}
</div>
{/* Description */}
{item.description && (
<div className="bg-slate-900/50 p-3 rounded-lg border border-slate-600 mb-4">
<strong className="text-xs text-slate-400 uppercase block mb-1">
Deskripsi:
</strong>
<p className="text-sm text-slate-300 line-clamp-3">
{item.description}
</p>
</div>
)}
{/* Pending Verification Alert */}
{isPendingVerification && (
<div className="bg-yellow-500/10 border-2 border-yellow-500/30 p-3 rounded-lg mb-4">
<div className="flex items-center gap-2 mb-2">
<span className="text-xl">⚠️</span>
<strong className="text-yellow-400">
Konfirmasi Diperlukan
</strong>
</div>
<p className="text-xs text-slate-300">
Seseorang menemukan barang yang sesuai dengan laporan
Anda. Silakan konfirmasi apakah ini benar barang Anda.
</p>
</div>
)}
{/* Verified Alert */}
{isVerified && (
<div className="bg-green-500/10 border-2 border-green-500/30 p-3 rounded-lg mb-4">
<div className="flex items-center gap-2 mb-2">
<span className="text-xl">✅</span>
<strong className="text-green-400">
Sudah Diverifikasi
</strong>
</div>
<p className="text-xs text-slate-300">
Anda telah mengonfirmasi ini barang Anda. Koordinasikan
pengambilan.
</p>
</div>
)}
{/* Action Buttons */}
<div className="flex gap-2">
{isPendingVerification && (
<>
{/* Approve Button */}
<button
onClick={() => handleApproveItem(item)}
className="flex-1 px-3 py-2 bg-gradient-to-r from-green-600 to-green-700 text-white text-sm rounded-lg hover:from-green-700 hover:to-green-800 transition shadow-lg font-semibold flex items-center justify-center gap-1"
>
<span>✅</span> Terima
</button>
{/* Reject Button */}
<button
onClick={() => handleRejectItem(item)}
className="flex-1 px-3 py-2 bg-gradient-to-r from-red-600 to-red-700 text-white text-sm rounded-lg hover:from-red-700 hover:to-red-800 transition shadow-lg font-semibold flex items-center justify-center gap-1"
>
<span>❌</span> Tolak
</button>
</>
)}
{/* Detail Button */}
<button
onClick={() =>
handlers.handleViewDetail &&
handlers.handleViewDetail(item)
}
className={`${
isPendingVerification ? "px-3" : "flex-1"
} py-2 bg-gradient-to-r from-blue-600 to-blue-700 text-white text-sm rounded-lg hover:from-blue-700 hover:to-blue-800 transition shadow-lg flex items-center justify-center gap-1`}
>
<span>📋</span> {isPendingVerification ? "" : "Detail"}
</button>
</div>
</div>
);
})}
</div>
) : (
<div className="text-center py-12 text-slate-400">
<div className="text-6xl mb-4">📭</div>
<p>Belum ada barang yang dilaporkan ditemukan</p>
<p className="text-sm mt-2">
Ketika seseorang menemukan barang Anda, akan muncul di sini
</p>
</div>
)}
</div>
);
};
window.MyFoundItemsTab = MyFoundItemsTab;
+321
View File
@@ -0,0 +1,321 @@
const MyLostItemsTab = ({ state, handlers }) => {
const { myLostItems, myClaims, loading } = state;
const lostItemsWithClaims = myLostItems.filter((item) => {
if (!item.direct_claim_id || !item.direct_claim_status) {
return false;
}
const excludedStatuses = ["completed", "resolved", "case_closed"];
if (excludedStatuses.includes(item.status)) {
return false;
}
if (excludedStatuses.includes(item.direct_claim_status)) {
return false;
}
const pendingStatuses = ["waiting_owner", "verified", "claimed", "pending"];
return pendingStatuses.includes(item.direct_claim_status);
});
const regularClaims = myClaims || [];
// UPDATED: Menambahkan parameter isMyLostItem (default true)
const getStatusBadge = (status, isMyLostItem = true) => {
const badges = {
active: { color: "bg-blue-500", text: "📍 Masih Hilang", emoji: "😢" },
claimed: {
color: "bg-yellow-500",
text: "⏳ Menunggu Persetujuan Anda",
emoji: "🤔",
},
found: { color: "bg-green-500", text: "✅ Ditemukan", emoji: "🎉" },
completed: {
color: "bg-purple-600",
text: "✅ Selesai - Barang Diterima",
emoji: "🎉",
},
resolved: {
color: "bg-purple-600",
text: "✅ Selesai - Dikembalikan",
emoji: "🎉",
},
pending: {
color: "bg-yellow-500",
text: "⏳ Menunggu Verifikasi",
emoji: "⏳",
},
approved: { color: "bg-green-500", text: "✅ Disetujui", emoji: "✅" },
rejected: { color: "bg-red-500", text: "❌ Ditolak", emoji: "❌" },
waiting_owner: {
color: "bg-orange-500",
// LOGIC FIX: Membedakan teks berdasarkan kepemilikan
text: isMyLostItem ? "⏳ Menunggu Anda" : "⏳ Menunggu Owner",
emoji: "👤",
},
verified: {
color: "bg-green-600",
text: "✅ Terverifikasi",
emoji: "✅",
},
};
return (
badges[status] || {
color: "bg-gray-500",
text: status,
emoji: "❓",
}
);
};
if (loading) {
return (
<div className="text-center py-12 text-slate-400">
<div className="animate-spin text-4xl mb-4">⏳</div>
<p>Memuat data klaim...</p>
</div>
);
}
return (
<div className="space-y-8">
{/* Header Section */}
<div className="flex justify-between items-center bg-gradient-to-r from-blue-600 to-blue-700 rounded-xl p-4 shadow-lg">
<div>
<h3 className="text-xl font-bold text-white">
😢 Barang Hilang Saya
</h3>
<p className="text-blue-100 text-sm">
Kelola laporan kehilangan barang Anda
</p>
</div>
<button
onClick={() => state.setShowReportLostModal(true)}
className="px-6 py-3 bg-white text-blue-600 rounded-lg font-bold hover:bg-blue-50 transition shadow-xl hover:scale-105 transform flex items-center gap-2"
>
<span className="text-xl">➕</span>
Lapor Kehilangan
</button>
</div>
{/* SECTION 1: Barang Hilang Saya (Anda sebagai Owner) */}
{lostItemsWithClaims.length > 0 && (
<div className="bg-slate-800/50 backdrop-blur-sm rounded-2xl border border-slate-700 p-6">
<h3 className="text-2xl font-bold text-white mb-4 flex items-center gap-3">
<span className="text-3xl">😢</span>
Barang Hilang Saya
<span className="bg-orange-500/20 text-orange-400 text-sm px-3 py-1 rounded-full border border-orange-500/30">
{lostItemsWithClaims.length} item dengan klaim pending
</span>
</h3>
<p className="text-slate-400 mb-6">
Ada yang menemukan barang Anda! Silakan approve atau reject klaim
mereka.
</p>
<div className="space-y-4">
{lostItemsWithClaims.map((lostItem) => {
// Context: True (Ini barang saya)
const statusBadge = getStatusBadge(
lostItem.direct_claim_status || lostItem.status,
true
);
const isWaitingOwner =
lostItem.direct_claim_status === "waiting_owner";
const isVerified =
lostItem.direct_claim_status === "verified" ||
lostItem.status === "found";
return (
<div
key={lostItem.id}
className={`bg-slate-900/80 rounded-xl border ${
isWaitingOwner
? "border-orange-500/50 shadow-lg shadow-orange-500/20"
: "border-slate-700"
} p-6 hover:shadow-xl transition-all`}
>
<div className="flex justify-between items-start mb-4">
<div className="flex-1">
<div className="flex items-center gap-3 mb-2">
<h4 className="text-xl font-bold text-white">
{lostItem.name}
</h4>
<span
className={`${statusBadge.color} text-white text-xs px-3 py-1 rounded-full font-medium`}
>
{statusBadge.emoji} {statusBadge.text}
</span>
</div>
<div className="space-y-1 text-sm text-slate-400">
<p>
<span className="font-medium">📦 Kategori:</span>{" "}
{lostItem.category}
</p>
{lostItem.color && (
<p>
<span className="font-medium">🎨 Warna:</span>{" "}
{lostItem.color}
</p>
)}
<p>
<span className="font-medium">📍 Lokasi Hilang:</span>{" "}
{lostItem.location}
</p>
<p>
<span className="font-medium">
📅 Tanggal Hilang:
</span>{" "}
{new Date(lostItem.date_lost).toLocaleDateString(
"id-ID"
)}
</p>
<p className="mt-2">
<span className="font-medium">📝 Deskripsi:</span>{" "}
{lostItem.description}
</p>
</div>
</div>
</div>
{isWaitingOwner && (
<div className="bg-orange-900/30 border border-orange-500/30 rounded-lg p-4 mt-4">
<p className="text-orange-300 font-semibold mb-3 flex items-center gap-2">
<span className="text-2xl">🔔</span>
Ada yang mengaku menemukan barang ini!
</p>
<p className="text-sm text-slate-300 mb-4">
Seseorang telah mengajukan klaim untuk barang ini.
Silakan putuskan apakah ini benar barang Anda.
</p>
<div className="flex gap-3">
<button
onClick={() =>
handlers.handleUserRespondClaim(
lostItem.direct_claim_id,
"approve"
)
}
className="flex-1 bg-gradient-to-r from-green-600 to-green-700 text-white px-4 py-3 rounded-lg font-semibold hover:from-green-700 hover:to-green-800 transition shadow-lg shadow-green-500/30"
>
✅ Approve (Ini barang saya!)
</button>
<button
onClick={() =>
handlers.handleUserRespondClaim(
lostItem.direct_claim_id,
"reject"
)
}
className="flex-1 bg-gradient-to-r from-red-600 to-red-700 text-white px-4 py-3 rounded-lg font-semibold hover:from-red-700 hover:to-red-800 transition shadow-lg shadow-red-500/30"
>
❌ Reject (Bukan barang saya)
</button>
</div>
</div>
)}
{isVerified && (
<div className="bg-green-900/30 border border-green-500/30 rounded-lg p-4 mt-4">
<p className="text-green-300 font-semibold mb-3 flex items-center gap-2">
<span className="text-2xl">✅</span>
Klaim Disetujui!
</p>
<p className="text-sm text-slate-300 mb-4">
Anda telah menyetujui bahwa penemu menemukan barang
Anda. Koordinasikan pengambilan barang, lalu konfirmasi
setelah menerima barang.
</p>
<button
onClick={() =>
handlers.handleUserCompleteCase(
lostItem.direct_claim_id
)
}
className="w-full bg-gradient-to-r from-purple-600 to-purple-700 text-white px-4 py-3 rounded-lg font-semibold hover:from-purple-700 hover:to-purple-800 transition shadow-lg shadow-purple-500/30"
>
🎉 Konfirmasi: Saya Sudah Terima Barang
</button>
</div>
)}
</div>
);
})}
</div>
</div>
)}
{/* SECTION 2: Klaim Barang yang Saya Ajukan (Anda sebagai Penemu) */}
{regularClaims.length > 0 && (
<div className="bg-slate-800/50 backdrop-blur-sm rounded-2xl border border-slate-700 p-6">
<h3 className="text-2xl font-bold text-white mb-4 flex items-center gap-3">
<span className="text-3xl">🤝</span>
Klaim Barang yang Saya Ajukan
<span className="bg-blue-500/20 text-blue-400 text-sm px-3 py-1 rounded-full border border-blue-500/30">
{regularClaims.length} klaim
</span>
</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{regularClaims.map((claim) => {
// Context: False (Ini klaim saya ke orang lain/owner)
// UPDATED: Pass false agar text menjadi "Menunggu Owner"
const statusBadge = getStatusBadge(claim.status, false);
return (
<div
key={claim.id}
className="bg-slate-900/80 rounded-xl border border-slate-700 p-5 hover:shadow-xl transition-all"
>
<div className="flex items-start justify-between mb-3">
<h4 className="text-lg font-bold text-white flex-1">
{claim.item_name}
</h4>
<span
className={`${statusBadge.color} text-white text-xs px-3 py-1 rounded-full font-medium whitespace-nowrap ml-2`}
>
{statusBadge.emoji} {statusBadge.text}
</span>
</div>
<div className="space-y-2 text-sm text-slate-400">
<p>
<span className="font-medium">📝 Deskripsi:</span>{" "}
{claim.description}
</p>
<p>
<span className="font-medium">📞 Kontak:</span>{" "}
{claim.contact}
</p>
<p>
<span className="font-medium">📅 Tanggal Klaim:</span>{" "}
{new Date(claim.created_at).toLocaleDateString("id-ID")}
</p>
{claim.notes && (
<p className="text-yellow-400 mt-2">
<span className="font-medium">💬 Catatan:</span>{" "}
{claim.notes}
</p>
)}
</div>
</div>
);
})}
</div>
</div>
)}
{lostItemsWithClaims.length === 0 && regularClaims.length === 0 && (
<div className="text-center py-16 bg-slate-800/30 rounded-2xl border border-slate-700">
<div className="text-6xl mb-4">🔭</div>
<h3 className="text-xl font-bold text-white mb-2">Belum Ada Klaim</h3>
<p className="text-slate-400 mb-6">
Anda belum memiliki klaim atau barang hilang dengan klaim pending.
</p>
<button
onClick={() => state.setShowReportLostModal(true)}
className="px-6 py-3 bg-gradient-to-r from-blue-600 to-blue-700 text-white rounded-lg font-semibold hover:from-blue-700 hover:to-blue-800 transition shadow-lg"
>
➕ Lapor Kehilangan Sekarang
</button>
</div>
)}
</div>
);
};
window.MyLostItemsTab = MyLostItemsTab;
@@ -0,0 +1,131 @@
const PublicLostItemsTab = ({ state, handlers }) => {
const { publicLostItems, user } = state;
const getStatusDisplay = (lostItem) => {
const statusMap = {
active: { label: "Aktif", color: "bg-green-600 text-white" },
claimed: { label: "Sedang Diklaim", color: "bg-yellow-600 text-white" },
resolved: { label: "Selesai", color: "bg-blue-600 text-white" },
};
return (
statusMap[lostItem.status] || {
label: lostItem.status,
color: "bg-gray-600 text-white",
}
);
};
const shouldShowFoundButton = (lostItem) => {
return lostItem.user_id !== user.id && lostItem.status === "active";
};
const shouldShowClaimedInfo = (lostItem) => {
return lostItem.status === "claimed" || lostItem.status === "resolved";
};
return (
<div className="bg-slate-800 rounded-xl p-6 border border-slate-700">
<h3 className="text-2xl font-bold text-white mb-4">
😢 Barang Hilang (Dari Pengguna Lain)
</h3>
<p className="text-slate-400 mb-6">
Jika Anda menemukan barang yang cocok, klik "Saya Menemukan!" untuk
memberitahu pemiliknya
</p>
{publicLostItems.length === 0 ? (
<div className="text-center py-12 text-slate-400">
<p className="text-6xl mb-4">🔭</p>
<p className="text-xl">Tidak ada barang hilang saat ini</p>
</div>
) : (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{publicLostItems.map((lostItem) => {
const statusDisplay = getStatusDisplay(lostItem);
const showFoundButton = shouldShowFoundButton(lostItem);
const showClaimedInfo = shouldShowClaimedInfo(lostItem);
return (
<div
key={lostItem.id}
className={`bg-slate-900/50 rounded-lg p-5 border transition ${
lostItem.status === "resolved"
? "border-blue-500/50 opacity-75"
: "border-slate-700 hover:border-blue-500"
}`}
>
<div className="flex justify-between items-start mb-3">
<div>
<h4 className="font-bold text-white text-lg">
{lostItem.name}
</h4>
<p className="text-sm text-slate-400">
{lostItem.category}
</p>
</div>
<span
className={`text-xs px-2 py-1 rounded-full ${statusDisplay.color}`}
>
{statusDisplay.label}
</span>
</div>
<div className="space-y-2 mb-4 text-sm text-slate-300">
<p>
<strong>📍 Lokasi:</strong>{" "}
{lostItem.location || "Tidak disebutkan"}
</p>
<p>
<strong>🎨 Warna:</strong>{" "}
{lostItem.color || "Tidak disebutkan"}
</p>
<p>
<strong>📅 Tanggal Hilang:</strong>{" "}
{new Date(lostItem.date_lost).toLocaleDateString("id-ID")}
</p>
<p>
<strong>📝 Deskripsi:</strong> {lostItem.description}
</p>
</div>
{/* PERBAIKAN: Ubah dari handleOpenDirectClaimModal ke handleOpenFoundOption */}
{showFoundButton && (
<button
onClick={() => handlers.handleOpenFoundOption(lostItem)}
className="w-full bg-gradient-to-r from-green-600 to-emerald-600 hover:from-green-700 hover:to-emerald-700 text-white px-4 py-3 rounded-lg font-semibold transition shadow-lg"
>
🎉 Saya Menemukan Barang Ini!
</button>
)}
{lostItem.status === "claimed" && (
<div className="bg-yellow-900/30 border border-yellow-700/50 rounded-lg p-3 text-center">
<p className="text-yellow-400 text-sm">
⏳ Sedang menunggu konfirmasi pemilik
</p>
</div>
)}
{lostItem.status === "resolved" && (
<div className="bg-blue-900/30 border border-blue-700/50 rounded-lg p-3 text-center">
<p className="text-blue-400 text-sm font-semibold">
✅ Barang sudah dikembalikan ke pemilik
</p>
</div>
)}
{lostItem.user_id === user.id && (
<div className="bg-purple-900/30 border border-purple-700/50 rounded-lg p-3 text-center">
<p className="text-purple-400 text-sm">
📋 Ini laporan kehilangan Anda
</p>
</div>
)}
</div>
);
})}
</div>
)}
</div>
);
};