Initial commit - Lost and Found Revisi
This commit is contained in:
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user