upd: update pelayanan surat

Deskripsi:
- pengaplikasian api
- modal konfirmasi update pelayanan surat
- modal konfirmasi create pelayanan surat

NO Issues
This commit is contained in:
2025-12-22 14:37:42 +08:00
parent 3904527c2a
commit 91a3dfdb5d
4 changed files with 431 additions and 228 deletions

View File

@@ -4,11 +4,13 @@ import { IconCheck } from "@tabler/icons-react";
type SuccessPengajuanProps = { type SuccessPengajuanProps = {
noPengajuan: string; noPengajuan: string;
onClose?: () => void; onClose?: () => void;
category?: 'create' | 'update';
}; };
export default function SuccessPengajuan({ export default function SuccessPengajuan({
noPengajuan, noPengajuan,
onClose, onClose,
category
}: SuccessPengajuanProps) { }: SuccessPengajuanProps) {
return ( return (
<Center h="100vh"> <Center h="100vh">
@@ -17,11 +19,11 @@ export default function SuccessPengajuan({
<IconCheck size={56} color="green" /> <IconCheck size={56} color="green" />
<Title order={3} ta="center"> <Title order={3} ta="center">
Pengajuan Berhasil Dibuat {category == 'create' ? 'Pengajuan Berhasil Dibuat' : 'Pengajuan Berhasil Diupdate'}
</Title> </Title>
<Text ta="center" size="sm" c="dimmed"> <Text ta="center" size="sm" c="dimmed">
Pengajuan layanan surat sudah dibuat dengan nomor: {category == 'create' ? 'Pengajuan layanan surat sudah dibuat dengan nomor:' : 'Pengajuan layanan surat sudah diupdate dengan nomor:'}
</Text> </Text>
<Badge size="xl" variant="light" color="green"> <Badge size="xl" variant="light" color="green">

View File

@@ -15,13 +15,14 @@ import {
Flex, Flex,
Grid, Grid,
Group, Group,
Modal,
Select, Select,
Stack, Stack,
Text, Text,
TextInput, TextInput,
Tooltip, Tooltip,
} from "@mantine/core"; } from "@mantine/core";
import { useShallowEffect } from "@mantine/hooks"; import { useDisclosure, useShallowEffect } from "@mantine/hooks";
import { import {
IconBuildingCommunity, IconBuildingCommunity,
IconInfoCircle, IconInfoCircle,
@@ -46,6 +47,7 @@ type FormSurat = {
}; };
export default function FormSurat() { export default function FormSurat() {
const [opened, { open, close }] = useDisclosure(false);
const [noPengajuan, setNoPengajuan] = useState(""); const [noPengajuan, setNoPengajuan] = useState("");
const [submitLoading, setSubmitLoading] = useState(false); const [submitLoading, setSubmitLoading] = useState(false);
const navigate = useNavigate(); const navigate = useNavigate();
@@ -141,7 +143,7 @@ export default function FormSurat() {
} }
}, [jenisSuratFix.id]); }, [jenisSuratFix.id]);
async function onSubmit() { function onChecking() {
const isFormKosong = Object.values(formSurat).some((value) => { const isFormKosong = Object.values(formSurat).some((value) => {
if (Array.isArray(value)) { if (Array.isArray(value)) {
return ( return (
@@ -166,8 +168,12 @@ export default function FormSurat() {
message: "Silahkan lengkapi form surat", message: "Silahkan lengkapi form surat",
type: "error", type: "error",
}); });
} else {
open();
}
} }
async function onSubmit() {
try { try {
setSubmitLoading(true); setSubmitLoading(true);
// 🔥 CLONE state SEKALI // 🔥 CLONE state SEKALI
@@ -197,11 +203,7 @@ export default function FormSurat() {
const res = await apiFetch.api.pelayanan.create.post(finalFormSurat); const res = await apiFetch.api.pelayanan.create.post(finalFormSurat);
if (res.status === 200) { if (res.status === 200) {
notification({ setNoPengajuan(res.data?.noPengajuan || "");
title: "Berhasil",
message: res.data?.message || "Pengajuan surat berhasil dibuat",
type: "success",
});
} else { } else {
notification({ notification({
title: "Gagal", title: "Gagal",
@@ -252,17 +254,46 @@ export default function FormSurat() {
} }
return ( return (
<Container size="md" w={"100%"}> <Container size="md" w={"100%"} pb={"lg"}>
<Modal
opened={opened}
onClose={close}
title={"Konfirmasi"}
overlayProps={{ backgroundOpacity: 0.55, blur: 3 }}
>
<Stack gap="sm">
<Text>
Apakah anda yakin ingin mengirim pengajuan surat ini?
</Text>
<Group justify="center" grow>
<Button variant="light" onClick={close}>
Tidak
</Button>
<Button
variant="filled"
color="green"
onClick={() => {
onSubmit();
close();
}}
>
Ya
</Button>
</Group>
</Stack>
</Modal>
<FullScreenLoading visible={submitLoading} /> <FullScreenLoading visible={submitLoading} />
{noPengajuan != "" && ( {noPengajuan != "" ? (
<SuccessPengajuan <SuccessPengajuan
noPengajuan={noPengajuan} noPengajuan={noPengajuan}
onClose={() => { onClose={() => {
onResetAll(); onResetAll();
navigate("/darmasaba/surat"); navigate("/darmasaba/surat");
}} }}
category="create"
/> />
)} )
:
<Box> <Box>
<Stack gap="lg"> <Stack gap="lg">
<Group justify="space-between" align="center"> <Group justify="space-between" align="center">
@@ -397,16 +428,18 @@ export default function FormSurat() {
{/* Actions */} {/* Actions */}
<Group justify="right" mt="md"> <Group justify="right" mt="md">
<Button variant="default" onClick={() => {}}> {/* <Button variant="default" onClick={() => { }}>
Reset Reset
</Button> </Button> */}
<Button onClick={onSubmit}>Kirim</Button> <Button onClick={onChecking}>Kirim</Button>
</Group> </Group>
</> </>
)} )}
</Stack> </Stack>
</Stack> </Stack>
</Box> </Box>
}
</Container> </Container>
); );
} }

View File

@@ -1,5 +1,7 @@
import FullScreenLoading from "@/components/FullScreenLoading";
import ModalFile from "@/components/ModalFile"; import ModalFile from "@/components/ModalFile";
import notification from "@/components/notificationGlobal"; import notification from "@/components/notificationGlobal";
import SuccessPengajuan from "@/components/SuccessPengajuanSurat";
import apiFetch from "@/lib/apiFetch"; import apiFetch from "@/lib/apiFetch";
import { import {
ActionIcon, ActionIcon,
@@ -15,12 +17,13 @@ import {
Flex, Flex,
Grid, Grid,
Group, Group,
Modal,
Stack, Stack,
Text, Text,
TextInput, TextInput,
Tooltip Tooltip
} from "@mantine/core"; } from "@mantine/core";
import { useShallowEffect } from "@mantine/hooks"; import { useDisclosure, useShallowEffect } from "@mantine/hooks";
import { import {
IconBuildingCommunity, IconBuildingCommunity,
IconInfoCircle, IconInfoCircle,
@@ -336,6 +339,10 @@ function SearchData() {
function DataUpdate({ noPengajuan }: { noPengajuan: string }) { function DataUpdate({ noPengajuan }: { noPengajuan: string }) {
const [opened, { open, close }] = useDisclosure(false)
const navigate = useNavigate()
const [sukses, setSukses] = useState(false)
const [submitLoading, setSubmitLoading] = useState(false)
const [dataPelengkap, setDataPelengkap] = useState<DataItem[]>([]) const [dataPelengkap, setDataPelengkap] = useState<DataItem[]>([])
const [dataSyaratDokumen, setDataSyaratDokumen] = useState<DataItem[]>([]) const [dataSyaratDokumen, setDataSyaratDokumen] = useState<DataItem[]>([])
const [dataPengajuan, setDataPengajuan] = useState<DataPengajuan | {}>({}) const [dataPengajuan, setDataPengajuan] = useState<DataPengajuan | {}>({})
@@ -406,9 +413,146 @@ function DataUpdate({ noPengajuan }: { noPengajuan: string }) {
})); }));
} }
function updateArrayByKey(
list: UpdateDataItem[],
id: string,
value: any,
): UpdateDataItem[] {
return list.map((item) =>
item.id === id ? { ...item, value } : item,
);
}
function onChecking() {
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 ( return (
<>
<FullScreenLoading visible={submitLoading} />
<Modal
opened={opened}
onClose={close}
title={"Konfirmasi"}
overlayProps={{ backgroundOpacity: 0.55, blur: 3 }}
>
<Stack gap="sm">
<Text>
Apakah anda yakin ingin mengupdate pengajuan surat ini?
</Text>
<Group justify="center" grow>
<Button variant="light" onClick={close}>
Tidak
</Button>
<Button
variant="filled"
color="green"
onClick={() => {
onSubmit();
close();
}}
>
Ya
</Button>
</Group>
</Stack>
</Modal>
{
sukses ?
<SuccessPengajuan
noPengajuan={noPengajuan}
onClose={() => {
navigate("/darmasaba/update-data-surat");
}}
category="update"
/>
:
<> <>
{ {
(status != "ditolak" && status != "antrian") (status != "ditolak" && status != "antrian")
@@ -467,8 +611,10 @@ function DataUpdate({ noPengajuan }: { noPengajuan: string }) {
</FormSection> </FormSection>
<Group justify="right" mt="md"> <Group justify="right" mt="md">
<Button onClick={() => console.log('Submit clicked')}>Kirim</Button> <Button onClick={() => { onChecking() }}>Kirim</Button>
</Group> </Group>
</> </>
}
</>
) )
} }

View File

@@ -299,14 +299,26 @@ const PelayananRoute = new Elysia({
key: string; key: string;
}[]; }[];
const dataTextFix = dataText.map((item) => { const refMap = new Map(
dataTextCategory.map((v, i) => [
v.key,
{ ...v, order: i }
])
);
const dataTextFix = dataText
.map((item) => {
const ref = refMap.get(item.jenis);
const nama = dataTextCategory.find((v) => v.key == item.jenis)?.name const nama = dataTextCategory.find((v) => v.key == item.jenis)?.name
return { return {
id: item.id, id: item.id,
jenis: nama, jenis: nama,
value: item.value, value: item.value,
} order: ref?.order ?? Infinity,
};
}) })
.sort((a, b) => a.order - b.order)
.map(({ order, ...rest }) => rest); // hapus order
const dataHistory = await prisma.historyPelayanan.findMany({ const dataHistory = await prisma.historyPelayanan.findMany({
where: { where: {
@@ -477,7 +489,7 @@ const PelayananRoute = new Elysia({
} }
}) })
return { success: true, message: 'Pengajuan layanan surat sudah dibuat dengan nomer ' + noPengajuan + ', nomer ini akan digunakan untuk mengakses pengajuan ini' } return { success: true, message: 'Pengajuan layanan surat sudah dibuat dengan nomer ' + noPengajuan + ', nomer ini akan digunakan untuk mengakses pengajuan ini', noPengajuan }
}, { }, {
body: t.Object({ body: t.Object({
kategoriId: t.String({ kategoriId: t.String({
@@ -664,18 +676,29 @@ const PelayananRoute = new Elysia({
key: string; key: string;
}[]; }[];
const dataTextFix = dataPelengkap.map((item) => { const refMap = new Map(
const ini = dataPelengkapList.find((v) => v.key == item.jenis) dataPelengkapList.map((v, i) => [
const desc = ini?.desc v.key,
const name = ini?.name { ...v, order: i }
])
);
const dataTextFix = dataPelengkap
.map((item) => {
const ref = refMap.get(item.jenis);
return { return {
id: item.id, id: item.id,
key: item.jenis, key: item.jenis,
value: item.value, value: item.value,
desc: desc ?? '', desc: ref?.desc ?? "",
name: name ?? '' name: ref?.name ?? "",
} order: ref?.order ?? Infinity,
};
}) })
.sort((a, b) => a.order - b.order)
.map(({ order, ...rest }) => rest); // hapus order
const dataHistory = await prisma.historyPelayanan.findMany({ const dataHistory = await prisma.historyPelayanan.findMany({
where: { where: {
@@ -733,7 +756,6 @@ const PelayananRoute = new Elysia({
dataPelengkap: dataTextFix, dataPelengkap: dataTextFix,
} }
return datafix return datafix
}, { }, {