diff --git a/src/AppRoutes.tsx b/src/AppRoutes.tsx index f0b024d..decc2be 100644 --- a/src/AppRoutes.tsx +++ b/src/AppRoutes.tsx @@ -14,6 +14,7 @@ import FormSuratKeteranganBelumKawin from "./pages/darmasaba/form_surat_keterang import FormKeteranganKelahiran from "./pages/darmasaba/form_keterangan_kelahiran"; import FormSuratKeteranganTempatUsaha from "./pages/darmasaba/form_surat_keterangan_tempat_usaha"; import FormSuratKeteranganKelakuanBaik from "./pages/darmasaba/form_surat_keterangan_kelakuan_baik"; +import Surat from "./pages/darmasaba/surat"; import Home from "./pages/Home"; import CredentialPage from "./pages/scr/dashboard/credential/credential_page"; import DashboardHome from "./pages/scr/dashboard/dashboard_home"; @@ -84,6 +85,7 @@ export default function AppRoutes() { path="/darmasaba/surat-keterangan-kelakuan-baik" element={} /> + } /> } /> diff --git a/src/clientRoutes.ts b/src/clientRoutes.ts index 9329ce6..a4e9444 100644 --- a/src/clientRoutes.ts +++ b/src/clientRoutes.ts @@ -14,6 +14,7 @@ const clientRoutes = { "/darmasaba/keterangan-kelahiran": "/darmasaba/keterangan-kelahiran", "/darmasaba/surat-keterangan-tempat-usaha": "/darmasaba/surat-keterangan-tempat-usaha", "/darmasaba/surat-keterangan-kelakuan-baik": "/darmasaba/surat-keterangan-kelakuan-baik", + "/darmasaba/surat": "/darmasaba/surat", "/": "/", "/scr": "/scr", "/scr/dashboard": "/scr/dashboard", diff --git a/src/components/DashboardCountData.tsx b/src/components/DashboardCountData.tsx index 8ba3f5a..f5cf787 100644 --- a/src/components/DashboardCountData.tsx +++ b/src/components/DashboardCountData.tsx @@ -1,98 +1,101 @@ import apiFetch from "@/lib/apiFetch"; import { Card, Flex, Grid, Group, Stack, Text } from "@mantine/core"; import { useShallowEffect } from "@mantine/hooks"; -import { IconFileCertificate, IconMessageReport, IconUsers } from "@tabler/icons-react"; +import { + IconFileCertificate, + IconMessageReport, + IconUsers, +} from "@tabler/icons-react"; import useSWR from "swr"; export default function DashboardCountData() { - const { data, mutate, isLoading } = useSWR("/", () => - apiFetch.api.dashboard.count.get() - ); + const { data, mutate, isLoading } = useSWR("/", () => + apiFetch.api.dashboard.count.get(), + ); - useShallowEffect(() => { - mutate(); - }, []); + useShallowEffect(() => { + mutate(); + }, []); - return ( - - - } - label="Pengaduan Hari Ini" - value={String(data?.data?.pengaduan?.today)} - change={String(data?.data?.pengaduan?.kenaikan) + "%"} - color={"gray"} - /> - - - } - label="Pengajuan Surat Hari Ini" - value={String(data?.data?.pelayanan?.today)} - change={String(data?.data?.pelayanan?.kenaikan) + "%"} - color="gray" - /> - - - } - label="Warga" - value={String(data?.data?.warga)} - color="blue" - /> - - - ); + return ( + + + } + label="Pengaduan Hari Ini" + value={String(data?.data?.pengaduan?.today)} + change={String(data?.data?.pengaduan?.kenaikan) + "%"} + color={"gray"} + /> + + + } + label="Pengajuan Surat Hari Ini" + value={String(data?.data?.pelayanan?.today)} + change={String(data?.data?.pelayanan?.kenaikan) + "%"} + color="gray" + /> + + + } + label="Warga" + value={String(data?.data?.warga)} + color="blue" + /> + + + ); } - function MetricCard({ - icon, - label, - value, - change, - color, + icon, + label, + value, + change, + color, }: { - icon: React.ReactNode; - label: string; - value: string; - change?: string; - color: string; + icon: React.ReactNode; + label: string; + value: string; + change?: string; + color: string; }) { - return ( - - (e.currentTarget.style.boxShadow = "0 0 10px rgba(0,255,200,0.2)") - } - onMouseLeave={(e) => (e.currentTarget.style.boxShadow = "none")} - > - - - {icon} - - {label} - - - - - {value} - - {change && ( - - {change} - - )} - - - - ); -} \ No newline at end of file + return ( + + (e.currentTarget.style.boxShadow = "0 0 10px rgba(0,255,200,0.2)") + } + onMouseLeave={(e) => (e.currentTarget.style.boxShadow = "none")} + > + + + {icon} + + {label} + + + + + {value} + + {change && ( + + {change} + + )} + + + + ); +} diff --git a/src/components/DashboardGrafik.tsx b/src/components/DashboardGrafik.tsx index 0748231..a8b6d71 100644 --- a/src/components/DashboardGrafik.tsx +++ b/src/components/DashboardGrafik.tsx @@ -7,77 +7,71 @@ import { useEffect, useState } from "react"; import useSWR from "swr"; export default function DashboardGrafik() { - const [options, setOptions] = useState({}); - const { data, mutate, isLoading } = useSWR( - "grafik-dashboard", - async () => { - return apiFetch.api.dashboard.grafik.get().then(res => res.data); - } - ); + const [options, setOptions] = useState({}); + const { data, mutate, isLoading } = useSWR("grafik-dashboard", async () => { + return apiFetch.api.dashboard.grafik.get().then((res) => res.data); + }); - const loadData = () => { - if (!data) return; - const option: EChartsOption = { - darkMode: true, - animation: true, - legend: { - textStyle: { color: "#fff" } - }, - tooltip: {}, - dataset: { - dimensions: data.dimensions, - source: data.source - }, - xAxis: { - type: "category", - axisLabel: { color: "#fff" } - }, - yAxis: { - type: "value", - minInterval: 1, - axisLabel: { color: "#fff" } - }, - color: ["#1abc9c", "#10816aff"], - series: [ - { type: "bar" }, - { type: "bar" } - ] - }; + const loadData = () => { + if (!data) return; + const option: EChartsOption = { + darkMode: true, + animation: true, + legend: { + textStyle: { color: "#fff" }, + }, + tooltip: {}, + dataset: { + dimensions: data.dimensions, + source: data.source, + }, + xAxis: { + type: "category", + axisLabel: { color: "#fff" }, + }, + yAxis: { + type: "value", + minInterval: 1, + axisLabel: { color: "#fff" }, + }, + color: ["#1abc9c", "#10816aff"], + series: [{ type: "bar" }, { type: "bar" }], + }; - setOptions(option); - }; + setOptions(option); + }; - useEffect(() => { - if (data) loadData(); - }, [data]); + useEffect(() => { + if (data) loadData(); + }, [data]); - return ( - - - - - - Grafik Pengaduan dan Pelayanan Surat - - 7 Hari Terakhir - - - - - - - - - - ) -} \ No newline at end of file + return ( + + + + + + Grafik Pengaduan dan Pelayanan Surat + + 7 Hari Terakhir + + + + + + + + + + ); +} diff --git a/src/components/DashboardLastData.tsx b/src/components/DashboardLastData.tsx index 47bcac3..00b299f 100644 --- a/src/components/DashboardLastData.tsx +++ b/src/components/DashboardLastData.tsx @@ -1,133 +1,210 @@ import apiFetch from "@/lib/apiFetch"; -import { Badge, Button, Card, Flex, Group, Stack, Text, Title, Tooltip } from "@mantine/core"; +import { + Badge, + Button, + Card, + Flex, + Group, + Stack, + Text, + Title, + Tooltip, +} from "@mantine/core"; import { useShallowEffect } from "@mantine/hooks"; import { useNavigate } from "react-router-dom"; import useSWR from "swr"; export default function DashboardLastData() { - const navigate = useNavigate(); - const { data, mutate, isLoading } = useSWR("last-update", async () => { - const res = await apiFetch.api.dashboard["last-update"].get(); - return res.data - }); + const navigate = useNavigate(); + const { data, mutate, isLoading } = useSWR("last-update", async () => { + const res = await apiFetch.api.dashboard["last-update"].get(); + return res.data; + }); - useShallowEffect(() => { - mutate(); - }, []); + useShallowEffect(() => { + mutate(); + }, []); + return ( + + + + + + Last update pengaduan + + + + + {data && + Array.isArray(data.pengaduan) && + data.pengaduan.length > 0 ? ( + data.pengaduan.map((item: any, index: number) => ( + + )) + ) : ( + + Tidak ada data + + )} + + + - return ( - - - - - - Last update pengaduan - - - - - { - data && Array.isArray(data.pengaduan) && data.pengaduan.length > 0 ? data.pengaduan.map((item: any, index: number) => ( - - )) : Tidak ada data - } - - - - - - - - - Last update pelayanan surat - - - - - { - data && Array.isArray(data.pelayanan) && data.pelayanan.length > 0 ? data.pelayanan.map((item: any, index: number) => ( - - )) : Tidak ada data - } - - - - - ); + + + + + Last update pelayanan surat + + + + + {data && + Array.isArray(data.pelayanan) && + data.pelayanan.length > 0 ? ( + data.pelayanan.map((item: any, index: number) => ( + + )) + ) : ( + + Tidak ada data + + )} + + + + + ); } -function PengaduanSection({ id, nomer, judul, status, updated, kategori }: { id: string, nomer: string, judul: string, status: string, updated: string, kategori: 'pengaduan' | 'pelayanan' }) { - const navigate = useNavigate(); +function PengaduanSection({ + id, + nomer, + judul, + status, + updated, + kategori, +}: { + id: string; + nomer: string; + judul: string; + status: string; + updated: string; + kategori: "pengaduan" | "pelayanan"; +}) { + const navigate = useNavigate(); - return ( - navigate(kategori == "pelayanan" ? `/scr/dashboard/pelayanan-surat/detail-pelayanan?id=${id}` : `/scr/dashboard/pengaduan/detail?id=${id}`)} + return ( + + navigate( + kategori == "pelayanan" + ? `/scr/dashboard/pelayanan-surat/detail-pelayanan?id=${id}` + : `/scr/dashboard/pengaduan/detail?id=${id}`, + ) + } + > + - - - - {judul} - - - - #{nomer} ∙ {updated} - - - - - - - - - ) -} \ No newline at end of file + + + {judul} + + + + #{nomer} ∙ {updated} + + + + + + + + + ); +} diff --git a/src/components/DesaSetting.tsx b/src/components/DesaSetting.tsx index f6457d8..b5361b1 100644 --- a/src/components/DesaSetting.tsx +++ b/src/components/DesaSetting.tsx @@ -12,7 +12,7 @@ import { Stack, Table, Title, - Tooltip + Tooltip, } from "@mantine/core"; import { useDisclosure, useShallowEffect } from "@mantine/hooks"; import { IconEdit } from "@tabler/icons-react"; @@ -23,11 +23,15 @@ import useSWR from "swr"; import ModalFile from "./ModalFile"; import notification from "./notificationGlobal"; -export default function DesaSetting({ permissions }: { permissions: JsonValue[] }) { +export default function DesaSetting({ + permissions, +}: { + permissions: JsonValue[]; +}) { const [btnDisable, setBtnDisable] = useState(false); const [btnLoading, setBtnLoading] = useState(false); const [opened, { open, close }] = useDisclosure(false); - const [img, setImg] = useState() + const [img, setImg] = useState(); const [openedPreview, setOpenedPreview] = useState(false); const [viewImg, setViewImg] = useState(""); const { data, mutate, isLoading } = useSWR("/", () => @@ -51,13 +55,19 @@ export default function DesaSetting({ permissions }: { permissions: JsonValue[] let finalData = { ...dataEdit }; // ← buffer data terbaru if (dataEdit.name === "TTD") { - const oldImg = await apiFetch.api.pengaduan["delete-image"].post({ file: dataEdit.value, folder: "lainnya" }); - const resImg = await apiFetch.api.pengaduan.upload.post({ file: img, folder: "lainnya" }); + const oldImg = await apiFetch.api.pengaduan["delete-image"].post({ + file: dataEdit.value, + folder: "lainnya", + }); + const resImg = await apiFetch.api.pengaduan.upload.post({ + file: img, + folder: "lainnya", + }); if (resImg.status === 200) { finalData = { ...finalData, - value: resImg.data?.filename || "" + value: resImg.data?.filename || "", }; setDataEdit(finalData); // update state @@ -70,7 +80,6 @@ export default function DesaSetting({ permissions }: { permissions: JsonValue[] } } - const res = await apiFetch.api["configuration-desa"].edit.post(finalData); if (res.status === 200) { @@ -100,8 +109,11 @@ export default function DesaSetting({ permissions }: { permissions: JsonValue[] } } - - function chooseEdit({ data }: { data: { id: string; value: string; name: string }; }) { + function chooseEdit({ + data, + }: { + data: { id: string; value: string; name: string }; + }) { setDataEdit(data); open(); } @@ -133,31 +145,27 @@ export default function DesaSetting({ permissions }: { permissions: JsonValue[] overlayProps={{ backgroundOpacity: 0.55, blur: 3 }} > - { - dataEdit.name == "TTD" - ? - ( - - { setImg(e) }} - /> - - ) - : - ( - - - onValidation({ kat: "value", value: e.target.value }) - } - /> - - ) - } + {dataEdit.name == "TTD" ? ( + + { + setImg(e); + }} + /> + + ) : ( + + + onValidation({ kat: "value", value: e.target.value }) + } + /> + + )} - - ) - } + {permissions.includes("setting.kategori_pelayanan.tambah") && ( + + + + )} @@ -578,7 +579,15 @@ export default function KategoriPelayananSurat({ permissions }: { permissions: J - + - + diff --git a/src/components/KategoriPengaduan.tsx b/src/components/KategoriPengaduan.tsx index 99b0fab..2b36353 100644 --- a/src/components/KategoriPengaduan.tsx +++ b/src/components/KategoriPengaduan.tsx @@ -20,7 +20,11 @@ import { useState } from "react"; import useSWR from "swr"; import notification from "./notificationGlobal"; -export default function KategoriPengaduan({ permissions }: { permissions: JsonValue[] }) { +export default function KategoriPengaduan({ + permissions, +}: { + permissions: JsonValue[]; +}) { const [openedDelete, { open: openDelete, close: closeDelete }] = useDisclosure(false); const [btnDisable, setBtnDisable] = useState(true); @@ -294,19 +298,17 @@ export default function KategoriPengaduan({ permissions }: { permissions: JsonVa Kategori Pengaduan - { - permissions.includes("setting.kategori_pengaduan.tambah") && ( - - - - ) - } + {permissions.includes("setting.kategori_pengaduan.tambah") && ( + + + + )} @@ -323,18 +325,38 @@ export default function KategoriPengaduan({ permissions }: { permissions: JsonVa {v.name} - + chooseEdit({ data: v })} - disabled={!permissions.includes("setting.kategori_pengaduan.edit") || v.id == "lainnya"} + disabled={ + !permissions.includes( + "setting.kategori_pengaduan.edit", + ) || v.id == "lainnya" + } > - + diff --git a/src/components/ModalFile.tsx b/src/components/ModalFile.tsx index c480e0a..d7f4e7f 100644 --- a/src/components/ModalFile.tsx +++ b/src/components/ModalFile.tsx @@ -3,92 +3,100 @@ import { Flex, Image, Loader, Modal } from "@mantine/core"; import { useEffect, useState } from "react"; import notification from "./notificationGlobal"; -export default function ModalFile({ open, onClose, folder, fileName }: { open: boolean, onClose: () => void, folder: string, fileName: string }) { - const [viewFile, setViewFile] = useState(""); - const [loading, setLoading] = useState(false); - const [typeFile, setTypeFile] = useState(""); - const [error, setError] = useState(false); +export default function ModalFile({ + open, + onClose, + folder, + fileName, +}: { + open: boolean; + onClose: () => void; + folder: string; + fileName: string; +}) { + const [viewFile, setViewFile] = useState(""); + const [loading, setLoading] = useState(false); + const [typeFile, setTypeFile] = useState(""); + const [error, setError] = useState(false); - useEffect(() => { - if (open && fileName) { - loadImage(); + useEffect(() => { + if (open && fileName) { + loadImage(); + } + }, [open, fileName]); + + const loadImage = async () => { + try { + setViewFile(""); + setLoading(true); + + // detect type of file + const { ext, type } = detectFileType(fileName); + setTypeFile(type || ""); + + // load file + const urlApi = + "/api/pengaduan/image?folder=" + folder + "&fileName=" + fileName; + const res = await fetch(urlApi); + if (!res.ok) { + setError(true); + return notification({ + title: "Error", + message: "Failed to load image", + type: "error", + }); } - }, [open, fileName]); + const blob = await res.blob(); + const url = URL.createObjectURL(blob); + setViewFile(url); + } catch (err) { + setError(true); + notification({ + title: "Error", + message: "Failed to load image", + type: "error", + }); + } finally { + setLoading(false); + } + }; - const loadImage = async () => { - try { - setViewFile(""); - setLoading(true); + useEffect(() => { + if (error) { + onClose(); + } + }, [error]); - // detect type of file - const { ext, type } = detectFileType(fileName); - setTypeFile(type || ""); - - // load file - const urlApi = '/api/pengaduan/image?folder=' + folder + '&fileName=' + fileName; - const res = await fetch(urlApi); - if (!res.ok) { - setError(true); - return notification({ - title: "Error", - message: "Failed to load image", - type: "error", - }); - } - const blob = await res.blob(); - const url = URL.createObjectURL(blob); - - setViewFile(url); - } catch (err) { - setError(true); - notification({ - title: "Error", - message: "Failed to load image", - type: "error", - }); - } finally { - setLoading(false); - } - }; - - useEffect(() => { - if (error) { - onClose(); - } - }, [error]); - - - - return ( - - {loading && ( - - - - )} - {viewFile && ( - <> - {typeFile == "pdf" ? ( - - ) : ( - - )} - - )} - - ); + return ( + + {loading && ( + + + + )} + {viewFile && ( + <> + {typeFile == "pdf" ? ( + + ) : ( + + )} + + )} + + ); } diff --git a/src/components/ModalSurat.tsx b/src/components/ModalSurat.tsx index cd07704..926aefc 100644 --- a/src/components/ModalSurat.tsx +++ b/src/components/ModalSurat.tsx @@ -18,122 +18,129 @@ 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(null); - const { data, mutate, isLoading } = useSWR("surat", () => - apiFetch.api.surat.detail.get({ - query: { - id: surat, - }, - }), - ); +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(null); + const { data, mutate, isLoading } = useSWR("surat", () => + apiFetch.api.surat.detail.get({ + query: { + id: surat, + }, + }), + ); - useShallowEffect(() => { - mutate(); - }, []); + 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 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 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 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; - const imgWidth = pageWidth; - 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`); + }; - pdf.save(`${data?.data?.surat?.nameCategory}.pdf`); - }; + return ( + <> + 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={ + +
Preview Surat
- return ( - <> - 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={ - -
- Preview Surat -
+ + + + - - - - - - - - - - - } - > -
- { - data && data.data - ? data.data.surat.idCategory == "skusaha" - ? - : data.data.surat.idCategory == "skkelahiran" - ? - : data.data.surat.idCategory == "skkelakuanbaik" - ? - : data.data.surat.idCategory == "skpenghasilan" - ? - : data.data.surat.idCategory == "sktidakmampu" - ? - : data.data.surat.idCategory == "skyatimpiatu" - ? - : data.data.surat.idCategory == "skdomisiliorganisasi" - ? - : data.data.surat.idCategory == "skbedabiodata" - ? - : data.data.surat.idCategory == "sktempatusaha" - ? - : data.data.surat.idCategory == "skbelumkawin" - ? - : data.data.surat.idCategory == "skkematian" - ? - : <> - : <> - } -
-
- - ) -} \ No newline at end of file + + + +
+ + } + > +
+ {data && data.data ? ( + data.data.surat.idCategory == "skusaha" ? ( + + ) : data.data.surat.idCategory == "skkelahiran" ? ( + + ) : data.data.surat.idCategory == "skkelakuanbaik" ? ( + + ) : data.data.surat.idCategory == "skpenghasilan" ? ( + + ) : data.data.surat.idCategory == "sktidakmampu" ? ( + + ) : data.data.surat.idCategory == "skyatimpiatu" ? ( + + ) : data.data.surat.idCategory == "skdomisiliorganisasi" ? ( + + ) : data.data.surat.idCategory == "skbedabiodata" ? ( + + ) : data.data.surat.idCategory == "sktempatusaha" ? ( + + ) : data.data.surat.idCategory == "skbelumkawin" ? ( + + ) : data.data.surat.idCategory == "skkematian" ? ( + + ) : ( + <> + ) + ) : ( + <> + )} +
+
+ + ); +} diff --git a/src/components/PermissionRole.tsx b/src/components/PermissionRole.tsx index 38a49c1..b0c44dd 100644 --- a/src/components/PermissionRole.tsx +++ b/src/components/PermissionRole.tsx @@ -3,65 +3,66 @@ import { Anchor, Flex, Stack, Text } from "@mantine/core"; import { useState } from "react"; interface Node { - label: string; - children: any; - actions: string[]; + label: string; + children: any; + actions: string[]; } function RenderNode({ node }: { node: Node }) { - const sub = Object.values(node.children || {}); + const sub = Object.values(node.children || {}); - return ( - - {/* Title */} - - {node.label} + return ( + + {/* Title */} + - {node.label} - {/* Children */} - {sub.map((child: any, i) => ( - - ))} - - ); + {/* Children */} + {sub.map((child: any, i) => ( + + ))} + + ); } function RenderNode2({ node }: { node: Node }) { - const sub = Object.values(node.children || {}); + const sub = Object.values(node.children || {}); - return ( - - {/* Title */} - {node.label}, + return ( + + {/* Title */} + {node.label}, - {/* Children */} - {sub.map((child: any, i) => ( - - ))} - - ); + {/* Children */} + {sub.map((child: any, i) => ( + + ))} + + ); } -export default function PermissionRole({ permissions }: { permissions: string[] }) { - const [showAll, setShowAll] = useState(false); - if (!permissions?.length) return -; +export default function PermissionRole({ + permissions, +}: { + permissions: string[]; +}) { + const [showAll, setShowAll] = useState(false); + if (!permissions?.length) return -; - const groups = groupPermissions(permissions); - const rootNodes = Object.values(groups); + const groups = groupPermissions(permissions); + const rootNodes = Object.values(groups); - return ( - - { - showAll ? - rootNodes.map((node: any, idx) => ( - - )) - : - rootNodes.slice(0, 2).map((node: any, idx) => ( - - )) - } - setShowAll(!showAll)} > - {showAll ? "View less" : "View more"} - - - ); + return ( + + {showAll + ? rootNodes.map((node: any, idx) => ( + + )) + : rootNodes + .slice(0, 2) + .map((node: any, idx) => )} + setShowAll(!showAll)}> + {showAll ? "View less" : "View more"} + + + ); } diff --git a/src/components/PermissionTree.tsx b/src/components/PermissionTree.tsx index e422dda..9d1242c 100644 --- a/src/components/PermissionTree.tsx +++ b/src/components/PermissionTree.tsx @@ -1,177 +1,183 @@ import permissionConfig from "@/lib/listPermission.json"; -import { ActionIcon, Checkbox, Collapse, Group, Stack, Text } from "@mantine/core"; +import { + ActionIcon, + Checkbox, + Collapse, + Group, + Stack, + Text, +} from "@mantine/core"; import { IconChevronDown, IconChevronRight } from "@tabler/icons-react"; import { useState } from "react"; interface Node { - label: string; - key: string; - children?: Node[]; + label: string; + key: string; + children?: Node[]; } export default function PermissionTree({ - selected, - onChange, + selected, + onChange, }: { - selected: string[]; - onChange: (val: string[]) => void; + selected: string[]; + onChange: (val: string[]) => void; }) { - // Ambil semua child dari node - const [openNodes, setOpenNodes] = useState>({}); + // Ambil semua child dari node + const [openNodes, setOpenNodes] = useState>({}); - function toggleNode(label: string) { - setOpenNodes(prev => ({ ...prev, [label]: !prev[label] })); - } + function toggleNode(label: string) { + setOpenNodes((prev) => ({ ...prev, [label]: !prev[label] })); + } - function getAllChildKeys(node: Node): string[] { - let result: string[] = []; - if (node.children) { - node.children.forEach((c) => { - result.push(c.key); - result = [...result, ...getAllChildKeys(c)]; - }); + function getAllChildKeys(node: Node): string[] { + let result: string[] = []; + if (node.children) { + node.children.forEach((c) => { + result.push(c.key); + result = [...result, ...getAllChildKeys(c)]; + }); + } + return result; + } + + // Dapatkan parentKey, jika ada + function getParentKey(key: string) { + const split = key.split("."); + if (split.length <= 1) return null; + split.pop(); + return split.join("."); + } + + // Update parent ke atas secara rekursif + function updateParent(next: string[], parentKey: string | null): string[] { + if (!parentKey) return next; + + const allChildKeys = findAllChildKeysFromKey(parentKey); + + const selectedChild = allChildKeys.filter((c) => next.includes(c)); + + if (selectedChild.length === 0) { + // Semua child uncheck → parent uncheck + next = next.filter((x) => x !== parentKey); + } else if (selectedChild.length === allChildKeys.length) { + // Semua child check → parent check + if (!next.includes(parentKey)) { + next.push(parentKey); } - return result; - } - - // Dapatkan parentKey, jika ada - function getParentKey(key: string) { - const split = key.split("."); - if (split.length <= 1) return null; - split.pop(); - return split.join("."); - } - - - // Update parent ke atas secara rekursif - function updateParent(next: string[], parentKey: string | null): string[] { - if (!parentKey) return next; - - const allChildKeys = findAllChildKeysFromKey(parentKey); - - const selectedChild = allChildKeys.filter((c) => next.includes(c)); - - if (selectedChild.length === 0) { - // Semua child uncheck → parent uncheck - next = next.filter((x) => x !== parentKey); - } else if (selectedChild.length === allChildKeys.length) { - // Semua child check → parent check - if (!next.includes(parentKey)) { - next.push(parentKey); - } - } else { - // Sebagian child check → parent intermediate (checked = true, rendered sebagai indeterminate) - if (!next.includes(parentKey)) { - next.push(parentKey); - } + } else { + // Sebagian child check → parent intermediate (checked = true, rendered sebagai indeterminate) + if (!next.includes(parentKey)) { + next.push(parentKey); } + } - // Rekursif naik ke atas - return updateParent(next, getParentKey(parentKey)); - } + // Rekursif naik ke atas + return updateParent(next, getParentKey(parentKey)); + } - // dapatkan child dari string key - function findAllChildKeysFromKey(parentKey: string) { - const list: string[] = []; + // dapatkan child dari string key + function findAllChildKeysFromKey(parentKey: string) { + const list: string[] = []; - function traverse(nodes: Node[]) { - nodes.forEach((n) => { - if (n.key.startsWith(parentKey + ".") && n.key !== parentKey) { - list.push(n.key); - } - if (n.children) traverse(n.children); - }); - } + function traverse(nodes: Node[]) { + nodes.forEach((n) => { + if (n.key.startsWith(parentKey + ".") && n.key !== parentKey) { + list.push(n.key); + } + if (n.children) traverse(n.children); + }); + } - traverse(permissionConfig.menus); - return list; - } + traverse(permissionConfig.menus); + return list; + } - const RenderMenu = ({ menu }: { menu: Node }) => { - const hasChild = menu.children && menu.children.length > 0; - const open = openNodes[menu.label] ?? false; - const childKeys = getAllChildKeys(menu); - const isChecked = selected.includes(menu.key); - const isIndeterminate = - !isChecked && - selected.some( - (x) => - typeof x === "string" && - x.startsWith(menu.key + ".") - ); - - function handleCheck() { - let next = [...selected]; - - if (childKeys.length > 0) { - // klik parent - if (!isChecked) { - next = [...new Set([...next, menu.key, ...childKeys])]; - } else { - next = next.filter((x) => x !== menu.key && !childKeys.includes(x)); - } - - next = updateParent(next, getParentKey(menu.key)); - onChange(next); - return; - } - - // klik child - if (isChecked) { - next = next.filter((x) => x !== menu.key); - } else { - next.push(menu.key); - } - - next = updateParent(next, getParentKey(menu.key)); - onChange(next); - } - - return ( - - - {menu.children && menu.children.length > 0 ? ( - toggleNode(menu.label)} - > - {openNodes[menu.label] ? ( - - ) : ( - - )} - - ) : ( -
- )} - - - - - {menu.children && ( - - - {menu.children.map((child) => ( - - ))} - - - )} - + const RenderMenu = ({ menu }: { menu: Node }) => { + const hasChild = menu.children && menu.children.length > 0; + const open = openNodes[menu.label] ?? false; + const childKeys = getAllChildKeys(menu); + const isChecked = selected.includes(menu.key); + const isIndeterminate = + !isChecked && + selected.some( + (x) => typeof x === "string" && x.startsWith(menu.key + "."), ); - }; - return ( - - Hak Akses - {permissionConfig.menus.filter((menu: Node) => !menu.key.startsWith("api") && !menu.key.startsWith("credential")).map((menu: Node) => ( - - ))} + function handleCheck() { + let next = [...selected]; + + if (childKeys.length > 0) { + // klik parent + if (!isChecked) { + next = [...new Set([...next, menu.key, ...childKeys])]; + } else { + next = next.filter((x) => x !== menu.key && !childKeys.includes(x)); + } + + next = updateParent(next, getParentKey(menu.key)); + onChange(next); + return; + } + + // klik child + if (isChecked) { + next = next.filter((x) => x !== menu.key); + } else { + next.push(menu.key); + } + + next = updateParent(next, getParentKey(menu.key)); + onChange(next); + } + + return ( + + + {menu.children && menu.children.length > 0 ? ( + toggleNode(menu.label)}> + {openNodes[menu.label] ? ( + + ) : ( + + )} + + ) : ( +
+ )} + + + + + {menu.children && ( + + + {menu.children.map((child) => ( + + ))} + + + )} - ); + ); + }; + + return ( + + Hak Akses + {permissionConfig.menus + .filter( + (menu: Node) => + !menu.key.startsWith("api") && !menu.key.startsWith("credential"), + ) + .map((menu: Node) => ( + + ))} + + ); } diff --git a/src/components/ProfileUser.tsx b/src/components/ProfileUser.tsx index 9f0250f..3db29f5 100644 --- a/src/components/ProfileUser.tsx +++ b/src/components/ProfileUser.tsx @@ -13,7 +13,11 @@ import type { JsonValue } from "generated/prisma/runtime/library"; import { useEffect, useState } from "react"; import notification from "./notificationGlobal"; -export default function ProfileUser({ permissions }: { permissions: JsonValue[] }) { +export default function ProfileUser({ + permissions, +}: { + permissions: JsonValue[]; +}) { const [opened, setOpened] = useState(false); const [openedPassword, setOpenedPassword] = useState(false); const [pwdBaru, setPwdBaru] = useState(""); @@ -127,21 +131,17 @@ export default function ProfileUser({ permissions }: { permissions: JsonValue[] Profile Pengguna - { - permissions.includes("setting.profile.edit") && ( - - ) - } + {permissions.includes("setting.profile.edit") && ( + + )} - { - permissions.includes("setting.profile.password") && ( - - ) - } + {permissions.includes("setting.profile.password") && ( + + )} diff --git a/src/components/UserRoleSetting.tsx b/src/components/UserRoleSetting.tsx index fbc1976..6a2db75 100644 --- a/src/components/UserRoleSetting.tsx +++ b/src/components/UserRoleSetting.tsx @@ -1,17 +1,17 @@ import apiFetch from "@/lib/apiFetch"; import { - ActionIcon, - Button, - Divider, - Flex, - Group, - Input, - Modal, - Stack, - Table, - Text, - Title, - Tooltip + ActionIcon, + Button, + Divider, + Flex, + Group, + Input, + Modal, + Stack, + Table, + Text, + Title, + Tooltip, } from "@mantine/core"; import { useDisclosure, useShallowEffect } from "@mantine/hooks"; import { IconEdit, IconPlus, IconTrash } from "@tabler/icons-react"; @@ -24,404 +24,449 @@ import PermissionRole from "./PermissionRole"; import PermissionTree from "./PermissionTree"; interface MenuNode { - key: string; - label: string; - default: boolean; - children?: MenuNode[]; + key: string; + label: string; + default: boolean; + children?: MenuNode[]; } -export default function UserRoleSetting({ permissions }: { permissions: JsonValue[] }) { - const [btnDisable, setBtnDisable] = useState(true); - const [btnLoading, setBtnLoading] = useState(false); - const [opened, { open, close }] = useDisclosure(false); - const [openedDelete, { open: openDelete, close: closeDelete }] = - useDisclosure(false); - const [dataDelete, setDataDelete] = useState(""); - const { - data: dataRole, - mutate: mutateRole, - isLoading: isLoadingRole, - } = useSWR("user-role", () => apiFetch.api.user.role.get()); - const [openedTambah, { open: openTambah, close: closeTambah }] = - useDisclosure(false); - const { data, mutate, isLoading } = useSWR("role-list", () => - apiFetch.api.user.role.get(), - ); - const list = data?.data || []; - const listRole = dataRole?.data || []; - const [dataEdit, setDataEdit] = useState({ - id: "", - name: "", - permissions: [], - }); - const [dataTambah, setDataTambah] = useState({ - name: "", - permissions: [], - }); - const [error, setError] = useState({ - name: false, - permissions: false, - }); +export default function UserRoleSetting({ + permissions, +}: { + permissions: JsonValue[]; +}) { + const [btnDisable, setBtnDisable] = useState(true); + const [btnLoading, setBtnLoading] = useState(false); + const [opened, { open, close }] = useDisclosure(false); + const [openedDelete, { open: openDelete, close: closeDelete }] = + useDisclosure(false); + const [dataDelete, setDataDelete] = useState(""); + const { + data: dataRole, + mutate: mutateRole, + isLoading: isLoadingRole, + } = useSWR("user-role", () => apiFetch.api.user.role.get()); + const [openedTambah, { open: openTambah, close: closeTambah }] = + useDisclosure(false); + const { data, mutate, isLoading } = useSWR("role-list", () => + apiFetch.api.user.role.get(), + ); + const list = data?.data || []; + const listRole = dataRole?.data || []; + const [dataEdit, setDataEdit] = useState({ + id: "", + name: "", + permissions: [], + }); + const [dataTambah, setDataTambah] = useState({ + name: "", + permissions: [], + }); + const [error, setError] = useState({ + name: false, + permissions: false, + }); - useShallowEffect(() => { - mutate(); - }, []); + useShallowEffect(() => { + mutate(); + }, []); - async function handleCreate() { - try { - setBtnLoading(true); - const res = await apiFetch.api.user["role-create"].post(dataTambah as any); - if (res.status === 200) { - mutate(); - closeTambah(); - setDataTambah({ - name: "", - permissions: [], - }); - notification({ - title: "Success", - message: "Your role have been saved", - type: "success", - }); - } else { - notification({ - title: "Error", - message: "Failed to create role", - type: "error", - }); - } - } catch (error) { - console.error(error); - notification({ - title: "Error", - message: "Failed to create role", - type: "error", - }); - } finally { - setBtnLoading(false); - } - } - - async function handleEdit() { - try { - setBtnLoading(true); - const res = await apiFetch.api.user["role-update"].post(dataEdit as any); - if (res.status === 200) { - mutate(); - close(); - notification({ - title: "Success", - message: "Your role have been saved", - type: "success", - }); - } else { - notification({ - title: "Error", - message: "Failed to edit role", - type: "error", - }); - } - } catch (error) { - console.error(error); - notification({ - title: "Error", - message: "Failed to edit role", - type: "error", - }); - } finally { - setBtnLoading(false); - } - } - - async function handleDelete() { - try { - setBtnLoading(true); - const res = await apiFetch.api.user["role-delete"].post({ id: dataDelete }); - if (res.status === 200) { - mutate(); - closeDelete(); - notification({ - title: "Success", - message: "Your role have been deleted", - type: "success", - }); - } else { - notification({ - title: "Error", - message: "Failed to delete role", - type: "error", - }); - } - } catch (error) { - console.error(error); - notification({ - title: "Error", - message: "Failed to delete role", - type: "error", - }); - } finally { - setBtnLoading(false); - } - } - - function chooseEdit({ data }: { data: { id: string; name: string; permissions: []; }; }) { - setDataEdit({ - id: data.id, name: data.name, permissions: data.permissions ? data.permissions : [] - }); - open(); - } - - function onValidation({ kat, value, aksi, }: { kat: "name" | "permission"; value: string | null; aksi: "edit" | "tambah"; }) { - if (value == null || value.length < 1) { - setBtnDisable(true); - setError({ ...error, [kat]: true }); + async function handleCreate() { + try { + setBtnLoading(true); + const res = await apiFetch.api.user["role-create"].post( + dataTambah as any, + ); + if (res.status === 200) { + mutate(); + closeTambah(); + setDataTambah({ + name: "", + permissions: [], + }); + notification({ + title: "Success", + message: "Your role have been saved", + type: "success", + }); } else { - setBtnDisable(false); - setError({ ...error, [kat]: false }); + notification({ + title: "Error", + message: "Failed to create role", + type: "error", + }); } - - if (aksi === "edit") { - setDataEdit({ ...dataEdit, [kat]: value }); - } else { - setDataTambah({ ...dataTambah, [kat]: value }); - } - } - - function buildOrderList(menus: MenuNode[]): string[] { - const list: string[] = []; - - const traverse = (nodes: MenuNode[]) => { - nodes.forEach((node) => { - list.push(node.key); - if (node.children) traverse(node.children); - }); - }; - - traverse(menus); - return list; - } - - function sortByJsonOrder(arrayData: string[]): string[] { - const orderList = buildOrderList(listMenu.menus); - - return arrayData.sort((a, b) => { - return orderList.indexOf(a) - orderList.indexOf(b); + } catch (error) { + console.error(error); + notification({ + title: "Error", + message: "Failed to create role", + type: "error", }); - } + } finally { + setBtnLoading(false); + } + } - useShallowEffect(() => { - if (dataEdit.name.length > 0) { - setBtnDisable(false); + async function handleEdit() { + try { + setBtnLoading(true); + const res = await apiFetch.api.user["role-update"].post(dataEdit as any); + if (res.status === 200) { + mutate(); + close(); + notification({ + title: "Success", + message: "Your role have been saved", + type: "success", + }); + } else { + notification({ + title: "Error", + message: "Failed to edit role", + type: "error", + }); } - }, [dataEdit.id]); + } catch (error) { + console.error(error); + notification({ + title: "Error", + message: "Failed to edit role", + type: "error", + }); + } finally { + setBtnLoading(false); + } + } - return ( - <> - {/* Modal Edit */} - - - - - onValidation({ - kat: "name", - value: e.target.value, - aksi: "edit", - }) - } - /> - - { - setDataEdit({ ...dataEdit, permissions: sortByJsonOrder(permissions) as never[] }); - }} - /> - - - - - - + async function handleDelete() { + try { + setBtnLoading(true); + const res = await apiFetch.api.user["role-delete"].post({ + id: dataDelete, + }); + if (res.status === 200) { + mutate(); + closeDelete(); + notification({ + title: "Success", + message: "Your role have been deleted", + type: "success", + }); + } else { + notification({ + title: "Error", + message: "Failed to delete role", + type: "error", + }); + } + } catch (error) { + console.error(error); + notification({ + title: "Error", + message: "Failed to delete role", + type: "error", + }); + } finally { + setBtnLoading(false); + } + } - {/* Modal Tambah */} - - - - - onValidation({ - kat: "name", - value: e.target.value, - aksi: "tambah", - }) - } - /> - - { - setDataTambah({ ...dataTambah, permissions: sortByJsonOrder(permissions) as never[] }); - }} - /> - - - - - - + function chooseEdit({ + data, + }: { + data: { id: string; name: string; permissions: [] }; + }) { + setDataEdit({ + id: data.id, + name: data.name, + permissions: data.permissions ? data.permissions : [], + }); + open(); + } - {/* Modal Delete */} - - - - Apakah anda yakin ingin menghapus role ini? - - - - - - - + function onValidation({ + kat, + value, + aksi, + }: { + kat: "name" | "permission"; + value: string | null; + aksi: "edit" | "tambah"; + }) { + if (value == null || value.length < 1) { + setBtnDisable(true); + setError({ ...error, [kat]: true }); + } else { + setBtnDisable(false); + setError({ ...error, [kat]: false }); + } - - - - Daftar Role - - { - permissions.includes('setting.user_role.tambah') && ( - - + + + + + + {/* Modal Tambah */} + + + + + onValidation({ + kat: "name", + value: e.target.value, + aksi: "tambah", + }) + } + /> + + { + setDataTambah({ + ...dataTambah, + permissions: sortByJsonOrder(permissions) as never[], + }); + }} + /> + + + + + + + + {/* Modal Delete */} + + + + Apakah anda yakin ingin menghapus role ini? + + + + + + + + + + + + Daftar Role + + {permissions.includes("setting.user_role.tambah") && ( + + + + )} + + + + + + + Role + Permission + Aksi + + + + {list.length > 0 ? ( + list?.map((v: any) => ( + + {v.name} + + + + + + - Tambah - - - ) - } - - - -
- - - Role - Permission - Aksi - - - - {list.length > 0 ? ( - list?.map((v: any) => ( - - {v.name} - - - - - - - chooseEdit({ data: v })} - disabled={!permissions.includes('setting.user_role.edit') || v.id == "developer"} - > - - - - - { - setDataDelete(v.id); - openDelete(); - }} - disabled={!permissions.includes('setting.user_role.delete') || v.id == "developer"} - > - - - - - - - )) - ) : ( - - - Data Role Tidak Ditemukan - - - )} - -
-
-
- - ); + chooseEdit({ data: v })} + disabled={ + !permissions.includes("setting.user_role.edit") || + v.id == "developer" + } + > + + + + + { + setDataDelete(v.id); + openDelete(); + }} + disabled={ + !permissions.includes( + "setting.user_role.delete", + ) || v.id == "developer" + } + > + + + + + + + )) + ) : ( + + + Data Role Tidak Ditemukan + + + )} + + + + + + ); } diff --git a/src/components/UserSetting.tsx b/src/components/UserSetting.tsx index 0154d36..8c6e8be 100644 --- a/src/components/UserSetting.tsx +++ b/src/components/UserSetting.tsx @@ -21,7 +21,11 @@ import { useState } from "react"; import useSWR from "swr"; import notification from "./notificationGlobal"; -export default function UserSetting({ permissions }: { permissions: JsonValue[] }) { +export default function UserSetting({ + permissions, +}: { + permissions: JsonValue[]; +}) { const [btnDisable, setBtnDisable] = useState(true); const [btnLoading, setBtnLoading] = useState(false); const [opened, { open, close }] = useDisclosure(false); @@ -437,20 +441,17 @@ export default function UserSetting({ permissions }: { permissions: JsonValue[] Daftar User - { - permissions.includes('setting.user.tambah') && ( - - - - ) - } - + {permissions.includes("setting.user.tambah") && ( + + + + )} @@ -474,18 +475,33 @@ export default function UserSetting({ permissions }: { permissions: JsonValue[] {v.nameRole} - + chooseEdit({ data: v })} - disabled={!permissions.includes('setting.user.edit') || v.roleId == "developer"} + disabled={ + !permissions.includes("setting.user.edit") || + v.roleId == "developer" + } > - + diff --git a/src/components/surat/SKBedaBiodataDiri.tsx b/src/components/surat/SKBedaBiodataDiri.tsx index 9279246..4cb6030 100644 --- a/src/components/surat/SKBedaBiodataDiri.tsx +++ b/src/components/surat/SKBedaBiodataDiri.tsx @@ -3,157 +3,242 @@ import { useEffect, useState } from "react"; import notification from "../notificationGlobal"; export default function SKBedaBiodataDiri({ data }: { data: any }) { - const [viewImg, setViewImg] = useState(); - const getValue = (jenis: string) => - _.upperFirst( - data.surat.dataText.find((item: any) => item.jenis === jenis)?.value || "" - ); + const [viewImg, setViewImg] = useState(); + const getValue = (jenis: string) => + _.upperFirst( + data.surat.dataText.find((item: any) => item.jenis === jenis)?.value || + "", + ); - const loadImage = async () => { - try { - setViewImg(""); - if (!data.setting.perbekelTTD) return; + const loadImage = async () => { + try { + setViewImg(""); + if (!data.setting.perbekelTTD) return; - const urlApi = '/api/pengaduan/image?folder=lainnya&fileName=' + data.setting.perbekelTTD; - // Fetch manual agar mendapatkan Response asli - const res = await fetch(urlApi); - if (!res.ok) - return notification({ - title: "Error", - message: "Failed to load image sign", - type: "error", - }); - const blob = await res.blob(); - const url = URL.createObjectURL(blob); + const urlApi = + "/api/pengaduan/image?folder=lainnya&fileName=" + + data.setting.perbekelTTD; + // Fetch manual agar mendapatkan Response asli + const res = await fetch(urlApi); + if (!res.ok) + return notification({ + title: "Error", + message: "Failed to load image sign", + type: "error", + }); + const blob = await res.blob(); + const url = URL.createObjectURL(blob); - setViewImg(url); - } catch (err) { - console.error("Gagal load gambar:", err); - } - }; + setViewImg(url); + } catch (err) { + console.error("Gagal load gambar:", err); + } + }; - useEffect(() => { - loadImage(); - }, [data]); - - - return ( -
- {/* HEADER */} -
- PEMERINTAH KABUPATEN {_.upperCase(data.setting.desaKabupaten)}
- KECAMATAN {_.upperCase(data.setting.desaKecamatan)}
- DESA / KELURAHAN {_.upperCase(data.setting.desaNama)}
- Alamat: {data.setting.desaAlamat}
- Kode Pos: {data.setting.desaPos} -
- - {/* JUDUL */} -
- SURAT KETERANGAN BEDA BIODATA DIRI
- Nomor: {data.surat.noSurat} -
- - {/* YANG BERTANDA TANGAN */} -
- Yang bertanda tangan di bawah ini: - - - - - - - - - - - - - - - - - - - - - - - -
Nama:{data.setting.perbekelNama}
Jabatan:{data.setting.perbekelJabatan + " " + data.setting.desaNama}
Kecamatan:{data.setting.desaKecamatan}
Kabupaten:{data.setting.desaKabupaten}
-
- - {/* IDENTITAS ORANG YG MEMINTA SURAT */} -
- Dengan ini menerangkan bahwa berdasarkan keterangan dari yang bersangkutan: - - - - - - - - - -
Nama:{getValue("nama")}
Tempat/Tanggal Lahir:{getValue("tempat tanggal lahir")}
Jenis Kelamin:{getValue("jenis kelamin")}
Alamat:{getValue("alamat")}
Pekerjaan:{getValue("pekerjaan")}
NIK:{getValue("nik")}
-
- -
- Bahwa orang tersebut di atas benar merupakan orang yang sama, meskipun terdapat perbedaan data pribadi (biodata) pada beberapa dokumen, sebagai berikut: -
- -
- - - - - - -
1. Nama:{getValue("nama")}
Tertulis pada dokumen A:{getValue("tertulis pada dokumen a")}
Tertulis pada dokumen B:{getValue("tertulis pada dokumen b")}
-
- -
- - - - - - -
2. Tempat/Tanggal Lahir:{getValue("tempat tanggal lahir")}
Tertulis pada dokumen A:{getValue("tertulis pada dokumen a")}
Tertulis pada dokumen B:{getValue("tertulis pada dokumen b")}
-
- -
- - - - - - -
3. Nama Orang Tua:{getValue("nama orang tua")}
Tertulis pada dokumen A:{getValue("tertulis pada dokumen a")}
Tertulis pada dokumen B:{getValue("tertulis pada dokumen b")}
-
- -
- Perbedaan tersebut terjadi karena kesalahan penulisan/pencatatan administratif, namun yang bersangkutan adalah orang yang sama. -
- Dengan surat keterangan ini dibuat dengan sebenar-benarnya untuk dipergunakan sebagaimana mestinya. -
- -
- Dikeluarkan di {data.setting.desaNama}
- Pada tanggal {data.surat.createdAt} -
- - {/* TANDA TANGAN */} -
-
- Kepala Desa / Lurah {data.setting.desaNama} -

- ttd perbekel
- {data.setting.perbekelNama}
- NIP. {data.setting.perbekelNIP} -
-
+ useEffect(() => { + loadImage(); + }, [data]); + return ( +
+ {/* HEADER */} +
+ PEMERINTAH KABUPATEN {_.upperCase(data.setting.desaKabupaten)} +
+ KECAMATAN {_.upperCase(data.setting.desaKecamatan)} +
+ DESA / KELURAHAN {_.upperCase(data.setting.desaNama)} +
+ Alamat: {data.setting.desaAlamat} +
+ Kode Pos: {data.setting.desaPos}
- ); + + {/* JUDUL */} +
+ + SURAT KETERANGAN BEDA BIODATA DIRI + +
+ Nomor: {data.surat.noSurat} +
+ + {/* YANG BERTANDA TANGAN */} +
+ Yang bertanda tangan di bawah ini: + + + + + + + + + + + + + + + + + + + + + + + +
Nama:{data.setting.perbekelNama}
Jabatan: + {data.setting.perbekelJabatan + " " + data.setting.desaNama} +
Kecamatan:{data.setting.desaKecamatan}
Kabupaten:{data.setting.desaKabupaten}
+
+ + {/* IDENTITAS ORANG YG MEMINTA SURAT */} +
+ Dengan ini menerangkan bahwa berdasarkan keterangan dari yang + bersangkutan: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Nama:{getValue("nama")}
Tempat/Tanggal Lahir:{getValue("tempat tanggal lahir")}
Jenis Kelamin:{getValue("jenis kelamin")}
Alamat:{getValue("alamat")}
Pekerjaan:{getValue("pekerjaan")}
NIK:{getValue("nik")}
+
+ +
+ Bahwa orang tersebut di atas benar merupakan orang yang sama, + meskipun terdapat perbedaan data pribadi (biodata) pada beberapa + dokumen, sebagai berikut: +
+ +
+ + + + + + + + + + + + + + + + + + +
1. Nama:{getValue("nama")}
Tertulis pada dokumen A:{getValue("tertulis pada dokumen a")}
Tertulis pada dokumen B:{getValue("tertulis pada dokumen b")}
+
+ +
+ + + + + + + + + + + + + + + + + + +
2. Tempat/Tanggal Lahir:{getValue("tempat tanggal lahir")}
Tertulis pada dokumen A:{getValue("tertulis pada dokumen a")}
Tertulis pada dokumen B:{getValue("tertulis pada dokumen b")}
+
+ +
+ + + + + + + + + + + + + + + + + + +
3. Nama Orang Tua:{getValue("nama orang tua")}
Tertulis pada dokumen A:{getValue("tertulis pada dokumen a")}
Tertulis pada dokumen B:{getValue("tertulis pada dokumen b")}
+
+ +
+ Perbedaan tersebut terjadi karena{" "} + kesalahan penulisan/pencatatan administratif, namun yang + bersangkutan adalah orang yang sama. +
+ Dengan surat keterangan ini dibuat dengan sebenar-benarnya untuk + dipergunakan sebagaimana mestinya. +
+ +
+ Dikeluarkan di {data.setting.desaNama}
+ Pada tanggal {data.surat.createdAt} +
+ + {/* TANDA TANGAN */} +
+
+ Kepala Desa / Lurah {data.setting.desaNama} +
+
+ ttd perbekel{" "} +
+ {data.setting.perbekelNama}
+ NIP. {data.setting.perbekelNIP} +
+
+
+ ); } diff --git a/src/components/surat/SKBelumKawin.tsx b/src/components/surat/SKBelumKawin.tsx index 46f0f23..1b69835 100644 --- a/src/components/surat/SKBelumKawin.tsx +++ b/src/components/surat/SKBelumKawin.tsx @@ -3,108 +3,166 @@ import { useEffect, useState } from "react"; import notification from "../notificationGlobal"; export default function SKBelumKawin({ data }: { data: any }) { - const [viewImg, setViewImg] = useState(); - const getValue = (jenis: string) => - _.upperFirst( - data.surat.dataText.find((item: any) => item.jenis === jenis)?.value || "" - ); + const [viewImg, setViewImg] = useState(); + const getValue = (jenis: string) => + _.upperFirst( + data.surat.dataText.find((item: any) => item.jenis === jenis)?.value || + "", + ); - const loadImage = async () => { - try { - setViewImg(""); - if (!data.setting.perbekelTTD) return; + const loadImage = async () => { + try { + setViewImg(""); + if (!data.setting.perbekelTTD) return; - const urlApi = '/api/pengaduan/image?folder=lainnya&fileName=' + data.setting.perbekelTTD; - // Fetch manual agar mendapatkan Response asli - const res = await fetch(urlApi); - if (!res.ok) - return notification({ - title: "Error", - message: "Failed to load image sign", - type: "error", - }); - const blob = await res.blob(); - const url = URL.createObjectURL(blob); + const urlApi = + "/api/pengaduan/image?folder=lainnya&fileName=" + + data.setting.perbekelTTD; + // Fetch manual agar mendapatkan Response asli + const res = await fetch(urlApi); + if (!res.ok) + return notification({ + title: "Error", + message: "Failed to load image sign", + type: "error", + }); + const blob = await res.blob(); + const url = URL.createObjectURL(blob); - setViewImg(url); - } catch (err) { - console.error("Gagal load gambar:", err); - } - }; + setViewImg(url); + } catch (err) { + console.error("Gagal load gambar:", err); + } + }; - useEffect(() => { - loadImage(); - }, [data]); - - return ( -
- {/* HEADER */} -
- PEMERINTAH KABUPATEN {_.upperCase(data.setting.desaKabupaten)}
- KECAMATAN {_.upperCase(data.setting.desaKecamatan)}
- DESA / KELURAHAN {_.upperCase(data.setting.desaNama)}
- Alamat: {data.setting.desaAlamat}
- Kode Pos: {data.setting.desaPos} -
- - {/* JUDUL */} -
- SURAT KETERANGAN BELUM KAWIN
- Nomor: {data.surat.noSurat} -
- - {/* YANG BERTANDA TANGAN */} -
- Yang bertanda tangan di bawah ini {data.setting.perbekelJabatan} {data.setting.desaNama}, Kecamatan {data.setting.desaKecamatan}, Kabupaten {data.setting.desaKabupaten}, dengan ini menerangkan bahwa: -
- - {/* IDENTITAS ORANG YG MEMINTA SURAT */} -
- - - - - - - - - - -
Nama:{getValue("nama")}
NIK:{getValue("nik")}
Tempat/Tanggal Lahir:{getValue("tempat tanggal lahir")}
Jenis Kelamin:{getValue("jenis kelamin")}
Agama:{getValue("agama")}
Pekerjaan:{getValue("pekerjaan")}
Alamat:{getValue("alamat")}
-
- -
- Berdasarkan keterangan dari yang bersangkutan dan data administrasi kependudukan yang ada di Desa {data.setting.desaNama}, - yang bersangkutan benar sampai saat ini belum pernah menikah, baik secara adat, agama, maupun hukum negara. -
- -
- Demikian surat keterangan ini dibuat dengan sebenarnya agar dapat digunakan sebagaimana mestinya. -
- -
- Dikeluarkan di {data.setting.desaNama}
- Pada tanggal {data.surat.createdAt} -
- - {/* TANDA TANGAN */} -
-
-

- Pemohon -





- {getValue("nama")}
-
-
-

- Kepala Desa / Lurah {data.setting.desaNama} -

- ttd perbekel
- {data.setting.perbekelNama}
- NIP. {data.setting.perbekelNIP} -
-
+ useEffect(() => { + loadImage(); + }, [data]); + return ( +
+ {/* HEADER */} +
+ PEMERINTAH KABUPATEN {_.upperCase(data.setting.desaKabupaten)} +
+ KECAMATAN {_.upperCase(data.setting.desaKecamatan)} +
+ DESA / KELURAHAN {_.upperCase(data.setting.desaNama)} +
+ Alamat: {data.setting.desaAlamat} +
+ Kode Pos: {data.setting.desaPos}
- ); + + {/* JUDUL */} +
+ + SURAT KETERANGAN BELUM KAWIN + +
+ Nomor: {data.surat.noSurat} +
+ + {/* YANG BERTANDA TANGAN */} +
+ Yang bertanda tangan di bawah ini {data.setting.perbekelJabatan}{" "} + {data.setting.desaNama}, Kecamatan {data.setting.desaKecamatan}, + Kabupaten {data.setting.desaKabupaten}, dengan ini menerangkan bahwa: +
+ + {/* IDENTITAS ORANG YG MEMINTA SURAT */} +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Nama:{getValue("nama")}
NIK:{getValue("nik")}
Tempat/Tanggal Lahir:{getValue("tempat tanggal lahir")}
Jenis Kelamin:{getValue("jenis kelamin")}
Agama:{getValue("agama")}
Pekerjaan:{getValue("pekerjaan")}
Alamat:{getValue("alamat")}
+
+ +
+ Berdasarkan keterangan dari yang bersangkutan dan data administrasi + kependudukan yang ada di Desa {data.setting.desaNama}, yang bersangkutan + benar sampai saat ini belum pernah menikah, baik secara adat, agama, + maupun hukum negara. +
+ +
+ Demikian surat keterangan ini dibuat dengan sebenarnya agar dapat + digunakan sebagaimana mestinya. +
+ +
+ Dikeluarkan di {data.setting.desaNama}
+ Pada tanggal {data.surat.createdAt} +
+ + {/* TANDA TANGAN */} +
+
+
+
+ Pemohon +
+
+
+
+
+
+ {getValue("nama")}
+
+
+
+
+ Kepala Desa / Lurah {data.setting.desaNama} +
+
+ ttd perbekel{" "} +
+ {data.setting.perbekelNama}
+ NIP. {data.setting.perbekelNIP} +
+
+
+ ); } diff --git a/src/components/surat/SKDomisiliOrganisasi.tsx b/src/components/surat/SKDomisiliOrganisasi.tsx index 769d70a..3885123 100644 --- a/src/components/surat/SKDomisiliOrganisasi.tsx +++ b/src/components/surat/SKDomisiliOrganisasi.tsx @@ -3,121 +3,163 @@ import { useEffect, useState } from "react"; import notification from "../notificationGlobal"; export default function SKDomisiliOrganisasi({ data }: { data: any }) { - const [viewImg, setViewImg] = useState(""); - const getValue = (jenis: string) => - _.upperFirst( - data.surat.dataText.find((item: any) => item.jenis === jenis)?.value || "" - ); + const [viewImg, setViewImg] = useState(""); + const getValue = (jenis: string) => + _.upperFirst( + data.surat.dataText.find((item: any) => item.jenis === jenis)?.value || + "", + ); - const loadImage = async () => { - try { - setViewImg(""); - if (!data.setting.perbekelTTD) return; + const loadImage = async () => { + try { + setViewImg(""); + if (!data.setting.perbekelTTD) return; - const urlApi = '/api/pengaduan/image?folder=lainnya&fileName=' + data.setting.perbekelTTD; - // Fetch manual agar mendapatkan Response asli - const res = await fetch(urlApi); - if (!res.ok) - return notification({ - title: "Error", - message: "Failed to load image sign", - type: "error", - }); - const blob = await res.blob(); - const url = URL.createObjectURL(blob); + const urlApi = + "/api/pengaduan/image?folder=lainnya&fileName=" + + data.setting.perbekelTTD; + // Fetch manual agar mendapatkan Response asli + const res = await fetch(urlApi); + if (!res.ok) + return notification({ + title: "Error", + message: "Failed to load image sign", + type: "error", + }); + const blob = await res.blob(); + const url = URL.createObjectURL(blob); - setViewImg(url); - } catch (err) { - console.error("Gagal load gambar:", err); - } - }; + setViewImg(url); + } catch (err) { + console.error("Gagal load gambar:", err); + } + }; - useEffect(() => { - loadImage(); - }, [data]); - - return ( -
- {/* HEADER */} -
- PEMERINTAH KABUPATEN {_.upperCase(data.setting.desaKabupaten)}
- KECAMATAN {_.upperCase(data.setting.desaKecamatan)}
- DESA / KELURAHAN {_.upperCase(data.setting.desaNama)}
- Alamat: {data.setting.desaAlamat}
- Kode Pos: {data.setting.desaPos} -
- - {/* JUDUL */} -
- SURAT KETERANGAN DOMISILI ORGANISASI
- Nomor: {data.surat.noSurat} -
- - {/* YANG BERTANDA TANGAN */} -
- Yang bertanda tangan di bawah ini: - - - - - - - - - - - - - - - - - - -
Nama:{data.setting.perbekelNama}
Jabatan:{data.setting.perbekelJabatan}
Alamat Kantor:{data.setting.desaAlamat}
-
- - {/* IDENTITAS ORANG YG MEMINTA SURAT */} -
- Dengan ini menerangkan bahwa: - - - - - - - - -
Nama Organisasi:{getValue("nama")}
Jenis Organisasi:{getValue("jenis kelamin")}
Alamat:{getValue("tempat tanggal lahir")}
Nomor Telepon:{getValue("negara")}
Nama Pimpinan:{getValue("agama")}
-
- -
- Benar bahwa organisasi tersebut berdomisili di wilayah Desa / Kelurahan {data.setting.desaNama}, Kecamatan {data.setting.desaKecamatan}, Kabupaten {data.setting.desaKabupaten}. - Dan sampai saat ini masih aktif melakukan kegiatan sesuai dengan bidangnya.
- Surat keterangan ini dibuat untuk keperluan {getValue("keperluan")}. -
- -
- Demikian surat keterangan ini dibuat dengan sebenarnya agar dapat digunakan sebagaimana mestinya. -
- -
- Dikeluarkan di {data.setting.desaNama}
- Pada tanggal {data.surat.createdAt} -
- - {/* TANDA TANGAN */} -
-
-
- Kepala Desa / Lurah {data.setting.desaNama} -

- ttd perbekel
- {data.setting.perbekelNama}
- NIP. {data.setting.perbekelNIP} -
-
+ useEffect(() => { + loadImage(); + }, [data]); + return ( +
+ {/* HEADER */} +
+ PEMERINTAH KABUPATEN {_.upperCase(data.setting.desaKabupaten)} +
+ KECAMATAN {_.upperCase(data.setting.desaKecamatan)} +
+ DESA / KELURAHAN {_.upperCase(data.setting.desaNama)} +
+ Alamat: {data.setting.desaAlamat} +
+ Kode Pos: {data.setting.desaPos}
- ); + + {/* JUDUL */} +
+ + SURAT KETERANGAN DOMISILI ORGANISASI + +
+ Nomor: {data.surat.noSurat} +
+ + {/* YANG BERTANDA TANGAN */} +
+ Yang bertanda tangan di bawah ini: + + + + + + + + + + + + + + + + + + +
Nama:{data.setting.perbekelNama}
Jabatan:{data.setting.perbekelJabatan}
Alamat Kantor:{data.setting.desaAlamat}
+
+ + {/* IDENTITAS ORANG YG MEMINTA SURAT */} +
+ Dengan ini menerangkan bahwa: + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Nama Organisasi:{getValue("nama")}
Jenis Organisasi:{getValue("jenis kelamin")}
Alamat:{getValue("tempat tanggal lahir")}
Nomor Telepon:{getValue("negara")}
Nama Pimpinan:{getValue("agama")}
+
+ +
+ Benar bahwa organisasi tersebut berdomisili di wilayah Desa / Kelurahan{" "} + {data.setting.desaNama}, Kecamatan {data.setting.desaKecamatan}, + Kabupaten {data.setting.desaKabupaten}. Dan sampai saat ini masih aktif + melakukan kegiatan sesuai dengan bidangnya. +
+ Surat keterangan ini dibuat untuk keperluan {getValue("keperluan")}. +
+ +
+ Demikian surat keterangan ini dibuat dengan sebenarnya agar dapat + digunakan sebagaimana mestinya. +
+ +
+ Dikeluarkan di {data.setting.desaNama}
+ Pada tanggal {data.surat.createdAt} +
+ + {/* TANDA TANGAN */} +
+
+
+ Kepala Desa / Lurah {data.setting.desaNama} +
+
+ ttd perbekel{" "} +
+ {data.setting.perbekelNama}
+ NIP. {data.setting.perbekelNIP} +
+
+
+ ); } diff --git a/src/components/surat/SKKelahiran.tsx b/src/components/surat/SKKelahiran.tsx index 9d7722b..751e749 100644 --- a/src/components/surat/SKKelahiran.tsx +++ b/src/components/surat/SKKelahiran.tsx @@ -3,142 +3,244 @@ import { useEffect, useState } from "react"; import notification from "../notificationGlobal"; export default function SKKelahiran({ data }: { data: any }) { - const [viewImg, setViewImg] = useState(""); - const getValue = (jenis: string) => - _.upperFirst( - data.surat.dataText.find((item: any) => item.jenis === jenis)?.value || "" - ); + const [viewImg, setViewImg] = useState(""); + const getValue = (jenis: string) => + _.upperFirst( + data.surat.dataText.find((item: any) => item.jenis === jenis)?.value || + "", + ); - const loadImage = async () => { - try { - setViewImg(""); - if (!data.setting.perbekelTTD) return; + const loadImage = async () => { + try { + setViewImg(""); + if (!data.setting.perbekelTTD) return; - const urlApi = '/api/pengaduan/image?folder=lainnya&fileName=' + data.setting.perbekelTTD; - // Fetch manual agar mendapatkan Response asli - const res = await fetch(urlApi); - if (!res.ok) - return notification({ - title: "Error", - message: "Failed to load image sign", - type: "error", - }); - const blob = await res.blob(); - const url = URL.createObjectURL(blob); + const urlApi = + "/api/pengaduan/image?folder=lainnya&fileName=" + + data.setting.perbekelTTD; + // Fetch manual agar mendapatkan Response asli + const res = await fetch(urlApi); + if (!res.ok) + return notification({ + title: "Error", + message: "Failed to load image sign", + type: "error", + }); + const blob = await res.blob(); + const url = URL.createObjectURL(blob); - setViewImg(url); - } catch (err) { - console.error("Gagal load gambar:", err); - } - }; + setViewImg(url); + } catch (err) { + console.error("Gagal load gambar:", err); + } + }; - useEffect(() => { - loadImage(); - }, [data]); - - return ( -
- - {/* HEADER */} -
- PEMERINTAH KABUPATEN/KOTA {_.upperCase(data.setting.desaKabupaten)}
- KECAMATAN {_.upperCase(data.setting.desaKecamatan)}
- DESA / KELURAHAN {_.upperCase(data.setting.desaNama)}
- Alamat: {data.setting.desaAlamat} -
- - {/* JUDUL */} -
- SURAT KETERANGAN KELAHIRAN
- Nomor : {data.surat.noSurat} -
- - {/* PEMBUKA */} -
- Yang bertanda tangan di bawah ini, {data.setting.perbekelJabatan} - {` ${data.setting.desaNama}, Kecamatan ${data.setting.desaKecamatan}, Kabupaten/Kota ${data.setting.desaKabupaten}`} - , dengan ini menerangkan bahwa: -
- - {/* DATA KELAHIRAN ANAK */} -
- Telah lahir seorang anak pada: - - - - - - - - - -
Tanggal Lahir:{getValue("tanggal lahir anak")}
Pukul:{getValue("pukul lahir anak")}
Tempat Kelahiran:{getValue("tempat lahir anak")}
Jenis Kelamin:{getValue("jenis kelamin anak")}
Anak ke:{getValue("anak ke")}
Nama Anak:{getValue("nama anak")}
-
- - {/* DATA IBU */} -
- Dari seorang ibu bernama: - - - - - - - - -
Nama Lengkap Ibu:{getValue("nama ibu")}
NIK:{getValue("nik ibu")}
Tempat & Tanggal Lahir:{getValue("tempat tanggal lahir ibu")}
Pekerjaan:{getValue("pekerjaan ibu")}
Alamat:{getValue("alamat ibu")}
-
- - {/* DATA AYAH */} -
- Dan seorang ayah bernama: - - - - - - - - -
Nama Lengkap Ayah:{getValue("nama ayah")}
NIK:{getValue("nik ayah")}
Tempat & Tanggal Lahir:{getValue("tempat tanggal lahir ayah")}
Pekerjaan:{getValue("pekerjaan ayah")}
Alamat:{getValue("alamat ayah")}
-
- - {/* DATA PELAPOR */} -
- Berdasarkan laporan dari: - - - - - - -
Nama Pelapor:{getValue("nama pelapor")}
Hubungan dengan Anak:{getValue("hubungan pelapor")}
Alamat:{getValue("alamat pelapor")}
-
- - {/* PENUTUP */} -
- Demikian Surat Keterangan Kelahiran ini dibuat dengan sebenarnya agar dapat digunakan sebagaimana mestinya. -
- - {/* TEMPAT TANGGAL */} - - - - - -
Dikeluarkan di:{data.setting.desaNama}
Pada tanggal:{data.surat.createdAt}
- - {/* TANDA TANGAN */} -
-
- Kepala Desa / Lurah {data.setting.desaNama} -
- ttd perbekel
- {data.setting.perbekelNama}
- NIP. {data.setting.perbekelNIP} -
-
+ useEffect(() => { + loadImage(); + }, [data]); + return ( +
+ {/* HEADER */} +
+ + PEMERINTAH KABUPATEN/KOTA {_.upperCase(data.setting.desaKabupaten)} + +
+ KECAMATAN {_.upperCase(data.setting.desaKecamatan)} +
+ DESA / KELURAHAN {_.upperCase(data.setting.desaNama)} +
+ Alamat: {data.setting.desaAlamat}
- ); + + {/* JUDUL */} +
+ + SURAT KETERANGAN KELAHIRAN + +
+ Nomor : {data.surat.noSurat} +
+ + {/* PEMBUKA */} +
+ Yang bertanda tangan di bawah ini, {data.setting.perbekelJabatan} + {` ${data.setting.desaNama}, Kecamatan ${data.setting.desaKecamatan}, Kabupaten/Kota ${data.setting.desaKabupaten}`} + , dengan ini menerangkan bahwa: +
+ + {/* DATA KELAHIRAN ANAK */} +
+ Telah lahir seorang anak pada: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Tanggal Lahir:{getValue("tanggal lahir anak")}
Pukul:{getValue("pukul lahir anak")}
Tempat Kelahiran:{getValue("tempat lahir anak")}
Jenis Kelamin:{getValue("jenis kelamin anak")}
Anak ke:{getValue("anak ke")}
Nama Anak:{getValue("nama anak")}
+
+ + {/* DATA IBU */} +
+ Dari seorang ibu bernama: + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Nama Lengkap Ibu:{getValue("nama ibu")}
NIK:{getValue("nik ibu")}
Tempat & Tanggal Lahir:{getValue("tempat tanggal lahir ibu")}
Pekerjaan:{getValue("pekerjaan ibu")}
Alamat:{getValue("alamat ibu")}
+
+ + {/* DATA AYAH */} +
+ Dan seorang ayah bernama: + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Nama Lengkap Ayah:{getValue("nama ayah")}
NIK:{getValue("nik ayah")}
Tempat & Tanggal Lahir:{getValue("tempat tanggal lahir ayah")}
Pekerjaan:{getValue("pekerjaan ayah")}
Alamat:{getValue("alamat ayah")}
+
+ + {/* DATA PELAPOR */} +
+ Berdasarkan laporan dari: + + + + + + + + + + + + + + + + + + +
Nama Pelapor:{getValue("nama pelapor")}
Hubungan dengan Anak:{getValue("hubungan pelapor")}
Alamat:{getValue("alamat pelapor")}
+
+ + {/* PENUTUP */} +
+ Demikian Surat Keterangan Kelahiran ini dibuat dengan sebenarnya agar + dapat digunakan sebagaimana mestinya. +
+ + {/* TEMPAT TANGGAL */} + + + + + + + + + + + + + +
Dikeluarkan di:{data.setting.desaNama}
Pada tanggal:{data.surat.createdAt}
+ + {/* TANDA TANGAN */} +
+
+ Kepala Desa / Lurah {data.setting.desaNama} +
+ ttd perbekel{" "} +
+ {data.setting.perbekelNama}
+ NIP. {data.setting.perbekelNIP} +
+
+
+ ); } diff --git a/src/components/surat/SKKelakuanBaik.tsx b/src/components/surat/SKKelakuanBaik.tsx index fdb862d..668aebf 100644 --- a/src/components/surat/SKKelakuanBaik.tsx +++ b/src/components/surat/SKKelakuanBaik.tsx @@ -3,143 +3,153 @@ import { useEffect, useState } from "react"; import notification from "../notificationGlobal"; export default function SKKelakuanBaik({ data }: { data: any }) { - const [viewImg, setViewImg] = useState(""); - const getValue = (jenis: string) => - _.upperFirst( - data.surat.dataText.find((item: any) => item.jenis === jenis)?.value || "" - ); + const [viewImg, setViewImg] = useState(""); + const getValue = (jenis: string) => + _.upperFirst( + data.surat.dataText.find((item: any) => item.jenis === jenis)?.value || + "", + ); - const loadImage = async () => { - try { - setViewImg(""); - if (!data.setting.perbekelTTD) return; + const loadImage = async () => { + try { + setViewImg(""); + if (!data.setting.perbekelTTD) return; - const urlApi = '/api/pengaduan/image?folder=lainnya&fileName=' + data.setting.perbekelTTD; - // Fetch manual agar mendapatkan Response asli - const res = await fetch(urlApi); - if (!res.ok) - return notification({ - title: "Error", - message: "Failed to load image sign", - type: "error", - }); - const blob = await res.blob(); - const url = URL.createObjectURL(blob); + const urlApi = + "/api/pengaduan/image?folder=lainnya&fileName=" + + data.setting.perbekelTTD; + // Fetch manual agar mendapatkan Response asli + const res = await fetch(urlApi); + if (!res.ok) + return notification({ + title: "Error", + message: "Failed to load image sign", + type: "error", + }); + const blob = await res.blob(); + const url = URL.createObjectURL(blob); - setViewImg(url); - } catch (err) { - console.error("Gagal load gambar:", err); - } - }; + setViewImg(url); + } catch (err) { + console.error("Gagal load gambar:", err); + } + }; - useEffect(() => { - loadImage(); - }, [data]); - - return ( -
- - {/* HEADER */} -
- SURAT KETERANGAN KELAKUAN BAIK
- (PENGANTAR SKCK)
- Nomor: {data.surat.noSurat} -
- - {/* PEMBUKA */} -
- Yang bertanda tangan di bawah ini menerangkan dengan sebenarnya bahwa: -
- - {/* IDENTITAS PENDUDUK */} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Nama lengkap:{getValue("nama")}
NIK:{getValue("nik")}
Tempat/Tgl Lahir:{getValue("tempat tanggal lahir")}
Jenis Kelamin:{getValue("jenis kelamin")}
Agama:{getValue("agama")}
Pekerjaan:{getValue("pekerjaan")}
Alamat:{getValue("alamat")}
- - {/* ISI */} -
- Adalah benar penduduk yang berdomisili di wilayah kami dan selama tinggal di lingkungan - Desa {data.setting.desaNama}, berkelakuan baik, tidak pernah terlibat perbuatan melanggar hukum, - serta dikenal sopan dan aktif dalam kegiatan kemasyarakatan. -
- -
- Surat keterangan ini diberikan sebagai pengantar permohonan penerbitan Surat Keterangan - Catatan Kepolisian (SKCK) ke Polsek/Polres {getValue("polsek")}. -
- -
- Surat ini berlaku selama 6 (enam) bulan sejak tanggal diterbitkan, kecuali terdapat perubahan - data yang mendasar. -
- -
- Demikian surat keterangan ini dibuat dengan sebenarnya untuk dipergunakan sebagaimana mestinya. -
- - {/* TANGGAL */} - - - - - - - - - - - - - -
Dikeluarkan di:{data.setting.desaNama}
Pada tanggal:{data.surat.createdAt}
- - {/* TANDA TANGAN */} -
-
- Kepala Desa {data.setting.desaNama} -

- ttd perbekel
- {data.setting.perbekelNama}
- NIP. {data.setting.perbekelNIP} -
-
+ useEffect(() => { + loadImage(); + }, [data]); + return ( +
+ {/* HEADER */} +
+ SURAT KETERANGAN KELAKUAN BAIK +
+ (PENGANTAR SKCK) +
+ Nomor: {data.surat.noSurat}
- ); + + {/* PEMBUKA */} +
+ Yang bertanda tangan di bawah ini menerangkan dengan sebenarnya bahwa: +
+ + {/* IDENTITAS PENDUDUK */} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Nama lengkap:{getValue("nama")}
NIK:{getValue("nik")}
Tempat/Tgl Lahir:{getValue("tempat tanggal lahir")}
Jenis Kelamin:{getValue("jenis kelamin")}
Agama:{getValue("agama")}
Pekerjaan:{getValue("pekerjaan")}
Alamat:{getValue("alamat")}
+ + {/* ISI */} +
+ Adalah benar penduduk yang berdomisili di wilayah kami dan selama + tinggal di lingkungan Desa {data.setting.desaNama}, berkelakuan baik, + tidak pernah terlibat perbuatan melanggar hukum, serta dikenal sopan dan + aktif dalam kegiatan kemasyarakatan. +
+ +
+ Surat keterangan ini diberikan sebagai pengantar permohonan penerbitan + Surat Keterangan Catatan Kepolisian (SKCK) ke Polsek/Polres{" "} + {getValue("polsek")}. +
+ +
+ Surat ini berlaku selama 6 (enam) bulan sejak tanggal diterbitkan, + kecuali terdapat perubahan data yang mendasar. +
+ +
+ Demikian surat keterangan ini dibuat dengan sebenarnya untuk + dipergunakan sebagaimana mestinya. +
+ + {/* TANGGAL */} + + + + + + + + + + + + + +
Dikeluarkan di:{data.setting.desaNama}
Pada tanggal:{data.surat.createdAt}
+ + {/* TANDA TANGAN */} +
+
+ Kepala Desa {data.setting.desaNama} +

+ ttd perbekel{" "} +
+ {data.setting.perbekelNama} +
+ NIP. {data.setting.perbekelNIP} +
+
+
+ ); } diff --git a/src/components/surat/SKKematian.tsx b/src/components/surat/SKKematian.tsx index 417544b..2554422 100644 --- a/src/components/surat/SKKematian.tsx +++ b/src/components/surat/SKKematian.tsx @@ -3,118 +3,201 @@ import { useEffect, useState } from "react"; import notification from "../notificationGlobal"; export default function SKKematian({ data }: { data: any }) { - const [viewImg, setViewImg] = useState(""); - const getValue = (jenis: string) => - _.upperFirst( - data.surat.dataText.find((item: any) => item.jenis === jenis)?.value || "" - ); + const [viewImg, setViewImg] = useState(""); + const getValue = (jenis: string) => + _.upperFirst( + data.surat.dataText.find((item: any) => item.jenis === jenis)?.value || + "", + ); - const loadImage = async () => { - try { - setViewImg(""); - if (!data.setting.perbekelTTD) return; + const loadImage = async () => { + try { + setViewImg(""); + if (!data.setting.perbekelTTD) return; - const urlApi = '/api/pengaduan/image?folder=lainnya&fileName=' + data.setting.perbekelTTD; - // Fetch manual agar mendapatkan Response asli - const res = await fetch(urlApi); - if (!res.ok) - return notification({ - title: "Error", - message: "Failed to load image sign", - type: "error", - }); - const blob = await res.blob(); - const url = URL.createObjectURL(blob); + const urlApi = + "/api/pengaduan/image?folder=lainnya&fileName=" + + data.setting.perbekelTTD; + // Fetch manual agar mendapatkan Response asli + const res = await fetch(urlApi); + if (!res.ok) + return notification({ + title: "Error", + message: "Failed to load image sign", + type: "error", + }); + const blob = await res.blob(); + const url = URL.createObjectURL(blob); - setViewImg(url); - } catch (err) { - console.error("Gagal load gambar:", err); - } - }; + setViewImg(url); + } catch (err) { + console.error("Gagal load gambar:", err); + } + }; - useEffect(() => { - loadImage(); - }, [data]); - - return ( -
- {/* HEADER */} -
- PEMERINTAH KABUPATEN {_.upperCase(data.setting.desaKabupaten)}
- KECAMATAN {_.upperCase(data.setting.desaKecamatan)}
- DESA / KELURAHAN {_.upperCase(data.setting.desaNama)}
- Alamat: {data.setting.desaAlamat}
- Kode Pos: {data.setting.desaPos} -
- - {/* JUDUL */} -
- SURAT KETERANGAN KEMATIAN
- Nomor: {data.surat.noSurat} -
- - {/* YANG BERTANDA TANGAN */} -
- Yang bertanda tangan di bawah ini: - - - - - - - - -
Nama:{getValue("nama")}
NIK:{getValue("nik")}
Pekerjaan:{getValue("pekerjaan")}
Alamat:{getValue("alamat")}
Hubungan dengan almarhum/almarhumah:{getValue("hubungan dengan almarhum")}
-
- -
- Melaporkan bahwa: - - - - - - - - - -
Nama:{getValue("nama")}
NIK:{getValue("nik")}
Jenis Kelamin:{getValue("jenis kelamin")}
Tempat/Tanggal Lahir:{getValue("tempat tanggal lahir")}
Agama:{getValue("agama")}
Alamat:{getValue("alamat")}
-
- -
- Telah meninggal dunia pada: - - - - - - - -
Tanggal Kematian:{getValue("tanggal kematian")}
Waktu Kematian:{getValue("waktu kematian")}
Tempat Kematian:{getValue("tempat kematian")}
Penyebab Kematian:{getValue("penyebab kematian")}
-
- -
- Demikian surat keterangan ini dibuat dengan sebenarnya agar dapat digunakan sebagaimana mestinya. -
- - {/* TANDA TANGAN */} -
-
-

- Pemohon -




- {getValue("nama")}
-
-
-
- Kepala Desa / Lurah {data.setting.desaNama} -

- ttd perbekel
- {data.setting.perbekelNama}
- NIP. {data.setting.perbekelNIP} -
-
+ useEffect(() => { + loadImage(); + }, [data]); + return ( +
+ {/* HEADER */} +
+ PEMERINTAH KABUPATEN {_.upperCase(data.setting.desaKabupaten)} +
+ KECAMATAN {_.upperCase(data.setting.desaKecamatan)} +
+ DESA / KELURAHAN {_.upperCase(data.setting.desaNama)} +
+ Alamat: {data.setting.desaAlamat} +
+ Kode Pos: {data.setting.desaPos}
- ); + + {/* JUDUL */} +
+ + SURAT KETERANGAN KEMATIAN + +
+ Nomor: {data.surat.noSurat} +
+ + {/* YANG BERTANDA TANGAN */} +
+ Yang bertanda tangan di bawah ini: + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Nama:{getValue("nama")}
NIK:{getValue("nik")}
Pekerjaan:{getValue("pekerjaan")}
Alamat:{getValue("alamat")}
Hubungan dengan almarhum/almarhumah:{getValue("hubungan dengan almarhum")}
+
+ +
+ Melaporkan bahwa: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Nama:{getValue("nama")}
NIK:{getValue("nik")}
Jenis Kelamin:{getValue("jenis kelamin")}
Tempat/Tanggal Lahir:{getValue("tempat tanggal lahir")}
Agama:{getValue("agama")}
Alamat:{getValue("alamat")}
+
+ +
+ Telah meninggal dunia pada: + + + + + + + + + + + + + + + + + + + + + + + +
Tanggal Kematian:{getValue("tanggal kematian")}
Waktu Kematian:{getValue("waktu kematian")}
Tempat Kematian:{getValue("tempat kematian")}
Penyebab Kematian:{getValue("penyebab kematian")}
+
+ +
+ Demikian surat keterangan ini dibuat dengan sebenarnya agar dapat + digunakan sebagaimana mestinya. +
+ + {/* TANDA TANGAN */} +
+
+
+
+ Pemohon +
+
+
+

+ {getValue("nama")}
+
+
+
+ Kepala Desa / Lurah {data.setting.desaNama} +
+
+ ttd perbekel +
+ {data.setting.perbekelNama}
+ NIP. {data.setting.perbekelNIP} +
+
+
+ ); } diff --git a/src/components/surat/SKPenghasilan.tsx b/src/components/surat/SKPenghasilan.tsx index 0b920f2..b11028a 100644 --- a/src/components/surat/SKPenghasilan.tsx +++ b/src/components/surat/SKPenghasilan.tsx @@ -3,141 +3,180 @@ import { useEffect, useState } from "react"; import notification from "../notificationGlobal"; export default function SKPenghasilan({ data }: { data: any }) { - const [viewImg, setViewImg] = useState(""); - const getValue = (jenis: string) => - _.upperFirst( - data.surat.dataText.find((item: any) => item.jenis === jenis)?.value || "" - ); + const [viewImg, setViewImg] = useState(""); + const getValue = (jenis: string) => + _.upperFirst( + data.surat.dataText.find((item: any) => item.jenis === jenis)?.value || + "", + ); - const loadImage = async () => { - try { - setViewImg(""); - if (!data.setting.perbekelTTD) return; + const loadImage = async () => { + try { + setViewImg(""); + if (!data.setting.perbekelTTD) return; - const urlApi = '/api/pengaduan/image?folder=lainnya&fileName=' + data.setting.perbekelTTD; - // Fetch manual agar mendapatkan Response asli - const res = await fetch(urlApi); - if (!res.ok) - return notification({ - title: "Error", - message: "Failed to load image sign", - type: "error", - }); - const blob = await res.blob(); - const url = URL.createObjectURL(blob); + const urlApi = + "/api/pengaduan/image?folder=lainnya&fileName=" + + data.setting.perbekelTTD; + // Fetch manual agar mendapatkan Response asli + const res = await fetch(urlApi); + if (!res.ok) + return notification({ + title: "Error", + message: "Failed to load image sign", + type: "error", + }); + const blob = await res.blob(); + const url = URL.createObjectURL(blob); - setViewImg(url); - } catch (err) { - console.error("Gagal load gambar:", err); - } - }; + setViewImg(url); + } catch (err) { + console.error("Gagal load gambar:", err); + } + }; - useEffect(() => { - loadImage(); - }, [data]); + useEffect(() => { + loadImage(); + }, [data]); - return ( -
- {/* HEADER */} -
- PEMERINTAH KABUPATEN {_.upperCase(data.setting.desaKabupaten)}
- KECAMATAN {_.upperCase(data.setting.desaKecamatan)}
- DESA / KELURAHAN {_.upperCase(data.setting.desaNama)}
- Alamat: {data.setting.desaAlamat}
- Kode Pos: {data.setting.desaPos} -
- - {/* JUDUL */} -
- SURAT KETERANGAN PENGHASILAN
- Nomor: {data.surat.noSurat} -
- - {/* YANG BERTANDA TANGAN */} -
- Yang bertanda tangan di bawah ini: - - - - - - - - - - - - - - - - - - - - - - - -
Nama:{data.setting.perbekelNama}
Jabatan:{data.setting.perbekelJabatan}
Kecamatan:{data.setting.desaKecamatan}
Kabupaten:{data.setting.desaKabupaten}
-
- - {/* IDENTITAS */} -
- Dengan ini menerangkan bahwa: - - - - - - - - -
Nama:{getValue("nama")}
Jenis Kelamin:{getValue("jenis kelamin")}
Tempat / Tanggal Lahir:{getValue("tempat tanggal lahir")}
Pekerjaan:{getValue("pekerjaan")}
Alamat:{getValue("alamat")}
-
- - {/* PENGHASILAN */} -
- Berdasarkan keterangan yang bersangkutan, orang tersebut memiliki penghasilan rata-rata: - - - - - - - - -
Penghasilan: - Rp {getValue("penghasilan")} - {" "} - ({getValue("penghasilan terbilang")}) per bulan -
-
- - {/* KEPERLUAN */} -
- Surat keterangan ini dibuat untuk keperluan: {getValue("alasan permohonan")}. -
- -
- Demikian surat keterangan ini dibuat dengan sebenarnya untuk dapat dipergunakan sebagaimana mestinya. -
- - {/* TANGGAL & TANDA TANGAN */} -
- Dikeluarkan di {data.setting.desaNama}
- Pada tanggal {data.surat.createdAt} -
- -
-
- Kepala Desa / Lurah {data.setting.desaNama} -

- ttd perbekel
- {data.setting.perbekelNama}
- NIP. {data.setting.perbekelNIP} -
-
+ return ( +
+ {/* HEADER */} +
+ PEMERINTAH KABUPATEN {_.upperCase(data.setting.desaKabupaten)} +
+ KECAMATAN {_.upperCase(data.setting.desaKecamatan)} +
+ DESA / KELURAHAN {_.upperCase(data.setting.desaNama)} +
+ Alamat: {data.setting.desaAlamat} +
+ Kode Pos: {data.setting.desaPos}
- ); + + {/* JUDUL */} +
+ + SURAT KETERANGAN PENGHASILAN + +
+ Nomor: {data.surat.noSurat} +
+ + {/* YANG BERTANDA TANGAN */} +
+ Yang bertanda tangan di bawah ini: + + + + + + + + + + + + + + + + + + + + + + + +
Nama:{data.setting.perbekelNama}
Jabatan:{data.setting.perbekelJabatan}
Kecamatan:{data.setting.desaKecamatan}
Kabupaten:{data.setting.desaKabupaten}
+
+ + {/* IDENTITAS */} +
+ Dengan ini menerangkan bahwa: + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Nama:{getValue("nama")}
Jenis Kelamin:{getValue("jenis kelamin")}
Tempat / Tanggal Lahir:{getValue("tempat tanggal lahir")}
Pekerjaan:{getValue("pekerjaan")}
Alamat:{getValue("alamat")}
+
+ + {/* PENGHASILAN */} +
+ Berdasarkan keterangan yang bersangkutan, orang tersebut memiliki + penghasilan rata-rata: + + + + + + + + +
Penghasilan: + Rp {getValue("penghasilan")} ( + {getValue("penghasilan terbilang")}) per bulan +
+
+ + {/* KEPERLUAN */} +
+ Surat keterangan ini dibuat untuk keperluan:{" "} + {getValue("alasan permohonan")}. +
+ +
+ Demikian surat keterangan ini dibuat dengan sebenarnya untuk dapat + dipergunakan sebagaimana mestinya. +
+ + {/* TANGGAL & TANDA TANGAN */} +
+ Dikeluarkan di {data.setting.desaNama}
+ Pada tanggal {data.surat.createdAt} +
+ +
+
+ Kepala Desa / Lurah {data.setting.desaNama} +

+ ttd perbekel +
+ {data.setting.perbekelNama}
+ NIP. {data.setting.perbekelNIP} +
+
+
+ ); } diff --git a/src/components/surat/SKTempatUsaha.tsx b/src/components/surat/SKTempatUsaha.tsx index 96d4b28..260ee15 100644 --- a/src/components/surat/SKTempatUsaha.tsx +++ b/src/components/surat/SKTempatUsaha.tsx @@ -3,119 +3,142 @@ import { useEffect, useState } from "react"; import notification from "../notificationGlobal"; export default function SKTempatUsaha({ data }: { data: any }) { - const [viewImg, setViewImg] = useState(""); - const getValue = (key: string) => - _.upperFirst(data.surat.dataText.find((i: any) => i.jenis === key)?.value || ""); + const [viewImg, setViewImg] = useState(""); + const getValue = (key: string) => + _.upperFirst( + data.surat.dataText.find((i: any) => i.jenis === key)?.value || "", + ); - const loadImage = async () => { - try { - setViewImg(""); - if (!data.setting.perbekelTTD) return; + const loadImage = async () => { + try { + setViewImg(""); + if (!data.setting.perbekelTTD) return; - const urlApi = '/api/pengaduan/image?folder=lainnya&fileName=' + data.setting.perbekelTTD; - // Fetch manual agar mendapatkan Response asli - const res = await fetch(urlApi); - if (!res.ok) - return notification({ - title: "Error", - message: "Failed to load image sign", - type: "error", - }); - const blob = await res.blob(); - const url = URL.createObjectURL(blob); + const urlApi = + "/api/pengaduan/image?folder=lainnya&fileName=" + + data.setting.perbekelTTD; + // Fetch manual agar mendapatkan Response asli + const res = await fetch(urlApi); + if (!res.ok) + return notification({ + title: "Error", + message: "Failed to load image sign", + type: "error", + }); + const blob = await res.blob(); + const url = URL.createObjectURL(blob); - setViewImg(url); - } catch (err) { - console.error("Gagal load gambar:", err); - } - }; + setViewImg(url); + } catch (err) { + console.error("Gagal load gambar:", err); + } + }; - useEffect(() => { - loadImage(); - }, [data]); + useEffect(() => { + loadImage(); + }, [data]); - - return ( -
- {/* TITLE */} -
- SURAT KETERANGAN TEMPAT USAHA
- Nomor: {data.surat.noSurat} -
- - {/* ISI */} -
-
- Yang bertanda tangan dibawah ini, saya: -
- - {/* DATA PEJABAT */} -
- - - -
- -
- -
Dengan ini menerangkan bahwa:
- - {/* DATA WARGA */} -
- - - - -
- -
- -
Benar yang bersangkutan memiliki tempat usaha dengan keterangan seperti berikut:
- -
- - - - - - -
- -

- Surat keterangan ini dibuat untuk keperluan {getValue("alasan permohonan")}. -

- -

- Demikian surat keterangan ini dibuat dengan sebenarnya untuk dapat dipergunakan sebagaimana mestinya. -

- -
- - -
- -

- - {/* TANDA TANGAN */} -
-
- {data.setting.desaKabupaten}, {data.surat.createdAt}

- ttd perbekel
- {data.setting.perbekelNama}
- {data.setting.perbekelJabatan + " " + data.setting.desaNama} -
-
-
+ return ( +
+ {/* TITLE */} +
+ SURAT KETERANGAN TEMPAT USAHA +
+ Nomor: {data.surat.noSurat}
- ); + + {/* ISI */} +
+
+ Yang bertanda tangan dibawah ini, saya: +
+ + {/* DATA PEJABAT */} +
+ + + +
+ +
+ +
Dengan ini menerangkan bahwa:
+ + {/* DATA WARGA */} +
+ + + + +
+ +
+ +
+ Benar yang bersangkutan memiliki tempat usaha dengan keterangan + seperti berikut: +
+ +
+ + + + + + +
+ +

+ Surat keterangan ini dibuat untuk keperluan{" "} + {getValue("alasan permohonan")}. +

+ +

+ Demikian surat keterangan ini dibuat dengan sebenarnya untuk dapat + dipergunakan sebagaimana mestinya. +

+ +
+ + +
+ +
+
+ + {/* TANDA TANGAN */} +
+
+ {data.setting.desaKabupaten}, {data.surat.createdAt}

+ ttd perbekel +
+ {data.setting.perbekelNama} +
+ {data.setting.perbekelJabatan + " " + data.setting.desaNama} +
+
+
+
+ ); } -function Row({ label, value }: { label: string, value: string }) { - return ( -
-
{label}
-
:
-
{value}
-
- ); +function Row({ label, value }: { label: string; value: string }) { + return ( +
+
{label}
+
:
+
{value}
+
+ ); } diff --git a/src/components/surat/SKTidakMampu.tsx b/src/components/surat/SKTidakMampu.tsx index f483e6c..47a224f 100644 --- a/src/components/surat/SKTidakMampu.tsx +++ b/src/components/surat/SKTidakMampu.tsx @@ -3,110 +3,118 @@ import { useEffect, useState } from "react"; import notification from "../notificationGlobal"; export default function SKTidakMampu({ data }: { data: any }) { - const [viewImg, setViewImg] = useState(""); - const getValue = (key: string) => - _.upperFirst(data.surat.dataText.find((i: any) => i.jenis === key)?.value || ""); + const [viewImg, setViewImg] = useState(""); + const getValue = (key: string) => + _.upperFirst( + data.surat.dataText.find((i: any) => i.jenis === key)?.value || "", + ); + const loadImage = async () => { + try { + setViewImg(""); + if (!data.setting.perbekelTTD) return; - const loadImage = async () => { - try { - setViewImg(""); - if (!data.setting.perbekelTTD) return; + const urlApi = + "/api/pengaduan/image?folder=lainnya&fileName=" + + data.setting.perbekelTTD; + // Fetch manual agar mendapatkan Response asli + const res = await fetch(urlApi); + if (!res.ok) + return notification({ + title: "Error", + message: "Failed to load image sign", + type: "error", + }); + const blob = await res.blob(); + const url = URL.createObjectURL(blob); - const urlApi = '/api/pengaduan/image?folder=lainnya&fileName=' + data.setting.perbekelTTD; - // Fetch manual agar mendapatkan Response asli - const res = await fetch(urlApi); - if (!res.ok) - return notification({ - title: "Error", - message: "Failed to load image sign", - type: "error", - }); - const blob = await res.blob(); - const url = URL.createObjectURL(blob); + setViewImg(url); + } catch (err) { + console.error("Gagal load gambar:", err); + } + }; - setViewImg(url); - } catch (err) { - console.error("Gagal load gambar:", err); - } - }; + useEffect(() => { + loadImage(); + }, [data]); - useEffect(() => { - loadImage(); - }, [data]); - - return ( -
- {/* TITLE */} -
- SURAT KETERANGAN TIDAK MAMPU
- Nomor: {data.surat.noSurat} -
- - {/* ISI */} -
-
- Yang bertanda tangan dibawah ini, saya -
- - {/* DATA PEJABAT */} -
- - - - - -
- -
- -
Dengan ini menerangkan bahwa:
- - {/* DATA WARGA */} -
- - - - - - -
- -
- -

- Orang tersebut benar-benar penduduk desa {data.setting.desaNama} dan termasuk keluarga tidak mampu. - Surat keterangan ini dipergunakan untuk - {getValue("alasan permohonan")}. -

- -

- Demikian surat keterangan ini kami buat dengan sebenar-benarnya untuk dapat dipergunakan - sebagaimana mestinya. -

- -

- - {/* TANDA TANGAN */} -
-
- {data.setting.desaKabupaten}, {data.surat.createdAt}

- ttd perbekel
- {data.setting.perbekelNama}
- {data.setting.perbekelJabatan + " " + data.setting.desaNama} -
-
-
+ return ( +
+ {/* TITLE */} +
+ SURAT KETERANGAN TIDAK MAMPU +
+ Nomor: {data.surat.noSurat}
- ); + + {/* ISI */} +
+
+ Yang bertanda tangan dibawah ini, saya +
+ + {/* DATA PEJABAT */} +
+ + + +
+ +
+ +
Dengan ini menerangkan bahwa:
+ + {/* DATA WARGA */} +
+ + + + +
+ +
+ +

+ Orang tersebut benar-benar penduduk desa {data.setting.desaNama} dan + termasuk keluarga tidak mampu. Surat keterangan ini dipergunakan untuk + {getValue("alasan permohonan")}. +

+ +

+ Demikian surat keterangan ini kami buat dengan sebenar-benarnya untuk + dapat dipergunakan sebagaimana mestinya. +

+ +
+
+ + {/* TANDA TANGAN */} +
+
+ {data.setting.desaKabupaten}, {data.surat.createdAt}

+ ttd perbekel +
+ {data.setting.perbekelNama} +
+ {data.setting.perbekelJabatan + " " + data.setting.desaNama} +
+
+
+
+ ); } -function Row({ label, value }: { label: string, value: string }) { - return ( -
-
{label}
-
:
-
{value}
-
- ); +function Row({ label, value }: { label: string; value: string }) { + return ( +
+
{label}
+
:
+
{value}
+
+ ); } diff --git a/src/components/surat/SKUsaha.tsx b/src/components/surat/SKUsaha.tsx index 470d188..c35be99 100644 --- a/src/components/surat/SKUsaha.tsx +++ b/src/components/surat/SKUsaha.tsx @@ -3,147 +3,217 @@ import { useEffect, useState } from "react"; import notification from "../notificationGlobal"; export default function SKUsaha({ data }: { data: any }) { - const [viewImg, setViewImg] = useState(""); - const getValue = (jenis: string) => - _.upperFirst( - data.surat.dataText.find((item: any) => item.jenis === jenis)?.value || "" - ); + const [viewImg, setViewImg] = useState(""); + const getValue = (jenis: string) => + _.upperFirst( + data.surat.dataText.find((item: any) => item.jenis === jenis)?.value || + "", + ); - const loadImage = async () => { - try { - setViewImg(""); - if (!data.setting.perbekelTTD) return; + const loadImage = async () => { + try { + setViewImg(""); + if (!data.setting.perbekelTTD) return; - const urlApi = '/api/pengaduan/image?folder=lainnya&fileName=' + data.setting.perbekelTTD; - // Fetch manual agar mendapatkan Response asli - const res = await fetch(urlApi); - if (!res.ok) - return notification({ - title: "Error", - message: "Failed to load image sign", - type: "error", - }); - const blob = await res.blob(); - const url = URL.createObjectURL(blob); + const urlApi = + "/api/pengaduan/image?folder=lainnya&fileName=" + + data.setting.perbekelTTD; + // Fetch manual agar mendapatkan Response asli + const res = await fetch(urlApi); + if (!res.ok) + return notification({ + title: "Error", + message: "Failed to load image sign", + type: "error", + }); + const blob = await res.blob(); + const url = URL.createObjectURL(blob); - setViewImg(url); - } catch (err) { - console.error("Gagal load gambar:", err); - } - }; + setViewImg(url); + } catch (err) { + console.error("Gagal load gambar:", err); + } + }; - useEffect(() => { - loadImage(); - }, [data]); - - return ( -
- {/* HEADER */} -
- PEMERINTAH KABUPATEN {_.upperCase(data.setting.desaKabupaten)}
- KECAMATAN {_.upperCase(data.setting.desaKecamatan)}
- DESA / KELURAHAN {_.upperCase(data.setting.desaNama)}
- Alamat: {data.setting.desaAlamat}
- Kode Pos: {data.setting.desaPos} -
- - {/* JUDUL */} -
- SURAT KETERANGAN USAHA
- Nomor: {data.surat.noSurat} -
- - {/* YANG BERTANDA TANGAN */} -
- Yang bertanda tangan di bawah ini: - - - - - - - - - - - - - - - - - - - - - - - -
Nama:{data.setting.perbekelNama}
Jabatan:{data.setting.perbekelJabatan}
Kecamatan:{data.setting.desaKecamatan}
Kabupaten:{data.setting.desaKabupaten}
-
- - {/* IDENTITAS ORANG YG MEMINTA SURAT */} -
- Dengan ini menerangkan dengan sesungguhnya bahwa: - - - - - - - - - - - -
Nama:{getValue("nama")}
Jenis Kelamin:{getValue("jenis kelamin")}
Tempat / Tanggal Lahir:{getValue("tempat tanggal lahir")}
Warga Negara:{getValue("negara")}
Agama:{getValue("agama")}
Status:{getValue("status perkawinan")}
Pekerjaan:{getValue("pekerjaan")}
Alamat:{getValue("alamat")}
-
- - {/* DOMISILI */} -
- Bahwa orang tersebut di atas benar-benar penduduk: - - - - - - -
Desa / Kelurahan:{data.setting.desaNama}
Kecamatan:{data.setting.desaKecamatan}
Kabupaten:{data.setting.desaKabupaten}
-
- - {/* USAHA */} -
- Dan yang bersangkutan benar memiliki usaha: - - - - - -
Jenis Usaha:{getValue("jenis usaha")}
Alamat Usaha:{getValue("alamat usaha")}
-
- -
- Surat keterangan ini dibuat dengan sebenarnya untuk dipergunakan sebagaimana mestinya. -
- -
- Dikeluarkan di {data.setting.desaNama}
- Pada tanggal {data.surat.createdAt} -
- - {/* TANDA TANGAN */} -
-
-
- Kepala Desa / Lurah {data.setting.desaNama} -

- ttd perbekel -
- {data.setting.perbekelNama}
- NIP. {data.setting.perbekelNIP} -
-
+ useEffect(() => { + loadImage(); + }, [data]); + return ( +
+ {/* HEADER */} +
+ PEMERINTAH KABUPATEN {_.upperCase(data.setting.desaKabupaten)} +
+ KECAMATAN {_.upperCase(data.setting.desaKecamatan)} +
+ DESA / KELURAHAN {_.upperCase(data.setting.desaNama)} +
+ Alamat: {data.setting.desaAlamat} +
+ Kode Pos: {data.setting.desaPos}
- ); + + {/* JUDUL */} +
+ + SURAT KETERANGAN USAHA + +
+ Nomor: {data.surat.noSurat} +
+ + {/* YANG BERTANDA TANGAN */} +
+ Yang bertanda tangan di bawah ini: + + + + + + + + + + + + + + + + + + + + + + + +
Nama:{data.setting.perbekelNama}
Jabatan:{data.setting.perbekelJabatan}
Kecamatan:{data.setting.desaKecamatan}
Kabupaten:{data.setting.desaKabupaten}
+
+ + {/* IDENTITAS ORANG YG MEMINTA SURAT */} +
+ Dengan ini menerangkan dengan sesungguhnya bahwa: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Nama:{getValue("nama")}
Jenis Kelamin:{getValue("jenis kelamin")}
Tempat / Tanggal Lahir:{getValue("tempat tanggal lahir")}
Warga Negara:{getValue("negara")}
Agama:{getValue("agama")}
Status:{getValue("status perkawinan")}
Pekerjaan:{getValue("pekerjaan")}
Alamat:{getValue("alamat")}
+
+ + {/* DOMISILI */} +
+ Bahwa orang tersebut di atas benar-benar penduduk: + + + + + + + + + + + + + + + + + + +
Desa / Kelurahan:{data.setting.desaNama}
Kecamatan:{data.setting.desaKecamatan}
Kabupaten:{data.setting.desaKabupaten}
+
+ + {/* USAHA */} +
+ Dan yang bersangkutan benar memiliki usaha: + + + + + + + + + + + + + +
Jenis Usaha:{getValue("jenis usaha")}
Alamat Usaha:{getValue("alamat usaha")}
+
+ +
+ Surat keterangan ini dibuat dengan sebenarnya untuk dipergunakan + sebagaimana mestinya. +
+ +
+ Dikeluarkan di {data.setting.desaNama}
+ Pada tanggal {data.surat.createdAt} +
+ + {/* TANDA TANGAN */} +
+
+
+ Kepala Desa / Lurah {data.setting.desaNama} +
+
+ ttd perbekel +
+ {data.setting.perbekelNama}
+ NIP. {data.setting.perbekelNIP} +
+
+
+ ); } diff --git a/src/components/surat/SKYatimPiatu.tsx b/src/components/surat/SKYatimPiatu.tsx index e8c18f8..c2a86c1 100644 --- a/src/components/surat/SKYatimPiatu.tsx +++ b/src/components/surat/SKYatimPiatu.tsx @@ -4,191 +4,209 @@ import { useState } from "react"; import notification from "../notificationGlobal"; export default function SKYatim({ data }: { data: any }) { - const [viewImg, setViewImg] = useState(""); - const getValue = (key: string) => - _.upperFirst(data.surat.dataText.find((i: any) => i.jenis === key)?.value || ""); + const [viewImg, setViewImg] = useState(""); + const getValue = (key: string) => + _.upperFirst( + data.surat.dataText.find((i: any) => i.jenis === key)?.value || "", + ); - const loadImage = async () => { - try { - setViewImg(""); - if (!data.setting.perbekelTTD) return; + const loadImage = async () => { + try { + setViewImg(""); + if (!data.setting.perbekelTTD) return; - const urlApi = '/api/pengaduan/image?folder=lainnya&fileName=' + data.setting.perbekelTTD; - // Fetch manual agar mendapatkan Response asli - const res = await fetch(urlApi); - if (!res.ok) - return notification({ - title: "Error", - message: "Failed to load image sign", - type: "error", - }); - const blob = await res.blob(); - const url = URL.createObjectURL(blob); + const urlApi = + "/api/pengaduan/image?folder=lainnya&fileName=" + + data.setting.perbekelTTD; + // Fetch manual agar mendapatkan Response asli + const res = await fetch(urlApi); + if (!res.ok) + return notification({ + title: "Error", + message: "Failed to load image sign", + type: "error", + }); + const blob = await res.blob(); + const url = URL.createObjectURL(blob); - setViewImg(url); - } catch (err) { - console.error("Gagal load gambar:", err); - } - }; + setViewImg(url); + } catch (err) { + console.error("Gagal load gambar:", err); + } + }; - useShallowEffect(() => { - loadImage(); - }, [data]); - - return ( -
- - {/* HEADER */} -
- PEMERINTAH KABUPATEN {_.upperCase(data.setting.desaKabupaten)}
- KECAMATAN {_.upperCase(data.setting.desaKecamatan)}
- DESA {_.upperCase(data.setting.desaNama)}
- Alamat: {data.setting.desaAlamat}. Kode Pos: {data.setting.desaPos} -
- -
- SURAT KETERANGAN YATIM / PIATU / YATIM PIATU
- Nomor: {data.surat.noSurat} -
- -
- - {/* BAGIAN PENANDATANGAN */} -
Yang bertanda tangan di bawah ini:
- - - - - - - - - - - - - - - -
Nama: {data.setting.perbekelNama}
Jabatan: {data.setting.perbekelJabatan}
Alamat Kantor: {data.setting.desaAlamat}
- -
- - {/* BAGIAN IDENTITAS ANAK */} -
Dengan ini menerangkan bahwa:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Nama: {getValue("nama")}
Tempat/Tanggal Lahir: {getValue("tempat tanggal lahir")}
Jenis Kelamin: {getValue("jenis kelamin")}
Alamat: {getValue("alamat")}
NIK: {getValue("nik")}
Pekerjaan: {getValue("pekerjaan")}
- -
- - {/* KETERANGAN ORANG TUA */} -
- Benar bahwa yang bersangkutan adalah anak (Yatim / Piatu / Yatim Piatu), - dengan keterangan sebagai berikut: -
- -
- -
1. Nama Ayah
- - - - - - - - - - - -
Nama Ayah: {getValue("nama ayah")}
Status: {getValue("status ayah")}
- -
- -
2. Nama Ibu
- - - - - - - - - - - -
Nama Ibu: {getValue("nama ibu")}
Status: {getValue("status ibu")}
- -
- -
- Dengan demikian, berdasarkan keterangan pihak keluarga dan data di Kantor Desa, - maka benar bahwa yang bersangkutan adalah - anak (Yatim / Piatu / Yatim Piatu). -
- -
- -
- Surat keterangan ini dibuat dengan sebenar-benarnya untuk dipergunakan sebagaimana mestinya. -
- -
- - {/* TANGGAL & TEMPAT */} - - - - - - - - - - - -
Dikeluarkan di: {data.setting.desaNama}
Pada tanggal: {data.surat.createdAt}
- -
- - {/* TTD */} -
-
- Kepala Desa {data.setting.desaNama} -

- ttd perbekel
- {data.setting.perbekelNama}
- NIP. {data.setting.perbekelNIP} -
-
+ useShallowEffect(() => { + loadImage(); + }, [data]); + return ( +
+ {/* HEADER */} +
+ PEMERINTAH KABUPATEN {_.upperCase(data.setting.desaKabupaten)} +
+ KECAMATAN {_.upperCase(data.setting.desaKecamatan)} +
+ DESA {_.upperCase(data.setting.desaNama)} +
+ Alamat: {data.setting.desaAlamat}. Kode Pos: {data.setting.desaPos}
- ); + +
+ + SURAT KETERANGAN YATIM / PIATU / YATIM PIATU + +
+ Nomor: {data.surat.noSurat} +
+ +
+ + {/* BAGIAN PENANDATANGAN */} +
Yang bertanda tangan di bawah ini:
+ + + + + + + + + + + + + + + +
Nama: {data.setting.perbekelNama}
Jabatan: {data.setting.perbekelJabatan}
Alamat Kantor: {data.setting.desaAlamat}
+ +
+ + {/* BAGIAN IDENTITAS ANAK */} +
Dengan ini menerangkan bahwa:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Nama: {getValue("nama")}
Tempat/Tanggal Lahir: {getValue("tempat tanggal lahir")}
Jenis Kelamin: {getValue("jenis kelamin")}
Alamat: {getValue("alamat")}
NIK: {getValue("nik")}
Pekerjaan: {getValue("pekerjaan")}
+ +
+ + {/* KETERANGAN ORANG TUA */} +
+ Benar bahwa yang bersangkutan adalah{" "} + anak (Yatim / Piatu / Yatim Piatu), dengan keterangan sebagai + berikut: +
+ +
+ +
+ 1. Nama Ayah +
+ + + + + + + + + + + +
Nama Ayah: {getValue("nama ayah")}
Status: {getValue("status ayah")}
+ +
+ +
+ 2. Nama Ibu +
+ + + + + + + + + + + +
Nama Ibu: {getValue("nama ibu")}
Status: {getValue("status ibu")}
+ +
+ +
+ Dengan demikian, berdasarkan keterangan pihak keluarga dan data di + Kantor Desa, maka benar bahwa yang bersangkutan adalah + anak (Yatim / Piatu / Yatim Piatu). +
+ +
+ +
+ Surat keterangan ini dibuat dengan sebenar-benarnya untuk dipergunakan + sebagaimana mestinya. +
+ +
+ + {/* TANGGAL & TEMPAT */} + + + + + + + + + + + +
Dikeluarkan di: {data.setting.desaNama}
Pada tanggal: {data.surat.createdAt}
+ +
+ + {/* TTD */} +
+
+ Kepala Desa {data.setting.desaNama} +
+
+ ttd perbekel{" "} +
+ {data.setting.perbekelNama}
+ NIP. {data.setting.perbekelNIP} +
+
+
+ ); } diff --git a/src/pages/Login.tsx b/src/pages/Login.tsx index 6c9a759..da726da 100644 --- a/src/pages/Login.tsx +++ b/src/pages/Login.tsx @@ -28,16 +28,19 @@ export default function Login() { window.location.href = clientRoutes["/scr/dashboard/warga/list-warga"]; break; case "credential": - window.location.href = clientRoutes["/scr/dashboard/credential/credential"]; + window.location.href = + clientRoutes["/scr/dashboard/credential/credential"]; break; case "setting": - window.location.href = clientRoutes["/scr/dashboard/setting/detail-setting"]; + window.location.href = + clientRoutes["/scr/dashboard/setting/detail-setting"]; break; case "api_key": window.location.href = clientRoutes["/scr/dashboard/apikey/apikey"]; break; case "pelayanan": - window.location.href = clientRoutes["/scr/dashboard/pelayanan-surat/list-pelayanan"]; + window.location.href = + clientRoutes["/scr/dashboard/pelayanan-surat/list-pelayanan"]; break; default: window.location.href = clientRoutes["/scr/dashboard"]; diff --git a/src/pages/darmasaba/surat.tsx b/src/pages/darmasaba/surat.tsx new file mode 100644 index 0000000..be6a19f --- /dev/null +++ b/src/pages/darmasaba/surat.tsx @@ -0,0 +1,331 @@ +import apiFetch from "@/lib/apiFetch"; +import { + ActionIcon, + Badge, + Box, + Button, + Card, + Container, + Divider, + Grid, + Group, + Select, + Stack, + Text, + TextInput, + Textarea, + Tooltip +} from "@mantine/core"; +import { useShallowEffect } from "@mantine/hooks"; +import { + IconBuildingCommunity, + IconInfoCircle, + IconMapPin, + IconUser +} from "@tabler/icons-react"; +import React from "react"; +import useSWR from "swr"; + +// ========================= +// Reusable UI components +// ========================= + +function FieldLabel({ label, hint }: { label: string; hint?: string }) { + return ( + + {label} + {hint && ( + + + + + + )} + + ); +} + +function FormSection({ + title, + icon, + children, + description, +}: { + title: string; + icon?: React.ReactNode; + children: React.ReactNode; + description?: string; +}) { + return ( + + + + {icon} + {title} + + {description && {description}} + + + + {children} + + ); +} + +// ========================= +// Main form component +// ========================= + +export default function FormSurat() { + const { data, mutate, isLoading } = useSWR("category-pelayanan-list", () => + apiFetch.api.pelayanan.category.get(), + ); + + const listCategory = data?.data || []; + + + useShallowEffect(() => { + mutate(); + }, []); + + return ( + + + + + + +
+ + Surat Keterangan Tidak Mampu (SKTM) + + + Blangko resmi untuk pengajuan Surat Keterangan Tidak Mampu — + digunakan untuk keperluan pendidikan, kesehatan, atau + administrasi. + +
+
+ + Form Length: 3 Sections + +
+ +
+ + {/* Header Section */} + } + description="Informasi identitas pemohon" + > + + + + } + placeholder="Budi Setiawan" + /> + + + + + } + placeholder="08123456789" + /> + + + + } + placeholder="Pilih jenis kelamin" + data={["Laki-laki", "Perempuan"]} + /> + + + + } + placeholder="Pilih status" + data={[ + "Belum Kawin", + "Kawin", + "Cerai Hidup", + "Cerai Mati", + ]} + /> + + + + } + placeholder="Pekerjaan" + /> + + + +