diff --git a/src/components/KategoriPelayananSurat.tsx b/src/components/KategoriPelayananSurat.tsx
index da0752b..f39ba3e 100644
--- a/src/components/KategoriPelayananSurat.tsx
+++ b/src/components/KategoriPelayananSurat.tsx
@@ -13,7 +13,7 @@ import {
Table,
Text,
Title,
- Tooltip
+ Tooltip,
} from "@mantine/core";
import { useDisclosure, useShallowEffect } from "@mantine/hooks";
import { IconEdit, IconEye, IconPlus, IconTrash } from "@tabler/icons-react";
@@ -190,7 +190,10 @@ export default function KategoriPelayananSurat({
function handleAddSyarat() {
setDataChoose({
...dataChoose,
- syaratDokumen: [...dataChoose.syaratDokumen, { key: "", name: "", desc: "" }],
+ syaratDokumen: [
+ ...dataChoose.syaratDokumen,
+ { key: "", name: "", desc: "" },
+ ],
});
}
diff --git a/src/components/NotFoundPengajuanSurat.tsx b/src/components/NotFoundPengajuanSurat.tsx
index e63ea48..95252b2 100644
--- a/src/components/NotFoundPengajuanSurat.tsx
+++ b/src/components/NotFoundPengajuanSurat.tsx
@@ -1,33 +1,27 @@
-import {
- Button,
- Center,
- Group,
- Stack,
- Text,
- Title
-} from "@mantine/core"
-import { IconSearch } from "@tabler/icons-react"
+import { Button, Center, Group, Stack, Text, Title } from "@mantine/core";
+import { IconSearch } from "@tabler/icons-react";
export function DataNotFound({
- onRetry,
- backTo
+ onRetry,
+ backTo,
}: {
- onRetry?: () => void
- backTo?: () => void
+ onRetry?: () => void;
+ backTo?: () => void;
}) {
- return (
-
-
-
+ return (
+
+
+
- Data Pengajuan Tidak Ditemukan
+ Data Pengajuan Tidak Ditemukan
-
- Kami tidak dapat menemukan data pengajuan dengan nomor pengajuan yg diinputkan. Silakan periksa kembali data Anda.
-
+
+ Kami tidak dapat menemukan data pengajuan dengan nomor pengajuan yg
+ diinputkan. Silakan periksa kembali data Anda.
+
-
- {/* {onRetry && (
+
+ {/* {onRetry && (
}
@@ -37,15 +31,15 @@ export function DataNotFound({
)} */}
- }
- onClick={backTo}
- >
- Cari Kembali
-
-
-
-
- )
+ }
+ onClick={backTo}
+ >
+ Cari Kembali
+
+
+
+
+ );
}
diff --git a/src/components/SuccessPengajuanSurat.tsx b/src/components/SuccessPengajuanSurat.tsx
index 9a21128..355b00c 100644
--- a/src/components/SuccessPengajuanSurat.tsx
+++ b/src/components/SuccessPengajuanSurat.tsx
@@ -4,13 +4,13 @@ import { IconCheck } from "@tabler/icons-react";
type SuccessPengajuanProps = {
noPengajuan: string;
onClose?: () => void;
- category?: 'create' | 'update';
+ category?: "create" | "update";
};
export default function SuccessPengajuan({
noPengajuan,
onClose,
- category
+ category,
}: SuccessPengajuanProps) {
return (
@@ -19,11 +19,15 @@ export default function SuccessPengajuan({
- {category == 'create' ? 'Pengajuan Berhasil Dibuat' : 'Pengajuan Berhasil Diupdate'}
+ {category == "create"
+ ? "Pengajuan Berhasil Dibuat"
+ : "Pengajuan Berhasil Diupdate"}
- {category == 'create' ? 'Pengajuan layanan surat sudah dibuat dengan nomor:' : 'Pengajuan layanan surat sudah diupdate dengan nomor:'}
+ {category == "create"
+ ? "Pengajuan layanan surat sudah dibuat dengan nomor:"
+ : "Pengajuan layanan surat sudah diupdate dengan nomor:"}
diff --git a/src/components/surat/SKBedaBiodataDiri.tsx b/src/components/surat/SKBedaBiodataDiri.tsx
index 59efc34..241937f 100644
--- a/src/components/surat/SKBedaBiodataDiri.tsx
+++ b/src/components/surat/SKBedaBiodataDiri.tsx
@@ -7,7 +7,7 @@ export default function SKBedaBiodataDiri({ data }: { data: any }) {
const getValue = (jenis: string) =>
_.upperFirst(
data.surat.dataText.find((item: any) => item.jenis === jenis)?.value ||
- "",
+ "",
);
const loadImage = async () => {
@@ -146,9 +146,7 @@ export default function SKBedaBiodataDiri({ data }: { data: any }) {
1. {getValue("data_dokumen")}
-
- {/* {getValue("nama")} */}
-
+ {/* {getValue("nama")} */}
Tertulis pada dokumen A
diff --git a/src/components/surat/SKBelumKawin.tsx b/src/components/surat/SKBelumKawin.tsx
index 9033893..66640ef 100644
--- a/src/components/surat/SKBelumKawin.tsx
+++ b/src/components/surat/SKBelumKawin.tsx
@@ -7,7 +7,7 @@ export default function SKBelumKawin({ data }: { data: any }) {
const getValue = (jenis: string) =>
_.upperFirst(
data.surat.dataText.find((item: any) => item.jenis === jenis)?.value ||
- "",
+ "",
);
const loadImage = async () => {
diff --git a/src/components/surat/SKKematian.tsx b/src/components/surat/SKKematian.tsx
index ad0654a..740e135 100644
--- a/src/components/surat/SKKematian.tsx
+++ b/src/components/surat/SKKematian.tsx
@@ -7,7 +7,7 @@ export default function SKKematian({ data }: { data: any }) {
const getValue = (jenis: string) =>
_.upperFirst(
data.surat.dataText.find((item: any) => item.jenis === jenis)?.value ||
- "",
+ "",
);
const loadImage = async () => {
diff --git a/src/components/surat/SKPenghasilan.tsx b/src/components/surat/SKPenghasilan.tsx
index 72c07fe..173a7ce 100644
--- a/src/components/surat/SKPenghasilan.tsx
+++ b/src/components/surat/SKPenghasilan.tsx
@@ -135,9 +135,7 @@ export default function SKPenghasilan({ data }: { data: any }) {
Penghasilan
:
-
- Rp. {getValue("penghasilan")} per bulan
-
+ Rp. {getValue("penghasilan")} per bulan
@@ -145,8 +143,8 @@ export default function SKPenghasilan({ data }: { data: any }) {
{/* KEPERLUAN */}
- Surat keterangan ini dibuat untuk keperluan:{" "}
- {getValue("alasan")} .
+ Surat keterangan ini dibuat untuk keperluan: {getValue("alasan")}
+ .
diff --git a/src/components/surat/SKTempatUsaha.tsx b/src/components/surat/SKTempatUsaha.tsx
index fd20642..e0bb245 100644
--- a/src/components/surat/SKTempatUsaha.tsx
+++ b/src/components/surat/SKTempatUsaha.tsx
@@ -71,7 +71,10 @@ export default function SKTempatUsaha({ data }: { data: any }) {
label="Tempat/Tanggal Lahir"
value={`${getValue("tempat_lahir")}, ${getValue("tanggal_lahir")}`}
/>
-
+
@@ -86,14 +89,8 @@ export default function SKTempatUsaha({ data }: { data: any }) {
-
-
+
+
diff --git a/src/components/surat/SKUsaha.tsx b/src/components/surat/SKUsaha.tsx
index 4d604c7..25ab268 100644
--- a/src/components/surat/SKUsaha.tsx
+++ b/src/components/surat/SKUsaha.tsx
@@ -7,7 +7,7 @@ export default function SKUsaha({ data }: { data: any }) {
const getValue = (jenis: string) =>
_.upperFirst(
data.surat.dataText.find((item: any) => item.jenis === jenis)?.value ||
- "",
+ "",
);
const loadImage = async () => {
diff --git a/src/components/surat/SKYatimPiatu.tsx b/src/components/surat/SKYatimPiatu.tsx
index 69e74d0..46c621c 100644
--- a/src/components/surat/SKYatimPiatu.tsx
+++ b/src/components/surat/SKYatimPiatu.tsx
@@ -98,7 +98,9 @@ export default function SKYatim({ data }: { data: any }) {
Tempat/Tanggal Lahir
- : {`${getValue("tempat_lahir")}, ${getValue("tanggal_lahir")}`}
+
+ : {`${getValue("tempat_lahir")}, ${getValue("tanggal_lahir")}`}
+
Jenis Kelamin
diff --git a/src/index.tsx b/src/index.tsx
index 1622581..cc5738b 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -15,6 +15,7 @@ import LayananRoute from "./server/routes/layanan_route";
import { MCPRoute } from "./server/routes/mcp_route";
import PelayananRoute from "./server/routes/pelayanan_surat_route";
import PengaduanRoute from "./server/routes/pengaduan_route";
+import SendWaRoute from "./server/routes/send_wa_route";
import SuratRoute from "./server/routes/surat_route";
import TestPengaduanRoute from "./server/routes/test_pengaduan";
import UserRoute from "./server/routes/user_route";
@@ -45,7 +46,8 @@ const Api = new Elysia({
.use(CredentialRoute)
.use(UserRoute)
.use(LayananRoute)
- .use(AduanRoute);
+ .use(AduanRoute)
+ .use(SendWaRoute);
const app = new Elysia()
.use(Api)
diff --git a/src/pages/darmasaba/surat.tsx b/src/pages/darmasaba/surat.tsx
index 0b6d33b..66223e2 100644
--- a/src/pages/darmasaba/surat.tsx
+++ b/src/pages/darmasaba/surat.tsx
@@ -31,7 +31,7 @@ import {
IconInfoCircle,
IconNotes,
IconPhone,
- IconUpload
+ IconUpload,
} from "@tabler/icons-react";
import dayjs from "dayjs";
import "dayjs/locale/id";
@@ -268,9 +268,7 @@ export default function FormSurat() {
overlayProps={{ backgroundOpacity: 0.55, blur: 3 }}
>
-
- Apakah anda yakin ingin mengirim pengajuan surat ini?
-
+ Apakah anda yakin ingin mengirim pengajuan surat ini?
Tidak
@@ -298,8 +296,7 @@ export default function FormSurat() {
}}
category="create"
/>
- )
- :
+ ) : (
@@ -348,7 +345,6 @@ export default function FormSurat() {
-
{/* Kontak Section */}
- {jenisSuratFix.id != "" && dataSurat && dataSurat.dataPelengkap && (
- <>
- }
- >
-
- {dataSurat.dataPelengkap.map((item: any, index: number) => (
-
- {
- item.type == "enum"
- ?
-
- }
- data={item.options ?? []}
- placeholder={item.name}
- onChange={(e) => {
- validationForm({
- key: "dataPelengkap",
- value: { key: item.key, value: e }
- })
- }}
- value={formSurat.dataPelengkap.find((n: any) => n.key == item.key)?.value}
- />
- : item.type == "date"
- ?
+ {jenisSuratFix.id != "" &&
+ dataSurat &&
+ dataSurat.dataPelengkap && (
+ <>
+ }
+ >
+
+ {dataSurat.dataPelengkap.map(
+ (item: any, index: number) => (
+
+ {item.type == "enum" ? (
+
+ }
+ data={item.options ?? []}
+ placeholder={item.name}
+ onChange={(e) => {
+ validationForm({
+ key: "dataPelengkap",
+ value: { key: item.key, value: e },
+ });
+ }}
+ value={
+ formSurat.dataPelengkap.find(
+ (n: any) => n.key == item.key,
+ )?.value
+ }
+ />
+ ) : item.type == "date" ? (
+
}
placeholder={item.name}
onChange={(e) => {
const formatted = e
- ? dayjs(e).locale("id").format("DD MMMM YYYY")
+ ? dayjs(e)
+ .locale("id")
+ .format("DD MMMM YYYY")
: "";
validationForm({
key: "dataPelengkap",
- value: { key: item.key, value: formatted },
- })
+ value: {
+ key: item.key,
+ value: formatted,
+ },
+ });
}}
/>
- :
+ ) : (
+
}
placeholder={item.name}
onChange={(e) =>
validationForm({
key: "dataPelengkap",
- value: { key: item.key, value: e.target.value },
+ value: {
+ key: item.key,
+ value: e.target.value,
+ },
})
}
value={
@@ -450,52 +467,53 @@ export default function FormSurat() {
)?.value
}
/>
- }
+ )}
+
+ ),
+ )}
+
+
-
- ))}
-
-
+ }
+ >
+
+ {dataSurat.syaratDokumen.map(
+ (item: any, index: number) => (
+
+
+ validationForm({
+ key: "syaratDokumen",
+ value: { key: item.key, value: file },
+ })
+ }
+ name={item.name}
+ />
+
+ ),
+ )}
+
+
- }
- >
-
- {dataSurat.syaratDokumen.map((item: any, index: number) => (
-
-
- validationForm({
- key: "syaratDokumen",
- value: { key: item.key, value: file },
- })
- }
- name={item.name}
- />
-
- ))}
-
-
-
- {/* Actions */}
-
- {/* { }}>
+ {/* Actions */}
+
+ {/* { }}>
Reset
*/}
- Kirim
-
- >
- )}
+ Kirim
+
+ >
+ )}
- }
-
+ )}
);
}
diff --git a/src/pages/darmasaba/update_data_surat.tsx b/src/pages/darmasaba/update_data_surat.tsx
index 43b4aae..aaa4b28 100644
--- a/src/pages/darmasaba/update_data_surat.tsx
+++ b/src/pages/darmasaba/update_data_surat.tsx
@@ -6,34 +6,34 @@ import SuccessPengajuan from "@/components/SuccessPengajuanSurat";
import apiFetch from "@/lib/apiFetch";
import { parseTanggalID } from "@/server/lib/stringToDate";
import {
- ActionIcon,
- Alert,
- Anchor,
- Badge,
- Box,
- Button,
- Card,
- Container,
- Divider,
- FileInput,
- Flex,
- Grid,
- Group,
- Modal,
- Select,
- Stack,
- Text,
- TextInput,
- Tooltip
+ ActionIcon,
+ Alert,
+ Anchor,
+ Badge,
+ Box,
+ Button,
+ Card,
+ Container,
+ Divider,
+ FileInput,
+ Flex,
+ Grid,
+ Group,
+ Modal,
+ Select,
+ Stack,
+ Text,
+ TextInput,
+ Tooltip,
} from "@mantine/core";
import { DateInput } from "@mantine/dates";
import { useDisclosure, useShallowEffect } from "@mantine/hooks";
import {
- IconBuildingCommunity,
- IconFiles,
- IconInfoCircle,
- IconNotes,
- IconUpload
+ IconBuildingCommunity,
+ IconFiles,
+ IconInfoCircle,
+ IconNotes,
+ IconUpload,
} from "@tabler/icons-react";
import dayjs from "dayjs";
import "dayjs/locale/id";
@@ -42,645 +42,689 @@ import React, { useState } from "react";
import { useLocation, useNavigate } from "react-router-dom";
type DataItem = {
- key: string;
- value: string;
+ key: string;
+ value: string;
};
type UpdateDataItem = {
- id: string;
- key: string;
- value: any;
+ id: string;
+ key: string;
+ value: any;
};
type FormSurat = {
- kategoriId: string;
- nama: string;
- phone: string;
- dataPelengkap: DataItem[];
- syaratDokumen: DataItem[];
+ kategoriId: string;
+ nama: string;
+ phone: string;
+ dataPelengkap: DataItem[];
+ syaratDokumen: DataItem[];
};
type FormUpdateSurat = {
- dataPelengkap: UpdateDataItem[];
- syaratDokumen: UpdateDataItem[];
+ dataPelengkap: UpdateDataItem[];
+ syaratDokumen: UpdateDataItem[];
};
type DataPengajuan = {
- id: string;
- noPengajuan: string;
- category: string;
- status: "antrian" | "diproses" | "selesai" | "ditolak";
- createdAt: Date;
- updatedAt: Date;
- idSurat: string | undefined;
-}
+ id: string;
+ noPengajuan: string;
+ category: string;
+ status: "antrian" | "diproses" | "selesai" | "ditolak";
+ createdAt: Date;
+ updatedAt: Date;
+ idSurat: string | undefined;
+};
export default function UpdateDataSurat() {
- const navigate = useNavigate();
- const { search } = useLocation();
- const query = new URLSearchParams(search);
- const noPengajuan = query.get("pengajuan");
- const [found, setFound] = useState(true);
+ const navigate = useNavigate();
+ const { search } = useLocation();
+ const query = new URLSearchParams(search);
+ const noPengajuan = query.get("pengajuan");
+ const [found, setFound] = useState(true);
-
- return (
-
-
-
- {
- found &&
-
-
-
-
-
- Update Data Pengajuan Surat Administrasi
-
-
- Formulir ini digunakan untuk memperbarui data pengajuan surat administrasi yang telah diajukan sebelumnya.
-
-
-
-
- }
-
- {
- !noPengajuan ? (
-
- )
- :
- found ? (
- { setFound(e) }} />
- ) : (
- navigate("/darmasaba/update-data-surat")} />
- )
- }
-
-
-
-
- );
+ return (
+
+
+
+ {found && (
+
+
+
+
+
+ Update Data Pengajuan Surat Administrasi
+
+
+ Formulir ini digunakan untuk memperbarui data pengajuan
+ surat administrasi yang telah diajukan sebelumnya.
+
+
+
+
+ )}
+
+ {!noPengajuan ? (
+
+ ) : found ? (
+ {
+ setFound(e);
+ }}
+ />
+ ) : (
+ navigate("/darmasaba/update-data-surat")}
+ />
+ )}
+
+
+
+
+ );
}
function FieldLabel({ label, hint }: { label: string; hint?: string }) {
- return (
-
- {label}
- {hint && (
-
-
-
-
-
- )}
-
- );
+ return (
+
+ {label}
+ {hint && (
+
+
+
+
+
+ )}
+
+ );
}
function FormSection({
- title,
- icon,
- children,
- description,
- info,
+ title,
+ icon,
+ children,
+ description,
+ info,
}: {
- title?: string;
- icon?: React.ReactNode;
- children: React.ReactNode;
- description?: string;
- info?: string;
+ title?: string;
+ icon?: React.ReactNode;
+ children: React.ReactNode;
+ description?: string;
+ info?: string;
}) {
- return (
-
-
-
-
- {icon}
- {title && {title} }
-
- {description && {description} }
-
- {info && {info} }
-
- {
- title &&
- }
- {children}
-
- );
+ return (
+
+
+
+
+ {icon}
+ {title && {title} }
+
+ {description && {description} }
+
+ {info && (
+
+ {info}
+
+ )}
+
+ {title && }
+ {children}
+
+ );
}
function FileInputWrapper({
- label,
- placeholder,
- accept,
- onChange,
- preview,
- name,
- description,
- linkView,
- disabled
+ label,
+ placeholder,
+ accept,
+ onChange,
+ preview,
+ name,
+ description,
+ linkView,
+ disabled,
}: {
- label: string;
- placeholder?: string;
- accept?: string;
- linkView?: string;
- onChange: (file: File | null) => void;
- preview?: string | null;
- name: string;
- description?: string;
- disabled?: boolean;
+ label: string;
+ placeholder?: string;
+ accept?: string;
+ linkView?: string;
+ onChange: (file: File | null) => void;
+ preview?: string | null;
+ name: string;
+ description?: string;
+ disabled?: boolean;
}) {
- const [viewImg, setViewImg] = useState("");
- const [openedPreviewFile, setOpenedPreviewFile] = useState(false);
+ const [viewImg, setViewImg] = useState("");
+ const [openedPreviewFile, setOpenedPreviewFile] = useState(false);
+ useShallowEffect(() => {
+ if (viewImg) {
+ setOpenedPreviewFile(true);
+ }
+ }, [viewImg]);
- useShallowEffect(() => {
- if (viewImg) {
- setOpenedPreviewFile(true);
- }
- }, [viewImg]);
+ return (
+ <>
+ {
+ setOpenedPreviewFile(false);
+ }}
+ folder="syarat-dokumen"
+ fileName={viewImg}
+ />
- return (
- <>
- {
- setOpenedPreviewFile(false);
- }}
- folder="syarat-dokumen"
- fileName={viewImg}
- />
+
+
+
+ {label}
+
+ {description && (
+
+ {description}
+
+ )}
+ {linkView && (
+ setViewImg(linkView)} size="sm">
+ Lihat dokumen sebelumnya
+
+ )}
+
-
-
-
- {label}
-
- {description && (
-
- {description}
-
- )}
- {
- linkView && (
- setViewImg(linkView)} size="sm">
- Lihat dokumen sebelumnya
-
- )
- }
-
+ onChange(f)}
+ leftSection={ }
+ aria-label={label}
+ name={name}
+ disabled={disabled}
+ />
- onChange(f)}
- leftSection={ }
- aria-label={label}
- name={name}
- disabled={disabled}
- />
-
- {preview ? (
-
-
- Preview:
-
-
-
-
-
- ) : null}
-
- >
-
- );
+ {preview ? (
+
+
+ Preview:
+
+
+
+
+
+ ) : null}
+
+ >
+ );
}
function SearchData() {
- const [submitLoading, setSubmitLoading] = useState(false);
- const [searchPengajuan, setSearchPengajuan] = useState("");
- const [searchPengajuanPhone, setSearchPengajuanPhone] = useState("");
- const navigate = useNavigate();
+ const [submitLoading, setSubmitLoading] = useState(false);
+ const [searchPengajuan, setSearchPengajuan] = useState("");
+ const [searchPengajuanPhone, setSearchPengajuanPhone] = useState("");
+ const navigate = useNavigate();
- async function handleSearch() {
- try {
- setSubmitLoading(true);
- if (searchPengajuan == "" || searchPengajuanPhone == "") {
- notification({
- title: "Peringatan",
- message: "Silakan isi nomor pengajuan atau nomor telephone",
- type: "warning"
- });
- return;
- }
-
- const response = await apiFetch.api.pelayanan["get-no-pengajuan"].post({
- phone: searchPengajuanPhone,
- noPengajuan: searchPengajuan
- });
-
- if (response.status === 200) {
- if (response.data?.success) {
- navigate(`/darmasaba/update-data-surat?pengajuan=${response.data.nomer}`);
- } else {
- notification({
- title: "Peringatan",
- message: response.data?.message || "Data pengajuan tidak valid",
- type: "warning"
- });
- }
- } else {
- notification({
- title: "Error",
- message: "Pengajuan tidak ditemukan atau gagal memuat data",
- type: "error"
- });
- }
-
- } catch (error) {
- console.error("Error searching:", error);
- notification({
- title: "Error",
- message: "Gagal mencari data pengajuan",
- type: "error"
- });
- } finally {
- setSubmitLoading(false);
- }
- }
-
- return (
-
-
-
- }
- placeholder="PS-2025-000123"
- onChange={(e) => { setSearchPengajuan(e.target.value) }}
- />
-
-
-
-
- }
- placeholder="08123456789"
- type="number"
- onChange={(e) => { setSearchPengajuanPhone(e.target.value) }}
- />
-
-
-
- { handleSearch() }} loading={submitLoading}>
- Cari Pengajuan
-
-
-
-
- )
-}
-
-
-function DataUpdate({ noPengajuan, onValidate }: { noPengajuan: string, onValidate: (e: boolean) => void }) {
- const [opened, { open, close }] = useDisclosure(false)
- const navigate = useNavigate()
- const [sukses, setSukses] = useState(false)
- const [submitLoading, setSubmitLoading] = useState(false)
- const [dataPelengkap, setDataPelengkap] = useState([])
- const [dataSyaratDokumen, setDataSyaratDokumen] = useState([])
- const [dataPengajuan, setDataPengajuan] = useState({})
- const [status, setStatus] = useState("")
- const [formSurat, setFormSurat] = useState({
- dataPelengkap: [],
- syaratDokumen: [],
- });
-
- async function fetchData() {
- try {
- const res = await apiFetch.api.pelayanan["detail-data"].post({ nomerPengajuan: noPengajuan });
- if (res.data && res.data.success === true) {
- onValidate(true)
- setDataPelengkap(res.data.dataPelengkap || []);
- setDataSyaratDokumen(res.data.syaratDokumen || []);
- setDataPengajuan(res.data.pengajuan || {});
-
- setStatus(res.data.pengajuan && 'status' in res.data.pengajuan ? res.data.pengajuan.status : "");
- } else {
- // notification({
- // title: "Error",
- // message: res.data?.message || "Gagal memuat data",
- // type: "error",
- // });
- onValidate(false)
- setDataPelengkap([]);
- setDataSyaratDokumen([]);
- setDataPengajuan({});
-
- }
- } catch (error) {
- console.error('Error fetching data:', error);
- }
- }
-
- useShallowEffect(() => {
- fetchData();
- }, []);
-
- function upsertById(
- array: T[],
- item: T
- ): T[] {
- const index = array.findIndex((v) => v.id === item.id)
-
- // ➕ insert
- if (index === -1) {
- return [...array, item]
+ async function handleSearch() {
+ try {
+ setSubmitLoading(true);
+ if (searchPengajuan == "" || searchPengajuanPhone == "") {
+ notification({
+ title: "Peringatan",
+ message: "Silakan isi nomor pengajuan atau nomor telephone",
+ type: "warning",
+ });
+ return;
}
- // ✏️ update
- return array.map((v, i) => (i === index ? { ...v, ...item } : v))
- }
-
-
- function validationForm({
- kategori,
- value,
- }: {
- kategori: "dataPelengkap" | "syaratDokumen";
- value: UpdateDataItem;
- }) {
- setFormSurat((prev) => ({
- ...prev,
- [kategori]: upsertById(prev[kategori], {
- id: value.id,
- key: value.key,
- value: value.value
- })
- }));
- }
-
- function updateArrayByKey(
- list: UpdateDataItem[],
- id: string,
- value: any,
- ): UpdateDataItem[] {
- return list.map((item) =>
- item.id === id ? { ...item, value } : item,
- );
- }
-
- function onChecking() {
- if (formSurat.dataPelengkap.length == 0 && formSurat.syaratDokumen.length == 0)
- return notification({
- title: "Peringatan",
- message: "Tidak ada data yang diupdate",
- type: "warning",
- });
- const isFormKosong = Object.values(formSurat).some((value: UpdateDataItem[] | string) => {
- if (Array.isArray(value)) {
- return (
- value.some(
- (item) =>
- typeof item.value === "string" && item.value.trim() === "",
- )
- );
- }
-
- if (typeof value === "string") {
- return value.trim() === "";
- }
-
- return false;
+ const response = await apiFetch.api.pelayanan["get-no-pengajuan"].post({
+ phone: searchPengajuanPhone,
+ noPengajuan: searchPengajuan,
});
- if (isFormKosong) {
- return notification({
- title: "Gagal",
- message: "Silahkan lengkapi form surat",
- type: "error",
- });
+ if (response.status === 200) {
+ if (response.data?.success) {
+ navigate(
+ `/darmasaba/update-data-surat?pengajuan=${response.data.nomer}`,
+ );
+ } else {
+ notification({
+ title: "Peringatan",
+ message: response.data?.message || "Data pengajuan tidak valid",
+ type: "warning",
+ });
+ }
} else {
- open()
+ notification({
+ title: "Error",
+ message: "Pengajuan tidak ditemukan atau gagal memuat data",
+ type: "error",
+ });
}
- }
+ } catch (error) {
+ console.error("Error searching:", error);
+ notification({
+ title: "Error",
+ message: "Gagal mencari data pengajuan",
+ type: "error",
+ });
+ } finally {
+ setSubmitLoading(false);
+ }
+ }
- async function onSubmit() {
- try {
- setSubmitLoading(true);
- // 🔥 CLONE state SEKALI
- let finalFormSurat = structuredClone(formSurat);
-
- // 2️⃣ Upload satu per satu
- for (const itemUpload of finalFormSurat.syaratDokumen) {
- const updImg = await apiFetch.api.pengaduan.upload.post({
- file: itemUpload.value,
- folder: "syarat-dokumen",
- });
-
- if (updImg.status === 200) {
- // 🔥 UPDATE OBJECT LOKAL (BUKAN STATE)
- finalFormSurat.syaratDokumen = updateArrayByKey(
- finalFormSurat.syaratDokumen,
- itemUpload.id,
- updImg.data?.filename || "",
- );
+ return (
+
+
+
+
}
- }
+ placeholder="PS-2025-000123"
+ onChange={(e) => {
+ setSearchPengajuan(e.target.value);
+ }}
+ />
+
- // 3️⃣ SET STATE SEKALI (optional, untuk UI)
- setFormSurat(finalFormSurat);
+
+
+ }
+ placeholder="08123456789"
+ type="number"
+ onChange={(e) => {
+ setSearchPengajuanPhone(e.target.value);
+ }}
+ />
+
- // 4️⃣ SUBMIT KE API
- const res = await apiFetch.api.pelayanan.update.post({
- id: dataPengajuan && ('id' in dataPengajuan) ? dataPengajuan.id : "",
- dataPelengkap: finalFormSurat.dataPelengkap,
- syaratDokumen: finalFormSurat.syaratDokumen,
- });
-
- if (res.status === 200) {
- setSukses(true);
- } else {
- notification({
- title: "Gagal",
- message:
- "Pengajuan surat gagal dibuat, silahkan coba beberapa saat lagi",
- type: "error",
- });
- }
- } catch (error) {
- notification({
- title: "Gagal",
- message: "Server Error",
- type: "error",
- });
- } finally {
- setSubmitLoading(false);
- }
- }
-
-
- return (
- <>
-
-
-
-
- Apakah anda yakin ingin mengupdate pengajuan surat ini?
-
-
-
- Tidak
-
- {
- onSubmit();
- close();
- }}
- >
- Ya
-
-
-
-
- {
- sukses ?
- {
- navigate("/darmasaba/update-data-surat");
- }}
- category="update"
- />
- :
- <>
- {
- (status != "ditolak" && status != "antrian")
- && ⚠} />
- }
- }
- >
-
- {dataPelengkap.map((item: any, index: number) => (
-
- {
- item.type == "enum"
- ?
-
- }
- data={item.options ?? []}
- placeholder={item.name}
- onChange={(e) => {
- validationForm({
- kategori: "dataPelengkap",
- value: { id: item.id, key: item.key, value: e }
- })
- }}
- value={formSurat.dataPelengkap.find((n: any) => n.key == item.key)?.value || dataPelengkap.find((n: any) => n.key == item.key)?.value}
- />
- : item.type == "date"
- ?
-
- }
- placeholder={item.name}
- onChange={(e) => {
- const formatted = e
- ? dayjs(e).locale("id").format("DD MMMM YYYY")
- : "";
- validationForm({
- kategori: "dataPelengkap",
- value: { id: item.id, key: item.key, value: formatted },
- })
- }}
- value={
- formSurat.dataPelengkap.find((n: any) => n.key === item.key)?.value ?
- parseTanggalID(
- formSurat.dataPelengkap.find((n: any) => n.key === item.key)?.value
- ) :
- parseTanggalID(
- item.value
- )
- }
- />
- :
- }
- placeholder={item.name}
- onChange={(e) =>
- validationForm({
- kategori: "dataPelengkap",
- value: { id: item.id, key: item.key, value: e.target.value },
- })
- }
- value={formSurat.dataPelengkap.find((n) => n.id === item.id)?.value ?? dataPelengkap.find((n: any) => n.key == item.key,)?.value}
- disabled={status != "ditolak" && status != "antrian"}
- />
- }
-
- ))}
-
-
-
- }
- >
-
- {dataSyaratDokumen.map((item: any, index: number) => (
-
-
- validationForm({
- kategori: "syaratDokumen",
- value: { id: item.id, key: item.key, value: file },
- })
- }
- name={item.name}
- disabled={status != "ditolak" && status != "antrian"}
- />
-
- ))}
-
-
-
-
- { onChecking() }} disabled={status != "ditolak" && status != "antrian"}>Kirim
-
- >
- }
- >
- )
+
+ {
+ handleSearch();
+ }}
+ loading={submitLoading}
+ >
+ Cari Pengajuan
+
+
+
+
+ );
+}
+
+function DataUpdate({
+ noPengajuan,
+ onValidate,
+}: {
+ noPengajuan: string;
+ onValidate: (e: boolean) => void;
+}) {
+ const [opened, { open, close }] = useDisclosure(false);
+ const navigate = useNavigate();
+ const [sukses, setSukses] = useState(false);
+ const [submitLoading, setSubmitLoading] = useState(false);
+ const [dataPelengkap, setDataPelengkap] = useState([]);
+ const [dataSyaratDokumen, setDataSyaratDokumen] = useState([]);
+ const [dataPengajuan, setDataPengajuan] = useState({});
+ const [status, setStatus] = useState("");
+ const [formSurat, setFormSurat] = useState({
+ dataPelengkap: [],
+ syaratDokumen: [],
+ });
+
+ async function fetchData() {
+ try {
+ const res = await apiFetch.api.pelayanan["detail-data"].post({
+ nomerPengajuan: noPengajuan,
+ });
+ if (res.data && res.data.success === true) {
+ onValidate(true);
+ setDataPelengkap(res.data.dataPelengkap || []);
+ setDataSyaratDokumen(res.data.syaratDokumen || []);
+ setDataPengajuan(res.data.pengajuan || {});
+
+ setStatus(
+ res.data.pengajuan && "status" in res.data.pengajuan
+ ? res.data.pengajuan.status
+ : "",
+ );
+ } else {
+ // notification({
+ // title: "Error",
+ // message: res.data?.message || "Gagal memuat data",
+ // type: "error",
+ // });
+ onValidate(false);
+ setDataPelengkap([]);
+ setDataSyaratDokumen([]);
+ setDataPengajuan({});
+ }
+ } catch (error) {
+ console.error("Error fetching data:", error);
+ }
+ }
+
+ useShallowEffect(() => {
+ fetchData();
+ }, []);
+
+ function upsertById(array: T[], item: T): T[] {
+ const index = array.findIndex((v) => v.id === item.id);
+
+ // ➕ insert
+ if (index === -1) {
+ return [...array, item];
+ }
+
+ // ✏️ update
+ return array.map((v, i) => (i === index ? { ...v, ...item } : v));
+ }
+
+ function validationForm({
+ kategori,
+ value,
+ }: {
+ kategori: "dataPelengkap" | "syaratDokumen";
+ value: UpdateDataItem;
+ }) {
+ setFormSurat((prev) => ({
+ ...prev,
+ [kategori]: upsertById(prev[kategori], {
+ id: value.id,
+ key: value.key,
+ value: value.value,
+ }),
+ }));
+ }
+
+ function updateArrayByKey(
+ list: UpdateDataItem[],
+ id: string,
+ value: any,
+ ): UpdateDataItem[] {
+ return list.map((item) => (item.id === id ? { ...item, value } : item));
+ }
+
+ function onChecking() {
+ if (
+ formSurat.dataPelengkap.length == 0 &&
+ formSurat.syaratDokumen.length == 0
+ )
+ return notification({
+ title: "Peringatan",
+ message: "Tidak ada data yang diupdate",
+ type: "warning",
+ });
+ const isFormKosong = Object.values(formSurat).some(
+ (value: UpdateDataItem[] | string) => {
+ if (Array.isArray(value)) {
+ return value.some(
+ (item) =>
+ typeof item.value === "string" && item.value.trim() === "",
+ );
+ }
+
+ if (typeof value === "string") {
+ return value.trim() === "";
+ }
+
+ return false;
+ },
+ );
+
+ if (isFormKosong) {
+ return notification({
+ title: "Gagal",
+ message: "Silahkan lengkapi form surat",
+ type: "error",
+ });
+ } else {
+ open();
+ }
+ }
+
+ async function onSubmit() {
+ try {
+ setSubmitLoading(true);
+ // 🔥 CLONE state SEKALI
+ let finalFormSurat = structuredClone(formSurat);
+
+ // 2️⃣ Upload satu per satu
+ for (const itemUpload of finalFormSurat.syaratDokumen) {
+ const updImg = await apiFetch.api.pengaduan.upload.post({
+ file: itemUpload.value,
+ folder: "syarat-dokumen",
+ });
+
+ if (updImg.status === 200) {
+ // 🔥 UPDATE OBJECT LOKAL (BUKAN STATE)
+ finalFormSurat.syaratDokumen = updateArrayByKey(
+ finalFormSurat.syaratDokumen,
+ itemUpload.id,
+ updImg.data?.filename || "",
+ );
+ }
+ }
+
+ // 3️⃣ SET STATE SEKALI (optional, untuk UI)
+ setFormSurat(finalFormSurat);
+
+ // 4️⃣ SUBMIT KE API
+ const res = await apiFetch.api.pelayanan.update.post({
+ id: dataPengajuan && "id" in dataPengajuan ? dataPengajuan.id : "",
+ dataPelengkap: finalFormSurat.dataPelengkap,
+ syaratDokumen: finalFormSurat.syaratDokumen,
+ });
+
+ if (res.status === 200) {
+ setSukses(true);
+ } else {
+ notification({
+ title: "Gagal",
+ message:
+ "Pengajuan surat gagal dibuat, silahkan coba beberapa saat lagi",
+ type: "error",
+ });
+ }
+ } catch (error) {
+ notification({
+ title: "Gagal",
+ message: "Server Error",
+ type: "error",
+ });
+ } finally {
+ setSubmitLoading(false);
+ }
+ }
+
+ return (
+ <>
+
+
+
+ Apakah anda yakin ingin mengupdate pengajuan surat ini?
+
+
+ Tidak
+
+ {
+ onSubmit();
+ close();
+ }}
+ >
+ Ya
+
+
+
+
+ {sukses ? (
+ {
+ navigate("/darmasaba/update-data-surat");
+ }}
+ category="update"
+ />
+ ) : (
+ <>
+ {status != "ditolak" && status != "antrian" && (
+ ⚠}
+ />
+ )}
+ }
+ >
+
+ {dataPelengkap.map((item: any, index: number) => (
+
+ {item.type == "enum" ? (
+ }
+ data={item.options ?? []}
+ placeholder={item.name}
+ onChange={(e) => {
+ validationForm({
+ kategori: "dataPelengkap",
+ value: { id: item.id, key: item.key, value: e },
+ });
+ }}
+ value={
+ formSurat.dataPelengkap.find(
+ (n: any) => n.key == item.key,
+ )?.value ||
+ dataPelengkap.find((n: any) => n.key == item.key)?.value
+ }
+ />
+ ) : item.type == "date" ? (
+ }
+ placeholder={item.name}
+ onChange={(e) => {
+ const formatted = e
+ ? dayjs(e).locale("id").format("DD MMMM YYYY")
+ : "";
+ validationForm({
+ kategori: "dataPelengkap",
+ value: {
+ id: item.id,
+ key: item.key,
+ value: formatted,
+ },
+ });
+ }}
+ value={
+ formSurat.dataPelengkap.find(
+ (n: any) => n.key === item.key,
+ )?.value
+ ? parseTanggalID(
+ formSurat.dataPelengkap.find(
+ (n: any) => n.key === item.key,
+ )?.value,
+ )
+ : parseTanggalID(item.value)
+ }
+ />
+ ) : (
+ }
+ placeholder={item.name}
+ onChange={(e) =>
+ validationForm({
+ kategori: "dataPelengkap",
+ value: {
+ id: item.id,
+ key: item.key,
+ value: e.target.value,
+ },
+ })
+ }
+ value={
+ formSurat.dataPelengkap.find((n) => n.id === item.id)
+ ?.value ??
+ dataPelengkap.find((n: any) => n.key == item.key)?.value
+ }
+ disabled={status != "ditolak" && status != "antrian"}
+ />
+ )}
+
+ ))}
+
+
+
+ }
+ >
+
+ {dataSyaratDokumen.map((item: any, index: number) => (
+
+
+ validationForm({
+ kategori: "syaratDokumen",
+ value: { id: item.id, key: item.key, value: file },
+ })
+ }
+ name={item.name}
+ disabled={status != "ditolak" && status != "antrian"}
+ />
+
+ ))}
+
+
+
+
+ {
+ onChecking();
+ }}
+ disabled={status != "ditolak" && status != "antrian"}
+ >
+ Kirim
+
+
+ >
+ )}
+ >
+ );
}
diff --git a/src/pages/scr/dashboard/pelayanan-surat/detail_pelayanan_page.tsx b/src/pages/scr/dashboard/pelayanan-surat/detail_pelayanan_page.tsx
index 2327872..c834e2f 100644
--- a/src/pages/scr/dashboard/pelayanan-surat/detail_pelayanan_page.tsx
+++ b/src/pages/scr/dashboard/pelayanan-surat/detail_pelayanan_page.tsx
@@ -437,7 +437,12 @@ function DetailDataHistori({ data }: { data: any }) {
-
+
diff --git a/src/pages/scr/dashboard/pengaduan/detail_page.tsx b/src/pages/scr/dashboard/pengaduan/detail_page.tsx
index 0e13810..2bfa9d8 100644
--- a/src/pages/scr/dashboard/pengaduan/detail_page.tsx
+++ b/src/pages/scr/dashboard/pengaduan/detail_page.tsx
@@ -38,6 +38,7 @@ import { useEffect, useState } from "react";
import { useLocation } from "react-router-dom";
import useSwr from "swr";
+
export default function DetailPengaduanPage() {
const { search } = useLocation();
const query = new URLSearchParams(search);
@@ -61,6 +62,7 @@ export default function DetailPengaduanPage() {
{
mutate();
}}
@@ -78,9 +80,11 @@ export default function DetailPengaduanPage() {
function DetailDataPengaduan({
data,
+ phone,
onAction,
}: {
data: any | null;
+ phone?: string | null;
onAction: () => void;
}) {
const [opened, { open, close }] = useDisclosure(false);
@@ -122,6 +126,21 @@ function DetailDataPengaduan({
});
if (res?.status === 200) {
+ const resWA = await apiFetch.api["send-wa"].pengaduan.post({
+ noPengaduan: data?.noPengaduan,
+ judulPengaduan: data?.title,
+ status:
+ cat == "tolak"
+ ? "ditolak"
+ : data.status == "antrian"
+ ? "diterima"
+ : data.status == "diterima"
+ ? "dikerjakan"
+ : "selesai",
+ alasan: keterangan,
+ tlp: String(phone),
+ })
+
onAction();
close();
notification({
@@ -129,6 +148,28 @@ function DetailDataPengaduan({
message: "Success update pengaduan",
type: "success",
});
+
+ if (resWA?.status === 200) {
+ if (resWA.data?.success) {
+ notification({
+ title: "Success",
+ message: "Success send message to warga",
+ type: "success",
+ });
+ } else {
+ notification({
+ title: "Failed",
+ message: "Failed send message to warga",
+ type: "error",
+ });
+ }
+ } else {
+ notification({
+ title: "Failed",
+ message: "Failed send message to warga",
+ type: "error",
+ });
+ }
} else {
notification({
title: "Error",
@@ -425,7 +466,12 @@ function DetailDataHistori({ data }: { data: any }) {
-
+
diff --git a/src/server/routes/pengaduan_route.ts b/src/server/routes/pengaduan_route.ts
index fb60b14..8fb9ade 100644
--- a/src/server/routes/pengaduan_route.ts
+++ b/src/server/routes/pengaduan_route.ts
@@ -415,7 +415,7 @@ const PengaduanRoute = new Elysia({
const datafix = {
pengaduan: {},
history: [],
- warga: {},
+ warga: null,
}
return datafix
diff --git a/src/server/routes/send_wa_route.ts b/src/server/routes/send_wa_route.ts
new file mode 100644
index 0000000..16b2322
--- /dev/null
+++ b/src/server/routes/send_wa_route.ts
@@ -0,0 +1,81 @@
+import Elysia, { t } from "elysia";
+
+const SendWaRoute = new Elysia({
+ prefix: "send-wa",
+ tags: ["send-wa"],
+})
+
+ // --- KATEGORI PENGADUAN ---
+ .post("/pengaduan", async ({ body }) => {
+ const { noPengaduan, judulPengaduan, status, alasan, tlp } = body
+
+ let text = ""
+
+ if (status === "ditolak") {
+ text = `Pemberitahuan Aduan
+
+Aduan dengan Nomor Pengaduan: ${noPengaduan}
+Judul Pengaduan: ${judulPengaduan}
+Kami informasikan bahwa aduan tersebut tidak dapat ditindaklanjuti (ditolak).
+Alasan penolakan:${alasan}
+
+Terima kasih atas pengertian Bapak/Ibu.`
+ } else if (status == "diterima") {
+ text = `Pemberitahuan Aduan
+
+Aduan dengan Nomor Pengaduan: ${noPengaduan}
+Judul Pengaduan: ${judulPengaduan}
+Telah kami terima dan akan segera diproses sesuai ketentuan yang berlaku.
+
+Terima kasih atas laporan Bapak/Ibu.`
+ } else if (status == "dikerjakan") {
+ text = `Pemberitahuan Aduan
+
+Aduan dengan Nomor Pengaduan: ${noPengaduan}
+Judul Pengaduan: ${judulPengaduan}
+Saat ini sedang dalam proses penanganan oleh petugas terkait.
+
+Mohon menunggu informasi selanjutnya.`
+ } else if (status == "selesai") {
+ text = `Pemberitahuan Aduan
+
+Aduan dengan Nomor Pengaduan: ${noPengaduan}
+Judul Pengaduan: ${judulPengaduan}
+Telah selesai ditindaklanjuti.
+
+Terima kasih atas partisipasi dan kepercayaan Bapak/Ibu.`
+ }
+
+ const textFix = encodeURIComponent(text)
+
+ const res = await fetch(
+ `https://cld-dkr-prod-wajs-server.wibudev.com/api/wa/code?nom=${tlp}&text=${textFix}`,
+ {
+ cache: "no-cache",
+ headers: {
+ Authorization: `Bearer ${process.env.WA_SERVER_TOKEN}`,
+ },
+ }
+ );
+
+ if (res.status !== 200)
+ return { success: false, message: "Nomor Whatsapp Tidak Aktif" }
+
+
+ return { success: true, message: 'Pemberitahuan berhasil dikirim ke warga' }
+ }, {
+ body: t.Object({
+ noPengaduan: t.String({ minLength: 1, error: "nomer pengaduan harus diisi" }),
+ judulPengaduan: t.String({ minLength: 1, error: "judul pengaduan harus diisi" }),
+ status: t.String({ minLength: 1, error: "status harus diisi" }),
+ alasan: t.String({ optional: true }),
+ tlp: t.String({ minLength: 1, error: "nomor telepon harus diisi" }),
+ }),
+ detail: {
+ summary: "Send pemberitahuan pengaduan lewat WA",
+ description: `tool untuk send pemberitahuan pengaduan lewat WA`
+ }
+ })
+ ;
+
+export default SendWaRoute