Initial commit - Lost and Found Revisi
This commit is contained in:
@@ -0,0 +1,187 @@
|
||||
// assets/js/pages/admin/modals/ArchiveDetailModal.js
|
||||
|
||||
const ArchiveDetailModal = ({ isOpen, onClose, archive }) => (
|
||||
<Modal isOpen={isOpen} onClose={onClose} title="Detail Arsip" size="large">
|
||||
{archive && (
|
||||
<div>
|
||||
{/* Image */}
|
||||
<img
|
||||
src={
|
||||
archive.photo_url ||
|
||||
"https://via.placeholder.com/600x400?text=No+Image"
|
||||
}
|
||||
alt={archive.name}
|
||||
className="w-full h-64 object-cover rounded-xl mb-4 border-2 border-slate-600"
|
||||
onError={(e) =>
|
||||
(e.target.src = "https://via.placeholder.com/600x400?text=No+Image")
|
||||
}
|
||||
/>
|
||||
|
||||
{/* Header Info */}
|
||||
<div className="bg-green-500/10 border-2 border-green-500/30 p-4 rounded-xl mb-6">
|
||||
<h3 className="text-xl font-bold text-white mb-2">{archive.name}</h3>
|
||||
<div className="grid grid-cols-2 gap-3 text-sm text-slate-300">
|
||||
<div>
|
||||
<strong className="text-green-400">Status:</strong>{" "}
|
||||
<span className="px-3 py-1 rounded-full text-xs font-semibold bg-green-500/20 text-green-400 border border-green-500/50">
|
||||
✅ CASE CLOSED
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<strong className="text-green-400">Kategori:</strong>{" "}
|
||||
{archive.category || "-"}
|
||||
</div>
|
||||
<div>
|
||||
<strong className="text-green-400">Lokasi Ditemukan:</strong>{" "}
|
||||
{archive.location}
|
||||
</div>
|
||||
<div>
|
||||
<strong className="text-green-400">Tanggal Ditemukan:</strong>{" "}
|
||||
{Helpers.formatDate(archive.date_found)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Description Sections */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6">
|
||||
<div className="bg-slate-900/50 p-4 rounded-xl border border-slate-600">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<span className="text-xl">📝</span>
|
||||
<strong className="text-blue-400">Deskripsi Umum</strong>
|
||||
</div>
|
||||
<p className="text-sm text-slate-300 leading-relaxed">
|
||||
{archive.description || "Tidak ada deskripsi"}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="bg-yellow-500/10 p-4 rounded-xl border border-yellow-500/30">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<span className="text-xl">🔒</span>
|
||||
<strong className="text-yellow-400">Ciri Khusus Rahasia</strong>
|
||||
</div>
|
||||
<p className="text-sm text-slate-300 leading-relaxed">
|
||||
{archive.secret_details || "Tidak ada deskripsi rahasia"}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Reporter Info */}
|
||||
<div className="bg-slate-900/50 p-4 rounded-xl border border-slate-600 mb-6">
|
||||
<strong className="text-slate-300 block mb-2">
|
||||
Informasi Pelapor:
|
||||
</strong>
|
||||
<div className="grid grid-cols-2 gap-3 text-sm text-slate-300">
|
||||
<div>
|
||||
<strong className="text-blue-400">Nama:</strong>{" "}
|
||||
{archive.reporter_name || "Tidak ada data"}
|
||||
</div>
|
||||
<div>
|
||||
<strong className="text-blue-400">Kontak:</strong>{" "}
|
||||
{archive.reporter_contact || "Tidak ada data"}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Case Closed Info */}
|
||||
<div className="bg-green-500/10 border-2 border-green-500/30 p-4 rounded-xl mb-6">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<span className="text-3xl">📋</span>
|
||||
<strong className="text-green-400 text-lg">
|
||||
Informasi Penutupan Case
|
||||
</strong>
|
||||
</div>
|
||||
<div className="space-y-2 text-sm text-slate-300">
|
||||
<div>
|
||||
No. Berita Acara:{" "}
|
||||
<strong className="text-white text-lg">
|
||||
{archive.berita_acara_no || "-"}
|
||||
</strong>
|
||||
</div>
|
||||
{archive.case_closed_at && (
|
||||
<div>
|
||||
Tanggal Ditutup:{" "}
|
||||
<strong>
|
||||
{Helpers.formatDateTime(archive.case_closed_at)}
|
||||
</strong>
|
||||
</div>
|
||||
)}
|
||||
{archive.case_closed_by_name && (
|
||||
<div>
|
||||
Ditutup Oleh: <strong>{archive.case_closed_by_name}</strong>
|
||||
</div>
|
||||
)}
|
||||
{archive.claimer_name && (
|
||||
<div>
|
||||
Diterima Oleh (Penerima):{" "}
|
||||
<strong className="text-green-400">
|
||||
{archive.claimer_name}
|
||||
</strong>
|
||||
</div>
|
||||
)}
|
||||
{archive.claimer_contact && (
|
||||
<div>
|
||||
Kontak Penerima: <strong>{archive.claimer_contact}</strong>
|
||||
</div>
|
||||
)}
|
||||
{archive.case_closed_notes && (
|
||||
<div className="mt-3 pt-3 border-t border-green-500/30">
|
||||
<strong className="block mb-1">Catatan Penutupan:</strong>
|
||||
<p className="text-slate-300 bg-slate-900/50 p-3 rounded-lg">
|
||||
{archive.case_closed_notes}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{archive.bukti_serah_terima && (
|
||||
<a
|
||||
href={archive.bukti_serah_terima}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="inline-block mt-3 px-4 py-2 bg-blue-600 text-white text-sm rounded-lg hover:bg-blue-700 transition shadow-lg"
|
||||
>
|
||||
📄 Lihat Bukti Serah Terima
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Timeline */}
|
||||
<div className="bg-slate-900/50 p-4 rounded-xl border border-slate-600">
|
||||
<strong className="text-slate-300 block mb-3">Timeline:</strong>
|
||||
<div className="space-y-2 text-sm">
|
||||
<div className="flex items-start gap-3">
|
||||
<span className="text-blue-400 mt-1">📅</span>
|
||||
<div>
|
||||
<strong className="text-slate-300">Ditemukan:</strong>
|
||||
<p className="text-slate-400">
|
||||
{Helpers.formatDateTime(archive.date_found)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{archive.created_at && (
|
||||
<div className="flex items-start gap-3">
|
||||
<span className="text-blue-400 mt-1">📝</span>
|
||||
<div>
|
||||
<strong className="text-slate-300">Dilaporkan:</strong>
|
||||
<p className="text-slate-400">
|
||||
{Helpers.formatDateTime(archive.created_at)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{archive.case_closed_at && (
|
||||
<div className="flex items-start gap-3">
|
||||
<span className="text-green-400 mt-1">✅</span>
|
||||
<div>
|
||||
<strong className="text-slate-300">Ditutup:</strong>
|
||||
<p className="text-slate-400">
|
||||
{Helpers.formatDateTime(archive.case_closed_at)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Modal>
|
||||
);
|
||||
@@ -0,0 +1,171 @@
|
||||
// assets/js/pages/admin/modals/ClaimDetailModalAdmin.js
|
||||
|
||||
const ClaimDetailModalAdmin = ({ isOpen, onClose, claim }) => (
|
||||
<Modal isOpen={isOpen} onClose={onClose} title="Detail Klaim" size="large">
|
||||
{claim && (
|
||||
<div>
|
||||
{/* Header Info */}
|
||||
<div className="bg-blue-500/10 border-2 border-blue-500/30 p-4 rounded-xl mb-6">
|
||||
<h3 className="text-xl font-bold text-white mb-2">
|
||||
{claim.item_name}
|
||||
</h3>
|
||||
<div className="grid grid-cols-2 gap-3 text-sm text-slate-300">
|
||||
<div>
|
||||
<strong className="text-blue-400">Pengklaim:</strong>{" "}
|
||||
{claim.user_name}
|
||||
</div>
|
||||
<div>
|
||||
<strong className="text-blue-400">Kontak:</strong> {claim.contact}
|
||||
</div>
|
||||
<div>
|
||||
<strong className="text-blue-400">Status:</strong>{" "}
|
||||
<span
|
||||
className={`px-3 py-1 rounded-full text-xs font-semibold ${Helpers.getStatusBadgeClass(
|
||||
claim.status
|
||||
)}`}
|
||||
>
|
||||
{claim.status}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<strong className="text-blue-400">Tanggal:</strong>{" "}
|
||||
{Helpers.formatDateTime(claim.created_at)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Comparison Section */}
|
||||
<div className="grid grid-cols-2 gap-6 mb-6">
|
||||
<div className="bg-yellow-500/10 p-4 rounded-xl border border-yellow-500/30">
|
||||
<h4 className="font-semibold mb-3 text-yellow-400 flex items-center gap-2">
|
||||
<span>🔒</span>
|
||||
Deskripsi Rahasia Barang
|
||||
</h4>
|
||||
<p className="text-sm text-slate-300 font-medium">
|
||||
{claim.item_secret_details || "Tidak ada ciri khusus rahasia"}
|
||||
</p>
|
||||
<div className="mt-3 pt-3 border-t border-yellow-500/30">
|
||||
<span className="text-xs text-slate-500 uppercase">
|
||||
Deskripsi Umum:
|
||||
</span>
|
||||
<p className="text-xs text-slate-400 mt-1">
|
||||
{claim.item_description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-blue-500/10 p-4 rounded-xl border border-blue-500/30">
|
||||
<h4 className="font-semibold mb-3 text-blue-400 flex items-center gap-2">
|
||||
<span>💬</span>
|
||||
Deskripsi dari Pengklaim
|
||||
</h4>
|
||||
<p className="text-sm text-slate-300">{claim.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Match Percentage */}
|
||||
{claim.match_percentage && (
|
||||
<div
|
||||
className={`p-6 rounded-xl text-center mb-6 ${
|
||||
claim.match_percentage >= 70
|
||||
? "bg-green-500/10 border-2 border-green-500/30"
|
||||
: "bg-yellow-500/10 border-2 border-yellow-500/30"
|
||||
}`}
|
||||
>
|
||||
<strong className="text-slate-300">Similarity Match:</strong>
|
||||
<div
|
||||
className={`text-5xl font-bold mt-2 ${
|
||||
claim.match_percentage >= 70
|
||||
? "text-green-400"
|
||||
: "text-yellow-400"
|
||||
}`}
|
||||
>
|
||||
{claim.match_percentage}%
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Verification Info */}
|
||||
{claim.verified_at && (
|
||||
<div className="bg-slate-900/50 p-4 rounded-xl border border-slate-700 mb-6">
|
||||
<strong className="text-slate-300 block mb-2">
|
||||
Informasi Verifikasi:
|
||||
</strong>
|
||||
<div className="space-y-1 text-sm text-slate-400">
|
||||
<div>
|
||||
✅ Diverifikasi: {Helpers.formatDateTime(claim.verified_at)}
|
||||
</div>
|
||||
{claim.verified_by_name && (
|
||||
<div>👤 Oleh: {claim.verified_by_name}</div>
|
||||
)}
|
||||
{claim.notes && (
|
||||
<div className="mt-2 pt-2 border-t border-slate-700">
|
||||
<strong className="text-slate-300">Catatan:</strong>
|
||||
<p className="text-slate-400 mt-1">{claim.notes}</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Case Closed Info */}
|
||||
{claim.berita_acara_no && (
|
||||
<div className="bg-green-500/10 border-2 border-green-500/30 p-4 rounded-xl">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<span className="text-3xl">📋</span>
|
||||
<strong className="text-green-400 text-lg">Case Closed</strong>
|
||||
</div>
|
||||
<div className="space-y-2 text-sm text-slate-300">
|
||||
<div>
|
||||
No. BA:{" "}
|
||||
<strong className="text-white text-lg">
|
||||
{claim.berita_acara_no}
|
||||
</strong>
|
||||
</div>
|
||||
{claim.case_closed_at && (
|
||||
<div>
|
||||
Ditutup: {Helpers.formatDateTime(claim.case_closed_at)}
|
||||
</div>
|
||||
)}
|
||||
{claim.case_closed_by_name && (
|
||||
<div>Oleh: {claim.case_closed_by_name}</div>
|
||||
)}
|
||||
{claim.case_closed_notes && (
|
||||
<div className="mt-2 pt-2 border-t border-green-500/30">
|
||||
<strong>Catatan:</strong>
|
||||
<p className="text-slate-400 mt-1">
|
||||
{claim.case_closed_notes}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{claim.bukti_serah_terima && (
|
||||
<a
|
||||
href={claim.bukti_serah_terima}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="inline-block mt-3 px-4 py-2 bg-blue-600 text-white text-sm rounded-lg hover:bg-blue-700 transition shadow-lg"
|
||||
>
|
||||
📄 Lihat Bukti Serah Terima
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Proof Image */}
|
||||
{claim.proof_url && (
|
||||
<div className="mt-6">
|
||||
<strong className="text-slate-300 block mb-2">
|
||||
Bukti Pendukung:
|
||||
</strong>
|
||||
<img
|
||||
src={claim.proof_url}
|
||||
alt="Bukti Klaim"
|
||||
className="w-full h-64 object-cover rounded-xl border-2 border-slate-600"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</Modal>
|
||||
);
|
||||
@@ -0,0 +1,122 @@
|
||||
// assets/js/pages/admin/modals/CreateClaimModal.js
|
||||
|
||||
const CreateClaimModal = ({ state, handlers }) => {
|
||||
const {
|
||||
showCreateClaimModal,
|
||||
setShowCreateClaimModal,
|
||||
items,
|
||||
users,
|
||||
loading,
|
||||
} = state;
|
||||
const { handleCreateClaim } = handlers;
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={showCreateClaimModal}
|
||||
onClose={() => setShowCreateClaimModal(false)}
|
||||
title="➕ Tambah Klaim Manual"
|
||||
size="large"
|
||||
>
|
||||
<form onSubmit={handleCreateClaim} className="space-y-4">
|
||||
<div className="bg-blue-500/10 border border-blue-500/30 p-4 rounded-lg mb-4">
|
||||
<p className="text-sm text-blue-300">
|
||||
ℹ️ Fitur ini untuk menambahkan klaim manual oleh admin, misalnya
|
||||
jika user melaporkan secara offline.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block font-semibold mb-2 text-slate-300">
|
||||
Pilih Barang *
|
||||
</label>
|
||||
<select
|
||||
name="item_id"
|
||||
required
|
||||
className="w-full px-4 py-3 bg-slate-700 border-2 border-slate-600 rounded-xl text-white focus:border-blue-500 focus:outline-none"
|
||||
>
|
||||
<option value="">-- Pilih Barang --</option>
|
||||
{items
|
||||
.filter(
|
||||
(item) =>
|
||||
item.status === "unclaimed" || item.status === "pending_claim"
|
||||
)
|
||||
.map((item) => (
|
||||
<option key={item.id} value={item.id}>
|
||||
{item.name} - {item.location} (
|
||||
{Helpers.formatDate(item.date_found)})
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block font-semibold mb-2 text-slate-300">
|
||||
Pilih User Pengklaim *
|
||||
</label>
|
||||
<select
|
||||
name="user_id"
|
||||
required
|
||||
className="w-full px-4 py-3 bg-slate-700 border-2 border-slate-600 rounded-xl text-white focus:border-blue-500 focus:outline-none"
|
||||
>
|
||||
<option value="">-- Pilih User --</option>
|
||||
{users &&
|
||||
users.map((user) => (
|
||||
<option key={user.id} value={user.id}>
|
||||
{user.name} ({user.email})
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<p className="text-xs text-slate-400 mt-1">
|
||||
User yang akan menjadi pengklaim barang ini
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block font-semibold mb-2 text-slate-300">
|
||||
Deskripsi Klaim *
|
||||
</label>
|
||||
<textarea
|
||||
name="description"
|
||||
required
|
||||
rows="4"
|
||||
className="w-full px-4 py-3 bg-slate-700 border-2 border-slate-600 rounded-xl text-white placeholder-slate-400 focus:border-blue-500 focus:outline-none"
|
||||
placeholder="Jelaskan ciri-ciri yang diklaim oleh user..."
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block font-semibold mb-2 text-slate-300">
|
||||
No. Kontak *
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="contact"
|
||||
required
|
||||
className="w-full px-4 py-3 bg-slate-700 border-2 border-slate-600 rounded-xl text-white placeholder-slate-400 focus:border-blue-500 focus:outline-none"
|
||||
placeholder="08123456789"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block font-semibold mb-2 text-slate-300">
|
||||
Catatan Admin (Opsional)
|
||||
</label>
|
||||
<textarea
|
||||
name="admin_notes"
|
||||
rows="2"
|
||||
className="w-full px-4 py-3 bg-slate-700 border-2 border-slate-600 rounded-xl text-white placeholder-slate-400 focus:border-blue-500 focus:outline-none"
|
||||
placeholder="Catatan internal untuk admin..."
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
className="w-full px-4 py-3 bg-gradient-to-r from-green-600 to-green-700 text-white rounded-xl hover:from-green-700 hover:to-green-800 transition font-semibold shadow-lg disabled:from-slate-600 disabled:to-slate-700"
|
||||
>
|
||||
{loading ? "⏳ Menyimpan..." : "✅ Simpan Klaim"}
|
||||
</button>
|
||||
</form>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,177 @@
|
||||
// assets/js/pages/admin/modals/CreateItemModal.js
|
||||
|
||||
const CreateItemModal = ({ state, handlers }) => {
|
||||
const {
|
||||
showCreateItemModal,
|
||||
setShowCreateItemModal,
|
||||
photoPreview,
|
||||
loading,
|
||||
categories,
|
||||
} = state;
|
||||
const { handleCreateItem, handlePhotoChange } = handlers;
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={showCreateItemModal}
|
||||
onClose={() => {
|
||||
setShowCreateItemModal(false);
|
||||
state.setPhotoPreview(null);
|
||||
}}
|
||||
title="➕ Tambah Barang Baru"
|
||||
size="large"
|
||||
>
|
||||
<form onSubmit={handleCreateItem} className="space-y-4">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block font-semibold mb-2 text-slate-300">
|
||||
Nama Barang *
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="name"
|
||||
required
|
||||
className="w-full px-4 py-3 bg-slate-700 border-2 border-slate-600 rounded-xl text-white placeholder-slate-400 focus:border-blue-500 focus:outline-none"
|
||||
placeholder="Contoh: Dompet Kulit Coklat"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block font-semibold mb-2 text-slate-300">
|
||||
Kategori *
|
||||
</label>
|
||||
<select
|
||||
name="category_id"
|
||||
required
|
||||
className="w-full px-4 py-3 bg-slate-700 border-2 border-slate-600 rounded-xl text-white focus:border-blue-500 focus:outline-none"
|
||||
>
|
||||
<option value="">Pilih Kategori</option>
|
||||
|
||||
{/* UBAH BAGIAN INI */}
|
||||
{categories && categories.length > 0 ? (
|
||||
categories.map((cat) => (
|
||||
<option key={cat.id} value={cat.id}>
|
||||
{cat.name}
|
||||
</option>
|
||||
))
|
||||
) : (
|
||||
<option disabled>Memuat kategori...</option>
|
||||
)}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block font-semibold mb-2 text-slate-300">
|
||||
Foto Barang
|
||||
</label>
|
||||
<input
|
||||
type="file"
|
||||
name="photo"
|
||||
accept="image/*"
|
||||
onChange={handlePhotoChange}
|
||||
className="w-full px-4 py-3 bg-slate-700 border-2 border-slate-600 rounded-xl text-white file:mr-4 file:py-2 file:px-4 file:rounded-lg file:border-0 file:text-sm file:font-semibold file:bg-blue-600 file:text-white hover:file:bg-blue-700 focus:outline-none"
|
||||
/>
|
||||
{photoPreview && (
|
||||
<img
|
||||
src={photoPreview}
|
||||
alt="Preview"
|
||||
className="mt-3 w-full h-48 object-cover rounded-xl border-2 border-blue-500"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block font-semibold mb-2 text-slate-300">
|
||||
Lokasi Ditemukan *
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="location"
|
||||
required
|
||||
className="w-full px-4 py-3 bg-slate-700 border-2 border-slate-600 rounded-xl text-white placeholder-slate-400 focus:border-blue-500 focus:outline-none"
|
||||
placeholder="Contoh: Perpustakaan Lantai 2"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block font-semibold mb-2 text-slate-300">
|
||||
Deskripsi Umum *
|
||||
</label>
|
||||
<textarea
|
||||
name="description"
|
||||
required
|
||||
rows="3"
|
||||
className="w-full px-4 py-3 bg-slate-700 border-2 border-slate-600 rounded-xl text-white placeholder-slate-400 focus:border-blue-500 focus:outline-none"
|
||||
placeholder="Deskripsi umum barang..."
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block font-semibold mb-2 text-slate-300">
|
||||
Ciri Khusus Rahasia * 🔒
|
||||
</label>
|
||||
<textarea
|
||||
name="secret_details"
|
||||
required
|
||||
rows="3"
|
||||
className="w-full px-4 py-3 bg-slate-700 border-2 border-slate-600 rounded-xl text-white placeholder-slate-400 focus:border-blue-500 focus:outline-none"
|
||||
placeholder="Ciri khusus untuk verifikasi klaim..."
|
||||
/>
|
||||
<p className="text-xs text-yellow-400 mt-1">
|
||||
⚠️ Info ini RAHASIA untuk verifikasi klaim
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block font-semibold mb-2 text-slate-300">
|
||||
Tanggal Ditemukan *
|
||||
</label>
|
||||
<input
|
||||
type="date"
|
||||
name="date_found"
|
||||
required
|
||||
max={new Date().toISOString().split("T")[0]}
|
||||
className="w-full px-4 py-3 bg-slate-700 border-2 border-slate-600 rounded-xl text-white focus:border-blue-500 focus:outline-none"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block font-semibold mb-2 text-slate-300">
|
||||
Kontak Pelapor *
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="reporter_contact"
|
||||
required
|
||||
className="w-full px-4 py-3 bg-slate-700 border-2 border-slate-600 rounded-xl text-white placeholder-slate-400 focus:border-blue-500 focus:outline-none"
|
||||
placeholder="08123456789"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block font-semibold mb-2 text-slate-300">
|
||||
Nama Pelapor *
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="reporter_name"
|
||||
required
|
||||
className="w-full px-4 py-3 bg-slate-700 border-2 border-slate-600 rounded-xl text-white placeholder-slate-400 focus:border-blue-500 focus:outline-none"
|
||||
placeholder="Nama lengkap pelapor"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
className="w-full px-4 py-3 bg-gradient-to-r from-green-600 to-green-700 text-white rounded-xl hover:from-green-700 hover:to-green-800 transition font-semibold shadow-lg disabled:from-slate-600 disabled:to-slate-700"
|
||||
>
|
||||
{loading ? "⏳ Menyimpan..." : "✅ Simpan Barang"}
|
||||
</button>
|
||||
</form>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
const CreateLostItemModal = ({ state, handlers }) => {
|
||||
const {
|
||||
showCreateLostItemModal,
|
||||
setShowCreateLostItemModal,
|
||||
categories,
|
||||
users,
|
||||
loading,
|
||||
} = state;
|
||||
|
||||
const { handleCreateLostItem } = handlers;
|
||||
|
||||
if (!showCreateLostItemModal) return null;
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={showCreateLostItemModal}
|
||||
onClose={() => setShowCreateLostItemModal(false)}
|
||||
title="➕ Tambah Laporan Barang Hilang"
|
||||
size="large"
|
||||
>
|
||||
<form onSubmit={handleCreateLostItem} className="space-y-6">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-slate-300 mb-2 font-semibold">
|
||||
User ID (Pelapor) <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<select
|
||||
name="user_id"
|
||||
required
|
||||
className="w-full 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="">Pilih User</option>
|
||||
{users &&
|
||||
users.map((user) => (
|
||||
<option key={user.id} value={user.id}>
|
||||
{user.name} (#{user.id})
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<p className="text-xs text-slate-400 mt-1">
|
||||
Pilih user yang melaporkan kehilangan
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-slate-300 mb-2 font-semibold">
|
||||
Nama Barang <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="name"
|
||||
required
|
||||
className="w-full 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"
|
||||
placeholder="Contoh: HP Samsung Galaxy"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-slate-300 mb-2 font-semibold">
|
||||
Kategori <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<select
|
||||
name="category_id"
|
||||
required
|
||||
className="w-full 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="">Pilih Kategori</option>
|
||||
{categories &&
|
||||
categories.map((cat) => (
|
||||
<option key={cat.id} value={cat.id}>
|
||||
{cat.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-slate-300 mb-2 font-semibold">
|
||||
Warna
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="color"
|
||||
className="w-full 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"
|
||||
placeholder="Contoh: Hitam, Biru"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-slate-300 mb-2 font-semibold">
|
||||
Tanggal Hilang <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="date"
|
||||
name="date_lost"
|
||||
required
|
||||
max={new Date().toISOString().split("T")[0]}
|
||||
className="w-full px-4 py-2 bg-slate-700 border-2 border-slate-600 rounded-xl text-white focus:border-blue-500 focus:outline-none"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-slate-300 mb-2 font-semibold">
|
||||
Lokasi Hilang
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="location"
|
||||
className="w-full 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"
|
||||
placeholder="Contoh: Perpustakaan Lantai 2"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="col-span-2">
|
||||
<label className="block text-slate-300 mb-2 font-semibold">
|
||||
Deskripsi <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<textarea
|
||||
name="description"
|
||||
required
|
||||
rows="4"
|
||||
className="w-full 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"
|
||||
placeholder="Jelaskan ciri-ciri barang yang hilang secara detail..."
|
||||
/>
|
||||
<p className="text-xs text-slate-400 mt-1">
|
||||
Deskripsi ini akan digunakan untuk auto-matching dengan barang
|
||||
yang ditemukan
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-blue-500/10 border border-blue-500/30 rounded-lg p-4">
|
||||
<p className="text-sm text-blue-300">
|
||||
ℹ️ <strong>Info:</strong> Setelah laporan dibuat, sistem akan
|
||||
otomatis mencari kecocokan dengan barang yang telah ditemukan. User
|
||||
akan mendapat notifikasi jika ada barang yang cocok.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-3 justify-end pt-4 border-t border-slate-700">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowCreateLostItemModal(false)}
|
||||
className="px-6 py-2 bg-slate-700 text-white rounded-lg hover:bg-slate-600 transition"
|
||||
disabled={loading}
|
||||
>
|
||||
Batal
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
className="px-6 py-2 bg-gradient-to-r from-green-600 to-green-700 text-white rounded-lg hover:from-green-700 hover:to-green-800 transition shadow-lg disabled:opacity-50"
|
||||
disabled={loading}
|
||||
>
|
||||
{loading ? "Memproses..." : "➕ Tambah Laporan"}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,84 @@
|
||||
// assets/js/pages/admin/modals/EditClaimModal.js
|
||||
|
||||
const EditClaimModal = ({ state, handlers }) => {
|
||||
const {
|
||||
showEditClaimModal,
|
||||
setShowEditClaimModal,
|
||||
selectedClaimToEdit,
|
||||
loading,
|
||||
} = state;
|
||||
const { handleUpdateClaim } = handlers;
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={showEditClaimModal}
|
||||
onClose={() => setShowEditClaimModal(false)}
|
||||
title="✏️ Edit Klaim"
|
||||
size="large"
|
||||
>
|
||||
{selectedClaimToEdit && (
|
||||
<form onSubmit={handleUpdateClaim} className="space-y-4">
|
||||
<div className="bg-blue-500/10 border border-blue-500/30 p-4 rounded-lg mb-4">
|
||||
<div className="grid grid-cols-2 gap-2 text-sm text-slate-300">
|
||||
<div>
|
||||
<strong className="text-blue-400">Barang:</strong>{" "}
|
||||
{selectedClaimToEdit.item_name}
|
||||
</div>
|
||||
<div>
|
||||
<strong className="text-blue-400">Pengklaim:</strong>{" "}
|
||||
{selectedClaimToEdit.user_name}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block font-semibold mb-2 text-slate-300">
|
||||
Deskripsi Klaim *
|
||||
</label>
|
||||
<textarea
|
||||
name="description"
|
||||
defaultValue={selectedClaimToEdit.description}
|
||||
required
|
||||
rows="4"
|
||||
className="w-full px-4 py-3 bg-slate-700 border-2 border-slate-600 rounded-xl text-white focus:border-blue-500 focus:outline-none"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block font-semibold mb-2 text-slate-300">
|
||||
No. Kontak *
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="contact"
|
||||
defaultValue={selectedClaimToEdit.contact}
|
||||
required
|
||||
className="w-full px-4 py-3 bg-slate-700 border-2 border-slate-600 rounded-xl text-white focus:border-blue-500 focus:outline-none"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block font-semibold mb-2 text-slate-300">
|
||||
Alasan Edit *
|
||||
</label>
|
||||
<textarea
|
||||
name="reason"
|
||||
required
|
||||
rows="2"
|
||||
className="w-full px-4 py-3 bg-slate-700 border-2 border-slate-600 rounded-xl text-white placeholder-slate-400 focus:border-blue-500 focus:outline-none"
|
||||
placeholder="Jelaskan alasan edit klaim..."
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
className="w-full px-4 py-3 bg-gradient-to-r from-blue-600 to-blue-700 text-white rounded-xl hover:from-blue-700 hover:to-blue-800 transition font-semibold shadow-lg disabled:from-slate-600 disabled:to-slate-700"
|
||||
>
|
||||
{loading ? "⏳ Menyimpan..." : "✅ Update Klaim"}
|
||||
</button>
|
||||
</form>
|
||||
)}
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,220 @@
|
||||
// assets/js/pages/admin/modals/EditItemModal.js
|
||||
|
||||
const EditItemModal = ({ state, handlers }) => {
|
||||
const {
|
||||
showEditItemModal,
|
||||
setShowEditItemModal,
|
||||
selectedItemToEdit,
|
||||
setSelectedItemToEdit,
|
||||
loading,
|
||||
photoPreview,
|
||||
setPhotoPreview, // <--- Pastikan mengambil setPhotoPreview dari state
|
||||
} = state;
|
||||
const { handleUpdateItem, handlePhotoChange } = handlers;
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={showEditItemModal}
|
||||
onClose={() => {
|
||||
setShowEditItemModal(false);
|
||||
setSelectedItemToEdit(null);
|
||||
setPhotoPreview(null);
|
||||
}}
|
||||
title="✏️ Edit Barang"
|
||||
size="large"
|
||||
>
|
||||
{selectedItemToEdit && (
|
||||
<form onSubmit={handleUpdateItem} className="space-y-4">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block font-semibold mb-2 text-slate-300">
|
||||
Nama Barang *
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="name"
|
||||
defaultValue={selectedItemToEdit.name}
|
||||
required
|
||||
className="w-full px-4 py-3 bg-slate-700 border-2 border-slate-600 rounded-xl text-white focus:border-blue-500 focus:outline-none"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block font-semibold mb-2 text-slate-300">
|
||||
Kategori *
|
||||
</label>
|
||||
<select
|
||||
name="category_id"
|
||||
defaultValue={selectedItemToEdit.category_id}
|
||||
required
|
||||
className="w-full px-4 py-3 bg-slate-700 border-2 border-slate-600 rounded-xl text-white focus:border-blue-500 focus:outline-none"
|
||||
>
|
||||
{state.categories &&
|
||||
state.categories.map((cat) => (
|
||||
<option key={cat.id} value={cat.id}>
|
||||
{" "}
|
||||
{/* Sesuaikan value dengan slug/id dari DB */}
|
||||
{cat.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* ✅ BAGIAN BARU: STATUS DROPDOWN */}
|
||||
<div>
|
||||
<label className="block font-semibold mb-2 text-slate-300">
|
||||
Status Barang *
|
||||
</label>
|
||||
<select
|
||||
name="status"
|
||||
defaultValue={selectedItemToEdit.status}
|
||||
required
|
||||
className="w-full px-4 py-3 bg-slate-700 border-2 border-slate-600 rounded-xl text-white focus:border-blue-500 focus:outline-none"
|
||||
>
|
||||
<option value="unclaimed">Unclaimed (Aktif/Belum Diklaim)</option>
|
||||
<option value="pending_claim">
|
||||
Pending Claim (Sedang Diverifikasi)
|
||||
</option>
|
||||
<option value="verified">Verified (Terverifikasi)</option>
|
||||
<option value="expired">Expired (Kadaluarsa)</option>
|
||||
<option value="case_closed">
|
||||
Case Closed (Selesai/Diarsipkan)
|
||||
</option>
|
||||
</select>
|
||||
<p className="text-xs text-yellow-400 mt-1">
|
||||
ℹ️ Ubah status ke <strong>Unclaimed</strong> jika ingin
|
||||
mengaktifkan kembali barang Expired agar bisa diklaim user.
|
||||
</p>
|
||||
</div>
|
||||
{/* ✅ AKHIR BAGIAN BARU */}
|
||||
|
||||
<div>
|
||||
<label className="block font-semibold mb-2 text-slate-300">
|
||||
Update Foto Barang (Opsional)
|
||||
</label>
|
||||
<input
|
||||
type="file"
|
||||
name="photo"
|
||||
accept="image/*"
|
||||
onChange={handlePhotoChange}
|
||||
className="w-full px-4 py-3 bg-slate-700 border-2 border-slate-600 rounded-xl text-white file:mr-4 file:py-2 file:px-4 file:rounded-lg file:border-0 file:text-sm file:font-semibold file:bg-blue-600 file:text-white hover:file:bg-blue-700 focus:outline-none"
|
||||
/>
|
||||
{/* Tampilkan preview foto baru ATAU foto lama */}
|
||||
<div className="mt-3">
|
||||
<p className="text-xs text-slate-400 mb-1">Preview:</p>
|
||||
<img
|
||||
src={photoPreview || selectedItemToEdit.photo_url}
|
||||
alt="Preview"
|
||||
className="w-full h-48 object-cover rounded-xl border-2 border-blue-500"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block font-semibold mb-2 text-slate-300">
|
||||
Lokasi *
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="location"
|
||||
defaultValue={selectedItemToEdit.location}
|
||||
required
|
||||
className="w-full px-4 py-3 bg-slate-700 border-2 border-slate-600 rounded-xl text-white focus:border-blue-500 focus:outline-none"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block font-semibold mb-2 text-slate-300">
|
||||
Deskripsi Umum *
|
||||
</label>
|
||||
<textarea
|
||||
name="description"
|
||||
defaultValue={selectedItemToEdit.description}
|
||||
required
|
||||
rows="3"
|
||||
className="w-full px-4 py-3 bg-slate-700 border-2 border-slate-600 rounded-xl text-white focus:border-blue-500 focus:outline-none"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block font-semibold mb-2 text-slate-300">
|
||||
Ciri Khusus Rahasia * 🔒
|
||||
</label>
|
||||
<textarea
|
||||
name="secret_details"
|
||||
defaultValue={selectedItemToEdit.secret_details}
|
||||
required
|
||||
rows="3"
|
||||
className="w-full px-4 py-3 bg-slate-700 border-2 border-slate-600 rounded-xl text-white focus:border-blue-500 focus:outline-none"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block font-semibold mb-2 text-slate-300">
|
||||
Tanggal Ditemukan *
|
||||
</label>
|
||||
<input
|
||||
type="date"
|
||||
name="date_found"
|
||||
defaultValue={selectedItemToEdit.date_found?.split("T")[0]}
|
||||
required
|
||||
className="w-full px-4 py-3 bg-slate-700 border-2 border-slate-600 rounded-xl text-white focus:border-blue-500 focus:outline-none"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block font-semibold mb-2 text-slate-300">
|
||||
Kontak Pelapor *
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="reporter_contact"
|
||||
defaultValue={selectedItemToEdit.reporter_contact}
|
||||
required
|
||||
className="w-full px-4 py-3 bg-slate-700 border-2 border-slate-600 rounded-xl text-white focus:border-blue-500 focus:outline-none"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block font-semibold mb-2 text-slate-300">
|
||||
Nama Pelapor *
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="reporter_name"
|
||||
defaultValue={selectedItemToEdit.reporter_name}
|
||||
required
|
||||
className="w-full px-4 py-3 bg-slate-700 border-2 border-slate-600 rounded-xl text-white focus:border-blue-500 focus:outline-none"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block font-semibold mb-2 text-slate-300">
|
||||
Alasan Edit *
|
||||
</label>
|
||||
<textarea
|
||||
name="reason"
|
||||
required
|
||||
rows="2"
|
||||
className="w-full px-4 py-3 bg-slate-700 border-2 border-slate-600 rounded-xl text-white placeholder-slate-400 focus:border-blue-500 focus:outline-none"
|
||||
placeholder="Jelaskan alasan edit..."
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
className="w-full px-4 py-3 bg-gradient-to-r from-blue-600 to-blue-700 text-white rounded-xl hover:from-blue-700 hover:to-blue-800 transition font-semibold shadow-lg disabled:from-slate-600 disabled:to-slate-700"
|
||||
>
|
||||
{loading ? "⏳ Menyimpan..." : "✅ Update Barang"}
|
||||
</button>
|
||||
</form>
|
||||
)}
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
window.EditItemModal = EditItemModal;
|
||||
@@ -0,0 +1,178 @@
|
||||
const EditLostItemModal = ({ state, handlers }) => {
|
||||
const {
|
||||
selectedLostItemToEdit,
|
||||
showEditLostItemModal,
|
||||
setShowEditLostItemModal,
|
||||
categories,
|
||||
loading,
|
||||
} = state;
|
||||
const { handleUpdateLostItem } = handlers;
|
||||
|
||||
// ✅ DEBUG: Log data saat modal dibuka
|
||||
React.useEffect(() => {
|
||||
if (selectedLostItemToEdit) {
|
||||
console.log("📋 Selected Lost Item Data:", selectedLostItemToEdit);
|
||||
console.log("🏷️ Category ID:", selectedLostItemToEdit.category_id);
|
||||
console.log("📦 Available Categories:", categories);
|
||||
}
|
||||
}, [selectedLostItemToEdit, categories]);
|
||||
|
||||
if (!showEditLostItemModal || !selectedLostItemToEdit) return null;
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={showEditLostItemModal}
|
||||
onClose={() => setShowEditLostItemModal(false)}
|
||||
title="✏️ Edit Laporan Barang Hilang"
|
||||
size="large"
|
||||
>
|
||||
<form onSubmit={handleUpdateLostItem} className="space-y-6">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-slate-300 mb-2 font-semibold">
|
||||
Nama Barang <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="name"
|
||||
defaultValue={selectedLostItemToEdit.name}
|
||||
required
|
||||
className="w-full 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"
|
||||
placeholder="Contoh: HP Samsung"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-slate-300 mb-2 font-semibold">
|
||||
Kategori <span className="text-red-500">*</span>
|
||||
</label>
|
||||
{/* ✅ PERBAIKAN: Gunakan category_id, bukan category */}
|
||||
<select
|
||||
name="category_id"
|
||||
defaultValue={selectedLostItemToEdit.category_id || ""}
|
||||
required
|
||||
className="w-full px-4 py-2 bg-slate-700 border-2 border-slate-600 rounded-xl text-white focus:border-blue-500 focus:outline-none"
|
||||
onChange={(e) => {
|
||||
console.log("🔄 Category changed to:", e.target.value);
|
||||
}}
|
||||
>
|
||||
<option value="">Pilih Kategori</option>
|
||||
{categories &&
|
||||
categories.map((cat) => (
|
||||
<option key={cat.id} value={cat.id}>
|
||||
{cat.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
{/* ✅ DEBUGGING: Tampilkan nilai saat ini */}
|
||||
<small className="text-slate-400 text-xs mt-1 block">
|
||||
Current: {selectedLostItemToEdit.category} (ID:{" "}
|
||||
{selectedLostItemToEdit.category_id})
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-slate-300 mb-2 font-semibold">
|
||||
Warna
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="color"
|
||||
defaultValue={selectedLostItemToEdit.color || ""}
|
||||
className="w-full 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"
|
||||
placeholder="Contoh: Hitam, Biru"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-slate-300 mb-2 font-semibold">
|
||||
Tanggal Hilang <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="date"
|
||||
name="date_lost"
|
||||
defaultValue={selectedLostItemToEdit.date_lost?.split("T")[0]}
|
||||
required
|
||||
className="w-full px-4 py-2 bg-slate-700 border-2 border-slate-600 rounded-xl text-white focus:border-blue-500 focus:outline-none"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="col-span-2">
|
||||
<label className="block text-slate-300 mb-2 font-semibold">
|
||||
Lokasi Hilang
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="location"
|
||||
defaultValue={selectedLostItemToEdit.location || ""}
|
||||
className="w-full 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"
|
||||
placeholder="Contoh: Perpustakaan Lantai 2"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="col-span-2">
|
||||
<label className="block text-slate-300 mb-2 font-semibold">
|
||||
Deskripsi <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<textarea
|
||||
name="description"
|
||||
defaultValue={selectedLostItemToEdit.description}
|
||||
required
|
||||
rows="4"
|
||||
className="w-full 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"
|
||||
placeholder="Jelaskan ciri-ciri barang yang hilang..."
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="col-span-2">
|
||||
<label className="block text-slate-300 mb-2 font-semibold">
|
||||
Status <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<select
|
||||
name="status"
|
||||
defaultValue={selectedLostItemToEdit.status}
|
||||
required
|
||||
className="w-full 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="active">Active</option>
|
||||
<option value="pending_verification">Pending Verification</option>
|
||||
<option value="claimed">Claimed</option>
|
||||
<option value="completed">Completed</option>
|
||||
<option value="closed">Closed</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className="col-span-2">
|
||||
<label className="block text-slate-300 mb-2 font-semibold">
|
||||
Alasan Update
|
||||
</label>
|
||||
<textarea
|
||||
name="reason"
|
||||
rows="2"
|
||||
className="w-full 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"
|
||||
placeholder="Alasan melakukan update (opsional)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-3 justify-end pt-4 border-t border-slate-700">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowEditLostItemModal(false)}
|
||||
className="px-6 py-2 bg-slate-700 text-white rounded-lg hover:bg-slate-600 transition"
|
||||
disabled={loading}
|
||||
>
|
||||
Batal
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
className="px-6 py-2 bg-gradient-to-r from-blue-600 to-blue-700 text-white rounded-lg hover:from-blue-700 hover:to-blue-800 transition shadow-lg disabled:opacity-50"
|
||||
disabled={loading}
|
||||
>
|
||||
{loading ? "Memproses..." : "💾 Simpan"}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,155 @@
|
||||
const LostItemDetailModal = ({ state }) => {
|
||||
const {
|
||||
selectedLostItemDetail,
|
||||
showLostItemDetailModal,
|
||||
setShowLostItemDetailModal,
|
||||
} = state;
|
||||
|
||||
if (!showLostItemDetailModal || !selectedLostItemDetail) return null;
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={showLostItemDetailModal}
|
||||
onClose={() => setShowLostItemDetailModal(false)}
|
||||
title="📋 Detail Laporan Barang Hilang"
|
||||
size="large"
|
||||
>
|
||||
<div className="space-y-6">
|
||||
{/* Info Barang */}
|
||||
<div className="bg-slate-700/50 rounded-xl p-6 border border-slate-600">
|
||||
<h4 className="text-lg font-semibold text-white mb-4 flex items-center gap-2">
|
||||
🔍 Informasi Barang
|
||||
</h4>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="text-slate-400 text-sm">ID Laporan</label>
|
||||
<p className="text-white font-semibold">
|
||||
#{selectedLostItemDetail.id}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-slate-400 text-sm">Nama Barang</label>
|
||||
<p className="text-white font-semibold">
|
||||
{selectedLostItemDetail.name}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-slate-400 text-sm">Kategori</label>
|
||||
<p className="text-white">{selectedLostItemDetail.category}</p>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-slate-400 text-sm">Warna</label>
|
||||
<p className="text-white">
|
||||
{selectedLostItemDetail.color || "-"}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-slate-400 text-sm">Lokasi Hilang</label>
|
||||
<p className="text-white">
|
||||
{selectedLostItemDetail.location || "-"}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-slate-400 text-sm">Tanggal Hilang</label>
|
||||
<p className="text-white">
|
||||
{Helpers.formatDate(selectedLostItemDetail.date_lost)}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-slate-400 text-sm">Status</label>
|
||||
<span
|
||||
className={`inline-block px-3 py-1 rounded-full text-xs font-semibold ${Helpers.getStatusBadgeClass(
|
||||
selectedLostItemDetail.status
|
||||
)}`}
|
||||
>
|
||||
{selectedLostItemDetail.status}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-slate-400 text-sm">Dibuat</label>
|
||||
<p className="text-white">
|
||||
{Helpers.formatDateTime(selectedLostItemDetail.created_at)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Deskripsi */}
|
||||
<div className="bg-slate-700/50 rounded-xl p-6 border border-slate-600">
|
||||
<h4 className="text-lg font-semibold text-white mb-3">
|
||||
📝 Deskripsi
|
||||
</h4>
|
||||
<p className="text-slate-300 whitespace-pre-wrap">
|
||||
{selectedLostItemDetail.description}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Info Pelapor */}
|
||||
<div className="bg-slate-700/50 rounded-xl p-6 border border-slate-600">
|
||||
<h4 className="text-lg font-semibold text-white mb-4 flex items-center gap-2">
|
||||
👤 Informasi Pelapor
|
||||
</h4>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="text-slate-400 text-sm">User ID</label>
|
||||
<p className="text-white">#{selectedLostItemDetail.user_id}</p>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-slate-400 text-sm">Nama</label>
|
||||
<p className="text-white font-semibold">
|
||||
{selectedLostItemDetail.user_name}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Direct Claim Info (jika ada) */}
|
||||
{selectedLostItemDetail.direct_claim && (
|
||||
<div className="bg-yellow-500/10 border-2 border-yellow-500/50 rounded-xl p-6">
|
||||
<h4 className="text-lg font-semibold text-yellow-400 mb-4 flex items-center gap-2">
|
||||
🎯 Direct Claim
|
||||
</h4>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="text-slate-400 text-sm">Claim ID</label>
|
||||
<p className="text-white">
|
||||
#{selectedLostItemDetail.direct_claim.id}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-slate-400 text-sm">Status Claim</label>
|
||||
<span
|
||||
className={`inline-block px-3 py-1 rounded-full text-xs font-semibold ${Helpers.getStatusBadgeClass(
|
||||
selectedLostItemDetail.direct_claim.status
|
||||
)}`}
|
||||
>
|
||||
{selectedLostItemDetail.direct_claim.status}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-slate-400 text-sm">Penemu</label>
|
||||
<p className="text-white">
|
||||
{selectedLostItemDetail.direct_claim.user_name}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-slate-400 text-sm">Kontak</label>
|
||||
<p className="text-white">
|
||||
{selectedLostItemDetail.direct_claim.contact}
|
||||
</p>
|
||||
</div>
|
||||
<div className="col-span-2">
|
||||
<label className="text-slate-400 text-sm">
|
||||
Deskripsi Claim
|
||||
</label>
|
||||
<p className="text-white mt-1">
|
||||
{selectedLostItemDetail.direct_claim.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,127 @@
|
||||
const RoleModal = ({
|
||||
isOpen,
|
||||
onClose,
|
||||
role,
|
||||
allPermissions,
|
||||
onSubmit,
|
||||
loading,
|
||||
}) => {
|
||||
const [formData, setFormData] = React.useState({
|
||||
name: "",
|
||||
description: "",
|
||||
permission_ids: [],
|
||||
});
|
||||
|
||||
React.useEffect(() => {
|
||||
if (role) {
|
||||
// Edit Mode
|
||||
setFormData({
|
||||
name: role.name,
|
||||
description: role.description || "",
|
||||
permission_ids: role.permissions
|
||||
? role.permissions.map((p) => p.id)
|
||||
: [],
|
||||
});
|
||||
} else {
|
||||
// Create Mode
|
||||
setFormData({ name: "", description: "", permission_ids: [] });
|
||||
}
|
||||
}, [role, isOpen]);
|
||||
|
||||
const handlePermissionToggle = (permId) => {
|
||||
setFormData((prev) => {
|
||||
const exists = prev.permission_ids.includes(permId);
|
||||
if (exists) {
|
||||
return {
|
||||
...prev,
|
||||
permission_ids: prev.permission_ids.filter((id) => id !== permId),
|
||||
};
|
||||
} else {
|
||||
return { ...prev, permission_ids: [...prev.permission_ids, permId] };
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
if (!isOpen) return null;
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
title={role ? "✏️ Edit Role" : "➕ Buat Role Baru"}
|
||||
size="large"
|
||||
>
|
||||
<form
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
onSubmit(formData);
|
||||
}}
|
||||
className="space-y-4"
|
||||
>
|
||||
<div>
|
||||
<label className="block font-semibold mb-2 text-slate-300">
|
||||
Nama Role
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={formData.name}
|
||||
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
||||
disabled={role && (role.name === "admin" || role.name === "user")}
|
||||
className="w-full px-4 py-3 bg-slate-700 border-2 border-slate-600 rounded-xl text-white focus:border-blue-500 focus:outline-none"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block font-semibold mb-2 text-slate-300">
|
||||
Deskripsi
|
||||
</label>
|
||||
<textarea
|
||||
value={formData.description}
|
||||
onChange={(e) =>
|
||||
setFormData({ ...formData, description: e.target.value })
|
||||
}
|
||||
className="w-full px-4 py-3 bg-slate-700 border-2 border-slate-600 rounded-xl text-white focus:border-blue-500 focus:outline-none"
|
||||
rows="2"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block font-semibold mb-2 text-slate-300">
|
||||
Permissions (Hak Akses)
|
||||
</label>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3 max-h-60 overflow-y-auto bg-slate-900/50 p-4 rounded-xl border border-slate-700">
|
||||
{allPermissions.map((perm) => (
|
||||
<label
|
||||
key={perm.id}
|
||||
className="flex items-start gap-2 cursor-pointer p-2 hover:bg-slate-700 rounded transition"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
className="mt-1"
|
||||
checked={formData.permission_ids.includes(perm.id)}
|
||||
onChange={() => handlePermissionToggle(perm.id)}
|
||||
/>
|
||||
<div>
|
||||
<div className="text-sm font-semibold text-white">
|
||||
{perm.name}
|
||||
</div>
|
||||
<div className="text-xs text-slate-400">{perm.slug}</div>
|
||||
</div>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
className="w-full px-4 py-3 bg-gradient-to-r from-blue-600 to-blue-700 text-white rounded-xl hover:from-blue-700 hover:to-blue-800 transition font-semibold shadow-lg"
|
||||
>
|
||||
{loading ? "Menyimpan..." : "Simpan Role"}
|
||||
</button>
|
||||
</form>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
window.RoleModal = RoleModal;
|
||||
Reference in New Issue
Block a user