upd: notif warga

Deskripsi:
- tolak pengaduan
- terima pengaduan
- kerjakan pengaduan
- pengaduan selesai

NO Issues
This commit is contained in:
2026-01-02 14:52:00 +08:00
parent 3944e1ee82
commit 487395bdb3
17 changed files with 958 additions and 766 deletions

View File

@@ -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: "" },
],
});
}

View File

@@ -1,19 +1,12 @@
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
backTo,
}: {
onRetry?: () => void
backTo?: () => void
onRetry?: () => void;
backTo?: () => void;
}) {
return (
<Center mih={320}>
@@ -23,7 +16,8 @@ export function DataNotFound({
<Title order={4}>Data Pengajuan Tidak Ditemukan</Title>
<Text size="sm" c="dimmed" ta="center" maw={380}>
Kami tidak dapat menemukan data pengajuan dengan nomor pengajuan yg diinputkan. Silakan periksa kembali data Anda.
Kami tidak dapat menemukan data pengajuan dengan nomor pengajuan yg
diinputkan. Silakan periksa kembali data Anda.
</Text>
<Group mt="md">
@@ -47,5 +41,5 @@ export function DataNotFound({
</Group>
</Stack>
</Center>
)
);
}

View File

@@ -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 (
<Center h="100vh">
@@ -19,11 +19,15 @@ export default function SuccessPengajuan({
<IconCheck size={56} color="green" />
<Title order={3} ta="center">
{category == 'create' ? 'Pengajuan Berhasil Dibuat' : 'Pengajuan Berhasil Diupdate'}
{category == "create"
? "Pengajuan Berhasil Dibuat"
: "Pengajuan Berhasil Diupdate"}
</Title>
<Text ta="center" size="sm" c="dimmed">
{category == 'create' ? 'Pengajuan layanan surat sudah dibuat dengan nomor:' : 'Pengajuan layanan surat sudah diupdate dengan nomor:'}
{category == "create"
? "Pengajuan layanan surat sudah dibuat dengan nomor:"
: "Pengajuan layanan surat sudah diupdate dengan nomor:"}
</Text>
<Badge size="xl" variant="light" color="green">

View File

@@ -146,9 +146,7 @@ export default function SKBedaBiodataDiri({ data }: { data: any }) {
<tr>
<td style={{ width: "160px" }}>1. {getValue("data_dokumen")}</td>
<td style={{ width: "10px" }}></td>
<td>
{/* {getValue("nama")} */}
</td>
<td>{/* {getValue("nama")} */}</td>
</tr>
<tr>
<td>Tertulis pada dokumen A</td>

View File

@@ -135,9 +135,7 @@ export default function SKPenghasilan({ data }: { data: any }) {
<tr>
<td style={{ width: "160px" }}>Penghasilan</td>
<td style={{ width: "10px" }}>:</td>
<td>
Rp. {getValue("penghasilan")} per bulan
</td>
<td>Rp. {getValue("penghasilan")} per bulan</td>
</tr>
</tbody>
</table>
@@ -145,8 +143,8 @@ export default function SKPenghasilan({ data }: { data: any }) {
{/* KEPERLUAN */}
<div style={{ marginTop: "20px" }}>
Surat keterangan ini dibuat untuk keperluan:{" "}
<b>{getValue("alasan")}</b>.
Surat keterangan ini dibuat untuk keperluan: <b>{getValue("alasan")}</b>
.
</div>
<div style={{ marginTop: "20px" }}>

View File

@@ -71,7 +71,10 @@ export default function SKTempatUsaha({ data }: { data: any }) {
label="Tempat/Tanggal Lahir"
value={`${getValue("tempat_lahir")}, ${getValue("tanggal_lahir")}`}
/>
<Row label="Alamat Pemilik Usaha" value={getValue("alamat_pemilik")} />
<Row
label="Alamat Pemilik Usaha"
value={getValue("alamat_pemilik")}
/>
<Row label="Nomor KTP" value={getValue("nik")} />
</div>
@@ -86,14 +89,8 @@ export default function SKTempatUsaha({ data }: { data: any }) {
<Row label="Nama Usaha" value={getValue("nama_usaha")} />
<Row label="Bidang Usaha" value={getValue("bidang_usaha")} />
<Row label="Alamat Usaha" value={getValue("alamat_usaha")} />
<Row
label="Status Tempat Usaha"
value={getValue("status_tempat")}
/>
<Row
label="Luas Tempat Usaha"
value={getValue("luas_usaha")}
/>
<Row label="Status Tempat Usaha" value={getValue("status_tempat")} />
<Row label="Luas Tempat Usaha" value={getValue("luas_usaha")} />
<Row label="Jumlah Karyawan" value={getValue("jumlah_karyawan")} />
</div>

View File

@@ -98,7 +98,9 @@ export default function SKYatim({ data }: { data: any }) {
</tr>
<tr>
<td>Tempat/Tanggal Lahir</td>
<td>: {`${getValue("tempat_lahir")}, ${getValue("tanggal_lahir")}`}</td>
<td>
: {`${getValue("tempat_lahir")}, ${getValue("tanggal_lahir")}`}
</td>
</tr>
<tr>
<td>Jenis Kelamin</td>

View File

@@ -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)

View File

@@ -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 }}
>
<Stack gap="sm">
<Text>
Apakah anda yakin ingin mengirim pengajuan surat ini?
</Text>
<Text>Apakah anda yakin ingin mengirim pengajuan surat ini?</Text>
<Group justify="center" grow>
<Button variant="light" onClick={close}>
Tidak
@@ -298,8 +296,7 @@ export default function FormSurat() {
}}
category="create"
/>
)
:
) : (
<Box>
<Stack gap="lg">
<Group justify="space-between" align="center">
@@ -348,7 +345,6 @@ export default function FormSurat() {
</Grid>
</FormSection>
{/* Kontak Section */}
<FormSection
title="Kontak"
@@ -385,7 +381,9 @@ export default function FormSurat() {
</Grid>
</FormSection>
{jenisSuratFix.id != "" && dataSurat && dataSurat.dataPelengkap && (
{jenisSuratFix.id != "" &&
dataSurat &&
dataSurat.dataPelengkap && (
<>
<FormSection
title="Data Yang Diperlukan"
@@ -393,55 +391,74 @@ export default function FormSurat() {
icon={<IconNotes size={16} />}
>
<Grid>
{dataSurat.dataPelengkap.map((item: any, index: number) => (
{dataSurat.dataPelengkap.map(
(item: any, index: number) => (
<Grid.Col span={6} key={index}>
{
item.type == "enum"
?
{item.type == "enum" ? (
<Select
label={
<FieldLabel label={item.name} hint={item.desc} />
<FieldLabel
label={item.name}
hint={item.desc}
/>
}
data={item.options ?? []}
placeholder={item.name}
onChange={(e) => {
validationForm({
key: "dataPelengkap",
value: { key: item.key, value: e }
})
value: { key: item.key, value: e },
});
}}
value={formSurat.dataPelengkap.find((n: any) => n.key == item.key)?.value}
value={
formSurat.dataPelengkap.find(
(n: any) => n.key == item.key,
)?.value
}
/>
: item.type == "date"
?
) : item.type == "date" ? (
<DateInput
locale="id"
valueFormat="DD MMMM YYYY"
label={
<FieldLabel label={item.name} hint={item.desc} />
<FieldLabel
label={item.name}
hint={item.desc}
/>
}
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,
},
});
}}
/>
:
) : (
<TextInput
type={item.type}
label={
<FieldLabel label={item.name} hint={item.desc} />
<FieldLabel
label={item.name}
hint={item.desc}
/>
}
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,10 +467,10 @@ export default function FormSurat() {
)?.value
}
/>
}
)}
</Grid.Col>
))}
),
)}
</Grid>
</FormSection>
@@ -463,7 +480,8 @@ export default function FormSurat() {
icon={<IconFiles size={16} />}
>
<Grid>
{dataSurat.syaratDokumen.map((item: any, index: number) => (
{dataSurat.syaratDokumen.map(
(item: any, index: number) => (
<Grid.Col span={6} key={index}>
<FileInputWrapper
label={item.desc}
@@ -478,7 +496,8 @@ export default function FormSurat() {
name={item.name}
/>
</Grid.Col>
))}
),
)}
</Grid>
</FormSection>
@@ -494,8 +513,7 @@ export default function FormSurat() {
</Stack>
</Stack>
</Box>
}
)}
</Container>
);
}

View File

@@ -24,7 +24,7 @@ import {
Stack,
Text,
TextInput,
Tooltip
Tooltip,
} from "@mantine/core";
import { DateInput } from "@mantine/dates";
import { useDisclosure, useShallowEffect } from "@mantine/hooks";
@@ -33,7 +33,7 @@ import {
IconFiles,
IconInfoCircle,
IconNotes,
IconUpload
IconUpload,
} from "@tabler/icons-react";
import dayjs from "dayjs";
import "dayjs/locale/id";
@@ -73,7 +73,7 @@ type DataPengajuan = {
createdAt: Date;
updatedAt: Date;
idSurat: string | undefined;
}
};
export default function UpdateDataSurat() {
const navigate = useNavigate();
@@ -82,13 +82,11 @@ export default function UpdateDataSurat() {
const noPengajuan = query.get("pengajuan");
const [found, setFound] = useState(true);
return (
<Container size="md" w={"100%"}>
<Box>
<Stack gap="lg">
{
found &&
{found && (
<Group justify="space-between" align="center" mt={"lg"}>
<Group align="center">
<IconBuildingCommunity size={28} />
@@ -97,24 +95,28 @@ export default function UpdateDataSurat() {
Update Data Pengajuan Surat Administrasi
</Text>
<Text size="sm" c="dimmed">
Formulir ini digunakan untuk memperbarui data pengajuan surat administrasi yang telah diajukan sebelumnya.
Formulir ini digunakan untuk memperbarui data pengajuan
surat administrasi yang telah diajukan sebelumnya.
</Text>
</div>
</Group>
</Group>
}
)}
<Stack gap="lg" mb="lg">
{
!noPengajuan ? (
{!noPengajuan ? (
<SearchData />
)
:
found ? (
<DataUpdate noPengajuan={noPengajuan} onValidate={(e) => { setFound(e) }} />
) : found ? (
<DataUpdate
noPengajuan={noPengajuan}
onValidate={(e) => {
setFound(e);
}}
/>
) : (
<DataNotFound backTo={() => navigate("/darmasaba/update-data-surat")} />
)
}
<DataNotFound
backTo={() => navigate("/darmasaba/update-data-surat")}
/>
)}
</Stack>
</Stack>
</Box>
@@ -160,11 +162,13 @@ function FormSection({
</Group>
{description && <Badge variant="light">{description}</Badge>}
</Group>
{info && <Text size="sm" c="dimmed">{info}</Text>}
{info && (
<Text size="sm" c="dimmed">
{info}
</Text>
)}
</Box>
{
title && <Divider mb="sm" />
}
{title && <Divider mb="sm" />}
<Stack gap="sm">{children}</Stack>
</Card>
);
@@ -179,7 +183,7 @@ function FileInputWrapper({
name,
description,
linkView,
disabled
disabled,
}: {
label: string;
placeholder?: string;
@@ -194,7 +198,6 @@ function FileInputWrapper({
const [viewImg, setViewImg] = useState("");
const [openedPreviewFile, setOpenedPreviewFile] = useState(false);
useShallowEffect(() => {
if (viewImg) {
setOpenedPreviewFile(true);
@@ -222,13 +225,11 @@ function FileInputWrapper({
{description}
</Text>
)}
{
linkView && (
{linkView && (
<Anchor onClick={() => setViewImg(linkView)} size="sm">
Lihat dokumen sebelumnya
</Anchor>
)
}
)}
</Flex>
<FileInput
@@ -257,7 +258,6 @@ function FileInputWrapper({
) : null}
</Stack>
</>
);
}
@@ -274,40 +274,41 @@ function SearchData() {
notification({
title: "Peringatan",
message: "Silakan isi nomor pengajuan atau nomor telephone",
type: "warning"
type: "warning",
});
return;
}
const response = await apiFetch.api.pelayanan["get-no-pengajuan"].post({
phone: searchPengajuanPhone,
noPengajuan: searchPengajuan
noPengajuan: searchPengajuan,
});
if (response.status === 200) {
if (response.data?.success) {
navigate(`/darmasaba/update-data-surat?pengajuan=${response.data.nomer}`);
navigate(
`/darmasaba/update-data-surat?pengajuan=${response.data.nomer}`,
);
} else {
notification({
title: "Peringatan",
message: response.data?.message || "Data pengajuan tidak valid",
type: "warning"
type: "warning",
});
}
} else {
notification({
title: "Error",
message: "Pengajuan tidak ditemukan atau gagal memuat data",
type: "error"
type: "error",
});
}
} catch (error) {
console.error("Error searching:", error);
notification({
title: "Error",
message: "Gagal mencari data pengajuan",
type: "error"
type: "error",
});
} finally {
setSubmitLoading(false);
@@ -322,9 +323,16 @@ function SearchData() {
<Grid>
<Grid.Col span={6}>
<TextInput
label={<FieldLabel label="Nomor Pengajuan" hint="Nomor pengajuan surat" />}
label={
<FieldLabel
label="Nomor Pengajuan"
hint="Nomor pengajuan surat"
/>
}
placeholder="PS-2025-000123"
onChange={(e) => { setSearchPengajuan(e.target.value) }}
onChange={(e) => {
setSearchPengajuan(e.target.value);
}}
/>
</Grid.Col>
@@ -338,30 +346,45 @@ function SearchData() {
}
placeholder="08123456789"
type="number"
onChange={(e) => { setSearchPengajuanPhone(e.target.value) }}
onChange={(e) => {
setSearchPengajuanPhone(e.target.value);
}}
/>
</Grid.Col>
<Grid.Col span={12}>
<Button fullWidth variant="light" color="blue" onClick={() => { handleSearch() }} loading={submitLoading}>
<Button
fullWidth
variant="light"
color="blue"
onClick={() => {
handleSearch();
}}
loading={submitLoading}
>
Cari Pengajuan
</Button>
</Grid.Col>
</Grid>
</FormSection>
)
);
}
function DataUpdate({ noPengajuan, onValidate }: { noPengajuan: string, onValidate: (e: boolean) => void }) {
const [opened, { open, close }] = useDisclosure(false)
const navigate = useNavigate()
const [sukses, setSukses] = useState(false)
const [submitLoading, setSubmitLoading] = useState(false)
const [dataPelengkap, setDataPelengkap] = useState<DataItem[]>([])
const [dataSyaratDokumen, setDataSyaratDokumen] = useState<DataItem[]>([])
const [dataPengajuan, setDataPengajuan] = useState<DataPengajuan | {}>({})
const [status, setStatus] = useState("")
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<DataItem[]>([]);
const [dataSyaratDokumen, setDataSyaratDokumen] = useState<DataItem[]>([]);
const [dataPengajuan, setDataPengajuan] = useState<DataPengajuan | {}>({});
const [status, setStatus] = useState("");
const [formSurat, setFormSurat] = useState<FormUpdateSurat>({
dataPelengkap: [],
syaratDokumen: [],
@@ -369,28 +392,33 @@ function DataUpdate({ noPengajuan, onValidate }: { noPengajuan: string, onValida
async function fetchData() {
try {
const res = await apiFetch.api.pelayanan["detail-data"].post({ nomerPengajuan: noPengajuan });
const res = await apiFetch.api.pelayanan["detail-data"].post({
nomerPengajuan: noPengajuan,
});
if (res.data && res.data.success === true) {
onValidate(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 : "");
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)
onValidate(false);
setDataPelengkap([]);
setDataSyaratDokumen([]);
setDataPengajuan({});
}
} catch (error) {
console.error('Error fetching data:', error);
console.error("Error fetching data:", error);
}
}
@@ -398,22 +426,18 @@ function DataUpdate({ noPengajuan, onValidate }: { noPengajuan: string, onValida
fetchData();
}, []);
function upsertById<T extends { id: string }>(
array: T[],
item: T
): T[] {
const index = array.findIndex((v) => v.id === item.id)
function upsertById<T extends { id: string }>(array: T[], item: T): T[] {
const index = array.findIndex((v) => v.id === item.id);
// insert
if (index === -1) {
return [...array, item]
return [...array, item];
}
// ✏️ update
return array.map((v, i) => (i === index ? { ...v, ...item } : v))
return array.map((v, i) => (i === index ? { ...v, ...item } : v));
}
function validationForm({
kategori,
value,
@@ -426,8 +450,8 @@ function DataUpdate({ noPengajuan, onValidate }: { noPengajuan: string, onValida
[kategori]: upsertById(prev[kategori], {
id: value.id,
key: value.key,
value: value.value
})
value: value.value,
}),
}));
}
@@ -436,25 +460,25 @@ function DataUpdate({ noPengajuan, onValidate }: { noPengajuan: string, onValida
id: string,
value: any,
): UpdateDataItem[] {
return list.map((item) =>
item.id === id ? { ...item, value } : item,
);
return list.map((item) => (item.id === id ? { ...item, value } : item));
}
function onChecking() {
if (formSurat.dataPelengkap.length == 0 && formSurat.syaratDokumen.length == 0)
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) => {
const isFormKosong = Object.values(formSurat).some(
(value: UpdateDataItem[] | string) => {
if (Array.isArray(value)) {
return (
value.some(
return value.some(
(item) =>
typeof item.value === "string" && item.value.trim() === "",
)
);
}
@@ -463,7 +487,8 @@ function DataUpdate({ noPengajuan, onValidate }: { noPengajuan: string, onValida
}
return false;
});
},
);
if (isFormKosong) {
return notification({
@@ -472,7 +497,7 @@ function DataUpdate({ noPengajuan, onValidate }: { noPengajuan: string, onValida
type: "error",
});
} else {
open()
open();
}
}
@@ -504,7 +529,7 @@ function DataUpdate({ noPengajuan, onValidate }: { noPengajuan: string, onValida
// 4⃣ SUBMIT KE API
const res = await apiFetch.api.pelayanan.update.post({
id: dataPengajuan && ('id' in dataPengajuan) ? dataPengajuan.id : "",
id: dataPengajuan && "id" in dataPengajuan ? dataPengajuan.id : "",
dataPelengkap: finalFormSurat.dataPelengkap,
syaratDokumen: finalFormSurat.syaratDokumen,
});
@@ -530,7 +555,6 @@ function DataUpdate({ noPengajuan, onValidate }: { noPengajuan: string, onValida
}
}
return (
<>
<FullScreenLoading visible={submitLoading} />
@@ -541,9 +565,7 @@ function DataUpdate({ noPengajuan, onValidate }: { noPengajuan: string, onValida
overlayProps={{ backgroundOpacity: 0.55, blur: 3 }}
>
<Stack gap="sm">
<Text>
Apakah anda yakin ingin mengupdate pengajuan surat ini?
</Text>
<Text>Apakah anda yakin ingin mengupdate pengajuan surat ini?</Text>
<Group justify="center" grow>
<Button variant="light" onClick={close}>
Tidak
@@ -561,8 +583,7 @@ function DataUpdate({ noPengajuan, onValidate }: { noPengajuan: string, onValida
</Group>
</Stack>
</Modal>
{
sukses ?
{sukses ? (
<SuccessPengajuan
noPengajuan={noPengajuan}
onClose={() => {
@@ -570,12 +591,17 @@ function DataUpdate({ noPengajuan, onValidate }: { noPengajuan: string, onValida
}}
category="update"
/>
:
) : (
<>
{
(status != "ditolak" && status != "antrian")
&& <Alert variant="light" color="yellow" radius="lg" title={`Data pengajuan surat ini tidak dapat diupdate karena berstatus ${status}.`} icon={<span style={{ fontSize: '1.2rem' }}></span>} />
}
{status != "ditolak" && status != "antrian" && (
<Alert
variant="light"
color="yellow"
radius="lg"
title={`Data pengajuan surat ini tidak dapat diupdate karena berstatus ${status}.`}
icon={<span style={{ fontSize: "1.2rem" }}></span>}
/>
)}
<FormSection
title="Data Yang Diperlukan"
description="Data yang diperlukan"
@@ -584,31 +610,29 @@ function DataUpdate({ noPengajuan, onValidate }: { noPengajuan: string, onValida
<Grid>
{dataPelengkap.map((item: any, index: number) => (
<Grid.Col span={6} key={index}>
{
item.type == "enum"
?
{item.type == "enum" ? (
<Select
label={
<FieldLabel label={item.name} hint={item.desc} />
}
label={<FieldLabel label={item.name} hint={item.desc} />}
data={item.options ?? []}
placeholder={item.name}
onChange={(e) => {
validationForm({
kategori: "dataPelengkap",
value: { id: item.id, key: item.key, value: e }
})
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}
value={
formSurat.dataPelengkap.find(
(n: any) => n.key == item.key,
)?.value ||
dataPelengkap.find((n: any) => n.key == item.key)?.value
}
/>
: item.type == "date"
?
) : item.type == "date" ? (
<DateInput
locale="id"
valueFormat="DD MMMM YYYY"
label={
<FieldLabel label={item.name} hint={item.desc} />
}
label={<FieldLabel label={item.name} hint={item.desc} />}
placeholder={item.name}
onChange={(e) => {
const formatted = e
@@ -616,34 +640,47 @@ function DataUpdate({ noPengajuan, onValidate }: { noPengajuan: string, onValida
: "";
validationForm({
kategori: "dataPelengkap",
value: { id: item.id, key: item.key, value: formatted },
})
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
formSurat.dataPelengkap.find(
(n: any) => n.key === item.key,
)?.value
? parseTanggalID(
formSurat.dataPelengkap.find(
(n: any) => n.key === item.key,
)?.value,
)
: parseTanggalID(item.value)
}
/>
: <TextInput
label={
<FieldLabel label={item.name} hint={item.desc} />
}
) : (
<TextInput
label={<FieldLabel label={item.name} hint={item.desc} />}
placeholder={item.name}
onChange={(e) =>
validationForm({
kategori: "dataPelengkap",
value: { id: item.id, key: item.key, value: e.target.value },
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}
value={
formSurat.dataPelengkap.find((n) => n.id === item.id)
?.value ??
dataPelengkap.find((n: any) => n.key == item.key)?.value
}
disabled={status != "ditolak" && status != "antrian"}
/>
}
)}
</Grid.Col>
))}
</Grid>
@@ -677,10 +714,17 @@ function DataUpdate({ noPengajuan, onValidate }: { noPengajuan: string, onValida
</FormSection>
<Group justify="right" mt="md">
<Button onClick={() => { onChecking() }} disabled={status != "ditolak" && status != "antrian"}>Kirim</Button>
<Button
onClick={() => {
onChecking();
}}
disabled={status != "ditolak" && status != "antrian"}
>
Kirim
</Button>
</Group>
</>
}
)}
</>
)
);
}

View File

@@ -437,7 +437,12 @@ function DetailDataHistori({ data }: { data: any }) {
</Title>
</Flex>
<Divider my={0} />
<Spoiler maxHeight={200} showLabel="Show more" hideLabel="Hide" transitionDuration={1000}>
<Spoiler
maxHeight={200}
showLabel="Show more"
hideLabel="Hide"
transitionDuration={1000}
>
<Table>
<Table.Thead>
<Table.Tr>

View File

@@ -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() {
<Stack gap={"xl"}>
<DetailDataPengaduan
data={data?.data?.pengaduan}
phone={data && data.data && data.data.warga ? data.data.warga.phone : null}
onAction={() => {
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 }) {
</Title>
</Flex>
<Divider my={0} />
<Spoiler maxHeight={200} showLabel="Show more" hideLabel="Hide" transitionDuration={1000}>
<Spoiler
maxHeight={200}
showLabel="Show more"
hideLabel="Hide"
transitionDuration={1000}
>
<Table>
<Table.Thead>
<Table.Tr>

View File

@@ -415,7 +415,7 @@ const PengaduanRoute = new Elysia({
const datafix = {
pengaduan: {},
history: [],
warga: {},
warga: null,
}
return datafix

View File

@@ -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