upd: dahsboar admin
Deskripsi: - list kategori pelayanan surat - edit kategori pelayanan surat - tambah kategori pelayanan surat - hapush kategori pelayanan surat No Issues
This commit is contained in:
492
src/components/KategoriPelayananSurat.tsx
Normal file
492
src/components/KategoriPelayananSurat.tsx
Normal file
@@ -0,0 +1,492 @@
|
||||
import apiFetch from "@/lib/apiFetch";
|
||||
import {
|
||||
ActionIcon,
|
||||
Button,
|
||||
Divider,
|
||||
Flex,
|
||||
Grid,
|
||||
Group,
|
||||
Input,
|
||||
List,
|
||||
Modal,
|
||||
Stack,
|
||||
Table,
|
||||
TagsInput,
|
||||
Text,
|
||||
Title,
|
||||
Tooltip
|
||||
} from "@mantine/core";
|
||||
import { useDisclosure, useShallowEffect } from "@mantine/hooks";
|
||||
import { IconEdit, IconEye, IconPlus, IconTrash } from "@tabler/icons-react";
|
||||
import { useState } from "react";
|
||||
import useSWR from "swr";
|
||||
import notification from "./notificationGlobal";
|
||||
|
||||
export default function KategoriPelayananSurat() {
|
||||
const [openedDelete, { open: openDelete, close: closeDelete }] = useDisclosure(false);
|
||||
const [openedDetail, { open: openDetail, close: closeDetail }] = useDisclosure(false);
|
||||
const [btnLoading, setBtnLoading] = useState(false);
|
||||
const [opened, { open, close }] = useDisclosure(false);
|
||||
const [openedTambah, { open: openTambah, close: closeTambah }] = useDisclosure(false);
|
||||
const [dataDelete, setDataDelete] = useState("")
|
||||
const { data, mutate, isLoading } = useSWR("/", () =>
|
||||
apiFetch.api.pelayanan.category.get(),
|
||||
);
|
||||
const list = data?.data || [];
|
||||
const [dataChoose, setDataChoose] = useState({
|
||||
id: "",
|
||||
name: "",
|
||||
syaratDokumen: [{ name: "", desc: "" }],
|
||||
dataText: [""],
|
||||
});
|
||||
const [dataTambah, setDataTambah] = useState({
|
||||
name: "",
|
||||
syaratDokumen: [{ name: "", desc: "" }],
|
||||
dataText: [""],
|
||||
})
|
||||
|
||||
useShallowEffect(() => {
|
||||
mutate();
|
||||
}, []);
|
||||
|
||||
async function handleCreate() {
|
||||
try {
|
||||
setBtnLoading(true);
|
||||
const cleanedDataText = dataTambah.dataText.map(v => v.trim()).filter(v => v !== "");
|
||||
const cleanedSyarat = dataTambah.syaratDokumen.map((item) => ({
|
||||
name: item.name.trim(),
|
||||
desc: item.desc.trim(),
|
||||
})).filter(item => item.name !== "" && item.desc !== "");
|
||||
|
||||
const cleanedTambah = {
|
||||
name: dataTambah.name.trim(),
|
||||
syaratDokumen: cleanedSyarat,
|
||||
dataText: cleanedDataText,
|
||||
}
|
||||
const res = await apiFetch.api.pelayanan.category.create.post(cleanedTambah);
|
||||
if (res.status === 200) {
|
||||
mutate();
|
||||
closeTambah();
|
||||
setDataTambah({
|
||||
name: "",
|
||||
syaratDokumen: [{ name: "", desc: "" }],
|
||||
dataText: [""],
|
||||
});
|
||||
notification({
|
||||
title: "Success",
|
||||
message: "Your category have been saved",
|
||||
type: "success",
|
||||
})
|
||||
} else {
|
||||
notification({
|
||||
title: "Error",
|
||||
message: "Failed to create category",
|
||||
type: "error",
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
notification({
|
||||
title: "Error",
|
||||
message: "Failed to create category",
|
||||
type: "error",
|
||||
})
|
||||
} finally {
|
||||
setBtnLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleEdit() {
|
||||
try {
|
||||
setBtnLoading(true);
|
||||
const cleanedDataText = dataChoose.dataText.map(v => v.trim()).filter(v => v !== "");
|
||||
const cleanedSyarat = dataChoose.syaratDokumen.map((item) => ({
|
||||
name: item.name.trim(),
|
||||
desc: item.desc.trim(),
|
||||
})).filter(item => item.name !== "" && item.desc !== "");
|
||||
|
||||
const res = await apiFetch.api.pelayanan.category.update.post({
|
||||
id: dataChoose.id,
|
||||
name: dataChoose.name,
|
||||
syaratDokumen: cleanedSyarat,
|
||||
dataText: cleanedDataText,
|
||||
});
|
||||
if (res.status === 200) {
|
||||
mutate();
|
||||
close();
|
||||
notification({
|
||||
title: "Success",
|
||||
message: "Your category have been saved",
|
||||
type: "success",
|
||||
})
|
||||
} else {
|
||||
notification({
|
||||
title: "Error",
|
||||
message: "Failed to edit category",
|
||||
type: "error",
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
notification({
|
||||
title: "Error",
|
||||
message: "Failed to edit category",
|
||||
type: "error",
|
||||
})
|
||||
} finally {
|
||||
setBtnLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async function handleDelete() {
|
||||
try {
|
||||
setBtnLoading(true);
|
||||
const res = await apiFetch.api.pelayanan.category.delete.post({ id: dataDelete });
|
||||
if (res.status === 200) {
|
||||
mutate();
|
||||
closeDelete();
|
||||
notification({
|
||||
title: "Success",
|
||||
message: "Your category have been deleted",
|
||||
type: "success",
|
||||
})
|
||||
} else {
|
||||
notification({
|
||||
title: "Error",
|
||||
message: "Failed to delete category",
|
||||
type: "error",
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
notification({
|
||||
title: "Error",
|
||||
message: "Failed to delete category",
|
||||
type: "error",
|
||||
})
|
||||
} finally {
|
||||
setBtnLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function handleAddSyarat() {
|
||||
setDataChoose({
|
||||
...dataChoose,
|
||||
syaratDokumen: [...dataChoose.syaratDokumen, { name: "", desc: "" }],
|
||||
});
|
||||
}
|
||||
|
||||
function handleDeleteSyarat(index: number) {
|
||||
setDataChoose({
|
||||
...dataChoose,
|
||||
syaratDokumen: dataChoose.syaratDokumen.filter((_, i) => i !== index),
|
||||
});
|
||||
}
|
||||
|
||||
function handleEditSyarat(index: number, data: { name: string; desc: string }) {
|
||||
setDataChoose({
|
||||
...dataChoose,
|
||||
syaratDokumen: dataChoose.syaratDokumen.map((v, i) => (i === index ? data : v)),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Modal Edit */}
|
||||
<Modal
|
||||
opened={opened}
|
||||
onClose={close}
|
||||
title={"Edit"}
|
||||
size="xl"
|
||||
overlayProps={{ backgroundOpacity: 0.55, blur: 3 }}
|
||||
>
|
||||
<Stack gap="lg">
|
||||
<Input.Wrapper label="Kategori">
|
||||
<Input value={dataChoose.name} onChange={(e) => setDataChoose({ ...dataChoose, name: e.target.value })} />
|
||||
</Input.Wrapper>
|
||||
<TagsInput
|
||||
label="Data Pelengkap"
|
||||
placeholder="Tambah data pelengkap"
|
||||
splitChars={[',']}
|
||||
value={dataChoose.dataText}
|
||||
onChange={(value) => setDataChoose({ ...dataChoose, dataText: value })}
|
||||
/>
|
||||
<Flex direction={"column"} gap={"md"}>
|
||||
<Group>
|
||||
<Text size="sm" c={"white"}>
|
||||
Syarat dokumen
|
||||
</Text>
|
||||
<Tooltip label="Tambah Syarat Dokumen">
|
||||
<ActionIcon
|
||||
variant="light"
|
||||
size="sm"
|
||||
color="blue"
|
||||
style={{ boxShadow: "0 0 8px rgba(0,255,200,0.2)" }}
|
||||
onClick={handleAddSyarat}
|
||||
>
|
||||
<IconPlus size={20} />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
</Group>
|
||||
{
|
||||
dataChoose?.syaratDokumen?.map((v: any, i: number) => (
|
||||
<Grid key={i} style={{ borderBottom: "1px solid gray", paddingBottom: "10px" }}>
|
||||
<Grid.Col span={1} style={{ display: "flex", alignItems: "center", justifyContent: "center" }}>
|
||||
<Tooltip label="Delete Syarat Dokumen">
|
||||
<ActionIcon
|
||||
variant="light"
|
||||
size="sm"
|
||||
color="red"
|
||||
style={{ boxShadow: "0 0 8px rgba(0,255,200,0.2)" }}
|
||||
onClick={() => {
|
||||
handleDeleteSyarat(i)
|
||||
}}
|
||||
>
|
||||
<IconTrash size={20} />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={5}>
|
||||
<Input.Wrapper label="Nama">
|
||||
<Input value={v.name} onChange={(e) => handleEditSyarat(i, { name: e.target.value, desc: v.desc })} />
|
||||
</Input.Wrapper>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={6}>
|
||||
<Input.Wrapper label="Deskripsi">
|
||||
<Input value={v.desc} onChange={(e) => handleEditSyarat(i, { name: v.name, desc: e.target.value })} />
|
||||
</Input.Wrapper>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
))
|
||||
}
|
||||
</Flex>
|
||||
|
||||
<Group justify="center" grow>
|
||||
<Button variant="light" onClick={close}>
|
||||
Batal
|
||||
</Button>
|
||||
<Button variant="filled" onClick={handleEdit} loading={btnLoading}>
|
||||
Simpan
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Modal>
|
||||
|
||||
{/* Modal Tambah */}
|
||||
<Modal
|
||||
opened={openedTambah}
|
||||
onClose={closeTambah}
|
||||
title={"Tambah"}
|
||||
overlayProps={{ backgroundOpacity: 0.55, blur: 3 }}
|
||||
size={"lg"}
|
||||
>
|
||||
<Stack gap="lg">
|
||||
<Input.Wrapper label="Tambah Kategori Pelayanan Surat">
|
||||
<Input value={dataTambah.name} onChange={(e) => setDataTambah({ ...dataTambah, name: e.target.value })} />
|
||||
</Input.Wrapper>
|
||||
<TagsInput
|
||||
label="Data Pelengkap"
|
||||
placeholder="Tambah data pelengkap"
|
||||
splitChars={[',']}
|
||||
value={dataTambah.dataText}
|
||||
onChange={(value) => setDataTambah({ ...dataTambah, dataText: value })}
|
||||
/>
|
||||
<Flex direction={"column"} gap={"md"}>
|
||||
<Group>
|
||||
<Text size="sm" c={"white"}>
|
||||
Syarat dokumen
|
||||
</Text>
|
||||
<Tooltip label="Tambah Syarat Dokumen">
|
||||
<ActionIcon
|
||||
variant="light"
|
||||
size="sm"
|
||||
color="blue"
|
||||
style={{ boxShadow: "0 0 8px rgba(0,255,200,0.2)" }}
|
||||
onClick={() => {
|
||||
setDataTambah({ ...dataTambah, syaratDokumen: [...dataTambah.syaratDokumen, { name: "", desc: "" }] })
|
||||
}}
|
||||
>
|
||||
<IconPlus size={20} />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
</Group>
|
||||
{
|
||||
|
||||
dataTambah?.syaratDokumen?.map((v: any, index: number) => (
|
||||
<Grid key={index} style={{ borderBottom: "1px solid gray", paddingBottom: "10px" }}>
|
||||
<Grid.Col span={1} style={{ display: "flex", alignItems: "center", justifyContent: "center" }}>
|
||||
<Tooltip label="Delete Syarat Dokumen">
|
||||
<ActionIcon
|
||||
variant="light"
|
||||
size="sm"
|
||||
color="red"
|
||||
style={{ boxShadow: "0 0 8px rgba(0,255,200,0.2)" }}
|
||||
onClick={() => {
|
||||
setDataTambah({ ...dataTambah, syaratDokumen: dataTambah.syaratDokumen.filter((v: any, i: number) => i !== index) })
|
||||
}}
|
||||
disabled={dataTambah?.syaratDokumen?.length === 1}
|
||||
>
|
||||
<IconTrash size={20} />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={5}>
|
||||
<Input.Wrapper label="Nama">
|
||||
<Input value={dataTambah?.syaratDokumen[index]?.name} onChange={(e) => setDataTambah({ ...dataTambah, syaratDokumen: dataTambah.syaratDokumen.map((v: any, i: number) => i === index ? { ...v, name: e.target.value } : v) })} />
|
||||
</Input.Wrapper>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={6}>
|
||||
<Input.Wrapper label="Deskripsi">
|
||||
<Input value={dataTambah?.syaratDokumen[index]?.desc} onChange={(e) => setDataTambah({ ...dataTambah, syaratDokumen: dataTambah.syaratDokumen.map((v: any, i: number) => i === index ? { ...v, desc: e.target.value } : v) })} />
|
||||
</Input.Wrapper>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
))
|
||||
}
|
||||
</Flex>
|
||||
<Group justify="center" grow>
|
||||
<Button variant="light" onClick={closeTambah}>
|
||||
Batal
|
||||
</Button>
|
||||
<Button variant="filled" onClick={handleCreate} loading={btnLoading}>
|
||||
Simpan
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Modal>
|
||||
|
||||
{/* Modal Delete */}
|
||||
<Modal
|
||||
opened={openedDelete}
|
||||
onClose={closeDelete}
|
||||
title={"Delete Kategori Pelayanan Surat"}
|
||||
overlayProps={{ backgroundOpacity: 0.55, blur: 3 }}
|
||||
>
|
||||
<Stack gap="md">
|
||||
<Text size="md" color="gray.6">
|
||||
Apakah anda yakin ingin menghapus kategori ini?
|
||||
</Text>
|
||||
<Group justify="center" grow>
|
||||
<Button variant="light" onClick={closeDelete}>
|
||||
Batal
|
||||
</Button>
|
||||
<Button variant="filled" color="red" onClick={handleDelete} loading={btnLoading}>
|
||||
Hapus
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Modal>
|
||||
|
||||
{/* Modal Detail */}
|
||||
<Modal
|
||||
opened={openedDetail}
|
||||
onClose={closeDetail}
|
||||
title={"Detail Kategori"}
|
||||
overlayProps={{ backgroundOpacity: 0.55, blur: 3 }}
|
||||
size={"lg"}
|
||||
>
|
||||
<Stack gap="sm">
|
||||
<Flex direction={"column"}>
|
||||
<Text size="sm" color="white">Kategori</Text>
|
||||
<Text size="md" >{dataChoose?.name ?? ""}</Text>
|
||||
</Flex>
|
||||
<Flex direction={"column"}>
|
||||
<Text size="sm" color="white">Syarat Dokumen</Text>
|
||||
<List>
|
||||
{dataChoose?.syaratDokumen?.map((v: any) => (
|
||||
<List.Item key={v.id}>{v.desc}</List.Item>
|
||||
))}
|
||||
</List>
|
||||
</Flex>
|
||||
<Flex direction={"column"}>
|
||||
<Text size="sm" color="white">Data Pelengkap</Text>
|
||||
<List>
|
||||
{dataChoose?.dataText?.map((v: any) => (
|
||||
<List.Item key={v.id}>{v}</List.Item>
|
||||
))}
|
||||
</List>
|
||||
</Flex>
|
||||
</Stack>
|
||||
</Modal>
|
||||
|
||||
|
||||
{/* Table */}
|
||||
<Stack gap={"md"}>
|
||||
<Flex align="center" justify="space-between">
|
||||
<Title order={4} c="gray.2">
|
||||
Kategori Pelayanan Surat
|
||||
</Title>
|
||||
<Tooltip label="Tambah Kategori Pelayanan Surat">
|
||||
<Button
|
||||
variant="light"
|
||||
leftSection={<IconPlus size={20} />}
|
||||
onClick={openTambah}
|
||||
>
|
||||
Tambah
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</Flex>
|
||||
<Divider my={0} />
|
||||
<Stack gap={"md"}>
|
||||
<Table highlightOnHover>
|
||||
<Table.Thead>
|
||||
<Table.Tr>
|
||||
<Table.Th>Kategori</Table.Th>
|
||||
<Table.Th>Aksi</Table.Th>
|
||||
</Table.Tr>
|
||||
</Table.Thead>
|
||||
<Table.Tbody>
|
||||
{list?.map((v: any) => (
|
||||
<Table.Tr key={v.id}>
|
||||
<Table.Td>{v.name}</Table.Td>
|
||||
<Table.Td>
|
||||
<Group>
|
||||
<Tooltip label="View Detail">
|
||||
<ActionIcon
|
||||
variant="light"
|
||||
size="sm"
|
||||
color="green"
|
||||
style={{ boxShadow: "0 0 8px rgba(0,255,200,0.2)" }}
|
||||
onClick={() => { setDataChoose(v); openDetail() }}
|
||||
>
|
||||
<IconEye size={20} />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
<Tooltip label="Edit Kategori">
|
||||
<ActionIcon
|
||||
variant="light"
|
||||
size="sm"
|
||||
style={{ boxShadow: "0 0 8px rgba(0,255,200,0.2)" }}
|
||||
onClick={() => { setDataChoose(v); open(); }}
|
||||
>
|
||||
<IconEdit size={20} />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
<Tooltip label="Delete Kategori">
|
||||
<ActionIcon
|
||||
variant="light"
|
||||
size="sm"
|
||||
color="red"
|
||||
style={{ boxShadow: "0 0 8px rgba(0,255,200,0.2)" }}
|
||||
onClick={() => {
|
||||
setDataDelete(v.id)
|
||||
openDelete()
|
||||
}}
|
||||
>
|
||||
<IconTrash size={20} />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
</Group>
|
||||
</Table.Td>
|
||||
</Table.Tr>
|
||||
))}
|
||||
</Table.Tbody>
|
||||
</Table>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import DesaSetting from "@/components/DesaSetting";
|
||||
import KategoriPelayananSurat from "@/components/KategoriPelayananSurat";
|
||||
import KategoriPengaduan from "@/components/KategoriPengaduan";
|
||||
import ProfileUser from "@/components/ProfileUser";
|
||||
import UserSetting from "@/components/UserSetting";
|
||||
@@ -90,7 +91,7 @@ export default function DetailSettingPage() {
|
||||
{type === "cat-pengaduan" ? (
|
||||
<KategoriPengaduan />
|
||||
) : type === "cat-pelayanan" ? (
|
||||
<KategoriPengaduanPage />
|
||||
<KategoriPelayananSurat />
|
||||
) : type === "desa" ? (
|
||||
<DesaSetting />
|
||||
) : type === "user" ? (
|
||||
@@ -103,48 +104,4 @@ export default function DetailSettingPage() {
|
||||
</Grid>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
function KategoriPengaduanPage() {
|
||||
const elements = [
|
||||
{ position: 6, mass: 12.011, symbol: "C", name: "Carbon" },
|
||||
{ position: 7, mass: 14.007, symbol: "N", name: "Nitrogen" },
|
||||
{ position: 39, mass: 88.906, symbol: "Y", name: "Yttrium" },
|
||||
{ position: 56, mass: 137.33, symbol: "Ba", name: "Barium" },
|
||||
{ position: 58, mass: 140.12, symbol: "Ce", name: "Cerium" },
|
||||
];
|
||||
|
||||
const rows = elements.map((element) => (
|
||||
<Table.Tr key={element.name}>
|
||||
<Table.Td>{element.position}</Table.Td>
|
||||
<Table.Td>{element.name}</Table.Td>
|
||||
<Table.Td>{element.symbol}</Table.Td>
|
||||
<Table.Td>{element.mass}</Table.Td>
|
||||
</Table.Tr>
|
||||
));
|
||||
return (
|
||||
<Stack gap={"md"}>
|
||||
<Flex align="center" justify="space-between">
|
||||
<Title order={4} c="gray.2">
|
||||
Kategori Pengaduan
|
||||
</Title>
|
||||
<Button variant="light">Tambah</Button>
|
||||
</Flex>
|
||||
<Divider my={0} />
|
||||
<Stack gap={"md"}>
|
||||
<Table>
|
||||
<Table.Thead>
|
||||
<Table.Tr>
|
||||
<Table.Th>Tanggal</Table.Th>
|
||||
<Table.Th>Deskripsi</Table.Th>
|
||||
<Table.Th>Status</Table.Th>
|
||||
<Table.Th>User</Table.Th>
|
||||
</Table.Tr>
|
||||
</Table.Thead>
|
||||
<Table.Tbody>{rows}</Table.Tbody>
|
||||
</Table>
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
import Elysia, { StatusMap, t } from "elysia"
|
||||
import { generateNoPengajuanSurat } from "../lib/no-pengajuan-surat"
|
||||
import { prisma } from "../lib/prisma"
|
||||
import Elysia, { t } from "elysia"
|
||||
import type { StatusPengaduan } from "generated/prisma"
|
||||
import { generateNoPengajuanSurat } from "../lib/no-pengajuan-surat"
|
||||
import { normalizePhoneNumber } from "../lib/normalizePhone"
|
||||
import { prisma } from "../lib/prisma"
|
||||
|
||||
const PelayananRoute = new Elysia({
|
||||
prefix: "pelayanan",
|
||||
@@ -15,7 +15,7 @@ const PelayananRoute = new Elysia({
|
||||
where: {
|
||||
isActive: true
|
||||
},
|
||||
orderBy:{
|
||||
orderBy: {
|
||||
name: "asc"
|
||||
}
|
||||
})
|
||||
@@ -42,8 +42,8 @@ const PelayananRoute = new Elysia({
|
||||
}, {
|
||||
body: t.Object({
|
||||
name: t.String({ minLength: 1, error: "name harus diisi" }),
|
||||
syaratDokumen: t.Array(t.String({ minLength: 1, error: "syaratDokumen harus diisi" })),
|
||||
dataText: t.Array(t.String({ minLength: 1, error: "dataText harus diisi" })),
|
||||
syaratDokumen: t.Any(),
|
||||
dataText: t.Any(),
|
||||
}),
|
||||
detail: {
|
||||
summary: "buat kategori pelayanan surat",
|
||||
@@ -69,8 +69,8 @@ const PelayananRoute = new Elysia({
|
||||
body: t.Object({
|
||||
id: t.String({ minLength: 1, error: "id harus diisi" }),
|
||||
name: t.String({ minLength: 1, error: "name harus diisi" }),
|
||||
syaratDokumen: t.Array(t.String({ minLength: 1, error: "syaratDokumen harus diisi" })),
|
||||
dataText: t.Array(t.String({ minLength: 1, error: "dataText harus diisi" })),
|
||||
syaratDokumen: t.Any(),
|
||||
dataText: t.Any(),
|
||||
}),
|
||||
detail: {
|
||||
summary: "update kategori pelayanan surat",
|
||||
|
||||
Reference in New Issue
Block a user