Basdat/web/js/pages/admin/tabs/ItemsTab.js
2025-12-20 00:01:08 +07:00

267 lines
10 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// assets/js/pages/admin/tabs/ItemsTab.js
const ItemsTab = ({ state, handlers }) => {
const {
showExpired,
setShowExpired,
itemSearchTerm,
setItemSearchTerm,
itemStatusFilter,
setItemStatusFilter,
itemCategoryFilter,
setItemCategoryFilter,
filteredItems,
items,
showCreateItemModal,
setShowCreateItemModal,
photoPreview,
itemPage,
itemTotalPages,
itemTotalRecords,
loading,
} = state;
const {
handleViewItemDetail,
handleCreateItem,
handleEditItemClick,
handleDeleteItem,
handlePhotoChange,
loadItems,
} = handlers;
// ✅ Gunakan useEffect untuk reload data saat Filter / Page berubah
// Kita menggunakan debounce (tunda) untuk search agar tidak spam API
React.useEffect(() => {
const timer = setTimeout(() => {
// Reset ke halaman 1 jika filter berubah
// Jika hanya halaman yang berubah, load halaman tersebut
loadItems(itemPage);
}, 500);
return () => clearTimeout(timer);
}, [
itemPage,
itemSearchTerm,
itemStatusFilter,
itemCategoryFilter,
showExpired,
]);
// Reset page ke 1 jika filter berubah (kecuali itemPage itu sendiri)
React.useEffect(() => {
state.setItemPage(1);
}, [itemSearchTerm, itemStatusFilter, itemCategoryFilter, showExpired]);
return (
<div className="bg-gradient-to-br from-slate-800 to-slate-900 rounded-xl shadow-xl border border-slate-700">
<div className="p-6 border-b border-slate-700">
<div className="flex justify-between items-center mb-4">
<h2 className="text-xl font-semibold text-white">
{showExpired ? "📦 Barang Expired" : "📦 Kelola Barang"}
</h2>
<div className="flex gap-3">
<button
onClick={() => setShowCreateItemModal(true)}
className="px-4 py-2 bg-gradient-to-r from-green-600 to-green-700 text-white rounded-lg font-semibold hover:from-green-700 hover:to-green-800 transition shadow-lg"
>
Tambah Barang
</button>
<button
onClick={() => setShowExpired(false)}
className={`px-4 py-2 rounded-lg font-semibold transition ${
!showExpired
? "bg-gradient-to-r from-blue-600 to-blue-700 text-white shadow-lg"
: "bg-slate-700 text-slate-300 hover:bg-slate-600"
}`}
>
📦 Aktif
</button>
<button
onClick={() => setShowExpired(true)}
className={`px-4 py-2 rounded-lg font-semibold transition ${
showExpired
? "bg-gradient-to-r from-red-600 to-red-700 text-white shadow-lg"
: "bg-slate-700 text-slate-300 hover:bg-slate-600"
}`}
>
Expired
</button>
</div>
</div>
<div className="flex gap-3 flex-wrap">
<input
type="text"
placeholder="Cari barang (nama/lokasi)..."
value={itemSearchTerm}
onChange={(e) => setItemSearchTerm(e.target.value)}
className="flex-1 min-w-[200px] px-4 py-2 bg-slate-700 border-2 border-slate-600 rounded-xl text-white placeholder-slate-400 focus:border-blue-500 focus:outline-none"
/>
<select
value={itemStatusFilter}
onChange={(e) => setItemStatusFilter(e.target.value)}
className="px-4 py-2 bg-slate-700 border-2 border-slate-600 rounded-xl text-white focus:border-blue-500 focus:outline-none"
>
<option value="">Semua Status</option>
<option value="unclaimed">Unclaimed</option>
<option value="pending_claim">Pending Claim</option>
<option value="verified">Verified</option>
<option value="case_closed">Case Closed</option>
<option value="expired">Expired</option>
</select>
<select
value={itemCategoryFilter}
onChange={(e) => setItemCategoryFilter(e.target.value)}
className="px-4 py-2 bg-slate-700 border-2 border-slate-600 rounded-xl text-white focus:border-blue-500 focus:outline-none"
>
<option value="">Semua Kategori</option>
{state.categories &&
state.categories.map((cat) => (
<option key={cat.id} value={cat.slug || cat.id}>
{" "}
{/* Sesuaikan value dengan slug/id dari DB */}
{cat.name}
</option>
))}
</select>
</div>
</div>
<div className="p-6">
{loading ? (
<div className="text-center py-12 text-slate-400">
<div className="text-6xl mb-4 animate-pulse"></div>
<p>Memuat data...</p>
</div>
) : (
<>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{/* ✅ PERUBAHAN UTAMA:
Kita me-map 'items' secara langsung.
Tidak perlu filteredItems.filter() lagi karena backend sudah mengirimkan data
yang sudah terfilter (hanya 10 biji) sesuai halaman aktif.
*/}
{items.map((item) => (
<div
key={item.id}
className={`bg-gradient-to-br from-slate-700 to-slate-800 border-2 rounded-xl overflow-hidden hover:shadow-2xl transition-all ${
item.status === "expired"
? "border-red-500/50 hover:border-red-500"
: "border-slate-600 hover:border-blue-500 hover:shadow-blue-500/20"
}`}
>
{item.status === "expired" && (
<div className="bg-red-600/90 text-white text-center py-1 text-xs font-bold">
EXPIRED
</div>
)}
<img
src={
item.photo_url ||
"https://via.placeholder.com/280x200?text=No+Image"
}
alt={item.name}
className={`w-full h-48 object-cover cursor-pointer ${
item.status === "expired" ? "opacity-50" : ""
}`}
onClick={() => handleViewItemDetail(item)}
onError={(e) =>
(e.target.src =
"https://via.placeholder.com/280x200?text=No+Image")
}
/>
<div className="p-4">
<h3 className="text-lg font-semibold text-white mb-2">
{item.name}
</h3>
<div className="flex flex-col gap-1 text-sm text-slate-400 mb-3">
<span>📍 {item.location}</span>
<span>📅 {Helpers.formatDate(item.date_found)}</span>
<span>👤 {item.reporter_name}</span>
</div>
<span
className={`inline-block px-3 py-1 rounded-full text-xs font-semibold mb-3 ${Helpers.getStatusBadgeClass(
item.status
)}`}
>
{item.status}
</span>
<div className="flex gap-2">
<button
onClick={() => handleViewItemDetail(item)}
className="flex-1 px-3 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"
>
📋 Detail
</button>
{/* Tombol Edit/Hapus hanya muncul jika bukan case_closed */}
{item.status !== "case_closed" && (
<>
<button
onClick={() => handleEditItemClick(item)}
className="flex-1 px-3 py-2 bg-gradient-to-r from-yellow-600 to-yellow-700 text-white text-sm rounded-lg hover:from-yellow-700 hover:to-yellow-800 transition shadow-lg"
>
Edit
</button>
<button
onClick={() => {
if (
confirm(
`⚠️ Yakin ingin menghapus "${item.name}"?`
)
) {
handleDeleteItem(item.id);
}
}}
className="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"
>
🗑
</button>
</>
)}
</div>
</div>
</div>
))}
</div>
{items.length === 0 && (
<div className="text-center py-12 text-slate-400">
<div className="text-6xl mb-4">{showExpired ? "⚠️" : "📦"}</div>
<p>
{showExpired
? "Belum ada barang expired"
: "Tidak ada barang ditemukan"}
</p>
</div>
)}
{/* ✅ KOMPONEN PAGINATION */}
{items.length > 0 && (
<Pagination
currentPage={itemPage}
totalPages={itemTotalPages}
totalRecords={itemTotalRecords}
onPageChange={(page) => state.setItemPage(page)}
itemsPerPage={10}
/>
)}
</>
)}
</div>
<CreateItemModal state={state} handlers={handlers} />
<EditItemModal state={state} handlers={handlers} />
</div>
);
};