diff --git a/bun.lockb b/bun.lockb index c65dcabb..0618a3a7 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index 1ecbbbab..3f5f3a58 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,7 @@ "react-simple-toasts": "^6.1.0", "react-toastify": "^11.0.5", "readdirp": "^4.1.1", - "recharts": "2", + "recharts": "^2.15.3", "swr": "^2.3.2", "valtio": "^2.1.3", "zod": "^3.24.3" diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 53c29bf3..4bce5939 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -46,6 +46,7 @@ model AppMenuChild { AppMenu AppMenu? @relation(fields: [appMenuId], references: [id]) appMenuId String? } + // ========================================= MENU DESA ========================================= // // ========================================= BERITA ========================================= // model Berita { @@ -106,6 +107,7 @@ model Images { updatedAt DateTime @updatedAt GalleryFoto GalleryFoto[] } + // ========================================= VIDEOS ========================================= // model Videos { id String @id @default(cuid()) @@ -117,7 +119,6 @@ model Videos { GalleryVideo GalleryVideo[] } - // ========================================= GALLERY ========================================= // model GalleryFoto { id String @id @default(cuid()) @@ -132,14 +133,14 @@ model GalleryFoto { } model GalleryVideo { - id String @id @default(cuid()) - name String - video String - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - deletedAt DateTime @default(now()) - isActive Boolean @default(true) - videosId String? @unique + id String @id @default(cuid()) + name String + video String + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + deletedAt DateTime @default(now()) + isActive Boolean @default(true) + videosId String? @unique videosGalleryVideo Videos? @relation(fields: [videosId], references: [id]) } @@ -147,6 +148,159 @@ model GalleryVideo { // ========================================= DATA KESEHATAN WARGA ========================================= // // ========================================= FASILITAS KESEHATAN ========================================= // +model FasilitasKesehatan { + id String @id @default(cuid()) + name String + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + deletedAt DateTime @default(now()) + isActive Boolean @default(true) + InformasiUmum InformasiUmum[] + LayananUnggulan LayananUnggulan[] + DokterdanTenagaMedis DokterdanTenagaMedis[] + FasilitasPendukung FasilitasPendukung[] + ProsedurPendaftaran ProsedurPendaftaran[] + TarifDanLayanan TarifDanLayanan[] +} + +model InformasiUmum { + id String @id @default(cuid()) + fasilitas String + alamat String + jamOperasional String + FasilitasKesehatan FasilitasKesehatan[] + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + deletedAt DateTime @default(now()) + isActive Boolean @default(true) +} + +model LayananUnggulan { + id String @id @default(cuid()) + content String + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + deletedAt DateTime @default(now()) + isActive Boolean @default(true) + FasilitasKesehatan FasilitasKesehatan[] +} + +model DokterdanTenagaMedis { + id String @id @default(cuid()) + name String + specialist String + jadwal String + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + deletedAt DateTime @default(now()) + isActive Boolean @default(true) + FasilitasKesehatan FasilitasKesehatan[] +} + +model FasilitasPendukung { + id String @id @default(cuid()) + content String + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + deletedAt DateTime @default(now()) + isActive Boolean @default(true) + FasilitasKesehatan FasilitasKesehatan[] +} + +model ProsedurPendaftaran { + id String @id @default(cuid()) + content String + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + deletedAt DateTime @default(now()) + isActive Boolean @default(true) + FasilitasKesehatan FasilitasKesehatan[] +} + +model TarifDanLayanan { + id String @id @default(cuid()) + layanan String + tarif String + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + deletedAt DateTime @default(now()) + isActive Boolean @default(true) + FasilitasKesehatan FasilitasKesehatan[] +} + +// ========================================= JADWAL KEGIATAN ========================================= // +model JadwalKegiatan { + id String @id @default(cuid()) + content String + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + deletedAt DateTime @default(now()) + isActive Boolean @default(true) +} + +model InformasiJadwalKegiatan { + id String @id @default(cuid()) + name String + tanggal String + waktu String + lokasi String + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + deletedAt DateTime @default(now()) + isActive Boolean @default(true) +} + +model DeskripsiJadwalKegiatan { + id String @id @default(cuid()) + deskripsi String + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + deletedAt DateTime @default(now()) + isActive Boolean @default(true) +} + +model LayananJadwalKegiatan { + id String @id @default(cuid()) + content String + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + deletedAt DateTime @default(now()) + isActive Boolean @default(true) +} + +model SyaratKetentuanJadwalKegiatan { + id String @id @default(cuid()) + content String + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + deletedAt DateTime @default(now()) + isActive Boolean @default(true) +} + +model DokumenJadwalKegiatan { + id String @id @default(cuid()) + content String + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + deletedAt DateTime @default(now()) + isActive Boolean @default(true) +} + +model PendaftaranJadwalKegiatan { + id String @id @default(cuid()) + name String + tanggal String + namaOrangtua String + nomor String + alamat String + catatan String + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + deletedAt DateTime @default(now()) + isActive Boolean @default(true) +} + +// ========================================= PERSENTASE KELAHIRAN & KEMATIAN ========================================= // model DataKematian_Kelahiran { id Int @id @default(autoincrement()) tahun String @@ -155,156 +309,17 @@ model DataKematian_Kelahiran { kelahiranKasar String createdAt DateTime @default(now()) updatedAt DateTime @updatedAt + deletedAt DateTime @default(now()) + isActive Boolean @default(true) } -model FasilitasKesehatan { - id String @id @default(cuid()) - name String +// ========================================= GRAFIK KEPUASAN ========================================= // +model GrafikKepuasan { + id Int @id @default(autoincrement()) + label String + jumlah String createdAt DateTime @default(now()) updatedAt DateTime @updatedAt deletedAt DateTime @default(now()) - isActive Boolean @default(true) - InformasiUmum InformasiUmum[] - LayananUnggulan LayananUnggulan[] - DokterdanTenagaMedis DokterdanTenagaMedis[] - FasilitasPendukung FasilitasPendukung[] - ProsedurPendaftaran ProsedurPendaftaran[] - TarifDanLayanan TarifDanLayanan[] -} - -model InformasiUmum{ - id String @id @default(cuid()) - fasilitas String - alamat String - jamOperasional String - FasilitasKesehatan FasilitasKesehatan[] - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - deletedAt DateTime @default(now()) - isActive Boolean @default(true) -} - -model LayananUnggulan{ - id String @id @default(cuid()) - content String - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - deletedAt DateTime @default(now()) - isActive Boolean @default(true) - FasilitasKesehatan FasilitasKesehatan[] -} - -model DokterdanTenagaMedis{ - id String @id @default(cuid()) - name String - specialist String - jadwal String - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - deletedAt DateTime @default(now()) - isActive Boolean @default(true) - FasilitasKesehatan FasilitasKesehatan[] -} - -model FasilitasPendukung{ - id String @id @default(cuid()) - content String - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - deletedAt DateTime @default(now()) - isActive Boolean @default(true) - FasilitasKesehatan FasilitasKesehatan[] -} - -model ProsedurPendaftaran{ - id String @id @default(cuid()) - content String - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - deletedAt DateTime @default(now()) - isActive Boolean @default(true) - FasilitasKesehatan FasilitasKesehatan[] -} - -model TarifDanLayanan{ - id String @id @default(cuid()) - layanan String - tarif String - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - deletedAt DateTime @default(now()) - isActive Boolean @default(true) - FasilitasKesehatan FasilitasKesehatan[] -} - -// ========================================= JADWAL KEGIATAN ========================================= // -model JadwalKegiatan{ - id String @id @default(cuid()) - content String - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - deletedAt DateTime @default(now()) - isActive Boolean @default(true) -} - -model InformasiJadwalKegiatan{ - id String @id @default(cuid()) - name String - tanggal String - waktu String - lokasi String - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - deletedAt DateTime @default(now()) - isActive Boolean @default(true) -} - -model DeskripsiJadwalKegiatan{ - id String @id @default(cuid()) - deskripsi String - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - deletedAt DateTime @default(now()) - isActive Boolean @default(true) -} - -model LayananJadwalKegiatan{ - id String @id @default(cuid()) - content String - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - deletedAt DateTime @default(now()) - isActive Boolean @default(true) -} - -model SyaratKetentuanJadwalKegiatan{ - id String @id @default(cuid()) - content String - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - deletedAt DateTime @default(now()) - isActive Boolean @default(true) -} - -model DokumenJadwalKegiatan{ - id String @id @default(cuid()) - content String - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - deletedAt DateTime @default(now()) - isActive Boolean @default(true) -} - -model PendaftaranJadwalKegiatan{ - id String @id @default(cuid()) - name String - tanggal DateTime - namaOrangtua String - nomor String - alamat String - catatan String - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - deletedAt DateTime @default(now()) - isActive Boolean @default(true) + isActive Boolean @default(true) } diff --git a/src/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/grafikKepuasan.ts b/src/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/grafikKepuasan.ts new file mode 100644 index 00000000..ac614ba9 --- /dev/null +++ b/src/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/grafikKepuasan.ts @@ -0,0 +1,76 @@ +import ApiFetch from "@/lib/api-fetch"; +import { Prisma } from "@prisma/client"; +import { toast } from "react-toastify"; +import { proxy } from "valtio"; +import { z } from "zod"; + +const templateGrafikKepuasan = z.object({ + label: z.string().min(2, "Label harus diisi"), + jumlah: z.string().min(2, "Jumlah harus diisi"), +}); + +type GrafikKepuasan = Prisma.GrafikKepuasanGetPayload<{ + select: { + label: true; + jumlah: true; + }; +}>; + +const defaultForm: GrafikKepuasan = { + label: "", + jumlah: "" +}; + +const grafikkepuasan = proxy({ + create: { + form: defaultForm, + loading: false, + async create() { + const cek = templateGrafikKepuasan.safeParse(grafikkepuasan.create.form); + if (!cek.success) { + const err = `[${cek.error.issues + .map((v) => `${v.path.join(".")}`) + .join("\n")}] required`; + return toast.error(err); + } + try { + grafikkepuasan.create.loading = true; + const res = await ApiFetch.api.kesehatan.grafikkepuasan["create"].post( + grafikkepuasan.create.form + ); + if (res.status === 200) { + grafikkepuasan.create.form = { + label: "", + jumlah: "" + }; + grafikkepuasan.findMany.load(); + return toast.success("success create"); + } + return toast.error("failed create"); + } catch (error) { + console.log((error as Error).message); + } finally { + grafikkepuasan.create.loading = false; + } + }, + }, + findMany: { + data: null as + | Prisma.GrafikKepuasanGetPayload<{ omit: { isActive: true } }>[] + | null, + async load() { + const res = await ApiFetch.api.kesehatan.grafikkepuasan[ + "find-many" + ].get(); + if (res.status === 200) { + grafikkepuasan.findMany.data = res.data?.data ?? []; + } + }, + }, +}); + +const stategrafikKepuasan = proxy({ + grafikkepuasan, +}); + +export default stategrafikKepuasan; diff --git a/src/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/persentaseKelahiran.ts b/src/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/persentaseKelahiran.ts new file mode 100644 index 00000000..3521775c --- /dev/null +++ b/src/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/persentaseKelahiran.ts @@ -0,0 +1,84 @@ +import ApiFetch from "@/lib/api-fetch"; +import { Prisma } from "@prisma/client"; +import { toast } from "react-toastify"; +import { proxy } from "valtio"; +import { z } from "zod"; + +const templatePersentase = z.object({ + tahun: z.string().min(4, "Tahun harus diisi"), + kematianKasar: z.string().min(2, "Kematian kasar harus diisi"), + kelahiranKasar: z.string().min(2, "Kelahiran kasar harus diisi"), + kematianBayi: z.string().min(2, "Kematian bayi harus diisi"), +}); + +type Persentase = Prisma.DataKematian_KelahiranGetPayload<{ + select: { + tahun: true; + kematianKasar: true; + kelahiranKasar: true; + kematianBayi: true; + }; +}>; + +const defaultForm: Persentase = { + tahun: "", + kematianKasar: "", + kelahiranKasar: "", + kematianBayi: "", +}; + +const persentasekelahiran = proxy({ + create: { + form: defaultForm, + loading: false, + async create() { + const cek = templatePersentase.safeParse(persentasekelahiran.create.form); + if (!cek.success) { + const err = `[${cek.error.issues + .map((v) => `${v.path.join(".")}`) + .join("\n")}] required`; + return toast.error(err); + } + try { + persentasekelahiran.create.loading = true; + const res = await ApiFetch.api.kesehatan.persentasekelahiran[ + "create" + ].post(persentasekelahiran.create.form); + if (res.status === 200) { + persentasekelahiran.create.form = { + tahun: "", + kematianKasar: "", + kelahiranKasar: "", + kematianBayi: "", + }; + persentasekelahiran.findMany.load(); + return toast.success("success create"); + } + return toast.error("failed create"); + } catch (error) { + console.log((error as Error).message); + } finally { + persentasekelahiran.create.loading = false; + } + }, + }, + findMany: { + data: null as + | Prisma.DataKematian_KelahiranGetPayload<{ omit: { isActive: true } }>[] + | null, + async load() { + const res = await ApiFetch.api.kesehatan.persentasekelahiran[ + "find-many" + ].get(); + if (res.status === 200) { + persentasekelahiran.findMany.data = res.data?.data ?? []; + } + }, + }, +}); + +const statePersentase = proxy({ + persentasekelahiran, +}); + +export default statePersentase; diff --git a/src/app/admin/(dashboard)/kesehatan/data-kesehatan-warga/_ui/grafik_hasil_kepuasan/page.tsx b/src/app/admin/(dashboard)/kesehatan/data-kesehatan-warga/_ui/grafik_hasil_kepuasan/page.tsx index f1da5b60..d9aefcf6 100644 --- a/src/app/admin/(dashboard)/kesehatan/data-kesehatan-warga/_ui/grafik_hasil_kepuasan/page.tsx +++ b/src/app/admin/(dashboard)/kesehatan/data-kesehatan-warga/_ui/grafik_hasil_kepuasan/page.tsx @@ -1,10 +1,72 @@ -import { Stack, Title } from '@mantine/core'; -import React from 'react'; +'use client' +import stategrafikKepuasan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/grafikKepuasan'; +import colors from '@/con/colors'; +import { Box, Button, Group, Stack, TextInput, Title } from '@mantine/core'; +import { useShallowEffect } from '@mantine/hooks'; +import { useState } from 'react'; +import { Bar, Legend, RadialBarChart, ResponsiveContainer, Tooltip, XAxis, YAxis } from 'recharts'; +import { useProxy } from 'valtio/utils'; function GrafikHasilKepuasan() { + const grafikkepuasan = useProxy(stategrafikKepuasan.grafikkepuasan) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const [chartData, setChartData] = useState([]) + useShallowEffect(() => { + const fetchData = async () => { + await grafikkepuasan.findMany.load(); + if (grafikkepuasan.findMany.data && grafikkepuasan.findMany.data.length > 0) { + setChartData(grafikkepuasan.findMany.data); + } + }; + fetchData(); + }, []); return ( - + Grafik Hasil Kepuasan + + { + grafikkepuasan.create.form.label = val.currentTarget.value + }} + /> + { + grafikkepuasan.create.form.jumlah = val.currentTarget.value + }} + /> + + + + + + Data Kelahiran & Kematian + + + + + + + + + + ); } diff --git a/src/app/admin/(dashboard)/kesehatan/data-kesehatan-warga/_ui/jadwal_kegiatan/page.tsx b/src/app/admin/(dashboard)/kesehatan/data-kesehatan-warga/_ui/jadwal_kegiatan/page.tsx index d3006d90..a38ea92f 100644 --- a/src/app/admin/(dashboard)/kesehatan/data-kesehatan-warga/_ui/jadwal_kegiatan/page.tsx +++ b/src/app/admin/(dashboard)/kesehatan/data-kesehatan-warga/_ui/jadwal_kegiatan/page.tsx @@ -61,7 +61,7 @@ function JadwalKegiatan() { - @@ -84,13 +84,15 @@ function AllList() { allList.layanantersedia.findMany.load() allList.syaratketentuan.findMany.load() allList.dokumenjadwalkegiatan.findMany.load() - }) + allList.pendaftaranjadwal.findMany.load() + }, []) const isLoading = !allList.informasiKegiatan.findMany.data || !allList.deskripsiKegiatan.findMany.data || !allList.layanantersedia.findMany.data || !allList.syaratketentuan.findMany.data || - !allList.dokumenjadwalkegiatan.findMany.data + !allList.dokumenjadwalkegiatan.findMany.data || + !allList.pendaftaranjadwal.findMany.data if (isLoading) { return ( diff --git a/src/app/admin/(dashboard)/kesehatan/data-kesehatan-warga/_ui/jadwal_kegiatan/pendaftaran/page.tsx b/src/app/admin/(dashboard)/kesehatan/data-kesehatan-warga/_ui/jadwal_kegiatan/pendaftaran/page.tsx index 8541a65a..f59af324 100644 --- a/src/app/admin/(dashboard)/kesehatan/data-kesehatan-warga/_ui/jadwal_kegiatan/pendaftaran/page.tsx +++ b/src/app/admin/(dashboard)/kesehatan/data-kesehatan-warga/_ui/jadwal_kegiatan/pendaftaran/page.tsx @@ -1,21 +1,10 @@ 'use client' import stateJadwalKegiatan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/jadwalKegiatan'; -import { ActionIcon, Box, Text, Textarea, TextInput } from '@mantine/core'; -import { DateInput } from '@mantine/dates'; -import { IconCalendar } from '@tabler/icons-react'; -import { useState } from 'react'; +import { Box, Text, Textarea, TextInput } from '@mantine/core'; import { useProxy } from 'valtio/utils'; function Pendaftaran() { const pendaftaran = useProxy(stateJadwalKegiatan.pendaftaranjadwal) - const [dateInputOpened, setDateInputOpened] = useState(false); - const pickerControl = ( - setDateInputOpened(true)} variant="subtle" color="gray"> - - - ); - - const formatDate = (date: Date | null): string => { if (!date) return ""; return date.toISOString().split('T')[0]; } return ( @@ -27,16 +16,11 @@ function Pendaftaran() { pendaftaran.create.form.name = val.target.value }} /> - { - pendaftaran.create.form.tanggal = formatDate(val); + pendaftaran.create.form.tanggal = val.target.value }} /> ([]); + const [mounted, setMounted] = useState(false); // untuk memastikan DOM sudah ready + + useEffect(() => { + setMounted(true); + }, []); + + useShallowEffect(() => { + const fetchData = async () => { + await persentase.findMany.load(); + if (persentase.findMany.data && persentase.findMany.data.length > 0) { + setChartData(persentase.findMany.data); + } + }; + fetchData(); + }, []); + return ( - Persentase Data Kelahiran & Kematian + {/* Form Input */} + + Persentase Data Kelahiran & Kematian + { + persentase.create.form.tahun = val.currentTarget.value; + }} + /> + { + persentase.create.form.kematianKasar = val.currentTarget.value; + }} + /> + { + persentase.create.form.kematianBayi = val.currentTarget.value; + }} + /> + { + persentase.create.form.kelahiranKasar = val.currentTarget.value; + }} + /> + + + + {/* Chart */} + + Data Kelahiran & Kematian + {mounted && chartData.length > 0 && ( + + + + + + + + + + + + )} + ); } diff --git a/src/app/admin/(dashboard)/kesehatan/data-kesehatan-warga/page.tsx b/src/app/admin/(dashboard)/kesehatan/data-kesehatan-warga/page.tsx index e8924f81..14e45529 100644 --- a/src/app/admin/(dashboard)/kesehatan/data-kesehatan-warga/page.tsx +++ b/src/app/admin/(dashboard)/kesehatan/data-kesehatan-warga/page.tsx @@ -1,10 +1,10 @@ +import colors from '@/con/colors'; import { Stack, Tabs, TabsList, TabsPanel, TabsTab } from '@mantine/core'; import ArtikelKesehatan from './_ui/artikel_kesehatan/page'; import FasilitasKesehatan from './_ui/fasilitas_kesehatan/page'; +import GrafikHasilKepuasan from './_ui/grafik_hasil_kepuasan/page'; import JadwalKegiatan from './_ui/jadwal_kegiatan/page'; import PersentaseDataKelahiranKematian from './_ui/persentase_data_kelahiran_kematian/page'; -import GrafikHasilKepuasan from './_ui/grafik_hasil_kepuasan/page'; -import colors from '@/con/colors'; function Page() { diff --git a/src/app/api/[[...slugs]]/_lib/kesehatan/data_kesehatan_warga/grafik_kepuasan/create.ts b/src/app/api/[[...slugs]]/_lib/kesehatan/data_kesehatan_warga/grafik_kepuasan/create.ts new file mode 100644 index 00000000..cb7e6f8b --- /dev/null +++ b/src/app/api/[[...slugs]]/_lib/kesehatan/data_kesehatan_warga/grafik_kepuasan/create.ts @@ -0,0 +1,27 @@ +import prisma from "@/lib/prisma"; +import { Prisma } from "@prisma/client"; +import { Context } from "elysia"; + +type FormCreate = Prisma.GrafikKepuasanGetPayload<{ + select: { + label: true; + jumlah: true + }; +}>; +export default async function grafikKepuasanCreate(context: Context) { + const body = context.body as FormCreate; + + await prisma.grafikKepuasan.create({ + data: { + label: body.label, + jumlah: body.jumlah, + }, + }); + return { + success: true, + message: "Success create grafik kepuasan", + data: { + ...body, + }, + }; +} diff --git a/src/app/api/[[...slugs]]/_lib/kesehatan/data_kesehatan_warga/grafik_kepuasan/find-many.ts b/src/app/api/[[...slugs]]/_lib/kesehatan/data_kesehatan_warga/grafik_kepuasan/find-many.ts new file mode 100644 index 00000000..f01bdab5 --- /dev/null +++ b/src/app/api/[[...slugs]]/_lib/kesehatan/data_kesehatan_warga/grafik_kepuasan/find-many.ts @@ -0,0 +1,8 @@ +import prisma from "@/lib/prisma" + +export default async function grafikKepuasanFindMany() { + const res = await prisma.grafikKepuasan.findMany() + return { + data: res + } +} \ No newline at end of file diff --git a/src/app/api/[[...slugs]]/_lib/kesehatan/data_kesehatan_warga/grafik_kepuasan/index.ts b/src/app/api/[[...slugs]]/_lib/kesehatan/data_kesehatan_warga/grafik_kepuasan/index.ts new file mode 100644 index 00000000..e533a509 --- /dev/null +++ b/src/app/api/[[...slugs]]/_lib/kesehatan/data_kesehatan_warga/grafik_kepuasan/index.ts @@ -0,0 +1,16 @@ +import Elysia, { t } from "elysia"; +import grafikKepuasanCreate from "./create"; +import grafikKepuasanFindMany from "./find-many"; + +const GrafikKepuasan = new Elysia({ + prefix: "/grafikkepuasan", + tags: ["Data Kesehatan/Grafik Kepuasan"] +}) + .get("/find-many", grafikKepuasanFindMany) + .post("/create", grafikKepuasanCreate, { + body: t.Object({ + label: t.String(), + jumlah: t.String(), + }), + }) +export default GrafikKepuasan \ No newline at end of file diff --git a/src/app/api/[[...slugs]]/_lib/kesehatan/data_kesehatan_warga/persentase_kelahiran_kematian/create.ts b/src/app/api/[[...slugs]]/_lib/kesehatan/data_kesehatan_warga/persentase_kelahiran_kematian/create.ts new file mode 100644 index 00000000..70942825 --- /dev/null +++ b/src/app/api/[[...slugs]]/_lib/kesehatan/data_kesehatan_warga/persentase_kelahiran_kematian/create.ts @@ -0,0 +1,32 @@ +import prisma from "@/lib/prisma"; +import { Prisma } from "@prisma/client"; +import { Context } from "elysia"; + +type FormCreate = Prisma.DataKematian_KelahiranGetPayload<{ + select: { + tahun: true; + kematianKasar: true; + kematianBayi: true; + kelahiranKasar: true; + }; +}>; + +export default async function persentaseKelahiranKematianCreate(context: Context) { + const body = context.body as FormCreate + + await prisma.dataKematian_Kelahiran.create({ + data: { + tahun: body.tahun, + kematianKasar: body.kematianKasar, + kematianBayi: body.kematianBayi, + kelahiranKasar: body.kelahiranKasar, + } + }) + return{ + success: true, + message: "Success create persentase kelahiran kematian", + data: { + ...body + } + } +} \ No newline at end of file diff --git a/src/app/api/[[...slugs]]/_lib/kesehatan/data_kesehatan_warga/persentase_kelahiran_kematian/find-many.ts b/src/app/api/[[...slugs]]/_lib/kesehatan/data_kesehatan_warga/persentase_kelahiran_kematian/find-many.ts new file mode 100644 index 00000000..c3ed94ee --- /dev/null +++ b/src/app/api/[[...slugs]]/_lib/kesehatan/data_kesehatan_warga/persentase_kelahiran_kematian/find-many.ts @@ -0,0 +1,9 @@ +import prisma from "@/lib/prisma"; + +export default async function persentaseKelahiranKematianFindMany() { + const res = await prisma.dataKematian_Kelahiran.findMany(); + return { + data: res + } +} + \ No newline at end of file diff --git a/src/app/api/[[...slugs]]/_lib/kesehatan/data_kesehatan_warga/persentase_kelahiran_kematian/index.ts b/src/app/api/[[...slugs]]/_lib/kesehatan/data_kesehatan_warga/persentase_kelahiran_kematian/index.ts new file mode 100644 index 00000000..7ecb2381 --- /dev/null +++ b/src/app/api/[[...slugs]]/_lib/kesehatan/data_kesehatan_warga/persentase_kelahiran_kematian/index.ts @@ -0,0 +1,19 @@ +import Elysia, { t } from "elysia"; +import persentaseKelahiranKematianCreate from "./create"; +import persentaseKelahiranKematianFindMany from "./find-many"; + +const PersentaseKelahiranKematian = new Elysia({ + prefix: "/persentasekelahiran", + tags: ["Data Kesehatan/Persentase Kelahiran Kematian"], +}) + .get("/find-many", persentaseKelahiranKematianFindMany) + .post("/create", persentaseKelahiranKematianCreate, { + body: t.Object({ + tahun: t.String(), + kematianKasar: t.String(), + kematianBayi: t.String(), + kelahiranKasar: t.String(), + }), + }) + +export default PersentaseKelahiranKematian; \ No newline at end of file diff --git a/src/app/api/[[...slugs]]/_lib/kesehatan/index.ts b/src/app/api/[[...slugs]]/_lib/kesehatan/index.ts index 5a06c789..b064f356 100644 --- a/src/app/api/[[...slugs]]/_lib/kesehatan/index.ts +++ b/src/app/api/[[...slugs]]/_lib/kesehatan/index.ts @@ -11,6 +11,9 @@ import LayananTersedia from "./data_kesehatan_warga/jadwal_kegiatan/layanan_yang import SyaratKetentuan from "./data_kesehatan_warga/jadwal_kegiatan/syarat_dan_ketentuan"; import DokumenDiperlukan from "./data_kesehatan_warga/jadwal_kegiatan/dokumen_yang_diperlukan"; import PendaftaranJadwal from "./data_kesehatan_warga/jadwal_kegiatan/pendaftaran"; +import PersentaseKelahiranKematian from "./data_kesehatan_warga/persentase_kelahiran_kematian"; +import GrafikKepuasan from "./data_kesehatan_warga/grafik_kepuasan"; + const Kesehatan = new Elysia({ prefix: "/api/kesehatan", @@ -28,4 +31,6 @@ const Kesehatan = new Elysia({ .use(SyaratKetentuan) .use(DokumenDiperlukan) .use(PendaftaranJadwal) +.use(PersentaseKelahiranKematian) +.use(GrafikKepuasan) export default Kesehatan; diff --git a/src/app/percobaan/_lib/ClientRouter.txt b/src/app/percobaan/_lib/ClientRouter.txt new file mode 100644 index 00000000..b666bba3 --- /dev/null +++ b/src/app/percobaan/_lib/ClientRouter.txt @@ -0,0 +1,300 @@ +"use client"; +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable @typescript-eslint/no-empty-object-type */ +import { z } from "zod"; + +type RouterLeaf> = { + get: () => string; + query: (params: z.infer) => string; + parse: (searchParams: URLSearchParams) => z.infer; +}; + +// Helper type to convert dashes to camelCase +type DashToCamelCase = S extends `${infer F}-${infer R}` + ? `${F}${Capitalize>}` + : S; + +// Modified RouterPath to handle dash conversion +type RouterPath< + T extends z.ZodType = z.ZodObject<{}>, + Segments extends string[] = [] +> = Segments extends [infer Head extends string, ...infer Tail extends string[]] + ? { [K in DashToCamelCase]: RouterPath } + : RouterLeaf; + +type RemoveLeadingSlash = S extends `/${infer Rest}` + ? Rest + : S; + +type SplitPath = S extends `${infer Head}/${infer Tail}` + ? [Head, ...SplitPath] + : S extends "" + ? [] + : [S]; + +type WibuRouterOptions = { + prefix?: string; + name?: string; +}; + +export class V2ClientRouter { + private tree: any = {}; + private prefix: string = ""; + private name: string = ""; + private querySchemas: Map = new Map(); + + constructor(options?: WibuRouterOptions) { + if (options?.prefix) { + // Ensure prefix starts with / and doesn't end with / + this.prefix = options.prefix.startsWith("/") + ? options.prefix + : `/${options.prefix}`; + + if (this.prefix.endsWith("/")) { + this.prefix = this.prefix.slice(0, -1); + } + } + + if (options?.name) { + this.name = options.name; + } + } + + // Convert dash-case to camelCase + private toCamelCase(str: string): string { + return str.replace(/-([a-z])/g, (g) => g[1].toUpperCase()); + } + + add< + Path extends string, + NormalizedPath extends string = RemoveLeadingSlash, + Segments extends string[] = SplitPath, + T extends z.ZodType = z.ZodObject<{}> + >( + path: Path, + schema?: { query: T } + ): V2ClientRouter< + Routes & + (NormalizedPath extends "" + ? RouterLeaf + : { + [K in Segments[0] as DashToCamelCase]: RouterPath< + T, + Segments extends [any, ...infer Rest] ? Rest : [] + >; + }) + > { + const normalizedPath = path.startsWith("/") ? path : `/${path}`; + const fullPath = `${this.prefix}${normalizedPath}`; + const segments = normalizedPath.split("/").filter(Boolean); + + // Store the Zod schema for this path + if (schema) { + this.querySchemas.set(fullPath, schema.query); + } else { + // Default empty schema + this.querySchemas.set(fullPath, z.object({})); + } + + const handleQuery = (params: any): string => { + if (!params || Object.keys(params).length === 0) return fullPath; + + // Validate params against schema + const schema = this.querySchemas.get(fullPath); + if (schema) { + try { + schema.parse(params); + } catch (error) { + console.error("Query params validation failed:", error); + throw new Error("Invalid query parameters"); + } + } + + const queryString = Object.entries(params) + .map( + ([key, value]) => + `${encodeURIComponent(key)}=${encodeURIComponent(String(value))}` + ) + .join("&"); + return `${fullPath}?${queryString}`; + }; + + const handleGet = () => fullPath; + + const handleParse = (searchParams: URLSearchParams): any => { + const schema = this.querySchemas.get(fullPath); + if (!schema) return {}; + + // Convert URLSearchParams to object + const queryObject: Record = {}; + searchParams.forEach((value, key) => { + queryObject[key] = value; + }); + + // Parse through Zod schema + try { + return schema.parse(queryObject); + } catch (error) { + console.error("Failed to parse search params:", error); + // Return safe default values + const safeParsed = schema.safeParse(queryObject); + if (safeParsed.success) { + return safeParsed.data; + } + return {}; + } + }; + + // Special case for root path "/" + if (segments.length === 0) { + this.tree.get = handleGet; + this.tree.query = handleQuery; + this.tree.parse = handleParse; + } else { + let current = this.tree; + for (const segment of segments) { + // Use camelCase version for the property name + const camelSegment = this.toCamelCase(segment); + if (!current[camelSegment]) { + current[camelSegment] = {}; + } + current = current[camelSegment]; + } + + current.get = handleGet; + current.query = handleQuery; + current.parse = handleParse; + } + + return this as any; + } + + // Add a method to incorporate another router's routes into this one + use( + name: N, + childRouter: V2ClientRouter + ): V2ClientRouter, ChildRoutes>> { + const camelName = this.toCamelCase(name); + + if (!this.tree[camelName]) { + this.tree[camelName] = {}; + } + + // Copy query schemas from child router + childRouter.querySchemas.forEach((schema, path) => { + const newPath = `${this.prefix}/${name}${path.substring( + childRouter.prefix.length + )}`; + this.querySchemas.set(newPath, schema); + }); + + // Create a deep copy of the child router's tree with updated paths + const updatePaths = (obj: any, childPrefix: string): any => { + const result: any = {}; + + for (const key in obj) { + if (key === "get" && typeof obj[key] === "function") { + // Capture the original path from the child router + const originalPath = obj[key](); + // Create a new function that returns the combined path + result[key] = () => { + const newPath = `${this.prefix}/${name}${originalPath.substring( + childPrefix.length + )}`; + return newPath; + }; + } else if (key === "query" && typeof obj[key] === "function") { + // Capture the child router's prefix for path adjustment + result[key] = (params: any) => { + // Get the original result without query params + const originalPathWithoutParams = obj["get"](); + // Create the proper path with our parent prefix + const newBasePath = `${ + this.prefix + }/${name}${originalPathWithoutParams.substring( + childPrefix.length + )}`; + + // Add query params if any + if (!params || Object.keys(params).length === 0) return newBasePath; + + // Validate params against schema + const newPath = `${ + this.prefix + }/${name}${originalPathWithoutParams.substring( + childPrefix.length + )}`; + const schema = this.querySchemas.get(newPath); + + if (schema) { + try { + schema.parse(params); + } catch (error) { + console.error("Query params validation failed:", error); + throw new Error("Invalid query parameters"); + } + } + + const queryString = Object.entries(params) + .map( + ([k, v]) => + `${encodeURIComponent(k)}=${encodeURIComponent(String(v))}` + ) + .join("&"); + return `${newBasePath}?${queryString}`; + }; + } else if (key === "parse" && typeof obj[key] === "function") { + result[key] = (searchParams: URLSearchParams) => { + const originalPath = obj["get"](); + const newPath = `${this.prefix}/${name}${originalPath.substring( + childPrefix.length + )}`; + const schema = this.querySchemas.get(newPath); + + if (!schema) return {}; + + // Convert URLSearchParams to object + const queryObject: Record = {}; + searchParams.forEach((value, key) => { + queryObject[key] = value; + }); + + // Parse through Zod schema + try { + return schema.parse(queryObject); + } catch (error) { + console.error("Failed to parse search params:", error); + // Return safe default values + const safeParsed = schema.safeParse(queryObject); + if (safeParsed.success) { + return safeParsed.data; + } + return {}; + } + }; + } else if (typeof obj[key] === "object" && obj[key] !== null) { + result[key] = updatePaths(obj[key], childPrefix); + } else { + result[key] = obj[key]; + } + } + + return result; + }; + + // Copy the child router's tree into this router + this.tree[camelName] = updatePaths( + (childRouter as any).tree, + childRouter.prefix + ); + + return this as any; + } + + // Allow access to the tree with strong typing + get routes(): Routes { + return this.tree as Routes; + } +} + +export default V2ClientRouter; \ No newline at end of file diff --git a/src/app/percobaan/_router/router.txt b/src/app/percobaan/_router/router.txt new file mode 100644 index 00000000..e394a306 --- /dev/null +++ b/src/app/percobaan/_router/router.txt @@ -0,0 +1,20 @@ +import { z } from "zod"; +import V2ClientRouter from "../_lib/ClientRouter"; + +const dashboard = new V2ClientRouter({ + prefix: "/dashboard", + name: "dashboard", +}) + .add("/", { + query: z.object({ + page: z.string(), + }), + }) + .add("/berita"); + +const router = new V2ClientRouter({ + prefix: "/percobaan", + name: "percobaan", +}).use("dashboard", dashboard); + +export default router; diff --git a/src/app/percobaan/dashboard/berita/page.txt b/src/app/percobaan/dashboard/berita/page.txt new file mode 100644 index 00000000..4bba2a3f --- /dev/null +++ b/src/app/percobaan/dashboard/berita/page.txt @@ -0,0 +1,11 @@ +import React from 'react'; + +function Page() { + return ( +
+ berita +
+ ); +} + +export default Page; diff --git a/src/app/percobaan/dashboard/page.txt b/src/app/percobaan/dashboard/page.txt new file mode 100644 index 00000000..cc00a4fd --- /dev/null +++ b/src/app/percobaan/dashboard/page.txt @@ -0,0 +1,33 @@ +'use client' +import { useSearchParams } from 'next/navigation'; +import router from '../_router/router'; +import { Box } from '@mantine/core'; + + +function Page() { + const { page } = router.routes.dashboard.parse(useSearchParams()) + switch (page) { + case "1": + return + case "2": + return + case "3": + return + default: + return + } +} + +const Page1 = () => { + return Page 1 +} + +const Page2 = () => { + return Page 2 +} + +const Page3 = () => { + return Page 3 +} + +export default Page; diff --git a/src/app/percobaan/page2.txt b/src/app/percobaan/page2.txt new file mode 100644 index 00000000..69f2114b --- /dev/null +++ b/src/app/percobaan/page2.txt @@ -0,0 +1,18 @@ +'use client' +import { Button, Group, Stack } from "@mantine/core" +import { Link } from "next-view-transitions" +import router from "./_router/router" + +const Page = () => { + return + + {[1, 2, 3].map((v) => ())} + + + + +} + +export default Page \ No newline at end of file