import FullScreenLoading from "@/components/FullScreenLoading"; import ModalFile from "@/components/ModalFile"; import { DataNotFound } from "@/components/NotFoundPengajuanSurat"; import notification from "@/components/notificationGlobal"; import SuccessPengajuan from "@/components/SuccessPengajuanSurat"; import apiFetch from "@/lib/apiFetch"; import { parseTanggalID } from "@/server/lib/stringToDate"; import { ActionIcon, Alert, Anchor, Badge, Box, Button, Card, Container, Divider, FileInput, Flex, Grid, Group, Modal, Select, Stack, Text, TextInput, Tooltip, } from "@mantine/core"; import { DateInput } from "@mantine/dates"; import { useDisclosure, useShallowEffect } from "@mantine/hooks"; import { IconBuildingCommunity, IconFiles, IconInfoCircle, IconNotes, IconUpload, } from "@tabler/icons-react"; import dayjs from "dayjs"; import "dayjs/locale/id"; import _ from "lodash"; import React, { useState } from "react"; import { useLocation, useNavigate } from "react-router-dom"; type DataItem = { key: string; value: string; }; type UpdateDataItem = { id: string; key: string; value: any; }; type FormSurat = { kategoriId: string; nama: string; phone: string; dataPelengkap: DataItem[]; syaratDokumen: DataItem[]; }; type FormUpdateSurat = { dataPelengkap: UpdateDataItem[]; syaratDokumen: UpdateDataItem[]; }; type DataPengajuan = { id: string; noPengajuan: string; category: string; status: "antrian" | "diproses" | "selesai" | "ditolak"; createdAt: Date; updatedAt: Date; idSurat: string | undefined; alasan: string | undefined | null; }; export default function UpdateDataSurat() { const navigate = useNavigate(); const { search } = useLocation(); const query = new URLSearchParams(search); const noPengajuan = query.get("pengajuan"); const [found, setFound] = useState(true); return ( {found && ( Update Data Pengajuan Surat Administrasi Formulir ini digunakan untuk memperbarui data pengajuan surat administrasi yang telah diajukan sebelumnya. )} {!noPengajuan ? ( ) : found ? ( { setFound(e); }} /> ) : ( navigate("/darmasaba/update-data-surat")} /> )} ); } function FieldLabel({ label, hint }: { label: string; hint?: string }) { return ( {label} {hint && ( )} ); } function FormSection({ title, icon, children, description, info, }: { title?: string; icon?: React.ReactNode; children: React.ReactNode; description?: string; info?: string; }) { return ( {icon} {title && {title}} {description && {description}} {info && ( {info} )} {title && } {children} ); } function FileInputWrapper({ label, placeholder, accept, onChange, preview, name, description, linkView, disabled, }: { label: string; placeholder?: string; accept?: string; linkView?: string; onChange: (file: File | null) => void; preview?: string | null; name: string; description?: string; disabled?: boolean; }) { const [viewImg, setViewImg] = useState(""); const [openedPreviewFile, setOpenedPreviewFile] = useState(false); useShallowEffect(() => { if (viewImg) { setOpenedPreviewFile(true); } }, [viewImg]); return ( <> { setOpenedPreviewFile(false); }} folder="syarat-dokumen" fileName={viewImg} /> {label} {description && ( {description} )} {linkView && ( setViewImg(linkView)} size="sm"> Lihat dokumen sebelumnya )} onChange(f)} leftSection={} aria-label={label} name={name} disabled={disabled} /> {preview ? ( Preview: ) : null} > ); } function SearchData() { const [submitLoading, setSubmitLoading] = useState(false); const [searchPengajuan, setSearchPengajuan] = useState(""); const [searchPengajuanPhone, setSearchPengajuanPhone] = useState(""); const navigate = useNavigate(); async function handleSearch() { try { setSubmitLoading(true); if (searchPengajuan == "" || searchPengajuanPhone == "") { notification({ title: "Peringatan", message: "Silakan isi nomor pengajuan atau nomor telephone", type: "warning", }); return; } const response = await apiFetch.api.pelayanan["get-no-pengajuan"].post({ phone: searchPengajuanPhone, noPengajuan: searchPengajuan, }); if (response.status === 200) { if (response.data?.success) { navigate( `/darmasaba/update-data-surat?pengajuan=${response.data.nomer}`, ); } else { notification({ title: "Peringatan", message: response.data?.message || "Data pengajuan tidak valid", type: "warning", }); } } else { notification({ title: "Error", message: "Pengajuan tidak ditemukan atau gagal memuat data", type: "error", }); } } catch (error) { console.error("Error searching:", error); notification({ title: "Error", message: "Gagal mencari data pengajuan", type: "error", }); } finally { setSubmitLoading(false); } } return ( } placeholder="PS-2025-000123" onChange={(e) => { setSearchPengajuan(e.target.value); }} /> } placeholder="08123456789" type="number" onChange={(e) => { setSearchPengajuanPhone(e.target.value); }} /> { handleSearch(); }} loading={submitLoading} > Cari Pengajuan ); } function DataUpdate({ noPengajuan, onValidate, }: { noPengajuan: string; onValidate: (e: boolean) => void; }) { const [opened, { open, close }] = useDisclosure(false); const navigate = useNavigate(); const [errors, setErrors] = useState>({}); const [sukses, setSukses] = useState(false); const [submitLoading, setSubmitLoading] = useState(false); const [dataPelengkap, setDataPelengkap] = useState([]); const [dataSyaratDokumen, setDataSyaratDokumen] = useState([]); const [dataPengajuan, setDataPengajuan] = useState({}); const [status, setStatus] = useState(""); const [formSurat, setFormSurat] = useState({ dataPelengkap: [], syaratDokumen: [], }); async function fetchData() { try { const res = await apiFetch.api.pelayanan["detail-data"].post({ nomerPengajuan: noPengajuan, }); if (res.data && res.data.success === true) { onValidate(true); setDataPelengkap(res.data.dataPelengkap || []); setDataSyaratDokumen(res.data.syaratDokumen || []); setDataPengajuan(res.data.pengajuan || {}); setStatus( res.data.pengajuan && "status" in res.data.pengajuan ? res.data.pengajuan.status : "", ); } else { // notification({ // title: "Error", // message: res.data?.message || "Gagal memuat data", // type: "error", // }); onValidate(false); setDataPelengkap([]); setDataSyaratDokumen([]); setDataPengajuan({}); } } catch (error) { console.error("Error fetching data:", error); } } useShallowEffect(() => { fetchData(); }, []); function validateField(key: string, value: any) { const stringValue = String(value ?? "").trim(); // wajib diisi if (!stringValue) { return "Field wajib diisi"; } // 🔥 semua key yg mengandung "nik" if (key.toLowerCase().includes("nik")) { if (!/^\d+$/.test(stringValue)) { return "NIK harus berupa angka"; } if (stringValue.length !== 16) { return "NIK harus 16 digit"; } } return null; } function upsertById(array: T[], item: T): T[] { const index = array.findIndex((v) => v.id === item.id); // ➕ insert if (index === -1) { return [...array, item]; } // ✏️ update return array.map((v, i) => (i === index ? { ...v, ...item } : v)); } function validationForm({ kategori, value, }: { kategori: "dataPelengkap" | "syaratDokumen"; value: UpdateDataItem; }) { const errorMsg = validateField(value.key, value.value); setErrors((prev) => ({ ...prev, [value.id]: errorMsg, })); setFormSurat((prev) => ({ ...prev, [kategori]: upsertById(prev[kategori], { id: value.id, key: value.key, value: value.value, }), })); } function updateArrayByKey( list: UpdateDataItem[], id: string, value: any, ): UpdateDataItem[] { return list.map((item) => (item.id === id ? { ...item, value } : item)); } function onChecking() { const hasError = Object.values(errors).some((v) => v); if (hasError) { return notification({ title: "Gagal", message: "Masih ada data yang belum valid", type: "error", }); } if ( formSurat.dataPelengkap.length == 0 && formSurat.syaratDokumen.length == 0 ) return notification({ title: "Peringatan", message: "Tidak ada data yang diupdate", type: "warning", }); const isFormKosong = Object.values(formSurat).some( (value: UpdateDataItem[] | string) => { if (Array.isArray(value)) { return value.some( (item) => typeof item.value === "string" && item.value.trim() === "", ); } if (typeof value === "string") { return value.trim() === ""; } return false; }, ); if (isFormKosong) { return notification({ title: "Gagal", message: "Silahkan lengkapi form surat", type: "error", }); } else { open(); } } async function onSubmit() { try { setSubmitLoading(true); // 🔥 CLONE state SEKALI let finalFormSurat = structuredClone(formSurat); // 2️⃣ Upload satu per satu for (const itemUpload of finalFormSurat.syaratDokumen) { const updImg = await apiFetch.api.pengaduan.upload.post({ file: itemUpload.value, folder: "syarat-dokumen", }); if (updImg.status === 200) { // 🔥 UPDATE OBJECT LOKAL (BUKAN STATE) finalFormSurat.syaratDokumen = updateArrayByKey( finalFormSurat.syaratDokumen, itemUpload.id, updImg.data?.filename || "", ); } } // 3️⃣ SET STATE SEKALI (optional, untuk UI) setFormSurat(finalFormSurat); // 4️⃣ SUBMIT KE API const res = await apiFetch.api.pelayanan.update.post({ id: dataPengajuan && "id" in dataPengajuan ? dataPengajuan.id : "", dataPelengkap: finalFormSurat.dataPelengkap, syaratDokumen: finalFormSurat.syaratDokumen, }); if (res.status === 200) { setSukses(true); } else { notification({ title: "Gagal", message: "Pengajuan surat gagal dibuat, silahkan coba beberapa saat lagi", type: "error", }); } } catch (error) { notification({ title: "Gagal", message: "Server Error", type: "error", }); } finally { setSubmitLoading(false); } } return ( <> Apakah anda yakin ingin mengupdate pengajuan surat ini? Tidak { onSubmit(); close(); }} > Ya {sukses ? ( { navigate("/darmasaba/update-data-surat"); }} category="update" /> ) : ( <> {status != "ditolak" && status != "antrian" && ( ⚠} /> )} {status == "ditolak" && ( ⚠} /> )} } > {dataPelengkap.map((item: any, index: number) => ( {item.type == "enum" ? ( } data={item.options ?? []} placeholder={item.name} onChange={(e) => { validationForm({ kategori: "dataPelengkap", value: { id: item.id, key: item.key, value: e }, }); }} value={ formSurat.dataPelengkap.find( (n: any) => n.key == item.key, )?.value || dataPelengkap.find((n: any) => n.key == item.key)?.value } /> ) : item.type == "date" ? ( } placeholder={item.name} onChange={(e) => { const formatted = e ? dayjs(e).locale("id").format("DD MMMM YYYY") : ""; validationForm({ kategori: "dataPelengkap", value: { id: item.id, key: item.key, value: formatted, }, }); }} value={ formSurat.dataPelengkap.find( (n: any) => n.key === item.key, )?.value ? parseTanggalID( formSurat.dataPelengkap.find( (n: any) => n.key === item.key, )?.value, ) : parseTanggalID(item.value) } /> ) : ( } placeholder={item.name} type={item.type} onChange={(e) => validationForm({ kategori: "dataPelengkap", value: { id: item.id, key: item.key, value: e.target.value, }, }) } value={ formSurat.dataPelengkap.find((n) => n.id === item.id) ?.value ?? dataPelengkap.find((n: any) => n.key == item.key)?.value } disabled={status != "ditolak" && status != "antrian"} /> )} ))} } > {dataSyaratDokumen.map((item: any, index: number) => ( validationForm({ kategori: "syaratDokumen", value: { id: item.id, key: item.key, value: file }, }) } name={item.name} disabled={status != "ditolak" && status != "antrian"} /> ))} { onChecking(); }} disabled={status != "ditolak" && status != "antrian"} > Kirim > )} > ); }