upd: update data pelayanan surat
Deskripsi - form pencarian - detail data pengajuan - api - belom selesai submit NO Issues
This commit is contained in:
474
src/pages/darmasaba/update_data_surat.tsx
Normal file
474
src/pages/darmasaba/update_data_surat.tsx
Normal file
@@ -0,0 +1,474 @@
|
||||
import ModalFile from "@/components/ModalFile";
|
||||
import notification from "@/components/notificationGlobal";
|
||||
import apiFetch from "@/lib/apiFetch";
|
||||
import {
|
||||
ActionIcon,
|
||||
Alert,
|
||||
Anchor,
|
||||
Badge,
|
||||
Box,
|
||||
Button,
|
||||
Card,
|
||||
Container,
|
||||
Divider,
|
||||
FileInput,
|
||||
Flex,
|
||||
Grid,
|
||||
Group,
|
||||
Stack,
|
||||
Text,
|
||||
TextInput,
|
||||
Tooltip
|
||||
} from "@mantine/core";
|
||||
import { useShallowEffect } from "@mantine/hooks";
|
||||
import {
|
||||
IconBuildingCommunity,
|
||||
IconInfoCircle,
|
||||
IconUpload
|
||||
} from "@tabler/icons-react";
|
||||
import _ from "lodash";
|
||||
import React, { useState } from "react";
|
||||
import { useLocation, useNavigate } from "react-router-dom";
|
||||
|
||||
type DataItem = {
|
||||
key: string;
|
||||
value: string;
|
||||
};
|
||||
|
||||
type UpdateDataItem = {
|
||||
id: string;
|
||||
key: string;
|
||||
value: any;
|
||||
};
|
||||
|
||||
type FormSurat = {
|
||||
kategoriId: string;
|
||||
nama: string;
|
||||
phone: string;
|
||||
dataPelengkap: DataItem[];
|
||||
syaratDokumen: DataItem[];
|
||||
};
|
||||
|
||||
type FormUpdateSurat = {
|
||||
dataPelengkap: UpdateDataItem[];
|
||||
syaratDokumen: UpdateDataItem[];
|
||||
};
|
||||
|
||||
type DataPengajuan = {
|
||||
id: string;
|
||||
noPengajuan: string;
|
||||
category: string;
|
||||
status: "antrian" | "diproses" | "selesai" | "ditolak";
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
idSurat: string | undefined;
|
||||
}
|
||||
|
||||
export default function UpdateDataSurat() {
|
||||
const navigate = useNavigate();
|
||||
const { search } = useLocation();
|
||||
const query = new URLSearchParams(search);
|
||||
const noPengajuan = query.get("pengajuan");
|
||||
|
||||
|
||||
return (
|
||||
<Container size="md" w={"100%"}>
|
||||
<Box>
|
||||
<Stack gap="lg">
|
||||
<Group justify="space-between" align="center" mt={"lg"}>
|
||||
<Group align="center">
|
||||
<IconBuildingCommunity size={28} />
|
||||
<div>
|
||||
<Text fw={800} size="xl">
|
||||
Update Data Pengajuan Surat Administrasi
|
||||
</Text>
|
||||
<Text size="sm" c="dimmed">
|
||||
Formulir ini digunakan untuk memperbarui data pengajuan surat administrasi yang telah diajukan sebelumnya.
|
||||
</Text>
|
||||
</div>
|
||||
</Group>
|
||||
</Group>
|
||||
<Stack gap="lg" mb="lg">
|
||||
{
|
||||
!noPengajuan ? (
|
||||
<SearchData />
|
||||
)
|
||||
:
|
||||
<DataUpdate noPengajuan={noPengajuan} />
|
||||
}
|
||||
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Box>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
function FieldLabel({ label, hint }: { label: string; hint?: string }) {
|
||||
return (
|
||||
<Group justify="apart" gap="xs" align="center">
|
||||
<Text fw={600}>{label}</Text>
|
||||
{hint && (
|
||||
<Tooltip label={hint} withArrow>
|
||||
<ActionIcon size={24} variant="subtle">
|
||||
<IconInfoCircle size={16} />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
)}
|
||||
</Group>
|
||||
);
|
||||
}
|
||||
|
||||
function FormSection({
|
||||
title,
|
||||
icon,
|
||||
children,
|
||||
description,
|
||||
info,
|
||||
}: {
|
||||
title?: string;
|
||||
icon?: React.ReactNode;
|
||||
children: React.ReactNode;
|
||||
description?: string;
|
||||
info?: string;
|
||||
}) {
|
||||
return (
|
||||
<Card radius="md" shadow="sm" withBorder>
|
||||
<Box mb="xs">
|
||||
<Group justify="apart" align="center">
|
||||
<Group align="center" gap="xs">
|
||||
{icon}
|
||||
{title && <Text fw={700}>{title}</Text>}
|
||||
</Group>
|
||||
{description && <Badge variant="light">{description}</Badge>}
|
||||
</Group>
|
||||
{info && <Text size="sm" c="dimmed">{info}</Text>}
|
||||
</Box>
|
||||
{
|
||||
title && <Divider mb="sm" />
|
||||
}
|
||||
<Stack gap="sm">{children}</Stack>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
function FileInputWrapper({
|
||||
label,
|
||||
placeholder,
|
||||
accept,
|
||||
onChange,
|
||||
preview,
|
||||
name,
|
||||
description,
|
||||
linkView,
|
||||
disabled
|
||||
}: {
|
||||
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);
|
||||
|
||||
|
||||
useShallowEffect(() => {
|
||||
if (viewImg) {
|
||||
setOpenedPreviewFile(true);
|
||||
}
|
||||
}, [viewImg]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<ModalFile
|
||||
open={openedPreviewFile && !_.isEmpty(viewImg)}
|
||||
onClose={() => {
|
||||
setOpenedPreviewFile(false);
|
||||
}}
|
||||
folder="syarat-dokumen"
|
||||
fileName={viewImg}
|
||||
/>
|
||||
|
||||
<Stack gap="xs">
|
||||
<Flex direction={"column"}>
|
||||
<Group justify="apart" align="center">
|
||||
<Text fw={500}>{label}</Text>
|
||||
</Group>
|
||||
{description && (
|
||||
<Text size="sm" c="dimmed" mt={4} style={{ lineHeight: 1.2 }}>
|
||||
{description}
|
||||
</Text>
|
||||
)}
|
||||
{
|
||||
linkView && (
|
||||
<Anchor onClick={() => setViewImg(linkView)} size="sm">
|
||||
Lihat dokumen sebelumnya
|
||||
</Anchor>
|
||||
)
|
||||
}
|
||||
</Flex>
|
||||
|
||||
<FileInput
|
||||
accept={accept}
|
||||
placeholder={placeholder}
|
||||
onChange={(f) => onChange(f)}
|
||||
leftSection={<IconUpload />}
|
||||
aria-label={label}
|
||||
name={name}
|
||||
disabled={disabled}
|
||||
/>
|
||||
|
||||
{preview ? (
|
||||
<div>
|
||||
<Text size="xs" color="dimmed">
|
||||
Preview:
|
||||
</Text>
|
||||
<div style={{ marginTop: 6 }}>
|
||||
<img
|
||||
src={preview}
|
||||
alt={`${label} preview`}
|
||||
style={{ maxWidth: "200px", borderRadius: 4 }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</Stack>
|
||||
</>
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
function SearchData() {
|
||||
const [submitLoading, setSubmitLoading] = useState(false);
|
||||
const [searchPengajuan, setSearchPengajuan] = useState("");
|
||||
const [searchPengajuanPhone, setSearchPengajuanPhone] = useState("");
|
||||
const navigate = useNavigate();
|
||||
|
||||
async function handleSearch() {
|
||||
try {
|
||||
setSubmitLoading(true);
|
||||
if (searchPengajuan == "" || searchPengajuanPhone == "") {
|
||||
notification({
|
||||
title: "Peringatan",
|
||||
message: "Silakan isi nomor pengajuan atau nomor telephone",
|
||||
type: "warning"
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await apiFetch.api.pelayanan["get-no-pengajuan"].post({
|
||||
phone: searchPengajuanPhone,
|
||||
noPengajuan: searchPengajuan
|
||||
});
|
||||
|
||||
if (response.status === 200) {
|
||||
if (response.data?.success) {
|
||||
navigate(`/darmasaba/update-data-surat?pengajuan=${response.data.nomer}`);
|
||||
} else {
|
||||
notification({
|
||||
title: "Peringatan",
|
||||
message: response.data?.message || "Data pengajuan tidak valid",
|
||||
type: "warning"
|
||||
});
|
||||
}
|
||||
} else {
|
||||
notification({
|
||||
title: "Error",
|
||||
message: "Pengajuan tidak ditemukan atau gagal memuat data",
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error("Error searching:", error);
|
||||
notification({
|
||||
title: "Error",
|
||||
message: "Gagal mencari data pengajuan",
|
||||
type: "error"
|
||||
});
|
||||
} finally {
|
||||
setSubmitLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<FormSection
|
||||
title="Cari Pengajuan Surat"
|
||||
info="Masukkan nomor pengajuan dan nomor telepon yang digunakan saat pengajuan surat."
|
||||
>
|
||||
<Grid>
|
||||
<Grid.Col span={6}>
|
||||
<TextInput
|
||||
label={<FieldLabel label="Nomor Pengajuan" hint="Nomor pengajuan surat" />}
|
||||
placeholder="PS-2025-000123"
|
||||
onChange={(e) => { setSearchPengajuan(e.target.value) }}
|
||||
/>
|
||||
</Grid.Col>
|
||||
|
||||
<Grid.Col span={6}>
|
||||
<TextInput
|
||||
label={
|
||||
<FieldLabel
|
||||
label="Nomor Telephone"
|
||||
hint="Nomor telephone yang dapat dihubungi / terhubung dengan whatsapp"
|
||||
/>
|
||||
}
|
||||
placeholder="08123456789"
|
||||
type="number"
|
||||
onChange={(e) => { setSearchPengajuanPhone(e.target.value) }}
|
||||
/>
|
||||
</Grid.Col>
|
||||
|
||||
<Grid.Col span={12}>
|
||||
<Button fullWidth variant="light" color="blue" onClick={() => { handleSearch() }} loading={submitLoading}>
|
||||
Cari Pengajuan
|
||||
</Button>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</FormSection>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
function DataUpdate({ noPengajuan }: { noPengajuan: string }) {
|
||||
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: [],
|
||||
});
|
||||
|
||||
async function fetchData() {
|
||||
try {
|
||||
const res = await apiFetch.api.pelayanan["detail-data"].post({ nomerPengajuan: noPengajuan });
|
||||
if (res.data && res.data.success === 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",
|
||||
});
|
||||
setDataPelengkap([]);
|
||||
setDataSyaratDokumen([]);
|
||||
setDataPengajuan({});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching data:', error);
|
||||
}
|
||||
}
|
||||
|
||||
useShallowEffect(() => {
|
||||
fetchData();
|
||||
}, []);
|
||||
|
||||
function upsertById<T extends { id: string }>(
|
||||
array: T[],
|
||||
item: T
|
||||
): T[] {
|
||||
const index = array.findIndex((v) => v.id === item.id)
|
||||
|
||||
// ➕ insert
|
||||
if (index === -1) {
|
||||
return [...array, item]
|
||||
}
|
||||
|
||||
// ✏️ update
|
||||
return array.map((v, i) => (i === index ? { ...v, ...item } : v))
|
||||
}
|
||||
|
||||
|
||||
function validationForm({
|
||||
kategori,
|
||||
value,
|
||||
}: {
|
||||
kategori: "dataPelengkap" | "syaratDokumen";
|
||||
value: UpdateDataItem;
|
||||
}) {
|
||||
setFormSurat((prev) => ({
|
||||
...prev,
|
||||
[kategori]: upsertById(prev[kategori], {
|
||||
id: value.id,
|
||||
key: value.key,
|
||||
value: value.value
|
||||
})
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
{
|
||||
(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 Pelengkap"
|
||||
description="Data pelengkap yang diperlukan"
|
||||
>
|
||||
<Grid>
|
||||
{dataPelengkap.map((item: any, index: number) => (
|
||||
<Grid.Col span={6} key={index}>
|
||||
<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={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>
|
||||
</FormSection>
|
||||
|
||||
<FormSection
|
||||
title="Syarat Dokumen"
|
||||
description="Syarat dokumen yang diperlukan"
|
||||
>
|
||||
<Grid>
|
||||
{dataSyaratDokumen.map((item: any, index: number) => (
|
||||
<Grid.Col span={6} key={index}>
|
||||
<FileInputWrapper
|
||||
label={item.desc}
|
||||
placeholder={"Upload file terbaru untuk mengupdate"}
|
||||
accept="image/*,application/pdf"
|
||||
linkView={item.value}
|
||||
onChange={(file) =>
|
||||
validationForm({
|
||||
kategori: "syaratDokumen",
|
||||
value: { id: item.id, key: item.key, value: file },
|
||||
})
|
||||
}
|
||||
name={item.name}
|
||||
disabled={status != "ditolak" && status != "antrian"}
|
||||
/>
|
||||
</Grid.Col>
|
||||
))}
|
||||
</Grid>
|
||||
</FormSection>
|
||||
|
||||
<Group justify="right" mt="md">
|
||||
<Button onClick={() => console.log('Submit clicked')}>Kirim</Button>
|
||||
</Group>
|
||||
</>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user