1075 lines
32 KiB
JavaScript
1075 lines
32 KiB
JavaScript
// assets/js/pages/admin/useAdminHandlers.js - WITH ARCHIVES HANDLERS
|
|
const useAdminHandlers = (state) => {
|
|
const {
|
|
setToast,
|
|
setLoading,
|
|
setStats,
|
|
setUsers,
|
|
setTotalPages,
|
|
setTotalRecords,
|
|
setItems,
|
|
setSelectedItemDetail,
|
|
setShowItemDetailModal,
|
|
setSelectedUser,
|
|
setShowEditModal,
|
|
setShowCreateItemModal,
|
|
setShowEditItemModal,
|
|
setSelectedItemToEdit,
|
|
setPhotoPreview,
|
|
currentPage,
|
|
// Claims States
|
|
setClaims,
|
|
setSelectedClaim,
|
|
setShowClaimDetailModal,
|
|
setShowCreateClaimModal,
|
|
setShowEditClaimModal,
|
|
setSelectedClaimToEdit,
|
|
setCategories,
|
|
setSelectedCategory,
|
|
// ✅ Archives States
|
|
setArchives,
|
|
setSelectedArchive,
|
|
setShowArchiveDetailModal,
|
|
setAuditLogs,
|
|
setLostItems,
|
|
setSelectedLostItemDetail,
|
|
setShowLostItemDetailModal,
|
|
setShowCreateLostItemModal,
|
|
setShowEditLostItemModal,
|
|
setSelectedLostItemToEdit,
|
|
} = state;
|
|
|
|
const showToast = (message, type = "info") => {
|
|
setToast({ message, type });
|
|
};
|
|
|
|
const loadData = async () => {
|
|
try {
|
|
const [statsData, usersData] = await Promise.all([
|
|
ApiUtils.get(CONFIG.API_ENDPOINTS.ADMIN.DASHBOARD),
|
|
ApiUtils.get(
|
|
`${CONFIG.API_ENDPOINTS.ADMIN.USERS}?page=${currentPage}&limit=${CONFIG.PAGINATION.LIMIT}`
|
|
),
|
|
]);
|
|
|
|
setStats(statsData.data || {});
|
|
setUsers(usersData.data || []);
|
|
|
|
if (usersData.pagination) {
|
|
setTotalPages(usersData.pagination.total_pages);
|
|
setTotalRecords(usersData.pagination.total_records);
|
|
}
|
|
} catch (error) {
|
|
showToast("Gagal memuat data", "error");
|
|
}
|
|
};
|
|
|
|
// NEW: Load Lost Items
|
|
const loadLostItems = async (page = 1) => {
|
|
try {
|
|
setLoading(true);
|
|
const {
|
|
lostItemSearchTerm,
|
|
lostItemStatusFilter,
|
|
lostItemCategoryFilter,
|
|
} = state;
|
|
const queryParams = new URLSearchParams({
|
|
page: page,
|
|
limit: 10,
|
|
search: lostItemSearchTerm || "",
|
|
status: lostItemStatusFilter || "",
|
|
category: lostItemCategoryFilter || "",
|
|
});
|
|
const response = await ApiUtils.get(
|
|
`${CONFIG.API_ENDPOINTS.LOST_ITEMS}?${queryParams.toString()}`
|
|
);
|
|
setLostItems(response.data || []);
|
|
if (response.pagination) {
|
|
state.setLostItemPage(response.pagination.current_page);
|
|
state.setLostItemTotalPages(response.pagination.total_pages);
|
|
state.setLostItemTotalRecords(response.pagination.total_records);
|
|
}
|
|
state.setFilteredLostItems(response.data || []);
|
|
} catch (error) {
|
|
showToast("Gagal memuat data laporan barang hilang", "error");
|
|
console.error(error);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
// NEW: View Lost Item Detail
|
|
const handleViewLostItemDetail = async (lostItem) => {
|
|
try {
|
|
const response = await ApiUtils.get(
|
|
`${CONFIG.API_ENDPOINTS.LOST_ITEMS}/${lostItem.id}`
|
|
);
|
|
setSelectedLostItemDetail(response.data || response);
|
|
setShowLostItemDetailModal(true);
|
|
} catch (error) {
|
|
showToast("Gagal memuat detail laporan", "error");
|
|
}
|
|
};
|
|
|
|
// NEW: Create Lost Item
|
|
const handleCreateLostItem = async (e) => {
|
|
e.preventDefault();
|
|
const formData = new FormData(e.target);
|
|
try {
|
|
setLoading(true);
|
|
showToast("Memproses pembuatan laporan...", "info");
|
|
|
|
const dateLost = formData.get("date_lost");
|
|
const dateLostISO = new Date(dateLost + "T00:00:00Z").toISOString();
|
|
|
|
const lostItemData = {
|
|
user_id: parseInt(formData.get("user_id")),
|
|
name: formData.get("name"),
|
|
category_id: parseInt(formData.get("category_id")),
|
|
color: formData.get("color") || "",
|
|
location: formData.get("location") || "",
|
|
description: formData.get("description"),
|
|
date_lost: dateLostISO,
|
|
};
|
|
|
|
await ApiUtils.post(CONFIG.API_ENDPOINTS.LOST_ITEMS, lostItemData);
|
|
showToast("Laporan berhasil ditambahkan!", "success");
|
|
setShowCreateLostItemModal(false);
|
|
loadLostItems();
|
|
loadData();
|
|
} catch (error) {
|
|
showToast("Gagal menambahkan laporan: " + error.message, "error");
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
// NEW: Edit Lost Item Click
|
|
const handleEditLostItemClick = async (lostItem) => {
|
|
try {
|
|
const response = await ApiUtils.get(
|
|
`${CONFIG.API_ENDPOINTS.LOST_ITEMS}/${lostItem.id}`
|
|
);
|
|
setSelectedLostItemToEdit(response.data || response);
|
|
setShowEditLostItemModal(true);
|
|
} catch (error) {
|
|
showToast("Gagal memuat data laporan", "error");
|
|
}
|
|
};
|
|
|
|
// NEW: Update Lost Item
|
|
const handleUpdateLostItem = async (e) => {
|
|
e.preventDefault();
|
|
const formData = new FormData(e.target);
|
|
|
|
try {
|
|
setLoading(true);
|
|
showToast("Memproses update laporan...", "info");
|
|
|
|
const dateLost = formData.get("date_lost");
|
|
const dateLostISO = new Date(dateLost + "T00:00:00Z").toISOString();
|
|
|
|
// ✅ PERBAIKAN: Pastikan category_id dikirim sebagai integer
|
|
const categoryId = parseInt(formData.get("category_id"));
|
|
|
|
// ✅ VALIDASI: Cek apakah category_id valid
|
|
if (!categoryId || isNaN(categoryId)) {
|
|
showToast("Error: Category harus dipilih!", "error");
|
|
setLoading(false);
|
|
return;
|
|
}
|
|
|
|
const lostItemData = {
|
|
name: formData.get("name"),
|
|
category_id: categoryId, // ✅ Gunakan integer, bukan string
|
|
color: formData.get("color") || "",
|
|
location: formData.get("location") || "",
|
|
description: formData.get("description"),
|
|
date_lost: dateLostISO,
|
|
status: formData.get("status"),
|
|
reason: formData.get("reason") || "Admin update",
|
|
};
|
|
|
|
// ✅ DEBUG LOG
|
|
console.log("📤 Data yang dikirim ke API:", lostItemData);
|
|
console.log("🎯 Lost Item ID:", state.selectedLostItemToEdit.id);
|
|
|
|
const response = await ApiUtils.put(
|
|
`${CONFIG.API_ENDPOINTS.LOST_ITEMS}/${state.selectedLostItemToEdit.id}`,
|
|
lostItemData
|
|
);
|
|
|
|
// ✅ DEBUG LOG response
|
|
console.log("✅ Response dari API:", response);
|
|
|
|
showToast("Laporan berhasil diupdate!", "success");
|
|
setShowEditLostItemModal(false);
|
|
setSelectedLostItemToEdit(null);
|
|
|
|
// Reload data
|
|
await loadLostItems();
|
|
await loadData();
|
|
} catch (error) {
|
|
console.error("❌ Update error:", error);
|
|
showToast("Gagal update laporan: " + error.message, "error");
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
// NEW: Delete Lost Item
|
|
const handleDeleteLostItem = async (lostItemId) => {
|
|
try {
|
|
setLoading(true);
|
|
await ApiUtils.delete(`${CONFIG.API_ENDPOINTS.LOST_ITEMS}/${lostItemId}`);
|
|
showToast("Laporan berhasil dihapus!", "success");
|
|
loadLostItems();
|
|
loadData();
|
|
} catch (error) {
|
|
showToast("Gagal menghapus laporan: " + error.message, "error");
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
// ✅ UPDATE: loadItems dengan Pagination & Filter Server-side
|
|
const loadItems = async (page = 1) => {
|
|
try {
|
|
setLoading(true);
|
|
|
|
// Ambil filter dari state saat ini
|
|
const { itemSearchTerm, itemStatusFilter, itemCategoryFilter } = state;
|
|
|
|
// Susun Query Params
|
|
const queryParams = new URLSearchParams({
|
|
page: page,
|
|
limit: 10, // ✅ Batasi 10 barang per halaman
|
|
search: itemSearchTerm || "",
|
|
status: itemStatusFilter || "",
|
|
category: itemCategoryFilter || "", // Backend butuh slug kategori
|
|
});
|
|
|
|
const response = await ApiUtils.get(
|
|
`${CONFIG.API_ENDPOINTS.ITEMS}?${queryParams.toString()}`
|
|
);
|
|
|
|
// Set Data Items
|
|
setItems(response.data || []);
|
|
|
|
// Set Data Pagination Barang
|
|
if (response.pagination) {
|
|
state.setItemPage(response.pagination.current_page);
|
|
state.setItemTotalPages(response.pagination.total_pages);
|
|
state.setItemTotalRecords(response.pagination.total_records);
|
|
}
|
|
|
|
// Update filteredItems langsung dengan data yang diterima (karena filter sudah di server)
|
|
state.setFilteredItems(response.data || []);
|
|
} catch (error) {
|
|
showToast("Gagal memuat data barang", "error");
|
|
console.error(error);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const loadClaims = async () => {
|
|
try {
|
|
setLoading(true);
|
|
const response = await ApiUtils.get(
|
|
`${CONFIG.API_ENDPOINTS.CLAIMS}?page=1&limit=1000`
|
|
);
|
|
setClaims(response.data || []);
|
|
} catch (error) {
|
|
showToast("Gagal memuat data klaim", "error");
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const loadCategories = async () => {
|
|
try {
|
|
setLoading(true);
|
|
const response = await fetch(
|
|
`${CONFIG.API_URL}${CONFIG.API_ENDPOINTS.CATEGORIES}`,
|
|
{
|
|
method: "GET",
|
|
headers: {
|
|
Authorization: `Bearer ${AuthUtils.getToken()}`,
|
|
},
|
|
}
|
|
);
|
|
|
|
if (!response.ok) {
|
|
throw new Error("Gagal memuat kategori");
|
|
}
|
|
|
|
const result = await response.json();
|
|
|
|
// PERUBAHAN 1: Jangan pakai || CONFIG.CATEGORIES
|
|
// Jika data kosong, biarkan array kosong []
|
|
setCategories(result.data || []);
|
|
} catch (error) {
|
|
console.error("Load categories error:", error);
|
|
showToast("Gagal memuat kategori", "error");
|
|
|
|
// PERUBAHAN 2: Hapus fallback di sini
|
|
// Jangan paksa isi dengan data palsu/statis jika error
|
|
setCategories([]);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
// ✅ NEW: Create Category
|
|
const handleCreateCategory = async (e) => {
|
|
e.preventDefault();
|
|
const formData = new FormData(e.target);
|
|
|
|
try {
|
|
setLoading(true);
|
|
showToast("Memproses pembuatan kategori...", "info");
|
|
|
|
const categoryData = {
|
|
name: formData.get("name"),
|
|
value: formData.get("value").toLowerCase().replace(/\s+/g, "_"),
|
|
description: formData.get("description") || "",
|
|
};
|
|
|
|
// ✅ PATH: POST /api/categories
|
|
const response = await fetch(
|
|
`${CONFIG.API_URL}${CONFIG.API_ENDPOINTS.CATEGORIES}`,
|
|
{
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
Authorization: `Bearer ${AuthUtils.getToken()}`,
|
|
},
|
|
body: JSON.stringify(categoryData),
|
|
}
|
|
);
|
|
|
|
if (!response.ok) {
|
|
const errorData = await response.json();
|
|
throw new Error(errorData.error || "Gagal menambahkan kategori");
|
|
}
|
|
|
|
await response.json();
|
|
|
|
showToast("Kategori berhasil ditambahkan!", "success");
|
|
state.setShowCreateCategoryModal(false);
|
|
loadCategories();
|
|
loadData();
|
|
} catch (error) {
|
|
console.error("Create category error:", error);
|
|
showToast("Gagal menambahkan kategori: " + error.message, "error");
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
// ✅ FIXED: Update Category Handler
|
|
const handleUpdateCategory = async (e) => {
|
|
e.preventDefault();
|
|
const formData = new FormData(e.target);
|
|
|
|
try {
|
|
setLoading(true);
|
|
showToast("Memproses update kategori...", "info");
|
|
|
|
const categoryData = {
|
|
name: formData.get("name"),
|
|
value: formData.get("value").toLowerCase().replace(/\s+/g, "_"),
|
|
description: formData.get("description") || "",
|
|
};
|
|
|
|
// ✅ FIX: Tambahkan selectedCategory.id di URL
|
|
// PATH: PUT /api/categories/:id
|
|
const response = await fetch(
|
|
`${CONFIG.API_URL}${CONFIG.API_ENDPOINTS.CATEGORIES}/${state.selectedCategory.id}`,
|
|
{
|
|
method: "PUT",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
Authorization: `Bearer ${AuthUtils.getToken()}`,
|
|
},
|
|
body: JSON.stringify(categoryData),
|
|
}
|
|
);
|
|
|
|
if (!response.ok) {
|
|
const errorData = await response.json();
|
|
throw new Error(errorData.error || "Gagal update kategori");
|
|
}
|
|
|
|
await response.json();
|
|
|
|
showToast("Kategori berhasil diupdate!", "success");
|
|
state.setShowEditCategoryModal(false);
|
|
setSelectedCategory(null);
|
|
loadCategories();
|
|
loadData();
|
|
} catch (error) {
|
|
console.error("Update category error:", error);
|
|
showToast("Gagal update kategori: " + error.message, "error");
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const loadRolesAndPermissions = async () => {
|
|
try {
|
|
setLoading(true);
|
|
const [rolesData, permsData] = await Promise.all([
|
|
ApiUtils.get(CONFIG.API_ENDPOINTS.ROLES), // ✅ Benar (Sesuai config.js)
|
|
ApiUtils.get(CONFIG.API_ENDPOINTS.PERMISSIONS), // ✅ Benar (Sesuai config.js)
|
|
]);
|
|
state.setRoles(rolesData.data || []);
|
|
state.setPermissions(permsData.data || []);
|
|
} catch (error) {
|
|
showToast("Gagal memuat roles/permissions", "error");
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const handleCreateRole = async (formData) => {
|
|
try {
|
|
setLoading(true);
|
|
await ApiUtils.post(CONFIG.API_ENDPOINTS.ROLES, formData);
|
|
|
|
showToast("Role berhasil dibuat", "success");
|
|
state.setShowCreateRoleModal(false);
|
|
loadRolesAndPermissions();
|
|
} catch (error) {
|
|
showToast("Gagal membuat role: " + error.message, "error");
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const handleUpdateRole = async (formData) => {
|
|
try {
|
|
setLoading(true);
|
|
// GANTI DARI: CONFIG.API_ENDPOINTS.ADMIN.ROLES
|
|
// MENJADI: CONFIG.API_ENDPOINTS.ROLES
|
|
await ApiUtils.put(
|
|
`${CONFIG.API_ENDPOINTS.ROLES}/${state.selectedRole.id}`,
|
|
formData
|
|
);
|
|
|
|
showToast("Role berhasil diupdate", "success");
|
|
state.setShowEditRoleModal(false);
|
|
state.setSelectedRole(null);
|
|
loadRolesAndPermissions();
|
|
} catch (error) {
|
|
showToast("Gagal update role: " + error.message, "error");
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const handleDeleteRole = async (id) => {
|
|
if (!confirm("Yakin hapus role ini?")) return;
|
|
try {
|
|
// GANTI DARI: CONFIG.API_ENDPOINTS.ADMIN.ROLES
|
|
// MENJADI: CONFIG.API_ENDPOINTS.ROLES
|
|
await ApiUtils.delete(`${CONFIG.API_ENDPOINTS.ROLES}/${id}`);
|
|
|
|
showToast("Role dihapus", "success");
|
|
loadRolesAndPermissions();
|
|
} catch (error) {
|
|
showToast("Gagal hapus role: " + error.message, "error");
|
|
}
|
|
};
|
|
|
|
const handleEditRole = (role) => {
|
|
state.setSelectedRole(role);
|
|
state.setShowEditRoleModal(true);
|
|
};
|
|
|
|
// ✅ NEW: Delete Category
|
|
const handleDeleteCategory = async (categoryId) => {
|
|
if (!confirm("⚠️ Yakin ingin menghapus kategori ini?")) return;
|
|
|
|
try {
|
|
setLoading(true);
|
|
showToast("Menghapus kategori...", "info");
|
|
|
|
// ✅ FIX: Tambahkan categoryId di URL
|
|
// PATH: DELETE /api/categories/:id
|
|
const response = await fetch(
|
|
`${CONFIG.API_URL}${CONFIG.API_ENDPOINTS.CATEGORIES}/${categoryId}`,
|
|
{
|
|
method: "DELETE",
|
|
headers: {
|
|
Authorization: `Bearer ${AuthUtils.getToken()}`,
|
|
},
|
|
}
|
|
);
|
|
|
|
if (!response.ok) {
|
|
const errorData = await response.json();
|
|
throw new Error(errorData.error || "Gagal menghapus kategori");
|
|
}
|
|
|
|
await response.json();
|
|
|
|
showToast("Kategori berhasil dihapus!", "success");
|
|
loadCategories();
|
|
loadData();
|
|
} catch (error) {
|
|
console.error("Delete category error:", error);
|
|
showToast("Gagal menghapus kategori: " + error.message, "error");
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
// ✅ NEW: Load Archives
|
|
const loadArchives = async () => {
|
|
try {
|
|
setLoading(true);
|
|
const response = await ApiUtils.get(
|
|
`${CONFIG.API_ENDPOINTS.ITEMS}?status=case_closed&page=1&limit=1000`
|
|
);
|
|
setArchives(response.data || []);
|
|
} catch (error) {
|
|
showToast("Gagal memuat data arsip", "error");
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
// Load Audit Logs
|
|
const loadAuditLogs = async (page = 1) => {
|
|
try {
|
|
setLoading(true);
|
|
|
|
// Ambil filter dari state
|
|
const { auditLogActionFilter, auditLogUserFilter } = state;
|
|
|
|
// Susun Query Params
|
|
const queryParams = new URLSearchParams({
|
|
page: page,
|
|
limit: 10, // ✅ Set limit 10 per halaman
|
|
action: auditLogActionFilter || "",
|
|
// Kirim filter lain jika backend mendukung (misal user_id atau search generic)
|
|
// search: state.auditLogSearchTerm || ""
|
|
});
|
|
|
|
// Jika ada filter user (asumsi backend menerima user_id atau username)
|
|
if (auditLogUserFilter) {
|
|
queryParams.append("user_id", auditLogUserFilter); // Sesuaikan dengan parameter backend
|
|
}
|
|
|
|
const response = await ApiUtils.get(
|
|
`${CONFIG.API_ENDPOINTS.ADMIN.AUDIT_LOGS}?${queryParams.toString()}`
|
|
);
|
|
|
|
// Set Data
|
|
setAuditLogs(response.data || []);
|
|
|
|
// ✅ Update State Pagination (Generic State di useAdminState)
|
|
if (response.pagination) {
|
|
setTotalPages(response.pagination.total_pages);
|
|
setTotalRecords(response.pagination.total_records);
|
|
state.setCurrentPage(response.pagination.current_page);
|
|
}
|
|
|
|
// Karena pagination sekarang di server, filteredAuditLogs sama dengan data yang diterima
|
|
state.setFilteredAuditLogs(response.data || []);
|
|
} catch (error) {
|
|
showToast("Gagal memuat audit log", "error");
|
|
console.error(error);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
// ✅ NEW: View Archive Detail
|
|
const handleViewArchiveDetail = async (archive) => {
|
|
try {
|
|
const response = await ApiUtils.get(
|
|
`${CONFIG.API_ENDPOINTS.ITEMS}/${archive.id}`
|
|
);
|
|
setSelectedArchive(response.data || response);
|
|
setShowArchiveDetailModal(true);
|
|
} catch (error) {
|
|
showToast("Gagal memuat detail arsip", "error");
|
|
}
|
|
};
|
|
|
|
const handleCreateClaim = async (e) => {
|
|
e.preventDefault();
|
|
const formData = new FormData(e.target);
|
|
|
|
try {
|
|
setLoading(true);
|
|
showToast("Memproses pembuatan klaim...", "info");
|
|
|
|
const claimData = {
|
|
item_id: parseInt(formData.get("item_id")),
|
|
user_id: parseInt(formData.get("user_id")),
|
|
description: formData.get("description"),
|
|
contact: formData.get("contact"),
|
|
admin_notes: formData.get("admin_notes") || "",
|
|
};
|
|
|
|
await ApiUtils.post(CONFIG.API_ENDPOINTS.CLAIMS, claimData);
|
|
|
|
showToast("Klaim berhasil ditambahkan!", "success");
|
|
setShowCreateClaimModal(false);
|
|
loadClaims();
|
|
loadData();
|
|
} catch (error) {
|
|
showToast("Gagal menambahkan klaim: " + error.message, "error");
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const handleEditClaimClick = async (claim) => {
|
|
try {
|
|
const response = await ApiUtils.get(
|
|
`${CONFIG.API_ENDPOINTS.CLAIMS}/${claim.id}`
|
|
);
|
|
setSelectedClaimToEdit(response.data || response);
|
|
setShowEditClaimModal(true);
|
|
} catch (error) {
|
|
showToast("Gagal memuat data klaim", "error");
|
|
}
|
|
};
|
|
|
|
const handleUpdateClaim = async (e) => {
|
|
e.preventDefault();
|
|
const formData = new FormData(e.target);
|
|
|
|
try {
|
|
setLoading(true);
|
|
showToast("Memproses update klaim...", "info");
|
|
|
|
const claimData = {
|
|
description: formData.get("description"),
|
|
contact: formData.get("contact"),
|
|
reason: formData.get("reason") || "Admin update",
|
|
};
|
|
|
|
await ApiUtils.patch(
|
|
`${CONFIG.API_ENDPOINTS.CLAIMS}/${state.selectedClaimToEdit.id}`,
|
|
claimData
|
|
);
|
|
|
|
showToast("Klaim berhasil diupdate!", "success");
|
|
setShowEditClaimModal(false);
|
|
setSelectedClaimToEdit(null);
|
|
loadClaims();
|
|
loadData();
|
|
} catch (error) {
|
|
showToast("Gagal update klaim: " + error.message, "error");
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const handleViewClaimDetail = async (claim) => {
|
|
try {
|
|
const response = await ApiUtils.get(
|
|
`${CONFIG.API_ENDPOINTS.CLAIMS}/${claim.id}`
|
|
);
|
|
setSelectedClaim(response.data || response);
|
|
setShowClaimDetailModal(true);
|
|
} catch (error) {
|
|
showToast("Gagal memuat detail klaim", "error");
|
|
}
|
|
};
|
|
|
|
const handleDeleteClaim = async (claimId) => {
|
|
if (!confirm("⚠️ Yakin ingin menghapus klaim ini?")) return;
|
|
|
|
try {
|
|
setLoading(true);
|
|
await ApiUtils.delete(`${CONFIG.API_ENDPOINTS.CLAIMS}/${claimId}`);
|
|
showToast("Klaim berhasil dihapus!", "success");
|
|
loadClaims();
|
|
loadData();
|
|
} catch (error) {
|
|
showToast("Gagal menghapus klaim: " + error.message, "error");
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const handleViewItemDetail = async (item) => {
|
|
try {
|
|
const response = await ApiUtils.get(
|
|
`${CONFIG.API_ENDPOINTS.ITEMS}/${item.id}`
|
|
);
|
|
setSelectedItemDetail(response.data || response);
|
|
setShowItemDetailModal(true);
|
|
} catch (error) {
|
|
showToast("Gagal memuat detail barang", "error");
|
|
}
|
|
};
|
|
|
|
const handleCreateItem = async (e) => {
|
|
e.preventDefault();
|
|
const formData = new FormData(e.target);
|
|
|
|
try {
|
|
setLoading(true);
|
|
showToast("Memproses pembuatan barang...", "info");
|
|
|
|
let photoUrl = "";
|
|
const photoFile = formData.get("photo");
|
|
if (photoFile && photoFile.size > 0) {
|
|
showToast("Mengupload foto...", "info");
|
|
const uploadFormData = new FormData();
|
|
uploadFormData.append("image", photoFile);
|
|
const uploadData = await ApiUtils.uploadFile(
|
|
CONFIG.API_ENDPOINTS.UPLOAD,
|
|
uploadFormData
|
|
);
|
|
photoUrl = uploadData.data.url;
|
|
}
|
|
|
|
const dateFound = formData.get("date_found");
|
|
const dateFoundISO = new Date(dateFound + "T00:00:00Z").toISOString();
|
|
|
|
const itemData = {
|
|
name: formData.get("name"),
|
|
category_id: parseInt(formData.get("category_id")),
|
|
photo_url: photoUrl,
|
|
location: formData.get("location"),
|
|
description: formData.get("description"),
|
|
secret_details: formData.get("secret_details"),
|
|
date_found: dateFoundISO,
|
|
reporter_name: formData.get("reporter_name"),
|
|
reporter_contact: formData.get("reporter_contact"),
|
|
};
|
|
|
|
await ApiUtils.post(CONFIG.API_ENDPOINTS.ITEMS, itemData);
|
|
|
|
showToast("Barang berhasil ditambahkan!", "success");
|
|
setShowCreateItemModal(false);
|
|
setPhotoPreview(null);
|
|
loadItems();
|
|
loadData();
|
|
} catch (error) {
|
|
showToast("Gagal menambahkan barang: " + error.message, "error");
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const handleEditItemClick = async (item) => {
|
|
try {
|
|
const response = await ApiUtils.get(
|
|
`${CONFIG.API_ENDPOINTS.ITEMS}/${item.id}`
|
|
);
|
|
setSelectedItemToEdit(response.data || response);
|
|
setShowEditItemModal(true);
|
|
} catch (error) {
|
|
showToast("Gagal memuat data barang", "error");
|
|
}
|
|
};
|
|
|
|
const handleUpdateItem = async (e) => {
|
|
e.preventDefault();
|
|
const formData = new FormData(e.target);
|
|
|
|
try {
|
|
setLoading(true);
|
|
showToast("Memproses update barang...", "info");
|
|
|
|
// 🔧 FIX 1: Upload foto baru jika ada
|
|
let photoUrl = state.selectedItemToEdit.photo_url; // Gunakan foto lama sebagai default
|
|
const photoFile = formData.get("photo");
|
|
|
|
if (photoFile && photoFile.size > 0) {
|
|
showToast("Mengupload foto baru...", "info");
|
|
const uploadFormData = new FormData();
|
|
uploadFormData.append("image", photoFile);
|
|
|
|
try {
|
|
const uploadData = await ApiUtils.uploadFile(
|
|
CONFIG.API_ENDPOINTS.UPLOAD,
|
|
uploadFormData
|
|
);
|
|
photoUrl = uploadData.data.url;
|
|
} catch (uploadError) {
|
|
console.error("Upload error:", uploadError);
|
|
showToast(
|
|
"Gagal upload foto, melanjutkan dengan foto lama",
|
|
"warning"
|
|
);
|
|
}
|
|
}
|
|
|
|
const dateFound = formData.get("date_found");
|
|
const dateFoundISO = new Date(dateFound + "T00:00:00Z").toISOString();
|
|
const categoryId = parseInt(formData.get("category_id"));
|
|
|
|
if (isNaN(categoryId)) {
|
|
showToast("Error: Category ID tidak valid!", "error");
|
|
return;
|
|
}
|
|
|
|
// 🔧 FIX 2: Selalu kirim photo_url
|
|
const itemData = {
|
|
name: formData.get("name"),
|
|
category_id: categoryId,
|
|
photo_url: photoUrl, // ✅ TAMBAHKAN INI
|
|
location: formData.get("location"),
|
|
description: formData.get("description"),
|
|
secret_details: formData.get("secret_details"),
|
|
date_found: dateFoundISO,
|
|
reporter_name: formData.get("reporter_name"),
|
|
reporter_contact: formData.get("reporter_contact"),
|
|
status: formData.get("status"),
|
|
reason: formData.get("reason") || "Admin update",
|
|
};
|
|
|
|
console.log("📤 Data yang akan dikirim:", itemData);
|
|
console.log("📤 Item yang sedang diedit:", state.selectedItemToEdit);
|
|
|
|
await ApiUtils.put(
|
|
`${CONFIG.API_ENDPOINTS.ITEMS}/${state.selectedItemToEdit.id}`,
|
|
itemData
|
|
);
|
|
|
|
showToast("Barang berhasil diupdate!", "success");
|
|
setShowEditItemModal(false);
|
|
setSelectedItemToEdit(null);
|
|
setPhotoPreview(null); // Reset preview
|
|
loadItems();
|
|
loadData();
|
|
} catch (error) {
|
|
console.error("❌ Update error:", error);
|
|
showToast("Gagal update barang: " + error.message, "error");
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const handleDeleteItem = async (itemId) => {
|
|
if (!confirm("⚠️ Yakin ingin menghapus barang ini?")) return;
|
|
|
|
try {
|
|
setLoading(true);
|
|
await ApiUtils.delete(`${CONFIG.API_ENDPOINTS.ITEMS}/${itemId}`);
|
|
showToast("Barang berhasil dihapus!", "success");
|
|
loadItems();
|
|
loadData();
|
|
} catch (error) {
|
|
showToast("Gagal menghapus barang: " + error.message, "error");
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const handlePhotoChange = (e) => {
|
|
const file = e.target.files[0];
|
|
if (file) {
|
|
const reader = new FileReader();
|
|
reader.onloadend = () => {
|
|
setPhotoPreview(reader.result);
|
|
};
|
|
reader.readAsDataURL(file);
|
|
}
|
|
};
|
|
|
|
const handleExport = async (format) => {
|
|
try {
|
|
setLoading(true);
|
|
|
|
const period =
|
|
document.querySelector('select[name="period"]')?.value || "month";
|
|
const reportType =
|
|
document.querySelector('select[name="report_type"]')?.value || "all";
|
|
|
|
const endDate = new Date();
|
|
const startDate = new Date();
|
|
|
|
switch (period) {
|
|
case "month":
|
|
startDate.setMonth(startDate.getMonth() - 1);
|
|
break;
|
|
case "semester":
|
|
startDate.setMonth(startDate.getMonth() - 6);
|
|
break;
|
|
case "year":
|
|
startDate.setFullYear(startDate.getFullYear() - 1);
|
|
break;
|
|
}
|
|
|
|
const requestBody = {
|
|
type: reportType,
|
|
format: format,
|
|
start_date: startDate.toISOString(),
|
|
end_date: endDate.toISOString(),
|
|
};
|
|
|
|
showToast(`Generating ${format.toUpperCase()} report...`, "info");
|
|
|
|
const response = await fetch(
|
|
`${CONFIG.API_URL}${CONFIG.API_ENDPOINTS.ADMIN.EXPORT}`,
|
|
{
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
Authorization: `Bearer ${AuthUtils.getToken()}`,
|
|
},
|
|
body: JSON.stringify(requestBody),
|
|
}
|
|
);
|
|
|
|
if (!response.ok) {
|
|
const errorText = await response.text();
|
|
throw new Error(`Export failed: ${errorText}`);
|
|
}
|
|
|
|
const contentDisposition = response.headers.get("Content-Disposition");
|
|
let filename = `report_${Date.now()}.${
|
|
format === "pdf" ? "pdf" : "xlsx"
|
|
}`;
|
|
|
|
if (contentDisposition) {
|
|
const filenameMatch = contentDisposition.match(/filename="?(.+)"?/);
|
|
if (filenameMatch) {
|
|
filename = filenameMatch[1];
|
|
}
|
|
}
|
|
|
|
const blob = await response.blob();
|
|
const url = window.URL.createObjectURL(blob);
|
|
const a = document.createElement("a");
|
|
a.href = url;
|
|
a.download = filename;
|
|
document.body.appendChild(a);
|
|
a.click();
|
|
window.URL.revokeObjectURL(url);
|
|
document.body.removeChild(a);
|
|
|
|
showToast(`${format.toUpperCase()} exported successfully!`, "success");
|
|
} catch (error) {
|
|
showToast(`Failed to export: ${error.message}`, "error");
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const handleLogout = () => {
|
|
if (Helpers.showConfirm("Apakah Anda yakin ingin logout?")) {
|
|
AuthUtils.clearAuth();
|
|
window.location.href = "/login";
|
|
}
|
|
};
|
|
|
|
const handleEditUser = (userData) => {
|
|
setSelectedUser(userData);
|
|
setShowEditModal(true);
|
|
};
|
|
|
|
const handleUpdateUser = async (e) => {
|
|
e.preventDefault();
|
|
const formData = new FormData(e.target);
|
|
const role = formData.get("role");
|
|
|
|
const roleMapping = {
|
|
user: 3,
|
|
manager: 2,
|
|
admin: 1,
|
|
};
|
|
|
|
const role_id = roleMapping[role];
|
|
|
|
try {
|
|
setLoading(true);
|
|
await ApiUtils.patch(
|
|
`${CONFIG.API_ENDPOINTS.ADMIN.USERS}/${state.selectedUser.id}/role`,
|
|
{ role_id }
|
|
);
|
|
showToast("User berhasil diupdate!", "success");
|
|
setShowEditModal(false);
|
|
loadData();
|
|
} catch (error) {
|
|
showToast(`Gagal update user: ${error.message}`, "error");
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const handleBlockUser = async (userId) => {
|
|
if (!Helpers.showConfirm("Block user ini?")) return;
|
|
try {
|
|
await ApiUtils.post(
|
|
`${CONFIG.API_ENDPOINTS.ADMIN.USERS}/${userId}/block`,
|
|
{}
|
|
);
|
|
showToast("User berhasil diblock!", "success");
|
|
loadData();
|
|
} catch (error) {
|
|
showToast("Gagal block user", "error");
|
|
}
|
|
};
|
|
|
|
const handleUnblockUser = async (userId) => {
|
|
if (!Helpers.showConfirm("Unblock user ini?")) return;
|
|
try {
|
|
await ApiUtils.post(
|
|
`${CONFIG.API_ENDPOINTS.ADMIN.USERS}/${userId}/unblock`,
|
|
{}
|
|
);
|
|
showToast("User berhasil di-unblock!", "success");
|
|
loadData();
|
|
} catch (error) {
|
|
showToast("Gagal unblock user", "error");
|
|
}
|
|
};
|
|
|
|
return {
|
|
showToast,
|
|
loadData,
|
|
loadItems,
|
|
handleViewItemDetail,
|
|
handleExport,
|
|
handleLogout,
|
|
handleEditUser,
|
|
handleUpdateUser,
|
|
handleBlockUser,
|
|
handleUnblockUser,
|
|
handleCreateItem,
|
|
handleEditItemClick,
|
|
handleUpdateItem,
|
|
handleDeleteItem,
|
|
handlePhotoChange,
|
|
// Claims Handlers
|
|
loadClaims,
|
|
handleViewClaimDetail,
|
|
handleDeleteClaim,
|
|
handleCreateClaim,
|
|
handleEditClaimClick,
|
|
handleUpdateClaim,
|
|
loadCategories,
|
|
handleCreateCategory,
|
|
handleUpdateCategory,
|
|
handleDeleteCategory,
|
|
// ✅ NEW: Archives Handlers
|
|
loadArchives,
|
|
handleViewArchiveDetail,
|
|
loadAuditLogs,
|
|
loadRolesAndPermissions,
|
|
handleCreateRole,
|
|
handleUpdateRole,
|
|
handleDeleteRole,
|
|
handleEditRole,
|
|
loadLostItems,
|
|
handleViewLostItemDetail,
|
|
handleCreateLostItem,
|
|
handleEditLostItemClick,
|
|
handleUpdateLostItem,
|
|
handleDeleteLostItem,
|
|
};
|
|
};
|