Compare commits
73 Commits
amalia/16-
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| d6abc163fb | |||
| 9c08980bf1 | |||
| a2af3fbe36 | |||
| ec8722ffba | |||
| b0752dac8d | |||
| a8d3a3a9ff | |||
| f86703e7d1 | |||
| d8bb33cc93 | |||
| 5807e98069 | |||
| 59b4f1d73f | |||
| 8bd552ac22 | |||
| 5d48d06513 | |||
| 3da163ea1d | |||
| 57e4f34eb6 | |||
| 3348cbe8e3 | |||
| 727984a076 | |||
| a7a0ad7e37 | |||
| 9fed41cbe8 | |||
| fc387fe8e6 | |||
| 80df579499 | |||
| 5bbbc15c27 | |||
| 82765f6ef0 | |||
| e8b5720118 | |||
| 01334ec573 | |||
| 98ad9b0d72 | |||
| c0471f47f3 | |||
| 3d641d2035 | |||
| 694115dbfb | |||
| 7de5078868 | |||
| 7a3faa5719 | |||
| ea5072d9ab | |||
| e8bb4f5a41 | |||
| d63bf024d3 | |||
| 46f7dbf7bb | |||
| 1adea29990 | |||
| 2a5b6e7b7c | |||
| 2117612337 | |||
| 8f33ec2ffa | |||
| 411f61ec15 | |||
| 476319945e | |||
| 8480cec6ae | |||
| 4ca5e4c4f3 | |||
| 75758bcbe6 | |||
| 2d336ea467 | |||
| 112e931bad | |||
| 487395bdb3 | |||
| 3944e1ee82 | |||
| a9b34547f0 | |||
| 211aac3d5f | |||
| 73a2a4367c | |||
| a01f394e43 | |||
| 7dde0a4eb9 | |||
| 6debbf8c64 | |||
| c074e2bc0a | |||
| bab832b87f | |||
| 8bafb88086 | |||
| 777f2c04f1 | |||
| a81f6c4255 | |||
| 0f1b0196e7 | |||
| da86f5f10a | |||
| 91a3dfdb5d | |||
| 3904527c2a | |||
| ff0413be5a | |||
| fda2b0977a | |||
| 55fbf4836d | |||
| c897063eb5 | |||
| 84161db7f2 | |||
| a1766538b2 | |||
| d8cf7833a9 | |||
| c585d2481d | |||
| 18d3b40700 | |||
| baf00b1ba8 | |||
| dfc5c9144f |
146
bak/ModalSurat.tsx.txt
Normal file
146
bak/ModalSurat.tsx.txt
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
import apiFetch from "@/lib/apiFetch";
|
||||||
|
import { ActionIcon, Flex, Modal } from "@mantine/core";
|
||||||
|
import { useShallowEffect } from "@mantine/hooks";
|
||||||
|
import { IconDownload, IconX } from "@tabler/icons-react";
|
||||||
|
import html2canvas from "html2canvas";
|
||||||
|
import jsPDF from "jspdf";
|
||||||
|
import { useRef } from "react";
|
||||||
|
import useSWR from "swr";
|
||||||
|
import SKBedaBiodataDiri from "./surat/SKBedaBiodataDiri";
|
||||||
|
import SKBelumKawin from "./surat/SKBelumKawin";
|
||||||
|
import SKDomisiliOrganisasi from "./surat/SKDomisiliOrganisasi";
|
||||||
|
import SKKelahiran from "./surat/SKKelahiran";
|
||||||
|
import SKKelakuanBaik from "./surat/SKKelakuanBaik";
|
||||||
|
import SKKematian from "./surat/SKKematian";
|
||||||
|
import SKPenghasilan from "./surat/SKPenghasilan";
|
||||||
|
import SKTempatUsaha from "./surat/SKTempatUsaha";
|
||||||
|
import SKTidakMampu from "./surat/SKTidakMampu";
|
||||||
|
import SKUsaha from "./surat/SKUsaha";
|
||||||
|
import SKYatim from "./surat/SKYatimPiatu";
|
||||||
|
|
||||||
|
export default function ModalSurat({
|
||||||
|
open,
|
||||||
|
onClose,
|
||||||
|
surat,
|
||||||
|
}: {
|
||||||
|
open: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
surat: string;
|
||||||
|
}) {
|
||||||
|
const A4Style = {
|
||||||
|
width: "210mm",
|
||||||
|
height: "297mm",
|
||||||
|
padding: "20mm",
|
||||||
|
background: "#fff",
|
||||||
|
color: "#000",
|
||||||
|
fontSize: "14px",
|
||||||
|
fontFamily: "Times New Roman",
|
||||||
|
};
|
||||||
|
const hiddenRef = useRef<any>(null);
|
||||||
|
const { data, mutate, isLoading } = useSWR("surat", () =>
|
||||||
|
apiFetch.api.surat.detail.get({
|
||||||
|
query: {
|
||||||
|
id: surat,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
useShallowEffect(() => {
|
||||||
|
mutate();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const downloadPDF = async () => {
|
||||||
|
const element = hiddenRef.current;
|
||||||
|
const canvas = await html2canvas(element, {
|
||||||
|
scale: 2,
|
||||||
|
useCORS: true,
|
||||||
|
allowTaint: true,
|
||||||
|
width: element.offsetWidth,
|
||||||
|
height: element.offsetHeight,
|
||||||
|
});
|
||||||
|
|
||||||
|
const imgData = canvas.toDataURL("image/jpeg", 1.0);
|
||||||
|
|
||||||
|
const pdf = new jsPDF("p", "mm", "a4");
|
||||||
|
const pageWidth = 210; // A4 width mm
|
||||||
|
const pageHeight = 297; // A4 height mm
|
||||||
|
|
||||||
|
const imgWidth = pageWidth;
|
||||||
|
const imgHeight = (canvas.height * pageWidth) / canvas.width;
|
||||||
|
|
||||||
|
pdf.addImage(imgData, "JPEG", 0, 0, imgWidth, imgHeight);
|
||||||
|
|
||||||
|
pdf.save(`${data?.data?.surat?.nameCategory}.pdf`);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Modal
|
||||||
|
opened={open}
|
||||||
|
onClose={() => onClose()}
|
||||||
|
overlayProps={{ backgroundOpacity: 0.55, blur: 3 }}
|
||||||
|
size="auto"
|
||||||
|
withCloseButton={false}
|
||||||
|
removeScrollProps={{ allowPinchZoom: true }}
|
||||||
|
styles={{
|
||||||
|
header: {
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
alignItems: "center",
|
||||||
|
padding: "12px 16px",
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
width: "100%",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
title={
|
||||||
|
<Flex justify="space-between" align="center" w="100%">
|
||||||
|
<div style={{ fontSize: 18, fontWeight: 600 }}>Preview Surat</div>
|
||||||
|
|
||||||
|
<Flex gap={8}>
|
||||||
|
<ActionIcon size={32} variant="default">
|
||||||
|
<IconDownload size={20} onClick={downloadPDF} />
|
||||||
|
</ActionIcon>
|
||||||
|
|
||||||
|
<ActionIcon size={32} variant="default" onClick={onClose}>
|
||||||
|
<IconX size={20} />
|
||||||
|
</ActionIcon>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div ref={hiddenRef} style={A4Style}>
|
||||||
|
{data && data.data ? (
|
||||||
|
data.data.surat.idCategory == "skusaha" ? (
|
||||||
|
<SKUsaha data={data.data} />
|
||||||
|
) : data.data.surat.idCategory == "skkelahiran" ? (
|
||||||
|
<SKKelahiran data={data.data} />
|
||||||
|
) : data.data.surat.idCategory == "skkelakuanbaik" ? (
|
||||||
|
<SKKelakuanBaik data={data.data} />
|
||||||
|
) : data.data.surat.idCategory == "skpenghasilan" ? (
|
||||||
|
<SKPenghasilan data={data.data} />
|
||||||
|
) : data.data.surat.idCategory == "sktidakmampu" ? (
|
||||||
|
<SKTidakMampu data={data.data} />
|
||||||
|
) : data.data.surat.idCategory == "skyatimpiatu" ? (
|
||||||
|
<SKYatim data={data.data} />
|
||||||
|
) : data.data.surat.idCategory == "skdomisiliorganisasi" ? (
|
||||||
|
<SKDomisiliOrganisasi data={data.data} />
|
||||||
|
) : data.data.surat.idCategory == "skbedabiodata" ? (
|
||||||
|
<SKBedaBiodataDiri data={data.data} />
|
||||||
|
) : data.data.surat.idCategory == "sktempatusaha" ? (
|
||||||
|
<SKTempatUsaha data={data.data} />
|
||||||
|
) : data.data.surat.idCategory == "skbelumkawin" ? (
|
||||||
|
<SKBelumKawin data={data.data} />
|
||||||
|
) : data.data.surat.idCategory == "skkematian" ? (
|
||||||
|
<SKKematian data={data.data} />
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
310
bak/listPermission.json.txt
Normal file
310
bak/listPermission.json.txt
Normal file
@@ -0,0 +1,310 @@
|
|||||||
|
{
|
||||||
|
"menus": [
|
||||||
|
{
|
||||||
|
"key": "dashboard",
|
||||||
|
"label": "Dashboard",
|
||||||
|
"default": true,
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"key": "dashboard.view",
|
||||||
|
"label": "Melihat Dashboard",
|
||||||
|
"default": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "pengaduan",
|
||||||
|
"label": "Pengaduan",
|
||||||
|
"default": true,
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"key": "pengaduan.view",
|
||||||
|
"label": "Melihat List & Detail",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "pengaduan.antrian",
|
||||||
|
"label": "Detail pengaduan dengan status antrian",
|
||||||
|
"default": true,
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"key": "pengaduan.antrian.tolak",
|
||||||
|
"label": "Menolak pengaduan",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "pengaduan.antrian.terima",
|
||||||
|
"label": "Menerima pengaduan",
|
||||||
|
"default": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "pengaduan.diterima",
|
||||||
|
"label": "Detail pengaduan dengan status diterima",
|
||||||
|
"default": true,
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"key": "pengaduan.diterima.dikerjakan",
|
||||||
|
"label": "Menegerjakan pengaduan",
|
||||||
|
"default": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "pengaduan.dikerjakan",
|
||||||
|
"label": "Detail pengaduan dengan status dikerjakan",
|
||||||
|
"default": true,
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"key": "pengaduan.dikerjakan.selesai",
|
||||||
|
"label": "Menyelesaikan pengaduan",
|
||||||
|
"default": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "pelayanan",
|
||||||
|
"label": "Pelayanan",
|
||||||
|
"default": true,
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"key": "pelayanan.view",
|
||||||
|
"label": "Melihat List & Detail",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "pelayanan.antrian",
|
||||||
|
"label": "Detail pelayanan dengan status antrian",
|
||||||
|
"default": true,
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"key": "pelayanan.antrian.tolak",
|
||||||
|
"label": "Menolak pelayanan",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "pelayanan.antrian.terima",
|
||||||
|
"label": "Menerima pelayanan",
|
||||||
|
"default": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "pelayanan.diterima",
|
||||||
|
"label": "Detail pelayanan dengan status diterima",
|
||||||
|
"default": true,
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"key": "pelayanan.diterima.tolak",
|
||||||
|
"label": "Menolak pelayanan",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "pelayanan.diterima.setujui",
|
||||||
|
"label": "Menyetujui pelayanan",
|
||||||
|
"default": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "warga",
|
||||||
|
"label": "Warga",
|
||||||
|
"default": true,
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"key": "warga.view",
|
||||||
|
"label": "Melihat List & Detail",
|
||||||
|
"default": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "setting",
|
||||||
|
"label": "Setting",
|
||||||
|
"default": true,
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"key": "setting.profile",
|
||||||
|
"label": "Profile",
|
||||||
|
"default": true,
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"key": "setting.profile.view",
|
||||||
|
"label": "View",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "setting.profile.edit",
|
||||||
|
"label": "Edit",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "setting.profile.password",
|
||||||
|
"label": "Ubah Password",
|
||||||
|
"default": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "setting.user",
|
||||||
|
"label": "User",
|
||||||
|
"default": true,
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"key": "setting.user.view",
|
||||||
|
"label": "View List",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "setting.user.tambah",
|
||||||
|
"label": "Tambah",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "setting.user.edit",
|
||||||
|
"label": "Edit",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "setting.user.delete",
|
||||||
|
"label": "Delete",
|
||||||
|
"default": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "setting.user_role",
|
||||||
|
"label": "User Role",
|
||||||
|
"default": true,
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"key": "setting.user_role.view",
|
||||||
|
"label": "View List",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "setting.user_role.tambah",
|
||||||
|
"label": "Tambah",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "setting.user_role.edit",
|
||||||
|
"label": "Edit",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "setting.user_role.delete",
|
||||||
|
"label": "Delete",
|
||||||
|
"default": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "setting.kategori_pengaduan",
|
||||||
|
"label": "Kategori Pengaduan",
|
||||||
|
"default": true,
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"key": "setting.kategori_pengaduan.view",
|
||||||
|
"label": "View List",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "setting.kategori_pengaduan.tambah",
|
||||||
|
"label": "Tambah",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "setting.kategori_pengaduan.edit",
|
||||||
|
"label": "Edit",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "setting.kategori_pengaduan.delete",
|
||||||
|
"label": "Delete",
|
||||||
|
"default": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "setting.kategori_pelayanan",
|
||||||
|
"label": "Kategori Pelayanan Surat",
|
||||||
|
"default": true,
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"key": "setting.kategori_pelayanan.view",
|
||||||
|
"label": "View List",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "setting.kategori_pelayanan.detail",
|
||||||
|
"label": "View Detail",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "setting.kategori_pelayanan.tambah",
|
||||||
|
"label": "Tambah",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "setting.kategori_pelayanan.edit",
|
||||||
|
"label": "Edit",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "setting.kategori_pelayanan.delete",
|
||||||
|
"label": "Delete",
|
||||||
|
"default": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "setting.desa",
|
||||||
|
"label": "Desa",
|
||||||
|
"default": true,
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"key": "setting.desa.view",
|
||||||
|
"label": "View List",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "setting.desa.edit",
|
||||||
|
"label": "Edit",
|
||||||
|
"default": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "api_key",
|
||||||
|
"label": "API Key",
|
||||||
|
"default": true,
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"key": "api_key.view",
|
||||||
|
"label": "View List",
|
||||||
|
"default": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "credential",
|
||||||
|
"label": "Credential",
|
||||||
|
"default": true,
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"key": "credential.view",
|
||||||
|
"label": "View List",
|
||||||
|
"default": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
1
bun.lock
1
bun.lock
@@ -22,6 +22,7 @@
|
|||||||
"@types/lodash": "^4.17.20",
|
"@types/lodash": "^4.17.20",
|
||||||
"@types/uuid": "^11.0.0",
|
"@types/uuid": "^11.0.0",
|
||||||
"add": "^2.0.6",
|
"add": "^2.0.6",
|
||||||
|
"dayjs": "^1.11.19",
|
||||||
"echarts": "^6.0.0",
|
"echarts": "^6.0.0",
|
||||||
"echarts-for-react": "^3.0.5",
|
"echarts-for-react": "^3.0.5",
|
||||||
"elysia": "^1.4.15",
|
"elysia": "^1.4.15",
|
||||||
|
|||||||
@@ -28,6 +28,7 @@
|
|||||||
"@types/lodash": "^4.17.20",
|
"@types/lodash": "^4.17.20",
|
||||||
"@types/uuid": "^11.0.0",
|
"@types/uuid": "^11.0.0",
|
||||||
"add": "^2.0.6",
|
"add": "^2.0.6",
|
||||||
|
"dayjs": "^1.11.19",
|
||||||
"echarts": "^6.0.0",
|
"echarts": "^6.0.0",
|
||||||
"echarts-for-react": "^3.0.5",
|
"echarts-for-react": "^3.0.5",
|
||||||
"elysia": "^1.4.15",
|
"elysia": "^1.4.15",
|
||||||
|
|||||||
@@ -110,7 +110,8 @@ model CategoryPelayanan {
|
|||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
name String
|
name String
|
||||||
syaratDokumen Json[]
|
syaratDokumen Json[]
|
||||||
dataText String[]
|
dataText String[] @default([])
|
||||||
|
dataPelengkap Json[] @default([])
|
||||||
isActive Boolean @default(true)
|
isActive Boolean @default(true)
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
@@ -186,6 +187,7 @@ model SuratPelayanan {
|
|||||||
Warga Warga @relation(fields: [idWarga], references: [id])
|
Warga Warga @relation(fields: [idWarga], references: [id])
|
||||||
idWarga String
|
idWarga String
|
||||||
noSurat String
|
noSurat String
|
||||||
|
file String?
|
||||||
dateExpired DateTime? @db.Date
|
dateExpired DateTime? @db.Date
|
||||||
status Int @default(0)
|
status Int @default(0)
|
||||||
isActive Boolean @default(true)
|
isActive Boolean @default(true)
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import FormKartuKeluarga from "./pages/darmasaba/form_kartu_keluarga";
|
|||||||
import FormLaporanSampah from "./pages/darmasaba/form_laporan_sampah";
|
import FormLaporanSampah from "./pages/darmasaba/form_laporan_sampah";
|
||||||
import FormSuratKeteranganPenghasilan from "./pages/darmasaba/form_surat_keterangan_penghasilan";
|
import FormSuratKeteranganPenghasilan from "./pages/darmasaba/form_surat_keterangan_penghasilan";
|
||||||
import FormSuratKeteranganDomisiliOrganisasi from "./pages/darmasaba/form_surat_keterangan_domisili_organisasi";
|
import FormSuratKeteranganDomisiliOrganisasi from "./pages/darmasaba/form_surat_keterangan_domisili_organisasi";
|
||||||
|
import UpdateDataSurat from "./pages/darmasaba/update_data_surat";
|
||||||
import FormSuratKeteranganBelumKawin from "./pages/darmasaba/form_surat_keterangan_belum_kawin";
|
import FormSuratKeteranganBelumKawin from "./pages/darmasaba/form_surat_keterangan_belum_kawin";
|
||||||
import FormKeteranganKelahiran from "./pages/darmasaba/form_keterangan_kelahiran";
|
import FormKeteranganKelahiran from "./pages/darmasaba/form_keterangan_kelahiran";
|
||||||
import FormSuratKeteranganTempatUsaha from "./pages/darmasaba/form_surat_keterangan_tempat_usaha";
|
import FormSuratKeteranganTempatUsaha from "./pages/darmasaba/form_surat_keterangan_tempat_usaha";
|
||||||
@@ -69,6 +70,10 @@ export default function AppRoutes() {
|
|||||||
path="/darmasaba/surat-keterangan-domisili-organisasi"
|
path="/darmasaba/surat-keterangan-domisili-organisasi"
|
||||||
element={<FormSuratKeteranganDomisiliOrganisasi />}
|
element={<FormSuratKeteranganDomisiliOrganisasi />}
|
||||||
/>
|
/>
|
||||||
|
<Route
|
||||||
|
path="/darmasaba/update-data-surat"
|
||||||
|
element={<UpdateDataSurat />}
|
||||||
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/darmasaba/surat-keterangan-belum-kawin"
|
path="/darmasaba/surat-keterangan-belum-kawin"
|
||||||
element={<FormSuratKeteranganBelumKawin />}
|
element={<FormSuratKeteranganBelumKawin />}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ const clientRoutes = {
|
|||||||
"/darmasaba/laporan-sampah": "/darmasaba/laporan-sampah",
|
"/darmasaba/laporan-sampah": "/darmasaba/laporan-sampah",
|
||||||
"/darmasaba/surat-keterangan-penghasilan": "/darmasaba/surat-keterangan-penghasilan",
|
"/darmasaba/surat-keterangan-penghasilan": "/darmasaba/surat-keterangan-penghasilan",
|
||||||
"/darmasaba/surat-keterangan-domisili-organisasi": "/darmasaba/surat-keterangan-domisili-organisasi",
|
"/darmasaba/surat-keterangan-domisili-organisasi": "/darmasaba/surat-keterangan-domisili-organisasi",
|
||||||
|
"/darmasaba/update-data-surat": "/darmasaba/update-data-surat",
|
||||||
"/darmasaba/surat-keterangan-belum-kawin": "/darmasaba/surat-keterangan-belum-kawin",
|
"/darmasaba/surat-keterangan-belum-kawin": "/darmasaba/surat-keterangan-belum-kawin",
|
||||||
"/darmasaba/keterangan-kelahiran": "/darmasaba/keterangan-kelahiran",
|
"/darmasaba/keterangan-kelahiran": "/darmasaba/keterangan-kelahiran",
|
||||||
"/darmasaba/surat-keterangan-tempat-usaha": "/darmasaba/surat-keterangan-tempat-usaha",
|
"/darmasaba/surat-keterangan-tempat-usaha": "/darmasaba/surat-keterangan-tempat-usaha",
|
||||||
|
|||||||
44
src/components/BreadCrumbs.tsx
Normal file
44
src/components/BreadCrumbs.tsx
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import { ActionIcon, Anchor, Breadcrumbs, Card, Group } from "@mantine/core";
|
||||||
|
import { IconChevronLeft } from "@tabler/icons-react";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
|
export default function BreadCrumbs({ dataLink, back, linkBack }: { dataLink: { title: string, link: string, active: boolean }[], back?: boolean, linkBack?: string }) {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card
|
||||||
|
radius="md"
|
||||||
|
p="sm"
|
||||||
|
withBorder
|
||||||
|
style={{
|
||||||
|
background:
|
||||||
|
"linear-gradient(145deg, rgba(25,25,25,0.95), rgba(45,45,45,0.85))",
|
||||||
|
borderColor: "rgba(100,100,100,0.2)",
|
||||||
|
boxShadow: "0 0 20px rgba(0,255,200,0.08)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Group>
|
||||||
|
{
|
||||||
|
back &&
|
||||||
|
<ActionIcon variant="outline" aria-label="Settings" radius={"lg"} onClick={() => window.history.back()}>
|
||||||
|
<IconChevronLeft size={20} stroke={1.5} />
|
||||||
|
</ActionIcon>
|
||||||
|
}
|
||||||
|
<Breadcrumbs>
|
||||||
|
{
|
||||||
|
dataLink.map((item, index) => (
|
||||||
|
<Anchor
|
||||||
|
c={item.active ? "gray.0" : "gray.5"}
|
||||||
|
onClick={() => item.active || item.link == "#" ? null : navigate(item.link)}
|
||||||
|
key={index}
|
||||||
|
>
|
||||||
|
{item.title}
|
||||||
|
</Anchor>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</Breadcrumbs>
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -207,7 +207,7 @@ export default function DesaSetting({
|
|||||||
</Table.Tr>
|
</Table.Tr>
|
||||||
</Table.Thead>
|
</Table.Thead>
|
||||||
<Table.Tbody>
|
<Table.Tbody>
|
||||||
{list?.map((v: any) => (
|
{list.length > 0 && list?.map((v: any) => (
|
||||||
<Table.Tr key={v.id}>
|
<Table.Tr key={v.id}>
|
||||||
<Table.Td>{v.name}</Table.Td>
|
<Table.Td>{v.name}</Table.Td>
|
||||||
<Table.Td>
|
<Table.Td>
|
||||||
|
|||||||
38
src/components/FullScreenLoading.tsx
Normal file
38
src/components/FullScreenLoading.tsx
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import { Center, Loader, Overlay, Stack, Text } from "@mantine/core";
|
||||||
|
|
||||||
|
type FullScreenLoadingProps = {
|
||||||
|
visible: boolean;
|
||||||
|
text?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function FullScreenLoading({
|
||||||
|
visible,
|
||||||
|
text = "Memproses data...",
|
||||||
|
}: FullScreenLoadingProps) {
|
||||||
|
if (!visible) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Overlay fixed blur={6} backgroundOpacity={0.3} zIndex={10000}>
|
||||||
|
<Center h="100%">
|
||||||
|
<Stack align="center" justify="center">
|
||||||
|
<Loader size="lg" />
|
||||||
|
<Text size="sm" c="dimmed">
|
||||||
|
{text}
|
||||||
|
</Text>
|
||||||
|
</Stack>
|
||||||
|
</Center>
|
||||||
|
</Overlay>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const overlayStyle: React.CSSProperties = {
|
||||||
|
position: "fixed",
|
||||||
|
inset: 0,
|
||||||
|
zIndex: 9999,
|
||||||
|
backdropFilter: "blur(6px)",
|
||||||
|
backgroundColor: "rgba(255, 255, 255, 0.6)",
|
||||||
|
};
|
||||||
|
|
||||||
|
const contentStyle: React.CSSProperties = {
|
||||||
|
flexDirection: "column",
|
||||||
|
};
|
||||||
@@ -4,20 +4,17 @@ import {
|
|||||||
Button,
|
Button,
|
||||||
Divider,
|
Divider,
|
||||||
Flex,
|
Flex,
|
||||||
Grid,
|
|
||||||
Group,
|
Group,
|
||||||
Input,
|
|
||||||
List,
|
List,
|
||||||
Modal,
|
Modal,
|
||||||
Stack,
|
Stack,
|
||||||
Table,
|
Table,
|
||||||
TagsInput,
|
|
||||||
Text,
|
Text,
|
||||||
Title,
|
Title,
|
||||||
Tooltip,
|
Tooltip
|
||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
import { useDisclosure, useShallowEffect } from "@mantine/hooks";
|
import { useDisclosure, useShallowEffect } from "@mantine/hooks";
|
||||||
import { IconEdit, IconEye, IconPlus, IconTrash } from "@tabler/icons-react";
|
import { IconEye, IconTrash } from "@tabler/icons-react";
|
||||||
import type { JsonValue } from "generated/prisma/runtime/library";
|
import type { JsonValue } from "generated/prisma/runtime/library";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
@@ -44,13 +41,13 @@ export default function KategoriPelayananSurat({
|
|||||||
const [dataChoose, setDataChoose] = useState({
|
const [dataChoose, setDataChoose] = useState({
|
||||||
id: "",
|
id: "",
|
||||||
name: "",
|
name: "",
|
||||||
syaratDokumen: [{ name: "", desc: "" }],
|
syaratDokumen: [{ key: "", name: "", desc: "" }],
|
||||||
dataText: [""],
|
dataPelengkap: [{ key: "", name: "", desc: "" }],
|
||||||
});
|
});
|
||||||
const [dataTambah, setDataTambah] = useState({
|
const [dataTambah, setDataTambah] = useState({
|
||||||
name: "",
|
name: "",
|
||||||
syaratDokumen: [{ name: "", desc: "" }],
|
syaratDokumen: [{ key: "", name: "", desc: "" }],
|
||||||
dataText: [""],
|
dataPelengkap: [{ key: "", name: "", desc: "" }],
|
||||||
});
|
});
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
@@ -60,8 +57,8 @@ export default function KategoriPelayananSurat({
|
|||||||
async function handleCreate() {
|
async function handleCreate() {
|
||||||
try {
|
try {
|
||||||
setBtnLoading(true);
|
setBtnLoading(true);
|
||||||
const cleanedDataText = dataTambah.dataText
|
const cleanedDataText = dataTambah.dataPelengkap
|
||||||
.map((v) => v.trim())
|
.map((v) => v.name.trim())
|
||||||
.filter((v) => v !== "");
|
.filter((v) => v !== "");
|
||||||
const cleanedSyarat = dataTambah.syaratDokumen
|
const cleanedSyarat = dataTambah.syaratDokumen
|
||||||
.map((item) => ({
|
.map((item) => ({
|
||||||
@@ -82,8 +79,8 @@ export default function KategoriPelayananSurat({
|
|||||||
closeTambah();
|
closeTambah();
|
||||||
setDataTambah({
|
setDataTambah({
|
||||||
name: "",
|
name: "",
|
||||||
syaratDokumen: [{ name: "", desc: "" }],
|
syaratDokumen: [{ key: "", name: "", desc: "" }],
|
||||||
dataText: [""],
|
dataPelengkap: [{ key: "", name: "", desc: "" }],
|
||||||
});
|
});
|
||||||
notification({
|
notification({
|
||||||
title: "Success",
|
title: "Success",
|
||||||
@@ -112,8 +109,8 @@ export default function KategoriPelayananSurat({
|
|||||||
async function handleEdit() {
|
async function handleEdit() {
|
||||||
try {
|
try {
|
||||||
setBtnLoading(true);
|
setBtnLoading(true);
|
||||||
const cleanedDataText = dataChoose.dataText
|
const cleanedDataText = dataChoose.dataPelengkap
|
||||||
.map((v) => v.trim())
|
.map((v) => v.name.trim())
|
||||||
.filter((v) => v !== "");
|
.filter((v) => v !== "");
|
||||||
const cleanedSyarat = dataChoose.syaratDokumen
|
const cleanedSyarat = dataChoose.syaratDokumen
|
||||||
.map((item) => ({
|
.map((item) => ({
|
||||||
@@ -191,7 +188,10 @@ export default function KategoriPelayananSurat({
|
|||||||
function handleAddSyarat() {
|
function handleAddSyarat() {
|
||||||
setDataChoose({
|
setDataChoose({
|
||||||
...dataChoose,
|
...dataChoose,
|
||||||
syaratDokumen: [...dataChoose.syaratDokumen, { name: "", desc: "" }],
|
syaratDokumen: [
|
||||||
|
...dataChoose.syaratDokumen,
|
||||||
|
{ key: "", name: "", desc: "" },
|
||||||
|
],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,7 +204,7 @@ export default function KategoriPelayananSurat({
|
|||||||
|
|
||||||
function handleEditSyarat(
|
function handleEditSyarat(
|
||||||
index: number,
|
index: number,
|
||||||
data: { name: string; desc: string },
|
data: { key: string; name: string; desc: string },
|
||||||
) {
|
) {
|
||||||
setDataChoose({
|
setDataChoose({
|
||||||
...dataChoose,
|
...dataChoose,
|
||||||
@@ -217,7 +217,7 @@ export default function KategoriPelayananSurat({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* Modal Edit */}
|
{/* Modal Edit */}
|
||||||
<Modal
|
{/* <Modal
|
||||||
opened={opened}
|
opened={opened}
|
||||||
onClose={close}
|
onClose={close}
|
||||||
title={"Edit"}
|
title={"Edit"}
|
||||||
@@ -233,15 +233,85 @@ export default function KategoriPelayananSurat({
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</Input.Wrapper>
|
</Input.Wrapper>
|
||||||
<TagsInput
|
<Flex direction={"column"} gap={"md"}>
|
||||||
label="Data Pelengkap"
|
<Group>
|
||||||
placeholder="Tambah data pelengkap"
|
<Text size="sm" c={"white"}>
|
||||||
splitChars={[","]}
|
Data Pelengkap
|
||||||
value={dataChoose.dataText}
|
</Text>
|
||||||
onChange={(value) =>
|
<Tooltip label="Tambah Data Pelengkap">
|
||||||
setDataChoose({ ...dataChoose, dataText: value })
|
<ActionIcon
|
||||||
}
|
variant="light"
|
||||||
/>
|
size="sm"
|
||||||
|
color="blue"
|
||||||
|
style={{ boxShadow: "0 0 8px rgba(0,255,200,0.2)" }}
|
||||||
|
onClick={handleAddSyarat}
|
||||||
|
>
|
||||||
|
<IconPlus size={20} />
|
||||||
|
</ActionIcon>
|
||||||
|
</Tooltip>
|
||||||
|
</Group>
|
||||||
|
{dataChoose?.dataPelengkap?.map((v: any, i: number) => (
|
||||||
|
<Grid
|
||||||
|
key={i}
|
||||||
|
style={{
|
||||||
|
borderBottom: "1px solid gray",
|
||||||
|
paddingBottom: "10px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Grid.Col
|
||||||
|
span={1}
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Tooltip label="Delete Syarat Dokumen">
|
||||||
|
<ActionIcon
|
||||||
|
variant="light"
|
||||||
|
size="sm"
|
||||||
|
color="red"
|
||||||
|
style={{ boxShadow: "0 0 8px rgba(0,255,200,0.2)" }}
|
||||||
|
onClick={() => {
|
||||||
|
handleDeleteSyarat(i);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconTrash size={20} />
|
||||||
|
</ActionIcon>
|
||||||
|
</Tooltip>
|
||||||
|
</Grid.Col>
|
||||||
|
<Grid.Col span={5}>
|
||||||
|
<Input.Wrapper label="Label">
|
||||||
|
<Input
|
||||||
|
value={v.name}
|
||||||
|
onChange={(e) =>
|
||||||
|
handleEditSyarat(i, {
|
||||||
|
key: v.key,
|
||||||
|
name: e.target.value,
|
||||||
|
desc: v.desc,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Input.Wrapper>
|
||||||
|
</Grid.Col>
|
||||||
|
<Grid.Col span={6}>
|
||||||
|
<Input.Wrapper label="Deskripsi">
|
||||||
|
<Input
|
||||||
|
value={v.desc}
|
||||||
|
onChange={(e) =>
|
||||||
|
handleEditSyarat(i, {
|
||||||
|
key: v.key,
|
||||||
|
name: v.name,
|
||||||
|
desc: e.target.value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Input.Wrapper>
|
||||||
|
</Grid.Col>
|
||||||
|
</Grid>
|
||||||
|
))}
|
||||||
|
</Flex>
|
||||||
|
|
||||||
<Flex direction={"column"} gap={"md"}>
|
<Flex direction={"column"} gap={"md"}>
|
||||||
<Group>
|
<Group>
|
||||||
<Text size="sm" c={"white"}>
|
<Text size="sm" c={"white"}>
|
||||||
@@ -290,11 +360,12 @@ export default function KategoriPelayananSurat({
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Grid.Col>
|
</Grid.Col>
|
||||||
<Grid.Col span={5}>
|
<Grid.Col span={5}>
|
||||||
<Input.Wrapper label="Nama">
|
<Input.Wrapper label="Label">
|
||||||
<Input
|
<Input
|
||||||
value={v.name}
|
value={v.name}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
handleEditSyarat(i, {
|
handleEditSyarat(i, {
|
||||||
|
key: v.key,
|
||||||
name: e.target.value,
|
name: e.target.value,
|
||||||
desc: v.desc,
|
desc: v.desc,
|
||||||
})
|
})
|
||||||
@@ -308,6 +379,7 @@ export default function KategoriPelayananSurat({
|
|||||||
value={v.desc}
|
value={v.desc}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
handleEditSyarat(i, {
|
handleEditSyarat(i, {
|
||||||
|
key: v.key,
|
||||||
name: v.name,
|
name: v.name,
|
||||||
desc: e.target.value,
|
desc: e.target.value,
|
||||||
})
|
})
|
||||||
@@ -328,10 +400,10 @@ export default function KategoriPelayananSurat({
|
|||||||
</Button>
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Modal>
|
</Modal> */}
|
||||||
|
|
||||||
{/* Modal Tambah */}
|
{/* Modal Tambah */}
|
||||||
<Modal
|
{/* <Modal
|
||||||
opened={openedTambah}
|
opened={openedTambah}
|
||||||
onClose={closeTambah}
|
onClose={closeTambah}
|
||||||
title={"Tambah"}
|
title={"Tambah"}
|
||||||
@@ -347,15 +419,6 @@ export default function KategoriPelayananSurat({
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</Input.Wrapper>
|
</Input.Wrapper>
|
||||||
<TagsInput
|
|
||||||
label="Data Pelengkap"
|
|
||||||
placeholder="Tambah data pelengkap"
|
|
||||||
splitChars={[","]}
|
|
||||||
value={dataTambah.dataText}
|
|
||||||
onChange={(value) =>
|
|
||||||
setDataTambah({ ...dataTambah, dataText: value })
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<Flex direction={"column"} gap={"md"}>
|
<Flex direction={"column"} gap={"md"}>
|
||||||
<Group>
|
<Group>
|
||||||
<Text size="sm" c={"white"}>
|
<Text size="sm" c={"white"}>
|
||||||
@@ -372,7 +435,7 @@ export default function KategoriPelayananSurat({
|
|||||||
...dataTambah,
|
...dataTambah,
|
||||||
syaratDokumen: [
|
syaratDokumen: [
|
||||||
...dataTambah.syaratDokumen,
|
...dataTambah.syaratDokumen,
|
||||||
{ name: "", desc: "" },
|
{ key: "", name: "", desc: "" },
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
@@ -465,7 +528,7 @@ export default function KategoriPelayananSurat({
|
|||||||
</Button>
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Modal>
|
</Modal> */}
|
||||||
|
|
||||||
{/* Modal Delete */}
|
{/* Modal Delete */}
|
||||||
<Modal
|
<Modal
|
||||||
@@ -524,8 +587,8 @@ export default function KategoriPelayananSurat({
|
|||||||
Data Pelengkap
|
Data Pelengkap
|
||||||
</Text>
|
</Text>
|
||||||
<List>
|
<List>
|
||||||
{dataChoose?.dataText?.map((v: any) => (
|
{dataChoose?.dataPelengkap?.map((v: any) => (
|
||||||
<List.Item key={v.id}>{v}</List.Item>
|
<List.Item key={v.id}>{v.name}</List.Item>
|
||||||
))}
|
))}
|
||||||
</List>
|
</List>
|
||||||
</Flex>
|
</Flex>
|
||||||
@@ -538,7 +601,7 @@ export default function KategoriPelayananSurat({
|
|||||||
<Title order={4} c="gray.2">
|
<Title order={4} c="gray.2">
|
||||||
Kategori Pelayanan Surat
|
Kategori Pelayanan Surat
|
||||||
</Title>
|
</Title>
|
||||||
{permissions.includes("setting.kategori_pelayanan.tambah") && (
|
{/* {permissions.includes("setting.kategori_pelayanan.tambah") && (
|
||||||
<Tooltip label="Tambah Kategori Pelayanan Surat">
|
<Tooltip label="Tambah Kategori Pelayanan Surat">
|
||||||
<Button
|
<Button
|
||||||
variant="light"
|
variant="light"
|
||||||
@@ -548,7 +611,7 @@ export default function KategoriPelayananSurat({
|
|||||||
Tambah
|
Tambah
|
||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)} */}
|
||||||
</Flex>
|
</Flex>
|
||||||
<Divider my={0} />
|
<Divider my={0} />
|
||||||
<Stack gap={"md"}>
|
<Stack gap={"md"}>
|
||||||
@@ -560,7 +623,7 @@ export default function KategoriPelayananSurat({
|
|||||||
</Table.Tr>
|
</Table.Tr>
|
||||||
</Table.Thead>
|
</Table.Thead>
|
||||||
<Table.Tbody>
|
<Table.Tbody>
|
||||||
{list?.map((v: any) => (
|
{list.length > 0 && list?.map((v: any) => (
|
||||||
<Table.Tr key={v.id}>
|
<Table.Tr key={v.id}>
|
||||||
<Table.Td>{v.name}</Table.Td>
|
<Table.Td>{v.name}</Table.Td>
|
||||||
<Table.Td>
|
<Table.Td>
|
||||||
@@ -579,7 +642,7 @@ export default function KategoriPelayananSurat({
|
|||||||
<IconEye size={20} />
|
<IconEye size={20} />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip
|
{/* <Tooltip
|
||||||
label={
|
label={
|
||||||
permissions.includes(
|
permissions.includes(
|
||||||
"setting.kategori_pelayanan.edit",
|
"setting.kategori_pelayanan.edit",
|
||||||
@@ -604,7 +667,7 @@ export default function KategoriPelayananSurat({
|
|||||||
>
|
>
|
||||||
<IconEdit size={20} />
|
<IconEdit size={20} />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
</Tooltip>
|
</Tooltip> */}
|
||||||
<Tooltip
|
<Tooltip
|
||||||
label={
|
label={
|
||||||
permissions.includes(
|
permissions.includes(
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import apiFetch from "@/lib/apiFetch";
|
import apiFetch from "@/lib/apiFetch";
|
||||||
import { ActionIcon, Flex, Modal } from "@mantine/core";
|
import { Flex, Modal, Progress, Stack, Text } from "@mantine/core";
|
||||||
import { useShallowEffect } from "@mantine/hooks";
|
import { useShallowEffect } from "@mantine/hooks";
|
||||||
import { IconDownload, IconX } from "@tabler/icons-react";
|
|
||||||
import html2canvas from "html2canvas";
|
import html2canvas from "html2canvas";
|
||||||
import jsPDF from "jspdf";
|
import jsPDF from "jspdf";
|
||||||
import { useRef } from "react";
|
import { useRef, useState } from "react";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
import SKBedaBiodataDiri from "./surat/SKBedaBiodataDiri";
|
import SKBedaBiodataDiri from "./surat/SKBedaBiodataDiri";
|
||||||
import SKBelumKawin from "./surat/SKBelumKawin";
|
import SKBelumKawin from "./surat/SKBelumKawin";
|
||||||
@@ -24,7 +23,7 @@ export default function ModalSurat({
|
|||||||
surat,
|
surat,
|
||||||
}: {
|
}: {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
onClose: () => void;
|
onClose: (val: { success: boolean, data: string }) => void;
|
||||||
surat: string;
|
surat: string;
|
||||||
}) {
|
}) {
|
||||||
const A4Style = {
|
const A4Style = {
|
||||||
@@ -36,6 +35,7 @@ export default function ModalSurat({
|
|||||||
fontSize: "14px",
|
fontSize: "14px",
|
||||||
fontFamily: "Times New Roman",
|
fontFamily: "Times New Roman",
|
||||||
};
|
};
|
||||||
|
const [uploading, setUploading] = useState<{ text: "Menyiapkan" | "Mengupload" | "Selesai" | "Gagal", value: number }>({ text: "Menyiapkan", value: 10 })
|
||||||
const hiddenRef = useRef<any>(null);
|
const hiddenRef = useRef<any>(null);
|
||||||
const { data, mutate, isLoading } = useSWR("surat", () =>
|
const { data, mutate, isLoading } = useSWR("surat", () =>
|
||||||
apiFetch.api.surat.detail.get({
|
apiFetch.api.surat.detail.get({
|
||||||
@@ -45,42 +45,97 @@ export default function ModalSurat({
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
mutate();
|
mutate();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const downloadPDF = async () => {
|
const uploadPdf = async () => {
|
||||||
const element = hiddenRef.current;
|
try {
|
||||||
const canvas = await html2canvas(element, {
|
if (data && data.data && data.data.surat && (data.data.surat.file == "" || data.data.surat.file == null)) {
|
||||||
scale: 2,
|
setUploading({ text: "Mengupload", value: 75 });
|
||||||
useCORS: true,
|
const element = hiddenRef.current;
|
||||||
allowTaint: true,
|
const canvas = await html2canvas(element, {
|
||||||
width: element.offsetWidth,
|
scale: 2,
|
||||||
height: element.offsetHeight,
|
useCORS: true,
|
||||||
});
|
allowTaint: true,
|
||||||
|
width: element.offsetWidth,
|
||||||
|
height: element.offsetHeight,
|
||||||
|
});
|
||||||
|
|
||||||
const imgData = canvas.toDataURL("image/jpeg", 1.0);
|
const imgData = canvas.toDataURL("image/jpeg", 1.0);
|
||||||
|
|
||||||
const pdf = new jsPDF("p", "mm", "a4");
|
const pdf = new jsPDF("p", "mm", "a4");
|
||||||
const pageWidth = 210; // A4 width mm
|
const pageWidth = 210; // A4 width mm
|
||||||
const pageHeight = 297; // A4 height mm
|
const pageHeight = 297; // A4 height mm
|
||||||
|
|
||||||
const imgWidth = pageWidth;
|
const imgWidth = pageWidth;
|
||||||
const imgHeight = (canvas.height * pageWidth) / canvas.width;
|
const imgHeight = (canvas.height * pageWidth) / canvas.width;
|
||||||
|
|
||||||
pdf.addImage(imgData, "JPEG", 0, 0, imgWidth, imgHeight);
|
pdf.addImage(imgData, "JPEG", 0, 0, imgWidth, imgHeight);
|
||||||
|
|
||||||
pdf.save(`${data?.data?.surat?.nameCategory}.pdf`);
|
// ⬇️ ambil sebagai Blob
|
||||||
};
|
const pdfBlob = pdf.output("blob");
|
||||||
|
|
||||||
|
const pdfFile = new File(
|
||||||
|
[pdfBlob],
|
||||||
|
`${data?.data?.surat?.nameCategory}.pdf`,
|
||||||
|
{
|
||||||
|
type: "application/pdf",
|
||||||
|
lastModified: Date.now(),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const resImg = await apiFetch.api.pengaduan.upload.post({
|
||||||
|
file: pdfFile,
|
||||||
|
folder: "surat",
|
||||||
|
});
|
||||||
|
|
||||||
|
const resUpdate = await apiFetch.api.surat.update.post({
|
||||||
|
id: surat,
|
||||||
|
filename: resImg.data?.filename!,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (resUpdate?.data?.success) {
|
||||||
|
setUploading({ text: "Selesai", value: 100 });
|
||||||
|
setTimeout(() => {
|
||||||
|
onClose({ success: true, data: resUpdate.data?.link });
|
||||||
|
}, 1000)
|
||||||
|
} else {
|
||||||
|
setUploading({ text: "Gagal", value: 100 });
|
||||||
|
setTimeout(() => {
|
||||||
|
onClose({ success: false, data: "" });
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setUploading({ text: "Gagal", value: 100 });
|
||||||
|
setTimeout(() => {
|
||||||
|
onClose({ success: false, data: "" });
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error uploading PDF:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useShallowEffect(() => {
|
||||||
|
if (open) {
|
||||||
|
setTimeout(() => {
|
||||||
|
uploadPdf();
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
}, [surat, open]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Modal
|
<Modal
|
||||||
opened={open}
|
opened={open}
|
||||||
onClose={() => onClose()}
|
onClose={() => { }}
|
||||||
overlayProps={{ backgroundOpacity: 0.55, blur: 3 }}
|
overlayProps={{ backgroundOpacity: 0.55, blur: 3 }}
|
||||||
size="auto"
|
size="auto"
|
||||||
withCloseButton={false}
|
withCloseButton={false}
|
||||||
|
closeOnClickOutside={false}
|
||||||
removeScrollProps={{ allowPinchZoom: true }}
|
removeScrollProps={{ allowPinchZoom: true }}
|
||||||
styles={{
|
styles={{
|
||||||
header: {
|
header: {
|
||||||
@@ -94,19 +149,24 @@ export default function ModalSurat({
|
|||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
title={
|
title={
|
||||||
<Flex justify="space-between" align="center" w="100%">
|
<>
|
||||||
<div style={{ fontSize: 18, fontWeight: 600 }}>Preview Surat</div>
|
<Flex justify="space-between" align="center" w="100%">
|
||||||
|
<div style={{ fontSize: 18, fontWeight: 600 }}>Preview Surat</div>
|
||||||
|
|
||||||
<Flex gap={8}>
|
{/* <Flex gap={8} align="center">
|
||||||
<ActionIcon size={32} variant="default">
|
<Loader color="blue" size="xs" />
|
||||||
<IconDownload size={20} onClick={downloadPDF} />
|
<Text size="sm">{uploading.text}</Text>
|
||||||
</ActionIcon>
|
</Flex> */}
|
||||||
|
|
||||||
<ActionIcon size={32} variant="default" onClick={onClose}>
|
|
||||||
<IconX size={20} />
|
|
||||||
</ActionIcon>
|
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
<Stack
|
||||||
|
align="stretch"
|
||||||
|
justify="center"
|
||||||
|
gap="xs"
|
||||||
|
>
|
||||||
|
<Text size="sm" ta="center">{uploading.text} - Harap menunggu sampai selesai</Text>
|
||||||
|
<Progress radius="md" value={uploading.value} animated size="lg" />
|
||||||
|
</Stack>
|
||||||
|
</>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div ref={hiddenRef} style={A4Style}>
|
<div ref={hiddenRef} style={A4Style}>
|
||||||
|
|||||||
45
src/components/NotFoundPengajuanSurat.tsx
Normal file
45
src/components/NotFoundPengajuanSurat.tsx
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import { Button, Center, Group, Stack, Text, Title } from "@mantine/core";
|
||||||
|
import { IconSearch } from "@tabler/icons-react";
|
||||||
|
|
||||||
|
export function DataNotFound({
|
||||||
|
onRetry,
|
||||||
|
backTo,
|
||||||
|
}: {
|
||||||
|
onRetry?: () => void;
|
||||||
|
backTo?: () => void;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<Center mih={320}>
|
||||||
|
<Stack align="center" gap="sm">
|
||||||
|
<IconSearch size={64} opacity={0.5} />
|
||||||
|
|
||||||
|
<Title order={4}>Data Pengajuan Tidak Ditemukan</Title>
|
||||||
|
|
||||||
|
<Text size="sm" c="dimmed" ta="center" maw={380}>
|
||||||
|
Kami tidak dapat menemukan data pengajuan dengan nomor pengajuan yg
|
||||||
|
diinputkan. Silakan periksa kembali data Anda.
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<Group mt="md">
|
||||||
|
{/* {onRetry && (
|
||||||
|
<Button
|
||||||
|
variant="light"
|
||||||
|
leftSection={<IconSearch size={16} />}
|
||||||
|
onClick={onRetry}
|
||||||
|
>
|
||||||
|
Cari Ulang
|
||||||
|
</Button>
|
||||||
|
)} */}
|
||||||
|
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
// leftSection={<IconArrowLeft size={16} />}
|
||||||
|
onClick={backTo}
|
||||||
|
>
|
||||||
|
Cari Kembali
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Center>
|
||||||
|
);
|
||||||
|
}
|
||||||
49
src/components/SuccessPengajuanSurat.tsx
Normal file
49
src/components/SuccessPengajuanSurat.tsx
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import { Badge, Button, Card, Center, Stack, Text, Title } from "@mantine/core";
|
||||||
|
import { IconCheck } from "@tabler/icons-react";
|
||||||
|
|
||||||
|
type SuccessPengajuanProps = {
|
||||||
|
noPengajuan: string;
|
||||||
|
onClose?: () => void;
|
||||||
|
category?: "create" | "update";
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function SuccessPengajuan({
|
||||||
|
noPengajuan,
|
||||||
|
onClose,
|
||||||
|
category,
|
||||||
|
}: SuccessPengajuanProps) {
|
||||||
|
return (
|
||||||
|
<Center h="100vh">
|
||||||
|
<Card shadow="md" radius="md" p="xl" withBorder maw={520} w="100%">
|
||||||
|
<Stack align="center" gap="md">
|
||||||
|
<IconCheck size={56} color="green" />
|
||||||
|
|
||||||
|
<Title order={3} ta="center">
|
||||||
|
{category == "create"
|
||||||
|
? "Pengajuan Berhasil Dibuat"
|
||||||
|
: "Pengajuan Berhasil Diupdate"}
|
||||||
|
</Title>
|
||||||
|
|
||||||
|
<Text ta="center" size="sm" c="dimmed">
|
||||||
|
{category == "create"
|
||||||
|
? "Pengajuan layanan surat sudah dibuat dengan nomor:"
|
||||||
|
: "Pengajuan layanan surat sudah diupdate dengan nomor:"}
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<Badge size="xl" variant="light" color="green">
|
||||||
|
{noPengajuan}
|
||||||
|
</Badge>
|
||||||
|
|
||||||
|
<Text ta="center" size="sm">
|
||||||
|
Nomor ini akan digunakan untuk mengakses dan memantau status
|
||||||
|
pengajuan surat Anda.
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<Button fullWidth mt="md" onClick={onClose}>
|
||||||
|
Selesai
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
|
</Card>
|
||||||
|
</Center>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -108,12 +108,12 @@ export default function SKBedaBiodataDiri({ data }: { data: any }) {
|
|||||||
<tr>
|
<tr>
|
||||||
<td>Tempat/Tanggal Lahir</td>
|
<td>Tempat/Tanggal Lahir</td>
|
||||||
<td>:</td>
|
<td>:</td>
|
||||||
<td>{getValue("tempat tanggal lahir")}</td>
|
<td>{`${getValue("tempat_lahir")}, ${getValue("tanggal_lahir")}`}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Jenis Kelamin</td>
|
<td>Jenis Kelamin</td>
|
||||||
<td>:</td>
|
<td>:</td>
|
||||||
<td>{getValue("jenis kelamin")}</td>
|
<td>{getValue("jenis_kelamin")}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Alamat</td>
|
<td>Alamat</td>
|
||||||
@@ -144,36 +144,36 @@ export default function SKBedaBiodataDiri({ data }: { data: any }) {
|
|||||||
<table style={{ width: "100%", marginTop: "5px" }}>
|
<table style={{ width: "100%", marginTop: "5px" }}>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td style={{ width: "160px" }}>1. Nama</td>
|
<td style={{ width: "160px" }}>1. {getValue("data_dokumen")}</td>
|
||||||
<td style={{ width: "10px" }}>:</td>
|
<td style={{ width: "10px" }}></td>
|
||||||
<td>{getValue("nama")}</td>
|
<td>{/* {getValue("nama")} */}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Tertulis pada dokumen A</td>
|
<td>Tertulis pada dokumen A</td>
|
||||||
<td>:</td>
|
<td>:</td>
|
||||||
<td>{getValue("tertulis pada dokumen a")}</td>
|
<td>{getValue("dokumen_a")}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Tertulis pada dokumen B</td>
|
<td>Tertulis pada dokumen B</td>
|
||||||
<td>:</td>
|
<td>:</td>
|
||||||
<td>{getValue("tertulis pada dokumen b")}</td>
|
<td>{getValue("dokumen_b")}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style={{ marginTop: "15px" }}>
|
{/* <div style={{ marginTop: "15px" }}>
|
||||||
<table style={{ width: "100%", marginTop: "5px" }}>
|
<table style={{ width: "100%", marginTop: "5px" }}>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td style={{ width: "160px" }}>2. Tempat/Tanggal Lahir</td>
|
<td style={{ width: "160px" }}>2. Tempat/Tanggal Lahir</td>
|
||||||
<td style={{ width: "10px" }}>:</td>
|
<td style={{ width: "10px" }}>:</td>
|
||||||
<td>{getValue("tempat tanggal lahir")}</td>
|
<td>{getValue("tempat_lahir")}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Tertulis pada dokumen A</td>
|
<td>Tertulis pada dokumen A</td>
|
||||||
<td>:</td>
|
<td>:</td>
|
||||||
<td>{getValue("tertulis pada dokumen a")}</td>
|
<td>{getValue("dokumen_a")}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Tertulis pada dokumen B</td>
|
<td>Tertulis pada dokumen B</td>
|
||||||
@@ -204,7 +204,7 @@ export default function SKBedaBiodataDiri({ data }: { data: any }) {
|
|||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div> */}
|
||||||
|
|
||||||
<div style={{ marginTop: "15px" }}>
|
<div style={{ marginTop: "15px" }}>
|
||||||
Perbedaan tersebut terjadi karena{" "}
|
Perbedaan tersebut terjadi karena{" "}
|
||||||
|
|||||||
@@ -87,12 +87,12 @@ export default function SKBelumKawin({ data }: { data: any }) {
|
|||||||
<tr>
|
<tr>
|
||||||
<td>Tempat/Tanggal Lahir</td>
|
<td>Tempat/Tanggal Lahir</td>
|
||||||
<td>:</td>
|
<td>:</td>
|
||||||
<td>{getValue("tempat tanggal lahir")}</td>
|
<td>{`${getValue("tempat_lahir")}, ${getValue("tanggal_lahir")}`}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Jenis Kelamin</td>
|
<td>Jenis Kelamin</td>
|
||||||
<td>:</td>
|
<td>:</td>
|
||||||
<td>{getValue("jenis kelamin")}</td>
|
<td>{getValue("jenis_kelamin")}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Agama</td>
|
<td>Agama</td>
|
||||||
|
|||||||
@@ -95,27 +95,27 @@ export default function SKDomisiliOrganisasi({ data }: { data: any }) {
|
|||||||
<tr>
|
<tr>
|
||||||
<td style={{ width: "160px" }}>Nama Organisasi</td>
|
<td style={{ width: "160px" }}>Nama Organisasi</td>
|
||||||
<td style={{ width: "10px" }}>:</td>
|
<td style={{ width: "10px" }}>:</td>
|
||||||
<td>{getValue("nama")}</td>
|
<td>{getValue("nama_organisasi")}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Jenis Organisasi</td>
|
<td>Jenis Organisasi</td>
|
||||||
<td>:</td>
|
<td>:</td>
|
||||||
<td>{getValue("jenis kelamin")}</td>
|
<td>{getValue("jenis_organisasi")}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Alamat</td>
|
<td>Alamat</td>
|
||||||
<td>:</td>
|
<td>:</td>
|
||||||
<td>{getValue("tempat tanggal lahir")}</td>
|
<td>{getValue("alamat_organisasi")}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Nomor Telepon</td>
|
<td>Nomor Telepon</td>
|
||||||
<td>:</td>
|
<td>:</td>
|
||||||
<td>{getValue("negara")}</td>
|
<td>{getValue("no_telepon")}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Nama Pimpinan</td>
|
<td>Nama Pimpinan</td>
|
||||||
<td>:</td>
|
<td>:</td>
|
||||||
<td>{getValue("agama")}</td>
|
<td>{getValue("nama_pimpinan")}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ export default function SKKelahiran({ data }: { data: any }) {
|
|||||||
const getValue = (jenis: string) =>
|
const getValue = (jenis: string) =>
|
||||||
_.upperFirst(
|
_.upperFirst(
|
||||||
data.surat.dataText.find((item: any) => item.jenis === jenis)?.value ||
|
data.surat.dataText.find((item: any) => item.jenis === jenis)?.value ||
|
||||||
"",
|
"",
|
||||||
);
|
);
|
||||||
|
|
||||||
const loadImage = async () => {
|
const loadImage = async () => {
|
||||||
@@ -78,32 +78,32 @@ export default function SKKelahiran({ data }: { data: any }) {
|
|||||||
<tr>
|
<tr>
|
||||||
<td style={{ width: "200px" }}>Tanggal Lahir</td>
|
<td style={{ width: "200px" }}>Tanggal Lahir</td>
|
||||||
<td>:</td>
|
<td>:</td>
|
||||||
<td>{getValue("tanggal lahir anak")}</td>
|
<td>{getValue("tanggal_lahir_anak")}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Pukul</td>
|
<td>Pukul</td>
|
||||||
<td>:</td>
|
<td>:</td>
|
||||||
<td>{getValue("pukul lahir anak")}</td>
|
<td>{getValue("pukul_lahir")}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Tempat Kelahiran</td>
|
<td>Tempat Kelahiran</td>
|
||||||
<td>:</td>
|
<td>:</td>
|
||||||
<td>{getValue("tempat lahir anak")}</td>
|
<td>{getValue("tempat_lahir")}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Jenis Kelamin</td>
|
<td>Jenis Kelamin</td>
|
||||||
<td>:</td>
|
<td>:</td>
|
||||||
<td>{getValue("jenis kelamin anak")}</td>
|
<td>{getValue("jenis_kelamin")}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Anak ke</td>
|
<td>Anak ke</td>
|
||||||
<td>:</td>
|
<td>:</td>
|
||||||
<td>{getValue("anak ke")}</td>
|
<td>{getValue("anak_ke")}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Nama Anak</td>
|
<td>Nama Anak</td>
|
||||||
<td>:</td>
|
<td>:</td>
|
||||||
<td>{getValue("nama anak")}</td>
|
<td>{getValue("nama_anak")}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
@@ -117,27 +117,27 @@ export default function SKKelahiran({ data }: { data: any }) {
|
|||||||
<tr>
|
<tr>
|
||||||
<td style={{ width: "200px" }}>Nama Lengkap Ibu</td>
|
<td style={{ width: "200px" }}>Nama Lengkap Ibu</td>
|
||||||
<td>:</td>
|
<td>:</td>
|
||||||
<td>{getValue("nama ibu")}</td>
|
<td>{getValue("nama_ibu")}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>NIK</td>
|
<td>NIK</td>
|
||||||
<td>:</td>
|
<td>:</td>
|
||||||
<td>{getValue("nik ibu")}</td>
|
<td>{getValue("nik_ibu")}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Tempat & Tanggal Lahir</td>
|
<td>Tempat & Tanggal Lahir</td>
|
||||||
<td>:</td>
|
<td>:</td>
|
||||||
<td>{getValue("tempat tanggal lahir ibu")}</td>
|
<td>{`${getValue("tempat_lahir_ibu")}, ${getValue("tanggal_lahir_ibu")}`}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Pekerjaan</td>
|
<td>Pekerjaan</td>
|
||||||
<td>:</td>
|
<td>:</td>
|
||||||
<td>{getValue("pekerjaan ibu")}</td>
|
<td>{getValue("pekerjaan_ibu")}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Alamat</td>
|
<td>Alamat</td>
|
||||||
<td>:</td>
|
<td>:</td>
|
||||||
<td>{getValue("alamat ibu")}</td>
|
<td>{getValue("alamat_ibu")}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
@@ -151,27 +151,27 @@ export default function SKKelahiran({ data }: { data: any }) {
|
|||||||
<tr>
|
<tr>
|
||||||
<td style={{ width: "200px" }}>Nama Lengkap Ayah</td>
|
<td style={{ width: "200px" }}>Nama Lengkap Ayah</td>
|
||||||
<td>:</td>
|
<td>:</td>
|
||||||
<td>{getValue("nama ayah")}</td>
|
<td>{getValue("nama_ayah")}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>NIK</td>
|
<td>NIK</td>
|
||||||
<td>:</td>
|
<td>:</td>
|
||||||
<td>{getValue("nik ayah")}</td>
|
<td>{getValue("nik_ayah")}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Tempat & Tanggal Lahir</td>
|
<td>Tempat & Tanggal Lahir</td>
|
||||||
<td>:</td>
|
<td>:</td>
|
||||||
<td>{getValue("tempat tanggal lahir ayah")}</td>
|
<td>{`${getValue("tempat_lahir_ayah")}, ${getValue("tanggal_lahir_ayah")}`}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Pekerjaan</td>
|
<td>Pekerjaan</td>
|
||||||
<td>:</td>
|
<td>:</td>
|
||||||
<td>{getValue("pekerjaan ayah")}</td>
|
<td>{getValue("pekerjaan_ayah")}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Alamat</td>
|
<td>Alamat</td>
|
||||||
<td>:</td>
|
<td>:</td>
|
||||||
<td>{getValue("alamat ayah")}</td>
|
<td>{getValue("alamat_ayah")}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
@@ -185,17 +185,17 @@ export default function SKKelahiran({ data }: { data: any }) {
|
|||||||
<tr>
|
<tr>
|
||||||
<td style={{ width: "200px" }}>Nama Pelapor</td>
|
<td style={{ width: "200px" }}>Nama Pelapor</td>
|
||||||
<td>:</td>
|
<td>:</td>
|
||||||
<td>{getValue("nama pelapor")}</td>
|
<td>{getValue("nama_pelapor")}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Hubungan dengan Anak</td>
|
<td>Hubungan dengan Anak</td>
|
||||||
<td>:</td>
|
<td>:</td>
|
||||||
<td>{getValue("hubungan pelapor")}</td>
|
<td>{getValue("hubungan_pelapor")}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Alamat</td>
|
<td>Alamat</td>
|
||||||
<td>:</td>
|
<td>:</td>
|
||||||
<td>{getValue("alamat pelapor")}</td>
|
<td>{getValue("alamat_pelapor")}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@@ -71,12 +71,12 @@ export default function SKKelakuanBaik({ data }: { data: any }) {
|
|||||||
<tr>
|
<tr>
|
||||||
<td>Tempat/Tgl Lahir</td>
|
<td>Tempat/Tgl Lahir</td>
|
||||||
<td>:</td>
|
<td>:</td>
|
||||||
<td>{getValue("tempat tanggal lahir")}</td>
|
<td>{`${getValue("tempat_lahir")}, ${getValue("tanggal_lahir")}`}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Jenis Kelamin</td>
|
<td>Jenis Kelamin</td>
|
||||||
<td>:</td>
|
<td>:</td>
|
||||||
<td>{getValue("jenis kelamin")}</td>
|
<td>{getValue("jenis_kelamin")}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Agama</td>
|
<td>Agama</td>
|
||||||
|
|||||||
@@ -71,27 +71,27 @@ export default function SKKematian({ data }: { data: any }) {
|
|||||||
<tr>
|
<tr>
|
||||||
<td style={{ width: "160px" }}>Nama</td>
|
<td style={{ width: "160px" }}>Nama</td>
|
||||||
<td style={{ width: "10px" }}>:</td>
|
<td style={{ width: "10px" }}>:</td>
|
||||||
<td>{getValue("nama")}</td>
|
<td>{getValue("nama_pelapor")}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>NIK</td>
|
<td>NIK</td>
|
||||||
<td>:</td>
|
<td>:</td>
|
||||||
<td>{getValue("nik")}</td>
|
<td>{getValue("nik_pelapor")}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Pekerjaan</td>
|
<td>Pekerjaan</td>
|
||||||
<td>:</td>
|
<td>:</td>
|
||||||
<td>{getValue("pekerjaan")}</td>
|
<td>{getValue("pekerjaan_pelapor")}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Alamat</td>
|
<td>Alamat</td>
|
||||||
<td>:</td>
|
<td>:</td>
|
||||||
<td>{getValue("alamat")}</td>
|
<td>{getValue("alamat_pelapor")}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Hubungan dengan almarhum/almarhumah</td>
|
<td>Hubungan dengan almarhum/almarhumah</td>
|
||||||
<td>:</td>
|
<td>:</td>
|
||||||
<td>{getValue("hubungan dengan almarhum")}</td>
|
<td>{getValue("hubungan_pelapor")}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
@@ -104,32 +104,32 @@ export default function SKKematian({ data }: { data: any }) {
|
|||||||
<tr>
|
<tr>
|
||||||
<td style={{ width: "160px" }}>Nama</td>
|
<td style={{ width: "160px" }}>Nama</td>
|
||||||
<td style={{ width: "10px" }}>:</td>
|
<td style={{ width: "10px" }}>:</td>
|
||||||
<td>{getValue("nama")}</td>
|
<td>{getValue("nama_almarhum")}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>NIK</td>
|
<td>NIK</td>
|
||||||
<td>:</td>
|
<td>:</td>
|
||||||
<td>{getValue("nik")}</td>
|
<td>{getValue("nik_almarhum")}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Jenis Kelamin</td>
|
<td>Jenis Kelamin</td>
|
||||||
<td>:</td>
|
<td>:</td>
|
||||||
<td>{getValue("jenis kelamin")}</td>
|
<td>{getValue("jenis_kelamin_almarhum")}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Tempat/Tanggal Lahir</td>
|
<td>Tempat/Tanggal Lahir</td>
|
||||||
<td>:</td>
|
<td>:</td>
|
||||||
<td>{getValue("tempat tanggal lahir")}</td>
|
<td>{`${getValue("tempat_lahir_almarhum")}, ${getValue("tanggal_lahir_almarhum")}`}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Agama</td>
|
<td>Agama</td>
|
||||||
<td>:</td>
|
<td>:</td>
|
||||||
<td>{getValue("agama")}</td>
|
<td>{getValue("agama_almarhum")}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Alamat</td>
|
<td>Alamat</td>
|
||||||
<td>:</td>
|
<td>:</td>
|
||||||
<td>{getValue("alamat")}</td>
|
<td>{getValue("alamat_almarhum")}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
@@ -142,22 +142,22 @@ export default function SKKematian({ data }: { data: any }) {
|
|||||||
<tr>
|
<tr>
|
||||||
<td style={{ width: "160px" }}>Tanggal Kematian</td>
|
<td style={{ width: "160px" }}>Tanggal Kematian</td>
|
||||||
<td style={{ width: "10px" }}>:</td>
|
<td style={{ width: "10px" }}>:</td>
|
||||||
<td>{getValue("tanggal kematian")}</td>
|
<td>{getValue("tanggal_kematian")}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Waktu Kematian</td>
|
<td>Waktu Kematian</td>
|
||||||
<td>:</td>
|
<td>:</td>
|
||||||
<td>{getValue("waktu kematian")}</td>
|
<td>{getValue("waktu_kematian")}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Tempat Kematian</td>
|
<td>Tempat Kematian</td>
|
||||||
<td>:</td>
|
<td>:</td>
|
||||||
<td>{getValue("tempat kematian")}</td>
|
<td>{getValue("tempat_kematian")}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Penyebab Kematian</td>
|
<td>Penyebab Kematian</td>
|
||||||
<td>:</td>
|
<td>:</td>
|
||||||
<td>{getValue("penyebab kematian")}</td>
|
<td>{getValue("penyebab_kematian")}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
@@ -185,7 +185,7 @@ export default function SKKematian({ data }: { data: any }) {
|
|||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
<br /> <br />
|
<br /> <br />
|
||||||
<u>{getValue("nama")}</u> <br />
|
<u>{getValue("nama_pelapor")}</u> <br />
|
||||||
</div>
|
</div>
|
||||||
<div style={{ textAlign: "center" }}>
|
<div style={{ textAlign: "center" }}>
|
||||||
<br />
|
<br />
|
||||||
|
|||||||
@@ -105,12 +105,12 @@ export default function SKPenghasilan({ data }: { data: any }) {
|
|||||||
<tr>
|
<tr>
|
||||||
<td>Jenis Kelamin</td>
|
<td>Jenis Kelamin</td>
|
||||||
<td>:</td>
|
<td>:</td>
|
||||||
<td>{getValue("jenis kelamin")}</td>
|
<td>{getValue("jenis_kelamin")}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Tempat / Tanggal Lahir</td>
|
<td>Tempat / Tanggal Lahir</td>
|
||||||
<td>:</td>
|
<td>:</td>
|
||||||
<td>{getValue("tempat tanggal lahir")}</td>
|
<td>{`${getValue("tempat_lahir")}, ${getValue("tanggal_lahir")}`}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Pekerjaan</td>
|
<td>Pekerjaan</td>
|
||||||
@@ -135,10 +135,7 @@ export default function SKPenghasilan({ data }: { data: any }) {
|
|||||||
<tr>
|
<tr>
|
||||||
<td style={{ width: "160px" }}>Penghasilan</td>
|
<td style={{ width: "160px" }}>Penghasilan</td>
|
||||||
<td style={{ width: "10px" }}>:</td>
|
<td style={{ width: "10px" }}>:</td>
|
||||||
<td>
|
<td>Rp. {getValue("penghasilan")} per bulan</td>
|
||||||
Rp {getValue("penghasilan")} (
|
|
||||||
{getValue("penghasilan terbilang")}) per bulan
|
|
||||||
</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
@@ -146,8 +143,8 @@ export default function SKPenghasilan({ data }: { data: any }) {
|
|||||||
|
|
||||||
{/* KEPERLUAN */}
|
{/* KEPERLUAN */}
|
||||||
<div style={{ marginTop: "20px" }}>
|
<div style={{ marginTop: "20px" }}>
|
||||||
Surat keterangan ini dibuat untuk keperluan:{" "}
|
Surat keterangan ini dibuat untuk keperluan: <b>{getValue("alasan")}</b>
|
||||||
<b>{getValue("alasan permohonan")}</b>.
|
.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style={{ marginTop: "20px" }}>
|
<div style={{ marginTop: "20px" }}>
|
||||||
|
|||||||
@@ -66,12 +66,15 @@ export default function SKTempatUsaha({ data }: { data: any }) {
|
|||||||
|
|
||||||
{/* DATA WARGA */}
|
{/* DATA WARGA */}
|
||||||
<div>
|
<div>
|
||||||
<Row label="Nama Pemilik Usaha" value={getValue("nama")} />
|
<Row label="Nama Pemilik Usaha" value={getValue("nama_pemilik")} />
|
||||||
<Row
|
<Row
|
||||||
label="Tempat/Tanggal Lahir"
|
label="Tempat/Tanggal Lahir"
|
||||||
value={getValue("tempat tanggal lahir")}
|
value={`${getValue("tempat_lahir")}, ${getValue("tanggal_lahir")}`}
|
||||||
|
/>
|
||||||
|
<Row
|
||||||
|
label="Alamat Pemilik Usaha"
|
||||||
|
value={getValue("alamat_pemilik")}
|
||||||
/>
|
/>
|
||||||
<Row label="Alamat Pemilik Usaha" value={getValue("alamat")} />
|
|
||||||
<Row label="Nomor KTP" value={getValue("nik")} />
|
<Row label="Nomor KTP" value={getValue("nik")} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -83,23 +86,17 @@ export default function SKTempatUsaha({ data }: { data: any }) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Row label="Nama Usaha" value={getValue("nama usaha")} />
|
<Row label="Nama Usaha" value={getValue("nama_usaha")} />
|
||||||
<Row label="Bidang Usaha" value={getValue("bidang usaha")} />
|
<Row label="Bidang Usaha" value={getValue("bidang_usaha")} />
|
||||||
<Row label="Alamat Usaha" value={getValue("alamat usaha")} />
|
<Row label="Alamat Usaha" value={getValue("alamat_usaha")} />
|
||||||
<Row
|
<Row label="Status Tempat Usaha" value={getValue("status_tempat")} />
|
||||||
label="Status Tempat Usaha"
|
<Row label="Luas Tempat Usaha" value={getValue("luas_usaha") + " m2"} />
|
||||||
value={getValue("status tempat usaha")}
|
<Row label="Jumlah Karyawan" value={getValue("jumlah_karyawan")} />
|
||||||
/>
|
|
||||||
<Row
|
|
||||||
label="Luas Tempat Usaha"
|
|
||||||
value={getValue("luas tempat usaha")}
|
|
||||||
/>
|
|
||||||
<Row label="Jumlah Karyawan" value={getValue("jumlah karyawan")} />
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p style={{ textAlign: "justify" }}>
|
<p style={{ textAlign: "justify" }}>
|
||||||
Surat keterangan ini dibuat untuk keperluan{" "}
|
Surat keterangan ini dibuat untuk keperluan{" "}
|
||||||
<b>{getValue("alasan permohonan")}.</b>
|
<b>{getValue("tujuan")}.</b>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p style={{ textAlign: "justify" }}>
|
<p style={{ textAlign: "justify" }}>
|
||||||
|
|||||||
@@ -66,13 +66,13 @@ export default function SKTidakMampu({ data }: { data: any }) {
|
|||||||
|
|
||||||
{/* DATA WARGA */}
|
{/* DATA WARGA */}
|
||||||
<div>
|
<div>
|
||||||
|
<Row label="NIK" value={getValue("nik")} />
|
||||||
<Row label="Nama" value={getValue("nama")} />
|
<Row label="Nama" value={getValue("nama")} />
|
||||||
<Row
|
<Row
|
||||||
label="Tempat Tgl Lahir"
|
label="Tempat Tgl Lahir"
|
||||||
value={getValue("tempat tanggal lahir")}
|
value={`${getValue("tempat_lahir")}, ${getValue("tanggal_lahir")}`}
|
||||||
/>
|
/>
|
||||||
<Row label="Alamat" value={getValue("alamat")} />
|
<Row label="Alamat" value={getValue("alamat")} />
|
||||||
<Row label="NIK" value={getValue("nik")} />
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<br />
|
<br />
|
||||||
@@ -80,7 +80,7 @@ export default function SKTidakMampu({ data }: { data: any }) {
|
|||||||
<p style={{ textAlign: "justify" }}>
|
<p style={{ textAlign: "justify" }}>
|
||||||
Orang tersebut benar-benar penduduk desa {data.setting.desaNama} dan
|
Orang tersebut benar-benar penduduk desa {data.setting.desaNama} dan
|
||||||
termasuk keluarga tidak mampu. Surat keterangan ini dipergunakan untuk
|
termasuk keluarga tidak mampu. Surat keterangan ini dipergunakan untuk
|
||||||
<b>{getValue("alasan permohonan")}.</b>
|
<b>{getValue("alasan")}.</b>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p style={{ textAlign: "justify" }}>
|
<p style={{ textAlign: "justify" }}>
|
||||||
|
|||||||
@@ -105,12 +105,12 @@ export default function SKUsaha({ data }: { data: any }) {
|
|||||||
<tr>
|
<tr>
|
||||||
<td>Jenis Kelamin</td>
|
<td>Jenis Kelamin</td>
|
||||||
<td>:</td>
|
<td>:</td>
|
||||||
<td>{getValue("jenis kelamin")}</td>
|
<td>{getValue("jenis_kelamin")}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Tempat / Tanggal Lahir</td>
|
<td>Tempat / Tanggal Lahir</td>
|
||||||
<td>:</td>
|
<td>:</td>
|
||||||
<td>{getValue("tempat tanggal lahir")}</td>
|
<td>{`${getValue("tempat_lahir")}, ${getValue("tanggal_lahir")}`}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Warga Negara</td>
|
<td>Warga Negara</td>
|
||||||
@@ -125,7 +125,7 @@ export default function SKUsaha({ data }: { data: any }) {
|
|||||||
<tr>
|
<tr>
|
||||||
<td>Status</td>
|
<td>Status</td>
|
||||||
<td>:</td>
|
<td>:</td>
|
||||||
<td>{getValue("status perkawinan")}</td>
|
<td>{getValue("status_perkawinan")}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Pekerjaan</td>
|
<td>Pekerjaan</td>
|
||||||
@@ -173,12 +173,12 @@ export default function SKUsaha({ data }: { data: any }) {
|
|||||||
<tr>
|
<tr>
|
||||||
<td style={{ width: "160px" }}>Jenis Usaha</td>
|
<td style={{ width: "160px" }}>Jenis Usaha</td>
|
||||||
<td style={{ width: "10px" }}>:</td>
|
<td style={{ width: "10px" }}>:</td>
|
||||||
<td>{getValue("jenis usaha")}</td>
|
<td>{getValue("jenis_usaha")}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Alamat Usaha</td>
|
<td>Alamat Usaha</td>
|
||||||
<td>:</td>
|
<td>:</td>
|
||||||
<td>{getValue("alamat usaha")}</td>
|
<td>{getValue("alamat_usaha")}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@@ -88,26 +88,28 @@ export default function SKYatim({ data }: { data: any }) {
|
|||||||
|
|
||||||
<table style={{ width: "100%", marginTop: "5px" }}>
|
<table style={{ width: "100%", marginTop: "5px" }}>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>NIK</td>
|
||||||
|
<td>: {getValue("nik")}</td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td style={{ width: "180px" }}>Nama</td>
|
<td style={{ width: "180px" }}>Nama</td>
|
||||||
<td>: {getValue("nama")}</td>
|
<td>: {getValue("nama")}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Tempat/Tanggal Lahir</td>
|
<td>Tempat/Tanggal Lahir</td>
|
||||||
<td>: {getValue("tempat tanggal lahir")}</td>
|
<td>
|
||||||
|
: {`${getValue("tempat_lahir")}, ${getValue("tanggal_lahir")}`}
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Jenis Kelamin</td>
|
<td>Jenis Kelamin</td>
|
||||||
<td>: {getValue("jenis kelamin")}</td>
|
<td>: {getValue("jenis_kelamin")}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Alamat</td>
|
<td>Alamat</td>
|
||||||
<td>: {getValue("alamat")}</td>
|
<td>: {getValue("alamat")}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
|
||||||
<td>NIK</td>
|
|
||||||
<td>: {getValue("nik")}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
<tr>
|
||||||
<td>Pekerjaan</td>
|
<td>Pekerjaan</td>
|
||||||
<td>: {getValue("pekerjaan")}</td>
|
<td>: {getValue("pekerjaan")}</td>
|
||||||
@@ -133,11 +135,11 @@ export default function SKYatim({ data }: { data: any }) {
|
|||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td style={{ width: "180px" }}>Nama Ayah</td>
|
<td style={{ width: "180px" }}>Nama Ayah</td>
|
||||||
<td>: {getValue("nama ayah")}</td>
|
<td>: {getValue("nama_ayah")}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Status</td>
|
<td>Status</td>
|
||||||
<td>: {getValue("status ayah")}</td>
|
<td>: {getValue("status_ayah")}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
@@ -151,11 +153,11 @@ export default function SKYatim({ data }: { data: any }) {
|
|||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td style={{ width: "180px" }}>Nama Ibu</td>
|
<td style={{ width: "180px" }}>Nama Ibu</td>
|
||||||
<td>: {getValue("nama ibu")}</td>
|
<td>: {getValue("nama_ibu")}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Status</td>
|
<td>Status</td>
|
||||||
<td>: {getValue("status ibu")}</td>
|
<td>: {getValue("status_ibu")}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import LayananRoute from "./server/routes/layanan_route";
|
|||||||
import { MCPRoute } from "./server/routes/mcp_route";
|
import { MCPRoute } from "./server/routes/mcp_route";
|
||||||
import PelayananRoute from "./server/routes/pelayanan_surat_route";
|
import PelayananRoute from "./server/routes/pelayanan_surat_route";
|
||||||
import PengaduanRoute from "./server/routes/pengaduan_route";
|
import PengaduanRoute from "./server/routes/pengaduan_route";
|
||||||
|
import SendWaRoute from "./server/routes/send_wa_route";
|
||||||
import SuratRoute from "./server/routes/surat_route";
|
import SuratRoute from "./server/routes/surat_route";
|
||||||
import TestPengaduanRoute from "./server/routes/test_pengaduan";
|
import TestPengaduanRoute from "./server/routes/test_pengaduan";
|
||||||
import UserRoute from "./server/routes/user_route";
|
import UserRoute from "./server/routes/user_route";
|
||||||
@@ -45,7 +46,8 @@ const Api = new Elysia({
|
|||||||
.use(CredentialRoute)
|
.use(CredentialRoute)
|
||||||
.use(UserRoute)
|
.use(UserRoute)
|
||||||
.use(LayananRoute)
|
.use(LayananRoute)
|
||||||
.use(AduanRoute);
|
.use(AduanRoute)
|
||||||
|
.use(SendWaRoute);
|
||||||
|
|
||||||
const app = new Elysia()
|
const app = new Elysia()
|
||||||
.use(Api)
|
.use(Api)
|
||||||
|
|||||||
@@ -1,109 +1,384 @@
|
|||||||
|
import { enumAgama, enumJenisKelamin, enumStatusHidup, enumStatusPerkawinan, enumStatusTempatUsaha } from "./valueEnum";
|
||||||
|
|
||||||
export const categoryPelayananSurat = [
|
export const categoryPelayananSurat = [
|
||||||
{
|
{
|
||||||
id: "skbedabiodata",
|
id: "skbedabiodata",
|
||||||
name: "Surat Keterangan Beda Biodata Diri",
|
name: "Surat Keterangan Beda Biodata Diri",
|
||||||
syaratDokumen: [
|
syaratDokumen: [
|
||||||
{ name: "pengantar kelian", desc: "Surat Pengantar Kelian Banjar Dinas di Wilayah Masing-masing" },
|
{
|
||||||
{ name: "ktp/kk", desc: "Fotokopi KTP atau Kartu Keluarga" },
|
key: "pengantar_kelian",
|
||||||
{ name: "dokumen yang beda", desc: "Fotokopi dokumen bersangkutan yang terdapat perbedaan biodata diri, misalnya: Sertifikat Tanah, Ijazah, Polis Asuransi, dan lainnya." }
|
name: "Pengantar Kelian",
|
||||||
|
desc: "Surat Pengantar Kelian Banjar Dinas di Wilayah Masing-masing",
|
||||||
|
required: true, satuan: null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "ktp_kk",
|
||||||
|
name: "KTP / KK",
|
||||||
|
desc: "Fotokopi KTP atau Kartu Keluarga",
|
||||||
|
required: true, satuan: null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "dokumen_beda",
|
||||||
|
name: "Dokumen Pendukung",
|
||||||
|
desc: "Fotokopi dokumen yang terdapat perbedaan biodata (ijazah, sertifikat, dll)",
|
||||||
|
required: true, satuan: null
|
||||||
|
}
|
||||||
],
|
],
|
||||||
dataText: ["nik", "nama", "tempat tanggal lahir", "jenis kelamin", "alamat", "pekerjaan", "dokumen", "tertulis pada dokumen a", "tertulis pada dokumen b"]
|
dataText: [],
|
||||||
|
dataPelengkap: [
|
||||||
|
{ key: "nik", name: "NIK", desc: "Nomor Induk Kependudukan", type: "number", required: true, satuan: null },
|
||||||
|
{ key: "nama", name: "Nama Lengkap", desc: "Nama sesuai KTP", type: "text", required: true, satuan: null },
|
||||||
|
{ key: "tempat_lahir", name: "Tempat Lahir", desc: "Tempat lahir pemohon", type: "text", required: true, satuan: null },
|
||||||
|
{ key: "tanggal_lahir", name: "Tanggal Lahir", desc: "Tanggal lahir pemohon", type: "date", required: true, satuan: null },
|
||||||
|
{
|
||||||
|
key: "jenis_kelamin",
|
||||||
|
name: "Jenis Kelamin",
|
||||||
|
desc: "Jenis kelamin pemohon",
|
||||||
|
type: "enum",
|
||||||
|
options: enumJenisKelamin,
|
||||||
|
required: true, satuan: null
|
||||||
|
},
|
||||||
|
{ key: "alamat", name: "Alamat", desc: "Alamat lengkap tempat tinggal", type: "text", required: true, satuan: null },
|
||||||
|
{ key: "pekerjaan", name: "Pekerjaan", desc: "Pekerjaan pemohon", type: "text", required: true, satuan: null },
|
||||||
|
{
|
||||||
|
key: "dokumen",
|
||||||
|
name: "Nama Dokumen",
|
||||||
|
desc: "Jenis dokumen yang mengalami perbedaan biodata",
|
||||||
|
type: "text",
|
||||||
|
required: true, satuan: null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "data_dokumen",
|
||||||
|
name: "Data Dokumen",
|
||||||
|
desc: "Data dokumen yg berbeda",
|
||||||
|
type: "text",
|
||||||
|
required: true, satuan: null
|
||||||
|
},
|
||||||
|
{ key: "dokumen_a", name: "Data pada Dokumen A", desc: "Data biodata pada dokumen pertama", type: "text", required: true, satuan: null },
|
||||||
|
{ key: "dokumen_b", name: "Data pada Dokumen B", desc: "Data biodata pada dokumen kedua", type: "text", required: true, satuan: null }
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "skbelumkawin",
|
id: "skbelumkawin",
|
||||||
name: "Surat Keterangan Belum Kawin",
|
name: "Surat Keterangan Belum Kawin",
|
||||||
syaratDokumen: [
|
syaratDokumen: [
|
||||||
{ name: "pengantar kelian", desc: "Surat Pengantar Kelian Banjar Dinas" },
|
{ key: "pengantar_kelian", name: "Pengantar Kelian", desc: "Surat Pengantar Kelian Banjar Dinas", required: true, satuan: null },
|
||||||
{ name: "ktp/kk", desc: "Fotokopi KTP atau Kartu Keluarga" },
|
{ key: "ktp_kk", name: "KTP / KK", desc: "Fotokopi KTP atau Kartu Keluarga", required: true, satuan: null },
|
||||||
{ name: "akta cerai", desc: "Fotokopi Akta Cerai bagi yang berstatus janda/duda" }
|
{ key: "akta_cerai", name: "Akta Cerai", desc: "Fotokopi akta cerai (jika berstatus janda/duda)", required: false, satuan: null }
|
||||||
],
|
],
|
||||||
dataText: ["nik", "nama", "tempat tanggal lahir", "jenis kelamin", "alamat", "agama", "pekerjaan"]
|
dataText: [],
|
||||||
|
dataPelengkap: [
|
||||||
|
{ key: "nik", name: "NIK", desc: "Nomor Induk Kependudukan", type: "number", required: true, satuan: null },
|
||||||
|
{ key: "nama", name: "Nama Lengkap", desc: "Nama sesuai KTP", type: "text", required: true, satuan: null },
|
||||||
|
{ key: "tempat_lahir", name: "Tempat Lahir", desc: "Tempat lahir pemohon", type: "text", required: true, satuan: null },
|
||||||
|
{ key: "tanggal_lahir", name: "Tanggal Lahir", desc: "Tanggal lahir pemohon", type: "date", required: true, satuan: null },
|
||||||
|
{
|
||||||
|
key: "jenis_kelamin",
|
||||||
|
name: "Jenis Kelamin",
|
||||||
|
desc: "Jenis kelamin pemohon",
|
||||||
|
type: "enum",
|
||||||
|
options: enumJenisKelamin,
|
||||||
|
required: true, satuan: null
|
||||||
|
},
|
||||||
|
{ key: "alamat", name: "Alamat", desc: "Alamat tempat tinggal", type: "text", required: true, satuan: null },
|
||||||
|
{
|
||||||
|
key: "agama",
|
||||||
|
name: "Agama",
|
||||||
|
desc: "Agama pemohon",
|
||||||
|
type: "enum",
|
||||||
|
options: enumAgama,
|
||||||
|
required: true, satuan: null
|
||||||
|
},
|
||||||
|
{ key: "pekerjaan", name: "Pekerjaan", desc: "Pekerjaan pemohon", type: "text", required: true, satuan: null }
|
||||||
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
id: "skdomisiliorganisasi",
|
id: "skdomisiliorganisasi",
|
||||||
name: "Surat Keterangan Domisili Organisasi",
|
name: "Surat Keterangan Domisili Organisasi",
|
||||||
syaratDokumen: [
|
syaratDokumen: [
|
||||||
{ name: "pengantar kelian", desc: "Surat Pengantar Kelian Banjar Dinas" },
|
{ key: "pengantar_kelian", name: "Pengantar Kelian", desc: "Surat Pengantar Kelian Banjar Dinas", required: true, satuan: null },
|
||||||
{ name: "skt organisasi", desc: "Fotokopi Surat Keterangan Terdaftar (SKT) Organisasi atau Pengukuhan Kelompok" },
|
{ key: "skt_organisasi", name: "SKT Organisasi", desc: "Fotokopi SKT Organisasi", required: true, satuan: null },
|
||||||
{ name: "susunan pengurus", desc: "Jika Pengajuan baru pembuatan SKT maka melengkapi Susunan Pengurus lengkap denganKop Organisasi" }
|
{ key: "susunan_pengurus", name: "Susunan Pengurus", desc: "Susunan pengurus organisasi", required: true, satuan: null }
|
||||||
],
|
],
|
||||||
dataText: ["nama organisasi", "jenis organisasi", "alamat organisasi/sekretariat", "no telepon", "nama pimpinan", "keperluan"]
|
dataText: [],
|
||||||
|
dataPelengkap: [
|
||||||
|
{ key: "nama_organisasi", name: "Nama Organisasi", desc: "Nama resmi organisasi", type: "text", required: true, satuan: null },
|
||||||
|
{ key: "jenis_organisasi", name: "Jenis Organisasi", desc: "Jenis organisasi", type: "text", required: true, satuan: null },
|
||||||
|
{ key: "alamat_organisasi", name: "Alamat Organisasi", desc: "Alamat sekretariat", type: "text", required: true, satuan: null },
|
||||||
|
{ key: "no_telepon", name: "Nomor Telepon", desc: "Nomor telepon organisasi", type: "text", required: true, satuan: null },
|
||||||
|
{ key: "nama_pimpinan", name: "Nama Pimpinan", desc: "Nama pimpinan", type: "text", required: true, satuan: null },
|
||||||
|
{ key: "keperluan", name: "Keperluan", desc: "Keperluan pembuatan surat", type: "text", required: true, satuan: null }
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "skkelahiran",
|
id: "skkelahiran",
|
||||||
name: "Surat Keterangan Kelahiran",
|
name: "Surat Keterangan Kelahiran",
|
||||||
syaratDokumen: [
|
syaratDokumen: [
|
||||||
{ name: "pengantar kelian", desc: "Surat Pengantar Kelian Banjar Dinas" },
|
{ key: "pengantar_kelian", name: "Pengantar Kelian", desc: "Surat Pengantar Kelian Banjar Dinas", required: true, satuan: null },
|
||||||
{ name: "surat lahir", desc: "Fotokopi Surat Keterangan Lahir dari Bidan/Dokter (jika ada)" }
|
{ key: "surat_lahir", name: "Surat Keterangan Lahir", desc: "Surat keterangan lahir dari bidan/dokter (jika ada)", required: false, satuan: null }
|
||||||
],
|
],
|
||||||
dataText: ["nama ayah", "nama ibu", "nama anak", "tanggal lahir anak", "pukul lahir anak", "tempat lahir anak", "jenis kelamin anak", "anak ke", "nik ibu", "tempat tanggal lahir ibu", "pekerjaan ibu", "alamat ibu", "nik ayah", "tempat tanggal lahir ayah", "pekerjaan ayah", "alamat ayah", "nama pelapor", "hubungan pelapor", "alamat pelapor"]
|
dataText: [],
|
||||||
|
dataPelengkap: [
|
||||||
|
{ key: "nama_anak", name: "Nama Anak", desc: "Nama bayi/anak", type: "text", required: true, satuan: null },
|
||||||
|
{ key: "tanggal_lahir_anak", name: "Tanggal Lahir Anak", desc: "Tanggal lahir anak", type: "date", required: true, satuan: null },
|
||||||
|
{ key: "pukul_lahir", name: "Pukul Lahir", desc: "Waktu kelahiran", type: "text", required: true, satuan: null },
|
||||||
|
{ key: "tempat_lahir", name: "Tempat Lahir", desc: "Tempat kelahiran", type: "text", required: true, satuan: null },
|
||||||
|
{
|
||||||
|
key: "jenis_kelamin",
|
||||||
|
name: "Jenis Kelamin Anak",
|
||||||
|
desc: "Jenis kelamin anak",
|
||||||
|
type: "enum",
|
||||||
|
options: enumJenisKelamin,
|
||||||
|
required: true, satuan: null
|
||||||
|
},
|
||||||
|
{ key: "anak_ke", name: "Anak Ke-", desc: "Urutan kelahiran", type: "number", required: true, satuan: null },
|
||||||
|
{ key: "nik_ibu", name: "NIK Ibu", desc: "NIK ibu kandung", type: "number", required: true, satuan: null },
|
||||||
|
{ key: "nama_ibu", name: "Nama Ibu", desc: "Nama ibu kandung", type: "text", required: true, satuan: null },
|
||||||
|
{ key: "tempat_lahir_ibu", name: "Tempat Lahir Ibu", desc: "Tempat lahir ibu", type: "text", required: true, satuan: null },
|
||||||
|
{ key: "tanggal_lahir_ibu", name: "Tanggal Lahir Ibu", desc: "Tanggal lahir ibu", type: "date", required: true, satuan: null },
|
||||||
|
{ key: "pekerjaan_ibu", name: "Pekerjaan Ibu", desc: "Pekerjaan ibu", type: "text", required: true, satuan: null },
|
||||||
|
{ key: "alamat_ibu", name: "Alamat Ibu", desc: "Alamat ibu", type: "text", required: true, satuan: null },
|
||||||
|
{ key: "nama_ayah", name: "Nama Ayah", desc: "Nama ayah kandung", type: "text", required: true, satuan: null },
|
||||||
|
{ key: "nik_ayah", name: "NIK Ayah", desc: "NIK ayah kandung", type: "number", required: true, satuan: null },
|
||||||
|
{ key: "tempat_lahir_ayah", name: "Tempat Lahir Ayah", desc: "Tempat lahir ayah", type: "text", required: true, satuan: null },
|
||||||
|
{ key: "tanggal_lahir_ayah", name: "Tanggal Lahir Ayah", desc: "Tanggal lahir ayah", type: "date", required: true, satuan: null },
|
||||||
|
{ key: "pekerjaan_ayah", name: "Pekerjaan Ayah", desc: "Pekerjaan ayah", type: "text", required: true, satuan: null },
|
||||||
|
{ key: "alamat_ayah", name: "Alamat Ayah", desc: "Alamat ayah", type: "text", required: true, satuan: null },
|
||||||
|
{ key: "nama_pelapor", name: "Nama Pelapor", desc: "Nama pelapor", type: "text", required: true, satuan: null },
|
||||||
|
{ key: "hubungan_pelapor", name: "Hubungan Pelapor", desc: "Hubungan dengan anak", type: "text", required: true, satuan: null },
|
||||||
|
{ key: "alamat_pelapor", name: "Alamat Pelapor", desc: "Alamat pelapor", type: "text", required: true, satuan: null }
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "skkelakuanbaik",
|
id: "skkelakuanbaik",
|
||||||
name: "Surat Keterangan Kelakuan Baik (Pengantar SKCK)",
|
name: "Surat Keterangan Kelakuan Baik (Pengantar SKCK)",
|
||||||
syaratDokumen: [
|
syaratDokumen: [
|
||||||
{ name: "pengantar kelian", desc: "Surat Pengantar Kelian Banjar Dinas" },
|
{ key: "pengantar_kelian", name: "Pengantar Kelian", desc: "Surat Pengantar Kelian Banjar Dinas", required: true, satuan: null },
|
||||||
{ name: "ktp/kk", desc: "Fotokopi KTP atau Kartu Keluarga" }
|
{ key: "ktp_kk", name: "KTP / KK", desc: "Fotokopi KTP atau KK", required: true, satuan: null }
|
||||||
],
|
],
|
||||||
dataText: ["nik", "nama", "tempat tanggal lahir", "jenis kelamin", "agama", "alamat", "pekerjaan", "polsek"]
|
dataText: [],
|
||||||
|
dataPelengkap: [
|
||||||
|
{ key: "nik", name: "NIK", desc: "Nomor Induk Kependudukan", type: "number", required: true, satuan: null },
|
||||||
|
{ key: "nama", name: "Nama Lengkap", desc: "Nama sesuai KTP", type: "text", required: true, satuan: null },
|
||||||
|
{ key: "tempat_lahir", name: "Tempat Lahir", desc: "Tempat lahir", type: "text", required: true, satuan: null },
|
||||||
|
{ key: "tanggal_lahir", name: "Tanggal Lahir", desc: "Tanggal lahir", type: "date", required: true, satuan: null },
|
||||||
|
{
|
||||||
|
key: "jenis_kelamin",
|
||||||
|
name: "Jenis Kelamin",
|
||||||
|
desc: "Jenis kelamin",
|
||||||
|
type: "enum",
|
||||||
|
options: enumJenisKelamin,
|
||||||
|
required: true, satuan: null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "agama",
|
||||||
|
name: "Agama",
|
||||||
|
desc: "Agama",
|
||||||
|
type: "enum",
|
||||||
|
options: enumAgama,
|
||||||
|
required: true, satuan: null
|
||||||
|
},
|
||||||
|
{ key: "alamat", name: "Alamat", desc: "Alamat tempat tinggal", type: "text", required: true, satuan: null },
|
||||||
|
{ key: "pekerjaan", name: "Pekerjaan", desc: "Pekerjaan", type: "text", required: true, satuan: null },
|
||||||
|
{ key: "polsek", name: "Polsek Tujuan", desc: "Polsek tujuan", type: "text", required: true, satuan: null }
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "skkematian",
|
id: "skkematian",
|
||||||
name: "Surat Keterangan Kematian",
|
name: "Surat Keterangan Kematian",
|
||||||
syaratDokumen: [
|
syaratDokumen: [
|
||||||
{ name: "pengantar kelian", desc: "Surat Pengantar Kelian Banjar Dinas" },
|
{ key: "pengantar_kelian", name: "Pengantar Kelian", desc: "Surat Pengantar Kelian Banjar Dinas", required: true, satuan: null },
|
||||||
{ name: "ktp/kk", desc: "Fotokopi KTP atau Kartu Keluarga" },
|
{ key: "ktp_kk", name: "KTP / KK", desc: "Fotokopi KTP atau KK", required: true, satuan: null },
|
||||||
{ name: "surat kematian", desc: "Surat Keterangan Kematian dari Rumah Sakit/Dokter (jika ada)" }
|
{ key: "surat_kematian", name: "Surat Keterangan Kematian", desc: "Surat keterangan kematian dari rumah sakit/dokter (jika ada)", required: false, satuan: null }
|
||||||
],
|
],
|
||||||
dataText: ["nik pelapor", "nama pelapor", "pekerjaan pelapor", "alamat pelapor", "hubungan pelapor dengan almarhum", "nama almarhum", "nik almarhum", "tempat tanggal lahir almarhum", "alamat almarhum", "agama almarhum", "tanggal kematian", "waktu kematian", "tempat kematian", "penyebab kematian"]
|
dataText: [],
|
||||||
|
dataPelengkap: [
|
||||||
|
{ key: "nik_pelapor", name: "NIK Pelapor", desc: "NIK pelapor", type: "number", required: true, satuan: null },
|
||||||
|
{ key: "nama_pelapor", name: "Nama Pelapor", desc: "Nama pelapor", type: "text", required: true, satuan: null },
|
||||||
|
{ key: "pekerjaan_pelapor", name: "Pekerjaan Pelapor", desc: "Pekerjaan pelapor", type: "text", required: true, satuan: null },
|
||||||
|
{ key: "alamat_pelapor", name: "Alamat Pelapor", desc: "Alamat pelapor", type: "text", required: true, satuan: null },
|
||||||
|
{ key: "hubungan_pelapor", name: "Hubungan Pelapor", desc: "Hubungan dengan almarhum", type: "text", required: true, satuan: null },
|
||||||
|
{ key: "nama_almarhum", name: "Nama Almarhum", desc: "Nama almarhum", type: "text", required: true, satuan: null },
|
||||||
|
{ key: "nik_almarhum", name: "NIK Almarhum", desc: "NIK almarhum", type: "number", required: true, satuan: null },
|
||||||
|
{ key: "tempat_lahir_almarhum", name: "Tempat Lahir", desc: "Tempat lahir almarhum", type: "text", required: true, satuan: null },
|
||||||
|
{ key: "tanggal_lahir_almarhum", name: "Tanggal Lahir", desc: "Tanggal lahir almarhum", type: "date", required: true, satuan: null },
|
||||||
|
{ key: "alamat_almarhum", name: "Alamat", desc: "Alamat terakhir", type: "text", required: true, satuan: null },
|
||||||
|
{
|
||||||
|
key: "agama_almarhum",
|
||||||
|
name: "Agama Almarhum",
|
||||||
|
desc: "Agama almarhum",
|
||||||
|
type: "enum",
|
||||||
|
options: enumAgama,
|
||||||
|
required: true, satuan: null
|
||||||
|
},
|
||||||
|
{ key: "tanggal_kematian", name: "Tanggal Kematian", desc: "Tanggal meninggal dunia", type: "date", required: true, satuan: null },
|
||||||
|
{ key: "waktu_kematian", name: "Waktu Kematian", desc: "Waktu meninggal dunia", type: "text", required: true, satuan: null },
|
||||||
|
{ key: "tempat_kematian", name: "Tempat Kematian", desc: "Tempat meninggal dunia", type: "text", required: true, satuan: null },
|
||||||
|
{ key: "penyebab_kematian", name: "Penyebab Kematian", desc: "Penyebab meninggal dunia", type: "text", required: true, satuan: null }
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "skpenghasilan",
|
id: "skpenghasilan",
|
||||||
name: "Surat Keterangan Penghasilan",
|
name: "Surat Keterangan Penghasilan",
|
||||||
syaratDokumen: [
|
syaratDokumen: [
|
||||||
{ name: "pengantar kelian", desc: "Surat Pengantar Kelian Banjar Dinas" },
|
{ key: "pengantar_kelian", name: "Pengantar Kelian", desc: "Surat Pengantar Kelian Banjar Dinas", required: true, satuan: null },
|
||||||
{ name: "ktp ortu/kk", desc: "Fotokopi KTP orang tua atau Kartu Keluarga" },
|
{ key: "ktp_ortu_kk", name: "KTP Orang Tua / KK", desc: "Fotokopi KTP orang tua/KK", required: true, satuan: null },
|
||||||
{ name: "surat pernyataan", desc: "Surat Pernyataan Penghasilan bermaterai" }
|
{ key: "surat_pernyataan", name: "Surat Pernyataan Penghasilan", desc: "Surat pernyataan penghasilan bermaterai", required: true, satuan: null }
|
||||||
],
|
],
|
||||||
dataText: ["nama", "tempat tanggal lahir", "jenis kelamin", "alamat", "pekerjaan", "penghasilan", "alasan permohonan"]
|
dataText: [],
|
||||||
|
dataPelengkap: [
|
||||||
|
{ key: "nama", name: "Nama Lengkap", desc: "Nama pemohon", type: "text", required: true, satuan: null },
|
||||||
|
{ key: "tempat_lahir", name: "Tempat Lahir", desc: "Tempat lahir", type: "text", required: true, satuan: null },
|
||||||
|
{ key: "tanggal_lahir", name: "Tanggal Lahir", desc: "Tanggal lahir", type: "date", required: true, satuan: null },
|
||||||
|
{
|
||||||
|
key: "jenis_kelamin",
|
||||||
|
name: "Jenis Kelamin",
|
||||||
|
desc: "Jenis kelamin",
|
||||||
|
type: "enum",
|
||||||
|
options: enumJenisKelamin,
|
||||||
|
required: true, satuan: null
|
||||||
|
},
|
||||||
|
{ key: "alamat", name: "Alamat", desc: "Alamat tempat tinggal", type: "text", required: true, satuan: null },
|
||||||
|
{ key: "pekerjaan", name: "Pekerjaan", desc: "Pekerjaan pemohon", type: "text", required: true, satuan: null },
|
||||||
|
{ key: "penghasilan", name: "Penghasilan", desc: "Jumlah penghasilan per bulan", type: "number", required: true, satuan: "/Bulan" },
|
||||||
|
{ key: "alasan", name: "Alasan Permohonan", desc: "Alasan pengajuan surat penghasilan", type: "text", required: true, satuan: null }
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "sktempatusaha",
|
id: "sktempatusaha",
|
||||||
name: "Surat Keterangan Tempat Usaha",
|
name: "Surat Keterangan Tempat Usaha",
|
||||||
syaratDokumen: [
|
syaratDokumen: [
|
||||||
{ name: "pengantar kelian", desc: "Surat Pengantar Kelian Banjar Dinas" },
|
{ key: "pengantar_kelian", name: "Pengantar Kelian", desc: "Surat Pengantar Kelian Banjar Dinas", required: true, satuan: null },
|
||||||
{ name: "ktp/kk", desc: "Fotokopi KTP atau Kartu Keluarga" },
|
{ key: "ktp_kk", name: "KTP / KK", desc: "Fotokopi KTP/KK", required: true, satuan: null },
|
||||||
{ name: "foto lokasi", desc: "Foto lokasi usaha dicetak dalam selembar kertas, diparaf dan distempel oleh Kelian" },
|
{ key: "foto_lokasi", name: "Foto Lokasi Usaha", desc: "Foto lokasi usaha", required: true, satuan: null },
|
||||||
{ name: "sppt/sertifikat/sewa", desc: "Fotokopi SPPT, Sertifikat Hak Milik, Surat Perjanjian Sewa, atau Kwitansi Pembayaran Sewa 3 bulan terakhir" }
|
{ key: "dokumen_lahan", name: "Dokumen Lahan", desc: "SPPT/Sertifikat/surat sewa tempat usaha", required: true, satuan: null }
|
||||||
],
|
],
|
||||||
dataText: ["nik", "nama pemilik", "tempat tanggal lahir", "alamat pemilik", "nama usaha", "bidang usaha", "alamat usaha", "status tempat usaha", "luas tempat usaha", "jumlah karyawan", "tujuan pembuatan surat"]
|
dataText: [],
|
||||||
|
dataPelengkap: [
|
||||||
|
{ key: "nik", name: "NIK", desc: "NIK pemilik", type: "number", required: true, satuan: null },
|
||||||
|
{ key: "nama_pemilik", name: "Nama Pemilik", desc: "Nama pemilik usaha", type: "text", required: true, satuan: null },
|
||||||
|
{ key: "tempat_lahir", name: "Tempat Lahir", desc: "Tempat lahir", type: "text", required: true, satuan: null },
|
||||||
|
{ key: "tanggal_lahir", name: "Tanggal Lahir", desc: "Tanggal lahir", type: "date", required: true, satuan: null },
|
||||||
|
{ key: "alamat_pemilik", name: "Alamat Pemilik", desc: "Alamat pemilik", type: "text", required: true, satuan: null },
|
||||||
|
{ key: "nama_usaha", name: "Nama Usaha", desc: "Nama usaha", type: "text", required: true, satuan: null },
|
||||||
|
{ key: "bidang_usaha", name: "Bidang Usaha", desc: "Bidang usaha", type: "text", required: true, satuan: null },
|
||||||
|
{ key: "alamat_usaha", name: "Alamat Usaha", desc: "Alamat usaha", type: "text", required: true, satuan: null },
|
||||||
|
{
|
||||||
|
key: "status_tempat",
|
||||||
|
name: "Status Tempat Usaha",
|
||||||
|
desc: "Status kepemilikan tempat usaha",
|
||||||
|
type: "enum",
|
||||||
|
options: enumStatusTempatUsaha,
|
||||||
|
required: true, satuan: null
|
||||||
|
},
|
||||||
|
{ key: "luas_usaha", name: "Luas Tempat Usaha", desc: "Luas tempat usaha (m²)", type: "number", required: true, satuan: "m²" },
|
||||||
|
{ key: "jumlah_karyawan", name: "Jumlah Karyawan", desc: "Jumlah karyawan", type: "number", required: true, satuan: null },
|
||||||
|
{ key: "tujuan", name: "Tujuan Pembuatan Surat", desc: "Tujuan pembuatan surat keterangan", type: "text", required: true, satuan: null }
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "sktidakmampu",
|
id: "sktidakmampu",
|
||||||
name: "Surat Keterangan Tidak Mampu",
|
name: "Surat Keterangan Tidak Mampu",
|
||||||
syaratDokumen: [
|
syaratDokumen: [
|
||||||
{ name: "pengantar kelian", desc: "Surat Pengantar Kelian Banjar Dinas" },
|
{ key: "pengantar_kelian", name: "Pengantar Kelian", desc: "Surat Pengantar Kelian Banjar Dinas", required: true, satuan: null },
|
||||||
{ name: "ktp/kia/kk", desc: "Fotokopi KTP, KIA, atau Kartu Keluarga" }
|
{ key: "ktp_kia_kk", name: "KTP / KIA / KK", desc: "Fotokopi KTP/KIA/KK", required: true, satuan: null }
|
||||||
],
|
],
|
||||||
dataText: ["nik", "nama", "tempat tanggal lahir", "alamat", "alasan permohonan"]
|
dataText: [],
|
||||||
|
dataPelengkap: [
|
||||||
|
{ key: "nik", name: "NIK", desc: "NIK pemohon", type: "number", required: true, satuan: null },
|
||||||
|
{ key: "nama Lengkap", name: "Nama", desc: "Nama pemohon", type: "text", required: true, satuan: null },
|
||||||
|
{ key: "tempat_lahir", name: "Tempat Lahir", desc: "Tempat lahir", type: "text", required: true, satuan: null },
|
||||||
|
{ key: "tanggal_lahir", name: "Tanggal Lahir", desc: "Tanggal lahir", type: "date", required: true, satuan: null },
|
||||||
|
{ key: "alamat", name: "Alamat", desc: "Alamat pemohon", type: "text", required: true, satuan: null },
|
||||||
|
{ key: "alasan", name: "Alasan Permohonan", desc: "Alasan permohonan", type: "text", required: true, satuan: null }
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "skusaha",
|
id: "skusaha",
|
||||||
name: "Surat Keterangan Usaha",
|
name: "Surat Keterangan Usaha",
|
||||||
syaratDokumen: [
|
syaratDokumen: [
|
||||||
{ name: "pengantar kelian", desc: "Surat Pengantar Kelian Banjar Dinas" },
|
{ key: "pengantar_kelian", name: "Pengantar Kelian", desc: "Surat Pengantar Kelian Banjar Dinas", required: true, satuan: null },
|
||||||
{ name: "ktp/kk", desc: "Fotokopi KTP atau Kartu Keluarga" },
|
{ key: "ktp_kk", name: "KTP / KK", desc: "Fotokopi KTP/KK", required: true, satuan: null },
|
||||||
{ name: "foto lokasi", desc: "Foto lokasi usaha dicetak dalam selembar kertas, diparaf dan distempel oleh Kelian" }
|
{ key: "foto_lokasi", name: "Foto Lokasi Usaha", desc: "Foto lokasi usaha", required: true, satuan: null }
|
||||||
],
|
],
|
||||||
dataText: ["nama", "jenis kelamin", "tempat tanggal lahir", "negara", "agama", "status perkawinan", "alamat", "pekerjaan", "jenis usaha", "alamat usaha"]
|
dataText: [],
|
||||||
|
dataPelengkap: [
|
||||||
|
{ key: "nama", name: "Nama Lengkap", desc: "Nama pemilik usaha", type: "text", required: true, satuan: null },
|
||||||
|
{
|
||||||
|
key: "jenis_kelamin",
|
||||||
|
name: "Jenis Kelamin",
|
||||||
|
desc: "Jenis kelamin pemilik usaha",
|
||||||
|
type: "enum",
|
||||||
|
options: enumJenisKelamin,
|
||||||
|
required: true, satuan: null
|
||||||
|
},
|
||||||
|
{ key: "tempat_lahir", name: "Tempat Lahir", desc: "Tempat lahir", type: "text", required: true, satuan: null },
|
||||||
|
{ key: "tanggal_lahir", name: "Tanggal Lahir", desc: "Tanggal lahir", type: "date", required: true, satuan: null },
|
||||||
|
{ key: "negara", name: "Kewarganegaraan", desc: "Kewarganegaraan", type: "text", required: true, satuan: null },
|
||||||
|
{
|
||||||
|
key: "agama",
|
||||||
|
name: "Agama",
|
||||||
|
desc: "Agama",
|
||||||
|
type: "enum",
|
||||||
|
options: enumAgama,
|
||||||
|
required: true, satuan: null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "status_perkawinan",
|
||||||
|
name: "Status Perkawinan",
|
||||||
|
desc: "Status perkawinan",
|
||||||
|
type: "enum",
|
||||||
|
options: enumStatusPerkawinan,
|
||||||
|
required: true, satuan: null
|
||||||
|
},
|
||||||
|
{ key: "alamat", name: "Alamat", desc: "Alamat", type: "text", required: true, satuan: null },
|
||||||
|
{ key: "pekerjaan", name: "Pekerjaan", desc: "Pekerjaan", type: "text", required: true, satuan: null },
|
||||||
|
{ key: "jenis_usaha", name: "Jenis Usaha", desc: "Jenis usaha", type: "text", required: true, satuan: null },
|
||||||
|
{ key: "alamat_usaha", name: "Alamat Usaha", desc: "Alamat usaha", type: "text", required: true, satuan: null }
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "skyatimpiatu",
|
id: "skyatimpiatu",
|
||||||
name: "Surat Keterangan Yatim / Piatu / Yatim Piatu",
|
name: "Surat Keterangan Yatim / Piatu / Yatim Piatu",
|
||||||
syaratDokumen: [
|
syaratDokumen: [
|
||||||
{ name: "pengantar kelian", desc: "Surat Pengantar Kelian Banjar Dinas" },
|
{ key: "pengantar_kelian", name: "Pengantar Kelian", desc: "Surat Pengantar Kelian Banjar Dinas", required: true, satuan: null },
|
||||||
{ name: "ktp/kia/kk", desc: "Fotokopi KTP, KIA, atau Kartu Keluarga" }
|
{ key: "ktp_kia_kk", name: "KTP / KIA / KK", desc: "Fotokopi KTP/KIA/KK", required: true, satuan: null }
|
||||||
],
|
],
|
||||||
dataText: ["nik", "nama", "tempat tanggal lahir", "jenis kelamin", "alamat", "pekerjaan", "nama ayah", "status ayah", "nama ibu", "status ibu"]
|
dataText: [],
|
||||||
|
dataPelengkap: [
|
||||||
|
{ key: "nik", name: "NIK", desc: "NIK anak", type: "number", required: true, satuan: null },
|
||||||
|
{ key: "nama", name: "Nama", desc: "Nama anak", type: "text", required: true, satuan: null },
|
||||||
|
{ key: "tempat_lahir", name: "Tempat Lahir", desc: "Tempat lahir", type: "text", required: true, satuan: null },
|
||||||
|
{ key: "tanggal_lahir", name: "Tanggal Lahir", desc: "Tanggal lahir", type: "date", required: true, satuan: null },
|
||||||
|
{
|
||||||
|
key: "jenis_kelamin",
|
||||||
|
name: "Jenis Kelamin",
|
||||||
|
desc: "Jenis kelamin anak",
|
||||||
|
type: "enum",
|
||||||
|
options: enumJenisKelamin,
|
||||||
|
required: true, satuan: null
|
||||||
|
},
|
||||||
|
{ key: "alamat", name: "Alamat", desc: "Alamat", type: "text", required: true, satuan: null },
|
||||||
|
{ key: "pekerjaan", name: "Pekerjaan", desc: "Pekerjaan (jika ada)", type: "text", required: false, satuan: null },
|
||||||
|
{ key: "nama_ayah", name: "Nama Ayah", desc: "Nama ayah", type: "text", required: true, satuan: null },
|
||||||
|
{
|
||||||
|
key: "status_ayah",
|
||||||
|
name: "Status Ayah",
|
||||||
|
desc: "Status ayah",
|
||||||
|
type: "enum",
|
||||||
|
options: enumStatusHidup,
|
||||||
|
required: true, satuan: null
|
||||||
|
},
|
||||||
|
{ key: "nama_ibu", name: "Nama Ibu", desc: "Nama ibu", type: "text", required: true, satuan: null },
|
||||||
|
{
|
||||||
|
key: "status_ibu",
|
||||||
|
name: "Status Ibu",
|
||||||
|
desc: "Status ibu",
|
||||||
|
type: "enum",
|
||||||
|
options: enumStatusHidup,
|
||||||
|
required: true, satuan: null
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -246,16 +246,6 @@
|
|||||||
"label": "View Detail",
|
"label": "View Detail",
|
||||||
"default": true
|
"default": true
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"key": "setting.kategori_pelayanan.tambah",
|
|
||||||
"label": "Tambah",
|
|
||||||
"default": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "setting.kategori_pelayanan.edit",
|
|
||||||
"label": "Edit",
|
|
||||||
"default": true
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"key": "setting.kategori_pelayanan.delete",
|
"key": "setting.kategori_pelayanan.delete",
|
||||||
"label": "Delete",
|
"label": "Delete",
|
||||||
|
|||||||
31
src/lib/valueEnum.ts
Normal file
31
src/lib/valueEnum.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
export const enumJenisKelamin = [
|
||||||
|
{ label: "Laki-laki", value: "Laki-laki" },
|
||||||
|
{ label: "Perempuan", value: "Perempuan" }
|
||||||
|
];
|
||||||
|
|
||||||
|
export const enumAgama = [
|
||||||
|
{ label: "Islam", value: "Islam" },
|
||||||
|
{ label: "Kristen", value: "Kristen" },
|
||||||
|
{ label: "Katolik", value: "Katolik" },
|
||||||
|
{ label: "Hindu", value: "Hindu" },
|
||||||
|
{ label: "Buddha", value: "Buddha" },
|
||||||
|
{ label: "Konghucu", value: "Konghucu" }
|
||||||
|
];
|
||||||
|
|
||||||
|
export const enumStatusHidup = [
|
||||||
|
{ label: "Hidup", value: "Hidup" },
|
||||||
|
{ label: "Meninggal", value: "Meninggal" }
|
||||||
|
];
|
||||||
|
|
||||||
|
export const enumStatusPerkawinan = [
|
||||||
|
{ label: "Belum Kawin", value: "Belum Kawin" },
|
||||||
|
{ label: "Kawin", value: "Kawin" },
|
||||||
|
{ label: "Cerai Hidup", value: "Cerai Hidup" },
|
||||||
|
{ label: "Cerai Mati", value: "Cerai Mati" }
|
||||||
|
];
|
||||||
|
|
||||||
|
export const enumStatusTempatUsaha = [
|
||||||
|
{ label: "Milik Sendiri", value: "Milik Sendiri" },
|
||||||
|
{ label: "Sewa", value: "Sewa" },
|
||||||
|
{ label: "Pinjam", value: "Pinjam" }
|
||||||
|
];
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
import clientRoutes from "@/clientRoutes";
|
import clientRoutes from "@/clientRoutes";
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Container,
|
Center,
|
||||||
Group,
|
Paper,
|
||||||
PasswordInput,
|
PasswordInput,
|
||||||
Stack,
|
Stack,
|
||||||
Text,
|
Text,
|
||||||
TextInput,
|
TextInput
|
||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import apiFetch from "../lib/apiFetch";
|
import apiFetch from "../lib/apiFetch";
|
||||||
@@ -73,25 +73,73 @@ export default function Login() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Center
|
||||||
<Stack>
|
h="100vh"
|
||||||
<Text>Login</Text>
|
style={{
|
||||||
<TextInput
|
background:
|
||||||
placeholder="Email"
|
"radial-gradient(circle at top, #1f2d2b 0%, #0b0f0e 60%)",
|
||||||
value={email}
|
}}
|
||||||
onChange={(e) => setEmail(e.target.value)}
|
>
|
||||||
/>
|
<Paper
|
||||||
<PasswordInput
|
radius="lg"
|
||||||
placeholder="Password"
|
p="xl"
|
||||||
value={password}
|
w={420}
|
||||||
onChange={(e) => setPassword(e.target.value)}
|
style={{
|
||||||
/>
|
background: "rgba(20, 20, 20, 0.75)",
|
||||||
<Group justify="right">
|
backdropFilter: "blur(12px)",
|
||||||
<Button onClick={handleSubmit} disabled={loading}>
|
border: "1px solid rgba(255, 255, 255, 0.08)",
|
||||||
|
boxShadow: "0 20px 60px rgba(0,0,0,0.6)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Stack>
|
||||||
|
<Text
|
||||||
|
size="xl"
|
||||||
|
fw={700}
|
||||||
|
ta="center"
|
||||||
|
c="white"
|
||||||
|
>
|
||||||
|
Welcome Back
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<Text
|
||||||
|
size="sm"
|
||||||
|
ta="center"
|
||||||
|
c="dimmed"
|
||||||
|
>
|
||||||
|
Sign in to continue to your dashboard
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<TextInput
|
||||||
|
label="Email"
|
||||||
|
placeholder="Email"
|
||||||
|
value={email}
|
||||||
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<PasswordInput
|
||||||
|
label="Password"
|
||||||
|
placeholder="Password"
|
||||||
|
value={password}
|
||||||
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
fullWidth
|
||||||
|
mt="md"
|
||||||
|
radius="md"
|
||||||
|
size="md"
|
||||||
|
variant="gradient"
|
||||||
|
gradient={{ from: "teal", to: "cyan", deg: 45 }}
|
||||||
|
style={{
|
||||||
|
transition: "all 0.2s ease",
|
||||||
|
}}
|
||||||
|
onClick={handleSubmit}
|
||||||
|
disabled={loading}
|
||||||
|
>
|
||||||
Login
|
Login
|
||||||
</Button>
|
</Button>
|
||||||
</Group>
|
</Stack>
|
||||||
</Stack>
|
</Paper>
|
||||||
</Container>
|
</Center>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
837
src/pages/darmasaba/update_data_surat.tsx
Normal file
837
src/pages/darmasaba/update_data_surat.tsx
Normal file
@@ -0,0 +1,837 @@
|
|||||||
|
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;
|
||||||
|
required: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<Container size="md" w={"100%"}>
|
||||||
|
<Box>
|
||||||
|
<Stack gap="lg">
|
||||||
|
{found && (
|
||||||
|
<Group justify="space-between" align="center" mt={"lg"}>
|
||||||
|
<Group align="center">
|
||||||
|
<IconBuildingCommunity size={28} />
|
||||||
|
<div>
|
||||||
|
<Text fw={800} size="xl">
|
||||||
|
Update Data Pengajuan Surat Administrasi
|
||||||
|
</Text>
|
||||||
|
<Text size="sm" c="dimmed">
|
||||||
|
Formulir ini digunakan untuk memperbarui data pengajuan
|
||||||
|
surat administrasi yang telah diajukan sebelumnya.
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
</Group>
|
||||||
|
</Group>
|
||||||
|
)}
|
||||||
|
<Stack gap="lg" mb="lg">
|
||||||
|
{!noPengajuan ? (
|
||||||
|
<SearchData />
|
||||||
|
) : found ? (
|
||||||
|
<DataUpdate
|
||||||
|
noPengajuan={noPengajuan}
|
||||||
|
onValidate={(e) => {
|
||||||
|
setFound(e);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<DataNotFound
|
||||||
|
backTo={() => navigate("/darmasaba/update-data-surat")}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
</Box>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function FieldLabel({ label, hint, required = false, }: { label: string; hint?: string; required?: boolean; }) {
|
||||||
|
return (
|
||||||
|
<Group justify="apart" gap="xs" align="center">
|
||||||
|
<Group gap={4} align="center">
|
||||||
|
<Text fw={600}>
|
||||||
|
{label}
|
||||||
|
{required && (
|
||||||
|
<Text span c="red" ml={4}>
|
||||||
|
*
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</Text>
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
{hint && (
|
||||||
|
<Tooltip label={hint} withArrow>
|
||||||
|
<ActionIcon size={24} variant="subtle">
|
||||||
|
<IconInfoCircle size={16} />
|
||||||
|
</ActionIcon>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
</Group>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
function FormSection({
|
||||||
|
title,
|
||||||
|
icon,
|
||||||
|
children,
|
||||||
|
description,
|
||||||
|
info,
|
||||||
|
}: {
|
||||||
|
title?: string;
|
||||||
|
icon?: React.ReactNode;
|
||||||
|
children: React.ReactNode;
|
||||||
|
description?: string;
|
||||||
|
info?: string;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<Card radius="md" shadow="sm" withBorder>
|
||||||
|
<Box mb="xs">
|
||||||
|
<Group justify="apart" align="center">
|
||||||
|
<Group align="center" gap="xs">
|
||||||
|
{icon}
|
||||||
|
{title && <Text fw={700}>{title}</Text>}
|
||||||
|
</Group>
|
||||||
|
{description && <Badge variant="light">{description}</Badge>}
|
||||||
|
</Group>
|
||||||
|
{info && (
|
||||||
|
<Text size="sm" c="dimmed">
|
||||||
|
{info}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
{title && <Divider mb="sm" />}
|
||||||
|
<Stack gap="sm">{children}</Stack>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function FileInputWrapper({
|
||||||
|
label,
|
||||||
|
placeholder,
|
||||||
|
accept,
|
||||||
|
onChange,
|
||||||
|
preview,
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
linkView,
|
||||||
|
disabled,
|
||||||
|
required = false,
|
||||||
|
|
||||||
|
}: {
|
||||||
|
label: string;
|
||||||
|
placeholder?: string;
|
||||||
|
accept?: string;
|
||||||
|
linkView?: string;
|
||||||
|
onChange: (file: File | null) => void;
|
||||||
|
preview?: string | null;
|
||||||
|
name: string;
|
||||||
|
description?: string;
|
||||||
|
disabled?: boolean;
|
||||||
|
required?: boolean;
|
||||||
|
}) {
|
||||||
|
const [viewImg, setViewImg] = useState("");
|
||||||
|
const [openedPreviewFile, setOpenedPreviewFile] = useState(false);
|
||||||
|
|
||||||
|
useShallowEffect(() => {
|
||||||
|
if (viewImg) {
|
||||||
|
setOpenedPreviewFile(true);
|
||||||
|
}
|
||||||
|
}, [viewImg]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ModalFile
|
||||||
|
open={openedPreviewFile && !_.isEmpty(viewImg)}
|
||||||
|
onClose={() => {
|
||||||
|
setOpenedPreviewFile(false);
|
||||||
|
}}
|
||||||
|
folder="syarat-dokumen"
|
||||||
|
fileName={viewImg}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Stack gap="xs">
|
||||||
|
<Flex direction={"column"}>
|
||||||
|
<Group justify="apart" align="center">
|
||||||
|
<Text fw={500}>
|
||||||
|
{label}
|
||||||
|
{required && (
|
||||||
|
<Text span c="red" ml={4}>
|
||||||
|
*
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</Text>
|
||||||
|
</Group>
|
||||||
|
{description && (
|
||||||
|
<Text size="sm" c="dimmed" mt={4} style={{ lineHeight: 1.2 }}>
|
||||||
|
{description}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
{linkView && (
|
||||||
|
<Anchor onClick={() => setViewImg(linkView)} size="sm">
|
||||||
|
Lihat dokumen sebelumnya
|
||||||
|
</Anchor>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
|
||||||
|
<FileInput
|
||||||
|
accept={accept}
|
||||||
|
placeholder={placeholder}
|
||||||
|
onChange={(f) => onChange(f)}
|
||||||
|
leftSection={<IconUpload />}
|
||||||
|
aria-label={label}
|
||||||
|
name={name}
|
||||||
|
disabled={disabled}
|
||||||
|
clearable={true}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{preview ? (
|
||||||
|
<div>
|
||||||
|
<Text size="xs" color="dimmed">
|
||||||
|
Preview:
|
||||||
|
</Text>
|
||||||
|
<div style={{ marginTop: 6 }}>
|
||||||
|
<img
|
||||||
|
src={preview}
|
||||||
|
alt={`${label} preview`}
|
||||||
|
style={{ maxWidth: "200px", borderRadius: 4 }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</Stack>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<>
|
||||||
|
<FullScreenLoading visible={submitLoading} text="Mencari Data" />
|
||||||
|
<FormSection
|
||||||
|
title="Cari Pengajuan Surat"
|
||||||
|
info="Masukkan nomor pengajuan dan nomor telepon yang digunakan saat pengajuan surat."
|
||||||
|
>
|
||||||
|
<Grid>
|
||||||
|
<Grid.Col span={6}>
|
||||||
|
<TextInput
|
||||||
|
label={
|
||||||
|
<FieldLabel
|
||||||
|
label="Nomor Pengajuan"
|
||||||
|
hint="Nomor pengajuan surat"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
placeholder="PS-2025-000123"
|
||||||
|
onChange={(e) => {
|
||||||
|
setSearchPengajuan(e.target.value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Grid.Col>
|
||||||
|
|
||||||
|
<Grid.Col span={6}>
|
||||||
|
<TextInput
|
||||||
|
label={
|
||||||
|
<FieldLabel
|
||||||
|
label="Nomor Telephone"
|
||||||
|
hint="Nomor telephone yang dapat dihubungi / terhubung dengan whatsapp"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
placeholder="08123456789"
|
||||||
|
type="number"
|
||||||
|
onChange={(e) => {
|
||||||
|
setSearchPengajuanPhone(e.target.value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Grid.Col>
|
||||||
|
|
||||||
|
<Grid.Col span={12}>
|
||||||
|
<Button
|
||||||
|
fullWidth
|
||||||
|
variant="light"
|
||||||
|
color="blue"
|
||||||
|
onClick={() => {
|
||||||
|
handleSearch();
|
||||||
|
}}
|
||||||
|
loading={submitLoading}
|
||||||
|
>
|
||||||
|
Cari Pengajuan
|
||||||
|
</Button>
|
||||||
|
</Grid.Col>
|
||||||
|
</Grid>
|
||||||
|
</FormSection>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function DataUpdate({
|
||||||
|
noPengajuan,
|
||||||
|
onValidate,
|
||||||
|
}: {
|
||||||
|
noPengajuan: string;
|
||||||
|
onValidate: (e: boolean) => void;
|
||||||
|
}) {
|
||||||
|
const [opened, { open, close }] = useDisclosure(false);
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const [errors, setErrors] = useState<Record<string, string | null>>({});
|
||||||
|
const [sukses, setSukses] = useState(false);
|
||||||
|
const [submitLoading, setSubmitLoading] = useState(false);
|
||||||
|
const [dataPelengkap, setDataPelengkap] = useState<DataItem[]>([]);
|
||||||
|
const [dataSyaratDokumen, setDataSyaratDokumen] = useState<DataItem[]>([]);
|
||||||
|
const [dataPengajuan, setDataPengajuan] = useState<DataPengajuan | {}>({});
|
||||||
|
const [status, setStatus] = useState("");
|
||||||
|
const [loadingFetchData, setLoadingFetchData] = useState(false);
|
||||||
|
const [formSurat, setFormSurat] = useState<FormUpdateSurat>({
|
||||||
|
dataPelengkap: [],
|
||||||
|
syaratDokumen: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
async function fetchData() {
|
||||||
|
try {
|
||||||
|
setLoadingFetchData(true);
|
||||||
|
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);
|
||||||
|
} finally {
|
||||||
|
setLoadingFetchData(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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<T extends { id: string }>(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;
|
||||||
|
}) {
|
||||||
|
if (kategori == "syaratDokumen" && value.value == null) {
|
||||||
|
setFormSurat((prev) => ({
|
||||||
|
...prev,
|
||||||
|
syaratDokumen: prev.syaratDokumen.filter(
|
||||||
|
(item) => item.id !== value.id
|
||||||
|
),
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
if (value.required == true) {
|
||||||
|
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,
|
||||||
|
required: value.required,
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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() === "" && item.required) || (typeof item.value === "object" && item.value === null && item.required),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<>
|
||||||
|
<FullScreenLoading visible={submitLoading || loadingFetchData} />
|
||||||
|
<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" && (
|
||||||
|
<Alert
|
||||||
|
variant="light"
|
||||||
|
color="yellow"
|
||||||
|
radius="lg"
|
||||||
|
title={`Data pengajuan surat ini tidak dapat diupdate karena berstatus ${status}.`}
|
||||||
|
icon={<span style={{ fontSize: "1.2rem" }}>⚠</span>}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{status == "ditolak" && (
|
||||||
|
<Alert
|
||||||
|
variant="light"
|
||||||
|
color="yellow"
|
||||||
|
radius="lg"
|
||||||
|
title={`Data pengajuan surat ini ditolak, karena ${dataPengajuan && 'alasan' in dataPengajuan && dataPengajuan.alasan
|
||||||
|
? dataPengajuan.alasan
|
||||||
|
: "alasan tidak tersedia"
|
||||||
|
}. Silahkan perbaiki data pengajuan surat ini.`}
|
||||||
|
icon={<span style={{ fontSize: "1.2rem" }}>⚠</span>}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<FormSection
|
||||||
|
title="Data Yang Diperlukan"
|
||||||
|
description="Data yang diperlukan"
|
||||||
|
icon={<IconNotes size={16} />}
|
||||||
|
>
|
||||||
|
<Grid>
|
||||||
|
{dataPelengkap.map((item: any, index: number) => (
|
||||||
|
<Grid.Col span={6} key={index}>
|
||||||
|
{item.type == "enum" ? (
|
||||||
|
<Select
|
||||||
|
disabled={status != "ditolak" && status != "antrian"}
|
||||||
|
allowDeselect={false}
|
||||||
|
label={<FieldLabel label={item.name} hint={item.desc} required={item.required} />}
|
||||||
|
data={item.options ?? []}
|
||||||
|
placeholder={item.name}
|
||||||
|
onChange={(e) => {
|
||||||
|
validationForm({
|
||||||
|
kategori: "dataPelengkap",
|
||||||
|
value: { id: item.id, key: item.key, value: e, required: item.required },
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
value={
|
||||||
|
formSurat.dataPelengkap.find(
|
||||||
|
(n: any) => n.key == item.key,
|
||||||
|
)?.value ||
|
||||||
|
dataPelengkap.find((n: any) => n.key == item.key)?.value
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
) : item.type == "date" ? (
|
||||||
|
<DateInput
|
||||||
|
disabled={status != "ditolak" && status != "antrian"}
|
||||||
|
locale="id"
|
||||||
|
valueFormat="DD MMMM YYYY"
|
||||||
|
label={<FieldLabel label={item.name} hint={item.desc} required={item.required} />}
|
||||||
|
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,
|
||||||
|
required: item.required
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<TextInput
|
||||||
|
error={errors[item.id]}
|
||||||
|
label={<FieldLabel label={item.name} hint={item.desc} required={item.required} />}
|
||||||
|
placeholder={item.name}
|
||||||
|
type={item.type}
|
||||||
|
onChange={(e) =>
|
||||||
|
validationForm({
|
||||||
|
kategori: "dataPelengkap",
|
||||||
|
value: {
|
||||||
|
id: item.id,
|
||||||
|
key: item.key,
|
||||||
|
value: e.target.value,
|
||||||
|
required: item.required,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
value={
|
||||||
|
formSurat.dataPelengkap.find((n) => n.id === item.id)
|
||||||
|
?.value ??
|
||||||
|
dataPelengkap.find((n: any) => n.key == item.key)?.value
|
||||||
|
}
|
||||||
|
disabled={status != "ditolak" && status != "antrian"}
|
||||||
|
rightSection={
|
||||||
|
item.satuan != null &&
|
||||||
|
<Text mr={"lg"}>{item.satuan}</Text>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Grid.Col>
|
||||||
|
))}
|
||||||
|
</Grid>
|
||||||
|
</FormSection>
|
||||||
|
|
||||||
|
<FormSection
|
||||||
|
title="Syarat Dokumen hjh"
|
||||||
|
description="Syarat dokumen yang diperlukan"
|
||||||
|
icon={<IconFiles size={16} />}
|
||||||
|
>
|
||||||
|
<Grid>
|
||||||
|
{dataSyaratDokumen.map((item: any, index: number) => (
|
||||||
|
<Grid.Col span={6} key={index}>
|
||||||
|
<FileInputWrapper
|
||||||
|
required={item.required}
|
||||||
|
label={item.desc}
|
||||||
|
placeholder={"Upload file terbaru untuk mengupdate"}
|
||||||
|
accept="image/*,application/pdf"
|
||||||
|
linkView={item.value}
|
||||||
|
onChange={(file) =>
|
||||||
|
validationForm({
|
||||||
|
kategori: "syaratDokumen",
|
||||||
|
value: { id: item.id, key: item.key, value: file, required: item.required },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
name={item.name}
|
||||||
|
disabled={status != "ditolak" && status != "antrian"}
|
||||||
|
/>
|
||||||
|
</Grid.Col>
|
||||||
|
))}
|
||||||
|
</Grid>
|
||||||
|
</FormSection>
|
||||||
|
|
||||||
|
<Group justify="right" mt="md">
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
onChecking();
|
||||||
|
}}
|
||||||
|
disabled={status != "ditolak" && status != "antrian"}
|
||||||
|
>
|
||||||
|
Kirim
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -73,7 +73,7 @@ export default function DashboardLayout() {
|
|||||||
<AppShell
|
<AppShell
|
||||||
padding="lg"
|
padding="lg"
|
||||||
navbar={{
|
navbar={{
|
||||||
width: 260,
|
width: 300,
|
||||||
breakpoint: "sm",
|
breakpoint: "sm",
|
||||||
collapsed: { mobile: !opened, desktop: !opened },
|
collapsed: { mobile: !opened, desktop: !opened },
|
||||||
}}
|
}}
|
||||||
@@ -290,22 +290,31 @@ function NavigationDashboard() {
|
|||||||
.map((item) => (
|
.map((item) => (
|
||||||
<NavLink
|
<NavLink
|
||||||
key={item.path}
|
key={item.path}
|
||||||
active={isActive(item.path as keyof typeof clientRoute)}
|
active={isActive(item.path as keyof typeof clientRoute) ||
|
||||||
|
(location.pathname == "/scr/dashboard/pelayanan-surat/detail-pelayanan" && item.path == "/scr/dashboard/pelayanan-surat/list-pelayanan") ||
|
||||||
|
(location.pathname == "/scr/dashboard/pengaduan/detail" && item.path == "/scr/dashboard/pengaduan/list") ||
|
||||||
|
(location.pathname == "/scr/dashboard/warga/detail-warga" && item.path == "/scr/dashboard/warga/list-warga")}
|
||||||
leftSection={item.icon}
|
leftSection={item.icon}
|
||||||
label={
|
label={
|
||||||
<Flex align="center" gap={6}>
|
<Flex align="center" gap={6}>
|
||||||
<Text fw={500}>{item.label}</Text>
|
<Text fw={500}>{item.label}</Text>
|
||||||
{isActive(item.path as keyof typeof clientRoute) && (
|
{(
|
||||||
<Badge
|
isActive(item.path as keyof typeof clientRoute) ||
|
||||||
variant="light"
|
(location.pathname == "/scr/dashboard/pelayanan-surat/detail-pelayanan" && item.path == "/scr/dashboard/pelayanan-surat/list-pelayanan") ||
|
||||||
color="teal"
|
(location.pathname == "/scr/dashboard/pengaduan/detail" && item.path == "/scr/dashboard/pengaduan/list") ||
|
||||||
radius="sm"
|
(location.pathname == "/scr/dashboard/warga/detail-warga" && item.path == "/scr/dashboard/warga/list-warga")
|
||||||
size="xs"
|
)
|
||||||
style={{ textTransform: "none" }}
|
&& (
|
||||||
>
|
<Badge
|
||||||
Active
|
variant="light"
|
||||||
</Badge>
|
color="teal"
|
||||||
)}
|
radius="sm"
|
||||||
|
size="xs"
|
||||||
|
style={{ textTransform: "none" }}
|
||||||
|
>
|
||||||
|
Active
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
}
|
}
|
||||||
description={item.description}
|
description={item.description}
|
||||||
@@ -313,7 +322,10 @@ function NavigationDashboard() {
|
|||||||
navigate(clientRoutes[item.path as keyof typeof clientRoute])
|
navigate(clientRoutes[item.path as keyof typeof clientRoute])
|
||||||
}
|
}
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: isActive(item.path as keyof typeof clientRoute)
|
backgroundColor: isActive(item.path as keyof typeof clientRoute) ||
|
||||||
|
(location.pathname == "/scr/dashboard/pelayanan-surat/detail-pelayanan" && item.path == "/scr/dashboard/pelayanan-surat/list-pelayanan") ||
|
||||||
|
(location.pathname == "/scr/dashboard/pengaduan/detail" && item.path == "/scr/dashboard/pengaduan/list") ||
|
||||||
|
(location.pathname == "/scr/dashboard/warga/detail-warga" && item.path == "/scr/dashboard/warga/list-warga")
|
||||||
? "rgba(0,255,200,0.1)"
|
? "rgba(0,255,200,0.1)"
|
||||||
: "transparent",
|
: "transparent",
|
||||||
borderRadius: "8px",
|
borderRadius: "8px",
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
|
import BreadCrumbs from "@/components/BreadCrumbs";
|
||||||
|
import FullScreenLoading from "@/components/FullScreenLoading";
|
||||||
import ModalFile from "@/components/ModalFile";
|
import ModalFile from "@/components/ModalFile";
|
||||||
import ModalSurat from "@/components/ModalSurat";
|
import ModalSurat from "@/components/ModalSurat";
|
||||||
import notification from "@/components/notificationGlobal";
|
import notification from "@/components/notificationGlobal";
|
||||||
import apiFetch from "@/lib/apiFetch";
|
import apiFetch from "@/lib/apiFetch";
|
||||||
|
import { parseTanggalID } from "@/server/lib/stringToDate";
|
||||||
import {
|
import {
|
||||||
|
ActionIcon,
|
||||||
Anchor,
|
Anchor,
|
||||||
Badge,
|
Badge,
|
||||||
Button,
|
Button,
|
||||||
@@ -14,23 +18,31 @@ import {
|
|||||||
Group,
|
Group,
|
||||||
List,
|
List,
|
||||||
Modal,
|
Modal,
|
||||||
|
Select,
|
||||||
|
Spoiler,
|
||||||
Stack,
|
Stack,
|
||||||
Table,
|
Table,
|
||||||
Text,
|
Text,
|
||||||
Textarea,
|
Textarea,
|
||||||
|
TextInput,
|
||||||
ThemeIcon,
|
ThemeIcon,
|
||||||
Title,
|
Title,
|
||||||
|
Tooltip
|
||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
|
import { DateInput } from "@mantine/dates";
|
||||||
import { useDisclosure, useShallowEffect } from "@mantine/hooks";
|
import { useDisclosure, useShallowEffect } from "@mantine/hooks";
|
||||||
import {
|
import {
|
||||||
IconAlignJustified,
|
IconAlignJustified,
|
||||||
IconCheck,
|
IconCheck,
|
||||||
|
IconEdit,
|
||||||
IconFileCertificate,
|
IconFileCertificate,
|
||||||
IconFileCheck,
|
IconFileCheck,
|
||||||
|
IconInfoCircle,
|
||||||
IconMessageReport,
|
IconMessageReport,
|
||||||
IconPhone,
|
IconPhone,
|
||||||
IconUser,
|
IconUser,
|
||||||
} from "@tabler/icons-react";
|
} from "@tabler/icons-react";
|
||||||
|
import dayjs from "dayjs";
|
||||||
import type { User } from "generated/prisma";
|
import type { User } from "generated/prisma";
|
||||||
import type { JsonValue } from "generated/prisma/runtime/library";
|
import type { JsonValue } from "generated/prisma/runtime/library";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
@@ -39,6 +51,11 @@ import { useLocation } from "react-router-dom";
|
|||||||
import useSwr from "swr";
|
import useSwr from "swr";
|
||||||
|
|
||||||
export default function DetailPengajuanPage() {
|
export default function DetailPengajuanPage() {
|
||||||
|
const dataMenu = [
|
||||||
|
{ title: "Dashboard", link: "/scr/dashboard/dashboard-home", active: false },
|
||||||
|
{ title: "Pelayanan Surat", link: "/scr/dashboard/pelayanan-surat/list-pelayanan", active: false },
|
||||||
|
{ title: "Detail Pengajuan Surat", link: "#", active: true },
|
||||||
|
];
|
||||||
const { search } = useLocation();
|
const { search } = useLocation();
|
||||||
const query = new URLSearchParams(search);
|
const query = new URLSearchParams(search);
|
||||||
const id = query.get("id");
|
const id = query.get("id");
|
||||||
@@ -57,10 +74,14 @@ export default function DetailPengajuanPage() {
|
|||||||
return (
|
return (
|
||||||
<Container size="xl" py="xl" w={"100%"}>
|
<Container size="xl" py="xl" w={"100%"}>
|
||||||
<Grid>
|
<Grid>
|
||||||
|
<Grid.Col span={12}>
|
||||||
|
<BreadCrumbs dataLink={dataMenu} back />
|
||||||
|
</Grid.Col>
|
||||||
<Grid.Col span={8}>
|
<Grid.Col span={8}>
|
||||||
<Stack gap={"xl"}>
|
<Stack gap={"xl"}>
|
||||||
<DetailDataPengajuan
|
<DetailDataPengajuan
|
||||||
data={data?.data?.pengajuan}
|
data={data?.data?.pengajuan}
|
||||||
|
warga={data && data.data && data.data.warga ? data.data.warga : undefined}
|
||||||
syaratDokumen={data?.data?.syaratDokumen}
|
syaratDokumen={data?.data?.syaratDokumen}
|
||||||
dataText={data?.data?.dataText}
|
dataText={data?.data?.dataText}
|
||||||
onAction={() => {
|
onAction={() => {
|
||||||
@@ -80,15 +101,18 @@ export default function DetailPengajuanPage() {
|
|||||||
|
|
||||||
function DetailDataPengajuan({
|
function DetailDataPengajuan({
|
||||||
data,
|
data,
|
||||||
|
warga,
|
||||||
syaratDokumen,
|
syaratDokumen,
|
||||||
dataText,
|
dataText,
|
||||||
onAction,
|
onAction,
|
||||||
}: {
|
}: {
|
||||||
data: any;
|
data: any;
|
||||||
|
warga?: { phone?: string | null } | null;
|
||||||
syaratDokumen: any;
|
syaratDokumen: any;
|
||||||
dataText: any;
|
dataText: any;
|
||||||
onAction: () => void;
|
onAction: () => void;
|
||||||
}) {
|
}) {
|
||||||
|
|
||||||
const [opened, { open, close }] = useDisclosure(false);
|
const [opened, { open, close }] = useDisclosure(false);
|
||||||
const [catModal, setCatModal] = useState<"tolak" | "terima">("tolak");
|
const [catModal, setCatModal] = useState<"tolak" | "terima">("tolak");
|
||||||
const [keterangan, setKeterangan] = useState("");
|
const [keterangan, setKeterangan] = useState("");
|
||||||
@@ -97,7 +121,12 @@ function DetailDataPengajuan({
|
|||||||
const [openedPreview, setOpenedPreview] = useState(false);
|
const [openedPreview, setOpenedPreview] = useState(false);
|
||||||
const [openedPreviewFile, setOpenedPreviewFile] = useState(false);
|
const [openedPreviewFile, setOpenedPreviewFile] = useState(false);
|
||||||
const [permissions, setPermissions] = useState<JsonValue[]>([]);
|
const [permissions, setPermissions] = useState<JsonValue[]>([]);
|
||||||
const [viewImg, setViewImg] = useState("");
|
const [viewImg, setViewImg] = useState({ file: "", folder: "" });
|
||||||
|
const [uploading, setUploading] = useState({ ok: false, file: "" });
|
||||||
|
const [editValue, setEditValue] = useState({ id: "", jenis: "", val: "", satuan: null as string | null, option: null as any, type: "", key: "" })
|
||||||
|
const [openEdit, setOpenEdit] = useState(false)
|
||||||
|
const [loadingUpdate, setLoadingUpdate] = useState(false)
|
||||||
|
const [loadingFS, setLoadingFS] = useState({ value: false, text: "" })
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function fetchHost() {
|
async function fetchHost() {
|
||||||
@@ -114,22 +143,88 @@ function DetailDataPengajuan({
|
|||||||
fetchHost();
|
fetchHost();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
async function sendWA({ status, linkSurat, linkUpdate }: { status: string, linkSurat: string, linkUpdate: string }) {
|
||||||
|
try {
|
||||||
|
setLoadingFS({ value: true, text: "Sending message to warga" })
|
||||||
|
const resWA = await apiFetch.api["send-wa"]["pengajuan-surat"].post({
|
||||||
|
noPengajuan: data?.noPengajuan ?? "",
|
||||||
|
jenisSurat: data?.category ?? "",
|
||||||
|
alasan: keterangan,
|
||||||
|
status,
|
||||||
|
linkSurat,
|
||||||
|
linkUpdate,
|
||||||
|
tlp: warga?.phone ?? "",
|
||||||
|
})
|
||||||
|
|
||||||
|
if (resWA?.status === 200) {
|
||||||
|
if (resWA.data?.success) {
|
||||||
|
notification({
|
||||||
|
title: "Success",
|
||||||
|
message: "Success send message to warga",
|
||||||
|
type: "success",
|
||||||
|
});
|
||||||
|
|
||||||
|
if (status == "selesai") {
|
||||||
|
onAction()
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
notification({
|
||||||
|
title: "Failed",
|
||||||
|
message: "Failed send message to warga",
|
||||||
|
type: "error",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
notification({
|
||||||
|
title: "Failed",
|
||||||
|
message: "Failed send message to warga",
|
||||||
|
type: "error",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
notification({
|
||||||
|
title: "Failed",
|
||||||
|
message: "Failed send message to warga",
|
||||||
|
type: "error",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
setLoadingFS({ value: false, text: "" })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const handleKonfirmasi = async (cat: "terima" | "tolak") => {
|
const handleKonfirmasi = async (cat: "terima" | "tolak") => {
|
||||||
try {
|
try {
|
||||||
|
setLoadingFS({ value: true, text: "Updating status" })
|
||||||
|
const statusFix = cat == "tolak"
|
||||||
|
? "ditolak"
|
||||||
|
: data.status == "antrian"
|
||||||
|
? "diterima"
|
||||||
|
: "selesai"
|
||||||
|
|
||||||
const res = await apiFetch.api.pelayanan["update-status"].post({
|
const res = await apiFetch.api.pelayanan["update-status"].post({
|
||||||
id: data?.id,
|
id: data?.id,
|
||||||
status:
|
status: statusFix,
|
||||||
cat == "tolak"
|
|
||||||
? "ditolak"
|
|
||||||
: data.status == "antrian"
|
|
||||||
? "diterima"
|
|
||||||
: "selesai",
|
|
||||||
keterangan: keterangan,
|
keterangan: keterangan,
|
||||||
idUser: host?.id ?? "",
|
idUser: host?.id ?? "",
|
||||||
noSurat: noSurat,
|
noSurat: noSurat,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (res?.status === 200) {
|
if (res?.status === 200) {
|
||||||
|
if (statusFix == "selesai") {
|
||||||
|
setTimeout(() => {
|
||||||
|
setOpenedPreview(true)
|
||||||
|
}, 1000)
|
||||||
|
} else {
|
||||||
|
sendWA({
|
||||||
|
status: statusFix,
|
||||||
|
linkSurat: "",
|
||||||
|
linkUpdate: statusFix == "ditolak" ? res.data?.linkUpdate ?? '' : '',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
onAction();
|
onAction();
|
||||||
close();
|
close();
|
||||||
notification({
|
notification({
|
||||||
@@ -151,24 +246,158 @@ function DetailDataPengajuan({
|
|||||||
message: "Failed to update pengajuan surat",
|
message: "Failed to update pengajuan surat",
|
||||||
type: "error",
|
type: "error",
|
||||||
});
|
});
|
||||||
|
} finally {
|
||||||
|
setLoadingFS({ value: false, text: "" })
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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(() => {
|
useShallowEffect(() => {
|
||||||
if (viewImg) {
|
if (viewImg) {
|
||||||
setOpenedPreviewFile(true);
|
setOpenedPreviewFile(true);
|
||||||
}
|
}
|
||||||
}, [viewImg]);
|
}, [viewImg]);
|
||||||
|
|
||||||
|
useShallowEffect(() => {
|
||||||
|
if (uploading.ok && uploading.file) {
|
||||||
|
sendWA({
|
||||||
|
status: "selesai",
|
||||||
|
linkSurat: uploading.file,
|
||||||
|
linkUpdate: "",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [uploading]);
|
||||||
|
|
||||||
|
function FieldLabel({ label, hint }: { label: string; hint?: string }) {
|
||||||
|
return (
|
||||||
|
<Group justify="apart" gap="xs" align="center">
|
||||||
|
<Text fw={600}>{label}</Text>
|
||||||
|
{hint && (
|
||||||
|
<Tooltip label={hint} withArrow>
|
||||||
|
<ActionIcon size={24} variant="subtle">
|
||||||
|
<IconInfoCircle size={16} />
|
||||||
|
</ActionIcon>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
</Group>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
||||||
|
<FullScreenLoading visible={loadingFS.value} text={loadingFS.text} />
|
||||||
|
{/* MODAL EDIT DATA PELENGKAP */}
|
||||||
|
<Modal
|
||||||
|
opened={openEdit}
|
||||||
|
onClose={() => setOpenEdit(false)}
|
||||||
|
title={"Edit"}
|
||||||
|
overlayProps={{ backgroundOpacity: 0.55, blur: 3 }}
|
||||||
|
>
|
||||||
|
<Stack gap="ld">
|
||||||
|
{editValue.type == "enum" ? (
|
||||||
|
<Select
|
||||||
|
allowDeselect={false}
|
||||||
|
label={<FieldLabel label={editValue.jenis} />}
|
||||||
|
data={editValue.option ?? []}
|
||||||
|
placeholder={editValue.jenis}
|
||||||
|
onChange={(e) => { setEditValue({ ...editValue, val: e ?? "" }) }}
|
||||||
|
value={editValue.val}
|
||||||
|
/>
|
||||||
|
) : editValue.type == "date" ? (
|
||||||
|
<DateInput
|
||||||
|
locale="id"
|
||||||
|
valueFormat="DD MMMM YYYY"
|
||||||
|
label={<FieldLabel label={editValue.jenis} />}
|
||||||
|
placeholder={editValue.jenis}
|
||||||
|
onChange={(e) => {
|
||||||
|
const formatted = e
|
||||||
|
? dayjs(e).locale("id").format("DD MMMM YYYY")
|
||||||
|
: "";
|
||||||
|
setEditValue({
|
||||||
|
...editValue,
|
||||||
|
val: formatted
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
value={
|
||||||
|
editValue.val
|
||||||
|
? parseTanggalID(editValue.val)
|
||||||
|
: parseTanggalID(editValue.val)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<TextInput
|
||||||
|
label={<FieldLabel label={editValue.jenis} />}
|
||||||
|
placeholder={editValue.jenis}
|
||||||
|
type={editValue.type}
|
||||||
|
onChange={(e) => { setEditValue({ ...editValue, val: e.target.value }) }}
|
||||||
|
value={editValue.val}
|
||||||
|
rightSection={
|
||||||
|
editValue.satuan != null &&
|
||||||
|
<Text mr={"lg"}>{editValue.satuan}</Text>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Group justify="center" grow>
|
||||||
|
<Button variant="light" onClick={() => { setOpenEdit(false) }}>
|
||||||
|
Batal
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="filled"
|
||||||
|
onClick={updateDataText}
|
||||||
|
disabled={loadingUpdate || !editValue.val}
|
||||||
|
loading={loadingUpdate}
|
||||||
|
>
|
||||||
|
Simpan
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
<ModalFile
|
<ModalFile
|
||||||
open={openedPreviewFile && !_.isEmpty(viewImg)}
|
open={openedPreviewFile && !_.isEmpty(viewImg.file)}
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
setOpenedPreviewFile(false);
|
setOpenedPreviewFile(false);
|
||||||
|
setViewImg({ file: "", folder: "" })
|
||||||
}}
|
}}
|
||||||
folder="syarat-dokumen"
|
folder={viewImg.folder}
|
||||||
fileName={viewImg}
|
fileName={viewImg.file}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* MODAL KONFIRMASI */}
|
{/* MODAL KONFIRMASI */}
|
||||||
@@ -240,10 +469,15 @@ function DetailDataPengajuan({
|
|||||||
)}
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
</Modal>
|
</Modal>
|
||||||
{data?.status == "selesai" && (
|
|
||||||
|
{/* MODAL PREVIEW SURAT */}
|
||||||
|
{data?.status == "selesai" && !data?.fileSurat && (
|
||||||
<ModalSurat
|
<ModalSurat
|
||||||
open={openedPreview}
|
open={openedPreview}
|
||||||
onClose={() => setOpenedPreview(false)}
|
onClose={(val) => {
|
||||||
|
setOpenedPreview(false)
|
||||||
|
setUploading({ ok: val.success, file: val.data })
|
||||||
|
}}
|
||||||
surat={data?.idSurat}
|
surat={data?.idSurat}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -311,7 +545,7 @@ function DetailDataPengajuan({
|
|||||||
<List.Item key={v.id}>
|
<List.Item key={v.id}>
|
||||||
<Anchor
|
<Anchor
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setViewImg(v.value);
|
setViewImg({ file: v.value, folder: "syarat-dokumen" });
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{v.jenis}
|
{v.jenis}
|
||||||
@@ -338,7 +572,25 @@ function DetailDataPengajuan({
|
|||||||
</Table.Td>
|
</Table.Td>
|
||||||
<Table.Td>:</Table.Td>
|
<Table.Td>:</Table.Td>
|
||||||
<Table.Td style={{ width: "85%" }}>
|
<Table.Td style={{ width: "85%" }}>
|
||||||
{_.upperFirst(item.value)}
|
<Flex
|
||||||
|
gap="md"
|
||||||
|
justify="flex-start"
|
||||||
|
align="center"
|
||||||
|
direction="row"
|
||||||
|
>
|
||||||
|
<Text>
|
||||||
|
{_.upperFirst(item.value)} {item.satuan}
|
||||||
|
</Text>
|
||||||
|
<ActionIcon
|
||||||
|
variant="subtle"
|
||||||
|
aria-label="Edit"
|
||||||
|
onClick={() => {
|
||||||
|
setEditValue({ id: item.id, val: item.value, type: item.type, satuan: item.satuan, option: item.options, jenis: item.jenis, key: item.key })
|
||||||
|
setOpenEdit(true)
|
||||||
|
}}>
|
||||||
|
<IconEdit size={16} />
|
||||||
|
</ActionIcon>
|
||||||
|
</Flex>
|
||||||
</Table.Td>
|
</Table.Td>
|
||||||
</Table.Tr>
|
</Table.Tr>
|
||||||
))}
|
))}
|
||||||
@@ -396,18 +648,31 @@ function DetailDataPengajuan({
|
|||||||
Setujui
|
Setujui
|
||||||
</Button>
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
) : data?.status === "selesai" ? (
|
) : data?.status === "selesai" ?
|
||||||
<Group justify="center" grow>
|
!data?.fileSurat ?
|
||||||
<Button
|
(
|
||||||
variant="light"
|
<Group justify="center" grow>
|
||||||
onClick={() => setOpenedPreview(!openedPreview)}
|
<Button
|
||||||
>
|
variant="light"
|
||||||
Surat
|
onClick={() => { setOpenedPreview(true) }}
|
||||||
</Button>
|
>
|
||||||
</Group>
|
Kirim Ulang Surat
|
||||||
) : (
|
</Button>
|
||||||
<></>
|
</Group>
|
||||||
)}
|
)
|
||||||
|
:
|
||||||
|
(
|
||||||
|
<Group justify="center" grow>
|
||||||
|
<Button
|
||||||
|
variant="light"
|
||||||
|
onClick={() => { setViewImg({ file: data?.fileSurat, folder: "surat" }) }}
|
||||||
|
>
|
||||||
|
Surat
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)}
|
||||||
</Grid.Col>
|
</Grid.Col>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -432,41 +697,48 @@ function DetailDataHistori({ data }: { data: any }) {
|
|||||||
<Stack gap="md">
|
<Stack gap="md">
|
||||||
<Flex align="center" justify="space-between">
|
<Flex align="center" justify="space-between">
|
||||||
<Title order={4} c="gray.2">
|
<Title order={4} c="gray.2">
|
||||||
Histori Pengajuan Surat
|
Riwayat Pengajuan Surat
|
||||||
</Title>
|
</Title>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Divider my={0} />
|
<Divider my={0} />
|
||||||
<Table>
|
<Spoiler
|
||||||
<Table.Thead>
|
maxHeight={200}
|
||||||
<Table.Tr>
|
showLabel="Show more"
|
||||||
<Table.Th>Tanggal</Table.Th>
|
hideLabel="Hide"
|
||||||
<Table.Th>Deskripsi</Table.Th>
|
transitionDuration={1000}
|
||||||
<Table.Th>Status</Table.Th>
|
>
|
||||||
<Table.Th>User</Table.Th>
|
<Table>
|
||||||
</Table.Tr>
|
<Table.Thead>
|
||||||
</Table.Thead>
|
<Table.Tr>
|
||||||
<Table.Tbody>
|
<Table.Th>Tanggal</Table.Th>
|
||||||
{data?.map((item: any) => (
|
<Table.Th>Deskripsi</Table.Th>
|
||||||
<Table.Tr key={item.id}>
|
<Table.Th>Status</Table.Th>
|
||||||
<Table.Td style={{ whiteSpace: "nowrap" }}>
|
<Table.Th>User</Table.Th>
|
||||||
{item.createdAt.toLocaleString("id-ID", {
|
|
||||||
day: "2-digit",
|
|
||||||
month: "short",
|
|
||||||
year: "numeric",
|
|
||||||
hour: "2-digit",
|
|
||||||
minute: "2-digit",
|
|
||||||
hour12: false,
|
|
||||||
})}
|
|
||||||
</Table.Td>
|
|
||||||
<Table.Td>{item.deskripsi}</Table.Td>
|
|
||||||
<Table.Td>{item.status}</Table.Td>
|
|
||||||
<Table.Td style={{ whiteSpace: "nowrap" }}>
|
|
||||||
{item.nameUser ? item.nameUser : "-"}
|
|
||||||
</Table.Td>
|
|
||||||
</Table.Tr>
|
</Table.Tr>
|
||||||
))}
|
</Table.Thead>
|
||||||
</Table.Tbody>
|
<Table.Tbody>
|
||||||
</Table>
|
{data?.map((item: any) => (
|
||||||
|
<Table.Tr key={item.id}>
|
||||||
|
<Table.Td style={{ whiteSpace: "nowrap" }}>
|
||||||
|
{item.createdAt.toLocaleString("id-ID", {
|
||||||
|
day: "2-digit",
|
||||||
|
month: "short",
|
||||||
|
year: "numeric",
|
||||||
|
hour: "2-digit",
|
||||||
|
minute: "2-digit",
|
||||||
|
hour12: false,
|
||||||
|
})}
|
||||||
|
</Table.Td>
|
||||||
|
<Table.Td>{item.deskripsi}</Table.Td>
|
||||||
|
<Table.Td>{item.status}</Table.Td>
|
||||||
|
<Table.Td style={{ whiteSpace: "nowrap" }}>
|
||||||
|
{item.nameUser ? item.nameUser : "-"}
|
||||||
|
</Table.Td>
|
||||||
|
</Table.Tr>
|
||||||
|
))}
|
||||||
|
</Table.Tbody>
|
||||||
|
</Table>
|
||||||
|
</Spoiler>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import BreadCrumbs from "@/components/BreadCrumbs";
|
||||||
import ModalFile from "@/components/ModalFile";
|
import ModalFile from "@/components/ModalFile";
|
||||||
import notification from "@/components/notificationGlobal";
|
import notification from "@/components/notificationGlobal";
|
||||||
import apiFetch from "@/lib/apiFetch";
|
import apiFetch from "@/lib/apiFetch";
|
||||||
@@ -12,6 +13,7 @@ import {
|
|||||||
Grid,
|
Grid,
|
||||||
Group,
|
Group,
|
||||||
Modal,
|
Modal,
|
||||||
|
Spoiler,
|
||||||
Stack,
|
Stack,
|
||||||
Table,
|
Table,
|
||||||
Text,
|
Text,
|
||||||
@@ -37,7 +39,13 @@ import { useEffect, useState } from "react";
|
|||||||
import { useLocation } from "react-router-dom";
|
import { useLocation } from "react-router-dom";
|
||||||
import useSwr from "swr";
|
import useSwr from "swr";
|
||||||
|
|
||||||
|
|
||||||
export default function DetailPengaduanPage() {
|
export default function DetailPengaduanPage() {
|
||||||
|
const dataMenu = [
|
||||||
|
{ title: "Dashboard", link: "/scr/dashboard/dashboard-home", active: false },
|
||||||
|
{ title: "Pengaduan", link: "/scr/dashboard/pengaduan/list", active: false },
|
||||||
|
{ title: "Detail Pengaduan", link: "#", active: true },
|
||||||
|
];
|
||||||
const { search } = useLocation();
|
const { search } = useLocation();
|
||||||
const query = new URLSearchParams(search);
|
const query = new URLSearchParams(search);
|
||||||
const id = query.get("id");
|
const id = query.get("id");
|
||||||
@@ -56,10 +64,14 @@ export default function DetailPengaduanPage() {
|
|||||||
return (
|
return (
|
||||||
<Container size="xl" py="xl" w={"100%"}>
|
<Container size="xl" py="xl" w={"100%"}>
|
||||||
<Grid>
|
<Grid>
|
||||||
|
<Grid.Col span={12}>
|
||||||
|
<BreadCrumbs dataLink={dataMenu} back />
|
||||||
|
</Grid.Col>
|
||||||
<Grid.Col span={8}>
|
<Grid.Col span={8}>
|
||||||
<Stack gap={"xl"}>
|
<Stack gap={"xl"}>
|
||||||
<DetailDataPengaduan
|
<DetailDataPengaduan
|
||||||
data={data?.data?.pengaduan}
|
data={data?.data?.pengaduan}
|
||||||
|
phone={data && data.data && data.data.warga ? data.data.warga.phone : null}
|
||||||
onAction={() => {
|
onAction={() => {
|
||||||
mutate();
|
mutate();
|
||||||
}}
|
}}
|
||||||
@@ -77,9 +89,11 @@ export default function DetailPengaduanPage() {
|
|||||||
|
|
||||||
function DetailDataPengaduan({
|
function DetailDataPengaduan({
|
||||||
data,
|
data,
|
||||||
|
phone,
|
||||||
onAction,
|
onAction,
|
||||||
}: {
|
}: {
|
||||||
data: any | null;
|
data: any | null;
|
||||||
|
phone?: string | null;
|
||||||
onAction: () => void;
|
onAction: () => void;
|
||||||
}) {
|
}) {
|
||||||
const [opened, { open, close }] = useDisclosure(false);
|
const [opened, { open, close }] = useDisclosure(false);
|
||||||
@@ -88,6 +102,7 @@ function DetailDataPengaduan({
|
|||||||
const [keterangan, setKeterangan] = useState("");
|
const [keterangan, setKeterangan] = useState("");
|
||||||
const [host, setHost] = useState<User | null>(null);
|
const [host, setHost] = useState<User | null>(null);
|
||||||
const [permissions, setPermissions] = useState<JsonValue[]>([]);
|
const [permissions, setPermissions] = useState<JsonValue[]>([]);
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function fetchHost() {
|
async function fetchHost() {
|
||||||
@@ -106,6 +121,7 @@ function DetailDataPengaduan({
|
|||||||
|
|
||||||
const handleKonfirmasi = async (cat: "terima" | "tolak") => {
|
const handleKonfirmasi = async (cat: "terima" | "tolak") => {
|
||||||
try {
|
try {
|
||||||
|
setIsLoading(true);
|
||||||
const res = await apiFetch.api.pengaduan["update-status"].post({
|
const res = await apiFetch.api.pengaduan["update-status"].post({
|
||||||
id: data?.id,
|
id: data?.id,
|
||||||
status:
|
status:
|
||||||
@@ -121,6 +137,21 @@ function DetailDataPengaduan({
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (res?.status === 200) {
|
if (res?.status === 200) {
|
||||||
|
const resWA = await apiFetch.api["send-wa"].pengaduan.post({
|
||||||
|
noPengaduan: data?.noPengaduan,
|
||||||
|
judulPengaduan: data?.title,
|
||||||
|
status:
|
||||||
|
cat == "tolak"
|
||||||
|
? "ditolak"
|
||||||
|
: data.status == "antrian"
|
||||||
|
? "diterima"
|
||||||
|
: data.status == "diterima"
|
||||||
|
? "dikerjakan"
|
||||||
|
: "selesai",
|
||||||
|
alasan: keterangan,
|
||||||
|
tlp: String(phone),
|
||||||
|
})
|
||||||
|
|
||||||
onAction();
|
onAction();
|
||||||
close();
|
close();
|
||||||
notification({
|
notification({
|
||||||
@@ -128,6 +159,28 @@ function DetailDataPengaduan({
|
|||||||
message: "Success update pengaduan",
|
message: "Success update pengaduan",
|
||||||
type: "success",
|
type: "success",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (resWA?.status === 200) {
|
||||||
|
if (resWA.data?.success) {
|
||||||
|
notification({
|
||||||
|
title: "Success",
|
||||||
|
message: "Success send message to warga",
|
||||||
|
type: "success",
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
notification({
|
||||||
|
title: "Failed",
|
||||||
|
message: "Failed send message to warga",
|
||||||
|
type: "error",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
notification({
|
||||||
|
title: "Failed",
|
||||||
|
message: "Failed send message to warga",
|
||||||
|
type: "error",
|
||||||
|
});
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
notification({
|
notification({
|
||||||
title: "Error",
|
title: "Error",
|
||||||
@@ -142,6 +195,8 @@ function DetailDataPengaduan({
|
|||||||
message: "Failed to update pengaduan",
|
message: "Failed to update pengaduan",
|
||||||
type: "error",
|
type: "error",
|
||||||
});
|
});
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -176,6 +231,7 @@ function DetailDataPengaduan({
|
|||||||
color="red"
|
color="red"
|
||||||
disabled={keterangan.length < 1}
|
disabled={keterangan.length < 1}
|
||||||
onClick={() => handleKonfirmasi("tolak")}
|
onClick={() => handleKonfirmasi("tolak")}
|
||||||
|
loading={isLoading}
|
||||||
>
|
>
|
||||||
Tolak
|
Tolak
|
||||||
</Button>
|
</Button>
|
||||||
@@ -200,6 +256,7 @@ function DetailDataPengaduan({
|
|||||||
variant="filled"
|
variant="filled"
|
||||||
color="green"
|
color="green"
|
||||||
onClick={() => handleKonfirmasi("terima")}
|
onClick={() => handleKonfirmasi("terima")}
|
||||||
|
loading={isLoading}
|
||||||
>
|
>
|
||||||
Ya
|
Ya
|
||||||
</Button>
|
</Button>
|
||||||
@@ -420,41 +477,48 @@ function DetailDataHistori({ data }: { data: any }) {
|
|||||||
<Stack gap="md">
|
<Stack gap="md">
|
||||||
<Flex align="center" justify="space-between">
|
<Flex align="center" justify="space-between">
|
||||||
<Title order={4} c="gray.2">
|
<Title order={4} c="gray.2">
|
||||||
Histori Pengaduan
|
Riwayat Pengaduan
|
||||||
</Title>
|
</Title>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Divider my={0} />
|
<Divider my={0} />
|
||||||
<Table>
|
<Spoiler
|
||||||
<Table.Thead>
|
maxHeight={200}
|
||||||
<Table.Tr>
|
showLabel="Show more"
|
||||||
<Table.Th>Tanggal</Table.Th>
|
hideLabel="Hide"
|
||||||
<Table.Th>Deskripsi</Table.Th>
|
transitionDuration={1000}
|
||||||
<Table.Th>Status</Table.Th>
|
>
|
||||||
<Table.Th>User</Table.Th>
|
<Table>
|
||||||
</Table.Tr>
|
<Table.Thead>
|
||||||
</Table.Thead>
|
<Table.Tr>
|
||||||
<Table.Tbody>
|
<Table.Th>Tanggal</Table.Th>
|
||||||
{data?.map((item: any) => (
|
<Table.Th>Deskripsi</Table.Th>
|
||||||
<Table.Tr key={item.id}>
|
<Table.Th>Status</Table.Th>
|
||||||
<Table.Td style={{ whiteSpace: "nowrap" }}>
|
<Table.Th>User</Table.Th>
|
||||||
{item.createdAt.toLocaleString("id-ID", {
|
|
||||||
day: "2-digit",
|
|
||||||
month: "short",
|
|
||||||
year: "numeric",
|
|
||||||
hour: "2-digit",
|
|
||||||
minute: "2-digit",
|
|
||||||
hour12: false,
|
|
||||||
})}
|
|
||||||
</Table.Td>
|
|
||||||
<Table.Td>{item.deskripsi}</Table.Td>
|
|
||||||
<Table.Td>{item.status}</Table.Td>
|
|
||||||
<Table.Td style={{ whiteSpace: "nowrap" }}>
|
|
||||||
{item.nameUser ? item.nameUser : "-"}
|
|
||||||
</Table.Td>
|
|
||||||
</Table.Tr>
|
</Table.Tr>
|
||||||
))}
|
</Table.Thead>
|
||||||
</Table.Tbody>
|
<Table.Tbody>
|
||||||
</Table>
|
{data?.map((item: any) => (
|
||||||
|
<Table.Tr key={item.id}>
|
||||||
|
<Table.Td style={{ whiteSpace: "nowrap" }}>
|
||||||
|
{item.createdAt.toLocaleString("id-ID", {
|
||||||
|
day: "2-digit",
|
||||||
|
month: "short",
|
||||||
|
year: "numeric",
|
||||||
|
hour: "2-digit",
|
||||||
|
minute: "2-digit",
|
||||||
|
hour12: false,
|
||||||
|
})}
|
||||||
|
</Table.Td>
|
||||||
|
<Table.Td>{item.deskripsi}</Table.Td>
|
||||||
|
<Table.Td>{item.status}</Table.Td>
|
||||||
|
<Table.Td style={{ whiteSpace: "nowrap" }}>
|
||||||
|
{item.nameUser ? item.nameUser : "-"}
|
||||||
|
</Table.Td>
|
||||||
|
</Table.Tr>
|
||||||
|
))}
|
||||||
|
</Table.Tbody>
|
||||||
|
</Table>
|
||||||
|
</Spoiler>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import BreadCrumbs from "@/components/BreadCrumbs";
|
||||||
import DesaSetting from "@/components/DesaSetting";
|
import DesaSetting from "@/components/DesaSetting";
|
||||||
import KategoriPelayananSurat from "@/components/KategoriPelayananSurat";
|
import KategoriPelayananSurat from "@/components/KategoriPelayananSurat";
|
||||||
import KategoriPengaduan from "@/components/KategoriPengaduan";
|
import KategoriPengaduan from "@/components/KategoriPengaduan";
|
||||||
@@ -15,14 +16,21 @@ import {
|
|||||||
IconUsersGroup,
|
IconUsersGroup,
|
||||||
} from "@tabler/icons-react";
|
} from "@tabler/icons-react";
|
||||||
import type { JsonValue } from "generated/prisma/runtime/library";
|
import type { JsonValue } from "generated/prisma/runtime/library";
|
||||||
|
import _ from "lodash";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useLocation } from "react-router-dom";
|
import { useLocation, useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
export default function DetailSettingPage() {
|
export default function DetailSettingPage() {
|
||||||
const { search } = useLocation();
|
const { search } = useLocation();
|
||||||
const query = new URLSearchParams(search);
|
const query = new URLSearchParams(search);
|
||||||
const type = query.get("type");
|
const type = query.get("type");
|
||||||
|
const navigate = useNavigate();
|
||||||
const [permissions, setPermissions] = useState<JsonValue[]>([]);
|
const [permissions, setPermissions] = useState<JsonValue[]>([]);
|
||||||
|
const dataMenu = [
|
||||||
|
{ title: "Dashboard", link: "/scr/dashboard/dashboard-home", active: false },
|
||||||
|
{ title: "Setting", link: "#", active: false },
|
||||||
|
{ title: type == "cat-pengaduan" ? "Kategori Pengaduan" : type == "cat-pelayanan" ? "Kategori Pelayanan Surat" : type ? _.upperFirst(type) : "Profile", link: "#", active: true },
|
||||||
|
];
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function fetchPermissions() {
|
async function fetchPermissions() {
|
||||||
@@ -87,6 +95,9 @@ export default function DetailSettingPage() {
|
|||||||
return (
|
return (
|
||||||
<Container size="xl" py="xl" w={"100%"}>
|
<Container size="xl" py="xl" w={"100%"}>
|
||||||
<Grid>
|
<Grid>
|
||||||
|
<Grid.Col span={12}>
|
||||||
|
<BreadCrumbs dataLink={dataMenu} back />
|
||||||
|
</Grid.Col>
|
||||||
<Grid.Col span={3}>
|
<Grid.Col span={3}>
|
||||||
<Card
|
<Card
|
||||||
radius="md"
|
radius="md"
|
||||||
@@ -104,7 +115,7 @@ export default function DetailSettingPage() {
|
|||||||
.map((item) => (
|
.map((item) => (
|
||||||
<NavLink
|
<NavLink
|
||||||
key={item.key}
|
key={item.key}
|
||||||
href={"?type=" + item.path}
|
onClick={()=>{navigate("?type=" + item.path)}}
|
||||||
label={item.label}
|
label={item.label}
|
||||||
leftSection={item.icon}
|
leftSection={item.icon}
|
||||||
active={
|
active={
|
||||||
|
|||||||
@@ -1,62 +1,75 @@
|
|||||||
|
import BreadCrumbs from "@/components/BreadCrumbs";
|
||||||
|
import notification from "@/components/notificationGlobal";
|
||||||
import apiFetch from "@/lib/apiFetch";
|
import apiFetch from "@/lib/apiFetch";
|
||||||
import {
|
import {
|
||||||
Avatar,
|
Avatar,
|
||||||
Box,
|
Box,
|
||||||
Button,
|
Button,
|
||||||
Card,
|
Card,
|
||||||
|
CloseButton,
|
||||||
Container,
|
Container,
|
||||||
Divider,
|
Divider,
|
||||||
Flex,
|
Flex,
|
||||||
Grid,
|
Grid,
|
||||||
Group,
|
Group,
|
||||||
|
Input,
|
||||||
LoadingOverlay,
|
LoadingOverlay,
|
||||||
|
Pagination,
|
||||||
Stack,
|
Stack,
|
||||||
Table,
|
Table,
|
||||||
Text,
|
Text,
|
||||||
Title,
|
Title,
|
||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
import { useShallowEffect } from "@mantine/hooks";
|
import { useShallowEffect } from "@mantine/hooks";
|
||||||
import { IconPhone } from "@tabler/icons-react";
|
import { IconPhone, IconSearch } from "@tabler/icons-react";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
|
import { useState } from "react";
|
||||||
import { useLocation, useNavigate } from "react-router-dom";
|
import { useLocation, useNavigate } from "react-router-dom";
|
||||||
import useSwr from "swr";
|
|
||||||
|
|
||||||
export default function DetailWargaPage() {
|
export default function DetailWargaPage() {
|
||||||
|
const dataMenu = [
|
||||||
|
{ title: "Dashboard", link: "/scr/dashboard/dashboard-home", active: false },
|
||||||
|
{ title: "Warga", link: "/scr/dashboard/warga/list-warga", active: false },
|
||||||
|
{ title: "Detail Warga", link: "#", active: true },
|
||||||
|
];
|
||||||
const { search } = useLocation();
|
const { search } = useLocation();
|
||||||
const query = new URLSearchParams(search);
|
const query = new URLSearchParams(search);
|
||||||
const id = query.get("id");
|
const id = query.get("id");
|
||||||
const { data, mutate, isLoading } = useSwr("/", () =>
|
// const { data, mutate, isLoading } = useSwr("/", () =>
|
||||||
apiFetch.api.warga.detail.get({
|
// apiFetch.api.warga.detail.get({
|
||||||
query: {
|
// query: {
|
||||||
id: id!,
|
// id: id!,
|
||||||
},
|
// },
|
||||||
}),
|
// }),
|
||||||
);
|
// );
|
||||||
|
|
||||||
useShallowEffect(() => {
|
// useShallowEffect(() => {
|
||||||
mutate();
|
// mutate();
|
||||||
}, []);
|
// }, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<LoadingOverlay
|
<LoadingOverlay
|
||||||
visible={isLoading}
|
// visible={isLoading}
|
||||||
zIndex={1000}
|
zIndex={1000}
|
||||||
overlayProps={{ radius: "sm", blur: 2 }}
|
overlayProps={{ radius: "sm", blur: 2 }}
|
||||||
/>
|
/>
|
||||||
<Container size="xl" py="xl" w={"100%"}>
|
<Container size="xl" py="xl" w={"100%"}>
|
||||||
<Grid>
|
<Grid>
|
||||||
|
<Grid.Col span={12}>
|
||||||
|
<BreadCrumbs dataLink={dataMenu} back />
|
||||||
|
</Grid.Col>
|
||||||
<Grid.Col span={4}>
|
<Grid.Col span={4}>
|
||||||
<DetailWarga data={data?.data?.warga} />
|
<DetailWarga id={id!} />
|
||||||
</Grid.Col>
|
</Grid.Col>
|
||||||
<Grid.Col span={8}>
|
<Grid.Col span={8}>
|
||||||
<Stack gap={"xl"}>
|
<Stack gap={"xl"}>
|
||||||
<DetailDataHistori
|
<DetailDataHistori
|
||||||
data={data?.data?.pengaduan}
|
id={id!}
|
||||||
kategori="pengaduan"
|
kategori="pengaduan"
|
||||||
/>
|
/>
|
||||||
<DetailDataHistori
|
<DetailDataHistori
|
||||||
data={data?.data?.pelayanan}
|
id={id!}
|
||||||
kategori="pelayanan"
|
kategori="pelayanan"
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -68,13 +81,66 @@ export default function DetailWargaPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function DetailDataHistori({
|
function DetailDataHistori({
|
||||||
data,
|
id,
|
||||||
kategori,
|
kategori,
|
||||||
}: {
|
}: {
|
||||||
data: any;
|
id: string;
|
||||||
kategori: "pengaduan" | "pelayanan";
|
kategori: "pengaduan" | "pelayanan";
|
||||||
}) {
|
}) {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const [data, setData] = useState<any>([]);
|
||||||
|
const [totalPages, setTotalPages] = useState(1);
|
||||||
|
const [totalRows, setTotalRows] = useState(0);
|
||||||
|
const [page, setPage] = useState(1);
|
||||||
|
const [search, setSearch] = useState("");
|
||||||
|
|
||||||
|
async function getData() {
|
||||||
|
try {
|
||||||
|
const res = await apiFetch.api.warga.detail.get({
|
||||||
|
query: {
|
||||||
|
id,
|
||||||
|
category: kategori,
|
||||||
|
page: String(page),
|
||||||
|
search
|
||||||
|
}
|
||||||
|
}) as { data: { success: boolean; data: any[]; totalPages: number, totalRows: number } };
|
||||||
|
|
||||||
|
if (res?.data?.success) {
|
||||||
|
setData(res.data.data)
|
||||||
|
setTotalPages(res?.data?.totalPages)
|
||||||
|
setTotalRows(res?.data?.totalRows)
|
||||||
|
} else {
|
||||||
|
setData([])
|
||||||
|
setTotalPages(1)
|
||||||
|
setTotalRows(0)
|
||||||
|
notification({
|
||||||
|
title: "Failed",
|
||||||
|
message: "Failed to get data",
|
||||||
|
type: "error",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
notification({
|
||||||
|
title: "Failed",
|
||||||
|
message: "Failed to get data",
|
||||||
|
type: "error",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useShallowEffect(() => {
|
||||||
|
getData()
|
||||||
|
}, [page])
|
||||||
|
|
||||||
|
|
||||||
|
useShallowEffect(() => {
|
||||||
|
setPage(1)
|
||||||
|
if (page == 1) {
|
||||||
|
getData()
|
||||||
|
}
|
||||||
|
}, [search]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
@@ -93,6 +159,36 @@ function DetailDataHistori({
|
|||||||
<Title order={4} c="gray.2">
|
<Title order={4} c="gray.2">
|
||||||
Histori {_.upperFirst(kategori)}
|
Histori {_.upperFirst(kategori)}
|
||||||
</Title>
|
</Title>
|
||||||
|
<Flex
|
||||||
|
gap="md"
|
||||||
|
justify="flex-start"
|
||||||
|
align="center"
|
||||||
|
direction="row"
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
value={search}
|
||||||
|
placeholder="Cari data..."
|
||||||
|
onChange={(event) => setSearch(event.currentTarget.value)}
|
||||||
|
leftSection={<IconSearch size={16} />}
|
||||||
|
rightSectionPointerEvents="all"
|
||||||
|
rightSection={
|
||||||
|
<CloseButton
|
||||||
|
aria-label="Clear input"
|
||||||
|
onClick={() => setSearch("")}
|
||||||
|
style={{ display: search ? undefined : "none" }}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Text size="sm" c="gray.5" >
|
||||||
|
{`${5 * (page - 1) + 1} – ${Math.min(totalRows, 5 * page)} of ${totalRows}`}
|
||||||
|
</Text>
|
||||||
|
<Pagination
|
||||||
|
total={totalPages}
|
||||||
|
value={page}
|
||||||
|
onChange={setPage}
|
||||||
|
withPages={false}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Divider my={0} />
|
<Divider my={0} />
|
||||||
<Table>
|
<Table>
|
||||||
@@ -110,7 +206,7 @@ function DetailDataHistori({
|
|||||||
{data?.length > 0 ? (
|
{data?.length > 0 ? (
|
||||||
data?.map((item: any, index: number) => (
|
data?.map((item: any, index: number) => (
|
||||||
<Table.Tr key={index}>
|
<Table.Tr key={index}>
|
||||||
<Table.Td>{item.noPengaduan}</Table.Td>
|
<Table.Td w={"180"}>{item.noPengaduan}</Table.Td>
|
||||||
<Table.Td>
|
<Table.Td>
|
||||||
{kategori == "pengaduan" ? item.title : item.category}
|
{kategori == "pengaduan" ? item.title : item.category}
|
||||||
</Table.Td>
|
</Table.Td>
|
||||||
@@ -121,11 +217,11 @@ function DetailDataHistori({
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
kategori == "pengaduan"
|
kategori == "pengaduan"
|
||||||
? navigate(
|
? navigate(
|
||||||
`/scr/dashboard/pengaduan/detail?id=${item.id}`,
|
`/scr/dashboard/pengaduan/detail?id=${item.id}`,
|
||||||
)
|
)
|
||||||
: navigate(
|
: navigate(
|
||||||
`/scr/dashboard/pelayanan-surat/detail-pelayanan?id=${item.id}`,
|
`/scr/dashboard/pelayanan-surat/detail-pelayanan?id=${item.id}`,
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Detail
|
Detail
|
||||||
@@ -147,7 +243,33 @@ function DetailDataHistori({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function DetailWarga({ data }: { data: any }) {
|
function DetailWarga({ id }: { id: string }) {
|
||||||
|
const [data, setData] = useState<any>(null);
|
||||||
|
|
||||||
|
async function getWarga() {
|
||||||
|
try {
|
||||||
|
const res = await apiFetch.api.warga.detail.get({
|
||||||
|
query: {
|
||||||
|
id: id,
|
||||||
|
category: "warga",
|
||||||
|
page: "1",
|
||||||
|
search: "",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
setData(res.data);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
notification({
|
||||||
|
title: "Failed",
|
||||||
|
message: "Failed to get data warga",
|
||||||
|
type: "error",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useShallowEffect(() => {
|
||||||
|
getWarga();
|
||||||
|
}, []);
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
radius="md"
|
radius="md"
|
||||||
|
|||||||
@@ -8,16 +8,19 @@ export async function createSurat({ idPengajuan, idCategory, idWarga, noSurat }:
|
|||||||
idCategory,
|
idCategory,
|
||||||
idWarga,
|
idWarga,
|
||||||
noSurat,
|
noSurat,
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
id: true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!surat.id) {
|
if (!surat.id) {
|
||||||
return { success: false, message: 'gagal membuat surat' }
|
return { success: false, message: 'gagal membuat surat', idSurat: '' }
|
||||||
}
|
}
|
||||||
|
|
||||||
return { success: true, message: 'surat sudah dibuat' }
|
return { success: true, message: 'surat sudah dibuat', idSurat: surat.id }
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error)
|
console.error(error)
|
||||||
return { success: false, message: 'gagal membuat surat' }
|
return { success: false, message: 'gagal membuat surat' }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -248,14 +248,23 @@ export async function moveFile(config: Config, oldName: string, newName: string)
|
|||||||
return `✏️ Renamed ${oldName} → ${newName}`
|
return `✏️ Renamed ${oldName} → ${newName}`
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function downloadFile(config: Config, remoteFile: string, localFile?: string): Promise<string> {
|
export async function downloadFile(config: Config, fileName: string, folder: string, localFile?: string): Promise<string> {
|
||||||
const localName = localFile || remoteFile;
|
const localName = localFile || fileName;
|
||||||
const downloadUrlResponse = await fetchWithAuth(config, `${config.URL}/${config.REPO}/file/?p=/${remoteFile}`);
|
// 🔹 gabungkan path folder + file
|
||||||
const downloadUrl = (await downloadUrlResponse.text()).replace(/"/g, '');
|
const filePath = `/${folder}/${fileName}`.replace(/\/+/g, "/");
|
||||||
|
|
||||||
|
// 🔹 encode path agar aman (spasi, dll)
|
||||||
|
const params = new URLSearchParams({
|
||||||
|
p: filePath,
|
||||||
|
});
|
||||||
|
|
||||||
|
const downloadUrlResponse = await fetchWithAuth(config, `${config.URL}/${config.REPO}/file/?${params.toString()}`);
|
||||||
|
if(!downloadUrlResponse.ok)
|
||||||
|
return 'gagal'
|
||||||
|
const downloadUrl = (await downloadUrlResponse.text()).replace(/"/g, '');
|
||||||
const buffer = Buffer.from(await (await fetchWithAuth(config, downloadUrl)).arrayBuffer());
|
const buffer = Buffer.from(await (await fetchWithAuth(config, downloadUrl)).arrayBuffer());
|
||||||
await fs.writeFile(localName, buffer);
|
await fs.writeFile(localName, buffer);
|
||||||
return `⬇️ Downloaded ${remoteFile} → ${localName}`
|
return `⬇️ Downloaded ${fileName} → ${localName}`
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getFileLink(config: Config, fileName: string): Promise<string> {
|
export async function getFileLink(config: Config, fileName: string): Promise<string> {
|
||||||
|
|||||||
18
src/server/lib/slug_converter.ts
Normal file
18
src/server/lib/slug_converter.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
export function toSlug(text: string): string {
|
||||||
|
return encodeURIComponent(
|
||||||
|
text
|
||||||
|
.toLowerCase()
|
||||||
|
.trim()
|
||||||
|
.replace(/\s+/g, "-")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fromSlug(slug: string): string {
|
||||||
|
return decodeURIComponent(slug)
|
||||||
|
.replace(/-/g, " ")
|
||||||
|
.replace(/\b\w/g, c => c.toUpperCase());
|
||||||
|
}
|
||||||
|
|
||||||
|
export function capitalizeWords(text: string): string {
|
||||||
|
return text.replace(/\b\w/g, c => c.toUpperCase());
|
||||||
|
}
|
||||||
11
src/server/lib/stringToDate.ts
Normal file
11
src/server/lib/stringToDate.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import dayjs from "dayjs";
|
||||||
|
import customParseFormat from "dayjs/plugin/customParseFormat";
|
||||||
|
import "dayjs/locale/id";
|
||||||
|
dayjs.extend(customParseFormat);
|
||||||
|
|
||||||
|
export function parseTanggalID( value: string ): Date | null {
|
||||||
|
if (!value) return null;
|
||||||
|
|
||||||
|
const parsed = dayjs(value, "DD MMMM YYYY", "id", true);
|
||||||
|
return parsed.isValid() ? parsed.toDate() : null;
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ import { getLastUpdated } from "../lib/get-last-updated"
|
|||||||
import { generateNoPengajuanSurat } from "../lib/no-pengajuan-surat"
|
import { generateNoPengajuanSurat } from "../lib/no-pengajuan-surat"
|
||||||
import { isValidPhone, normalizePhoneNumber } from "../lib/normalizePhone"
|
import { isValidPhone, normalizePhoneNumber } from "../lib/normalizePhone"
|
||||||
import { prisma } from "../lib/prisma"
|
import { prisma } from "../lib/prisma"
|
||||||
|
import { toSlug } from "../lib/slug_converter"
|
||||||
|
|
||||||
|
|
||||||
const PelayananRoute = new Elysia({
|
const PelayananRoute = new Elysia({
|
||||||
@@ -20,13 +21,25 @@ const PelayananRoute = new Elysia({
|
|||||||
},
|
},
|
||||||
orderBy: {
|
orderBy: {
|
||||||
name: "asc"
|
name: "asc"
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
name: true,
|
||||||
|
syaratDokumen: true,
|
||||||
|
dataPelengkap: true,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return data
|
|
||||||
|
const dataFix = data.map(item => ({
|
||||||
|
...item,
|
||||||
|
link: `${process.env.BUN_PUBLIC_BASE_URL}/darmasaba/surat?jenis=${toSlug(item.name)}`
|
||||||
|
}));
|
||||||
|
|
||||||
|
return dataFix
|
||||||
}, {
|
}, {
|
||||||
detail: {
|
detail: {
|
||||||
summary: "List Kategori Pelayanan Surat",
|
summary: "List Kategori Pelayanan Surat",
|
||||||
description: `tool untuk mendapatkan list kategori pelayanan surat beserta syaratnya untuk memenuhi syarat dokumen sesuai kategori yg dipilih saat melakukan pengajuan surat`,
|
description: `tool untuk mendapatkan list kategori pelayanan surat dan juga berisi link form pengajuan surat`,
|
||||||
tags: ["mcp"]
|
tags: ["mcp"]
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -105,12 +118,42 @@ const PelayananRoute = new Elysia({
|
|||||||
.get("/category/detail", async ({ query }) => {
|
.get("/category/detail", async ({ query }) => {
|
||||||
const { id } = query
|
const { id } = query
|
||||||
const data = await prisma.categoryPelayanan.findUnique({
|
const data = await prisma.categoryPelayanan.findUnique({
|
||||||
where:{
|
where: {
|
||||||
id
|
id
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return data
|
if (!data) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dataPelengkap: { key: string }[] = Array.isArray(data.dataPelengkap)
|
||||||
|
? data.dataPelengkap.filter(
|
||||||
|
(v): v is { key: string } =>
|
||||||
|
typeof v === "object" &&
|
||||||
|
v !== null &&
|
||||||
|
"key" in v &&
|
||||||
|
typeof (v as any).key === "string"
|
||||||
|
)
|
||||||
|
: [];
|
||||||
|
|
||||||
|
const syaratDokumen: { name: string }[] = Array.isArray(data.syaratDokumen)
|
||||||
|
? data.syaratDokumen.filter(
|
||||||
|
(v): v is { name: string } =>
|
||||||
|
typeof v === "object" &&
|
||||||
|
v !== null &&
|
||||||
|
"name" in v &&
|
||||||
|
typeof (v as any).name === "string"
|
||||||
|
)
|
||||||
|
: [];
|
||||||
|
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: data.id,
|
||||||
|
name: data.name,
|
||||||
|
dataPelengkap,
|
||||||
|
syaratDokumen,
|
||||||
|
};
|
||||||
}, {
|
}, {
|
||||||
query: t.Object({
|
query: t.Object({
|
||||||
id: t.String({ minLength: 1, error: "id harus diisi" }),
|
id: t.String({ minLength: 1, error: "id harus diisi" }),
|
||||||
@@ -184,8 +227,8 @@ const PelayananRoute = new Elysia({
|
|||||||
CategoryPelayanan: {
|
CategoryPelayanan: {
|
||||||
select: {
|
select: {
|
||||||
name: true,
|
name: true,
|
||||||
dataText: true,
|
|
||||||
syaratDokumen: true,
|
syaratDokumen: true,
|
||||||
|
dataPelengkap: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Warga: {
|
Warga: {
|
||||||
@@ -222,6 +265,7 @@ const PelayananRoute = new Elysia({
|
|||||||
select: {
|
select: {
|
||||||
id: true,
|
id: true,
|
||||||
idCategory: true,
|
idCategory: true,
|
||||||
|
file: true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -251,10 +295,11 @@ const PelayananRoute = new Elysia({
|
|||||||
const syaratDokumen = (data?.CategoryPelayanan?.syaratDokumen ?? []) as {
|
const syaratDokumen = (data?.CategoryPelayanan?.syaratDokumen ?? []) as {
|
||||||
name: string;
|
name: string;
|
||||||
desc: string;
|
desc: string;
|
||||||
|
key: string;
|
||||||
}[];
|
}[];
|
||||||
|
|
||||||
const dataSyaratFix = dataSyarat.map((item) => {
|
const dataSyaratFix = dataSyarat.map((item) => {
|
||||||
const desc = syaratDokumen.find((v) => v.name == item.jenis)?.desc
|
const desc = syaratDokumen.find((v) => v.key == item.jenis)?.desc
|
||||||
return {
|
return {
|
||||||
id: item.id,
|
id: item.id,
|
||||||
jenis: desc,
|
jenis: desc,
|
||||||
@@ -262,14 +307,42 @@ const PelayananRoute = new Elysia({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const dataTextFix = dataText.map((item) => {
|
const dataTextCategory = (data?.CategoryPelayanan?.dataPelengkap ?? []) as {
|
||||||
const desc = data?.CategoryPelayanan?.dataText.find((v) => v == item.jenis)
|
type: string;
|
||||||
return {
|
options?: {
|
||||||
id: item.id,
|
label: string,
|
||||||
jenis: item.jenis,
|
value: string
|
||||||
value: item.value,
|
}[]; name: string;
|
||||||
}
|
desc: string;
|
||||||
})
|
key: string;
|
||||||
|
satuan?: string;
|
||||||
|
}[];
|
||||||
|
|
||||||
|
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
|
||||||
|
return {
|
||||||
|
id: item.id,
|
||||||
|
jenis: nama,
|
||||||
|
key: ref?.key,
|
||||||
|
value: item.value,
|
||||||
|
type: ref?.type ?? "",
|
||||||
|
options: ref?.options ?? [],
|
||||||
|
order: ref?.order ?? Infinity,
|
||||||
|
satuan: ref?.satuan ?? null
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.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: {
|
||||||
@@ -286,6 +359,9 @@ const PelayananRoute = new Elysia({
|
|||||||
name: true,
|
name: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
orderBy: {
|
||||||
|
createdAt: "desc"
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -315,6 +391,7 @@ const PelayananRoute = new Elysia({
|
|||||||
createdAt: data?.createdAt,
|
createdAt: data?.createdAt,
|
||||||
updatedAt: data?.updatedAt,
|
updatedAt: data?.updatedAt,
|
||||||
idSurat: dataSurat?.id,
|
idSurat: dataSurat?.id,
|
||||||
|
fileSurat: dataSurat?.file,
|
||||||
}
|
}
|
||||||
|
|
||||||
const datafix = {
|
const datafix = {
|
||||||
@@ -336,9 +413,9 @@ const PelayananRoute = new Elysia({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.post("/create", async ({ body, headers }) => {
|
.post("/create", async ({ body, headers }) => {
|
||||||
const { kategoriId, dataText, syaratDokumen } = body
|
const { kategoriId, dataPelengkap, syaratDokumen, nama, phone } = body
|
||||||
const namaWarga = headers['x-user'] || ""
|
// const namaWarga = headers['x-user'] || ""
|
||||||
const noTelepon = headers['x-phone'] || ""
|
// const noTelepon = headers['x-phone'] || ""
|
||||||
const noPengajuan = await generateNoPengajuanSurat()
|
const noPengajuan = await generateNoPengajuanSurat()
|
||||||
let idCategoryFix = kategoriId
|
let idCategoryFix = kategoriId
|
||||||
let idWargaFix = ""
|
let idWargaFix = ""
|
||||||
@@ -364,21 +441,21 @@ const PelayananRoute = new Elysia({
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isValidPhone(noTelepon)) {
|
if (!isValidPhone(phone)) {
|
||||||
return { success: false, message: 'nomor telepon tidak valid, harap masukkan nomor yang benar' }
|
return { success: false, message: 'nomor telepon tidak valid, harap masukkan nomor yang benar' }
|
||||||
}
|
}
|
||||||
|
|
||||||
const nomorHP = normalizePhoneNumber({ phone: noTelepon })
|
const nomorHP = normalizePhoneNumber({ phone: phone })
|
||||||
const dataWarga = await prisma.warga.upsert({
|
const dataWarga = await prisma.warga.upsert({
|
||||||
where: {
|
where: {
|
||||||
phone: nomorHP
|
phone: nomorHP
|
||||||
},
|
},
|
||||||
create: {
|
create: {
|
||||||
name: namaWarga,
|
name: nama,
|
||||||
phone: nomorHP,
|
phone: nomorHP,
|
||||||
},
|
},
|
||||||
update: {
|
update: {
|
||||||
name: namaWarga,
|
name: nama,
|
||||||
},
|
},
|
||||||
select: {
|
select: {
|
||||||
id: true
|
id: true
|
||||||
@@ -409,16 +486,16 @@ const PelayananRoute = new Elysia({
|
|||||||
dataInsertSyaratDokumen.push({
|
dataInsertSyaratDokumen.push({
|
||||||
idPengajuanLayanan: pengaduan.id,
|
idPengajuanLayanan: pengaduan.id,
|
||||||
idCategory: idCategoryFix,
|
idCategory: idCategoryFix,
|
||||||
jenis: item.jenis,
|
jenis: item.key,
|
||||||
value: item.value,
|
value: item.value,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const item of dataText) {
|
for (const item of dataPelengkap) {
|
||||||
dataInsertDataText.push({
|
dataInsertDataText.push({
|
||||||
idPengajuanLayanan: pengaduan.id,
|
idPengajuanLayanan: pengaduan.id,
|
||||||
idCategory: idCategoryFix,
|
idCategory: idCategoryFix,
|
||||||
jenis: item.jenis,
|
jenis: item.key,
|
||||||
value: item.value,
|
value: item.value,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -440,7 +517,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({
|
||||||
@@ -448,21 +525,20 @@ const PelayananRoute = new Elysia({
|
|||||||
examples: ["skusaha"],
|
examples: ["skusaha"],
|
||||||
error: "ID kategori harus diisi"
|
error: "ID kategori harus diisi"
|
||||||
}),
|
}),
|
||||||
// namaWarga: t.String({
|
nama: t.String({
|
||||||
// description: "Nama warga",
|
description: "Nama warga",
|
||||||
// examples: ["Budi Santoso"],
|
examples: ["Budi Santoso"],
|
||||||
// error: "Nama warga harus diisi"
|
error: "Nama warga harus diisi"
|
||||||
// }),
|
}),
|
||||||
|
phone: t.String({
|
||||||
|
error: "Nomor telepon harus diisi",
|
||||||
|
examples: ["08123456789", "+628123456789"],
|
||||||
|
description: "Nomor telepon warga pelapor"
|
||||||
|
}),
|
||||||
|
|
||||||
// noTelepon: t.String({
|
dataPelengkap: t.Array(
|
||||||
// error: "Nomor telepon harus diisi",
|
|
||||||
// examples: ["08123456789", "+628123456789"],
|
|
||||||
// description: "Nomor telepon warga pelapor"
|
|
||||||
// }),
|
|
||||||
|
|
||||||
dataText: t.Array(
|
|
||||||
t.Object({
|
t.Object({
|
||||||
jenis: t.String({
|
key: t.String({
|
||||||
description: "Jenis field yang dibutuhkan oleh kategori pelayanan. Biasanya dinamis.",
|
description: "Jenis field yang dibutuhkan oleh kategori pelayanan. Biasanya dinamis.",
|
||||||
examples: ["nama", "jenis kelamin", "tempat tanggal lahir", "negara", "agama", "status perkawinan", "alamat", "pekerjaan", "jenis usaha", "alamat usaha"],
|
examples: ["nama", "jenis kelamin", "tempat tanggal lahir", "negara", "agama", "status perkawinan", "alamat", "pekerjaan", "jenis usaha", "alamat usaha"],
|
||||||
error: "jenis harus diisi"
|
error: "jenis harus diisi"
|
||||||
@@ -477,25 +553,25 @@ const PelayananRoute = new Elysia({
|
|||||||
description: "Kumpulan data text dinamis sesuai kategori layanan.",
|
description: "Kumpulan data text dinamis sesuai kategori layanan.",
|
||||||
examples: [
|
examples: [
|
||||||
[
|
[
|
||||||
{ jenis: "nama", value: "Budi Santoso" },
|
{ key: "nama", value: "Budi Santoso" },
|
||||||
{ jenis: "jenis kelamin", value: "Laki-laki" },
|
{ key: "jenis kelamin", value: "Laki-laki" },
|
||||||
{ jenis: "tempat tanggal lahir", value: "Denpasar, 28 Februari 1990" },
|
{ key: "tempat tanggal lahir", value: "Denpasar, 28 Februari 1990" },
|
||||||
{ jenis: "negara", value: "Indonesia" },
|
{ key: "negara", value: "Indonesia" },
|
||||||
{ jenis: "agama", value: "Islam" },
|
{ key: "agama", value: "Islam" },
|
||||||
{ jenis: "status perkawinan", value: "Belum menikah" },
|
{ key: "status perkawinan", value: "Belum menikah" },
|
||||||
{ jenis: "alamat", value: "Jl. Mawar No. 10" },
|
{ key: "alamat", value: "Jl. Mawar No. 10" },
|
||||||
{ jenis: "pekerjaan", value: "Karyawan Swasta" },
|
{ key: "pekerjaan", value: "Karyawan Swasta" },
|
||||||
{ jenis: "jenis usaha", value: "usaha makanan" },
|
{ key: "jenis usaha", value: "usaha makanan" },
|
||||||
{ jenis: "alamat usaha", value: "Jl. Melati No. 21" },
|
{ key: "alamat usaha", value: "Jl. Melati No. 21" },
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
error: "dataText harus berupa array"
|
error: "Data Pelengkap harus berupa array"
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
|
|
||||||
syaratDokumen: t.Array(
|
syaratDokumen: t.Array(
|
||||||
t.Object({
|
t.Object({
|
||||||
jenis: t.String({
|
key: t.String({
|
||||||
description: "Jenis dokumen persyaratan yang diminta oleh kategori layanan.",
|
description: "Jenis dokumen persyaratan yang diminta oleh kategori layanan.",
|
||||||
examples: ["ktp", "kk", "surat_pengantar_rt"],
|
examples: ["ktp", "kk", "surat_pengantar_rt"],
|
||||||
error: "jenis harus diisi"
|
error: "jenis harus diisi"
|
||||||
@@ -510,26 +586,28 @@ const PelayananRoute = new Elysia({
|
|||||||
description: "Kumpulan dokumen yang wajib diupload sesuai persyaratan layanan.",
|
description: "Kumpulan dokumen yang wajib diupload sesuai persyaratan layanan.",
|
||||||
examples: [
|
examples: [
|
||||||
[
|
[
|
||||||
{ jenis: "pengantar kelian", value: "pengantar_kelurahan_budi.png" },
|
{ key: "pengantar kelian", value: "pengantar_kelurahan_budi.png" },
|
||||||
{ jenis: "ktp/kk", value: "kk_budi.png" },
|
{ key: "ktp/kk", value: "kk_budi.png" },
|
||||||
{ jenis: "foto lokasi", value: "foto_lokasi_budi.png" }
|
{ key: "foto lokasi", value: "foto_lokasi_budi.png" }
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
error: "syaratDokumen harus berupa array"
|
error: "Syarat Dokumen harus berupa array"
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
}),
|
}),
|
||||||
detail: {
|
detail: {
|
||||||
summary: "Buat Pengajuan Pelayanan Surat",
|
summary: "Buat Pengajuan Pelayanan Surat",
|
||||||
description: `tool untuk membuat pengajuan pelayanan surat dengan syarat dokumen serta data text sesuai kategori pelayanan surat yang dipilih`,
|
description: `tool untuk membuat pengajuan pelayanan surat dengan syarat dokumen serta data text sesuai kategori pelayanan surat yang dipilih`,
|
||||||
tags: ["mcp"]
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.post("/detail-data", async ({ body }) => {
|
.post("/detail-data", async ({ body }) => {
|
||||||
const { nomerPengajuan } = body
|
const { nomerPengajuan } = body
|
||||||
const data = await prisma.pelayananAjuan.findFirst({
|
const data = await prisma.pelayananAjuan.findFirst({
|
||||||
where: {
|
where: {
|
||||||
noPengajuan: nomerPengajuan
|
noPengajuan: {
|
||||||
|
equals: nomerPengajuan,
|
||||||
|
mode: "insensitive"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
id: true,
|
||||||
@@ -540,7 +618,7 @@ const PelayananRoute = new Elysia({
|
|||||||
CategoryPelayanan: {
|
CategoryPelayanan: {
|
||||||
select: {
|
select: {
|
||||||
name: true,
|
name: true,
|
||||||
dataText: true,
|
dataPelengkap: true,
|
||||||
syaratDokumen: true,
|
syaratDokumen: true,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -560,7 +638,15 @@ const PelayananRoute = new Elysia({
|
|||||||
})
|
})
|
||||||
|
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return { success: false, message: "Data tidak ditemukan" }
|
return {
|
||||||
|
success: false,
|
||||||
|
message: "Data tidak ditemukan",
|
||||||
|
pengajuan: {},
|
||||||
|
history: [],
|
||||||
|
warga: {},
|
||||||
|
syaratDokumen: [],
|
||||||
|
dataPelengkap: [],
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const dataSurat = await prisma.suratPelayanan.findFirst({
|
const dataSurat = await prisma.suratPelayanan.findFirst({
|
||||||
@@ -586,7 +672,7 @@ const PelayananRoute = new Elysia({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const dataText = await prisma.dataTextPelayanan.findMany({
|
const dataPelengkap = await prisma.dataTextPelayanan.findMany({
|
||||||
where: {
|
where: {
|
||||||
idPengajuanLayanan: data?.id,
|
idPengajuanLayanan: data?.id,
|
||||||
isActive: true
|
isActive: true
|
||||||
@@ -600,25 +686,64 @@ const PelayananRoute = new Elysia({
|
|||||||
const syaratDokumen = (data?.CategoryPelayanan?.syaratDokumen ?? []) as {
|
const syaratDokumen = (data?.CategoryPelayanan?.syaratDokumen ?? []) as {
|
||||||
name: string;
|
name: string;
|
||||||
desc: string;
|
desc: string;
|
||||||
|
key: string;
|
||||||
|
required: boolean;
|
||||||
}[];
|
}[];
|
||||||
|
|
||||||
const dataSyaratFix = dataSyarat.map((item) => {
|
const dataSyaratFix = dataSyarat.map((item) => {
|
||||||
// const desc = syaratDokumen.find((v) => v.name == item.jenis)?.desc
|
const desc = syaratDokumen.find((v) => v.key == item.jenis)?.desc
|
||||||
|
const name = syaratDokumen.find((v) => v.key == item.jenis)?.name
|
||||||
|
const required = syaratDokumen.find((v) => v.key == item.jenis)?.required
|
||||||
return {
|
return {
|
||||||
id: item.id,
|
id: item.id,
|
||||||
jenis: item.jenis,
|
key: item.jenis,
|
||||||
value: item.value,
|
value: item.value,
|
||||||
|
name: name ?? '',
|
||||||
|
desc: desc ?? '',
|
||||||
|
required: required ?? true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const dataTextFix = dataText.map((item) => {
|
const dataPelengkapList = (data?.CategoryPelayanan?.dataPelengkap ?? []) as {
|
||||||
// const desc = data?.CategoryPelayanan?.dataText.find((v) => v == item.jenis)
|
type: string;
|
||||||
return {
|
options?: {
|
||||||
id: item.id,
|
label: string,
|
||||||
jenis: item.jenis,
|
value: string
|
||||||
value: item.value,
|
}[];
|
||||||
}
|
name: string;
|
||||||
})
|
desc: string;
|
||||||
|
key: string;
|
||||||
|
required: boolean;
|
||||||
|
satuan?: string;
|
||||||
|
}[];
|
||||||
|
|
||||||
|
const refMap = new Map(
|
||||||
|
dataPelengkapList.map((v, i) => [
|
||||||
|
v.key,
|
||||||
|
{ ...v, order: i }
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
const dataTextFix = dataPelengkap
|
||||||
|
.map((item) => {
|
||||||
|
const ref = refMap.get(item.jenis);
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: item.id,
|
||||||
|
key: item.jenis,
|
||||||
|
value: item.value,
|
||||||
|
desc: ref?.desc ?? "",
|
||||||
|
name: ref?.name ?? "",
|
||||||
|
type: ref?.type ?? "",
|
||||||
|
options: ref?.options ?? [],
|
||||||
|
order: ref?.order ?? Infinity,
|
||||||
|
required: ref?.required ?? true,
|
||||||
|
satuan: ref?.satuan ?? null
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.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: {
|
||||||
@@ -638,6 +763,19 @@ const PelayananRoute = new Elysia({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const alasanDitolak = await prisma.historyPelayanan.findFirst({
|
||||||
|
where: {
|
||||||
|
idPengajuanLayanan: data?.id,
|
||||||
|
status: "ditolak"
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
keteranganAlasan: true,
|
||||||
|
},
|
||||||
|
orderBy: {
|
||||||
|
createdAt: "desc"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const dataHistoryFix = dataHistory.map((item) => {
|
const dataHistoryFix = dataHistory.map((item) => {
|
||||||
return {
|
return {
|
||||||
id: item.id,
|
id: item.id,
|
||||||
@@ -664,18 +802,20 @@ const PelayananRoute = new Elysia({
|
|||||||
createdAt: data?.createdAt,
|
createdAt: data?.createdAt,
|
||||||
updatedAt: data?.updatedAt,
|
updatedAt: data?.updatedAt,
|
||||||
idSurat: dataSurat?.id,
|
idSurat: dataSurat?.id,
|
||||||
|
alasan: alasanDitolak?.keteranganAlasan,
|
||||||
}
|
}
|
||||||
|
|
||||||
const datafix = {
|
const datafix = {
|
||||||
|
success: true,
|
||||||
|
message: 'sukses',
|
||||||
pengajuan: dataPengajuan,
|
pengajuan: dataPengajuan,
|
||||||
|
linkUpdate: `${process.env.BUN_PUBLIC_BASE_URL}/darmasaba/update-data-surat?pengajuan=${data.noPengajuan}`,
|
||||||
history: dataHistoryFix,
|
history: dataHistoryFix,
|
||||||
warga: warga,
|
warga: warga,
|
||||||
syaratDokumen: dataSyaratFix,
|
syaratDokumen: dataSyaratFix,
|
||||||
dataText: dataTextFix,
|
dataPelengkap: dataTextFix,
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('detail data syarat==', dataSyaratFix)
|
|
||||||
|
|
||||||
return datafix
|
return datafix
|
||||||
|
|
||||||
}, {
|
}, {
|
||||||
@@ -712,9 +852,14 @@ const PelayananRoute = new Elysia({
|
|||||||
})
|
})
|
||||||
|
|
||||||
if (!pengajuan) {
|
if (!pengajuan) {
|
||||||
return { success: false, message: 'gagal update status pengajuan surat' }
|
return { success: false, message: 'gagal update status pengajuan surat', linkUpdate: '', idSurat: '' }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const dataPengajuan = await prisma.pelayananAjuan.findUnique({
|
||||||
|
where: { id: pengajuan.id },
|
||||||
|
select: { noPengajuan: true }
|
||||||
|
});
|
||||||
|
|
||||||
if (status === "diterima") {
|
if (status === "diterima") {
|
||||||
deskripsi = "Pengajuan surat diterima"
|
deskripsi = "Pengajuan surat diterima"
|
||||||
} else if (status === "ditolak") {
|
} else if (status === "ditolak") {
|
||||||
@@ -733,11 +878,19 @@ const PelayananRoute = new Elysia({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
let idSurat = "";
|
||||||
if (status === "selesai") {
|
if (status === "selesai") {
|
||||||
await createSurat({ idPengajuan: pengajuan.id, idCategory: pengajuan.idCategory, idWarga: pengajuan.idWarga, noSurat })
|
const result = await createSurat({ idPengajuan: pengajuan.id, idCategory: pengajuan.idCategory, idWarga: pengajuan.idWarga, noSurat })
|
||||||
|
idSurat = result.idSurat ?? "";
|
||||||
}
|
}
|
||||||
|
|
||||||
return { success: true, message: 'pengajuan surat sudah diperbarui' }
|
return {
|
||||||
|
success: true,
|
||||||
|
message: 'pengajuan surat sudah diperbarui',
|
||||||
|
linkUpdate: status == "ditolak" ? `${process.env.BUN_PUBLIC_BASE_URL}/darmasaba/update-data-surat?pengajuan=${dataPengajuan?.noPengajuan}` : '',
|
||||||
|
idSurat: idSurat,
|
||||||
|
}
|
||||||
}, {
|
}, {
|
||||||
body: t.Object({
|
body: t.Object({
|
||||||
id: t.String({ minLength: 1, error: "id harus diisi" }),
|
id: t.String({ minLength: 1, error: "id harus diisi" }),
|
||||||
@@ -752,51 +905,34 @@ const PelayananRoute = new Elysia({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.post("/update", async ({ body }) => {
|
.post("/update", async ({ body }) => {
|
||||||
const { nomerPengajuan, syaratDokumen, dataText } = body
|
const { id, syaratDokumen, dataPelengkap } = body
|
||||||
let dataUpdate = []
|
let dataUpdate = []
|
||||||
console.log(body)
|
|
||||||
|
|
||||||
const pengajuan = await prisma.pelayananAjuan.findFirst({
|
const pengajuan = await prisma.pelayananAjuan.findUnique({
|
||||||
where: {
|
where: {
|
||||||
noPengajuan: nomerPengajuan,
|
id
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!pengajuan) {
|
if (!pengajuan) {
|
||||||
console.log("data pengajuan surat tidak ditemukan")
|
|
||||||
return { success: false, message: 'data pengajuan surat tidak ditemukan' }
|
return { success: false, message: 'data pengajuan surat tidak ditemukan' }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (pengajuan.status != "ditolak" && pengajuan.status != "antrian") {
|
if (pengajuan.status != "ditolak" && pengajuan.status != "antrian") {
|
||||||
console.log("pengajuan surat tidak dapat diupdate karena status " + pengajuan.status)
|
|
||||||
return { success: false, message: 'pengajuan surat tidak dapat diupdate karena status ' + pengajuan.status }
|
return { success: false, message: 'pengajuan surat tidak dapat diupdate karena status ' + pengajuan.status }
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dataText && dataText.length > 0) {
|
if (dataPelengkap && dataPelengkap.length > 0) {
|
||||||
console.log("dataText")
|
for (const item of dataPelengkap) {
|
||||||
for (const item of dataText) {
|
dataUpdate.push(item.key)
|
||||||
dataUpdate.push(item.jenis)
|
|
||||||
const hasil = await prisma.dataTextPelayanan.findFirst({
|
|
||||||
where: {
|
|
||||||
idPengajuanLayanan: pengajuan.id,
|
|
||||||
jenis: item.jenis,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
|
const upd = await prisma.dataTextPelayanan.update({
|
||||||
const upd = await prisma.dataTextPelayanan.upsert({
|
|
||||||
where: {
|
where: {
|
||||||
id: hasil?.id
|
id: item.id
|
||||||
},
|
},
|
||||||
update: {
|
data: {
|
||||||
value: item.value,
|
value: item.value,
|
||||||
},
|
|
||||||
create: {
|
|
||||||
value: item.value,
|
|
||||||
jenis: item.jenis,
|
|
||||||
idPengajuanLayanan: pengajuan.id,
|
|
||||||
idCategory: pengajuan.idCategory,
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -818,38 +954,16 @@ const PelayananRoute = new Elysia({
|
|||||||
|
|
||||||
|
|
||||||
if (syaratDokumen && syaratDokumen.length > 0) {
|
if (syaratDokumen && syaratDokumen.length > 0) {
|
||||||
console.log("syaratDokumen")
|
|
||||||
for (const item of syaratDokumen) {
|
for (const item of syaratDokumen) {
|
||||||
const pilih = syarat?.find((cat) => cat.desc.toLowerCase() == item.jenis.toLowerCase() || cat.name.toLowerCase() == item.jenis.toLowerCase())?.name;
|
dataUpdate.push(item.key)
|
||||||
console.log(syarat, pilih)
|
const upd = await prisma.syaratDokumenPelayanan.update({
|
||||||
dataUpdate.push(pilih)
|
|
||||||
|
|
||||||
const hasil = await prisma.syaratDokumenPelayanan.findFirst({
|
|
||||||
where: {
|
where: {
|
||||||
idPengajuanLayanan: pengajuan.id,
|
id: item.id
|
||||||
jenis: pilih,
|
},
|
||||||
|
data: {
|
||||||
|
value: item.value,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
console.log(hasil, item)
|
|
||||||
|
|
||||||
if (hasil && hasil.id) {
|
|
||||||
const upd = await prisma.syaratDokumenPelayanan.upsert({
|
|
||||||
where: {
|
|
||||||
id: hasil.id
|
|
||||||
},
|
|
||||||
update: {
|
|
||||||
value: item.value,
|
|
||||||
},
|
|
||||||
create: {
|
|
||||||
value: item.value,
|
|
||||||
jenis: hasil.jenis,
|
|
||||||
idPengajuanLayanan: pengajuan.id,
|
|
||||||
idCategory: pengajuan.idCategory,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
return { success: false, message: 'dokumen tidak dapat diupload' }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -874,22 +988,25 @@ const PelayananRoute = new Elysia({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
console.log("pengajuan surat sudah diperbarui")
|
|
||||||
|
|
||||||
return { success: true, message: 'pengajuan surat sudah diperbarui' }
|
return { success: true, message: 'pengajuan surat sudah diperbarui' }
|
||||||
|
|
||||||
}, {
|
}, {
|
||||||
body: t.Object({
|
body: t.Object({
|
||||||
nomerPengajuan: t.String({
|
id: t.String({
|
||||||
error: "nomer pengajuan harus diisi",
|
error: "id harus diisi",
|
||||||
description: "Nomer pengajuan yang ingin diupdate"
|
description: "ID yang ingin diupdate"
|
||||||
}),
|
}),
|
||||||
dataText: t.Optional(t.Array(
|
dataPelengkap: t.Optional(t.Array(
|
||||||
t.Object({
|
t.Object({
|
||||||
jenis: t.String({
|
id: t.String({
|
||||||
description: "Jenis field yang dibutuhkan oleh kategori pelayanan. Biasanya dinamis.",
|
description: "ID Data Pelengkap.",
|
||||||
|
error: "id harus diisi"
|
||||||
|
}),
|
||||||
|
key: t.String({
|
||||||
|
description: "Key field yang dibutuhkan oleh kategori pelayanan. Biasanya dinamis.",
|
||||||
examples: ["nama", "jenis kelamin", "tempat tanggal lahir", "negara", "agama", "status perkawinan", "alamat", "pekerjaan", "jenis usaha", "alamat usaha"],
|
examples: ["nama", "jenis kelamin", "tempat tanggal lahir", "negara", "agama", "status perkawinan", "alamat", "pekerjaan", "jenis usaha", "alamat usaha"],
|
||||||
error: "jenis harus diisi"
|
error: "key harus diisi"
|
||||||
}),
|
}),
|
||||||
value: t.String({
|
value: t.String({
|
||||||
description: "Isi atau nilai dari jenis field terkait.",
|
description: "Isi atau nilai dari jenis field terkait.",
|
||||||
@@ -901,26 +1018,30 @@ const PelayananRoute = new Elysia({
|
|||||||
description: "Kumpulan data text dinamis sesuai kategori layanan.",
|
description: "Kumpulan data text dinamis sesuai kategori layanan.",
|
||||||
examples: [
|
examples: [
|
||||||
[
|
[
|
||||||
{ jenis: "nama", value: "Budi Santoso" },
|
{ id: "1", key: "nama", value: "Budi Santoso" },
|
||||||
{ jenis: "jenis kelamin", value: "Laki-laki" },
|
{ id: "2", key: "jenis kelamin", value: "Laki-laki" },
|
||||||
{ jenis: "tempat tanggal lahir", value: "Denpasar, 28 Februari 1990" },
|
{ id: "3", key: "tempat tanggal lahir", value: "Denpasar, 28 Februari 1990" },
|
||||||
{ jenis: "negara", value: "Indonesia" },
|
{ id: "4", key: "negara", value: "Indonesia" },
|
||||||
{ jenis: "agama", value: "Islam" },
|
{ id: "5", key: "agama", value: "Islam" },
|
||||||
{ jenis: "status perkawinan", value: "Belum menikah" },
|
{ id: "6", key: "status perkawinan", value: "Belum menikah" },
|
||||||
{ jenis: "alamat", value: "Jl. Mawar No. 10" },
|
{ id: "7", key: "alamat", value: "Jl. Mawar No. 10" },
|
||||||
{ jenis: "pekerjaan", value: "Karyawan Swasta" },
|
{ id: "8", key: "pekerjaan", value: "Karyawan Swasta" },
|
||||||
{ jenis: "jenis usaha", value: "usaha makanan" },
|
{ id: "9", key: "jenis usaha", value: "usaha makanan" },
|
||||||
{ jenis: "alamat usaha", value: "Jl. Melati No. 21" },
|
{ id: "10", key: "alamat usaha", value: "Jl. Melati No. 21" },
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
)),
|
)),
|
||||||
syaratDokumen: t.Optional(t.Array(
|
syaratDokumen: t.Optional(t.Array(
|
||||||
t.Object({
|
t.Object({
|
||||||
jenis: t.String({
|
id: t.String({
|
||||||
description: "Jenis dokumen persyaratan yang diminta oleh kategori layanan.",
|
description: "ID syarat dokumen",
|
||||||
|
error: "id harus diisi"
|
||||||
|
}),
|
||||||
|
key: t.String({
|
||||||
|
description: "Key dokumen persyaratan yang diminta oleh kategori layanan.",
|
||||||
examples: ["ktp", "kk", "surat_pengantar_rt"],
|
examples: ["ktp", "kk", "surat_pengantar_rt"],
|
||||||
error: "jenis harus diisi"
|
error: "key harus diisi"
|
||||||
}),
|
}),
|
||||||
value: t.String({
|
value: t.String({
|
||||||
description: "Nama file atau identifier file dokumen yang diupload.",
|
description: "Nama file atau identifier file dokumen yang diupload.",
|
||||||
@@ -932,9 +1053,9 @@ const PelayananRoute = new Elysia({
|
|||||||
description: "Kumpulan dokumen yang wajib diupload sesuai persyaratan layanan.",
|
description: "Kumpulan dokumen yang wajib diupload sesuai persyaratan layanan.",
|
||||||
examples: [
|
examples: [
|
||||||
[
|
[
|
||||||
{ jenis: "pengantar kelian", value: "pengantar_kelurahan_budi.png" },
|
{ id: "1", key: "pengantar kelian", value: "pengantar_kelurahan_budi.png" },
|
||||||
{ jenis: "ktp/kk", value: "kk_budi.png" },
|
{ id: "2", key: "ktp/kk", value: "kk_budi.png" },
|
||||||
{ jenis: "foto lokasi", value: "foto_lokasi_budi.png" }
|
{ id: "3", key: "foto lokasi", value: "foto_lokasi_budi.png" }
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
@@ -943,7 +1064,73 @@ const PelayananRoute = new Elysia({
|
|||||||
detail: {
|
detail: {
|
||||||
summary: "Update Data Pengajuan Pelayanan Surat",
|
summary: "Update Data Pengajuan Pelayanan Surat",
|
||||||
description: `tool untuk update data pengajuan pelayanan surat`,
|
description: `tool untuk update data pengajuan pelayanan surat`,
|
||||||
tags: ["mcp"]
|
}
|
||||||
|
})
|
||||||
|
.post("/update-data-pelengkap", async ({ body }) => {
|
||||||
|
const { id, value, jenis, idUser } = body
|
||||||
|
|
||||||
|
const dataPelengkap = await prisma.dataTextPelayanan.findUnique({
|
||||||
|
where: {
|
||||||
|
id
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
idPengajuanLayanan: true,
|
||||||
|
PelayananAjuan: {
|
||||||
|
select: {
|
||||||
|
status: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!dataPelengkap) {
|
||||||
|
return { success: false, message: 'data pelengkap surat tidak ditemukan' }
|
||||||
|
}
|
||||||
|
|
||||||
|
const upd = await prisma.dataTextPelayanan.update({
|
||||||
|
where: {
|
||||||
|
id
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
value: value,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const history = await prisma.historyPelayanan.create({
|
||||||
|
data: {
|
||||||
|
idPengajuanLayanan: dataPelengkap.idPengajuanLayanan,
|
||||||
|
deskripsi: `Pengajuan surat diupdate oleh user (data yg diupdate: ${jenis})`,
|
||||||
|
status: dataPelengkap.PelayananAjuan.status,
|
||||||
|
idUser
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
return { success: true, message: 'data pelengkap surat sudah diperbarui' }
|
||||||
|
|
||||||
|
}, {
|
||||||
|
body: t.Object({
|
||||||
|
id: t.String({
|
||||||
|
error: "id harus diisi",
|
||||||
|
description: "ID yang ingin diupdate"
|
||||||
|
}),
|
||||||
|
value: t.String({
|
||||||
|
error: "value harus diisi",
|
||||||
|
description: "Value yang ingin diupdate"
|
||||||
|
}),
|
||||||
|
jenis: t.String({
|
||||||
|
error: "jenis harus diisi",
|
||||||
|
description: "Jenis data yang ingin diupdate"
|
||||||
|
}),
|
||||||
|
idUser: t.String({
|
||||||
|
error: "idUser harus diisi",
|
||||||
|
description: "ID user yang melakukan update"
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
detail: {
|
||||||
|
summary: "Update Data Pelengkap Pengajuan Pelayanan Surat oleh user admin",
|
||||||
|
description: `tool untuk update data pelengkap pengajuan pelayanan surat oleh user admin`,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.get("/list", async ({ query }) => {
|
.get("/list", async ({ query }) => {
|
||||||
@@ -1090,5 +1277,47 @@ const PelayananRoute = new Elysia({
|
|||||||
description: `tool untuk mendapatkan jumlah pengajuan pelayanan surat warga`,
|
description: `tool untuk mendapatkan jumlah pengajuan pelayanan surat warga`,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
.post("/get-no-pengajuan", async ({ body }) => {
|
||||||
|
const { phone, noPengajuan } = body;
|
||||||
|
|
||||||
|
if (!isValidPhone(phone)) {
|
||||||
|
return { success: false, message: 'nomor telepon tidak valid, harap masukkan nomor yang benar' }
|
||||||
|
}
|
||||||
|
|
||||||
|
const nomorHP = normalizePhoneNumber({ phone: phone })
|
||||||
|
const data = await prisma.pelayananAjuan.findMany({
|
||||||
|
where: {
|
||||||
|
noPengajuan: noPengajuan,
|
||||||
|
Warga: {
|
||||||
|
phone: nomorHP
|
||||||
|
}
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
noPengajuan: true,
|
||||||
|
status: true,
|
||||||
|
createdAt: true,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (data.length == 0) {
|
||||||
|
return { success: false, message: 'Data pengajuan tidak ditemukan' };
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
nomer: noPengajuan
|
||||||
|
};
|
||||||
|
|
||||||
|
}, {
|
||||||
|
body: t.Object({
|
||||||
|
phone: t.String({ minLength: 1, error: "Nomor telepon harus diisi" }),
|
||||||
|
noPengajuan: t.String({ minLength: 1, error: "Nomor pengajuan harus diisi" }),
|
||||||
|
}),
|
||||||
|
detail: {
|
||||||
|
summary: "Cek Nomor Pengajuan Surat",
|
||||||
|
description: `tool untuk memeriksa apakah nomor pengajuan surat valid dan terkait dengan nomor telepon warga. Jika valid, mengembalikan nomor pengajuan.`,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
export default PelayananRoute
|
export default PelayananRoute
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
import Elysia, { t } from "elysia"
|
import Elysia, { t } from "elysia";
|
||||||
import type { StatusPengaduan } from "generated/prisma"
|
import fs from 'fs';
|
||||||
import _ from "lodash"
|
import type { StatusPengaduan } from "generated/prisma";
|
||||||
import { v4 as uuidv4 } from "uuid"
|
import _ from "lodash";
|
||||||
import { getLastUpdated } from "../lib/get-last-updated"
|
import path from "path";
|
||||||
import { mimeToExtension } from "../lib/mimetypeToExtension"
|
import { v4 as uuidv4 } from "uuid";
|
||||||
import { generateNoPengaduan } from "../lib/no-pengaduan"
|
import { getLastUpdated } from "../lib/get-last-updated";
|
||||||
import { isValidPhone, normalizePhoneNumber } from "../lib/normalizePhone"
|
import { mimeToExtension } from "../lib/mimetypeToExtension";
|
||||||
import { prisma } from "../lib/prisma"
|
import { generateNoPengaduan } from "../lib/no-pengaduan";
|
||||||
import { renameFile } from "../lib/rename-file"
|
import { isValidPhone, normalizePhoneNumber } from "../lib/normalizePhone";
|
||||||
import { catFile, defaultConfigSF, removeFile, uploadFile, uploadFileToFolder } from "../lib/seafile"
|
import { prisma } from "../lib/prisma";
|
||||||
|
import { renameFile } from "../lib/rename-file";
|
||||||
|
import { catFile, defaultConfigSF, downloadFile, removeFile, uploadFile, uploadFileToFolder } from "../lib/seafile";
|
||||||
|
|
||||||
const PengaduanRoute = new Elysia({
|
const PengaduanRoute = new Elysia({
|
||||||
prefix: "pengaduan",
|
prefix: "pengaduan",
|
||||||
@@ -415,7 +417,7 @@ const PengaduanRoute = new Elysia({
|
|||||||
const datafix = {
|
const datafix = {
|
||||||
pengaduan: {},
|
pengaduan: {},
|
||||||
history: [],
|
history: [],
|
||||||
warga: {},
|
warga: null,
|
||||||
}
|
}
|
||||||
|
|
||||||
return datafix
|
return datafix
|
||||||
@@ -436,6 +438,9 @@ const PengaduanRoute = new Elysia({
|
|||||||
name: true,
|
name: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
orderBy: {
|
||||||
|
createdAt: "desc"
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -602,6 +607,43 @@ const PengaduanRoute = new Elysia({
|
|||||||
consumes: ["multipart/form-data"]
|
consumes: ["multipart/form-data"]
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
.get("/download", async ({ query, set }) => {
|
||||||
|
const { file, folder } = query;
|
||||||
|
|
||||||
|
// Validasi file
|
||||||
|
if (!file) {
|
||||||
|
return { success: false, message: "File tidak ditemukan" };
|
||||||
|
}
|
||||||
|
|
||||||
|
// if (!folder) {
|
||||||
|
// return { success: false, message: "Folder tidak ditemukan" };
|
||||||
|
// }
|
||||||
|
|
||||||
|
const localPath = path.join("/tmp", file);
|
||||||
|
|
||||||
|
// Upload ke Seafile (pastikan uploadFile menerima Blob atau ArrayBuffer)
|
||||||
|
// const buffer = await file.arrayBuffer();
|
||||||
|
const result = await downloadFile(defaultConfigSF, file, 'surat', localPath);
|
||||||
|
|
||||||
|
if(result=="gagal") {
|
||||||
|
return { success: false, message: "Download gagal" };
|
||||||
|
}
|
||||||
|
|
||||||
|
set.headers["Content-Type"] = "application/pdf";
|
||||||
|
set.headers["Content-Disposition"] = `attachment; filename="${file}"`;
|
||||||
|
|
||||||
|
// 🔹 kirim file ke browser
|
||||||
|
return fs.createReadStream(localPath);
|
||||||
|
}, {
|
||||||
|
body: t.Object({
|
||||||
|
file: t.Any(),
|
||||||
|
folder: t.String(),
|
||||||
|
}),
|
||||||
|
detail: {
|
||||||
|
summary: "Download Surat",
|
||||||
|
description: "Tool untuk download surat dari Seafile",
|
||||||
|
},
|
||||||
|
})
|
||||||
.post("/upload-file-form-data", async ({ body }) => {
|
.post("/upload-file-form-data", async ({ body }) => {
|
||||||
const { file } = body;
|
const { file } = body;
|
||||||
|
|
||||||
|
|||||||
153
src/server/routes/send_wa_route.ts
Normal file
153
src/server/routes/send_wa_route.ts
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
import Elysia, { t } from "elysia";
|
||||||
|
|
||||||
|
const SendWaRoute = new Elysia({
|
||||||
|
prefix: "send-wa",
|
||||||
|
tags: ["send-wa"],
|
||||||
|
})
|
||||||
|
|
||||||
|
// --- KATEGORI PENGADUAN ---
|
||||||
|
.post("/pengaduan", async ({ body }) => {
|
||||||
|
const { noPengaduan, judulPengaduan, status, alasan, tlp } = body
|
||||||
|
|
||||||
|
let text = ""
|
||||||
|
|
||||||
|
if (status === "ditolak") {
|
||||||
|
text = `Pemberitahuan Aduan
|
||||||
|
|
||||||
|
Aduan dengan Nomor Pengaduan: ${noPengaduan}
|
||||||
|
Judul Pengaduan: ${judulPengaduan}
|
||||||
|
Kami informasikan bahwa aduan tersebut tidak dapat ditindaklanjuti (ditolak).
|
||||||
|
Alasan penolakan:${alasan}
|
||||||
|
|
||||||
|
Terima kasih atas pengertian Bapak/Ibu.`
|
||||||
|
} else if (status == "diterima") {
|
||||||
|
text = `Pemberitahuan Aduan
|
||||||
|
|
||||||
|
Aduan dengan Nomor Pengaduan: ${noPengaduan}
|
||||||
|
Judul Pengaduan: ${judulPengaduan}
|
||||||
|
Telah kami terima dan akan segera diproses sesuai ketentuan yang berlaku.
|
||||||
|
|
||||||
|
Terima kasih atas laporan Bapak/Ibu.`
|
||||||
|
} else if (status == "dikerjakan") {
|
||||||
|
text = `Pemberitahuan Aduan
|
||||||
|
|
||||||
|
Aduan dengan Nomor Pengaduan: ${noPengaduan}
|
||||||
|
Judul Pengaduan: ${judulPengaduan}
|
||||||
|
Saat ini sedang dalam proses penanganan oleh petugas terkait.
|
||||||
|
|
||||||
|
Mohon menunggu informasi selanjutnya.`
|
||||||
|
} else if (status == "selesai") {
|
||||||
|
text = `Pemberitahuan Aduan
|
||||||
|
|
||||||
|
Aduan dengan Nomor Pengaduan: ${noPengaduan}
|
||||||
|
Judul Pengaduan: ${judulPengaduan}
|
||||||
|
Telah selesai ditindaklanjuti.
|
||||||
|
|
||||||
|
Terima kasih atas partisipasi dan kepercayaan Bapak/Ibu.`
|
||||||
|
}
|
||||||
|
|
||||||
|
const textFix = encodeURIComponent(text)
|
||||||
|
|
||||||
|
const res = await fetch(
|
||||||
|
`https://cld-dkr-prod-wajs-server.wibudev.com/api/wa/code?nom=${tlp}&text=${textFix}`,
|
||||||
|
{
|
||||||
|
cache: "no-cache",
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${process.env.WA_SERVER_TOKEN}`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (res.status !== 200)
|
||||||
|
return { success: false, message: "Nomor Whatsapp Tidak Aktif" }
|
||||||
|
|
||||||
|
|
||||||
|
return { success: true, message: 'Pemberitahuan berhasil dikirim ke warga' }
|
||||||
|
}, {
|
||||||
|
body: t.Object({
|
||||||
|
noPengaduan: t.String({ minLength: 1, error: "nomer pengaduan harus diisi" }),
|
||||||
|
judulPengaduan: t.String({ minLength: 1, error: "judul pengaduan harus diisi" }),
|
||||||
|
status: t.String({ minLength: 1, error: "status harus diisi" }),
|
||||||
|
alasan: t.String({ optional: true }),
|
||||||
|
tlp: t.String({ minLength: 1, error: "nomor telepon harus diisi" }),
|
||||||
|
}),
|
||||||
|
detail: {
|
||||||
|
summary: "Send pemberitahuan pengaduan lewat WA",
|
||||||
|
description: `tool untuk send pemberitahuan pengaduan lewat WA`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.post("/pengajuan-surat", async ({ body }) => {
|
||||||
|
const { noPengajuan, jenisSurat, status, alasan, tlp, linkSurat, linkUpdate } = body
|
||||||
|
|
||||||
|
let text = ""
|
||||||
|
|
||||||
|
if (status === "ditolak") {
|
||||||
|
text = `Pemberitahuan Pengajuan Surat
|
||||||
|
|
||||||
|
Nomor Pengajuan: ${noPengajuan}
|
||||||
|
Surat: ${jenisSurat}
|
||||||
|
Kami informasikan bahwa pengajuan surat tersebut tidak dapat diproses (ditolak).
|
||||||
|
Alasan penolakan: ${alasan}
|
||||||
|
|
||||||
|
Bapak/Ibu dapat melakukan perbaikan atau pembaruan data melalui tautan berikut:
|
||||||
|
👉 ${linkUpdate}
|
||||||
|
Setelah data diperbarui, pengajuan akan diproses kembali sesuai ketentuan yang berlaku.
|
||||||
|
|
||||||
|
Terima kasih atas pengertian Bapak/Ibu.`
|
||||||
|
} else if (status == "diterima") {
|
||||||
|
text = `Pemberitahuan Pengajuan Surat
|
||||||
|
|
||||||
|
Nomor Pengajuan: ${noPengajuan}
|
||||||
|
Surat: ${jenisSurat}
|
||||||
|
Kami informasikan bahwa pengajuan surat yang Bapak/Ibu ajukan telah kami terima dan sedang menunggu proses verifikasi serta penanganan lebih lanjut.
|
||||||
|
|
||||||
|
Terima kasih atas kesabaran Bapak/Ibu.`
|
||||||
|
} else if (status == "selesai") {
|
||||||
|
text = `Pemberitahuan Pengajuan Surat
|
||||||
|
|
||||||
|
Nomor Pengajuan: ${noPengajuan}
|
||||||
|
Surat: ${jenisSurat}
|
||||||
|
Kami informasikan bahwa pengajuan surat tersebut telah selesai diproses.
|
||||||
|
|
||||||
|
Bapak/Ibu dapat mengunduh surat melalui tautan berikut:
|
||||||
|
👉 ${linkSurat}
|
||||||
|
|
||||||
|
Terima kasih atas kepercayaan Bapak/Ibu.`
|
||||||
|
}
|
||||||
|
|
||||||
|
const textFix = encodeURIComponent(text)
|
||||||
|
|
||||||
|
|
||||||
|
const res = await fetch(
|
||||||
|
`https://cld-dkr-prod-wajs-server.wibudev.com/api/wa/code?nom=${tlp}&text=${textFix}`,
|
||||||
|
{
|
||||||
|
cache: "no-cache",
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${process.env.WA_SERVER_TOKEN}`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (res.status !== 200)
|
||||||
|
return { success: false, message: "Nomor Whatsapp Tidak Aktif" }
|
||||||
|
|
||||||
|
|
||||||
|
return { success: true, message: 'Pemberitahuan berhasil dikirim ke warga' }
|
||||||
|
}, {
|
||||||
|
body: t.Object({
|
||||||
|
noPengajuan: t.String({ minLength: 1, error: "nomer pengajuan harus diisi" }),
|
||||||
|
jenisSurat: t.String({ minLength: 1, error: "jenis surat harus diisi" }),
|
||||||
|
status: t.String({ minLength: 1, error: "status harus diisi" }),
|
||||||
|
alasan: t.String({ optional: true }),
|
||||||
|
linkSurat: t.String({ optional: true }),
|
||||||
|
linkUpdate: t.String({ optional: true }),
|
||||||
|
tlp: t.String({ minLength: 1, error: "nomor telepon harus diisi" }),
|
||||||
|
}),
|
||||||
|
detail: {
|
||||||
|
summary: "Send pemberitahuan pengajuan surat lewat WA",
|
||||||
|
description: `tool untuk send pemberitahuan pengajuan surat lewat WA`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
;
|
||||||
|
|
||||||
|
export default SendWaRoute
|
||||||
@@ -17,6 +17,7 @@ const SuratRoute = new Elysia({
|
|||||||
noSurat: true,
|
noSurat: true,
|
||||||
idCategory: true,
|
idCategory: true,
|
||||||
createdAt: true,
|
createdAt: true,
|
||||||
|
file: true,
|
||||||
PelayananAjuan: {
|
PelayananAjuan: {
|
||||||
select: {
|
select: {
|
||||||
DataTextPelayanan: true,
|
DataTextPelayanan: true,
|
||||||
@@ -44,6 +45,7 @@ const SuratRoute = new Elysia({
|
|||||||
idCategory: dataSurat?.idCategory,
|
idCategory: dataSurat?.idCategory,
|
||||||
nameCategory: dataSurat?.CategoryPelayanan?.name,
|
nameCategory: dataSurat?.CategoryPelayanan?.name,
|
||||||
noSurat: dataSurat?.noSurat,
|
noSurat: dataSurat?.noSurat,
|
||||||
|
file: dataSurat?.file,
|
||||||
dataText: dataSurat?.PelayananAjuan?.DataTextPelayanan,
|
dataText: dataSurat?.PelayananAjuan?.DataTextPelayanan,
|
||||||
createdAt: dataSurat?.createdAt.toLocaleDateString("id-ID", { day: "numeric", month: "long", year: "numeric" }),
|
createdAt: dataSurat?.createdAt.toLocaleDateString("id-ID", { day: "numeric", month: "long", year: "numeric" }),
|
||||||
},
|
},
|
||||||
@@ -60,6 +62,33 @@ const SuratRoute = new Elysia({
|
|||||||
}
|
}
|
||||||
|
|
||||||
})
|
})
|
||||||
|
.post("/update", async ({ body }) => {
|
||||||
|
const { id, filename } = body
|
||||||
|
|
||||||
|
await prisma.suratPelayanan.update({
|
||||||
|
where: {
|
||||||
|
id,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
file: filename,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: 'surat sudah diperbarui',
|
||||||
|
link: `${process.env.BUN_PUBLIC_BASE_URL}/api/pengaduan/download?file=${filename}`
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
body: t.Object({
|
||||||
|
id: t.String({ minLength: 1, error: "id harus diisi" }),
|
||||||
|
filename: t.String({ minLength: 1, error: "filename harus diisi" }),
|
||||||
|
}),
|
||||||
|
detail: {
|
||||||
|
summary: "update file surat",
|
||||||
|
description: `tool untuk update file surat`
|
||||||
|
}
|
||||||
|
})
|
||||||
;
|
;
|
||||||
|
|
||||||
export default SuratRoute
|
export default SuratRoute
|
||||||
|
|||||||
@@ -97,68 +97,137 @@ const WargaRoute = new Elysia({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.get("/detail", async ({ query }) => {
|
.get("/detail", async ({ query }) => {
|
||||||
const { id } = query
|
const { id, category, search, page } = query
|
||||||
|
const skip = !page ? 0 : (Number(page) - 1) * 5
|
||||||
|
|
||||||
const dataWarga = await prisma.warga.findUnique({
|
const dataWarga = await prisma.warga.findUnique({
|
||||||
where: {
|
where: {
|
||||||
id
|
id
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const dataPengaduan = await prisma.pengaduan.findMany({
|
if (!dataWarga)
|
||||||
orderBy: {
|
return { success: false, message: "data warga tidak ditemukan", data: null, totalPages: 1, totalRows: 0 }
|
||||||
createdAt: "desc"
|
|
||||||
},
|
if (category == "warga") {
|
||||||
where: {
|
return dataWarga
|
||||||
|
} else if (category == "pengaduan") {
|
||||||
|
const where: any = {
|
||||||
isActive: true,
|
isActive: true,
|
||||||
idWarga: id
|
idWarga: id,
|
||||||
},
|
OR: [
|
||||||
select: {
|
{
|
||||||
id: true,
|
title: {
|
||||||
status: true,
|
contains: search ?? "",
|
||||||
noPengaduan: true,
|
mode: "insensitive"
|
||||||
title: true
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
noPengaduan: {
|
||||||
|
contains: search ?? "",
|
||||||
|
mode: "insensitive"
|
||||||
|
},
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
})
|
|
||||||
|
|
||||||
|
const totalData = await prisma.pengaduan.count({
|
||||||
|
where
|
||||||
|
});
|
||||||
|
const dataPengaduan = await prisma.pengaduan.findMany({
|
||||||
|
skip,
|
||||||
|
take: 5,
|
||||||
|
orderBy: {
|
||||||
|
createdAt: "desc"
|
||||||
|
},
|
||||||
|
where,
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
status: true,
|
||||||
|
noPengaduan: true,
|
||||||
|
title: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const dataReturn = {
|
||||||
|
success: true,
|
||||||
|
message: "data pengaduan berhasil diambil",
|
||||||
|
data: dataPengaduan,
|
||||||
|
totalRows: totalData,
|
||||||
|
totalPages: Math.ceil(totalData / 5)
|
||||||
|
}
|
||||||
|
|
||||||
const dataPelayanan = await prisma.pelayananAjuan.findMany({
|
return dataReturn
|
||||||
orderBy: {
|
} else if (category == "pelayanan") {
|
||||||
createdAt: "desc"
|
const where: any = {
|
||||||
},
|
|
||||||
where: {
|
|
||||||
isActive: true,
|
isActive: true,
|
||||||
idWarga: id
|
idWarga: id,
|
||||||
},
|
OR: [
|
||||||
select: {
|
{
|
||||||
id: true,
|
CategoryPelayanan: {
|
||||||
noPengajuan: true,
|
name: {
|
||||||
status: true,
|
contains: search ?? "",
|
||||||
CategoryPelayanan: {
|
mode: "insensitive"
|
||||||
select: {
|
},
|
||||||
name: true
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
noPengajuan: {
|
||||||
|
contains: search ?? "",
|
||||||
|
mode: "insensitive"
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
const totalData = await prisma.pelayananAjuan.count({
|
||||||
|
where
|
||||||
|
});
|
||||||
|
|
||||||
|
const dataPelayanan = await prisma.pelayananAjuan.findMany({
|
||||||
|
skip,
|
||||||
|
take: 5,
|
||||||
|
orderBy: {
|
||||||
|
createdAt: "desc"
|
||||||
|
},
|
||||||
|
where,
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
noPengajuan: true,
|
||||||
|
status: true,
|
||||||
|
CategoryPelayanan: {
|
||||||
|
select: {
|
||||||
|
name: true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const dataPelayanFix = dataPelayanan.map((v: any) => ({
|
||||||
|
..._.omit(v, ["CategoryPelayanan"]),
|
||||||
|
id: v.id,
|
||||||
|
noPengaduan: v.noPengajuan,
|
||||||
|
status: v.status,
|
||||||
|
category: v.CategoryPelayanan.name
|
||||||
|
}))
|
||||||
|
|
||||||
|
const dataReturn = {
|
||||||
|
success: true,
|
||||||
|
message: "data pelayanan berhasil diambil",
|
||||||
|
data: dataPelayanFix,
|
||||||
|
totalRows: totalData,
|
||||||
|
totalPages: Math.ceil(totalData / 5)
|
||||||
}
|
}
|
||||||
})
|
|
||||||
|
|
||||||
const dataPelayanFix = dataPelayanan.map((v: any) => ({
|
return dataReturn
|
||||||
..._.omit(v, ["CategoryPelayanan"]),
|
|
||||||
id: v.id,
|
|
||||||
noPengaduan: v.noPengajuan,
|
|
||||||
status: v.status,
|
|
||||||
category: v.CategoryPelayanan.name
|
|
||||||
}))
|
|
||||||
|
|
||||||
return {
|
|
||||||
warga: dataWarga,
|
|
||||||
pengaduan: dataPengaduan,
|
|
||||||
pelayanan: dataPelayanFix
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}, {
|
}, {
|
||||||
query: t.Object({
|
query: t.Object({
|
||||||
id: t.String({ minLength: 1, error: "id harus diisi" })
|
id: t.String({ minLength: 1, error: "id harus diisi" }),
|
||||||
|
category: t.String({ minLength: 1, error: "kategori harus diisi" }),
|
||||||
|
page: t.String({ optional: true }),
|
||||||
|
search: t.String({ optional: true }),
|
||||||
}),
|
}),
|
||||||
detail: {
|
detail: {
|
||||||
summary: "Detail Warga",
|
summary: "Detail Warga",
|
||||||
|
|||||||
Reference in New Issue
Block a user