Compare commits

..

2 Commits

Author SHA1 Message Date
8a34a122d0 Tambahan Di menu PPID 2025-05-09 10:35:30 +08:00
795c79dd5f Tambahan Admin Di Menu PPID 2025-05-06 13:57:33 +08:00
76 changed files with 2951 additions and 512 deletions

View File

@@ -0,0 +1,5 @@
[
{"name": "Melihat/Membaca/Mendengarkan/Mencatat"},
{"name": "Mendapatkan Salinan Informasi (Hardcopy)"},
{"name": "Mendapatkan Salinan Informasi (Softcopy)"}
]

View File

@@ -0,0 +1,5 @@
[
{ "name": "Mengambil Langsung" },
{ "name": "Dikirim Via Post" },
{ "name": "Dikirim Via Email" }
]

View File

@@ -0,0 +1,6 @@
[
{ "name": "Keuangan Desa" },
{ "name": "Pembangunan Desa" },
{ "name": "Data Demografi" },
{ "name": "Lainnya" }
]

View File

@@ -0,0 +1,7 @@
[
{"name": "<p>I.B Surya Prabhawa Manuaba, S.H., M.H.</p>"},
{"biodata" : "<h2>Biodata</h2> <p>I.B Surya Prabhawa Manuaba, S.H., M.H., adalah Perbekel Darmasaba periode 2021-2027, seorang advokat, pendiri Mantra Legal Consultants & Advocates, serta aktif di bidang musik dan akademis. Dia menempuh pendidikan hukum di Universitas Udayana dan Universitas Mahasaraswati Denpasar, serta memiliki pengalaman luas di berbagai organisasi dan kepemimpinan.</p>"},
{"riwayat" : "<h2>Riwayat Karir</h2> <ul> <li>2021 - 2027: Perbekel Desa Darmasaba</li> <li>2015 - Sekarang: Founder & Managing Director Mantra Legal Consultants & Advocates</li> <li>2020 - Sekarang: Founder Ugawa Record Music Studio</li> <li>2010 - 2016: Dosen Fakultas Hukum Universitas Mahasaraswati Denpasar</li> </ul>"},
{"pengalaman" : "<h2>Pengalaman Organisasi</h2> <ul> <li>1996 1997: Ketua OSIS SMP Negeri 1 Abiansemal</li><li>1999 2000: Ketua OSIS SMA Negeri 1 Mengwi</li> <li>2008 2009: Ketua BEM Universitas Mahasaraswati Denpasar</li> <li>2008 2010: Ketua Sekaa Taruna Sila Dharma, Banjar Tengah, Desa Adat Tegal, Darmasaba</li> <li>2020 Sekarang: Pengurus Young Lawyer Committee Peradi Denpasar</li> <li>2021 Sekarang: Dewan Kehormatan Himpunan Pengusaha Muda Indonesia (HIPMI) Badung</li> <li>2023 2028: Komite Tetap Advokasi Bidang Hukum dan Regulasi Kamar Dagang dan Industri Badung</li> </ul>"},
{"unggulan" : "<h2>Program Kerja Unggulan</h2> <h3>Pemberdayaan Ekonomi dan UMKM</h3> <ul> <li>Pelatihan dan pendampingan UMKM lokal</li> <li>Program bantuan modal usaha bagi pelaku usaha kecil</li><li>Digitalisasi UMKM untuk meningkatkan pemasaran produk lokal</li></ul><h3>Peningkatan Infrastruktur Desa</h3><ul><li>Pembangunan dan perbaikan jalan desa</li><li>Penyediaan fasilitas umum dan ruang terbuka hijau</li><li>Optimalisasi layanan publik berbasis digital</li></ul>"}
]

View File

