Tambahan Admin Di Menu PPID
This commit is contained in:
@@ -47,6 +47,66 @@ model AppMenuChild {
|
||||
appMenuId String?
|
||||
}
|
||||
|
||||
//========================================= MENU PPID ========================================= //
|
||||
// ========================================= DAFTAR INFORMASI PUBLIK ========================================= //
|
||||
model DaftarInformasiPublik {
|
||||
id String @id @default(cuid())
|
||||
nomor Int @default(autoincrement())
|
||||
jenisInformasi String
|
||||
deskripsi String
|
||||
tanggal String
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime @default(now())
|
||||
isActive Boolean @default(true)
|
||||
}
|
||||
|
||||
//========================================= MENU PPID ========================================= //
|
||||
// ========================================= IKM ========================================= //
|
||||
model IndeksKepuasanMasyarakat {
|
||||
id Int @id @default(autoincrement())
|
||||
label String
|
||||
kepuasan String
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime @default(now())
|
||||
isActive Boolean @default(true)
|
||||
}
|
||||
|
||||
model GrafikBerdasarkanJenisKelamin {
|
||||
id String @id @default(cuid())
|
||||
perempuan String
|
||||
laki String
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime @default(now())
|
||||
isActive Boolean @default(true)
|
||||
}
|
||||
|
||||
model GrafikBerdasarkanResponden {
|
||||
id String @id @default(cuid())
|
||||
sangatbaik String
|
||||
baik String
|
||||
kurangbaik String
|
||||
tidakbaik String
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime @default(now())
|
||||
isActive Boolean @default(true)
|
||||
}
|
||||
|
||||
model GrafikBerdasarkanUmur {
|
||||
id String @id @default(cuid())
|
||||
remaja String
|
||||
dewasa String
|
||||
orangtua String
|
||||
lansia String
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime @default(now())
|
||||
isActive Boolean @default(true)
|
||||
}
|
||||
|
||||
// ========================================= MENU DESA ========================================= //
|
||||
// ========================================= BERITA ========================================= //
|
||||
model Berita {
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
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 templateDaftarInformasi = z.object({
|
||||
jenisInformasi: z.string().min(3, "Jenis Informasi minimal 3 karakter"),
|
||||
deskripsi: z.string().min(3, "Deskripsi minimal 3 karakter"),
|
||||
tanggal: z.string().min(3, "Tanggal minimal 3 karakter"),
|
||||
})
|
||||
|
||||
type DaftarInformasi = Prisma.DaftarInformasiPublikGetPayload<{
|
||||
select: {
|
||||
jenisInformasi: true;
|
||||
deskripsi: true;
|
||||
tanggal: true;
|
||||
};
|
||||
}>;
|
||||
|
||||
const daftarInformasi = proxy({
|
||||
create: {
|
||||
form: {} as DaftarInformasi,
|
||||
loading: false,
|
||||
async create() {
|
||||
const cek = templateDaftarInformasi.safeParse(daftarInformasi.create.form);
|
||||
if (!cek.success) {
|
||||
const err = `[${cek.error.issues
|
||||
.map((v) => `${v.path.join(".")}`)
|
||||
.join("\n")}] required`;
|
||||
return toast.error(err);
|
||||
}
|
||||
try {
|
||||
daftarInformasi.create.loading = true;
|
||||
const res = await ApiFetch.api.ppid.daftarinformasipublik["create"].post(daftarInformasi.create.form);
|
||||
if (res.status === 200) {
|
||||
daftarInformasi.findMany.load();
|
||||
return toast.success("success create");
|
||||
}
|
||||
return toast.error("failed create");
|
||||
} catch (error) {
|
||||
console.log((error as Error).message);
|
||||
} finally {
|
||||
daftarInformasi.create.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
findMany: {
|
||||
data: null as
|
||||
| Prisma.DaftarInformasiPublikGetPayload<{ omit: { isActive: true } }>[]
|
||||
| null,
|
||||
async load() {
|
||||
const res = await ApiFetch.api.ppid.daftarinformasipublik["find-many"].get();
|
||||
if (res.status === 200) {
|
||||
daftarInformasi.findMany.data = res.data?.data ?? [];
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const stateDaftarInformasiPublik = proxy({
|
||||
daftarInformasi
|
||||
})
|
||||
|
||||
export default stateDaftarInformasiPublik;
|
||||
@@ -0,0 +1,77 @@
|
||||
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 templateGrafikJenisKelamin = z.object({
|
||||
laki: z.string().min(2, "Data laki-laki harus diisi"),
|
||||
perempuan: z.string().min(2, "Data perempuan harus diisi"),
|
||||
});
|
||||
|
||||
type GrafikJenisKelamin = Prisma.GrafikBerdasarkanJenisKelaminGetPayload<{
|
||||
select: {
|
||||
laki: true;
|
||||
perempuan: true;
|
||||
};
|
||||
}>;
|
||||
|
||||
const defaultForm: GrafikJenisKelamin = {
|
||||
laki: "",
|
||||
perempuan: "",
|
||||
};
|
||||
|
||||
const grafikBerdasarkanJenisKelamin = proxy({
|
||||
create: {
|
||||
form: defaultForm,
|
||||
loading: false,
|
||||
async create() {
|
||||
const cek = templateGrafikJenisKelamin.safeParse(
|
||||
grafikBerdasarkanJenisKelamin.create.form
|
||||
);
|
||||
if (!cek.success) {
|
||||
const err = `[${cek.error.issues
|
||||
.map((v) => `${v.path.join(".")}`)
|
||||
.join("\n")}] required`;
|
||||
return toast.error(err);
|
||||
}
|
||||
try {
|
||||
grafikBerdasarkanJenisKelamin.create.loading = true;
|
||||
const res = await ApiFetch.api.ppid.grafikberdasarkanjeniskelamin[
|
||||
"create"
|
||||
].post(grafikBerdasarkanJenisKelamin.create.form);
|
||||
if (res.status === 200) {
|
||||
grafikBerdasarkanJenisKelamin.create.form = defaultForm;
|
||||
grafikBerdasarkanJenisKelamin.findMany.load();
|
||||
return toast.success("success create");
|
||||
}
|
||||
return toast.error("failed create");
|
||||
} catch (error) {
|
||||
console.log((error as Error).message);
|
||||
} finally {
|
||||
grafikBerdasarkanJenisKelamin.create.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
findMany: {
|
||||
data: null as
|
||||
| Prisma.GrafikBerdasarkanJenisKelaminGetPayload<{
|
||||
omit: { isActive: true };
|
||||
}>[]
|
||||
| null,
|
||||
loading: false,
|
||||
async load() {
|
||||
const res = await ApiFetch.api.ppid.grafikberdasarkanjeniskelamin[
|
||||
"find-many"
|
||||
].get();
|
||||
if (res.status === 200) {
|
||||
grafikBerdasarkanJenisKelamin.findMany.data = res.data?.data ?? [];
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const stateGrafikBerdasarkanJenisKelamin = proxy({
|
||||
grafikBerdasarkanJenisKelamin,
|
||||
});
|
||||
export default stateGrafikBerdasarkanJenisKelamin;
|
||||
@@ -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 templateGrafikResponden = z.object({
|
||||
sangatbaik: z.string().min(1, "Data sangat baik harus diisi"),
|
||||
baik: z.string().min(1, "Data baik harus diisi"),
|
||||
kurangbaik: z.string().min(1, "Data kurang baik harus diisi"),
|
||||
tidakbaik: z.string().min(1, "Data tidak baik harus diisi"),
|
||||
});
|
||||
|
||||
type GrafikResponden = Prisma.GrafikBerdasarkanRespondenGetPayload<{
|
||||
select: {
|
||||
sangatbaik: true;
|
||||
baik: true;
|
||||
kurangbaik: true;
|
||||
tidakbaik: true;
|
||||
};
|
||||
}>;
|
||||
|
||||
const defaultForm: GrafikResponden = {
|
||||
sangatbaik: "",
|
||||
baik: "",
|
||||
kurangbaik: "",
|
||||
tidakbaik: "",
|
||||
};
|
||||
|
||||
const grafikBerdasarkanResponden = proxy({
|
||||
create: {
|
||||
form: defaultForm,
|
||||
loading: false,
|
||||
async create() {
|
||||
const cek = templateGrafikResponden.safeParse(
|
||||
grafikBerdasarkanResponden.create.form
|
||||
);
|
||||
if (!cek.success) {
|
||||
const err = `[${cek.error.issues
|
||||
.map((v) => `${v.path.join(".")}`)
|
||||
.join("\n")}] required`;
|
||||
return toast.error(err);
|
||||
}
|
||||
try {
|
||||
grafikBerdasarkanResponden.create.loading = true;
|
||||
const res = await ApiFetch.api.ppid.grafikberdasarkanresponden[
|
||||
"create"
|
||||
].post(grafikBerdasarkanResponden.create.form);
|
||||
if (res.status === 200) {
|
||||
grafikBerdasarkanResponden.create.form = defaultForm;
|
||||
grafikBerdasarkanResponden.findMany.load();
|
||||
return toast.success("success create");
|
||||
}
|
||||
return toast.error("failed create");
|
||||
} catch (error) {
|
||||
console.log((error as Error).message);
|
||||
} finally {
|
||||
grafikBerdasarkanResponden.create.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
findMany: {
|
||||
data: null as
|
||||
| Prisma.GrafikBerdasarkanRespondenGetPayload<{
|
||||
omit: { isActive: true };
|
||||
}>[]
|
||||
| null,
|
||||
loading: false,
|
||||
async load() {
|
||||
const res = await ApiFetch.api.ppid.grafikberdasarkanresponden[
|
||||
"find-many"
|
||||
].get();
|
||||
if (res.status === 200) {
|
||||
grafikBerdasarkanResponden.findMany.data = res.data?.data ?? [];
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const stateGrafikResponden = proxy({
|
||||
grafikBerdasarkanResponden,
|
||||
});
|
||||
|
||||
export default stateGrafikResponden;
|
||||
@@ -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 templateGrafikUmur = z.object({
|
||||
remaja: z.string().min(2, "Data remaja harus diisi"),
|
||||
dewasa: z.string().min(2, "Data dewasa harus diisi"),
|
||||
orangtua: z.string().min(2, "Data orangtua harus diisi"),
|
||||
lansia: z.string().min(2, "Data lansia harus diisi"),
|
||||
});
|
||||
|
||||
type GrafikUmur = Prisma.GrafikBerdasarkanUmurGetPayload<{
|
||||
select: {
|
||||
remaja: true;
|
||||
dewasa: true;
|
||||
orangtua: true;
|
||||
lansia: true;
|
||||
};
|
||||
}>;
|
||||
|
||||
const defaultForm: GrafikUmur = {
|
||||
remaja: "",
|
||||
dewasa: "",
|
||||
orangtua: "",
|
||||
lansia: "",
|
||||
};
|
||||
|
||||
const grafikBerdasarkanUmur = proxy({
|
||||
create: {
|
||||
form: defaultForm,
|
||||
loading: false,
|
||||
async create() {
|
||||
const cek = templateGrafikUmur.safeParse(
|
||||
grafikBerdasarkanUmur.create.form
|
||||
);
|
||||
if (!cek.success) {
|
||||
const err = `[${cek.error.issues
|
||||
.map((v) => `${v.path.join(".")}`)
|
||||
.join("\n")}] required`;
|
||||
return toast.error(err);
|
||||
}
|
||||
try {
|
||||
grafikBerdasarkanUmur.create.loading = true;
|
||||
const res = await ApiFetch.api.ppid.grafikberdasarkanumur[
|
||||
"create"
|
||||
].post(grafikBerdasarkanUmur.create.form);
|
||||
if (res.status === 200) {
|
||||
grafikBerdasarkanUmur.create.form = defaultForm;
|
||||
grafikBerdasarkanUmur.findMany.load();
|
||||
return toast.success("success create");
|
||||
}
|
||||
return toast.error("failed create");
|
||||
} catch (error) {
|
||||
console.log((error as Error).message);
|
||||
} finally {
|
||||
grafikBerdasarkanUmur.create.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
findMany: {
|
||||
data: null as
|
||||
| Prisma.GrafikBerdasarkanUmurGetPayload<{
|
||||
omit: { isActive: true };
|
||||
}>[]
|
||||
| null,
|
||||
loading: false,
|
||||
async load() {
|
||||
const res = await ApiFetch.api.ppid.grafikberdasarkanumur[
|
||||
"find-many"
|
||||
].get();
|
||||
if (res.status === 200) {
|
||||
grafikBerdasarkanUmur.findMany.data = res.data?.data ?? [];
|
||||
}
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const stateGrafikBerdasarkanUmur = proxy({
|
||||
grafikBerdasarkanUmur,
|
||||
})
|
||||
|
||||
export default stateGrafikBerdasarkanUmur;
|
||||
@@ -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 templateGrafikHasilKepuasanMasyarakat = z.object({
|
||||
label: z.string().min(2, "Label harus diisi"),
|
||||
kepuasan: z.string().min(2, "Kepuasan harus diisi"),
|
||||
});
|
||||
|
||||
type GrafikHasilKepuasanMasyarakat = Prisma.IndeksKepuasanMasyarakatGetPayload<{
|
||||
select: {
|
||||
label: true;
|
||||
kepuasan: true;
|
||||
};
|
||||
}>;
|
||||
|
||||
const defaultForm: GrafikHasilKepuasanMasyarakat = {
|
||||
label: "",
|
||||
kepuasan: "",
|
||||
};
|
||||
|
||||
const grafikHasilKepuasanMasyarakat = proxy({
|
||||
create: {
|
||||
form: defaultForm,
|
||||
loading: false,
|
||||
async create() {
|
||||
const cek = templateGrafikHasilKepuasanMasyarakat.safeParse(
|
||||
grafikHasilKepuasanMasyarakat.create.form
|
||||
);
|
||||
if (!cek.success) {
|
||||
const err = `[${cek.error.issues
|
||||
.map((v) => `${v.path.join(".")}`)
|
||||
.join("\n")}] required`;
|
||||
return toast.error(err);
|
||||
}
|
||||
try {
|
||||
grafikHasilKepuasanMasyarakat.create.loading = true;
|
||||
const res = await ApiFetch.api.ppid.grafikhasilkepuasamanmasyarakat["create"].post(
|
||||
grafikHasilKepuasanMasyarakat.create.form
|
||||
);
|
||||
if (res.status === 200) {
|
||||
grafikHasilKepuasanMasyarakat.create.form = {
|
||||
label: "",
|
||||
kepuasan: ""
|
||||
};
|
||||
grafikHasilKepuasanMasyarakat.findMany.load();
|
||||
return toast.success("success create");
|
||||
}
|
||||
return toast.error("failed create");
|
||||
} catch (error) {
|
||||
console.log((error as Error).message);
|
||||
} finally {
|
||||
grafikHasilKepuasanMasyarakat.create.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
findMany: {
|
||||
data: null as
|
||||
| Prisma.IndeksKepuasanMasyarakatGetPayload<{ omit: { isActive: true } }>[]
|
||||
| null,
|
||||
async load() {
|
||||
const res = await ApiFetch.api.ppid.grafikhasilkepuasamanmasyarakat["find-many"].get();
|
||||
if (res.status === 200) {
|
||||
grafikHasilKepuasanMasyarakat.findMany.data = res.data?.data ?? [];
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const stateGrafikHasilKepuasanMasyarakat = proxy({
|
||||
grafikHasilKepuasanMasyarakat,
|
||||
});
|
||||
|
||||
export default stateGrafikHasilKepuasanMasyarakat;
|
||||
94
src/app/admin/(dashboard)/ppid/_com/ppid_Editor.tsx
Normal file
94
src/app/admin/(dashboard)/ppid/_com/ppid_Editor.tsx
Normal file
@@ -0,0 +1,94 @@
|
||||
'use client'
|
||||
import { Button, Stack } from '@mantine/core';
|
||||
import { Link, RichTextEditor } from '@mantine/tiptap';
|
||||
import Highlight from '@tiptap/extension-highlight';
|
||||
import SubScript from '@tiptap/extension-subscript';
|
||||
import Superscript from '@tiptap/extension-superscript';
|
||||
import TextAlign from '@tiptap/extension-text-align';
|
||||
import Underline from '@tiptap/extension-underline';
|
||||
import { useEditor } from '@tiptap/react';
|
||||
import StarterKit from '@tiptap/starter-kit';
|
||||
|
||||
const content =
|
||||
'<h2 style="text-align: center;">Welcome to Mantine rich text editor</h2><p><code>RichTextEditor</code> component focuses on usability and is designed to be as simple as possible to bring a familiar editing experience to regular users. <code>RichTextEditor</code> is based on <a href="https://tiptap.dev/" rel="noopener noreferrer" target="_blank">Tiptap.dev</a> and supports all of its features:</p><ul><li>General text formatting: <strong>bold</strong>, <em>italic</em>, <u>underline</u>, <s>strike-through</s> </li><li>Headings (h1-h6)</li><li>Sub and super scripts (<sup><sup /></sup> and <sub><sub /></sub> tags)</li><li>Ordered and bullet lists</li><li>Text align </li><li>And all <a href="https://tiptap.dev/extensions" target="_blank" rel="noopener noreferrer">other extensions</a></li></ul>';
|
||||
|
||||
export function PPIDEditor({ onSubmit, onChange, showSubmit = true }: {
|
||||
onSubmit?: (val: string) => void,
|
||||
onChange: (val: string) => void,
|
||||
showSubmit?: boolean }) {
|
||||
const editor = useEditor({
|
||||
extensions: [
|
||||
StarterKit,
|
||||
Underline,
|
||||
Link,
|
||||
Superscript,
|
||||
SubScript,
|
||||
Highlight,
|
||||
TextAlign.configure({ types: ['heading', 'paragraph'] }),
|
||||
],
|
||||
immediatelyRender: false,
|
||||
content,
|
||||
onUpdate : ({editor}) => {
|
||||
onChange(editor.getHTML())
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<Stack>
|
||||
<RichTextEditor editor={editor}>
|
||||
<RichTextEditor.Toolbar sticky stickyOffset={60}>
|
||||
<RichTextEditor.ControlsGroup>
|
||||
<RichTextEditor.Bold />
|
||||
<RichTextEditor.Italic />
|
||||
<RichTextEditor.Underline />
|
||||
<RichTextEditor.Strikethrough />
|
||||
<RichTextEditor.ClearFormatting />
|
||||
<RichTextEditor.Highlight />
|
||||
<RichTextEditor.Code />
|
||||
</RichTextEditor.ControlsGroup>
|
||||
|
||||
<RichTextEditor.ControlsGroup>
|
||||
<RichTextEditor.H1 />
|
||||
<RichTextEditor.H2 />
|
||||
<RichTextEditor.H3 />
|
||||
<RichTextEditor.H4 />
|
||||
</RichTextEditor.ControlsGroup>
|
||||
|
||||
<RichTextEditor.ControlsGroup>
|
||||
<RichTextEditor.Blockquote />
|
||||
<RichTextEditor.Hr />
|
||||
<RichTextEditor.BulletList />
|
||||
<RichTextEditor.OrderedList />
|
||||
<RichTextEditor.Subscript />
|
||||
<RichTextEditor.Superscript />
|
||||
</RichTextEditor.ControlsGroup>
|
||||
|
||||
<RichTextEditor.ControlsGroup>
|
||||
<RichTextEditor.Link />
|
||||
<RichTextEditor.Unlink />
|
||||
</RichTextEditor.ControlsGroup>
|
||||
|
||||
<RichTextEditor.ControlsGroup>
|
||||
<RichTextEditor.AlignLeft />
|
||||
<RichTextEditor.AlignCenter />
|
||||
<RichTextEditor.AlignJustify />
|
||||
<RichTextEditor.AlignRight />
|
||||
</RichTextEditor.ControlsGroup>
|
||||
|
||||
<RichTextEditor.ControlsGroup>
|
||||
<RichTextEditor.Undo />
|
||||
<RichTextEditor.Redo />
|
||||
</RichTextEditor.ControlsGroup>
|
||||
</RichTextEditor.Toolbar>
|
||||
|
||||
<RichTextEditor.Content />
|
||||
</RichTextEditor>
|
||||
{showSubmit && (
|
||||
<Button onClick={() => {
|
||||
if (!editor) return
|
||||
onSubmit?.(editor?.getHTML())
|
||||
}}>Submit</Button>
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
@@ -1,11 +1,109 @@
|
||||
'use client'
|
||||
import { Box, Button, Group, SimpleGrid, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, TextInput, Title } from '@mantine/core';
|
||||
import React from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import stateDaftarInformasiPublik from '../../_state/ppid/daftar_informasi_publik/daftarInformasiPublik';
|
||||
import { PPIDEditor } from '../_com/ppid_Editor';
|
||||
import colors from '@/con/colors';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
|
||||
function Page() {
|
||||
const daftarInformasi = useProxy(stateDaftarInformasiPublik.daftarInformasi)
|
||||
const submit = () => {
|
||||
if (daftarInformasi.create.form.jenisInformasi &&
|
||||
daftarInformasi.create.form.deskripsi &&
|
||||
daftarInformasi.create.form.tanggal) {
|
||||
daftarInformasi.create.create()
|
||||
}
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
daftar-informasi-publik-desa-darmasaba
|
||||
</div>
|
||||
<Box>
|
||||
<SimpleGrid cols={{ base: 1, md: 2 }}>
|
||||
<Box>
|
||||
<Stack gap={"xs"}>
|
||||
<Title fw={"bold"} order={3}>Daftar Informasi Publik Desa Darmasaba</Title>
|
||||
<TextInput
|
||||
label="Jenis Informasi"
|
||||
placeholder="masukkan jenis informasi"
|
||||
onChange={(val) => {
|
||||
daftarInformasi.create.form.jenisInformasi = val.target.value
|
||||
}}
|
||||
/>
|
||||
<PPIDEditor
|
||||
showSubmit={false}
|
||||
onChange={(val) => {
|
||||
daftarInformasi.create.form.deskripsi = val
|
||||
}}
|
||||
/>
|
||||
<TextInput
|
||||
label="Tanggal Publikasi"
|
||||
placeholder="masukkan tanggal publikasi"
|
||||
onChange={(val) => {
|
||||
daftarInformasi.create.form.tanggal = val.target.value
|
||||
}}
|
||||
/>
|
||||
<Group>
|
||||
<Button
|
||||
bg={colors['blue-button']}
|
||||
onClick={submit}
|
||||
>Submit</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Box>
|
||||
<Box>
|
||||
<ListDaftarInformasi />
|
||||
</Box>
|
||||
</SimpleGrid>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
function ListDaftarInformasi() {
|
||||
const listData = useProxy(stateDaftarInformasiPublik.daftarInformasi)
|
||||
useShallowEffect(() => {
|
||||
listData.findMany.load()
|
||||
}, [])
|
||||
if (!listData.findMany.data) return <Stack>
|
||||
{Array.from({length: 10}).map((v, k) => <Skeleton key={k} h={40} />)}
|
||||
</Stack>
|
||||
return <Stack gap={"xs"}>
|
||||
<Title fw={"bold"} order={3}>List Daftar Informasi Publik Desa Darmasaba</Title>
|
||||
<Table
|
||||
suppressHydrationWarning
|
||||
striped
|
||||
highlightOnHover
|
||||
withTableBorder
|
||||
withColumnBorders
|
||||
bg={colors['white-1']}
|
||||
>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh>
|
||||
No
|
||||
</TableTh>
|
||||
<TableTh>
|
||||
Jenis Informasi
|
||||
</TableTh>
|
||||
<TableTh>
|
||||
Deskripsi
|
||||
</TableTh>
|
||||
<TableTh>
|
||||
Tanggal Publikasi
|
||||
</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{listData.findMany.data?.map((item) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd>{item.nomor}</TableTd>
|
||||
<TableTd>{item.jenisInformasi}</TableTd>
|
||||
<TableTd dangerouslySetInnerHTML={{ __html: item.deskripsi }}></TableTd>
|
||||
<TableTd>{item.tanggal}</TableTd>
|
||||
</TableTr>
|
||||
))}
|
||||
</TableTbody>
|
||||
</Table>
|
||||
</Stack>
|
||||
}
|
||||
|
||||
export default Page;
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
'use client'
|
||||
import stateGrafikBerdasarkanJenisKelamin from '@/app/admin/(dashboard)/_state/ppid/indeks_kepuasan_masyarakat/grafikBerdasarkanJenisKelamin';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Center, Flex, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Cell, Pie, PieChart } from 'recharts';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
function GrafikBerdasarkanJenisKelamin() {
|
||||
const grafikBerdasarkanJenisKelamin = useProxy(stateGrafikBerdasarkanJenisKelamin.grafikBerdasarkanJenisKelamin)
|
||||
const [donutData, setDonutData] = useState<any[]>([]);
|
||||
const [mounted, setMounted] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
}, [])
|
||||
|
||||
const updateChartData = (data: any) => {
|
||||
if (data && data.length > 0) {
|
||||
const totalLaki = data.reduce((acc: number, cur: any) => acc + Number(cur.laki || 0), 0);
|
||||
const totalPerempuan = data.reduce((acc: number, cur: any) => acc + Number(cur.perempuan || 0), 0);
|
||||
|
||||
setDonutData([
|
||||
{ name: 'Laki-laki', value: totalLaki, color: colors['blue-button'], key: 'laki-laki' },
|
||||
{ name: 'Perempuan', value: totalPerempuan, color: '#FF6384', key: 'perempuan' }
|
||||
]);
|
||||
}
|
||||
};
|
||||
|
||||
useShallowEffect(() => {
|
||||
fetchData();
|
||||
}, []);
|
||||
|
||||
const fetchData = async () => {
|
||||
await grafikBerdasarkanJenisKelamin.findMany.load();
|
||||
if (grafikBerdasarkanJenisKelamin.findMany.data) {
|
||||
updateChartData(grafikBerdasarkanJenisKelamin.findMany.data);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
try {
|
||||
// Simpan data baru
|
||||
await grafikBerdasarkanJenisKelamin.create.create();
|
||||
|
||||
// Muat ulang data
|
||||
await grafikBerdasarkanJenisKelamin.findMany.load();
|
||||
|
||||
// Update chart dengan data baru
|
||||
if (grafikBerdasarkanJenisKelamin.findMany.data) {
|
||||
updateChartData(grafikBerdasarkanJenisKelamin.findMany.data);
|
||||
}
|
||||
|
||||
// Reset form setelah submit
|
||||
grafikBerdasarkanJenisKelamin.create.form.laki = '';
|
||||
grafikBerdasarkanJenisKelamin.create.form.perempuan = '';
|
||||
} catch (error) {
|
||||
console.error("Error submitting data:", error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Box py={15}>
|
||||
<Title order={3}>Grafik Hasil Kepuasan Masyarakat Terhadap Pelayanan Publik</Title>
|
||||
<TextInput
|
||||
w={{ base: '100%', md: '50%' }}
|
||||
label="Laki-laki"
|
||||
placeholder="masukkan jumlah laki-laki"
|
||||
value={grafikBerdasarkanJenisKelamin.create.form.laki}
|
||||
onChange={(val) => {
|
||||
grafikBerdasarkanJenisKelamin.create.form.laki = val.currentTarget.value;
|
||||
}}
|
||||
/>
|
||||
<TextInput
|
||||
w={{ base: '100%', md: '50%' }}
|
||||
label="Perempuan"
|
||||
type="number"
|
||||
placeholder="masukkan jumlah perempuan"
|
||||
value={grafikBerdasarkanJenisKelamin.create.form.perempuan}
|
||||
onChange={(val) => {
|
||||
grafikBerdasarkanJenisKelamin.create.form.perempuan = val.currentTarget.value;
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
mt={10}
|
||||
bg={colors['blue-button']}
|
||||
onClick={handleSubmit}
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
{/* Chart */}
|
||||
<Box>
|
||||
<Stack>
|
||||
<Title pb={10} order={3}>Grafik Berdasarkan Jenis Kelamin Responden</Title>
|
||||
{mounted && donutData.length > 0 && (
|
||||
<Box style={{ width: '100%', height: 'auto', minHeight: 900 }}>
|
||||
<Center>
|
||||
<PieChart
|
||||
width={1000} height={400}
|
||||
data={donutData}
|
||||
>
|
||||
|
||||
<Pie
|
||||
dataKey="value"
|
||||
nameKey="name"
|
||||
data={donutData}
|
||||
innerRadius={120}
|
||||
outerRadius={160}
|
||||
label
|
||||
>
|
||||
{donutData.map((entry, index) => (
|
||||
<Cell key={`cell-${index}`} fill={entry.color} />
|
||||
))}
|
||||
</Pie>
|
||||
</PieChart>
|
||||
</Center>
|
||||
<Flex gap={"md"} align={"center"}>
|
||||
<Box bg={'#FF6384'} w={20} h={20} />
|
||||
<Text>Perempuan: {donutData.find((entry) => entry.name === 'Perempuan')?.value}</Text>
|
||||
</Flex>
|
||||
<Flex gap={"md"} align={"center"}>
|
||||
<Box bg={colors['blue-button']} w={20} h={20} />
|
||||
<Text>Laki-laki: {donutData.find((entry) => entry.name === 'Laki-laki')?.value}</Text>
|
||||
</Flex>
|
||||
</Box>
|
||||
)}
|
||||
</Stack>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default GrafikBerdasarkanJenisKelamin;
|
||||
@@ -0,0 +1,166 @@
|
||||
'use client'
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import stateGrafikResponden from '@/app/admin/(dashboard)/_state/ppid/indeks_kepuasan_masyarakat/grafikBerdasarkanResponden';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Center, Flex, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { PieChart, Pie, Cell } from 'recharts';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
function GrafikBerdasarkanResponden() {
|
||||
const grafikBerdasarkanResponden = useProxy(stateGrafikResponden.grafikBerdasarkanResponden)
|
||||
const [donutData, setDonutData] = useState<any[]>([]);
|
||||
const [mounted, setMounted] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
}, [])
|
||||
|
||||
const updateChartData = (data: any) => {
|
||||
if (data && data.length > 0) {
|
||||
const totalSangatBaik = data.reduce((acc: number, cur: any) => acc + Number(cur.sangatbaik || 0), 0);
|
||||
const totalBaik = data.reduce((acc: number, cur: any) => acc + Number(cur.baik || 0), 0);
|
||||
const totalKurangBaik = data.reduce((acc: number, cur: any) => acc + Number(cur.kurangbaik || 0), 0);
|
||||
const totalTidakBaik = data.reduce((acc: number, cur: any) => acc + Number(cur.tidakbaik || 0), 0);
|
||||
setDonutData([
|
||||
{ name: 'sangatbaik', value: totalSangatBaik, color: colors['blue-button'], key: 'sangatbaik' },
|
||||
{ name: 'baik', value: totalBaik, color: '#10A85AFF', key: 'baik' },
|
||||
{ name: 'kurangbaik', value: totalKurangBaik, color: '#B3AA12FF', key: 'kurangbaik' },
|
||||
{ name: 'tidakbaik', value: totalTidakBaik, color: '#B21313FF', key: 'tidakbaik' }
|
||||
]);
|
||||
}
|
||||
};
|
||||
|
||||
useShallowEffect(() => {
|
||||
fetchData();
|
||||
}, []);
|
||||
|
||||
const fetchData = async () => {
|
||||
await grafikBerdasarkanResponden.findMany.load();
|
||||
if (grafikBerdasarkanResponden.findMany.data) {
|
||||
updateChartData(grafikBerdasarkanResponden.findMany.data);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
try {
|
||||
// Simpan data baru
|
||||
await grafikBerdasarkanResponden.create.create();
|
||||
|
||||
// Muat ulang data
|
||||
await grafikBerdasarkanResponden.findMany.load();
|
||||
|
||||
// Update chart dengan data baru
|
||||
if (grafikBerdasarkanResponden.findMany.data) {
|
||||
updateChartData(grafikBerdasarkanResponden.findMany.data);
|
||||
}
|
||||
|
||||
// Reset form setelah submit
|
||||
grafikBerdasarkanResponden.create.form.sangatbaik = '';
|
||||
grafikBerdasarkanResponden.create.form.baik = '';
|
||||
grafikBerdasarkanResponden.create.form.kurangbaik = '';
|
||||
grafikBerdasarkanResponden.create.form.tidakbaik = '';
|
||||
} catch (error) {
|
||||
console.error("Error submitting data:", error);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<Box>
|
||||
<Box py={15}>
|
||||
<Title order={3}>Grafik Berdasarkan Responden</Title>
|
||||
<TextInput
|
||||
w={{ base: '100%', md: '50%' }}
|
||||
label="Sangat Baik"
|
||||
value={grafikBerdasarkanResponden.create.form.sangatbaik}
|
||||
placeholder="masukkan jumlah respon sangat baik"
|
||||
onChange={(val) => {
|
||||
grafikBerdasarkanResponden.create.form.sangatbaik = val.currentTarget.value;
|
||||
}}
|
||||
/>
|
||||
<TextInput
|
||||
w={{ base: '100%', md: '50%' }}
|
||||
label="Baik"
|
||||
value={grafikBerdasarkanResponden.create.form.baik}
|
||||
placeholder="masukkan jumlah respon baik"
|
||||
onChange={(val) => {
|
||||
grafikBerdasarkanResponden.create.form.baik = val.currentTarget.value;
|
||||
}}
|
||||
/>
|
||||
<TextInput
|
||||
w={{ base: '100%', md: '50%' }}
|
||||
label="Kurang Baik"
|
||||
value={grafikBerdasarkanResponden.create.form.kurangbaik}
|
||||
placeholder="masukkan jumlah respon kurang baik"
|
||||
onChange={(val) => {
|
||||
grafikBerdasarkanResponden.create.form.kurangbaik = val.currentTarget.value;
|
||||
}}
|
||||
/>
|
||||
<TextInput
|
||||
w={{ base: '100%', md: '50%' }}
|
||||
label="Tidak Baik"
|
||||
value={grafikBerdasarkanResponden.create.form.tidakbaik}
|
||||
placeholder="masukkan jumlah respon tidak baik"
|
||||
onChange={(val) => {
|
||||
grafikBerdasarkanResponden.create.form.tidakbaik = val.currentTarget.value;
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
mt={10}
|
||||
bg={colors['blue-button']}
|
||||
onClick={handleSubmit}
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
{/* Chart */}
|
||||
<Box>
|
||||
<Stack>
|
||||
<Title pb={10} order={3}>Grafik Berdasarkan Responden</Title>
|
||||
{mounted && donutData.length > 0 && (
|
||||
<Box style={{ width: '100%', height: 'auto', minHeight: 900 }}>
|
||||
<Center>
|
||||
<PieChart
|
||||
width={1000} height={400}
|
||||
data={donutData}
|
||||
>
|
||||
<Pie
|
||||
dataKey="value"
|
||||
nameKey="name"
|
||||
data={donutData}
|
||||
innerRadius={120}
|
||||
outerRadius={160}
|
||||
label
|
||||
>
|
||||
{donutData.map((entry, index) => (
|
||||
<Cell key={`cell-${index}`} fill={entry.color} />
|
||||
))}
|
||||
</Pie>
|
||||
</PieChart>
|
||||
</Center>
|
||||
<Flex gap={"md"} align={"center"}>
|
||||
<Box bg={colors['blue-button']} w={20} h={20} />
|
||||
<Text>Sangat Baik: {donutData.find((entry) => entry.name === 'sangatbaik')?.value}</Text>
|
||||
</Flex>
|
||||
<Flex gap={"md"} align={"center"}>
|
||||
<Box bg={'#10A85AFF'} w={20} h={20} />
|
||||
<Text>Baik: {donutData.find((entry) => entry.name === 'baik')?.value}</Text>
|
||||
</Flex>
|
||||
<Flex gap={"md"} align={"center"}>
|
||||
<Box bg={'#B3AA12FF'} w={20} h={20} />
|
||||
<Text>Kurang Baik: {donutData.find((entry) => entry.name === 'kurangbaik')?.value}</Text>
|
||||
</Flex>
|
||||
<Flex gap={"md"} align={"center"}>
|
||||
<Box bg={'#B21313FF'} w={20} h={20} />
|
||||
<Text>Tidak Baik: {donutData.find((entry) => entry.name === 'tidakbaik')?.value}</Text>
|
||||
</Flex>
|
||||
</Box>
|
||||
)}
|
||||
</Stack>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default GrafikBerdasarkanResponden;
|
||||
@@ -0,0 +1,156 @@
|
||||
'use client'
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Center, Flex, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import stateGrafikBerdasarkanUmur from '@/app/admin/(dashboard)/_state/ppid/indeks_kepuasan_masyarakat/grafikBerdasarkanUmur';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { PieChart, Pie, Cell } from 'recharts';
|
||||
|
||||
function GrafikBerdasarakanUmur() {
|
||||
const grafikBerdasarkanUmur = useProxy(stateGrafikBerdasarkanUmur.grafikBerdasarkanUmur)
|
||||
const [donutData, setDonutData] = useState<any[]>([]);
|
||||
const [mounted, setMounted] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
}, []);
|
||||
|
||||
const updateChartData = (data: any) => {
|
||||
if (data && data.length > 0) {
|
||||
const totalRemaja = data.reduce((acc: number, cur: any) => acc + Number(cur.remaja || 0), 0);
|
||||
const totalDewasa = data.reduce((acc: number, cur: any) => acc + Number(cur.dewasa || 0), 0);
|
||||
const totalOrangtua = data.reduce((acc: number, cur: any) => acc + Number(cur.orangtua || 0), 0);
|
||||
const totalLansia = data.reduce((acc: number, cur: any) => acc + Number(cur.lansia || 0), 0);
|
||||
|
||||
setDonutData([
|
||||
{ name: 'Remaja', value: totalRemaja, color: colors['blue-button'], key: 'remaja' },
|
||||
{ name: 'Dewasa', value: totalDewasa, color: '#D32711FF', key: 'dewasa' },
|
||||
{ name: 'Orangtua', value: totalOrangtua, color: '#B46B04FF', key: 'orangtua' },
|
||||
{ name: 'Lansia', value: totalLansia, color: '#038617FF', key: 'lansia' }
|
||||
]);
|
||||
}
|
||||
};
|
||||
|
||||
useShallowEffect(() => {
|
||||
fetchData();
|
||||
}, []);
|
||||
|
||||
const fetchData = async () => {
|
||||
await grafikBerdasarkanUmur.findMany.load();
|
||||
if (grafikBerdasarkanUmur.findMany.data) {
|
||||
updateChartData(grafikBerdasarkanUmur.findMany.data);
|
||||
}
|
||||
}
|
||||
|
||||
const handleSubmit = async () => {
|
||||
try {
|
||||
await grafikBerdasarkanUmur.create.create();
|
||||
await grafikBerdasarkanUmur.findMany.load();
|
||||
if (grafikBerdasarkanUmur.findMany.data) {
|
||||
updateChartData(grafikBerdasarkanUmur.findMany.data);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error submitting data:", error);
|
||||
}
|
||||
}
|
||||
return (
|
||||
<Box>
|
||||
<Box py={15}>
|
||||
<Title order={3}>Grafik Berdasarkan Umur Responden</Title>
|
||||
<TextInput
|
||||
w={{ base: '100%', md: '50%' }}
|
||||
label="Remaja"
|
||||
placeholder="masukkan jumlah responden remaja"
|
||||
value={grafikBerdasarkanUmur.create.form.remaja}
|
||||
onChange={(val) => {
|
||||
grafikBerdasarkanUmur.create.form.remaja = val.currentTarget.value;
|
||||
}}
|
||||
/>
|
||||
<TextInput
|
||||
w={{ base: '100%', md: '50%' }}
|
||||
label="Dewasa"
|
||||
placeholder="masukkan jumlah responden dewasa"
|
||||
value={grafikBerdasarkanUmur.create.form.dewasa}
|
||||
onChange={(val) => {
|
||||
grafikBerdasarkanUmur.create.form.dewasa = val.currentTarget.value;
|
||||
}}
|
||||
/>
|
||||
<TextInput
|
||||
w={{ base: '100%', md: '50%' }}
|
||||
label="Orangtua"
|
||||
placeholder="masukkan jumlah responden orangtua"
|
||||
value={grafikBerdasarkanUmur.create.form.orangtua}
|
||||
onChange={(val) => {
|
||||
grafikBerdasarkanUmur.create.form.orangtua = val.currentTarget.value;
|
||||
}}
|
||||
/>
|
||||
<TextInput
|
||||
w={{ base: '100%', md: '50%' }}
|
||||
label="Lansia"
|
||||
placeholder="masukkan jumlah responden lansia"
|
||||
value={grafikBerdasarkanUmur.create.form.lansia}
|
||||
onChange={(val) => {
|
||||
grafikBerdasarkanUmur.create.form.lansia = val.currentTarget.value;
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
mt={10}
|
||||
bg={colors['blue-button']}
|
||||
onClick={handleSubmit}
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
{/* Chart */}
|
||||
<Box>
|
||||
<Stack>
|
||||
<Title pb={10} order={3}>Grafik Berdasarkan Umur Responden</Title>
|
||||
{mounted && donutData.length > 0 && (
|
||||
<Box style={{ width: '100%', height: 'auto', minHeight: 900 }}>
|
||||
<Center>
|
||||
<PieChart
|
||||
width={1000} height={400}
|
||||
data={donutData}
|
||||
>
|
||||
<Pie
|
||||
dataKey="value"
|
||||
nameKey="name"
|
||||
data={donutData}
|
||||
innerRadius={120}
|
||||
outerRadius={160}
|
||||
label
|
||||
>
|
||||
{donutData.map((entry, index) => (
|
||||
<Cell key={`cell-${index}`} fill={entry.color} />
|
||||
))}
|
||||
</Pie>
|
||||
</PieChart>
|
||||
</Center>
|
||||
<Flex gap={"md"} align={"center"}>
|
||||
<Box bg={colors['blue-button']} w={20} h={20} />
|
||||
<Text>17 - 25 tahun: {donutData.find((entry) => entry.name === 'remaja')?.value}</Text>
|
||||
</Flex>
|
||||
<Flex gap={"md"} align={"center"}>
|
||||
<Box bg={'#D32711FF'} w={20} h={20} />
|
||||
<Text>26 - 45 tahun: {donutData.find((entry) => entry.name === 'dewasa')?.value}</Text>
|
||||
</Flex>
|
||||
<Flex gap={"md"} align={"center"}>
|
||||
<Box bg={'#B46B04FF'} w={20} h={20} />
|
||||
<Text>46 - 60 tahun: {donutData.find((entry) => entry.name === 'orangtua')?.value}</Text>
|
||||
</Flex>
|
||||
<Flex gap={"md"} align={"center"}>
|
||||
<Box bg={'#038617FF'} w={20} h={20} />
|
||||
<Text>di atas 60 tahun: {donutData.find((entry) => entry.name === 'lansia')?.value}</Text>
|
||||
</Flex>
|
||||
</Box>
|
||||
)}
|
||||
</Stack>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default GrafikBerdasarakanUmur;
|
||||
@@ -0,0 +1,88 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
'use client'
|
||||
import stateGrafikHasilKepuasanMasyarakat from '@/app/admin/(dashboard)/_state/ppid/indeks_kepuasan_masyarakat/grafikHasilKepuasan';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, TextInput, Title } from '@mantine/core';
|
||||
import { useMediaQuery, useShallowEffect } from '@mantine/hooks';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Bar, BarChart, Legend, Tooltip, XAxis, YAxis } from 'recharts';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
function GrafikHasilKepuasan() {
|
||||
const grafikHasilKepuasan = useProxy(stateGrafikHasilKepuasanMasyarakat.grafikHasilKepuasanMasyarakat)
|
||||
const [chartData, setChartData] = useState<any[]>([]);
|
||||
const [mounted, setMounted] = useState(false);
|
||||
const isTablet = useMediaQuery('(max-width: 1024px)')
|
||||
const isMobile = useMediaQuery('(max-width: 768px)')
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
}, [])
|
||||
|
||||
useShallowEffect(() => {
|
||||
const fetchData = async () => {
|
||||
await grafikHasilKepuasan.findMany.load();
|
||||
if (grafikHasilKepuasan.findMany.data && grafikHasilKepuasan.findMany.data.length > 0) {
|
||||
setChartData(grafikHasilKepuasan.findMany.data);
|
||||
}
|
||||
};
|
||||
fetchData();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Box py={15}>
|
||||
<Title order={3}>Grafik Hasil Kepuasan Masyarakat Terhadap Pelayanan Publik</Title>
|
||||
<TextInput
|
||||
w={{ base: '100%', md: '50%' }}
|
||||
label="Label"
|
||||
placeholder="masukkan label"
|
||||
value={grafikHasilKepuasan.create.form.label}
|
||||
onChange={(val) => {
|
||||
grafikHasilKepuasan.create.form.label = val.currentTarget.value;
|
||||
}}
|
||||
/>
|
||||
<TextInput
|
||||
w={{ base: '100%', md: '50%' }}
|
||||
label="Jumlah Kepuasan"
|
||||
type="number"
|
||||
placeholder="masukkan jumlah kepuasan"
|
||||
value={grafikHasilKepuasan.create.form.kepuasan}
|
||||
onChange={(val) => {
|
||||
grafikHasilKepuasan.create.form.kepuasan = val.currentTarget.value;
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
mt={10}
|
||||
bg={colors['blue-button']}
|
||||
onClick={async () => {
|
||||
await grafikHasilKepuasan.create.create();
|
||||
await grafikHasilKepuasan.findMany.load();
|
||||
if (grafikHasilKepuasan.findMany.data) {
|
||||
setChartData(grafikHasilKepuasan.findMany.data);
|
||||
}
|
||||
}}
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
{/* Chart */}
|
||||
<Box style={{ width: '100%', minWidth: 300, height: 400, minHeight: 300 }}>
|
||||
<Title pb={10} order={3}>Data Kepuasan Masyarakat</Title>
|
||||
{mounted && chartData.length > 0 && (
|
||||
<BarChart width={isMobile ? 450 : isTablet ? 600 : 900} height={380} data={chartData} >
|
||||
<XAxis dataKey="label" />
|
||||
<YAxis />
|
||||
<Tooltip />
|
||||
<Legend />
|
||||
<Bar dataKey="kepuasan" fill={colors['blue-button']} name="Kepuasan" />
|
||||
</BarChart>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default GrafikHasilKepuasan;
|
||||
@@ -1,10 +1,44 @@
|
||||
import { Stack, Tabs, TabsList, TabsPanel, TabsTab } from '@mantine/core';
|
||||
import React from 'react';
|
||||
import colors from '@/con/colors';
|
||||
import GrafikHasilKepuasan from './_ui/grafik_hasil_kepuasan_masyarakat/page';
|
||||
import GrafikBerdasarkanJenisKelamin from './_ui/grafik_berdasarkan_jenis_kelamin_responden/page';
|
||||
import GrafikBerdasarkanResponden from './_ui/grafik_berdasarkan_responden/page';
|
||||
import GrafikBerdasarakanUmur from './_ui/grafik_berdasarkan_umur/page';
|
||||
|
||||
function Page() {
|
||||
return (
|
||||
<div>
|
||||
ikm-desa-darmasaba
|
||||
</div>
|
||||
<Stack>
|
||||
<Tabs color={colors['blue-button']} variant='pills' defaultValue={"Grafik Hasil Kepuasan Masyarakat Terhadap Pelayanan Publik"}>
|
||||
<TabsList>
|
||||
<TabsTab value="Grafik Hasil Kepuasan Masyarakat Terhadap Pelayanan Publik">
|
||||
Grafik Hasil Kepuasan Masyarakat Terhadap Pelayanan Publik
|
||||
</TabsTab>
|
||||
<TabsTab value="Grafik Berdasarkan Jenis Kelamin Responden">
|
||||
Grafik Berdasarkan Jenis Kelamin Responden
|
||||
</TabsTab>
|
||||
<TabsTab value="Grafik Berdasarkan Pilihan Responden">
|
||||
Grafik Berdasarkan Pilihan Responden
|
||||
</TabsTab>
|
||||
<TabsTab value="Grafik Berdasarkan Umur Responden">
|
||||
Grafik Berdasarkan Umur Responden
|
||||
</TabsTab>
|
||||
</TabsList>
|
||||
|
||||
<TabsPanel value="Grafik Hasil Kepuasan Masyarakat Terhadap Pelayanan Publik">
|
||||
<GrafikHasilKepuasan/>
|
||||
</TabsPanel>
|
||||
<TabsPanel value="Grafik Berdasarkan Jenis Kelamin Responden">
|
||||
<GrafikBerdasarkanJenisKelamin/>
|
||||
</TabsPanel>
|
||||
<TabsPanel value="Grafik Berdasarkan Pilihan Responden">
|
||||
<GrafikBerdasarkanResponden/>
|
||||
</TabsPanel>
|
||||
<TabsPanel value="Grafik Berdasarkan Umur Responden">
|
||||
<GrafikBerdasarakanUmur/>
|
||||
</TabsPanel>
|
||||
</Tabs>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { Context } from "elysia";
|
||||
|
||||
type FormCreate = Prisma.DaftarInformasiPublikGetPayload<{
|
||||
select: {
|
||||
jenisInformasi: true;
|
||||
deskripsi: true;
|
||||
tanggal: true;
|
||||
}
|
||||
}>
|
||||
export default async function daftarInformasiPublikCreate(context: Context) {
|
||||
const body = context.body as FormCreate;
|
||||
|
||||
await prisma.daftarInformasiPublik.create({
|
||||
data: {
|
||||
jenisInformasi: body.jenisInformasi,
|
||||
deskripsi: body.deskripsi,
|
||||
tanggal: body.tanggal,
|
||||
},
|
||||
})
|
||||
return {
|
||||
success: true,
|
||||
message: "Success create daftar informasi publik",
|
||||
data: {
|
||||
...body,
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
|
||||
export default async function daftarInformasiPublikFindMany() {
|
||||
const res = await prisma.daftarInformasiPublik.findMany();
|
||||
return {
|
||||
data: res
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import Elysia, { t } from "elysia";
|
||||
import daftarInformasiPublikCreate from "./create";
|
||||
import daftarInformasiPublikFindMany from "./find-many";
|
||||
|
||||
const DaftarInformasiPublik = new Elysia({
|
||||
prefix: "/daftarinformasipublik",
|
||||
tags: ["PPID/Daftar Informasi Publik"]
|
||||
})
|
||||
.get("/find-many", daftarInformasiPublikFindMany)
|
||||
.post("/create", daftarInformasiPublikCreate, {
|
||||
body: t.Object({
|
||||
jenisInformasi: t.String(),
|
||||
deskripsi: t.String(),
|
||||
tanggal: t.String(),
|
||||
}),
|
||||
})
|
||||
|
||||
export default DaftarInformasiPublik
|
||||
@@ -0,0 +1,27 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { Context } from "elysia";
|
||||
|
||||
type FormCreate = Prisma.GrafikBerdasarkanJenisKelaminGetPayload<{
|
||||
select: {
|
||||
perempuan: true;
|
||||
laki: true;
|
||||
}
|
||||
}>
|
||||
export default async function grafikBerdasarkanJenisKelaminCreate(context: Context) {
|
||||
const body = context.body as FormCreate;
|
||||
|
||||
await prisma.grafikBerdasarkanJenisKelamin.create({
|
||||
data: {
|
||||
perempuan: body.perempuan,
|
||||
laki: body.laki,
|
||||
},
|
||||
});
|
||||
return {
|
||||
success: true,
|
||||
message: "Success create grafik berdasarkan jenis kelamin",
|
||||
data: {
|
||||
...body,
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
|
||||
export default async function grafikBerdasarkanJenisKelaminFindMany() {
|
||||
const res = await prisma.grafikBerdasarkanJenisKelamin.findMany();
|
||||
return {
|
||||
data: res
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import Elysia, { t } from "elysia";
|
||||
import grafikBerdasarkanJenisKelaminCreate from "./create";
|
||||
import grafikBerdasarkanJenisKelaminFindMany from "./find-many";
|
||||
|
||||
|
||||
const GrafikBerdasarkanJenisKelamin = new Elysia({
|
||||
prefix: "/grafikberdasarkanjeniskelamin",
|
||||
tags: ["PPID/IKM/grafikberdasarkanjeniskelamin"],
|
||||
})
|
||||
.get("/find-many", grafikBerdasarkanJenisKelaminFindMany)
|
||||
.post("/create", grafikBerdasarkanJenisKelaminCreate, {
|
||||
body: t.Object({
|
||||
perempuan: t.String(),
|
||||
laki: t.String(),
|
||||
}),
|
||||
});
|
||||
export default GrafikBerdasarkanJenisKelamin;
|
||||
@@ -0,0 +1,31 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { Context } from "elysia";
|
||||
|
||||
type FormCreate = Prisma.GrafikBerdasarkanUmurGetPayload<{
|
||||
select: {
|
||||
remaja: true;
|
||||
dewasa: true;
|
||||
orangtua: true;
|
||||
lansia: true;
|
||||
}
|
||||
}>
|
||||
export async function grafikBerdasarkanUmurCreate(context: Context) {
|
||||
const body = context.body as FormCreate;
|
||||
|
||||
await prisma.grafikBerdasarkanUmur.create({
|
||||
data: {
|
||||
remaja: body.remaja,
|
||||
dewasa: body.dewasa,
|
||||
orangtua: body.orangtua,
|
||||
lansia: body.lansia,
|
||||
},
|
||||
});
|
||||
return {
|
||||
success: true,
|
||||
message: "Success create grafik berdasarkan umur",
|
||||
data: {
|
||||
...body,
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
|
||||
export async function grafikBerdasarkanUmurFindMany(){
|
||||
const res = await prisma.grafikBerdasarkanUmur.findMany();
|
||||
return {
|
||||
data: res
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import Elysia, { t } from "elysia";
|
||||
import { grafikBerdasarkanUmurFindMany } from "./find-many";
|
||||
import { grafikBerdasarkanUmurCreate } from "./create";
|
||||
|
||||
const GrafikBerdasarkanUmur = new Elysia({
|
||||
prefix: "/grafikberdasarkanumur",
|
||||
tags: ["PPID/IKM/grafikberdasarkanumur"]
|
||||
})
|
||||
.get("/find-many", grafikBerdasarkanUmurFindMany)
|
||||
.post("/create", grafikBerdasarkanUmurCreate, {
|
||||
body: t.Object({
|
||||
remaja: t.String(),
|
||||
dewasa: t.String(),
|
||||
orangtua: t.String(),
|
||||
lansia: t.String(),
|
||||
}),
|
||||
});
|
||||
|
||||
export default GrafikBerdasarkanUmur;
|
||||
@@ -0,0 +1,27 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { Context } from "elysia";
|
||||
|
||||
type FormCreate = Prisma.IndeksKepuasanMasyarakatGetPayload<{
|
||||
select: {
|
||||
label: true;
|
||||
kepuasan: true;
|
||||
};
|
||||
}>;
|
||||
export default async function grafikHasilKepuasanMasyarakatCreate(context: Context) {
|
||||
const body = context.body as FormCreate;
|
||||
|
||||
await prisma.indeksKepuasanMasyarakat.create({
|
||||
data: {
|
||||
label: body.label,
|
||||
kepuasan: body.kepuasan,
|
||||
},
|
||||
});
|
||||
return {
|
||||
success: true,
|
||||
message: "Success create grafik hasil kepuasan masyarakat",
|
||||
data: {
|
||||
...body,
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
|
||||
export default async function grafikHasilKepuasanMasyarakatFindMany() {
|
||||
const res = await prisma.indeksKepuasanMasyarakat.findMany();
|
||||
return {
|
||||
data: res,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import Elysia, { t } from "elysia";
|
||||
import grafikHasilKepuasanMasyarakatCreate from "./create";
|
||||
import grafikHasilKepuasanMasyarakatFindMany from "./find-many";
|
||||
|
||||
const GrafikHasilKepuasanMasyarakat = new Elysia({
|
||||
prefix: "/grafikhasilkepuasamanmasyarakat",
|
||||
tags: ["PPID/IKM/grafikhasilkepuasanmasyarakat"],
|
||||
})
|
||||
.get("/find-many", grafikHasilKepuasanMasyarakatFindMany)
|
||||
.post("/create", grafikHasilKepuasanMasyarakatCreate, {
|
||||
body: t.Object({
|
||||
label: t.String(),
|
||||
kepuasan: t.String(),
|
||||
}),
|
||||
});
|
||||
|
||||
export default GrafikHasilKepuasanMasyarakat;
|
||||
@@ -0,0 +1,31 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { Context } from "elysia";
|
||||
|
||||
type FormCreate = Prisma.GrafikBerdasarkanRespondenGetPayload<{
|
||||
select: {
|
||||
sangatbaik: true;
|
||||
baik: true;
|
||||
kurangbaik: true;
|
||||
tidakbaik: true
|
||||
};
|
||||
}>;
|
||||
export default async function grafikRespondenCreate(context: Context) {
|
||||
const body = context.body as FormCreate;
|
||||
|
||||
await prisma.grafikBerdasarkanResponden.create({
|
||||
data: {
|
||||
sangatbaik: body.sangatbaik,
|
||||
baik: body.baik,
|
||||
kurangbaik: body.kurangbaik,
|
||||
tidakbaik: body.tidakbaik,
|
||||
},
|
||||
});
|
||||
return {
|
||||
success: true,
|
||||
message: "Success create grafik berdasarkan responden",
|
||||
data: {
|
||||
...body,
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
|
||||
export default async function grafikRespondenFindMany(){
|
||||
const res = await prisma.grafikBerdasarkanResponden.findMany();
|
||||
return{
|
||||
data: res
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import Elysia, { t } from "elysia";
|
||||
import grafikRespondenCreate from "./create";
|
||||
import grafikRespondenFindMany from "./find-many";
|
||||
|
||||
const GrafikBerdasarkanResponden = new Elysia({
|
||||
prefix: "/grafikberdasarkanresponden",
|
||||
tags: ["PPID/IKM/grafikberdasarkanresponden"]
|
||||
})
|
||||
.get("/find-many", grafikRespondenFindMany)
|
||||
.post("/create", grafikRespondenCreate, {
|
||||
body: t.Object({
|
||||
sangatbaik: t.String(),
|
||||
baik: t.String(),
|
||||
kurangbaik: t.String(),
|
||||
tidakbaik: t.String(),
|
||||
}),
|
||||
})
|
||||
|
||||
|
||||
export default GrafikBerdasarkanResponden
|
||||
19
src/app/api/[[...slugs]]/_lib/ppid/index.ts
Normal file
19
src/app/api/[[...slugs]]/_lib/ppid/index.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import Elysia from "elysia";
|
||||
import DaftarInformasiPublik from "./daftar_informasi_publik";
|
||||
import GrafikHasilKepuasanMasyarakat from "./ikm/grafik_hasil_kepuasan_masyarakat";
|
||||
import GrafikBerdasarkanJenisKelamin from "./ikm/grafik_berdasarkan_jenis_kelamin";
|
||||
import GrafikBerdasarkanResponden from "./ikm/grafik_responden";
|
||||
import GrafikBerdasarkanUmur from "./ikm/grafik_berdasarkan_umur";
|
||||
|
||||
|
||||
const PPID = new Elysia({ prefix: "/api/ppid", tags: ["PPID"] })
|
||||
.use(DaftarInformasiPublik)
|
||||
.use(GrafikHasilKepuasanMasyarakat)
|
||||
.use(GrafikBerdasarkanJenisKelamin)
|
||||
.use(GrafikBerdasarkanResponden)
|
||||
.use(GrafikBerdasarkanUmur)
|
||||
|
||||
|
||||
|
||||
export default PPID
|
||||
|
||||
@@ -14,6 +14,8 @@ import uplImg from "./_lib/upl-img";
|
||||
import { uplImgSingle } from "./_lib/upl-img-single";
|
||||
import Desa from "./_lib/desa";
|
||||
import Kesehatan from "./_lib/kesehatan";
|
||||
import PPID from "./_lib/ppid";
|
||||
|
||||
const ROOT = process.cwd();
|
||||
|
||||
if (!process.env.WIBU_UPLOAD_DIR)
|
||||
@@ -61,6 +63,7 @@ const Utils = new Elysia({
|
||||
const ApiServer = new Elysia()
|
||||
.use(swagger({ path: "/api/docs" }))
|
||||
.use(cors(corsConfig))
|
||||
.use(PPID)
|
||||
.use(Kesehatan)
|
||||
.use(Desa)
|
||||
.use(Utils)
|
||||
|
||||
@@ -1,300 +0,0 @@
|
||||
"use client";
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable @typescript-eslint/no-empty-object-type */
|
||||
import { z } from "zod";
|
||||
|
||||
type RouterLeaf<T extends z.ZodType = z.ZodObject<{}>> = {
|
||||
get: () => string;
|
||||
query: (params: z.infer<T>) => string;
|
||||
parse: (searchParams: URLSearchParams) => z.infer<T>;
|
||||
};
|
||||
|
||||
// Helper type to convert dashes to camelCase
|
||||
type DashToCamelCase<S extends string> = S extends `${infer F}-${infer R}`
|
||||
? `${F}${Capitalize<DashToCamelCase<R>>}`
|
||||
: 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<Head>]: RouterPath<T, Tail> }
|
||||
: RouterLeaf<T>;
|
||||
|
||||
type RemoveLeadingSlash<S extends string> = S extends `/${infer Rest}`
|
||||
? Rest
|
||||
: S;
|
||||
|
||||
type SplitPath<S extends string> = S extends `${infer Head}/${infer Tail}`
|
||||
? [Head, ...SplitPath<Tail>]
|
||||
: S extends ""
|
||||
? []
|
||||
: [S];
|
||||
|
||||
type WibuRouterOptions = {
|
||||
prefix?: string;
|
||||
name?: string;
|
||||
};
|
||||
|
||||
export class V2ClientRouter<Routes = {}> {
|
||||
private tree: any = {};
|
||||
private prefix: string = "";
|
||||
private name: string = "";
|
||||
private querySchemas: Map<string, z.ZodType> = 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<Path>,
|
||||
Segments extends string[] = SplitPath<NormalizedPath>,
|
||||
T extends z.ZodType = z.ZodObject<{}>
|
||||
>(
|
||||
path: Path,
|
||||
schema?: { query: T }
|
||||
): V2ClientRouter<
|
||||
Routes &
|
||||
(NormalizedPath extends ""
|
||||
? RouterLeaf<T>
|
||||
: {
|
||||
[K in Segments[0] as DashToCamelCase<K>]: 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<string, any> = {};
|
||||
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<N extends string, ChildRoutes>(
|
||||
name: N,
|
||||
childRouter: V2ClientRouter<ChildRoutes>
|
||||
): V2ClientRouter<Routes & Record<DashToCamelCase<N>, 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<string, any> = {};
|
||||
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;
|
||||
@@ -1,20 +0,0 @@
|
||||
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;
|
||||
@@ -1,11 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
function Page() {
|
||||
return (
|
||||
<div>
|
||||
berita
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Page;
|
||||
@@ -1,33 +0,0 @@
|
||||
'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 <Page1 />
|
||||
case "2":
|
||||
return <Page2 />
|
||||
case "3":
|
||||
return <Page3 />
|
||||
default:
|
||||
return <Page1 />
|
||||
}
|
||||
}
|
||||
|
||||
const Page1 = () => {
|
||||
return <Box h={200} bg="red">Page 1</Box>
|
||||
}
|
||||
|
||||
const Page2 = () => {
|
||||
return <Box h={200} bg="blue">Page 2</Box>
|
||||
}
|
||||
|
||||
const Page3 = () => {
|
||||
return <Box h={200} bg="green">Page 3</Box>
|
||||
}
|
||||
|
||||
export default Page;
|
||||
@@ -1,105 +0,0 @@
|
||||
'use client'
|
||||
import { Box, Container, Flex, Grid, SimpleGrid, Skeleton, Stack, Text, Title } from '@mantine/core';
|
||||
import { useSearchParams } from 'next/navigation';
|
||||
import React from 'react';
|
||||
|
||||
const tx = `
|
||||
Untuk menambahkan fitur berbagi nomor WhatsApp di kode yang Anda miliki, saya akan menjelaskan beberapa pendekatan yang bisa digunakan. Biasanya ini dilakukan dengan membuat link yang ketika diklik akan membuka aplikasi WhatsApp dengan nomor tujuan yang sudah diatur.
|
||||
Berikut adalah cara mengimplementasikannya pada kode React Anda:
|
||||
`
|
||||
|
||||
function Page() {
|
||||
return (
|
||||
<Stack>
|
||||
<Grid>
|
||||
<Grid.Col span={{
|
||||
base: 12,
|
||||
sm: 6,
|
||||
md: 4,
|
||||
xl: 10
|
||||
}}>
|
||||
<Box h={"200"} bg={"blue"}>1</Box>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={{
|
||||
base: 12,
|
||||
sm: 6,
|
||||
md: 8,
|
||||
xl: 2
|
||||
}}>
|
||||
<Box h={"200"} bg={"red"}>1</Box>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
<SimpleGrid cols={{
|
||||
base: 1,
|
||||
sm: 2,
|
||||
md: 4,
|
||||
xl: 20
|
||||
}}>
|
||||
{Array.from({ length: 10 }).map((_, i) => (
|
||||
<Box key={i} h={"60"} bg={"blue"}>1</Box>
|
||||
))}
|
||||
</SimpleGrid>
|
||||
<Flex >
|
||||
<Box w={400} h={"200"} bg={"blue"}>1</Box>
|
||||
<Box w={400} bg={"red"}>
|
||||
<Text fz={"42"} lineClamp={1} >{tx}</Text>
|
||||
<Text bg={"blue"} style={{
|
||||
fontSize: "2rem"
|
||||
}} lineClamp={1} >{tx}</Text>
|
||||
<Title order={1}>apa kabar</Title>
|
||||
</Box>
|
||||
|
||||
</Flex>
|
||||
|
||||
<Page2/>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
export default Page;
|
||||
|
||||
const Halaman = [Halaman1, Halaman2, Halaman3]
|
||||
|
||||
function Page2() {
|
||||
const page = useSearchParams().get("p");
|
||||
if (!page) return <Container >
|
||||
<Stack>
|
||||
<Text>halo 1</Text>
|
||||
{Array.from({ length: 4 }).map((v, k) => <Skeleton h={100} key={k} />)}
|
||||
</Stack>
|
||||
</Container>
|
||||
|
||||
|
||||
return (
|
||||
<Container w={"100%"}>
|
||||
<Stack>
|
||||
<Text>halo 2</Text>
|
||||
{Halaman[Number(page)-1]()}
|
||||
</Stack>
|
||||
</Container>
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
function Halaman1() {
|
||||
return <Stack bg={"blue"}>
|
||||
ini halaman 1
|
||||
</Stack>
|
||||
}
|
||||
|
||||
|
||||
function Halaman2() {
|
||||
return <Stack bg={"red"}>
|
||||
ini halaman 2
|
||||
</Stack>
|
||||
}
|
||||
|
||||
|
||||
function Halaman3() {
|
||||
return <Stack bg={"grape"}>
|
||||
ini halaman 3
|
||||
</Stack>
|
||||
}
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
'use client'
|
||||
import { Button, Group, Stack } from "@mantine/core"
|
||||
import { Link } from "next-view-transitions"
|
||||
import router from "./_router/router"
|
||||
|
||||
const Page = () => {
|
||||
return <Group>
|
||||
<Stack>
|
||||
{[1, 2, 3].map((v) => (<Button component={Link} href={router.routes.dashboard.query({
|
||||
page: v.toString(),
|
||||
})} key={v}>ke dashboard {v}</Button>))}
|
||||
|
||||
<Button component={Link} href={router.routes.dashboard.berita.get()}>berita</Button>
|
||||
</Stack>
|
||||
</Group>
|
||||
}
|
||||
|
||||
export default Page
|
||||
Reference in New Issue
Block a user