Compare commits

...

2 Commits

Author SHA1 Message Date
795c79dd5f Tambahan Admin Di Menu PPID 2025-05-06 13:57:33 +08:00
a8556aacb7 Fix Eror Admin Persentase & Grafik 2025-05-02 16:08:30 +08:00
68 changed files with 2634 additions and 541 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "desa-darmasaba",
"version": "0.1.1",
"version": "0.1.2",
"private": true,
"scripts": {
"dev": "next dev --turbopack",

View File

@@ -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 {
@@ -323,3 +383,73 @@ model GrafikKepuasan {
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
// ========================================= ARTIKEL KESEHATAN ========================================= //
model ArtikelKesehatan {
id Int @id @default(autoincrement())
title String
content String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
model Introduction {
id Int @id @default(autoincrement())
content String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
model Symptom {
id Int @id @default(autoincrement())
title String
content String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
model Prevention {
id Int @id @default(autoincrement())
title String
content String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
model FirstAid {
id Int @id @default(autoincrement())
title String
content String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
model MythVsFact {
id Int @id @default(autoincrement())
title String
mitos String
fakta String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
model DoctorSign {
id Int @id @default(autoincrement())
content String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}

View File

@@ -0,0 +1,339 @@
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
import { proxy } from "valtio";
import { z } from "zod";
/* Introduction */
const templateIntroduction = z.object({
content: z.string().min(3, "Content minimal 3 karakter"),
})
type Introduction = Prisma.IntroductionGetPayload<{
select: {
content: true;
};
}>;
const introduction = proxy({
create: {
form: {} as Introduction,
loading: false,
async create() {
const cek = templateIntroduction.safeParse(introduction.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
introduction.create.loading = true;
const res = await ApiFetch.api.kesehatan.introduction["create"].post(introduction.create.form);
if (res.status === 200) {
introduction.findMany.load();
return toast.success("success create");
}
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} finally {
introduction.create.loading = false;
}
},
},
findMany: {
data: null as
| Prisma.IntroductionGetPayload<{ omit: { isActive: true } }>[]
| null,
async load() {
const res = await ApiFetch.api.kesehatan.introduction["find-many"].get();
if (res.status === 200) {
introduction.findMany.data = res.data?.data ?? [];
}
}
}
});
/* ======================================================================= */
/* symptom */
const templateSymptom = z.object({
title: z.string().min(3, "Title minimal 3 karakter"),
content: z.string().min(3, "Content minimal 3 karakter"),
})
type Symptom = Prisma.SymptomGetPayload<{
select: {
title: true;
content: true;
};
}>;
const symptom = proxy({
create: {
form: {} as Symptom,
loading: false,
async create() {
const cek = templateSymptom.safeParse(symptom.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
symptom.create.loading = true;
const res = await ApiFetch.api.kesehatan.symptom["create"].post(symptom.create.form);
if (res.status === 200) {
symptom.findMany.load();
return toast.success("success create");
}
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} finally {
symptom.create.loading = false;
}
},
},
findMany: {
data: null as
| Prisma.SymptomGetPayload<{ omit: { isActive: true } }>[]
| null,
async load() {
const res = await ApiFetch.api.kesehatan.symptom["find-many"].get();
if (res.status === 200) {
symptom.findMany.data = res.data?.data ?? [];
}
},
},
});
/* ======================================================================= */
/* Prevention */
const templatePrevention = z.object({
title: z.string().min(3, "Title minimal 3 karakter"),
content: z.string().min(3, "Content minimal 3 karakter"),
})
type Prevention = Prisma.PreventionGetPayload<{
select: {
title: true;
content: true;
};
}>;
const prevention = proxy({
create: {
form: {} as Prevention,
loading: false,
async create() {
const cek = templatePrevention.safeParse(prevention.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
prevention.create.loading = true;
const res = await ApiFetch.api.kesehatan.prevention["create"].post(prevention.create.form);
if (res.status === 200) {
prevention.findMany.load();
return toast.success("success create");
}
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} finally {
prevention.create.loading = false;
}
},
},
findMany: {
data: null as
| Prisma.PreventionGetPayload<{ omit: { isActive: true } }>[]
| null,
async load() {
const res = await ApiFetch.api.kesehatan.prevention["find-many"].get();
if (res.status === 200) {
prevention.findMany.data = res.data?.data ?? [];
}
},
},
});
/* ======================================================================= */
/* First Aid */
const templateFirstAid = z.object({
title: z.string().min(3, "Title minimal 3 karakter"),
content: z.string().min(3, "Content minimal 3 karakter"),
})
type FirstAid = Prisma.FirstAidGetPayload<{
select: {
title: true;
content: true;
};
}>;
const firstAid = proxy({
create: {
form: {} as FirstAid,
loading: false,
async create() {
const cek = templateFirstAid.safeParse(firstAid.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
firstAid.create.loading = true;
const res = await ApiFetch.api.kesehatan.firstaid["create"].post(firstAid.create.form);
if (res.status === 200) {
firstAid.findMany.load();
return toast.success("success create");
}
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} finally {
firstAid.create.loading = false;
}
},
},
findMany: {
data: null as
| Prisma.FirstAidGetPayload<{ omit: { isActive: true } }>[]
| null,
async load() {
const res = await ApiFetch.api.kesehatan.firstaid["find-many"].get();
if (res.status === 200) {
firstAid.findMany.data = res.data?.data ?? [];
}
},
},
})
/* ======================================================================= */
/* Myth vs Fact */
const templateMythFact = z.object({
title: z.string().min(3, "Title minimal 3 karakter"),
mitos: z.string().min(3, "Mitos minimal 3 karakter"),
fakta: z.string().min(3, "Fakta minimal 3 karakter"),
})
type MythFact = Prisma.MythVsFactGetPayload<{
select: {
title: true;
mitos: true;
fakta: true;
};
}>;
const mythFact = proxy({
create: {
form: {} as MythFact,
loading: false,
async create() {
const cek = templateMythFact.safeParse(mythFact.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
mythFact.create.loading = true;
const res = await ApiFetch.api.kesehatan.mythvsfact["create"].post(mythFact.create.form);
if (res.status === 200) {
mythFact.findMany.load();
return toast.success("success create");
}
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} finally {
mythFact.create.loading = false;
}
},
},
findMany: {
data: null as
| Prisma.MythVsFactGetPayload<{ omit: { isActive: true } }>[]
| null,
async load() {
const res = await ApiFetch.api.kesehatan.mythvsfact["find-many"].get();
if (res.status === 200) {
mythFact.findMany.data = res.data?.data ?? [];
}
},
},
})
/* ======================================================================= */
/* Doctor Sign */
const templateDoctorSign = z.object({
content: z.string().min(3, "Content minimal 3 karakter"),
})
type DoctorSign = Prisma.DoctorSignGetPayload<{
select: {
content: true
}
}>
const doctorSign = proxy({
create: {
form: {} as DoctorSign,
loading: false,
async create() {
const cek = templateDoctorSign.safeParse(doctorSign.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
doctorSign.create.loading = true;
const res = await ApiFetch.api.kesehatan.doctor_sign["create"].post(doctorSign.create.form);
if (res.status === 200) {
doctorSign.findMany.load();
return toast.success("success create");
}
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} finally {
doctorSign.create.loading = false;
}
},
},
findMany: {
data: null as
| Prisma.DoctorSignGetPayload<{ omit: { isActive: true } }>[]
| null,
async load() {
const res = await ApiFetch.api.kesehatan.doctor_sign["find-many"].get();
if (res.status === 200) {
doctorSign.findMany.data = res.data?.data ?? [];
}
},
},
})
/* ======================================================================= */
const stateArtikelKesehatan = proxy({
introduction,
symptom,
prevention,
firstAid,
mythFact,
doctorSign
})
export default stateArtikelKesehatan

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -24,8 +24,14 @@ export function BeritaEditor({ onSubmit }: { onSubmit: (val: string) => void })
TextAlign.configure({ types: ['heading', 'paragraph'] }),
],
content,
immediatelyRender: false
});
if (!editor) {
return null;
}
return (
<Stack>
<RichTextEditor editor={editor}>

View File

@@ -0,0 +1,23 @@
'use client'
import { Box, Text } from '@mantine/core';
import React from 'react';
import { KesehatanEditor } from '../../../_com/kesehatanEditor';
import { useProxy } from 'valtio/utils';
import stateArtikelKesehatan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/artikelKesehatan';
function DoctorSignUI() {
const doctorSign = useProxy(stateArtikelKesehatan.doctorSign)
return (
<Box>
<Text fw={"bold"}>Kapan Harus ke Dokter</Text>
<KesehatanEditor
showSubmit={false}
onChange={(val) => {
doctorSign.create.form.content = val
}}
/>
</Box>
);
}
export default DoctorSignUI;

View File

@@ -0,0 +1,29 @@
'use client'
import { Box, Text, TextInput } from '@mantine/core';
import React from 'react';
import { useProxy } from 'valtio/utils';
import stateArtikelKesehatan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/artikelKesehatan';
import { KesehatanEditor } from '../../../_com/kesehatanEditor';
function FirstAidUI() {
const firstAidState = useProxy(stateArtikelKesehatan.firstAid)
return (
<Box>
<TextInput
label={<Text fw={"bold"}>Title Pertolongan Pertama</Text>}
placeholder="Masukkan title"
onChange={(val) => {
firstAidState.create.form.title = val.target.value
}}
/>
<KesehatanEditor
showSubmit={false}
onChange={(val) => {
firstAidState.create.form.content = val
}}
/>
</Box>
);
}
export default FirstAidUI;

View File

@@ -0,0 +1,23 @@
'use client'
import { Box, Text } from '@mantine/core';
import React from 'react';
import { useProxy } from 'valtio/utils';
import { KesehatanEditor } from '../../../_com/kesehatanEditor';
import stateArtikelKesehatan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/artikelKesehatan';
function IntoductionUI() {
const introduction = useProxy(stateArtikelKesehatan.introduction)
return (
<Box py={10}>
<Text fw={"bold"}>Pendahuluan</Text>
<KesehatanEditor
showSubmit={false}
onChange={(val) => {
introduction.create.form.content = val;
}}
/>
</Box>
);
}
export default IntoductionUI;

View File

@@ -0,0 +1,35 @@
'use client'
import stateArtikelKesehatan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/artikelKesehatan';
import { Box, Text, TextInput } from '@mantine/core';
import { useProxy } from 'valtio/utils';
function MythFactUI() {
const mythFact = useProxy(stateArtikelKesehatan.mythFact)
return (
<Box py={10}>
<TextInput
label={<Text fw={"bold"}>Title Pertolongan Pertama Penyakit</Text>}
placeholder="Masukkan title"
onChange={(val) => {
mythFact.create.form.title = val.target.value
}}
/>
<TextInput
label={<Text fw={"bold"}>Mitos</Text>}
placeholder="Masukkan mitos"
onChange={(val) => {
mythFact.create.form.mitos = val.target.value
}}
/>
<TextInput
label={<Text fw={"bold"}>Fakta</Text>}
placeholder="Masukkan fakta"
onChange={(val) => {
mythFact.create.form.fakta = val.target.value
}}
/>
</Box>
);
}
export default MythFactUI;

View File

@@ -1,26 +1,149 @@
import { Box, SimpleGrid, Stack, TextInput, Title } from '@mantine/core';
import React from 'react';
'use client'
import { Box, Button, Center, SimpleGrid, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core';
import IntoductionUI from './introduction/page';
import SymptomUI from './symptom/page';
import PreventionUI from './prevention/page';
import MythFactUI from './mythVsfact/page';
import DoctorSignUI from './doctor_sign/page';
import { useProxy } from 'valtio/utils';
import stateArtikelKesehatan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/artikelKesehatan';
import FirstAidUI from './first_aid/page';
import { useShallowEffect } from '@mantine/hooks';
import colors from '@/con/colors';
function ArtikelKesehatan() {
const state = useProxy(stateArtikelKesehatan)
const submitAllForms = () => {
if (state.introduction.create.form.content) {
state.introduction.create.create()
}
if (state.symptom.create.form.title && state.symptom.create.form.content) {
state.symptom.create.create()
}
if (state.prevention.create.form.title && state.prevention.create.form.content) {
state.prevention.create.create()
}
if (state.firstAid.create.form.title && state.firstAid.create.form.content) {
state.firstAid.create.create()
}
if (state.mythFact.create.form.title && state.mythFact.create.form.mitos && state.mythFact.create.form.fakta) {
state.mythFact.create.create()
}
if (state.doctorSign.create.form.content) {
state.doctorSign.create.create()
}
}
return (
<Stack py={10}>
<SimpleGrid cols={{
base: 1, md: 2
}}>
<Box>
<Box >
<Title order={3}>Artikel Kesehatan</Title>
<TextInput
label="Jadwal"
placeholder='masukkan artikel kesehatan'
/>
<IntoductionUI />
<SymptomUI />
<PreventionUI />
<FirstAidUI />
<MythFactUI />
<DoctorSignUI />
<Button mt={10} onClick={submitAllForms}>Submit</Button>
</Box>
<Box>
<Title order={3}>List Artikel Kesehatan</Title>
<AllList />
</Box>
</SimpleGrid>
</Stack>
);
}
function AllList() {
const listState = useProxy(stateArtikelKesehatan)
useShallowEffect(() => {
listState.introduction.findMany.load();
listState.symptom.findMany.load();
listState.prevention.findMany.load();
listState.firstAid.findMany.load();
listState.mythFact.findMany.load();
listState.doctorSign.findMany.load();
}, [])
if (!listState.introduction.findMany.data
|| !listState.symptom.findMany.data
|| !listState.prevention.findMany.data
|| !listState.firstAid.findMany.data
|| !listState.mythFact.findMany.data
|| !listState.doctorSign.findMany.data
) return <Stack>
{Array.from({ length: 10 }).map((v, k) => <Skeleton key={k} h={40} />)}
</Stack>
return <Stack>
<Title order={4}>Intoduction</Title>
{listState.introduction.findMany.data?.map((item) => (
<Box key={item.id}>
<Text dangerouslySetInnerHTML={{ __html: item.content }}></Text>
</Box>
))}
{/* Symptom */}
{listState.symptom.findMany.data?.map((item) => (
<Box key={item.id}>
<Title order={4}>{item.title}</Title>
<Text dangerouslySetInnerHTML={{ __html: item.content }}></Text>
</Box>
))}
{/* Prevention */}
{listState.prevention.findMany.data?.map((item) => (
<Box key={item.id}>
<Title order={4}>{item.title}</Title>
<Text dangerouslySetInnerHTML={{ __html: item.content }}></Text>
</Box>
))}
{/* First Aid */}
{listState.firstAid.findMany.data?.map((item) => (
<Box key={item.id}>
<Title order={4}>{item.title}</Title>
<Text dangerouslySetInnerHTML={{ __html: item.content }}></Text>
</Box>
))}
{/* Myth Fact */}
{listState.mythFact.findMany.data?.map((item) => (
<Box key={item.id}>
<Title order={4}>{item.title}</Title>
<Table
striped
highlightOnHover
withTableBorder
withColumnBorders
bg={colors['white-1']}
>
<TableThead >
<TableTr >
<TableTh >
<Center>Mitos</Center>
</TableTh>
<TableTh >
<Center>Fakta</Center>
</TableTh>
</TableTr>
</TableThead>
<TableTbody >
<TableTr>
<TableTd ta="center">{item.mitos}</TableTd>
<TableTd ta="center">{item.fakta}</TableTd>
</TableTr>
</TableTbody>
</Table>
</Box>
))}
{/* Doctor Sign */}
<Title order={4}>Kapan Harus Ke Dokter?</Title>
{listState.doctorSign.findMany.data?.map((item) => (
<Box key={item.id}>
<Text dangerouslySetInnerHTML={{ __html: item.content }}/>
</Box>
))}
</Stack>
}
export default ArtikelKesehatan;

View File

@@ -0,0 +1,30 @@
'use client'
import stateArtikelKesehatan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/artikelKesehatan';
import { Box, Text, TextInput } from '@mantine/core';
import React from 'react';
import { useProxy } from 'valtio/utils';
import { KesehatanEditor } from '../../../_com/kesehatanEditor';
function PreventionUI() {
const preventionState = useProxy(stateArtikelKesehatan.prevention)
return (
<Box py={10}>
<TextInput
mb={10}
label={<Text fw={"bold"}>Title Pencegahan Penyakit</Text>}
placeholder="Masukkan title"
onChange={(val) => {
preventionState.create.form.title = val.target.value
}}
/>
<KesehatanEditor
showSubmit={false}
onChange={(val) => {
preventionState.create.form.content = val
}}
/>
</Box>
);
}
export default PreventionUI;

View File

@@ -0,0 +1,29 @@
'use client'
import stateArtikelKesehatan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/artikelKesehatan';
import { Box, Text, TextInput } from '@mantine/core';
import { useProxy } from 'valtio/utils';
import { KesehatanEditor } from '../../../_com/kesehatanEditor';
function SymptomUI() {
const symptomState = useProxy(stateArtikelKesehatan.symptom)
return (
<Box py={10}>
<TextInput
mb={10}
label={<Text fw={"bold"}>Title Gejala Penyakit</Text>}
placeholder='masukkan title'
onChange={(val) => {
symptomState.create.form.title = val.target.value
}}
/>
<KesehatanEditor
showSubmit={false}
onChange={(val) => {
symptomState.create.form.content = val
}}
/>
</Box>
);
}
export default SymptomUI;

View File

@@ -1,16 +1,20 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
'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 { useMediaQuery, useShallowEffect } from '@mantine/hooks';
import { useEffect, useState } from 'react';
import { Bar, BarChart, Legend, 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 [mounted, setMounted] = useState(false);
const isTablet = useMediaQuery('(max-width: 1024px)')
const isMobile = useMediaQuery('(max-width: 768px)')
const [chartData, setChartData] = useState<any[]>([])
useShallowEffect(() => {
const fetchData = async () => {
await grafikkepuasan.findMany.load();
@@ -20,11 +24,22 @@ function GrafikHasilKepuasan() {
};
fetchData();
}, []);
useEffect(() => {
setMounted(true); // setelah komponen siap di client
}, []);
if (!mounted) {
return null; // Jangan render apa-apa dulu sebelum mounted
}
return (
<Stack gap={"xs"}>
<Title order={3}>Grafik Hasil Kepuasan</Title>
<Box>
<TextInput
w={{ base: '100%', md: '50%' }}
label="Label"
placeholder='Masukkan label yang diinginkan'
value={grafikkepuasan.create.form.label}
@@ -33,9 +48,10 @@ function GrafikHasilKepuasan() {
}}
/>
<TextInput
label="Jumlah Penderita Farangitis Akut"
w={{ base: '100%', md: '50%' }}
label="Jumlah Penderita"
type='number'
placeholder='Masukkan jumlah penderita farangitis akut'
placeholder='Masukkan jumlah penderita'
value={grafikkepuasan.create.form.jumlah}
onChange={(val) => {
grafikkepuasan.create.form.jumlah = val.currentTarget.value
@@ -43,29 +59,29 @@ function GrafikHasilKepuasan() {
/>
</Box>
<Group>
<Button mt={10}
onClick={async () => {
await grafikkepuasan.create.create();
await grafikkepuasan.findMany.load();
if (grafikkepuasan.findMany.data) {
setChartData(grafikkepuasan.findMany.data);
}
}}
<Button mt={10}
onClick={async () => {
await grafikkepuasan.create.create();
await grafikkepuasan.findMany.load();
if (grafikkepuasan.findMany.data) {
setChartData(grafikkepuasan.findMany.data);
}
}}
>Submit</Button>
</Group>
<Box h={400} w={{ base: "100%", md: "80%" }}>
<Title order={3}>Data Kelahiran & Kematian</Title>
<ResponsiveContainer width="100%" height="100%">
<RadialBarChart
<Title py={15} order={3}>Data Kelahiran & Kematian</Title>
<BarChart
width={isMobile ? 450 : isTablet ? 600 : 900}
height={380}
data={chartData}
>
<XAxis dataKey="label" />
<YAxis />
<Tooltip />
<Legend />
<Bar dataKey="jumlah" fill={colors['blue-button']} name="Jumlah" />
</RadialBarChart>
</ResponsiveContainer>
>
<XAxis dataKey="label" />
<YAxis />
<Tooltip />
<Legend />
<Bar dataKey="jumlah" fill={colors['blue-button']} name="Jumlah" />
</BarChart>
</Box>
</Stack>
);

View File

@@ -2,15 +2,17 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import statePersentase from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/persentaseKelahiran';
import { Box, Button, Stack, TextInput, Title } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { useMediaQuery, useShallowEffect } from '@mantine/hooks';
import { useEffect, useState } from 'react';
import { Bar, BarChart, Legend, ResponsiveContainer, Tooltip, XAxis, YAxis } from 'recharts';
import { Bar, BarChart, Legend, Tooltip, XAxis, YAxis } from 'recharts';
import { useProxy } from 'valtio/utils';
function PersentaseDataKelahiranKematian() {
const persentase = useProxy(statePersentase.persentasekelahiran);
const [chartData, setChartData] = useState<any[]>([]);
const [mounted, setMounted] = useState(false); // untuk memastikan DOM sudah ready
const isTablet = useMediaQuery('(max-width: 1024px)')
const isMobile = useMediaQuery('(max-width: 768px)')
useEffect(() => {
setMounted(true);
@@ -87,19 +89,17 @@ function PersentaseDataKelahiranKematian() {
{/* Chart */}
<Box style={{ width: '100%', minWidth: 300, height: 400, minHeight: 300 }}>
<Title order={3}>Data Kelahiran & Kematian</Title>
<Title pb={10} order={3}>Data Kelahiran & Kematian</Title>
{mounted && chartData.length > 0 && (
<ResponsiveContainer width="100%" aspect={2}>
<BarChart width={300} data={chartData}>
<XAxis dataKey="tahun" />
<YAxis />
<Tooltip />
<Legend />
<Bar dataKey="kematianKasar" fill="#f03e3e" name="Kematian Kasar" />
<Bar dataKey="kematianBayi" fill="#ff922b" name="Kematian Bayi" />
<Bar dataKey="kelahiranKasar" fill="#4dabf7" name="Kelahiran Kasar" />
</BarChart>
</ResponsiveContainer>
<BarChart width={isMobile ? 450 : isTablet ? 600 : 900} height={380} data={chartData} >
<XAxis dataKey="tahun" />
<YAxis />
<Tooltip />
<Legend />
<Bar dataKey="kematianKasar" fill="#f03e3e" name="Kematian Kasar" />
<Bar dataKey="kematianBayi" fill="#ff922b" name="Kematian Bayi" />
<Bar dataKey="kelahiranKasar" fill="#4dabf7" name="Kelahiran Kasar" />
</BarChart>
)}
</Box>
</Stack>

View 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>&lt;sup /&gt;</sup> and <sub>&lt;sub /&gt;</sub> tags)</li><li>Ordered and bullet lists</li><li>Text align&nbsp;</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>
);
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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>
);
}

View File

@@ -0,0 +1,26 @@
import prisma from "@/lib/prisma";
import { Prisma } from "@prisma/client";
import { Context } from "elysia";
type FormCreate = Prisma.DoctorSignGetPayload<{
select: {
content: true;
}
}>
export default async function doctorSignCreate(context: Context) {
const body = context.body as FormCreate;
await prisma.doctorSign.create({
data: {
content: body.content,
},
});
return {
success: true,
message: "Success create doctor sign",
data: {
...body,
},
};
}

View File

@@ -0,0 +1,10 @@
import prisma from "@/lib/prisma";
export default async function doctorSignFindMany() {
const data = await prisma.doctorSign.findMany();
return {
success: true,
message: "Success get doctor sign",
data,
};
}

View File

@@ -0,0 +1,15 @@
import Elysia, { t } from "elysia"
import doctorSignCreate from "./create"
import doctorSignFindMany from "./find-many"
const DoctorSign = new Elysia({
prefix: "/doctor_sign",
tags: ["Data Kesehatan/Artikel Kesehatan/Doctor Sign"],
})
.get("/find-many", doctorSignFindMany)
.post("/create", doctorSignCreate, {
body: t.Object({
content: t.String(),
}),
})
export default DoctorSign

View File

@@ -0,0 +1,27 @@
import prisma from "@/lib/prisma";
import { Prisma } from "@prisma/client";
import { Context } from "elysia";
type FormCreate = Prisma.FirstAidGetPayload<{
select: {
title: true;
content: true;
}
}>
export default async function firstAidCreate(context: Context) {
const body = context.body as FormCreate;
await prisma.firstAid.create({
data: {
title: body.title,
content: body.content,
},
});
return {
success: true,
message: "Success create first aid",
data: {
...body,
},
};
}

View File

@@ -0,0 +1,8 @@
import prisma from "@/lib/prisma"
export default async function firstAidFindMany() {
const res = await prisma.firstAid.findMany()
return {
data: res
}
}

View File

@@ -0,0 +1,16 @@
import Elysia, { t } from "elysia"
import firstAidCreate from "./create"
import firstAidFindMany from "./find-many"
const FirstAid = new Elysia({
prefix: "/firstaid",
tags: ["Data Kesehatan/Artikel Kesehatan/First Aid"]
})
.get("/find-many", firstAidFindMany)
.post("/create", firstAidCreate, {
body: t.Object({
title: t.String(),
content: t.String(),
}),
})
export default FirstAid

View File

@@ -0,0 +1,26 @@
import prisma from "@/lib/prisma";
import { Prisma } from "@prisma/client";
import { Context } from "elysia";
type FormCreate = Prisma.IntroductionGetPayload<{
select: {
content: true;
};
}>;
export default async function introductionCreate(context: Context) {
const body = context.body as FormCreate;
await prisma.introduction.create({
data: {
content: body.content,
},
});
return {
success: true,
message: "Success create introduction",
data: {
...body,
},
};
}

View File

@@ -0,0 +1,8 @@
import prisma from "@/lib/prisma";
export default async function introductionFindMany() {
const res = await prisma.introduction.findMany();
return {
data: res,
};
}

View File

@@ -0,0 +1,16 @@
import Elysia, { t } from "elysia";
import introductionCreate from "./create";
import introductionFindMany from "./find-many";
const Introduction = new Elysia({
prefix: "/introduction",
tags: ["Data Kesehatan/Artikel Kesehatan/Introduction"]
})
.get("/find-many", introductionFindMany)
.post("/create", introductionCreate, {
body: t.Object({
content: t.String(),
}),
})
export default Introduction;

View File

@@ -0,0 +1,30 @@
import prisma from "@/lib/prisma";
import { Context } from "elysia";
import { Prisma } from "@prisma/client";
type FormCreate = Prisma.MythVsFactGetPayload<{
select: {
title: true;
mitos: true;
fakta: true;
}
}>
export default async function mythVsFactCreate(context: Context) {
const body = context.body as FormCreate;
await prisma.mythVsFact.create({
data: {
title: body.title,
mitos: body.mitos,
fakta: body.fakta,
},
});
return {
success: true,
message: "Success create myth vs fact",
data: {
...body,
},
};
}

View File

@@ -0,0 +1,10 @@
import prisma from "@/lib/prisma";
export default async function mythVsFactFindMany() {
const mythVsFacts = await prisma.mythVsFact.findMany();
return {
success: true,
message: "Success get myth vs fact",
data: mythVsFacts,
};
}

View File

@@ -0,0 +1,17 @@
import Elysia, { t } from "elysia";
import mythVsFactCreate from "./create";
import mythVsFactFindMany from "./find-many";
const MythVsFact = new Elysia({
prefix: "/mythvsfact",
tags: ["Data Kesehatan/Artikel Kesehatan/Myth vs Fact"]
})
.get("/find-many", mythVsFactFindMany)
.post("/create", mythVsFactCreate, {
body: t.Object({
title: t.String(),
mitos: t.String(),
fakta: t.String(),
}),
})
export default MythVsFact

View File

@@ -0,0 +1,28 @@
import prisma from "@/lib/prisma";
import { Prisma } from "@prisma/client";
import { Context } from "elysia";
type FormCreate = Prisma.PreventionGetPayload<{
select: {
title: true;
content: true;
}
}>
export default async function preventionCreate(context: Context) {
const body = context.body as FormCreate;
await prisma.prevention.create({
data: {
title: body.title,
content: body.content,
},
});
return {
success: true,
message: "Success create prevention",
data: {
...body,
},
};
}

View File

@@ -0,0 +1,8 @@
import prisma from "@/lib/prisma"
export default async function preventionFindMany() {
const res = await prisma.prevention.findMany()
return {
data: res
}
}

View File

@@ -0,0 +1,16 @@
import Elysia, { t } from "elysia"
import preventionCreate from "./create"
import preventionFindMany from "./find-many"
const Prevention = new Elysia({
prefix: "/prevention",
tags: ["Data Kesehatan/Artikel Kesehatan/Prevention"]
})
.get("/find-many", preventionFindMany)
.post("/create", preventionCreate, {
body: t.Object({
title: t.String(),
content: t.String(),
}),
})
export default Prevention

View File

@@ -0,0 +1,27 @@
import prisma from "@/lib/prisma";
import { Prisma } from "@prisma/client";
import { Context } from "elysia";
type FormCreate = Prisma.SymptomGetPayload<{
select: {
title: true;
content: true;
}
}>
export default async function symptomCreate(context: Context) {
const body = context.body as FormCreate;
await prisma.symptom.create({
data: {
title: body.title,
content: body.content,
},
});
return {
success: true,
message: "Success create symptom",
data: {
...body,
},
};
}

View File

@@ -0,0 +1,8 @@
import prisma from "@/lib/prisma"
export default async function symptomFindMany() {
const res = await prisma.symptom.findMany()
return {
data: res
}
}

View File

@@ -0,0 +1,16 @@
import Elysia, { t } from "elysia";
import symptomCreate from "./create";
import symptomFindMany from "./find-many";
const Syptom = new Elysia({
prefix: "/symptom",
tags: ["Data Kesehatan/Artikel Kesehatan/Syptom"]
})
.get("/find-many", symptomFindMany)
.post("/create", symptomCreate, {
body: t.Object({
title: t.String(),
content: t.String(),
}),
})
export default Syptom

View File

@@ -13,6 +13,12 @@ import DokumenDiperlukan from "./data_kesehatan_warga/jadwal_kegiatan/dokumen_ya
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";
import Introduction from "./data_kesehatan_warga/artikel_kesehatan/introduction";
import Syptom from "./data_kesehatan_warga/artikel_kesehatan/syptom";
import Prevention from "./data_kesehatan_warga/artikel_kesehatan/prevention";
import FirstAid from "./data_kesehatan_warga/artikel_kesehatan/first_aid";
import MythVsFact from "./data_kesehatan_warga/artikel_kesehatan/myth_vs_fact";
import DoctorSign from "./data_kesehatan_warga/artikel_kesehatan/doctor_sign";
const Kesehatan = new Elysia({
@@ -33,4 +39,10 @@ const Kesehatan = new Elysia({
.use(PendaftaranJadwal)
.use(PersentaseKelahiranKematian)
.use(GrafikKepuasan)
.use(Introduction)
.use(Syptom)
.use(Prevention)
.use(FirstAid)
.use(MythVsFact)
.use(DoctorSign)
export default Kesehatan;

View File

@@ -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,
},
};
}

View File

@@ -0,0 +1,9 @@
import prisma from "@/lib/prisma";
export default async function daftarInformasiPublikFindMany() {
const res = await prisma.daftarInformasiPublik.findMany();
return {
data: res
}
}

View File

@@ -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

View File

@@ -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,
},
};
}

View File

@@ -0,0 +1,8 @@
import prisma from "@/lib/prisma";
export default async function grafikBerdasarkanJenisKelaminFindMany() {
const res = await prisma.grafikBerdasarkanJenisKelamin.findMany();
return {
data: res
}
}

View File

@@ -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;

View File

@@ -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,
},
};
}

View File

@@ -0,0 +1,8 @@
import prisma from "@/lib/prisma";
export async function grafikBerdasarkanUmurFindMany(){
const res = await prisma.grafikBerdasarkanUmur.findMany();
return {
data: res
}
}

View File

@@ -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;

View File

@@ -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,
},
};
}

View File

@@ -0,0 +1,8 @@
import prisma from "@/lib/prisma";
export default async function grafikHasilKepuasanMasyarakatFindMany() {
const res = await prisma.indeksKepuasanMasyarakat.findMany();
return {
data: res,
};
}

View File

@@ -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;

View File

@@ -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,
},
};
}

View File

@@ -0,0 +1,8 @@
import prisma from "@/lib/prisma";
export default async function grafikRespondenFindMany(){
const res = await prisma.grafikBerdasarkanResponden.findMany();
return{
data: res
}
}

View File

@@ -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

View 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

View File

@@ -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)
@@ -45,11 +47,26 @@ async function layanan() {
const data = await prisma.layanan.findMany();
return { data };
}
const Utils = new Elysia({
prefix: "/api/utils",
tags: ["Utils"],
}).get("/version", async () => {
const packageJson = await fs.readFile(
path.join(ROOT, "package.json"),
"utf-8"
);
const version = JSON.parse(packageJson).version;
return { version };
});
const ApiServer = new Elysia()
.use(swagger({ path: "/api/docs" }))
.use(cors(corsConfig))
.use(PPID)
.use(Kesehatan)
.use(Desa)
.use(Utils)
.onError(({ code }) => {
if (code === "NOT_FOUND") {
return {

View File

@@ -8,7 +8,7 @@ const data = [
title: "Olahraga dan Kepemudaan",
deskripsi: "Tim Bola Voli Putri Dharma Temaja meraih juara 3 dalam Turnamen Bola Voli Mangupura Cup 2024 kategori Putri Se-Bali ",
image: "/api/img/prestasi-voli.jpeg",
link: "/darmasaba/prestasi/tim-bola-voli-putri-dharma-temaja-meraih-juara-3-dalam-turnamen-bola-voli-mangupura-cup-2024-kategori-putri-se-bali"
link: "/darmasaba/prestasi-desa/tim-bola-voli-putri-dharma-temaja-meraih-juara-3-dalam-turnamen-bola-voli-mangupura-cup-2024-kategori-putri-se-bali"
},
{
id: 2,

View File

@@ -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;

View File

@@ -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;

View File

@@ -1,11 +0,0 @@
import React from 'react';
function Page() {
return (
<div>
berita
</div>
);
}
export default Page;

View File

@@ -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;

View File

@@ -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>
}

View File

@@ -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