@@ -47,6 +47,162 @@ model AppMenuChild {
appMenuId String?
}
//========================================= MENU PPID ========================================= //
// ========================================= VISI MISI PPID ========================================= //
model VisiPPID {
id String @id @default(cuid())
content String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
model MisiPPID {
id String @id @default(cuid())
content String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
// ========================================= PROFILE PPID ========================================= //
model ProfilePPID {
id String @id @default(cuid())
name String @unique
biodata String
riwayat String
pengalaman String
unggulan String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
// ========================================= 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)
}
//=========================================PERMOHONAN INFORMASI PUBLIK========================= //
model PermohonanInformasiPublik {
id String @id @default(cuid())
nomor Int @default(autoincrement())
name String
nik String
notelp String
alamat String
email String
jenisInformasiDiminta JenisInformasiDiminta? @relation(fields: [jenisInformasiDimintaId], references: [id])
jenisInformasiDimintaId String?
caraMemperolehInformasi CaraMemperolehInformasi? @relation(fields: [caraMemperolehInformasiId], references: [id])
caraMemperolehInformasiId String?
caraMemperolehSalinanInformasi CaraMemperolehSalinanInformasi? @relation(fields: [caraMemperolehSalinanInformasiId], references: [id])
caraMemperolehSalinanInformasiId String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
model JenisInformasiDiminta {
id String @id @default(cuid())
name String @unique
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
PermohonanInformasiPublik PermohonanInformasiPublik[]
}
model CaraMemperolehInformasi {
id String @id @default(cuid())
name String @unique
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
PermohonanInformasiPublik PermohonanInformasiPublik[]
}
model CaraMemperolehSalinanInformasi {
id String @id @default(cuid())
name String @unique
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
PermohonanInformasiPublik PermohonanInformasiPublik[]
}
//=========================================PERMOHONAN INFORMASI KEBERATAN PUBLIK========================= //
model FormulirPermohonanKeberatan {
id String @id @default(cuid())
name String
email String
notelp String
alasan String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
// ========================================= 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 {

View File

@@ -1,8 +1,11 @@
import prisma from '@/lib/prisma'
import categoryPengumuman from './data/category-pengumuman.json'
import katagoryBerita from './data/katagory-berita.json'
import caraMemperolehInformasi from './data/list-caraMemperolehInformasi.json'
import caraMemperolehSalinanInformasi from './data/list-caraMemperolehSalinanInformasi.json'
import jenisInformasiDiminta from './data/list-jenisInfromasi.json'
import layanan from './data/list-layanan.json'
import potensi from './data/list-potensi.json'
import katagoryBerita from './data/katagory-berita.json'
import categoryPengumuman from './data/category-pengumuman.json'
import prisma from '@/lib/prisma';
(async () => {
for (const l of layanan) {
await prisma.layanan.upsert({
@@ -67,6 +70,54 @@ import prisma from '@/lib/prisma';
}
console.log("category pengumuman success ...")
for (const j of jenisInformasiDiminta) {
await prisma.jenisInformasiDiminta.upsert({
where: {
name: j.name
},
update: {
name: j.name
},
create: {
name: j.name
}
})
}
console.log("jenis informasi diminta success ...")
for (const c of caraMemperolehInformasi) {
await prisma.caraMemperolehInformasi.upsert({
where: {
name: c.name
},
update: {
name: c.name
},
create: {
name: c.name
}
})
}
console.log("cara memperoleh informasi success ...")
for (const c of caraMemperolehSalinanInformasi) {
await prisma.caraMemperolehSalinanInformasi.upsert({
where: {
name: c.name
},
update: {
name: c.name
},
create: {
name: c.name
}
})
}
console.log("cara memperoleh salinan informasi success ...")
})().then(() => prisma.$disconnect()).catch((e) => {
console.error(e)
prisma.$disconnect()

View File

@@ -87,4 +87,4 @@ const stateDashboardBerita = proxy({
berita,
});
export default stateDashboardBerita;
export default stateDashboardBerita;

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

@@ -0,0 +1,120 @@
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 templateForm = z.object({
name: z.string().min(3, "Nama minimal 3 karakter"),
nik: z.string().min(3, "NIK minimal 3 karakter"),
notelp: z.string().min(3, "Nomor Telepon minimal 3 karakter"),
alamat: z.string().min(3, "Alamat minimal 3 karakter"),
email: z.string().min(3, "Email minimal 3 karakter"),
jenisInformasiDimintaId: z.string().nonempty(),
caraMemperolehInformasiId: z.string().nonempty(),
caraMemperolehSalinanInformasiId: z.string().nonempty(),
})
const jenisInformasiDiminta = proxy({
findMany: {
data: null as
| null
| Prisma.JenisInformasiDimintaGetPayload<{ omit: { isActive: true } }>[],
async load(){
const res = await ApiFetch.api.ppid.permohonaninformasipublik.jenisInformasi["find-many"].get();
if (res.status === 200) {
jenisInformasiDiminta.findMany.data = res.data?.data ?? [];
}
}
}
})
const caraMemperolehInformasi = proxy({
findMany: {
data: null as
| null
| Prisma.CaraMemperolehInformasiGetPayload<{ omit: { isActive: true } }>[],
async load() {
const res = await ApiFetch.api.ppid.permohonaninformasipublik.memperolehInformasi["find-many"].get();
if (res.status === 200) {
caraMemperolehInformasi.findMany.data = res.data?.data ?? [];
}
}
}
})
const caraMemperolehSalinanInformasi = proxy({
findMany: {
data: null as
| null
| Prisma.CaraMemperolehSalinanInformasiGetPayload<{ omit: { isActive: true } }>[],
async load() {
const res = await ApiFetch.api.ppid.permohonaninformasipublik.salinanInformasi["find-many"].get();
if (res.status === 200) {
caraMemperolehSalinanInformasi.findMany.data = res.data?.data ?? [];
}
}
}
})
type PermohonanInformasiPublikForm = Prisma.PermohonanInformasiPublikGetPayload<{
select: {
name: true;
nik: true;
notelp: true;
alamat: true;
email: true;
jenisInformasiDimintaId: true;
caraMemperolehInformasiId: true;
caraMemperolehSalinanInformasiId: true;
};
}>;
const permohonanInformasiPublikForm = proxy({
create: {
form: {} as PermohonanInformasiPublikForm,
loading: false,
async create(){
const cek = templateForm.safeParse(permohonanInformasiPublikForm.create.form);
if(!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
permohonanInformasiPublikForm.create.loading = true;
const res = await ApiFetch.api.ppid.permohonaninformasipublik["create"].post(permohonanInformasiPublikForm.create.form);
if (res.status === 200) {
permohonanInformasiPublikForm.findMany.load();
return toast.success("success create");
}
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} finally {
permohonanInformasiPublikForm.create.loading = false;
}
}
},
findMany: {
data: null as
| Prisma.PermohonanInformasiPublikGetPayload<{ omit: { isActive: true } }>[]
| null,
async load() {
const res = await ApiFetch.api.ppid.permohonaninformasipublik["find-many"].get();
if (res.status === 200) {
permohonanInformasiPublikForm.findMany.data = res.data?.data ?? [];
}
}
}
})
const statePermohonanInformasi = proxy({
permohonanInformasiPublikForm,
jenisInformasiDiminta,
caraMemperolehInformasi,
caraMemperolehSalinanInformasi
})
export default statePermohonanInformasi;

View File

@@ -0,0 +1,68 @@
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 templateForm = z.object({
name: z.string().min(3, "Nama minimal 3 karakter"),
email: z.string().min(3, "Email minimal 3 karakter"),
notelp: z.string().min(3, "Nomor Telepon minimal 3 karakter"),
alasan: z.string().min(3, "Alasan minimal 3 karakter"),
})
type PermohonanKeberatanInformasiForm = Prisma.FormulirPermohonanKeberatanGetPayload<{
select: {
name: true;
email: true;
notelp: true;
alasan: true;
};
}>;
const permohonanKeberatanInformasiForm = proxy({
create: {
form: {} as PermohonanKeberatanInformasiForm,
loading: false,
async create(){
const cek = templateForm.safeParse(permohonanKeberatanInformasiForm.create.form);
if(!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
permohonanKeberatanInformasiForm.create.loading = true;
const res = await ApiFetch.api.ppid.permohonankeberataninformasipublik["create"].post(permohonanKeberatanInformasiForm.create.form);
if (res.status === 200) {
permohonanKeberatanInformasiForm.findMany.load();
return toast.success("success create");
}
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} finally {
permohonanKeberatanInformasiForm.create.loading = false;
}
},
},
findMany: {
data: null as
| Prisma.FormulirPermohonanKeberatanGetPayload<{omit: {isActive: true}}>[]
| null,
async load() {
const res = await ApiFetch.api.ppid.permohonankeberataninformasipublik["find-many"].get();
if (res.status === 200) {
permohonanKeberatanInformasiForm.findMany.data = res.data?.data ?? [];
}
}
}
});
const statePermohonanKeberatan = proxy({
permohonanKeberatanInformasiForm,
})
export default statePermohonanKeberatan;

View File

@@ -0,0 +1,69 @@
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 templateForm = z.object({
name: z.string().min(3, "Nama minimal 3 karakter"),
biodata: z.string().min(3, "Biodata minimal 3 karakter"),
riwayat: z.string().min(3, "Riwayat minimal 3 karakter"),
pengalaman: z.string().min(3, "Pengalaman minimal 3 karakter"),
unggulan: z.string().min(3, "Unggulan minimal 3 karakter"),
})
type ProfilePPIDForm = Prisma.ProfilePPIDGetPayload<{
select: {
name: true;
biodata: true;
riwayat: true;
pengalaman: true;
unggulan: true;
}
}>
const profilePPID = proxy({
create: {
form: {} as ProfilePPIDForm,
loading: false,
async create() {
const cek = templateForm.safeParse(profilePPID.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
return toast.error(err);
}
try {
profilePPID.create.loading = true;
const res = await ApiFetch.api.ppid.profileppid["create"].post(profilePPID.create.form);
if (res.status === 200) {
profilePPID.findMany.load();
return toast.success("success create");
}
return toast.error("failed create");
} catch (error) {
console.log((error as Error).message);
} finally {
profilePPID.create.loading = false;
}
}
},
findMany: {
data: null as
| Prisma.ProfilePPIDGetPayload<{omit: {isActive: true}}>[]
| null,
async load() {
const res = await ApiFetch.api.ppid.profileppid["find-many"].get();
if (res.status === 200) {
profilePPID.findMany.data = res.data?.data ?? [];
}
}
}
})
const stateProfilePPID = proxy({
profilePPID
})
export default stateProfilePPID;

View File

@@ -10,9 +10,9 @@ import { BeritaEditor } from './_com/BeritaEditor';
function Page() {
return (
<Stack>
<SimpleGrid cols={2}>
<BeritaList />
<SimpleGrid cols={{base: 1, md: 2}}>
<BeritaCreate />
<BeritaList />
</SimpleGrid>
</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 PPIDTextEditor({ 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

@@ -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,113 @@
'use client'
import { Box, Button, Group, Paper, 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>
<Paper bg={colors['white-1']} p={'md'}>
<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>
</Paper>
</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 <Paper bg={colors['white-1']} p={'md'}>
<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>
</Paper>
}
export default Page;

View File

@@ -0,0 +1,142 @@
/* 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, Paper, 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}>
<Paper bg={colors['white-1']} w={{ base: '100%', md: '50%' }} p={'md'}>
<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>
</Paper>
</Box>
{/* Chart */}
<Box>
<Paper bg={colors['white-1']} w={{ base: '100%', md: '50%' }} p={'md'}>
<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={230}
label={true}
>
{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>
</Paper>
</Box>
</Box>
);
}
export default GrafikBerdasarkanJenisKelamin;

View File

@@ -0,0 +1,173 @@
'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, Group, Paper, 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}>
<Paper bg={colors['white-1']} w={{ base: '100%', md: '50%' }} p={'md'}>
<Stack gap={"xs"}>
<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;
}}
/>
<Group>
<Button
bg={colors['blue-button']}
onClick={handleSubmit}
>
Submit
</Button>
</Group>
</Stack>
</Paper>
</Box>
{/* Chart */}
<Box>
<Paper bg={colors['white-1']} w={{ base: '100%', md: '50%' }} p={'md'}>
<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={230}
label={true}
>
{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>
</Paper>
</Box>
</Box>
);
}
export default GrafikBerdasarkanResponden;

View File

@@ -0,0 +1,164 @@
'use client'
/* eslint-disable @typescript-eslint/no-explicit-any */
import colors from '@/con/colors';
import { Box, Button, Center, Flex, Group, Paper, 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}>
<Paper bg={colors['white-1']} w={{ base: '100%', md: '50%' }} p={'md'}>
<Stack gap={"xs"}>
<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;
}}
/>
<Group>
<Button
mt={10}
bg={colors['blue-button']}
onClick={handleSubmit}
>
Submit
</Button>
</Group>
</Stack>
</Paper>
</Box>
{/* Chart */}
<Box>
<Paper bg={colors['white-1']} w={{ base: '100%', md: '50%' }} p={'md'}>
<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={230}
label={true}
>
{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>
</Paper>
</Box>
</Box>
);
}
export default GrafikBerdasarakanUmur;

View File

@@ -0,0 +1,96 @@
/* 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, Paper, Stack, 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}>
<Paper bg={colors['white-1']} w={{ base: '100%', md: '50%' }} p={'md'}>
<Stack>
<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>
</Stack>
</Paper>
</Box>
{/* Chart */}
<Box style={{ width: '100%', minWidth: 300, height: 400, minHeight: 300 }}>
<Paper style={{ width: '100%', minWidth: 300, height: 400, minHeight: 300 }} bg={colors['white-1']} p={'md'}>
<Stack gap={"xs"}>
<Title pb={10} order={3}>Data Kepuasan Masyarakat</Title>
{mounted && chartData.length > 0 && (
<BarChart width={isMobile ? 300 : isTablet ? 600 : 900} height={380} data={chartData} >
<XAxis dataKey="label" />
<YAxis />
<Tooltip />
<Legend />
<Bar dataKey="kepuasan" fill={colors['blue-button']} name="Kepuasan" />
</BarChart>
)}
</Stack>
</Paper>
</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,41 @@
import { Prisma } from '@prisma/client';
import React from 'react';
import { useProxy } from 'valtio/utils';
import statePermohonanInformasi from '../../_state/ppid/permohonan_informasi_publik/permohonanInformasiPublik';
import { useShallowEffect } from '@mantine/hooks';
import { Group, Select, Skeleton } from '@mantine/core';
function JenisInformasi({ onChange }: {
onChange: (value: Prisma.JenisInformasiDimintaGetPayload<{
select: {
id: true,
name: true
}
}>) => void
}) {
const jenisInformasiState = useProxy(statePermohonanInformasi.jenisInformasiDiminta)
useShallowEffect(() => {
jenisInformasiState.findMany.load()
}, [])
if (!jenisInformasiState.findMany.data) return <Skeleton h={40} />
return (
<Group>
<Select
placeholder='pilih jenis informasi'
label={'select jenis informasi'}
data={jenisInformasiState.findMany.data.map((item) => ({
value: item.id,
label: item.name
}))}
onChange={(v) => {
const data = jenisInformasiState.findMany.data?.find((item) => item.id === v)
if (!data) return
onChange(data)
}}
/>
</Group>
);
}
export default JenisInformasi;

View File

@@ -0,0 +1,38 @@
import { Group, Select } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { Prisma } from '@prisma/client';
import React from 'react';
import { useProxy } from 'valtio/utils';
import statePermohonanInformasi from '../../_state/ppid/permohonan_informasi_publik/permohonanInformasiPublik';
function MemperolehInformasi({ onChange }: {
onChange: (value: Prisma.CaraMemperolehInformasiGetPayload<{
select: {
id: true,
name: true
}
}>) => void
}) {
const memperolehInformasiState = useProxy(statePermohonanInformasi.caraMemperolehInformasi)
useShallowEffect(() => {
memperolehInformasiState.findMany.load()
}, [])
return (
<Group>
<Select
placeholder='pilih katagori'
label={"select katagori"}
data={memperolehInformasiState.findMany.data?.map((item) => ({
value: item.id,
label: item.name
}))} onChange={(v) => {
const data = memperolehInformasiState.findMany.data?.find((item) => item.id === v)
if (!data) return
onChange(data)
}} />
</Group>
);
}
export default MemperolehInformasi;

View File

@@ -0,0 +1,45 @@
import { Group, Select, Skeleton } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { Prisma } from '@prisma/client';
import React from 'react';
import { useProxy } from 'valtio/utils';
import statePermohonanInformasi from '../../_state/ppid/permohonan_informasi_publik/permohonanInformasiPublik';
function MemperolehSalinan({ onChange }: {
onChange: (value: Prisma.CaraMemperolehSalinanInformasiGetPayload<{
select: {
id: true,
name: true
}
}>) => void
}) {
const memperolehSalinanInformasiState = useProxy(statePermohonanInformasi.caraMemperolehSalinanInformasi)
useShallowEffect(() => {
memperolehSalinanInformasiState.findMany.load()
}, [])
if (!memperolehSalinanInformasiState.findMany.data) return <Skeleton h={40} />
return (
<Group>
<Select
placeholder='pilih salinan informasi'
label={'select salinan informasi'}
data={memperolehSalinanInformasiState.findMany.data.map((item) => ({
value: item.id,
label: item.name
}))}
onChange={(v) => {
if (!v) return
const selectedItem = memperolehSalinanInformasiState.findMany.data?.find(item => item.id === v)
if (selectedItem) {
onChange({
id: selectedItem.id,
name: selectedItem.name
})
}
}}
/>
</Group>
);
}
export default MemperolehSalinan;

View File

@@ -1,11 +1,115 @@
import React from 'react';
'use client'
import { Box, Button, Group, Paper, SimpleGrid, Stack, TextInput, Title } from '@mantine/core';
import { useProxy } from 'valtio/utils';
import statePermohonanInformasi from '../../_state/ppid/permohonan_informasi_publik/permohonanInformasiPublik';
import JenisInformasi from './jenisInformasi';
import MemperolehInformasi from './memperolehInformasi';
import MemperolehSalinan from './memperolehSalinan';
import colors from '@/con/colors';
function Page() {
return (
<div>
permohonan-informasi-publik
</div>
<Box>
<Stack>
<SimpleGrid cols={{ base: 1, md: 2 }}>
<PermohonanInformasiPublikCreate />
</SimpleGrid>
</Stack>
</Box>
);
}
export default Page;
function PermohonanInformasiPublikCreate() {
const permohonanInformasiPublikState = useProxy(statePermohonanInformasi)
const submitForms = () => {
// Tambahkan log untuk debugging
console.log("Form data sebelum submit:", {
name: permohonanInformasiPublikState.permohonanInformasiPublikForm.create.form.name,
nik: permohonanInformasiPublikState.permohonanInformasiPublikForm.create.form.nik,
notelp: permohonanInformasiPublikState.permohonanInformasiPublikForm.create.form.notelp,
alamat: permohonanInformasiPublikState.permohonanInformasiPublikForm.create.form.alamat,
email: permohonanInformasiPublikState.permohonanInformasiPublikForm.create.form.email,
jenisInformasiDimintaId: permohonanInformasiPublikState.jenisInformasiDiminta,
caraMemperolehInformasiId: permohonanInformasiPublikState.caraMemperolehInformasi,
caraMemperolehSalinanInformasiId: permohonanInformasiPublikState.caraMemperolehSalinanInformasi
});
if (permohonanInformasiPublikState.permohonanInformasiPublikForm.create.form.name &&
permohonanInformasiPublikState.permohonanInformasiPublikForm.create.form.nik &&
permohonanInformasiPublikState.permohonanInformasiPublikForm.create.form.notelp &&
permohonanInformasiPublikState.permohonanInformasiPublikForm.create.form.alamat &&
permohonanInformasiPublikState.permohonanInformasiPublikForm.create.form.email &&
permohonanInformasiPublikState.jenisInformasiDiminta &&
permohonanInformasiPublikState.caraMemperolehInformasi &&
permohonanInformasiPublikState.caraMemperolehSalinanInformasi) {
permohonanInformasiPublikState.permohonanInformasiPublikForm.create.create()
} else {
console.log("Validasi gagal, form tidak lengkap");
// Tampilkan pesan error ke pengguna di sini
}
}
return (
<Box py={5}>
<Paper bg={colors['white-1']} p={'md'}>
<Stack gap={"10"}>
<Title order={3}>Permohonan Informasi Publik</Title>
<TextInput
label="Nama Lengkap"
placeholder="masukkan nama lengkap"
onChange={(val) => {
permohonanInformasiPublikState.permohonanInformasiPublikForm.create.form.name = val.target.value
}}
/>
<TextInput
label="NIK"
placeholder="masukkan NIK"
onChange={(val) => {
permohonanInformasiPublikState.permohonanInformasiPublikForm.create.form.nik = val.target.value
}}
/>
<TextInput
label="No.Telp"
placeholder="masukkan no telp"
onChange={(val) => {
permohonanInformasiPublikState.permohonanInformasiPublikForm.create.form.notelp = val.target.value
}}
/>
<TextInput
label="Alamat"
placeholder="masukkan alamat"
onChange={(val) => {
permohonanInformasiPublikState.permohonanInformasiPublikForm.create.form.alamat = val.target.value
}}
/>
<TextInput
label="Email"
placeholder="masukkan email"
onChange={(val) => {
permohonanInformasiPublikState.permohonanInformasiPublikForm.create.form.email = val.target.value
}}
/>
<JenisInformasi
onChange={(val) => {
permohonanInformasiPublikState.permohonanInformasiPublikForm.create.form.jenisInformasiDimintaId = val.id
}}
/>
<MemperolehInformasi
onChange={(val) => {
permohonanInformasiPublikState.permohonanInformasiPublikForm.create.form.caraMemperolehInformasiId = val.id
}}
/>
<MemperolehSalinan
onChange={(val) => {
permohonanInformasiPublikState.permohonanInformasiPublikForm.create.form.caraMemperolehSalinanInformasiId = val.id
}}
/>
<Group>
<Button onClick={submitForms}>Submit</Button>
</Group>
</Stack>
</Paper>
</Box >
)
}
export default Page;

View File

@@ -1,11 +1,98 @@
'use client'
import colors from '@/con/colors';
import { Box, Button, Group, Paper, SimpleGrid, Skeleton, Stack, Text, TextInput, Title } from '@mantine/core';
import React from 'react';
import { useProxy } from 'valtio/utils';
import statePermohonanKeberatan from '../../_state/ppid/permohonan_keberatan_informasi_publik/permohonanKeberatanInformasi';
import { useShallowEffect } from '@mantine/hooks';
function Page() {
return (
<div>
permohonan-keberatan-informasi-publik
</div>
<Box>
<SimpleGrid cols={{ base: 1, md: 2 }}>
<PermohonanKeberatanInformasiCreate />
<PermohonanKeberatanInformasiList />
</SimpleGrid>
</Box>
);
}
function PermohonanKeberatanInformasiCreate() {
const state = useProxy(statePermohonanKeberatan.permohonanKeberatanInformasiForm)
const submit = () => {
if (state.create.form.name && state.create.form.email && state.create.form.notelp && state.create.form.alasan) {
state.create.create()
}
}
return (
<Box py={10}>
<Paper bg={colors['white-1']} p={'md'}>
<Stack gap={"xs"}>
<Title order={3}>Permohonan Keberatan Informasi Publik</Title>
<TextInput
label="Nama"
placeholder="masukkan nama lengkap"
onChange={(val) => {
state.create.form.name = val.target.value
}}
/>
<TextInput
label="Email"
placeholder="masukkan email"
onChange={(val) => {
state.create.form.email = val.target.value
}}
/>
<TextInput
label="Nomor Telepon"
placeholder="masukkan nomor telepon"
onChange={(val) => {
state.create.form.notelp = val.target.value
}}
/>
<TextInput
label="Alasan Keberatan"
placeholder="masukkan alasan keberatan"
onChange={(val) => {
state.create.form.alasan = val.target.value
}}
/>
<Group>
<Button onClick={submit} bg={colors['blue-button']}>Kirim Permohonan</Button>
</Group>
</Stack>
</Paper>
</Box>
)
}
function PermohonanKeberatanInformasiList() {
const listState = useProxy(statePermohonanKeberatan.permohonanKeberatanInformasiForm)
useShallowEffect(() => {
listState.findMany.load()
}, [])
if (!listState.findMany.data) return <Stack>
{Array.from({ length: 10 }).map((v, k) => <Skeleton key={k} h={40} />)}
</Stack>
return (
<Box py={10}>
<Paper bg={colors['white-1']} p={'md'}>
<Stack gap={"xs"}>
<Title order={3}>Permohonan Keberatan Informasi Publik</Title>
{listState.findMany.data?.map((item) => (
<Box key={item.id}>
<Text>Nama: {item.name}</Text>
<Text>Email: {item.email}</Text>
<Text>Telepon: {item.notelp}</Text>
<Text>Alasan: {item.alasan}</Text>
</Box>
))}
</Stack>
</Paper>
</Box>
)
}
export default Page;

View File

@@ -0,0 +1,24 @@
'use client'
import { Box, Text } from '@mantine/core';
import React from 'react';
import { useProxy } from 'valtio/utils';
import stateProfilePPID from '../../../_state/ppid/profile_ppid/profile_PPID';
import { PPIDEditor } from '../../_com/ppid_Editor';
function Biodata() {
const biodataState = useProxy(stateProfilePPID.profilePPID)
return (<Box>
<Text fw={"bold"}>Biodata</Text>
<PPIDEditor
showSubmit={false}
onChange={(val) => {
biodataState.create.form.biodata = val
}}
/>
</Box>
);
}
export default Biodata;

View File

@@ -1,11 +1,102 @@
import React from 'react';
'use client'
import colors from '@/con/colors';
import { Box, Button, Group, Paper, SimpleGrid, Skeleton, Stack, Text, TextInput, Title } from '@mantine/core';
import Biodata from './biodata/page';
import PengalamanOrganisasi from './pengalaman_organisasi/page';
import RiwayatKarir from './riwayat_karir/page';
import ProgramKerjaUnggulan from './program_kerja_unggulan/page';
import { useProxy } from 'valtio/utils';
import stateProfilePPID from '../../_state/ppid/profile_ppid/profile_PPID';
import { useShallowEffect } from '@mantine/hooks';
function Page() {
return (
<div>
profile-ppid
</div>
<Box>
<SimpleGrid cols={{ base: 1, md: 2 }}>
<ProfileCreate />
<ProfileList />
</SimpleGrid>
</Box>
);
}
function ProfileCreate() {
const allState = useProxy(stateProfilePPID)
const submit = () => {
if (
allState.profilePPID.create.form.name &&
allState.profilePPID.create.form.biodata &&
allState.profilePPID.create.form.riwayat &&
allState.profilePPID.create.form.pengalaman &&
allState.profilePPID.create.form.unggulan) {
allState.profilePPID.create.create()
}
}
return (
<Paper bg={colors['white-1']} p={'md'} radius={10}>
<Stack gap={"xs"}>
<Title order={3}>Profile PPID</Title>
<TextInput
label={<Text fw={"bold"}>Nama Perbekel</Text>}
placeholder="masukkan nama perbekel"
onChange={(val) => {
allState.profilePPID.create.form.name = val.currentTarget.value
}}
/>
<Biodata />
<RiwayatKarir />
<PengalamanOrganisasi />
<ProgramKerjaUnggulan />
<Group>
<Button bg={colors['blue-button']} onClick={submit}>Submit</Button>
</Group>
</Stack>
</Paper>
)
}
function ProfileList() {
const allList = useProxy(stateProfilePPID)
useShallowEffect(() => {
allList.profilePPID.findMany.load()
}, [])
if (!allList.profilePPID.findMany.data) return <Stack>
{Array.from({ length: 10 }).map((v, k) => <Skeleton key={k} h={40} />)}
</Stack>
return (
<Paper bg={colors['white-1']} p={'md'}>
<Stack gap={"xs"}>
<Title order={3}>List Profile PPID</Title>
{allList.profilePPID.findMany.data?.map((item) => (
<Box key={item.id}>
<Box>
<Text fw={"bold"}>Nama</Text>
<Text dangerouslySetInnerHTML={{ __html: item.name }}></Text>
</Box>
<Box>
<Text fw={"bold"}>Biodata</Text>
<Text dangerouslySetInnerHTML={{ __html: item.biodata }}></Text>
</Box>
<Box>
<Text fw={"bold"}>Riwayat</Text>
<Text dangerouslySetInnerHTML={{ __html: item.riwayat }}></Text>
</Box>
<Box>
<Text fw={"bold"}>Pengalaman</Text>
<Text dangerouslySetInnerHTML={{ __html: item.pengalaman }}></Text>
</Box>
<Box>
<Text fw={"bold"}>Program Kerja Unggulan</Text>
<Text dangerouslySetInnerHTML={{ __html: item.unggulan }}></Text>
</Box>
</Box>
))}
</Stack>
</Paper>
)
}
export default Page;

View File

@@ -0,0 +1,22 @@
'use client'
import { Box, Text } from '@mantine/core';
import React from 'react';
import { useProxy } from 'valtio/utils';
import stateProfilePPID from '../../../_state/ppid/profile_ppid/profile_PPID';
import { PPIDEditor } from '../../_com/ppid_Editor';
function PengalamanOrganisasi() {
const pengalamanOrganisasiState = useProxy(stateProfilePPID.profilePPID)
return (<Box>
<Text fw={"bold"}>Pengalaman Organisasi</Text>
<PPIDEditor
showSubmit={false}
onChange={(val) => {
pengalamanOrganisasiState.create.form.pengalaman = val
}}
/>
</Box>
);
}
export default PengalamanOrganisasi;

View File

@@ -0,0 +1,22 @@
'use client'
import { Box, Text } from '@mantine/core';
import React from 'react';
import { useProxy } from 'valtio/utils';
import stateProfilePPID from '../../../_state/ppid/profile_ppid/profile_PPID';
import { PPIDEditor } from '../../_com/ppid_Editor';
function ProgramKerjaUnggulan() {
const programKerjaUnggulanState = useProxy(stateProfilePPID.profilePPID)
return (<Box>
<Text fw={"bold"}>Program Kerja Unggulan</Text>
<PPIDEditor
showSubmit={false}
onChange={(val) => {
programKerjaUnggulanState.create.form.unggulan = val
}}
/>
</Box>
);
}
export default ProgramKerjaUnggulan;

View File

@@ -0,0 +1,30 @@
'use client';
import { Box, Text } from '@mantine/core';
import React from 'react';
import { useProxy } from 'valtio/utils';
import dynamic from 'next/dynamic';
import stateProfilePPID from '../../../_state/ppid/profile_ppid/profile_PPID';
// ini penting
const PPIDEditor = dynamic(() => import('../../_com/ppid_Editor').then(mod => mod.PPIDEditor), {
ssr: false, // disable server side rendering
});
function RiwayatKarir() {
const biodataState = useProxy(stateProfilePPID.profilePPID);
return (
<Box>
<Text fw={"bold"}>Riwayat Karir</Text>
<PPIDEditor
showSubmit={false}
onChange={(val) => {
biodataState.create.form.riwayat = val;
}}
/>
</Box>
);
}
export default RiwayatKarir;

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,30 @@
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";
import PermohonanInformasiPublik from "./permohonan_informasi_publik";
import PermohonanKeberatanInformasiPublik from "./permohonan_keberatan_informasi_publik";
import ProfilePPID from "./profile_ppid";
import VisiMisiPPID from "./visi_misi_ppid";
const PPID = new Elysia({ prefix: "/api/ppid", tags: ["PPID"] })
.use(ProfilePPID)
.use(DaftarInformasiPublik)
.use(GrafikHasilKepuasanMasyarakat)
.use(GrafikBerdasarkanJenisKelamin)
.use(GrafikBerdasarkanResponden)
.use(GrafikBerdasarkanUmur)
.use(PermohonanInformasiPublik)
.use(PermohonanKeberatanInformasiPublik)
.use(VisiMisiPPID)
export default PPID

View File

@@ -0,0 +1,40 @@
import prisma from "@/lib/prisma";
import { Prisma } from "@prisma/client";
import { Context } from "elysia";
type FormCreate = Prisma.PermohonanInformasiPublikGetPayload<{
select: {
name: true;
nik: true;
email: true;
notelp: true;
alamat: true;
jenisInformasiDimintaId: true;
caraMemperolehInformasiId: true;
caraMemperolehSalinanInformasiId: true;
}
}>
export default async function permohonanInformasiPublikCreate(context: Context) {
const body = context.body as FormCreate;
await prisma.permohonanInformasiPublik.create({
data: {
name: body.name,
nik: body.nik,
email: body.email,
notelp: body.notelp,
alamat: body.alamat,
jenisInformasiDimintaId: body.jenisInformasiDimintaId,
caraMemperolehInformasiId: body.caraMemperolehInformasiId,
}
})
return {
success: true,
message: "Permohonan Informasi Publik Berhasil Dibuat",
data: {
...body,
}
}
}

View File

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

View File

@@ -0,0 +1,29 @@
import Elysia, { t } from "elysia";
import permohonanInformasiPublikCreate from "./create";
import permohonanInformasiPublikFindMany from "./find-many";
import jenisInformasiFindMany from "./jenisInformasi";
import memperolehInformasiFindMany from "./memperolehInformasi";
import salinanInformasiFindMany from "./salinanInformasi";
const PermohonanInformasiPublik = new Elysia({
prefix: "/permohonaninformasipublik",
tags: ["PPID/Permohonan Informasi Publik"]
})
.get("/jenisInformasi/find-many", jenisInformasiFindMany)
.get("/memperolehInformasi/find-many", memperolehInformasiFindMany)
.get("/salinanInformasi/find-many", salinanInformasiFindMany)
.get("/find-many", permohonanInformasiPublikFindMany)
.post("/create", permohonanInformasiPublikCreate, {
body: t.Object({
name: t.String(),
nik: t.String(),
notelp: t.String(),
alamat: t.String(),
email: t.String(),
jenisInformasiDimintaId: t.Union([t.String(), t.Null()]),
caraMemperolehInformasiId: t.Union([t.String(), t.Null()]),
caraMemperolehSalinanInformasiId: t.Union([t.String(), t.Null()]),
}),
})
export default PermohonanInformasiPublik

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,33 @@
import prisma from "@/lib/prisma";
import { Prisma } from "@prisma/client";
import { Context } from "elysia";
type FormCreate = Prisma.FormulirPermohonanKeberatanGetPayload<{
select: {
name: true;
email: true;
notelp: true;
alasan: true;
}
}>
export default async function permohonanKeberatanInformasiPublikCreate(context: Context) {
const body = context.body as FormCreate;
await prisma.formulirPermohonanKeberatan.create({
data: {
name: body.name,
email: body.email,
notelp: body.notelp,
alasan: body.alasan,
}
})
return {
success: true,
message: "Permohonan Keberatan Informasi Publik Berhasil Dibuat",
data: {
...body,
}
}
}

View File

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

View File

@@ -0,0 +1,19 @@
import Elysia, { t } from "elysia";
import permohonanKeberatanInformasiPublikCreate from "./create";
import permohonanKeberatanInformasiPublikFindMany from "./find-many";
const PermohonanKeberatanInformasiPublik = new Elysia({
prefix: "/permohonankeberataninformasipublik",
tags: ["PPID/Permohonan Keberatan Informasi Publik"],
})
.get("/find-many", permohonanKeberatanInformasiPublikFindMany)
.post("/create", permohonanKeberatanInformasiPublikCreate, {
body: t.Object({
name: t.String(),
email: t.String(),
notelp: t.String(),
alasan: t.String(),
}),
})
export default PermohonanKeberatanInformasiPublik;

View File

@@ -0,0 +1,34 @@
import prisma from "@/lib/prisma";
import { Prisma } from "@prisma/client";
import { Context } from "elysia";
type FormCreate = Prisma.ProfilePPIDGetPayload<{
select: {
name: true;
biodata: true;
riwayat: true;
pengalaman: true;
unggulan: true;
}
}>
export default async function profilePPIDCreate(context: Context) {
const body = context.body as FormCreate;
await prisma.profilePPID.create({
data: {
name: body.name,
biodata: body.biodata,
riwayat: body.riwayat,
pengalaman: body.pengalaman,
unggulan: body.unggulan,
}
})
return {
success: true,
message: "Profile PPID Berhasil Dibuat",
data: {
...body,
}
}
}

View File

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

View File

@@ -0,0 +1,21 @@
import Elysia, { t } from "elysia";
import profilePPIDCreate from "./create";
import profilePPIDFindMany from "./find-many";
const ProfilePPID = new Elysia({
prefix: "/profileppid",
tags: ["PPID/Profile PPID"]
})
.get("/find-many", profilePPIDFindMany)
.post("/create", profilePPIDCreate, {
body: t.Object({
name: t.String(),
biodata: t.String(),
riwayat: t.String(),
pengalaman: t.String(),
unggulan: t.String(),
})
})
export default ProfilePPID;

View File

@@ -0,0 +1,7 @@
import Elysia from "elysia";
import MisiPPID from "./misi_ppid";
const VisiMisiPPID = new Elysia({ prefix: "/visimisippid", tags: ["PPID/Visi Misi PPID"] })
.use(MisiPPID)
export default VisiMisiPPID

View File

@@ -0,0 +1,27 @@
import prisma from "@/lib/prisma";
import { Prisma } from "@prisma/client";
import { Context } from "elysia";
type FormCreate = Prisma.MisiPPIDGetPayload<{
select: {
content: true;
}
}>
export default async function misiPPIDCreate(context: Context) {
const body = context.body as FormCreate;
await prisma.misiPPID.create({
data: {
content: body.content,
}
})
return {
success: true,
message: "Misi PPID Berhasil Dibuat",
data: {
...body,
}
}
}

View File

@@ -0,0 +1,17 @@
import prisma from "@/lib/prisma";
import { Context } from "elysia";
export async function misiPPIDDelete(context: Context) {
const id = context.params.id
await prisma.misiPPID.delete({
where: {
id: id
}
})
return {
success: true,
message: "Misi PPID Berhasil Dihapus",
}
}

View File

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

View File

@@ -0,0 +1,24 @@
import Elysia, { t } from "elysia";
import { misiPPIDFindMany } from "./find-many";
import misiPPIDCreate from "./create";
import { misiPPIDDelete } from "./delete";
import misiPPIDUpdate from "./update";
const MisiPPID = new Elysia({
prefix: "/misippid",
tags: ["PPID/Visi Misi PPID/ Misi PPID"],
})
.get("/find-many", misiPPIDFindMany)
.post("/create", misiPPIDCreate, {
body: t.Object({
content: t.String(),
})
})
.put("/update/:id", misiPPIDUpdate, {
body: t.Object({
content: t.String(),
})
})
.delete("/delete/:id", misiPPIDDelete)
export default MisiPPID;

View File

@@ -0,0 +1,29 @@
import prisma from "@/lib/prisma";
import { Prisma } from "@prisma/client";
import { Context } from "elysia";
type FormUpdate = Prisma.MisiPPIDGetPayload<{
select: {
content: true;
}
}>
export default async function misiPPIDUpdate(context: Context) {
const id = context.params.id
const body = context.body as FormUpdate;
await prisma.misiPPID.update({
where: { id: id },
data: {
content: body.content,
}
})
return {
success: true,
message: "Misi PPID Berhasil Diupdate",
data: {
...body,
}
}
}

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

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

View File

@@ -1,5 +1,7 @@
import { AppServer } from '@/app/api/[[...slugs]]/route'
import { treaty } from '@elysiajs/eden'
const ApiFetch = treaty<AppServer>(process.env.NEXT_PUBLIC_WIBU_URL || 'localhost:3000')
const BASE_URL = process.env.NEXT_PUBLIC_BASE_URL || 'localhost:3000'
const ApiFetch = treaty<AppServer>(BASE_URL)
export default ApiFetch