Compare commits
12 Commits
amalia/01-
...
amalia/02-
| Author | SHA1 | Date | |
|---|---|---|---|
| 700fbe3bd7 | |||
| 2d376663bb | |||
| 0ed9dc6ddd | |||
| 48a7d43713 | |||
| 6a52d10faa | |||
| 2b94684570 | |||
| cc7c8eb704 | |||
| 4996da4189 | |||
| 5af9b720ca | |||
| 1f95c7d7d8 | |||
| f52f5f87ca | |||
| 9c7c9d8595 |
@@ -21,7 +21,7 @@ export default function DashboardCountData() {
|
||||
label="Pengaduan Hari Ini"
|
||||
value={String(data?.data?.pengaduan?.today)}
|
||||
change={String(data?.data?.pengaduan?.kenaikan) + "%"}
|
||||
color={(data?.data?.pengaduan?.kenaikan || 0) > 0 ? "teal" : "gray"}
|
||||
color={"gray"}
|
||||
/>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={{ base: 12, sm: 6, md: 4 }}>
|
||||
@@ -30,7 +30,7 @@ export default function DashboardCountData() {
|
||||
label="Pengajuan Surat Hari Ini"
|
||||
value={String(data?.data?.pelayanan?.today)}
|
||||
change={String(data?.data?.pelayanan?.kenaikan) + "%"}
|
||||
color={(data?.data?.pelayanan?.kenaikan || 0) > 0 ? "teal" : "gray"}
|
||||
color="gray"
|
||||
/>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={{ base: 12, sm: 6, md: 4 }}>
|
||||
|
||||
@@ -6,8 +6,10 @@ import {
|
||||
Container,
|
||||
Divider,
|
||||
Flex,
|
||||
Grid,
|
||||
Group,
|
||||
Input,
|
||||
Pagination,
|
||||
Stack,
|
||||
Tabs,
|
||||
Text,
|
||||
@@ -113,22 +115,26 @@ type StatusKey =
|
||||
function ListPelayananSurat({ status }: { status: StatusKey }) {
|
||||
const [page, setPage] = useState(1);
|
||||
const [value, setValue] = useState("");
|
||||
const { data, mutate, isLoading } = useSwr("/", async () => {
|
||||
const res = await apiFetch.api.pelayanan.list.get({
|
||||
const { data, mutate, isLoading } = useSwr("/", async () =>
|
||||
apiFetch.api.pelayanan.list.get({
|
||||
query: {
|
||||
status,
|
||||
search: value,
|
||||
take: "",
|
||||
page: "",
|
||||
page: page.toString(),
|
||||
},
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
useShallowEffect(() => {
|
||||
setPage(1);
|
||||
mutate();
|
||||
}, [status, value]);
|
||||
|
||||
return Array.isArray(res?.data) ? res.data : []; // ⬅ paksa return array
|
||||
});
|
||||
|
||||
useShallowEffect(() => {
|
||||
mutate();
|
||||
}, [status, value]);
|
||||
}, [page]);
|
||||
|
||||
|
||||
useShallowEffect(() => {
|
||||
@@ -155,26 +161,39 @@ function ListPelayananSurat({ status }: { status: StatusKey }) {
|
||||
</Card>
|
||||
);
|
||||
|
||||
const list = data || [];
|
||||
const list = data?.data?.data || [];
|
||||
const total = data?.data?.total || 0;
|
||||
const totalPage = data?.data?.totalPages || 1;
|
||||
const pageSize = data?.data?.pageSize || 10;
|
||||
const pageNow = data?.data?.page || 1;
|
||||
const toDate = (d: any) => new Date(d);
|
||||
|
||||
return (
|
||||
<Stack gap="xl">
|
||||
<Group grow>
|
||||
<Input
|
||||
value={value}
|
||||
placeholder="Cari pengajuan..."
|
||||
onChange={(event) => setValue(event.currentTarget.value)}
|
||||
leftSection={<IconSearch size={16} />}
|
||||
rightSectionPointerEvents="all"
|
||||
rightSection={
|
||||
<CloseButton
|
||||
aria-label="Clear input"
|
||||
onClick={() => setValue("")}
|
||||
style={{ display: value ? undefined : "none" }}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</Group>
|
||||
<Grid>
|
||||
<Grid.Col span={9}>
|
||||
<Input
|
||||
value={value}
|
||||
placeholder="Cari pengajuan..."
|
||||
onChange={(event) => setValue(event.currentTarget.value)}
|
||||
leftSection={<IconSearch size={16} />}
|
||||
rightSectionPointerEvents="all"
|
||||
rightSection={
|
||||
<CloseButton
|
||||
aria-label="Clear input"
|
||||
onClick={() => setValue("")}
|
||||
style={{ display: value ? undefined : "none" }}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={3}>
|
||||
<Group justify="flex-end">
|
||||
<Text size="sm" c="gray.5">{`${pageSize * (page - 1) + 1} – ${Math.min(total, pageSize * page)} of ${total}`}</Text>
|
||||
<Pagination total={totalPage} value={page} onChange={setPage} withPages={false} />
|
||||
</Group>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
{Array.isArray(list) && list?.length === 0 ? (
|
||||
<Flex justify="center" align="center" py={"xl"}>
|
||||
<Stack gap={4} align="center">
|
||||
@@ -214,7 +233,7 @@ function ListPelayananSurat({ status }: { status: StatusKey }) {
|
||||
#{v.noPengajuan}
|
||||
</Title>
|
||||
<Text size="sm" c="dimmed">
|
||||
{v.updatedAt}
|
||||
{String(v.updatedAt)}
|
||||
</Text>
|
||||
</Group>
|
||||
</Flex>
|
||||
@@ -247,7 +266,7 @@ function ListPelayananSurat({ status }: { status: StatusKey }) {
|
||||
Tanggal Ajuan
|
||||
</Text>
|
||||
</Group>
|
||||
<Text size="md">{v.createdAt}</Text>
|
||||
<Text size="md">{toDate(v.createdAt).toLocaleDateString("id-ID", { day: "numeric", month: "long", year: "numeric" })}</Text>
|
||||
</Flex>
|
||||
<Flex direction={"column"} justify="flex-start">
|
||||
<Group gap="xs">
|
||||
|
||||
@@ -6,8 +6,10 @@ import {
|
||||
Container,
|
||||
Divider,
|
||||
Flex,
|
||||
Grid,
|
||||
Group,
|
||||
Input,
|
||||
Pagination,
|
||||
Stack,
|
||||
Tabs,
|
||||
Text,
|
||||
@@ -124,22 +126,25 @@ function ListPengaduan({ status }: { status: StatusKey }) {
|
||||
const navigate = useNavigate();
|
||||
const [page, setPage] = useState(1);
|
||||
const [value, setValue] = useState("");
|
||||
const { data, mutate, isLoading } = useSwr("/", async () => {
|
||||
const res = await apiFetch.api.pengaduan.list.get({
|
||||
const { data, mutate, isLoading } = useSwr("/", async () =>
|
||||
apiFetch.api.pengaduan.list.get({
|
||||
query: {
|
||||
status,
|
||||
search: value,
|
||||
take: "",
|
||||
page: "",
|
||||
page: page.toString(),
|
||||
},
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
return Array.isArray(res?.data) ? res.data : []; // ⬅ paksa return array
|
||||
});
|
||||
useShallowEffect(() => {
|
||||
setPage(1);
|
||||
mutate();
|
||||
}, [status, value]);
|
||||
|
||||
useShallowEffect(() => {
|
||||
mutate();
|
||||
}, [status, value]);
|
||||
}, [page]);
|
||||
|
||||
useShallowEffect(() => {
|
||||
const unsubscribe = subscribe(state, () => mutate());
|
||||
@@ -163,31 +168,41 @@ function ListPengaduan({ status }: { status: StatusKey }) {
|
||||
</Card>
|
||||
);
|
||||
|
||||
const list = data || [];
|
||||
const list = data?.data?.data || [];
|
||||
const total = data?.data?.total || 0;
|
||||
const totalPage = data?.data?.totalPages || 1;
|
||||
const pageSize = data?.data?.pageSize || 10;
|
||||
const pageNow = data?.data?.page || 1;
|
||||
const toDate = (d: any) => new Date(d);
|
||||
|
||||
|
||||
return (
|
||||
<Stack gap="xl">
|
||||
<Group grow>
|
||||
<Input
|
||||
value={value}
|
||||
placeholder="Cari pengaduan..."
|
||||
onChange={(event) => setValue(event.currentTarget.value)}
|
||||
leftSection={<IconSearch size={16} />}
|
||||
rightSectionPointerEvents="all"
|
||||
rightSection={
|
||||
<CloseButton
|
||||
aria-label="Clear input"
|
||||
onClick={() => setValue("")}
|
||||
style={{ display: value ? undefined : "none" }}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
{/* <Group justify="flex-end">
|
||||
<Text size="sm">Menampilkan {Number(data?.data?.length) * (page - 1) + 1} – {Math.min(10, Number(data?.data?.length) * page)} dari {Number(data?.data?.length)}</Text>
|
||||
<Pagination total={Number(data?.data?.length)} value={page} onChange={setPage} withPages={false} />
|
||||
</Group> */}
|
||||
</Group>
|
||||
{list.length === 0 ? (
|
||||
<Grid>
|
||||
<Grid.Col span={9}>
|
||||
<Input
|
||||
value={value}
|
||||
placeholder="Cari pengaduan..."
|
||||
onChange={(event) => setValue(event.currentTarget.value)}
|
||||
leftSection={<IconSearch size={16} />}
|
||||
rightSectionPointerEvents="all"
|
||||
rightSection={
|
||||
<CloseButton
|
||||
aria-label="Clear input"
|
||||
onClick={() => setValue("")}
|
||||
style={{ display: value ? undefined : "none" }}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={3}>
|
||||
<Group justify="flex-end">
|
||||
<Text size="sm" c="gray.5">{`${pageSize * (page - 1) + 1} – ${Math.min(total, pageSize * page)} of ${total}`}</Text>
|
||||
<Pagination total={totalPage} value={page} onChange={setPage} withPages={false} />
|
||||
</Group>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
{Array.isArray(list) && list.length === 0 ? (
|
||||
<Flex justify="center" align="center" py={"xl"}>
|
||||
<Stack gap={4} align="center">
|
||||
<IconFileSad size={32} color="gray" />
|
||||
@@ -224,7 +239,7 @@ function ListPengaduan({ status }: { status: StatusKey }) {
|
||||
#{v.noPengaduan}
|
||||
</Title>
|
||||
<Text size="sm" c="dimmed">
|
||||
{v.updatedAt}
|
||||
{String(v.updatedAt)}
|
||||
</Text>
|
||||
</Group>
|
||||
</Flex>
|
||||
@@ -257,7 +272,7 @@ function ListPengaduan({ status }: { status: StatusKey }) {
|
||||
Tanggal Aduan
|
||||
</Text>
|
||||
</Group>
|
||||
<Text size="md">{v.createdAt}</Text>
|
||||
<Text size="md">{toDate(v.createdAt).toLocaleDateString("id-ID", { day: "numeric", month: "long", year: "numeric" })}</Text>
|
||||
</Flex>
|
||||
<Flex direction={"column"} justify="flex-start">
|
||||
<Group gap="xs">
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { StatusPengaduan } from "generated/prisma"
|
||||
import { createSurat } from "../lib/create-surat"
|
||||
import { getLastUpdated } from "../lib/get-last-updated"
|
||||
import { generateNoPengajuanSurat } from "../lib/no-pengajuan-surat"
|
||||
import { normalizePhoneNumber } from "../lib/normalizePhone"
|
||||
import { isValidPhone, normalizePhoneNumber } from "../lib/normalizePhone"
|
||||
import { prisma } from "../lib/prisma"
|
||||
|
||||
const PelayananRoute = new Elysia({
|
||||
@@ -292,10 +292,10 @@ const PelayananRoute = new Elysia({
|
||||
}
|
||||
})
|
||||
.post("/create", async ({ body }) => {
|
||||
const { kategoriId, wargaId, noTelepon, dataText, syaratDokumen } = body
|
||||
const { kategoriId, namaWarga, noTelepon, dataText, syaratDokumen } = body
|
||||
const noPengajuan = await generateNoPengajuanSurat()
|
||||
let idCategoryFix = kategoriId
|
||||
let idWargaFix = wargaId
|
||||
let idWargaFix = ""
|
||||
const category = await prisma.categoryPelayanan.findUnique({
|
||||
where: {
|
||||
id: kategoriId,
|
||||
@@ -317,36 +317,28 @@ const PelayananRoute = new Elysia({
|
||||
|
||||
}
|
||||
|
||||
const warga = await prisma.warga.findUnique({
|
||||
if (!isValidPhone(noTelepon)) {
|
||||
return { success: false, message: 'nomor telepon tidak valid, harap masukkan nomor yang benar' }
|
||||
}
|
||||
|
||||
const nomorHP = normalizePhoneNumber({ phone: noTelepon })
|
||||
const dataWarga = await prisma.warga.upsert({
|
||||
where: {
|
||||
id: wargaId,
|
||||
phone: nomorHP
|
||||
},
|
||||
create: {
|
||||
name: namaWarga,
|
||||
phone: nomorHP,
|
||||
},
|
||||
update: {
|
||||
name: namaWarga,
|
||||
},
|
||||
select: {
|
||||
id: true
|
||||
}
|
||||
})
|
||||
|
||||
if (!warga) {
|
||||
const nomorHP = normalizePhoneNumber({ phone: noTelepon })
|
||||
const cariWarga = await prisma.warga.findFirst({
|
||||
where: {
|
||||
phone: nomorHP,
|
||||
}
|
||||
})
|
||||
|
||||
if (!cariWarga) {
|
||||
const wargaCreate = await prisma.warga.create({
|
||||
data: {
|
||||
name: wargaId,
|
||||
phone: nomorHP,
|
||||
},
|
||||
select: {
|
||||
id: true
|
||||
}
|
||||
})
|
||||
idWargaFix = wargaCreate.id
|
||||
} else {
|
||||
idWargaFix = cariWarga.id
|
||||
}
|
||||
|
||||
}
|
||||
idWargaFix = dataWarga.id
|
||||
|
||||
const pengaduan = await prisma.pelayananAjuan.create({
|
||||
data: {
|
||||
@@ -400,40 +392,34 @@ const PelayananRoute = new Elysia({
|
||||
}
|
||||
})
|
||||
|
||||
return { success: true, message: 'pengajuan surat sudah dibuat' }
|
||||
return { success: true, message: 'pengajuan layanan surat sudah dibuat dengan nomer ' + noPengajuan + ', nomer ini akan digunakan untuk mengakses pengajuan ini' }
|
||||
}, {
|
||||
body: t.Object({
|
||||
kategoriId: t.String({
|
||||
minLength: 1,
|
||||
description: "ID atau nama kategori pelayanan surat yang dipilih. Jika berupa nama, sistem akan mencocokkan secara otomatis.",
|
||||
examples: ["skusaha"],
|
||||
error: "ID kategori harus diisi"
|
||||
}),
|
||||
|
||||
wargaId: t.String({
|
||||
minLength: 1,
|
||||
description: "ID warga atau nama warga. Jika ID tidak ditemukan, sistem akan mencari berdasarkan nama.",
|
||||
namaWarga: t.String({
|
||||
description: "Nama warga",
|
||||
examples: ["Budi Santoso"],
|
||||
error: "ID warga harus diisi"
|
||||
error: "Nama warga harus diisi"
|
||||
}),
|
||||
|
||||
noTelepon: t.String({
|
||||
minLength: 8,
|
||||
description: "Nomor HP warga yang akan dinormalisasi. Jika data warga tidak ditemukan berdasarkan idWarga, pencarian dilakukan via nomor ini.",
|
||||
examples: ["081234567890"],
|
||||
error: "Nomor telepon harus diisi"
|
||||
error: "Nomor telepon harus diisi",
|
||||
examples: ["08123456789", "+628123456789"],
|
||||
description: "Nomor telepon warga pelapor"
|
||||
}),
|
||||
|
||||
dataText: t.Array(
|
||||
t.Object({
|
||||
jenis: t.String({
|
||||
minLength: 1,
|
||||
description: "Jenis field yang dibutuhkan oleh kategori pelayanan. Biasanya dinamis.",
|
||||
examples: ["nama", "jenis kelamin", "tempat tanggal lahir", "negara", "agama", "status perkawinan", "alamat", "pekerjaan", "jenis usaha", "alamat usaha"],
|
||||
error: "jenis harus diisi"
|
||||
}),
|
||||
value: t.String({
|
||||
minLength: 1,
|
||||
description: "Isi atau nilai dari jenis field terkait.",
|
||||
examples: ["Budi Santoso", "Laki-laki", "Denpasar, 28 Februari 1990", "Indonesia", "Islam", "Belum menikah", "Jl. Mawar No. 10", "Karyawan Swasta", "usaha makanan", "Jl. Melati No. 21"],
|
||||
error: "value harus diisi"
|
||||
@@ -462,13 +448,11 @@ const PelayananRoute = new Elysia({
|
||||
syaratDokumen: t.Array(
|
||||
t.Object({
|
||||
jenis: t.String({
|
||||
minLength: 1,
|
||||
description: "Jenis dokumen persyaratan yang diminta oleh kategori layanan.",
|
||||
examples: ["ktp", "kk", "surat_pengantar_rt"],
|
||||
error: "jenis harus diisi"
|
||||
}),
|
||||
value: t.String({
|
||||
minLength: 1,
|
||||
description: "Nama file atau identifier file dokumen yang diupload.",
|
||||
examples: ["ktp_budi.png", "kk_budi.png"],
|
||||
error: "value harus diisi"
|
||||
@@ -488,7 +472,7 @@ const PelayananRoute = new Elysia({
|
||||
),
|
||||
}),
|
||||
detail: {
|
||||
summary: "Create Pengajuan Pelayanan Surat",
|
||||
summary: "Buat Pengajuan Pelayanan Surat",
|
||||
description: `tool untuk membuat pengajuan pelayanan surat dengan syarat dokumen serta data text sesuai kategori pelayanan surat yang dipilih`,
|
||||
tags: ["mcp"]
|
||||
}
|
||||
@@ -581,6 +565,14 @@ const PelayananRoute = new Elysia({
|
||||
mode: "insensitive"
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Warga: {
|
||||
name: {
|
||||
contains: search ?? "",
|
||||
mode: "insensitive"
|
||||
},
|
||||
},
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -592,6 +584,11 @@ const PelayananRoute = new Elysia({
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const totalData = await prisma.pelayananAjuan.count({
|
||||
where
|
||||
});
|
||||
|
||||
const data = await prisma.pelayananAjuan.findMany({
|
||||
skip,
|
||||
take: !take ? 10 : Number(take),
|
||||
@@ -625,12 +622,20 @@ const PelayananRoute = new Elysia({
|
||||
category: item.CategoryPelayanan.name,
|
||||
warga: item.Warga.name,
|
||||
status: item.status,
|
||||
createdAt: item.createdAt.toLocaleDateString("id-ID", { day: "numeric", month: "long", year: "numeric" }),
|
||||
createdAt: item.createdAt.toISOString(),
|
||||
updatedAt: 'terakhir diperbarui ' + getLastUpdated(item.updatedAt),
|
||||
}
|
||||
})
|
||||
|
||||
return dataFix
|
||||
const dataReturn = {
|
||||
data: dataFix,
|
||||
total: totalData,
|
||||
page: Number(page) || 1,
|
||||
pageSize: !take ? 10 : Number(take),
|
||||
totalPages: Math.ceil(totalData / (!take ? 10 : Number(take)))
|
||||
}
|
||||
|
||||
return dataReturn
|
||||
}, {
|
||||
query: t.Object({
|
||||
take: t.String({ optional: true }),
|
||||
|
||||
@@ -646,6 +646,10 @@ const PengaduanRoute = new Elysia({
|
||||
}
|
||||
}
|
||||
|
||||
const totalData = await prisma.pengaduan.count({
|
||||
where
|
||||
});
|
||||
|
||||
const data = await prisma.pengaduan.findMany({
|
||||
skip,
|
||||
take: !take ? 10 : Number(take),
|
||||
@@ -683,12 +687,20 @@ const PengaduanRoute = new Elysia({
|
||||
detail: item.detail,
|
||||
status: item.status,
|
||||
location: item.location,
|
||||
createdAt: item.createdAt.toLocaleDateString("id-ID", { day: "numeric", month: "long", year: "numeric" }),
|
||||
createdAt: item.createdAt.toISOString(),
|
||||
updatedAt: 'terakhir diperbarui ' + getLastUpdated(item.updatedAt),
|
||||
}
|
||||
})
|
||||
|
||||
return dataFix
|
||||
const dataReturn = {
|
||||
data: dataFix,
|
||||
total: totalData,
|
||||
page: Number(page) || 1,
|
||||
pageSize: !take ? 10 : Number(take),
|
||||
totalPages: Math.ceil(totalData / (!take ? 10 : Number(take)))
|
||||
}
|
||||
|
||||
return dataReturn
|
||||
}, {
|
||||
query: t.Object({
|
||||
take: t.String({ optional: true }),
|
||||
@@ -790,8 +802,6 @@ const PengaduanRoute = new Elysia({
|
||||
description: "Tool untuk delete file Seafile",
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
;
|
||||
|
||||
export default PengaduanRoute
|
||||
|
||||
Reference in New Issue
Block a user