diff --git a/src/pages/darmasaba/surat.tsx b/src/pages/darmasaba/surat.tsx index 78d9ac0..a45b1dc 100644 --- a/src/pages/darmasaba/surat.tsx +++ b/src/pages/darmasaba/surat.tsx @@ -52,7 +52,10 @@ type FormSurat = { syaratDokumen: DataItem[]; }; +type ErrorState = Record; + export default function FormSurat() { + const [errors, setErrors] = useState({}); const [opened, { open, close }] = useDisclosure(false); const [noPengajuan, setNoPengajuan] = useState(""); const [submitLoading, setSubmitLoading] = useState(false); @@ -150,22 +153,24 @@ export default function FormSurat() { }, [jenisSuratFix.id]); function onChecking() { + const hasError = Object.values(errors).some((v) => v); + + if (hasError) { + return notification({ + title: "Gagal", + message: "Masih ada form yang belum valid", + type: "error", + }); + } + const isFormKosong = Object.values(formSurat).some((value) => { if (Array.isArray(value)) { - return ( - value.length === 0 || - value.some( - (item) => - typeof item.value === "string" && item.value.trim() === "", - ) + return value.some( + (item) => + typeof item.value === "string" && item.value.trim() === "", ); } - - if (typeof value === "string") { - return value.trim() === ""; - } - - return false; + return typeof value === "string" && value.trim() === ""; }); if (isFormKosong) { @@ -174,9 +179,9 @@ export default function FormSurat() { message: "Silahkan lengkapi form surat", type: "error", }); - } else { - open(); } + + open(); } async function onSubmit() { @@ -239,6 +244,30 @@ export default function FormSurat() { ); } + + function validateField(key: string, value: any) { + const stringValue = String(value ?? "").trim(); + + // wajib diisi + if (!stringValue) { + return "Field wajib diisi"; + } + + // 🔥 semua key yang 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 validationForm({ key, value, @@ -246,12 +275,27 @@ export default function FormSurat() { key: "nama" | "phone" | "dataPelengkap" | "syaratDokumen"; value: any; }) { - if (key == "dataPelengkap" || key == "syaratDokumen") { + if (key === "dataPelengkap" || key === "syaratDokumen") { + const errorMsg = validateField(value.key, value.value); + + setErrors((prev) => ({ + ...prev, + [value.key]: errorMsg, + })); + setFormSurat((prev) => ({ ...prev, [key]: updateArrayByKey(prev[key], value.key, value.value), })); } else { + const keyFix = key == "nama" ? "nama_kontak" : key; + const errorMsg = validateField(keyFix, value); + + setErrors((prev) => ({ + ...prev, + [keyFix]: errorMsg, + })); + setFormSurat({ ...formSurat, [key]: value, @@ -259,6 +303,7 @@ export default function FormSurat() { } } + return ( } placeholder="Budi Setiawan" value={formSurat.nama} + error={errors.nama_kontak} onChange={(e) => validationForm({ key: "nama", value: e.target.value }) } @@ -374,6 +420,8 @@ export default function FormSurat() { } placeholder="08123456789" value={formSurat.phone} + error={errors.phone} + type="number" onChange={(e) => validationForm({ key: "phone", value: e.target.value }) } @@ -446,6 +494,7 @@ export default function FormSurat() { /> ) : ( >({}); const [sukses, setSukses] = useState(false); const [submitLoading, setSubmitLoading] = useState(false); const [dataPelengkap, setDataPelengkap] = useState([]); @@ -427,6 +428,29 @@ function DataUpdate({ 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); @@ -446,6 +470,13 @@ function DataUpdate({ 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], { @@ -456,6 +487,7 @@ function DataUpdate({ })); } + function updateArrayByKey( list: UpdateDataItem[], id: string, @@ -465,6 +497,16 @@ function DataUpdate({ } 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 @@ -670,13 +712,15 @@ function DataUpdate({ (n: any) => n.key === item.key, )?.value, ) - : parseTanggalID(item.value) + : parseTanggalID(item.value) } /> ) : ( } placeholder={item.name} + type={item.type} onChange={(e) => validationForm({ kategori: "dataPelengkap", diff --git a/src/pages/scr/dashboard/pelayanan-surat/detail_pelayanan_page.tsx b/src/pages/scr/dashboard/pelayanan-surat/detail_pelayanan_page.tsx index 767efc6..b51e64d 100644 --- a/src/pages/scr/dashboard/pelayanan-surat/detail_pelayanan_page.tsx +++ b/src/pages/scr/dashboard/pelayanan-surat/detail_pelayanan_page.tsx @@ -2,7 +2,9 @@ import ModalFile from "@/components/ModalFile"; import ModalSurat from "@/components/ModalSurat"; import notification from "@/components/notificationGlobal"; import apiFetch from "@/lib/apiFetch"; +import { parseTanggalID } from "@/server/lib/stringToDate"; import { + ActionIcon, Anchor, Badge, Button, @@ -14,24 +16,31 @@ import { Group, List, Modal, + Select, Spoiler, Stack, Table, Text, Textarea, + TextInput, ThemeIcon, Title, + Tooltip } from "@mantine/core"; +import { DateInput } from "@mantine/dates"; import { useDisclosure, useShallowEffect } from "@mantine/hooks"; import { IconAlignJustified, IconCheck, + IconEdit, IconFileCertificate, IconFileCheck, + IconInfoCircle, IconMessageReport, IconPhone, IconUser, } from "@tabler/icons-react"; +import dayjs from "dayjs"; import type { User } from "generated/prisma"; import type { JsonValue } from "generated/prisma/runtime/library"; import _ from "lodash"; @@ -93,6 +102,7 @@ function DetailDataPengajuan({ dataText: any; onAction: () => void; }) { + const [opened, { open, close }] = useDisclosure(false); const [catModal, setCatModal] = useState<"tolak" | "terima">("tolak"); const [keterangan, setKeterangan] = useState(""); @@ -103,6 +113,9 @@ function DetailDataPengajuan({ const [permissions, setPermissions] = useState([]); const [viewImg, setViewImg] = useState({ file: "", folder: "" }); const [uploading, setUploading] = useState({ ok: false, file: "" }); + const [editValue, setEditValue] = useState({ id: "", jenis: "", val: "", option: null as any, type: "", key: "" }) + const [openEdit, setOpenEdit] = useState(false) + const [loadingUpdate, setLoadingUpdate] = useState(false) useEffect(() => { async function fetchHost() { @@ -215,6 +228,43 @@ function DetailDataPengajuan({ } }; + async function updateDataText() { + try { + setLoadingUpdate(true) + const res = await apiFetch.api.pelayanan["update-data-pelengkap"].post({ + id: editValue.id, + value: editValue.val, + jenis: editValue.key, + idUser: host?.id ?? "", + }) + + if (res?.status === 200) { + notification({ + title: "Success", + message: "Success update data", + type: "success", + }) + } else { + notification({ + title: "Error", + message: "Failed to update data", + type: "error", + }) + } + } catch (error) { + console.error(error) + notification({ + title: "Error", + message: "Failed to update data", + type: "error", + }) + } finally { + setLoadingUpdate(false) + setOpenEdit(false) + onAction() + } + } + useShallowEffect(() => { if (viewImg) { setOpenedPreviewFile(true); @@ -231,9 +281,86 @@ function DetailDataPengajuan({ } }, [uploading]); + function FieldLabel({ label, hint }: { label: string; hint?: string }) { + return ( + + {label} + {hint && ( + + + + + + )} + + ); + } return ( <> + {/* MODAL EDIT DATA PELENGKAP */} + setOpenEdit(false)} + title={"Edit"} + overlayProps={{ backgroundOpacity: 0.55, blur: 3 }} + > + + {editValue.type == "enum" ? ( +