From d79425d52995892beec5816a74067932adc6f433 Mon Sep 17 00:00:00 2001 From: nico Date: Tue, 19 Aug 2025 11:12:39 +0800 Subject: [PATCH] Sinkronisasi UI & API Menu Keamanan, Admin - User Submenu Keamanan Lingkungan & Polse Terdekat --- .../_state/keamanan/keamanan-lingkungan.ts | 39 +++++- .../_state/keamanan/polsek-terdekat.ts | 62 ++++++++- .../[id]/page.tsx | 8 +- .../page.tsx | 42 ++++-- .../keamanan/polsek-terdekat/page.tsx | 67 ++++++---- .../keamanan/keamanan-lingkungan/findMany.ts | 75 ++++++++--- .../keamanan/polsek-terdekat/findFirst.ts | 31 +++++ .../_lib/keamanan/polsek-terdekat/findMany.ts | 75 ++++++++--- .../_lib/keamanan/polsek-terdekat/index.ts | 2 + .../page.tsx | 121 +++++++++--------- .../(pages)/keamanan/polsek-terdekat/page.tsx | 116 +++++++++-------- .../polsek-terdekat/semua-polsek/page.tsx | 11 ++ src/app/percobaan/index.html | 2 +- 13 files changed, 444 insertions(+), 207 deletions(-) create mode 100644 src/app/api/[[...slugs]]/_lib/keamanan/polsek-terdekat/findFirst.ts create mode 100644 src/app/darmasaba/(pages)/keamanan/polsek-terdekat/semua-polsek/page.tsx diff --git a/src/app/admin/(dashboard)/_state/keamanan/keamanan-lingkungan.ts b/src/app/admin/(dashboard)/_state/keamanan/keamanan-lingkungan.ts index 5789278b..3fd458f4 100644 --- a/src/app/admin/(dashboard)/_state/keamanan/keamanan-lingkungan.ts +++ b/src/app/admin/(dashboard)/_state/keamanan/keamanan-lingkungan.ts @@ -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; } }, }, diff --git a/src/app/admin/(dashboard)/_state/keamanan/polsek-terdekat.ts b/src/app/admin/(dashboard)/_state/keamanan/polsek-terdekat.ts index cb1be694..ad0c696f 100644 --- a/src/app/admin/(dashboard)/_state/keamanan/polsek-terdekat.ts +++ b/src/app/admin/(dashboard)/_state/keamanan/polsek-terdekat.ts @@ -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; diff --git a/src/app/admin/(dashboard)/keamanan/keamanan-lingkungan-pecalang-patwal/[id]/page.tsx b/src/app/admin/(dashboard)/keamanan/keamanan-lingkungan-pecalang-patwal/[id]/page.tsx index e0118dff..c6683c1e 100644 --- a/src/app/admin/(dashboard)/keamanan/keamanan-lingkungan-pecalang-patwal/[id]/page.tsx +++ b/src/app/admin/(dashboard)/keamanan/keamanan-lingkungan-pecalang-patwal/[id]/page.tsx @@ -54,6 +54,10 @@ function DetailKeamananLingkungan() { {keamananState.findUnique.data ? ( + + Gambar + gambar + Judul Keamanan Lingkungan {keamananState.findUnique.data?.name} @@ -62,10 +66,6 @@ function DetailKeamananLingkungan() { Deskripsi - - Gambar - gambar - - - - ))} + + + + + ))} - + +
+ load(newPage)} + total={totalPages} + my="md" + /> +
); } diff --git a/src/app/api/[[...slugs]]/_lib/keamanan/keamanan-lingkungan/findMany.ts b/src/app/api/[[...slugs]]/_lib/keamanan/keamanan-lingkungan/findMany.ts index ea5985e9..59c30fa6 100644 --- a/src/app/api/[[...slugs]]/_lib/keamanan/keamanan-lingkungan/findMany.ts +++ b/src/app/api/[[...slugs]]/_lib/keamanan/keamanan-lingkungan/findMany.ts @@ -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", - }; - } -} \ No newline at end of file + // 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; \ No newline at end of file diff --git a/src/app/api/[[...slugs]]/_lib/keamanan/polsek-terdekat/findFirst.ts b/src/app/api/[[...slugs]]/_lib/keamanan/polsek-terdekat/findFirst.ts new file mode 100644 index 00000000..1f89030b --- /dev/null +++ b/src/app/api/[[...slugs]]/_lib/keamanan/polsek-terdekat/findFirst.ts @@ -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; \ No newline at end of file diff --git a/src/app/api/[[...slugs]]/_lib/keamanan/polsek-terdekat/findMany.ts b/src/app/api/[[...slugs]]/_lib/keamanan/polsek-terdekat/findMany.ts index 9d3e8a7e..36fed3ed 100644 --- a/src/app/api/[[...slugs]]/_lib/keamanan/polsek-terdekat/findMany.ts +++ b/src/app/api/[[...slugs]]/_lib/keamanan/polsek-terdekat/findMany.ts @@ -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", - }; - } -} \ No newline at end of file + // 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; \ No newline at end of file diff --git a/src/app/api/[[...slugs]]/_lib/keamanan/polsek-terdekat/index.ts b/src/app/api/[[...slugs]]/_lib/keamanan/polsek-terdekat/index.ts index 23382476..f87a7baa 100644 --- a/src/app/api/[[...slugs]]/_lib/keamanan/polsek-terdekat/index.ts +++ b/src/app/api/[[...slugs]]/_lib/keamanan/polsek-terdekat/index.ts @@ -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(), diff --git a/src/app/darmasaba/(pages)/keamanan/keamanan-lingkungan-pecalang-patwal/page.tsx b/src/app/darmasaba/(pages)/keamanan/keamanan-lingkungan-pecalang-patwal/page.tsx index 8f370026..48db077c 100644 --- a/src/app/darmasaba/(pages)/keamanan/keamanan-lingkungan-pecalang-patwal/page.tsx +++ b/src/app/darmasaba/(pages)/keamanan/keamanan-lingkungan-pecalang-patwal/page.tsx @@ -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: - Mengamankan upacara adat dan kegiatan keagamaan. - Mengatur lalu lintas saat ada perayaan atau kegiatan besar. - Berpatroli untuk mencegah gangguan keamanan di lingkungan desa. - Berkoordinasi dengan aparat desa dan kepolisian dalam penanganan situasi darurat. - - }, - { - 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: - Berpatroli secara rutin untuk memastikan lingkungan tetap aman. - Menjaga ketertiban lalu lintas di area desa. - Melakukan tindakan preventif terhadap potensi gangguan keamanan. - Siap siaga dalam keadaan darurat untuk membantu warga. - - }, - { - 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: - Pos Pecalang Desa: [Masukkan Nomor Kontak]. - Patwal Desa Darmasaba: [Masukkan Nomor Kontak]. - Polsek Terdekat: 110 (Layanan Kepolisian). - - }, - { - id: 4, - judul: 'Program Keamanan Desa', - image: '/api/img/rond.png', - pengertian: 'Untuk meningkatkan keamanan, Desa Darmasaba menjalankan berbagai program, seperti:', - deskripsi: - Ronda Malam Warga: Kegiatan jaga malam secara bergilir oleh warga bersama Pecalang dan Patwal. - Sosialisasi Keamanan: Edukasi bagi warga tentang cara menjaga keamanan lingkungan. - Pengawasan Kamera CCTV: Memantau titik- titik strategis untuk mencegah tindak kejahatan. - - } -] 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 ( + + + + ) + } + return ( - - Keamanan Lingkungan (Pecalang / Patwal) - - + + + + Keamanan Lingkungan (Pecalang / Patwal) + + + + setSearch(e.target.value)} + leftSection={} + w={{ base: "50%", md: "100%" }} + /> + + + 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. @@ -73,24 +67,19 @@ function Page() { base: 1, md: 3, }}> - {data1.map((v, k) => { + {data.map((v, k) => { return (
- +
- {v.judul} + {v.name} - - {v.pengertian} - - - {v.deskripsi} - +
@@ -100,6 +89,14 @@ function Page() {
+
+ load(newPage)} // ini penting! + total={totalPages} + my="md" + /> +
); } diff --git a/src/app/darmasaba/(pages)/keamanan/polsek-terdekat/page.tsx b/src/app/darmasaba/(pages)/keamanan/polsek-terdekat/page.tsx index f1881f4b..dcfb262e 100644 --- a/src/app/darmasaba/(pages)/keamanan/polsek-terdekat/page.tsx +++ b/src/app/darmasaba/(pages)/keamanan/polsek-terdekat/page.tsx @@ -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 ( @@ -17,7 +37,6 @@ function Page() { Desa Darmasaba, Kecamatan Abiansemal, Kabupaten Badung - } placeholder='Cari Kantor Polisi' /> @@ -30,59 +49,56 @@ function Page() { }} > {/* Content Sebelah Kiri */} - - Polsek Abiansemal - 2,5 Km dari Desa Darmasaba - - - Jl. Gandamayu 1 Blahkiuh - - - - 08xxxxxxxx - - - - 24 Jam - - - Layanan Yang Tersedia : - + {loading ? ( +
+ ) : data ? ( + <> + + {data.nama} + {data.jarakKeDesa} + + + {data.alamat} + + + + {data.nomorTelepon} + + + + {data.jamOperasional} + - Laporan Kehilangan + Layanan Yang Tersedia : + + + {data.layananPolsek.nama} + + - Laporan Kriminal + - - Pelayanan SKCK + + + + Buka - - Pengaduan Masyarakat + + -
-
- - - -
- - - Buka - - - - - - - - + + + +
+ + ) : null}
diff --git a/src/app/darmasaba/(pages)/keamanan/polsek-terdekat/semua-polsek/page.tsx b/src/app/darmasaba/(pages)/keamanan/polsek-terdekat/semua-polsek/page.tsx new file mode 100644 index 00000000..69da2f21 --- /dev/null +++ b/src/app/darmasaba/(pages)/keamanan/polsek-terdekat/semua-polsek/page.tsx @@ -0,0 +1,11 @@ +import React from 'react'; + +function Page() { + return ( +
+ Page +
+ ); +} + +export default Page; diff --git a/src/app/percobaan/index.html b/src/app/percobaan/index.html index e3847e3b..22d41e8d 100644 --- a/src/app/percobaan/index.html +++ b/src/app/percobaan/index.html @@ -4,7 +4,7 @@ - HIPMI Feature Checklist + Desa Darmasaba Feature Checklist