430 lines
13 KiB
JavaScript
430 lines
13 KiB
JavaScript
const useUserHandlers = (state) => {
|
||
const {
|
||
setToast,
|
||
setLoading,
|
||
setStats,
|
||
setItems,
|
||
setMyFoundItems,
|
||
setMyLostItems,
|
||
setPublicLostItems,
|
||
setMyClaims,
|
||
setSelectedItem,
|
||
setShowDetailModal,
|
||
setShowClaimModal,
|
||
setShowReportFoundModal,
|
||
setPhotoPreview,
|
||
setSuggestedLostItems,
|
||
setShowSelectLostItemModal,
|
||
setClaimInitialData,
|
||
myLostItems,
|
||
} = state;
|
||
|
||
const showToast = (message, type = "info") => {
|
||
setToast({ message, type });
|
||
};
|
||
|
||
const loadData = async () => {
|
||
try {
|
||
state.setLoading(true);
|
||
const [
|
||
itemsResponse,
|
||
myFoundResponse,
|
||
myLostResponse,
|
||
publicLostResponse,
|
||
myClaimsResponse,
|
||
categoriesResponse,
|
||
] = await Promise.all([
|
||
ApiUtils.get(`${CONFIG.API_ENDPOINTS.ITEMS}?page=1&limit=100`),
|
||
ApiUtils.get(CONFIG.API_ENDPOINTS.USER.ITEMS).catch(() => ({
|
||
data: [],
|
||
})),
|
||
ApiUtils.get(CONFIG.API_ENDPOINTS.USER.LOST_ITEMS).catch(() => ({
|
||
data: [],
|
||
})),
|
||
ApiUtils.get(CONFIG.API_ENDPOINTS.LOST_ITEMS).catch(() => ({
|
||
data: [],
|
||
})),
|
||
ApiUtils.get(CONFIG.API_ENDPOINTS.USER.CLAIMS).catch(() => ({
|
||
data: [],
|
||
})),
|
||
ApiUtils.get(
|
||
CONFIG.API_ENDPOINTS.CATEGORIES || "/api/categories"
|
||
).catch(() => ({ data: [] })),
|
||
]);
|
||
|
||
state.setStats({
|
||
lost_items: myLostResponse.data?.length || 0,
|
||
found_items: myFoundResponse.data?.length || 0,
|
||
claims: myClaimsResponse.data?.length || 0,
|
||
});
|
||
|
||
state.setItems(itemsResponse.data || []);
|
||
state.setMyFoundItems(myFoundResponse.data || []);
|
||
state.setMyLostItems(myLostResponse.data || []);
|
||
state.setPublicLostItems(publicLostResponse.data || []);
|
||
state.setMyClaims(myClaimsResponse.data || []);
|
||
|
||
if (state.setCategories) {
|
||
state.setCategories(categoriesResponse.data || []);
|
||
}
|
||
} catch (error) {
|
||
console.error("Error loading data:", error);
|
||
showToast("Gagal memuat data. Cek koneksi database.", "error");
|
||
} finally {
|
||
state.setLoading(false);
|
||
}
|
||
};
|
||
|
||
const handleLogout = () => {
|
||
if (Helpers.showConfirm("Apakah Anda yakin ingin logout?")) {
|
||
AuthUtils.clearAuth();
|
||
window.location.href = "/login";
|
||
}
|
||
};
|
||
|
||
const handleViewDetail = async (item) => {
|
||
try {
|
||
const response = await ApiUtils.get(
|
||
`${CONFIG.API_ENDPOINTS.ITEMS}/${item.id}`
|
||
);
|
||
setSelectedItem(response.data || response);
|
||
setShowDetailModal(true);
|
||
} catch (error) {
|
||
showToast("Gagal memuat detail", "error");
|
||
}
|
||
};
|
||
|
||
const handleUserRespondClaim = async (claimId, action) => {
|
||
const confirmMsg =
|
||
action === "approve"
|
||
? '✅ Yakin ini barang Anda?\n\nJika approve:\n- Status akan menjadi "Verified"\n- Anda bisa koordinasi pengambilan dengan penemu\n- Setelah terima barang, konfirmasi untuk case closed'
|
||
: "❌ Yakin menolak?\n\nJika reject:\n- Klaim akan ditolak\n- Barang kembali dalam status pencarian\n- Orang lain bisa mengajukan klaim";
|
||
|
||
if (!confirm(confirmMsg)) return;
|
||
|
||
try {
|
||
state.setLoading(true);
|
||
// FIXED: URL endpoint sesuai controller
|
||
await ApiUtils.post(`/api/claims/${claimId}/respond`, {
|
||
action: action,
|
||
});
|
||
|
||
showToast(
|
||
action === "approve"
|
||
? "✅ Berhasil! Silakan koordinasi pengambilan dengan penemu."
|
||
: "❌ Klaim ditolak. Barang kembali dalam pencarian.",
|
||
"success"
|
||
);
|
||
|
||
// Reload data agar tampilan tombol berubah
|
||
await loadData();
|
||
} catch (error) {
|
||
console.error("Error responding:", error);
|
||
showToast(
|
||
"Gagal memproses: " + (error.response?.data?.message || error.message),
|
||
"error"
|
||
);
|
||
} finally {
|
||
state.setLoading(false);
|
||
}
|
||
};
|
||
|
||
const handleOpenDirectClaimModal = (lostItem) => {
|
||
console.log("Membuka direct claim modal untuk:", lostItem.name);
|
||
state.setSelectedLostItemForFound(lostItem);
|
||
state.setShowDirectClaimModal(true);
|
||
};
|
||
|
||
const handleClaim = (item) => {
|
||
setSelectedItem(item);
|
||
const matches = myLostItems.filter(
|
||
(lostItem) =>
|
||
lostItem.status === "active" &&
|
||
lostItem.category_id === item.category_id
|
||
);
|
||
|
||
if (matches.length > 0) {
|
||
setSuggestedLostItems(matches);
|
||
setShowSelectLostItemModal(true);
|
||
} else {
|
||
setClaimInitialData(null);
|
||
setShowClaimModal(true);
|
||
}
|
||
};
|
||
|
||
const handleSelectLostItemForClaim = (lostItem) => {
|
||
const initialData = {
|
||
description: `[Berdasarkan Laporan Hilang #${lostItem.id} - ${lostItem.name}]\n${lostItem.description}`,
|
||
contact: state.user?.phone || "",
|
||
};
|
||
setClaimInitialData(initialData);
|
||
setShowSelectLostItemModal(false);
|
||
setShowClaimModal(true);
|
||
};
|
||
|
||
const handleManualClaimEntry = () => {
|
||
setClaimInitialData(null);
|
||
setShowSelectLostItemModal(false);
|
||
setShowClaimModal(true);
|
||
};
|
||
|
||
const submitClaim = async (e) => {
|
||
e.preventDefault();
|
||
const formData = new FormData(e.target);
|
||
|
||
if (!formData.get("description") || !formData.get("contact")) {
|
||
showToast("Deskripsi dan Kontak harus diisi", "error");
|
||
return;
|
||
}
|
||
|
||
try {
|
||
setLoading(true);
|
||
showToast("Memproses klaim...", "info");
|
||
|
||
let proofUrl = "";
|
||
const proofFile = formData.get("proof");
|
||
if (proofFile && proofFile.size > 0) {
|
||
showToast("Mengupload bukti...", "info");
|
||
const uploadFormData = new FormData();
|
||
uploadFormData.append("proof", proofFile);
|
||
const uploadResponse = await ApiUtils.uploadFile(
|
||
"/api/upload/claim-proof",
|
||
uploadFormData
|
||
);
|
||
proofUrl = uploadResponse.data.url;
|
||
}
|
||
|
||
const payload = {
|
||
item_id: parseInt(state.selectedItem.id),
|
||
description: formData.get("description"),
|
||
contact: formData.get("contact"),
|
||
proof_url: proofUrl,
|
||
};
|
||
|
||
await ApiUtils.post(CONFIG.API_ENDPOINTS.CLAIMS, payload);
|
||
showToast("Klaim berhasil disubmit!", "success");
|
||
state.setShowClaimModal(false);
|
||
loadData();
|
||
} catch (error) {
|
||
showToast(error.message || "Gagal submit klaim", "error");
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
};
|
||
|
||
const submitReportLost = async (e) => {
|
||
e.preventDefault();
|
||
const formData = new FormData(e.target);
|
||
|
||
try {
|
||
setLoading(true);
|
||
const dateLost = formData.get("date_lost");
|
||
const dateLostISO = new Date(dateLost + "T00:00:00Z").toISOString();
|
||
|
||
const data = {
|
||
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, data);
|
||
showToast("Laporan kehilangan berhasil!", "success");
|
||
state.setShowReportLostModal(false);
|
||
loadData();
|
||
} catch (error) {
|
||
showToast("Gagal submit laporan: " + error.message, "error");
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
};
|
||
|
||
const submitReportFound = async (e) => {
|
||
e.preventDefault();
|
||
const formData = new FormData(e.target);
|
||
|
||
try {
|
||
setLoading(true);
|
||
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 payload = {
|
||
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"),
|
||
};
|
||
|
||
if (
|
||
state.selectedLostItemForFound &&
|
||
state.foundReportMethod === "direct"
|
||
) {
|
||
payload.lost_item_id = state.selectedLostItemForFound.id;
|
||
payload.verification_flow = "direct";
|
||
payload.is_direct_to_owner = true;
|
||
}
|
||
|
||
await ApiUtils.post(CONFIG.API_ENDPOINTS.ITEMS, payload);
|
||
|
||
if (
|
||
state.foundReportMethod === "direct" &&
|
||
state.selectedLostItemForFound
|
||
) {
|
||
showToast("Laporan terkirim ke PEMILIK untuk disetujui!", "success");
|
||
} else {
|
||
showToast("Laporan terkirim ke MANAGER untuk verifikasi.", "success");
|
||
}
|
||
|
||
setShowReportFoundModal(false);
|
||
setPhotoPreview(null);
|
||
state.setSelectedLostItemForFound(null);
|
||
loadData();
|
||
} catch (error) {
|
||
showToast("Gagal mengirim laporan: " + error.message, "error");
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
};
|
||
|
||
const handleOpenFoundOption = (lostItem) => {
|
||
console.log("Membuka opsi penemuan untuk:", lostItem.name);
|
||
state.setSelectedLostItemForFound(lostItem);
|
||
state.setShowFoundOptionModal(true);
|
||
};
|
||
|
||
const handleSelectFoundOption = (method) => {
|
||
console.log("Metode dipilih:", method);
|
||
state.setFoundReportMethod(method);
|
||
state.setShowFoundOptionModal(false);
|
||
|
||
if (method === "direct") {
|
||
handleDirectClaimFlow();
|
||
} else {
|
||
state.setShowReportFoundModal(true);
|
||
}
|
||
};
|
||
|
||
const handleDirectClaimFlow = () => {
|
||
if (!state.selectedLostItemForFound) {
|
||
showToast("Tidak ada lost item yang dipilih", "error");
|
||
return;
|
||
}
|
||
state.setShowDirectClaimModal(true);
|
||
};
|
||
|
||
const handlePhotoChange = (e) => {
|
||
const file = e.target.files[0];
|
||
if (file) {
|
||
const reader = new FileReader();
|
||
reader.onloadend = () => {
|
||
setPhotoPreview(reader.result);
|
||
};
|
||
reader.readAsDataURL(file);
|
||
}
|
||
};
|
||
|
||
const handleUserCompleteCase = async (claimId) => {
|
||
const confirmMsg =
|
||
'🎉 Konfirmasi Penerimaan Barang\n\nApakah Anda sudah menerima barang fisik ini?\n\n✅ Jika Ya:\n- Status akan menjadi "Completed"\n- Kasus ditutup secara permanen\n- Tidak bisa dibatalkan\n\n⚠️ Pastikan barang sudah di tangan Anda!';
|
||
|
||
if (!confirm(confirmMsg)) return;
|
||
|
||
try {
|
||
state.setLoading(true);
|
||
|
||
// FIXED: Gunakan /api/claims/:id/complete (bukan /api/user/claims/:id/complete)
|
||
await ApiUtils.post(`/api/claims/${claimId}/complete`, {});
|
||
|
||
showToast(
|
||
"🎉 Selamat! Barang Anda telah kembali. Kasus selesai.",
|
||
"success"
|
||
);
|
||
|
||
loadData();
|
||
} catch (error) {
|
||
console.error("Error completing case:", error);
|
||
showToast("Gagal menyelesaikan: " + error.message, "error");
|
||
} finally {
|
||
state.setLoading(false);
|
||
}
|
||
};
|
||
|
||
const handleDirectClaimSubmit = async (e) => {
|
||
e.preventDefault();
|
||
const formData = new FormData(e.target);
|
||
|
||
if (!state.selectedLostItemForFound) {
|
||
showToast("Tidak ada lost item yang dipilih", "error");
|
||
return;
|
||
}
|
||
|
||
try {
|
||
state.setLoading(true);
|
||
|
||
const payload = {
|
||
description: formData.get("description"),
|
||
contact: formData.get("contact"),
|
||
proof_url: formData.get("proof_url") || "",
|
||
};
|
||
|
||
await ApiUtils.post(
|
||
`/api/lost-items/${state.selectedLostItemForFound.id}/direct-claim`,
|
||
payload
|
||
);
|
||
|
||
showToast("✅ Klaim terkirim ke pemilik untuk persetujuan!", "success");
|
||
state.setShowDirectClaimModal(false);
|
||
state.setSelectedLostItemForFound(null);
|
||
loadData();
|
||
} catch (error) {
|
||
console.error("Direct claim error:", error);
|
||
showToast("Gagal mengirim klaim: " + error.message, "error");
|
||
} finally {
|
||
state.setLoading(false);
|
||
}
|
||
};
|
||
|
||
return {
|
||
showToast,
|
||
loadData,
|
||
handleLogout,
|
||
handleViewDetail,
|
||
handleClaim,
|
||
submitClaim,
|
||
submitReportLost,
|
||
submitReportFound,
|
||
handlePhotoChange,
|
||
handleSelectLostItemForClaim,
|
||
handleManualClaimEntry,
|
||
handleOpenFoundOption,
|
||
handleSelectFoundOption,
|
||
handleUserRespondClaim,
|
||
handleUserCompleteCase,
|
||
handleDirectClaimSubmit,
|
||
handleDirectClaimFlow,
|
||
handleOpenDirectClaimModal,
|
||
};
|
||
};
|
||
|
||
window.useUserHandlers = useUserHandlers;
|