Compare commits

...

2 Commits

22 changed files with 981 additions and 572 deletions

View File

@@ -223,7 +223,7 @@ model KategoriPrestasiDesa {
model Responden {
id String @id @default(cuid())
name String @unique
tanggal DateTime // misal: 2025-05-01
tanggal String // misal: 2025-05-01
jenisKelamin JenisKelaminResponden @relation(fields: [jenisKelaminId], references: [id])
jenisKelaminId String
rating PilihanRatingResponden @relation(fields: [ratingId], references: [id])

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
@@ -53,15 +54,39 @@ const keamananLingkunganState = proxy({
findMany: {
data: null as
| Prisma.KeamananLingkunganGetPayload<{
include: { image: true };
include: {
image: true;
};
}>[]
| null,
async load() {
const res = await ApiFetch.api.keamanan.keamananlingkungan[
"find-many"
].get();
if (res.status === 200) {
keamananLingkunganState.findMany.data = res.data?.data ?? [];
page: 1,
totalPages: 1,
loading: false,
search: "",
load: async (page = 1, limit = 10, search = "") => {
keamananLingkunganState.findMany.loading = true; // ✅ Akses langsung via nama path
keamananLingkunganState.findMany.page = page;
keamananLingkunganState.findMany.search = search;
try {
const query: any = { page, limit };
if (search) query.search = search;
const res = await ApiFetch.api.keamanan.keamananlingkungan["find-many"].get({ query });
if (res.status === 200 && res.data?.success) {
keamananLingkunganState.findMany.data = res.data.data ?? [];
keamananLingkunganState.findMany.totalPages = res.data.totalPages ?? 1;
} else {
keamananLingkunganState.findMany.data = [];
keamananLingkunganState.findMany.totalPages = 1;
}
} catch (err) {
console.error("Gagal fetch keamanan lingkungan paginated:", err);
keamananLingkunganState.findMany.data = [];
keamananLingkunganState.findMany.totalPages = 1;
} finally {
keamananLingkunganState.findMany.loading = false;
}
},
},

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
@@ -63,13 +64,41 @@ const polsekTerdekatState = proxy({
findMany: {
data: null as
| Prisma.PolsekTerdekatGetPayload<{
include: { layananPolsek: true };
include: {
layananPolsek: true;
};
}>[]
| null,
async load() {
const res = await ApiFetch.api.keamanan.polsekterdekat["find-many"].get();
if (res.status === 200) {
polsekTerdekatState.findMany.data = res.data?.data ?? [];
page: 1,
totalPages: 1,
loading: false,
search: "",
load: async (page = 1, limit = 10, search = "") => {
polsekTerdekatState.findMany.loading = true; // ✅ Akses langsung via nama path
polsekTerdekatState.findMany.page = page;
polsekTerdekatState.findMany.search = search;
try {
const query: any = { page, limit };
if (search) query.search = search;
const res = await ApiFetch.api.keamanan.polsekterdekat["find-many"].get(
{ query }
);
if (res.status === 200 && res.data?.success) {
polsekTerdekatState.findMany.data = res.data.data ?? [];
polsekTerdekatState.findMany.totalPages = res.data.totalPages ?? 1;
} else {
polsekTerdekatState.findMany.data = [];
polsekTerdekatState.findMany.totalPages = 1;
}
} catch (err) {
console.error("Gagal fetch polsek terdekat paginated:", err);
polsekTerdekatState.findMany.data = [];
polsekTerdekatState.findMany.totalPages = 1;
} finally {
polsekTerdekatState.findMany.loading = false;
}
},
},
@@ -237,6 +266,29 @@ const polsekTerdekatState = proxy({
polsekTerdekatState.edit.form = { ...defaultForm };
},
},
findFirst: {
data: null as Prisma.PolsekTerdekatGetPayload<{
include: {
layananPolsek: true;
};
}> | null,
loading: false,
load: async () => { // Changed to arrow function
polsekTerdekatState.findFirst.loading = true;
try {
const res = await ApiFetch.api.keamanan.polsekterdekat["find-first"].get();
if (res.status === 200 && res.data?.success) {
polsekTerdekatState.findFirst.data = res.data.data || null;
} else {
polsekTerdekatState.findFirst.data = null;
}
} catch (err) {
console.error("Gagal fetch polsek terdekat terbaru:", err);
} finally {
polsekTerdekatState.findFirst.loading = false;
}
}
}
});
export default polsekTerdekatState;

View File

@@ -181,7 +181,13 @@ const responden = proxy({
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(this.form),
body: JSON.stringify({
name: this.form.name,
tanggal: this.form.tanggal,
jenisKelaminId: this.form.jenisKelaminId,
ratingId: this.form.ratingId,
kelompokUmurId: this.form.kelompokUmurId,
}),
});
const result = await response.json();
if (!response.ok || !result?.success) {

View File

@@ -49,35 +49,38 @@ const daftarInformasiPublik = proxy({
},
},
findMany: {
data: null as any[] | null,
data: null as
| Prisma.DaftarInformasiPublikGetPayload<{
omit: {
isActive: true;
};
}>[]
| null,
page: 1,
totalPages: 1,
total: 0,
loading: false,
load: async (page = 1, limit = 10) => { // Change to arrow function
daftarInformasiPublik.findMany.loading = true; // Use the full path to access the property
search: "",
load: async (page = 1, limit = 10, search = "") => {
daftarInformasiPublik.findMany.loading = true; // ✅ Akses langsung via nama path
daftarInformasiPublik.findMany.page = page;
try {
const res = await ApiFetch.api.ppid.daftarinformasipublik[
"find-many"
].get({
query: { page, limit },
});
daftarInformasiPublik.findMany.search = search;
try {
const query: any = { page, limit };
if (search) query.search = search;
const res = await ApiFetch.api.ppid.daftarinformasipublik["find-many"].get({ query });
if (res.status === 200 && res.data?.success) {
daftarInformasiPublik.findMany.data = res.data.data || [];
daftarInformasiPublik.findMany.total = res.data.total || 0;
daftarInformasiPublik.findMany.totalPages = res.data.totalPages || 1;
daftarInformasiPublik.findMany.data = res.data.data ?? [];
daftarInformasiPublik.findMany.totalPages = res.data.totalPages ?? 1;
} else {
console.error("Failed to load daftar informasi publik:", res.data?.message);
daftarInformasiPublik.findMany.data = [];
daftarInformasiPublik.findMany.total = 0;
daftarInformasiPublik.findMany.totalPages = 1;
}
} catch (error) {
console.error("Error loading daftar informasi publik:", error);
} catch (err) {
console.error("Gagal fetch daftar informasi publik paginated:", err);
daftarInformasiPublik.findMany.data = [];
daftarInformasiPublik.findMany.total = 0;
daftarInformasiPublik.findMany.totalPages = 1;
} finally {
daftarInformasiPublik.findMany.loading = false;

View File

@@ -54,6 +54,10 @@ function DetailKeamananLingkungan() {
{keamananState.findUnique.data ? (
<Paper key={keamananState.findUnique.data.id} bg={colors['BG-trans']} p={'md'}>
<Stack gap={"xs"}>
<Box>
<Text fw={"bold"} fz={"lg"}>Gambar</Text>
<Image w={{ base: 150, md: 490}} src={keamananState.findUnique.data?.image?.link} alt="gambar" />
</Box>
<Box>
<Text fw={"bold"} fz={"lg"}>Judul Keamanan Lingkungan</Text>
<Text fz={"lg"}>{keamananState.findUnique.data?.name}</Text>
@@ -62,10 +66,6 @@ function DetailKeamananLingkungan() {
<Text fw={"bold"} fz={"lg"}>Deskripsi</Text>
<Text fz={"lg"} dangerouslySetInnerHTML={{ __html: keamananState.findUnique.data?.deskripsi }} />
</Box>
<Box>
<Text fw={"bold"} fz={"lg"}>Gambar</Text>
<Image w={{ base: 150, md: 150, lg: 150 }} src={keamananState.findUnique.data?.image?.link} alt="gambar" />
</Box>
<Flex gap={"xs"} mt={10}>
<Button
onClick={() => {

View File

@@ -1,6 +1,6 @@
'use client'
import colors from '@/con/colors';
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
import HeaderSearch from '../../_com/header';
import JudulList from '../../_com/judulList';
@@ -30,19 +30,21 @@ function ListKeamananLingkungan({ search }: { search: string }) {
const keamananState = useProxy(keamananLingkunganState)
const router = useRouter();
const {
data,
page,
totalPages,
loading,
load,
} = keamananState.findMany;
useShallowEffect(() => {
keamananState.findMany.load()
}, [])
load(page, 10, search)
}, [page, search])
const filteredData = (keamananState.findMany.data || []).filter(item => {
const keyword = search.toLowerCase();
return (
item.name.toLowerCase().includes(keyword) ||
item.deskripsi.toLowerCase().includes(keyword)
);
});
const filteredData = data || []
if (!keamananState.findMany.data) {
if (loading || !data) {
return (
<Stack py={10}>
<Skeleton h={500} />
@@ -67,9 +69,15 @@ function ListKeamananLingkungan({ search }: { search: string }) {
<TableTbody>
{filteredData.map((item) => (
<TableTr key={item.id}>
<TableTd>{item.name}</TableTd>
<TableTd>
<Text fz={"md"} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
<Box w={180}>
<Text fz={"md"} truncate={"end"} lineClamp={1}>{item.name}</Text>
</Box>
</TableTd>
<TableTd>
<Box w={250}>
<Text fz={"md"} truncate={"end"} lineClamp={1} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
</Box>
</TableTd>
<TableTd>
<Button onClick={() => router.push(`/admin/keamanan/keamanan-lingkungan-pecalang-patwal/${item.id}`)}>
@@ -81,6 +89,14 @@ function ListKeamananLingkungan({ search }: { search: string }) {
</TableTbody>
</Table>
</Paper>
<Center>
<Pagination
value={page}
onChange={(newPage) => load(newPage)}
total={totalPages}
my={"md"}
/>
</Center>
</Box>
);
}

View File

@@ -1,6 +1,6 @@
'use client'
import colors from '@/con/colors';
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } from '@mantine/core';
import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
@@ -21,7 +21,7 @@ function PolsekTerdekat() {
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
/>
<ListPolsekTerdekat search={search}/>
<ListPolsekTerdekat search={search} />
</Box>
);
}
@@ -30,20 +30,21 @@ function ListPolsekTerdekat({ search }: { search: string }) {
const polsekState = useProxy(polsekTerdekat)
const router = useRouter();
const {
data,
page,
totalPages,
loading,
load,
} = polsekState.findMany;
useShallowEffect(() => {
polsekState.findMany.load()
}, [])
load(page, 10, search)
}, [page, search])
const filteredData = (polsekState.findMany.data || []).filter(item => {
const keyword = search.toLowerCase();
return (
item.nama.toLowerCase().includes(keyword) ||
item.jarakKeDesa.toLowerCase().includes(keyword) ||
item.alamat.toLowerCase().includes(keyword)
);
});
const filteredData = data || []
if (!polsekState.findMany.data) {
if (loading || !data) {
return (
<Stack py={10}>
<Skeleton h={500} />
@@ -64,24 +65,44 @@ function ListPolsekTerdekat({ search }: { search: string }) {
<TableTh>Jarak Polsek</TableTh>
<TableTh>Alamat</TableTh>
<TableTh>Detail</TableTh>
</TableTr>
</TableTr>
</TableThead>
<TableTbody>
{filteredData.map((item) => (
<TableTr key={item.id}>
<TableTd>{item.nama}</TableTd>
<TableTd>{item.jarakKeDesa}</TableTd>
<TableTd>{item.alamat}</TableTd>
<TableTd>
<Box w={180}>
<Text fz='md' truncate={"end"} lineClamp={1}>{item.nama}</Text>
</Box>
</TableTd>
<TableTd>
<Box w={180}>
<Text fz='md' truncate={"end"} lineClamp={1}>{item.jarakKeDesa}</Text>
</Box>
</TableTd>
<TableTd>
<Box w={250}>
<Text fz='md' truncate={"end"} lineClamp={1}>{item.alamat}</Text>
</Box>
</TableTd>
<TableTd>
<Button onClick={() => router.push(`/admin/keamanan/polsek-terdekat/${item.id}`)}>
<IconDeviceImac size={20} />
</Button>
</TableTd>
</TableTr>
))}
<IconDeviceImac size={20} />
</Button>
</TableTd>
</TableTr>
))}
</TableTbody>
</Table>
</Table>
</Paper>
<Center>
<Pagination
value={page}
onChange={(newPage) => load(newPage)}
total={totalPages}
my="md"
/>
</Center>
</Box>
);
}

View File

@@ -106,8 +106,16 @@ function ListDaftarInformasi({ search }: { search: string }) {
{filteredData.map((item, index) => (
<TableTr key={item.id}>
<TableTd style={{ textAlign: 'center' }}>{index + 1}</TableTd>
<TableTd style={{ wordWrap: 'break-word' }}>{item.jenisInformasi}</TableTd>
<TableTd style={{ wordWrap: 'break-word' }} dangerouslySetInnerHTML={{ __html: item.deskripsi }}></TableTd>
<TableTd style={{ wordWrap: 'break-word' }}>
<Box w={200}>
<Text fz={"md"} truncate={"end"} lineClamp={1}>{item.jenisInformasi}</Text>
</Box>
</TableTd>
<TableTd style={{ wordWrap: 'break-word' }}>
<Box w={200}>
<Text fz={"md"} truncate={"end"} lineClamp={1} dangerouslySetInnerHTML={{ __html: item.deskripsi }}></Text>
</Box>
</TableTd>
<TableTd style={{ textAlign: 'center' }}>
<Button bg={"green"} onClick={() => router.push(`/admin/ppid/daftar-informasi-publik-desa-darmasaba/${item.id}`)}>
<IconDeviceImacCog size={25} />

View File

@@ -41,15 +41,11 @@ function EditResponden() {
try {
const data = await state.update.load(id);
if (data) {
const formattedDate = data.tanggal && !isNaN(new Date(data.tanggal).getTime())
? new Date(data.tanggal).toISOString().split('T')[0]
: '';
// ⬇️ FIX PENTING: tambahkan ini
state.update.id = id;
state.update.form = {
name: data.name,
tanggal: formattedDate,
tanggal: data.tanggal,
jenisKelaminId: data.jenisKelaminId,
ratingId: data.ratingId,
kelompokUmurId: data.kelompokUmurId,
@@ -76,6 +72,7 @@ function EditResponden() {
const handleSubmit = async () => {
state.update.id = id;
state.update.form = { ...formData }; // <-- sinkronisasi manual
await state.update.submit();
router.push('/admin/ppid/ikm-desa-darmasaba/responden')
}
@@ -103,12 +100,15 @@ function EditResponden() {
}}
/>
<TextInput
label="Tanggal"
type="date"
value={formData.tanggal}
placeholder='Pilih tanggal'
value={formData.tanggal ? new Date(formData.tanggal).toISOString().split('T')[0] : ''}
onChange={(e) => {
const selectedDate = e.currentTarget.value;
setFormData({
...formData,
tanggal: e.currentTarget.value, // ✅ sudah format YYYY-MM-DD
tanggal: selectedDate,
});
}}
/>

View File

@@ -1,24 +1,57 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
// /api/berita/findManyPaginated.ts
import prisma from "@/lib/prisma";
import { Context } from "elysia";
export default async function keamananLingkunganFindMany() {
try {
const data = await prisma.keamananLingkungan.findMany({
where: { isActive: true },
include: {
image: true,
},
});
async function keamananLingkunganFindMany(context: Context) {
// Ambil parameter dari query
const page = Number(context.query.page) || 1;
const limit = Number(context.query.limit) || 10;
const search = (context.query.search as string) || '';
const skip = (page - 1) * limit;
return {
success: true,
message: "Success fetch keamanan lingkungan",
data,
};
} catch (e) {
console.error("Find many error:", e);
return {
success: false,
message: "Failed fetch keamanan lingkungan",
};
}
}
// Buat where clause
const where: any = { isActive: true };
// Tambahkan pencarian (jika ada)
if (search) {
where.OR = [
{ name: { contains: search, mode: 'insensitive' } },
{ deskripsi: { contains: search, mode: 'insensitive' } },
];
}
try {
// Ambil data dan total count secara paralel
const [data, total] = await Promise.all([
prisma.keamananLingkungan.findMany({
where,
include: {
image: true,
},
skip,
take: limit,
orderBy: { createdAt: 'desc' },
}),
prisma.keamananLingkungan.count({ where }),
]);
return {
success: true,
message: "Berhasil ambil keamanan lingkungan dengan pagination",
data,
page,
limit,
total,
totalPages: Math.ceil(total / limit),
};
} catch (e) {
console.error("Error di findMany paginated:", e);
return {
success: false,
message: "Gagal mengambil data keamanan lingkungan",
};
}
}
export default keamananLingkunganFindMany;

View File

@@ -0,0 +1,31 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
// find-first.ts
import prisma from "@/lib/prisma";
async function polsekTerdekatFindFirst() {
const where: any = { isActive: true };
try {
const data = await prisma.polsekTerdekat.findFirst({
where,
include: {
layananPolsek: true,
},
orderBy: { createdAt: 'desc' },
});
return {
success: true,
message: "Berhasil ambil polsek terdekat terbaru",
data,
};
} catch (e) {
console.error("Error di findFirst:", e);
return {
success: false,
message: "Gagal ambil polsek terdekat terbaru",
};
}
}
export default polsekTerdekatFindFirst;

View File

@@ -1,24 +1,57 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
// /api/berita/findManyPaginated.ts
import prisma from "@/lib/prisma";
import { Context } from "elysia";
export default async function polsekTerdekatFindMany() {
try {
const data = await prisma.polsekTerdekat.findMany({
where: { isActive: true },
include: {
layananPolsek: true,
},
});
async function polsekTerdekatFindMany(context: Context) {
// Ambil parameter dari query
const page = Number(context.query.page) || 1;
const limit = Number(context.query.limit) || 10;
const search = (context.query.search as string) || '';
const skip = (page - 1) * limit;
return {
success: true,
message: "Success fetch polsek terdekat",
data,
};
} catch (e) {
console.error("Find many error:", e);
return {
success: false,
message: "Failed fetch polsek terdekat",
};
}
}
// Buat where clause
const where: any = { isActive: true };
// Tambahkan pencarian (jika ada)
if (search) {
where.OR = [
{ nama: { contains: search, mode: 'insensitive' } },
{ alamat: { contains: search, mode: 'insensitive' } },
];
}
try {
// Ambil data dan total count secara paralel
const [data, total] = await Promise.all([
prisma.polsekTerdekat.findMany({
where,
include: {
layananPolsek: true,
},
skip,
take: limit,
orderBy: { createdAt: 'desc' },
}),
prisma.polsekTerdekat.count({ where }),
]);
return {
success: true,
message: "Berhasil ambil polsek terdekat dengan pagination",
data,
page,
limit,
total,
totalPages: Math.ceil(total / limit),
};
} catch (e) {
console.error("Error di findMany paginated:", e);
return {
success: false,
message: "Gagal mengambil data polsek terdekat",
};
}
}
export default polsekTerdekatFindMany;

View File

@@ -4,6 +4,7 @@ import polsekTerdekatDelete from "./del";
import polsekTerdekatFindMany from "./findMany";
import polsekTerdekatFindUnique from "./findUnique";
import polsekTerdekatUpdate from "./updt";
import polsekTerdekatFindFirst from "./findFirst";
const PolsekTerdekat = new Elysia({ prefix: "/polsekterdekat", tags: ["Keamanan/Polsek Terdekat"] })
@@ -12,6 +13,7 @@ const PolsekTerdekat = new Elysia({ prefix: "/polsekterdekat", tags: ["Keamanan/
const response = await polsekTerdekatFindUnique(new Request(context.request));
return response;
})
.get("/find-first", polsekTerdekatFindFirst)
.post("/create", polsekTerdekatCreate, {
body: t.Object({
nama: t.String(),

View File

@@ -15,16 +15,12 @@ export default async function respondenCreate(context: Context) {
try {
// Convert the date string to a Date object
const tanggal = new Date(body.tanggal);
// Validate the date
if (isNaN(tanggal.getTime())) {
throw new Error('Tanggal tidak valid');
}
const result = await prisma.responden.create({
data: {
name: body.name,
tanggal: tanggal, // Use the Date object
tanggal: tanggal.toISOString(), // Use the Date object
jenisKelaminId: body.jenisKelaminId,
ratingId: body.ratingId,
kelompokUmurId: body.kelompokUmurId,

View File

@@ -2,35 +2,40 @@ import prisma from "@/lib/prisma";
import { Context } from "elysia";
type FormUpdate = {
name: string;
tanggal: string;
jenisKelaminId: string;
ratingId: string;
kelompokUmurId: string;
}
name: string;
tanggal: string;
jenisKelaminId: string;
ratingId: string;
kelompokUmurId: string;
};
export default async function respondenUpdate(context: Context) {
const body = (await context.body) as FormUpdate;
const id = context.params.id as string;
const body = (await context.body) as FormUpdate;
const id = context.params.id as string;
try {
const result = await prisma.responden.update({
where: { id },
data: {
name: body.name,
tanggal: body.tanggal,
jenisKelaminId: body.jenisKelaminId,
ratingId: body.ratingId,
kelompokUmurId: body.kelompokUmurId,
},
});
return {
success: true,
message: "Berhasil mengupdate responden",
data: result,
};
} catch (error) {
console.error("Error updating responden:", error);
throw new Error("Gagal mengupdate responden: " + (error as Error).message);
}
try {
const result = await prisma.responden.update({
where: { id },
data: {
name: body.name,
tanggal: new Date(body.tanggal).toISOString(),
jenisKelaminId: body.jenisKelaminId,
ratingId: body.ratingId,
kelompokUmurId: body.kelompokUmurId,
},
});
return {
success: true,
message: "Berhasil mengupdate responden",
data: result,
};
} catch (error) {
console.error("Error updating responden:", error);
// Instead of throwing an error, return a proper JSON response
return {
success: false,
message: "Gagal mengupdate responden: " + (error as Error).message,
data: null,
};
}
}

View File

@@ -1,44 +1,54 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
// /api/berita/findManyPaginated.ts
import prisma from "@/lib/prisma";
import { Context } from "elysia";
// Di findMany.ts
export default async function daftarInformasiPublikFindMany(context: Context) {
async function daftarInformasiPublikFindMany(context: Context) {
// Ambil parameter dari query
const page = Number(context.query.page) || 1;
const limit = Number(context.query.limit) || 10;
const search = (context.query.search as string) || '';
const skip = (page - 1) * limit;
// Buat where clause
const where: any = { isActive: true };
// Tambahkan pencarian (jika ada)
if (search) {
where.OR = [
{ jenisInformasi: { contains: search, mode: 'insensitive' } },
{ deskripsi: { contains: search, mode: 'insensitive' } },
];
}
try {
// Ambil data dan total count secara paralel
const [data, total] = await Promise.all([
prisma.daftarInformasiPublik.findMany({
where: { isActive: true },
where,
skip,
take: limit,
orderBy: { createdAt: 'desc' },
}),
prisma.daftarInformasiPublik.count({
where: { isActive: true }
})
prisma.daftarInformasiPublik.count({ where }),
]);
const totalPages = Math.ceil(total / limit);
return {
success: true,
message: "Success fetch daftar informasi publik with pagination",
message: "Berhasil ambil daftar informasi publik dengan pagination",
data,
page,
totalPages,
limit,
total,
totalPages: Math.ceil(total / limit),
};
} catch (e) {
console.error("Find many paginated error:", e);
console.error("Error di findMany paginated:", e);
return {
success: false,
message: "Failed fetch daftar informasi publik with pagination",
data: [],
page: 1,
totalPages: 1,
total: 0,
message: "Gagal mengambil data daftar informasi publik",
};
}
}
}
export default daftarInformasiPublikFindMany;

View File

@@ -1,67 +1,61 @@
'use client'
import keamananLingkunganState from '@/app/admin/(dashboard)/_state/keamanan/keamanan-lingkungan';
import colors from '@/con/colors';
import { Box, Center, Image, List, ListItem, Paper, SimpleGrid, Stack, Text } from '@mantine/core';
import { Box, Center, Grid, GridCol, Image, Pagination, Paper, SimpleGrid, Skeleton, Stack, Text, TextInput } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconSearch } from '@tabler/icons-react';
import { useState } from 'react';
import { useProxy } from 'valtio/utils';
import BackButton from '../../desa/layanan/_com/BackButto';
const data1 = [
{
id: 1,
judul: 'Peran Pecalang dalam Keamanan Desa',
image: '/api/img/pecalang.png',
pengertian: 'Pecalang adalah petugas keamanan adat di Bali yang memiliki peran penting dalam menjaga ketertiban dan budaya lokal. Tugas mereka meliputi:',
deskripsi: <List>
<ListItem fz={{ base: 'h4', md: 'lg' }}>Mengamankan upacara adat dan kegiatan keagamaan.</ListItem>
<ListItem fz={{ base: 'h4', md: 'lg' }}>Mengatur lalu lintas saat ada perayaan atau kegiatan besar.</ListItem>
<ListItem fz={{ base: 'h4', md: 'lg' }}>Berpatroli untuk mencegah gangguan keamanan di lingkungan desa.</ListItem>
<ListItem fz={{ base: 'h4', md: 'lg' }}>Berkoordinasi dengan aparat desa dan kepolisian dalam penanganan situasi darurat.</ListItem>
</List>
},
{
id: 2,
judul: 'Patwal (Patroli Pengawal) Desa',
image: '/api/img/patwal-1.png',
pengertian: 'Selain Pecalang, Desa Darmasaba juga memiliki Patwal yang bertugas menjaga keamanan sehari-hari. Peran mereka antara lain:',
deskripsi: <List>
<ListItem fz={{ base: 'h4', md: 'lg' }}>Berpatroli secara rutin untuk memastikan lingkungan tetap aman.</ListItem>
<ListItem fz={{ base: 'h4', md: 'lg' }}>Menjaga ketertiban lalu lintas di area desa.</ListItem>
<ListItem fz={{ base: 'h4', md: 'lg' }}>Melakukan tindakan preventif terhadap potensi gangguan keamanan.</ListItem>
<ListItem fz={{ base: 'h4', md: 'lg' }}>Siap siaga dalam keadaan darurat untuk membantu warga.</ListItem>
</List>
},
{
id: 3,
judul: 'Layanan Keamanan yang Tersedia',
image: '/api/img/pospecalang.png',
pengertian: 'Jika terjadi keadaan darurat atau membutuhkan bantuan keamanan, warga dapat menghubungi:',
deskripsi: <List>
<ListItem fz={{ base: 'h4', md: 'lg' }}>Pos Pecalang Desa: [Masukkan Nomor Kontak].</ListItem>
<ListItem fz={{ base: 'h4', md: 'lg' }}>Patwal Desa Darmasaba: [Masukkan Nomor Kontak].</ListItem>
<ListItem fz={{ base: 'h4', md: 'lg' }}>Polsek Terdekat: 110 (Layanan Kepolisian).</ListItem>
</List>
},
{
id: 4,
judul: 'Program Keamanan Desa',
image: '/api/img/rond.png',
pengertian: 'Untuk meningkatkan keamanan, Desa Darmasaba menjalankan berbagai program, seperti:',
deskripsi: <List>
<ListItem fz={{ base: 'h4', md: 'lg' }}>Ronda Malam Warga: Kegiatan jaga malam secara bergilir oleh warga bersama Pecalang dan Patwal.</ListItem>
<ListItem fz={{ base: 'h4', md: 'lg' }}>Sosialisasi Keamanan: Edukasi bagi warga tentang cara menjaga keamanan lingkungan.</ListItem>
<ListItem fz={{ base: 'h4', md: 'lg' }}> Pengawasan Kamera CCTV: Memantau titik- titik strategis untuk mencegah tindak kejahatan.</ListItem>
</List>
}
]
function Page() {
const state = useProxy(keamananLingkunganState)
const [search, setSearch] = useState('')
const {
data,
page,
totalPages,
loading,
load,
} = state.findMany;
useShallowEffect(() => {
load(page, 3, search)
}, [page, search])
if (loading || !data) {
return (
<Box py={10}>
<Skeleton h={500} />
</Box>
)
}
return (
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
<Box px={{ base: 'md', md: 100 }}>
<BackButton />
</Box>
<Box>
<Text ta={"center"} fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
Keamanan Lingkungan (Pecalang / Patwal)
</Text>
<Text px={{ base: 20, md: 150 }} ta={"center"} fz={{ base: "h4", md: "h3" }} >
<Grid align='center' px={{ base: 'md', md: 100 }}>
<GridCol span={{ base: 12, md: 9 }}>
<Text fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
Keamanan Lingkungan (Pecalang / Patwal)
</Text>
</GridCol>
<GridCol span={{ base: 12, md: 3 }}>
<TextInput
radius={"lg"}
placeholder='Cari Puskesmas'
value={search}
onChange={(e) => setSearch(e.target.value)}
leftSection={<IconSearch size={20} />}
w={{ base: "50%", md: "100%" }}
/>
</GridCol>
</Grid>
<Text px={{ base: 'md', md: 100 }} ta={"justify"} fz={{ base: "h4", md: "h3" }} >
Keamanan dan ketertiban lingkungan di Desa Darmasaba dijaga melalui peran aktif Pecalang dan Patwal (Patroli Pengawal). Mereka bertugas memastikan desa tetap aman, tertib, dan kondusif bagi seluruh warga.
</Text>
</Box>
@@ -73,24 +67,19 @@ function Page() {
base: 1,
md: 3,
}}>
{data1.map((v, k) => {
{data.map((v, k) => {
return (
<Paper radius={10} key={k} bg={colors["white-trans-1"]}>
<Stack gap={'xs'}>
<Center px={10} py={20}>
<Image src={v.image} alt='' />
<Image src={v.image?.link} alt='' />
</Center>
<Box px={'lg'}>
<Box pb={20}>
<Text pb={10} c={colors["blue-button"]} fw={"bold"} fz={"h3"}>
{v.judul}
{v.name}
</Text>
<Text pb={10} fz={"h4"} ta={'justify'}>
{v.pengertian}
</Text>
<Box px={10}>
{v.deskripsi}
</Box>
<Text pb={10} fz={"h4"} ta={'justify'} dangerouslySetInnerHTML={{ __html: v.deskripsi }} />
</Box>
</Box>
</Stack>
@@ -100,6 +89,14 @@ function Page() {
</SimpleGrid>
</Stack>
</Box>
<Center>
<Pagination
value={page}
onChange={(newPage) => load(newPage)} // ini penting!
total={totalPages}
my="md"
/>
</Center>
</Stack>
);
}

View File

@@ -1,10 +1,30 @@
'use client'
/* eslint-disable react-hooks/exhaustive-deps */
import polsekTerdekatState from '@/app/admin/(dashboard)/_state/keamanan/polsek-terdekat';
import colors from '@/con/colors';
import { Badge, Box, Button, Flex, Paper, SimpleGrid, Stack, Text, TextInput } from '@mantine/core';
import { IconArrowDown, IconClock, IconNavigation, IconPhone, IconPin, IconSearch } from '@tabler/icons-react';
import { Badge, Box, Button, Center, Flex, Paper, SimpleGrid, Skeleton, Stack, Text } from '@mantine/core';
import { IconArrowDown, IconClock, IconNavigation, IconPhone, IconPin } from '@tabler/icons-react';
import { useEffect } from 'react';
import { useProxy } from 'valtio/utils';
import BackButton from '../../desa/layanan/_com/BackButto';
import { useRouter } from 'next/navigation';
function Page() {
const state = useProxy(polsekTerdekatState.findFirst);
const router = useRouter()
const {
data,
loading,
load,
} = state;
useEffect(() => {
if (!data && !loading) {
load();
}
}, [data, loading]);
return (
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
<Box px={{ base: 'md', md: 100 }}>
@@ -17,7 +37,6 @@ function Page() {
<Text pb={15} fz={'h4'} >
Desa Darmasaba, Kecamatan Abiansemal, Kabupaten Badung
</Text>
<TextInput radius={10} leftSection={<IconSearch size={20} />} placeholder='Cari Kantor Polisi' />
</Box>
<Box px={{ base: "md", md: 100 }}>
<Stack gap={'lg'}>
@@ -30,59 +49,56 @@ function Page() {
}}
>
{/* Content Sebelah Kiri */}
<Box>
<Text c={colors["blue-button"]} fw={'bold'} fz={'h2'}>Polsek Abiansemal</Text>
<Text c={colors["blue-button"]} fz={'sm'}>2,5 Km dari Desa Darmasaba</Text>
<Flex py={10} gap={9} align={'center'}>
<IconPin size={25} color={colors["blue-button"]} />
<Text c={colors["blue-button"]} fz={'lg'}>Jl. Gandamayu 1 Blahkiuh</Text>
</Flex>
<Flex gap={9} align={'center'}>
<IconPhone size={25} color={colors["blue-button"]} />
<Text c={colors["blue-button"]} fz={'lg'}>08xxxxxxxx</Text>
</Flex>
<Flex py={10} gap={9} align={'center'}>
<IconClock size={25} color={colors["blue-button"]} />
<Text c={colors["blue-button"]} fz={'lg'}>24 Jam</Text>
</Flex>
<Box>
<Text c={colors["blue-button"]} fw={'bold'} fz={'h2'}>Layanan Yang Tersedia :</Text>
<SimpleGrid
py={10}
cols={{
base: 1,
md: 2,
}}
>
{loading ? (
<Center><Skeleton h={400} /></Center>
) : data ? (
<>
<Box>
<Text c={colors["blue-button"]} fw={'bold'} fz={'h2'}>{data.nama}</Text>
<Text c={colors["blue-button"]} fz={'sm'}>{data.jarakKeDesa}</Text>
<Flex py={10} gap={9} align={'center'}>
<IconPin size={25} color={colors["blue-button"]} />
<Text c={colors["blue-button"]} fz={'lg'}>{data.alamat}</Text>
</Flex>
<Flex gap={9} align={'center'}>
<IconPhone size={25} color={colors["blue-button"]} />
<Text c={colors["blue-button"]} fz={'lg'}>{data.nomorTelepon}</Text>
</Flex>
<Flex py={10} gap={9} align={'center'}>
<IconClock size={25} color={colors["blue-button"]} />
<Text c={colors["blue-button"]} fz={'lg'}>{data.jamOperasional}</Text>
</Flex>
<Box>
<Text c={colors["blue-button"]} fz={'lg'}>Laporan Kehilangan</Text>
<Text c={colors["blue-button"]} fw={'bold'} fz={'h2'}>Layanan Yang Tersedia :</Text>
<SimpleGrid
py={10}
cols={{
base: 1,
md: 2,
}}
>
<Box>
<Text c={colors["blue-button"]} fz={'lg'}>{data.layananPolsek.nama}</Text>
</Box>
</SimpleGrid>
</Box>
<Box>
<Text c={colors["blue-button"]} fz={'lg'}>Laporan Kriminal</Text>
<Button bg={colors["blue-button"]} onClick={() => router.push(`/darmasaba/keamanan/polsek-terdekat/semua-polsek`)} rightSection={<IconArrowDown size={20} />}>Lihat Kantor Polisi Lainnya</Button>
</Box>
<Box>
<Text c={colors["blue-button"]} fz={'lg'}>Pelayanan SKCK</Text>
</Box>
<Box pos={'relative'}>
<Box style={{ position: 'absolute', top: 0, right: 0 }}>
<Badge size='lg' c={'#287407'} bg={'#A8EDC4'}>Buka</Badge>
</Box>
<Box>
<Text c={colors["blue-button"]} fz={'lg'}>Pengaduan Masyarakat</Text>
<Box pt={40}>
<iframe style={{ border: 2, width: "100%" }} src={data.embedMapUrl} width="550" height="300" ></iframe>
</Box>
</SimpleGrid>
</Box>
<Box>
<Button bg={colors["blue-button"]} rightSection={<IconArrowDown size={20}/>}>Lihat Kantor Polisi Lainnya</Button>
</Box>
</Box>
<Box pos={'relative'}>
<Box style={{ position: 'absolute', top: 0, right: 0 }}>
<Badge size='lg' c={'#287407'} bg={'#A8EDC4'}>Buka</Badge>
</Box>
<Box pt={40}>
<iframe style={{ border: 2, width: "100%" }} src="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d3945.7949871034166!2d115.20778387533218!3d-8.519275686287415!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x2dd23ceb4f6e5363%3A0xa353662f070f47d8!2sAbian%20Semal%20Police%20Station!5e0!3m2!1sid!2sid!4v1742789148825!5m2!1sid!2sid" width="550" height="300" ></iframe>
</Box>
<Box pt={20}>
<Button fullWidth bg={colors["blue-button"]} radius={10} leftSection={<IconNavigation size={20}/>}>Petunjuk Arah</Button>
</Box>
</Box>
<Box pt={20}>
<Button onClick={() => router.push(data.linkPetunjukArah)} fullWidth bg={colors["blue-button"]} radius={10} leftSection={<IconNavigation size={20} />}>Petunjuk Arah</Button>
</Box>
</Box>
</>
) : null}
</SimpleGrid>
</Stack>
</Paper>

View File

@@ -0,0 +1,100 @@
'use client'
import polsekTerdekatState from '@/app/admin/(dashboard)/_state/keamanan/polsek-terdekat';
import colors from '@/con/colors';
import { Box, Button, Center, Grid, GridCol, Pagination, Paper, SimpleGrid, Skeleton, Stack, Text, TextInput } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconNavigation, IconSearch } from '@tabler/icons-react';
import React, { useState } from 'react';
import { useProxy } from 'valtio/utils';
import BackButton from '../../../desa/layanan/_com/BackButto';
import { useRouter } from 'next/navigation';
function Page() {
const state = useProxy(polsekTerdekatState);
const [search, setSearch] = useState('');
const router = useRouter()
const {
data,
page,
totalPages,
loading,
load,
} = state.findMany;
useShallowEffect(() => {
load(page, 3, search)
}, [page, search])
if (loading || !data) {
return (
<Box py={10}>
<Skeleton h={500} />
</Box>
)
}
return (
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
<Box px={{ base: 'md', md: 100 }}>
<BackButton />
</Box>
<Grid align='center' px={{ base: 'md', md: 100 }}>
<GridCol span={{ base: 12, md: 9 }}>
<Text fz={{ base: "h1", md: "2.5rem" }} c={colors["blue-button"]} fw={"bold"}>
Semua Polsek Terdekat
</Text>
</GridCol>
<GridCol span={{ base: 12, md: 3 }}>
<TextInput
radius={"lg"}
placeholder='Cari Polsek'
value={search}
onChange={(e) => setSearch(e.target.value)}
leftSection={<IconSearch size={20} />}
w={{ base: "50%", md: "100%" }}
/>
</GridCol>
</Grid>
<Box px={{ base: "md", md: 100 }}>
<SimpleGrid
cols={{
base: 1,
md: 3,
}}
>
{data.map((v, k) => {
return (
<Paper p={"xl"} bg={colors['white-trans-1']} key={k}>
<Stack gap={"xs"}>
<Text fw={"bold"} fz={"h3"}>{v.nama}</Text>
<Text>Alamat: {v.alamat}</Text>
<Text>Jarak: {v.jarakKeDesa}</Text>
<Text>Telepon: {v.nomorTelepon}</Text>
<Text>Jam Operasional: {v.jamOperasional}</Text>
<Box pt={20}>
<iframe style={{ border: 2, width: "100%" }} src={v.embedMapUrl} width="550" height="300" ></iframe>
</Box>
<Box pt={20}>
<Button onClick={() => router.push(v.linkPetunjukArah)} fullWidth bg={colors["blue-button"]} radius={10} leftSection={<IconNavigation size={20} />}>Petunjuk Arah</Button>
</Box>
</Stack>
</Paper>
)
})}
</SimpleGrid>
</Box>
<Center>
<Pagination
value={page}
onChange={(newPage) => load(newPage)} // ini penting!
total={totalPages}
mt="md"
mb="md"
/>
</Center>
</Stack>
);
}
export default Page;

View File

@@ -1,18 +1,32 @@
'use client'
import daftarInformasiPublik from '@/app/admin/(dashboard)/_state/ppid/daftar_informasi_publik/daftarInformasiPublik';
import colors from '@/con/colors';
import { Box, Center, Image, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, TextInput } from '@mantine/core';
import { Box, Center, Image, Pagination, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, TextInput } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconSearch } from '@tabler/icons-react';
import { useState } from 'react';
import { useProxy } from 'valtio/utils';
import BackButton from '../../desa/layanan/_com/BackButto';
function Page() {
const listData = useProxy(daftarInformasiPublik)
const [search, setSearch] = useState('')
const {
data,
page,
totalPages,
loading,
load,
} = listData.findMany
useShallowEffect(() => {
listData.findMany.load()
}, [])
if (!listData.findMany.data) return <Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
load(page, 5, search)
}, [page, search])
if (loading || !data) return <Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
<Box px={{ base: 'md', md: 100 }}>
<Skeleton h={40} />
</Box>
@@ -40,6 +54,9 @@ function Page() {
<TextInput
placeholder='Cari Informasi...'
leftSection={<IconSearch size={20} />}
value={search}
onChange={(e) => setSearch(e.target.value)}
style={{ marginBottom: 16 }}
/>
<Table withRowBorders withColumnBorders withTableBorder>
<TableThead bg={colors['blue-button']}>
@@ -54,8 +71,17 @@ function Page() {
{listData.findMany.data?.map((item, index) => (
<TableTr key={item.id}>
<TableTd ta={'center'}>{index + 1}</TableTd>
<TableTd>{item.jenisInformasi}</TableTd>
<TableTd dangerouslySetInnerHTML={{ __html: item.deskripsi }}></TableTd>
<TableTd>
<Text fz={'md'}>
{item.jenisInformasi}
</Text>
</TableTd>
<TableTd>
<Text
fz={'md'}
dangerouslySetInnerHTML={{ __html: item.deskripsi }}
/>
</TableTd>
<TableTd style={{ width: '20%', textAlign: 'center' }}>{item.tanggal
? new Date(item.tanggal).toLocaleDateString('id-ID')
: '-'}</TableTd>
@@ -64,6 +90,14 @@ function Page() {
</TableTbody>
</Table>
</Stack>
<Center>
<Pagination
value={page}
onChange={(newPage) => load(newPage)}
total={totalPages}
my={"md"}
/>
</Center>
<Text pt={20} fz={'h4'} fw={"bold"}>Kontak PPID</Text>
<Text fz={'sm'}>Email: ppid@desadarmasaba.id | WhatsApp: 081-xxx-xxx-xxx</Text>
</Box>

View File

@@ -4,7 +4,7 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>HIPMI Feature Checklist</title>
<title>Desa Darmasaba Feature Checklist</title>
<style>
body {
font-family: Arial, sans-serif;
@@ -267,284 +267,262 @@
</li>
</ul>
<!-- SubMenu Indeks Kepuasan Masayarakat (IKM) (Landing Page - PPID) -->
<h4>Indeks Kepuasan Masayarakat (IKM) (Landing Page - PPID)</h4>
<!-- List Responden -->
<h4>List Desa Anti Korupsi</h4>
<ul>
<li>
<input type="checkbox" id="19" /> <label for="19">Search sudah berfungsi</label>
</li>
<li>
<input type="checkbox" id="20" /> <label for="20">Data tampil di halaman list</label>
<!-- SubMenu Indeks Kepuasan Masayarakat (IKM) (Landing Page - PPID) -->
<h4>Indeks Kepuasan Masayarakat (IKM) (Landing Page - PPID)</h4>
<!-- List Responden -->
<h4>List Responden</h4>
<ul>
<li>
<input type="checkbox" id="33" />
<label for="33">Search sudah berfungsi</label>
</li>
<li>
<input type="checkbox" id="34" />
<label for="34">Data tampil di halaman list</label>
</li>
<li>
<input type="checkbox" id="35" />
<label for="35">Create list responden</label>
</li>
<li>
<input type="checkbox" id="36" />
<label for="36">Detail mau tampil datanya</label>
</li>
<li>
<input type="checkbox" id="37" />
<label for="37">Hapus list responden bekerja</label>
</li>
<li>
<input type="checkbox" id="38" />
<label for="38">Edit / Update list responden bekerja</label>
</li>
<li>
<input type="checkbox" id="39" />
<label for="39">Pagination bekerja</label>
</li>
<li>
<input type="checkbox" id="21" />
<label for="21">Create list desa anti korupsi</label>
</li>
<li>
<input type="checkbox" id="22" />
<label for="22">Detail mau tampil datanya</label>
</li>
<li>
<input type="checkbox" id="23" />
<label for="23">Hapus list desa anti korupsi bekerja</label>
</li>
<li>
<input type="checkbox" id="24" />
<label for="24">Edit / Update list desa anti korupsi bekerja</label>
</li>
<li>
<h4>List Responden</h4>
<ul>
<li>
<input type="checkbox" id="33" />
<label for="33">Search sudah berfungsi</label>
</li>
<li>
<input type="checkbox" id="34" />
<label for="34">Data tampil di halaman list</label>
</li>
<li>
<input type="checkbox" id="35" />
<label for="35">Create list responden</label>
</li>
<li>
<input type="checkbox" id="36" />
<label for="36">Detail mau tampil datanya</label>
</li>
<li>
<input type="checkbox" id="37" />
<label for="37">Hapus list responden bekerja</label>
</li>
<li>
<input type="checkbox" id="38" />
<label for="38">Edit / Update list responden bekerja</label>
</li>
<li>
<input type="checkbox" id="39" />
<label for="39">Pagination bekerja</label>
</li>
<li>
<input type="checkbox" id="40" />
<label for="40">Sinkronisasi ke UI</label>
</li>
</ul>
<input type="checkbox" id="40" />
<label for="40">Sinkronisasi ke UI</label>
</li>
<!-- SubMenu SDGs Desa -->
<h4>SDGs Desa</h4>
<!-- List SDGs Desa -->
<h4>List SDGs Desa</h4>
</ul>
<!-- SubMenu SDGs Desa -->
<h4>SDGs Desa</h4>
<!-- List SDGs Desa -->
<h4>List SDGs Desa</h4>
<ul>
<li>
<input type="checkbox" id="41" />
<label for="41">Search sudah berfungsi</label>
</li>
<li>
<input type="checkbox" id="42" />
<label for="42">Data tampil di halaman list</label>
</li>
<li>
<input type="checkbox" id="43" />
<label for="43">Create list SDGs Desa</label>
</li>
<li>
<input type="checkbox" id="44" />
<label for="44">Detail mau tampil datanya</label>
</li>
<li>
<input type="checkbox" id="45" />
<label for="45">Hapus list SDGs Desa bekerja</label>
</li>
<li>
<input type="checkbox" id="46" />
<label for="46">Edit / Update list SDGs Desa bekerja</label>
</li>
<li>
<input type="checkbox" id="47" />
<label for="47">Pagination bekerja</label>
</li>
<li>
<input type="checkbox" id="48" />
<label for="48">Sinkronisasi ke UI</label>
</li>
</ul>
<!-- SubMenu APBDes -->
<h4>APBDes</h4>
<!-- List APBDes -->
<h4>List APBDes</h4>
<ul>
<li>
<input type="checkbox" id="49" />
<label for="49">Search sudah berfungsi</label>
</li>
<li>
<input type="checkbox" id="50" />
<label for="50">Data tampil di halaman list</label>
</li>
<li>
<input type="checkbox" id="51" />
<label for="51">Create list APBDes</label>
</li>
<li>
<input type="checkbox" id="52" />
<label for="52">Detail mau tampil datanya</label>
</li>
<li>
<input type="checkbox" id="53" />
<label for="53">Hapus list APBDes bekerja</label>
</li>
<li>
<input type="checkbox" id="54" />
<label for="54">Edit / Update list APBDes bekerja</label>
</li>
<li>
<input type="checkbox" id="55" />
<label for="55">Pagination bekerja</label>
</li>
<li>
<input type="checkbox" id="56" />
<label for="56">Sinkronisasi ke UI</label>
</li>
</ul>
<!-- SubMenu Prestasi Desa -->
<h4>Prestasi Desa</h4>
<!-- List Prestasi Desa -->
<h4>List Prestasi Desa</h4>
<ul>
<li>
<input type="checkbox" id="41" />
<label for="41">Search sudah berfungsi</label>
<input type="checkbox" id="57" />
<label for="57">Search sudah berfungsi</label>
</li>
<li>
<input type="checkbox" id="42" />
<label for="42">Data tampil di halaman list</label>
<input type="checkbox" id="58" />
<label for="58">Data tampil di halaman list</label>
</li>
<li>
<input type="checkbox" id="43" />
<label for="43">Create list SDGs Desa</label>
<input type="checkbox" id="59" />
<label for="59">Create list Prestasi Desa</label>
</li>
<li>
<input type="checkbox" id="44" />
<label for="44">Detail mau tampil datanya</label>
<input type="checkbox" id="60" />
<label for="60">Detail mau tampil datanya</label>
</li>
<li>
<input type="checkbox" id="45" />
<label for="45">Hapus list SDGs Desa bekerja</label>
<input type="checkbox" id="61" />
<label for="61">Hapus list Prestasi Desa bekerja</label>
</li>
<li>
<input type="checkbox" id="46" />
<label for="46">Edit / Update list SDGs Desa bekerja</label>
<input type="checkbox" id="62" />
<label for="62">Edit / Update list Prestasi Desa bekerja</label>
</li>
<li>
<input type="checkbox" id="47" />
<label for="47">Pagination bekerja</label>
<input type="checkbox" id="63" />
<label for="63">Pagination bekerja</label>
</li>
<li>
<input type="checkbox" id="48" />
<label for="48">Sinkronisasi ke UI</label>
<input type="checkbox" id="64" />
<label for="64">Sinkronisasi ke UI</label>
</li>
</ul>
<!-- SubMenu APBDes -->
<h4>APBDes</h4>
<!-- List APBDes -->
<h4>List APBDes</h4>
<ul>
<li>
<input type="checkbox" id="49" />
<label for="49">Search sudah berfungsi</label>
</li>
<li>
<input type="checkbox" id="50" />
<label for="50">Data tampil di halaman list</label>
</li>
<li>
<input type="checkbox" id="51" />
<label for="51">Create list APBDes</label>
</li>
<li>
<input type="checkbox" id="52" />
<label for="52">Detail mau tampil datanya</label>
</li>
<li>
<input type="checkbox" id="53" />
<label for="53">Hapus list APBDes bekerja</label>
</li>
<li>
<input type="checkbox" id="54" />
<label for="54">Edit / Update list APBDes bekerja</label>
</li>
<li>
<input type="checkbox" id="55" />
<label for="55">Pagination bekerja</label>
</li>
<li>
<input type="checkbox" id="56" />
<label for="56">Sinkronisasi ke UI</label>
<!-- Menu PPID -->
<h3>Menu PPID</h3>
<!-- SubMenu Profile PPID -->
<h4>Profile PPID</h4>
<ul>
<li>
<input type="checkbox" id="65" />
<label for="65">Data tampil di halaman</label>
</li>
<li>
<input type="checkbox" id="66" />
<label for="66">Edit / Update Profile PPID</label>
</li>
<li>
<input type="checkbox" id="67" />
<label for="67">Sinkronisasi ke UI</label>
</li>
</ul>
<!-- SubMenu Struktur PPID -->
<h4>Struktur PPID</h4>
<h4>Pegawai</h4>
<ul>
<li>
<input type="checkbox" id="68" />
<label for="68">Search sudah berfungsi</label>
</li>
<li>
<input type="checkbox" id="69" />
<label for="69">Data tampil di halaman list</label>
</li>
<li>
<input type="checkbox" id="70" />
<label for="70">Create list Pegawai</label>
</li>
<li>
<input type="checkbox" id="71" />
<label for="71">Detail mau tampil datanya</label>
</li>
<li>
<input type="checkbox" id="72" />
<label for="72">Hapus list Pegawai bekerja</label>
</li>
<li>
<input type="checkbox" id="73" />
<label for="73">Edit / Update list Pegawai bekerja</label>
</li>
<li>
<input type="checkbox" id="74" />
<label for="74">Pagination bekerja</label>
</li>
<li>
<input type="checkbox" id="75" />
<label for="75">Sinkronisasi ke UI</label>
</li>
</ul>
<!-- SubMenu Prestasi Desa -->
<h4>Prestasi Desa</h4>
<!-- List Prestasi Desa -->
<h4>List Prestasi Desa</h4>
<ul>
<li>
<input type="checkbox" id="57" />
<label for="57">Search sudah berfungsi</label>
</li>
<li>
<input type="checkbox" id="58" />
<label for="58">Data tampil di halaman list</label>
</li>
<li>
<input type="checkbox" id="59" />
<label for="59">Create list Prestasi Desa</label>
</li>
<li>
<input type="checkbox" id="60" />
<label for="60">Detail mau tampil datanya</label>
</li>
<li>
<input type="checkbox" id="61" />
<label for="61">Hapus list Prestasi Desa bekerja</label>
</li>
<li>
<input type="checkbox" id="62" />
<label for="62">Edit / Update list Prestasi Desa bekerja</label>
</li>
<li>
<input type="checkbox" id="63" />
<label for="63">Pagination bekerja</label>
</li>
<li>
<input type="checkbox" id="64" />
<label for="64">Sinkronisasi ke UI</label>
</li>
</ul>
<!-- Menu PPID -->
<h3>Menu PPID</h3>
<!-- SubMenu Profile PPID -->
<h4>Profile PPID</h4>
<ul>
<li>
<input type="checkbox" id="65" />
<label for="65">Data tampil di halaman</label>
</li>
<li>
<input type="checkbox" id="66" />
<label for="66">Edit / Update Profile PPID</label>
</li>
<li>
<input type="checkbox" id="67" />
<label for="67">Sinkronisasi ke UI</label>
</li>
</ul>
<!-- SubMenu Struktur PPID -->
<h4>Struktur PPID</h4>
<h4>Pegawai</h4>
<ul>
<li>
<input type="checkbox" id="68" />
<label for="68">Search sudah berfungsi</label>
</li>
<li>
<input type="checkbox" id="69" />
<label for="69">Data tampil di halaman list</label>
</li>
<li>
<input type="checkbox" id="70" />
<label for="70">Create list Pegawai</label>
</li>
<li>
<input type="checkbox" id="71" />
<label for="71">Detail mau tampil datanya</label>
</li>
<li>
<input type="checkbox" id="72" />
<label for="72">Hapus list Pegawai bekerja</label>
</li>
<li>
<input type="checkbox" id="73" />
<label for="73">Edit / Update list Pegawai bekerja</label>
</li>
<li>
<input type="checkbox" id="74" />
<label for="74">Pagination bekerja</label>
</li>
<li>
<input type="checkbox" id="75" />
<label for="75">Sinkronisasi ke UI</label>
</li>
</ul>
<h4>Posisi Organisasi PPID</h4>
<ul>
<li>
<input type="checkbox" id="76" />
<label for="76">Search sudah berfungsi</label>
</li>
<li>
<input type="checkbox" id="77" />
<label for="77">Data tampil di halaman list</label>
</li>
<li>
<input type="checkbox" id="78" />
<label for="78">Create list Posisi Organisasi PPID</label>
</li>
<li>
<input type="checkbox" id="79" />
<label for="79">Detail mau tampil datanya</label>
</li>
<li>
<input type="checkbox" id="80" />
<label for="80">Hapus list Posisi Organisasi PPID bekerja</label>
</li>
<li>
<input type="checkbox" id="81" />
<label for="81">Edit / Update list Posisi Organisasi PPID bekerja</label>
</li>
<li>
<input type="checkbox" id="82" />
<label for="82">Pagination bekerja</label>
</li>
<li>
<input type="checkbox" id="83" />
<label for="83">Sinkronisasi ke UI</label>
</li>
</ul>
<ul>
<li>
<input type="checkbox" id="76" />
<label for="76">Search sudah berfungsi</label>
</li>
<li>
<input type="checkbox" id="77" />
<label for="77">Data tampil di halaman list</label>
</li>
<li>
<input type="checkbox" id="78" />
<label for="78">Create list Posisi Organisasi PPID</label>
</li>
<li>
<input type="checkbox" id="79" />
<label for="79">Detail mau tampil datanya</label>
</li>
<li>
<input type="checkbox" id="80" />
<label for="80">Hapus list Posisi Organisasi PPID bekerja</label>
</li>
<li>
<input type="checkbox" id="81" />
<label for="81">Edit / Update list Posisi Organisasi PPID bekerja</label>
</li>
<li>
<input type="checkbox" id="82" />
<label for="82">Pagination bekerja</label>
</li>
<li>
<input type="checkbox" id="83" />
<label for="83">Sinkronisasi ke UI</label>
</li>
</ul>
<!-- SubMenu Visi Misi PPID -->
<h4>Visi Misi PPID</h4>
<ul>
<li>
<input type="checkbox" id="84" />
<input type="checkbox" id="84" />
<label for="84">Data tampil di halaman</label>
</li>
<li>
@@ -558,27 +536,27 @@
</ul>
<!-- SubMenu Dasar Hukum PPID -->
<h4>Dasar Hukum PPID</h4>
<ul>
<li>
<input type="checkbox" id="87" />
<label for="87">Data tampil di halaman</label>
</li>
<li>
<input type="checkbox" id="88" />
<label for="88">Edit / Update Dasar Hukum PPID</label>
</li>
<li>
<input type="checkbox" id="89" />
<label for="89">Sinkronisasi ke UI</label>
</li>
</ul>
<h4>Dasar Hukum PPID</h4>
<ul>
<li>
<input type="checkbox" id="87" />
<label for="87">Data tampil di halaman</label>
</li>
<li>
<input type="checkbox" id="88" />
<label for="88">Edit / Update Dasar Hukum PPID</label>
</li>
<li>
<input type="checkbox" id="89" />
<label for="89">Sinkronisasi ke UI</label>
</li>
</ul>
<!-- SubMenu Permohonan Informasi Publik -->
<h4>Permohonan Informasi Publik</h4>
<ul>
<li>
<input type="checkbox" id="90" />
<input type="checkbox" id="90" />
<label for="90">Data tampil di halaman</label>
</li>
<li>
@@ -591,22 +569,65 @@
</li>
</ul>
<!-- SubMenu Permohonan Keberatan Informasi Publik -->
<h4>Permohonan Keberatan Informasi Publik</h4>
<ul>
<li>
<input type="checkbox" id="93" />
<label for="93">Data tampil di halaman</label>
</li>
<li>
<input type="checkbox" id="94" />
<label for="94">Create Permohonan Keberatan Informasi Publik</label>
</li>
<li>
<input type="checkbox" id="95" />
<label for="95">Sinkronisasi ke UI</label>
</li>
</ul>
<!-- SubMenu Permohonan Keberatan Informasi Publik -->
<h4>Permohonan Keberatan Informasi Publik</h4>
<ul>
<li>
<input type="checkbox" id="93" />
<label for="93">Data tampil di halaman</label>
</li>
<li>
<input type="checkbox" id="94" />
<label for="94">Create Permohonan Keberatan Informasi Publik</label>
</li>
<li>
<input type="checkbox" id="95" />
<label for="95">Sinkronisasi ke UI</label>
</li>
</ul>
<!-- SubMenu Daftar Informasi Publik -->
<h4>Daftar Informasi Publik</h4>
<ul>
<li>
<input type="checkbox" id="96" />
<label for="96">Search sudah berfungsi</label>
</li>
<li>
<input type="checkbox" id="97" />
<label for="97">Data tampil di halaman list</label>
</li>
<li>
<input type="checkbox" id="98" />
<label for="98">Create list daftar informasi publik</label>
</li>
<li>
<input type="checkbox" id="99" />
<label for="99">Detail mau tampil datanya</label>
</li>
<li>
<input type="checkbox" id="100" />
<label for="100">Hapus list daftar informasi publik bekerja</label>
</li>
<li>
<input type="checkbox" id="101" />
<label for="101">Edit / Update list daftar informasi publik bekerja</label>
</li>
<li>
<input type="checkbox" id="102" />
<label for="102">Pagination bekerja</label>
</li>
<li>
<input type="checkbox" id="103" />
<label for="103">Sinkronisasi ke UI</label>
</li>
</ul>
</div>