diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 666a05fa..170cc067 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -292,6 +292,9 @@ model PosisiOrganisasiPPID { pegawai PegawaiPPID[] strukturOrganisasi StrukturPPID[] // Relasi balik parentId String? + isActive Boolean @default(true) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt parent PosisiOrganisasiPPID? @relation("Parent", fields: [parentId], references: [id]) children PosisiOrganisasiPPID[] @relation("Parent") } diff --git a/src/app/admin/(dashboard)/_state/ppid/struktur_ppid/struktur_PPID.ts b/src/app/admin/(dashboard)/_state/ppid/struktur_ppid/struktur_PPID.ts index e4f37f4e..5ffabf01 100644 --- a/src/app/admin/(dashboard)/_state/ppid/struktur_ppid/struktur_PPID.ts +++ b/src/app/admin/(dashboard)/_state/ppid/struktur_ppid/struktur_PPID.ts @@ -348,18 +348,34 @@ const posisiOrganisasi = proxy({ deskripsi: string | null; hierarki: number; }>, - async load() { + page: 1, + totalPages: 1, + loading: false, + search: "", + load: async (page = 1, limit = 10, search = "") => { + posisiOrganisasi.findMany.loading = true; // ✅ Akses langsung via nama path + posisiOrganisasi.findMany.page = page; + posisiOrganisasi.findMany.search = search; + try { - const res = await ApiFetch.api.ppid.strukturppid.posisiorganisasi[ - "find-many" - ].get(); - if (res.status === 200) { - // The API now returns the id field, so we can use it directly - this.data = res.data?.data ?? []; + const query: any = { page, limit }; + if (search) query.search = search; + + const res = await ApiFetch.api.ppid.strukturppid.posisiorganisasi["find-many"].get({ query }); + + if (res.status === 200 && res.data?.success) { + posisiOrganisasi.findMany.data = res.data.data ?? []; + posisiOrganisasi.findMany.totalPages = res.data.totalPages ?? 1; + } else { + posisiOrganisasi.findMany.data = []; + posisiOrganisasi.findMany.totalPages = 1; } - } catch (error) { - console.error("Find many error:", error); - this.data = []; + } catch (err) { + console.error("Gagal fetch posisi organisasi paginated:", err); + posisiOrganisasi.findMany.data = []; + posisiOrganisasi.findMany.totalPages = 1; + } finally { + posisiOrganisasi.findMany.loading = false; } }, }, @@ -438,9 +454,9 @@ const pegawai = proxy({ try { pegawai.create.loading = true; - const res = await ApiFetch.api.ppid.strukturppid.pegawai[ - "create" - ].post(pegawai.create.form); + const res = await ApiFetch.api.ppid.strukturppid.pegawai["create"].post( + pegawai.create.form + ); if (res.status === 200) { toast.success("Pegawai berhasil ditambahkan"); await pegawai.findMany.load(); @@ -457,42 +473,55 @@ const pegawai = proxy({ }, // In struktur-organisasi.ts -findMany: { - data: null as any[] | null, - page: 1, - totalPages: 1, - total: 0, - loading: false, - load: async (page = 1, limit = 10) => { // Change to arrow function - pegawai.findMany.loading = true; // Use the full path to access the property - pegawai.findMany.page = page; - try { - const res = await ApiFetch.api.ppid.strukturppid.pegawai[ - "find-many" - ].get({ - query: { page, limit }, - }); + findMany: { + data: null as + | Prisma.PegawaiPPIDGetPayload<{ + include: { + image: true; + posisi: true; + }; + }>[] + | null, + page: 1, + totalPages: 1, + total: 0, + loading: false, + search: "", + load: async (page = 1, limit = 10, search = "") => { + // Change to arrow function + pegawai.findMany.loading = true; // Use the full path to access the property + pegawai.findMany.page = page; + pegawai.findMany.search = search; + try { + const query: any = { page, limit }; + if (search) query.search = search; - if (res.status === 200 && res.data?.success) { - pegawai.findMany.data = res.data.data || []; - pegawai.findMany.total = res.data.total || 0; - pegawai.findMany.totalPages = res.data.totalPages || 1; - } else { - console.error("Failed to load pegawai:", res.data?.message); + const res = await ApiFetch.api.ppid.strukturppid.pegawai[ + "find-many" + ].get({ + query, + }); + + if (res.status === 200 && res.data?.success) { + pegawai.findMany.data = res.data.data || []; + pegawai.findMany.total = res.data.total || 0; + pegawai.findMany.totalPages = res.data.totalPages || 1; + } else { + console.error("Failed to load pegawai:", res.data?.message); + pegawai.findMany.data = []; + pegawai.findMany.total = 0; + pegawai.findMany.totalPages = 1; + } + } catch (error) { + console.error("Error loading pegawai:", error); pegawai.findMany.data = []; pegawai.findMany.total = 0; pegawai.findMany.totalPages = 1; + } finally { + pegawai.findMany.loading = false; } - } catch (error) { - console.error("Error loading pegawai:", error); - pegawai.findMany.data = []; - pegawai.findMany.total = 0; - pegawai.findMany.totalPages = 1; - } finally { - pegawai.findMany.loading = false; - } + }, }, -}, findUnique: { data: null as | (Prisma.PegawaiGetPayload<{ @@ -521,12 +550,9 @@ findMany: { if (!id) return toast.warn("ID tidak valid"); try { pegawai.delete.loading = true; - const res = await fetch( - `/api/ppid/strukturppid/pegawai/del/${id}`, - { - method: "DELETE", - } - ); + const res = await fetch(`/api/ppid/strukturppid/pegawai/del/${id}`, { + method: "DELETE", + }); const json = await res.json(); if (res.ok) { toast.success(json.message ?? "Berhasil hapus pegawai"); @@ -555,15 +581,12 @@ findMany: { } try { - const response = await fetch( - `/api/ppid/strukturppid/pegawai/${id}`, - { - method: "GET", - headers: { - "Content-Type": "application/json", - }, - } - ); + const response = await fetch(`/api/ppid/strukturppid/pegawai/${id}`, { + method: "GET", + headers: { + "Content-Type": "application/json", + }, + }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); @@ -677,7 +700,7 @@ findMany: { const stateStrukturPPID = proxy({ stateStruktur, posisiOrganisasi, - pegawai + pegawai, }); export default stateStrukturPPID; diff --git a/src/app/admin/(dashboard)/ppid/struktur-ppid/pegawai/page.tsx b/src/app/admin/(dashboard)/ppid/struktur-ppid/pegawai/page.tsx index 715c348d..456229ac 100644 --- a/src/app/admin/(dashboard)/ppid/struktur-ppid/pegawai/page.tsx +++ b/src/app/admin/(dashboard)/ppid/struktur-ppid/pegawai/page.tsx @@ -4,7 +4,7 @@ import colors from '@/con/colors'; import { Badge, Box, Button, Center, Group, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, ThemeIcon } from '@mantine/core'; import { IconCheck, IconDeviceImacCog, IconSearch, IconX } from '@tabler/icons-react'; import { useRouter } from 'next/navigation'; -import { useEffect, useMemo, useState } from 'react'; +import { useEffect, useState } from 'react'; import { useProxy } from 'valtio/utils'; import HeaderSearch from '../../../_com/header'; import JudulList from '../../../_com/judulList'; @@ -39,22 +39,10 @@ function ListPegawaiPPID({ search }: { search: string }) { } = stateOrganisasi.findMany; useEffect(() => { - load(page, 10); - }, [page]); + load(page, 10, search); + }, [page, search]); - const filteredData = useMemo(() => { - if (!data) return []; - return data.filter(item => { - const keyword = search.toLowerCase(); - return ( - item.namaLengkap?.toLowerCase().includes(keyword) || - item.gelarAkademik?.toLowerCase().includes(keyword) || - item.telepon?.toLowerCase().includes(keyword) || - item.posisi?.nama?.toLowerCase().includes(keyword) - ); - }) - .sort((a, b) => a.posisi?.hierarki - b.posisi?.hierarki); - }, [data, search]); + const filteredData = data || [] // Handle loading state if (loading || !data) { diff --git a/src/app/admin/(dashboard)/ppid/struktur-ppid/posisi-organisasi/page.tsx b/src/app/admin/(dashboard)/ppid/struktur-ppid/posisi-organisasi/page.tsx index 18332a04..0ae300f3 100644 --- a/src/app/admin/(dashboard)/ppid/struktur-ppid/posisi-organisasi/page.tsx +++ b/src/app/admin/(dashboard)/ppid/struktur-ppid/posisi-organisasi/page.tsx @@ -1,7 +1,7 @@ /* eslint-disable react-hooks/exhaustive-deps */ '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 { IconEdit, IconSearch, IconTrash } from '@tabler/icons-react'; import { useRouter } from 'next/navigation'; import { useEffect, useState } from 'react'; @@ -33,9 +33,17 @@ function ListPosisiOrganisasiPPID({ search }: { search: string }) { const [modalHapus, setModalHapus] = useState(false) const [selectedId, setSelectedId] = useState(null) + const { + data, + page, + totalPages, + loading, + load, + } = stateOrganisasi.findMany; + useEffect(() => { - stateOrganisasi.findMany.load() - }, []) + load(page, 10, search); + }, [page, search]); const handleHapus = async () => { if (selectedId) { @@ -45,17 +53,9 @@ function ListPosisiOrganisasiPPID({ search }: { search: string }) { } } - const filteredData = (stateOrganisasi.findMany.data || []).filter(item => { - const keyword = search.toLowerCase(); - return ( - item.nama?.toLowerCase().includes(keyword) || - item.deskripsi?.toLowerCase().includes(keyword) || - item.hierarki?.toString().toLowerCase().includes(keyword) - ); - }) - .sort((a, b) => a.hierarki - b.hierarki); + const filteredData = data || [] - if (!stateOrganisasi.findMany.data) { + if (loading || !data) { return ( @@ -120,6 +120,14 @@ function ListPosisiOrganisasiPPID({ search }: { search: string }) { +
+ load(newPage)} + total={totalPages} + my={"md"} + /> +
{/* Modal Hapus */} ({ - id: item.id, - nama: item.nama, - deskripsi: item.deskripsi, - hierarki: item.hierarki, - })), + success: true, + message: "Berhasil mengambil data posisi organisasi dengan pagination", + data: data.map((item: any) => ({ + id: item.id, + nama: item.nama, + deskripsi: item.deskripsi, + hierarki: item.hierarki, + })), + page, + totalPages: Math.ceil(total / limit), + total, }; -} \ No newline at end of file + } catch (e) { + console.error("Find many paginated error:", e); + return { + success: false, + message: "Gagal mengambil data posisi organisasi", + }; + } +} + +export default posisiOrganisasiFindMany; diff --git a/src/app/darmasaba/(pages)/ekonomi/PADesa-pendapatan-asli-desa/page.tsx b/src/app/darmasaba/(pages)/ekonomi/PADesa-pendapatan-asli-desa/page.tsx index c0a9e2b8..75553230 100644 --- a/src/app/darmasaba/(pages)/ekonomi/PADesa-pendapatan-asli-desa/page.tsx +++ b/src/app/darmasaba/(pages)/ekonomi/PADesa-pendapatan-asli-desa/page.tsx @@ -1,21 +1,157 @@ +'use client' +import PendapatanAsliDesa from '@/app/admin/(dashboard)/_state/ekonomi/PADesa'; import colors from '@/con/colors'; -import { Stack, Box, Text, Image, Paper } from '@mantine/core'; -import React from 'react'; +import { Box, Grid, GridCol, Paper, SimpleGrid, Stack, Text, Title } from '@mantine/core'; +import { useProxy } from 'valtio/utils'; import BackButton from '../../desa/layanan/_com/BackButto'; +import { useShallowEffect } from '@mantine/hooks'; + function Page() { + const state = useProxy(PendapatanAsliDesa.ApbDesa); + + useShallowEffect(() => { + state.findMany.load(); + }, []); + + useShallowEffect(() => { + PendapatanAsliDesa.pembiayaan.findMany.load(); + PendapatanAsliDesa.belanja.findMany.load(); + PendapatanAsliDesa.pendapatan.findMany.load(); + }, []); + + // Get the latest APB data + const latestApb = state.findMany.data?.[0]; + + // Calculate totals + const totalPendapatan = latestApb?.pendapatan?.reduce((sum, item) => sum + (item?.value || 0), 0) || 0; + const totalBelanja = latestApb?.belanja?.reduce((sum, item) => sum + (item?.value || 0), 0) || 0; + const totalPembiayaan = latestApb?.pembiayaan?.reduce((sum, item) => sum + (item?.value || 0), 0) || 0; + return ( - + - - Pendapatan Asli Desa - + + Pendapatan Asli Desa + - - - + + + + {/* Pendapatan Card */} + + + Pendapatan + {PendapatanAsliDesa.pendapatan.findMany.data?.map((item) => ( + + + + {item.name} + + + {new Intl.NumberFormat('id-ID', { + style: 'currency', + currency: 'IDR', + minimumFractionDigits: 0 + }).format(item.value)} + + + + ))} + + + Total Pendapatan + + + + {new Intl.NumberFormat('id-ID', { + style: 'currency', + currency: 'IDR', + minimumFractionDigits: 0 + }).format(totalPendapatan)} + + + + + + + {/* Belanja Card */} + + + Belanja + {PendapatanAsliDesa.belanja.findMany.data?.map((item) => ( + + + + {item.name} + + + {new Intl.NumberFormat('id-ID', { + style: 'currency', + currency: 'IDR', + minimumFractionDigits: 0 + }).format(item.value)} + + + + ))} + + + Total Belanja + + + + {new Intl.NumberFormat('id-ID', { + style: 'currency', + currency: 'IDR', + minimumFractionDigits: 0 + }).format(totalBelanja)} + + + + + + + {/* Pembiayaan Card */} + + + Pembiayaan + {PendapatanAsliDesa.pembiayaan.findMany.data?.map((item) => ( + + + + {item.name} + + + {new Intl.NumberFormat('id-ID', { + style: 'currency', + currency: 'IDR', + minimumFractionDigits: 0 + }).format(item.value)} + + + + ))} + + + Total Pembiayaan + + + + {new Intl.NumberFormat('id-ID', { + style: 'currency', + currency: 'IDR', + minimumFractionDigits: 0 + }).format(totalPembiayaan)} + + + + + + + diff --git a/src/app/darmasaba/(pages)/ppid/ikm-desa-darmasaba/grafik_berdasarkan_jenis_kelamin/page.tsx b/src/app/darmasaba/(pages)/ppid/ikm-desa-darmasaba/grafik_berdasarkan_jenis_kelamin/page.tsx deleted file mode 100644 index 6667281b..00000000 --- a/src/app/darmasaba/(pages)/ppid/ikm-desa-darmasaba/grafik_berdasarkan_jenis_kelamin/page.tsx +++ /dev/null @@ -1,87 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -'use client' -import grafikBerdasarkanJenisKelamin from '@/app/admin/(dashboard)/_state/ppid/indeks_kepuasan_masyarakat/grafikBerdasarkanJenisKelamin'; -import colors from '@/con/colors'; -import { Box, Center, Flex, Skeleton, Stack, Text, 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 stategrafikBerdasarkanJenisKelamin = useProxy(grafikBerdasarkanJenisKelamin) - const [mounted, setMounted] = useState(false); - const [donutData, setDonutData] = useState([]); - - 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 stategrafikBerdasarkanJenisKelamin.findMany.load(); - if (stategrafikBerdasarkanJenisKelamin.findMany.data) { - updateChartData(stategrafikBerdasarkanJenisKelamin.findMany.data); - } - }; - - if(!stategrafikBerdasarkanJenisKelamin.findMany.data) return - Grafik Berdasarkan Jenis Kelamin Responden - - - - return ( - - Grafik Berdasarkan Jenis Kelamin Responden - {mounted && donutData.length > 0 && ( - -
- - - - {donutData.map((entry, index) => ( - - ))} - - -
- - - Perempuan: {donutData.find((entry) => entry.name === 'Perempuan')?.value} - - - - Laki-laki: {donutData.find((entry) => entry.name === 'Laki-laki')?.value} - -
- )} -
- ); -} - -export default GrafikBerdasarkanJenisKelamin; diff --git a/src/app/darmasaba/(pages)/ppid/ikm-desa-darmasaba/grafik_berdasarkan_pilihan_responden/page.tsx b/src/app/darmasaba/(pages)/ppid/ikm-desa-darmasaba/grafik_berdasarkan_pilihan_responden/page.tsx deleted file mode 100644 index ae20835b..00000000 --- a/src/app/darmasaba/(pages)/ppid/ikm-desa-darmasaba/grafik_berdasarkan_pilihan_responden/page.tsx +++ /dev/null @@ -1,96 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -'use client' -import grafikBerdasarkanResponden from '@/app/admin/(dashboard)/_state/ppid/indeks_kepuasan_masyarakat/grafikBerdasarkanResponden'; -import colors from '@/con/colors'; -import { Box, Center, Flex, Skeleton, Stack, Text, 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 GrafikBerdasarkanResponden() { - const stategrafikBerdasarkanResponden = useProxy(grafikBerdasarkanResponden) - const [donutData, setDonutData] = useState([]); - 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 stategrafikBerdasarkanResponden.findMany.load(); - if (stategrafikBerdasarkanResponden.findMany.data) { - updateChartData(stategrafikBerdasarkanResponden.findMany.data); - } - }; - - if (!stategrafikBerdasarkanResponden.findMany.data) return - Grafik Berdasarkan Responden - - - return ( - - Grafik Berdasarkan Responden - {mounted && donutData.length > 0 && ( - -
- - - {donutData.map((entry, index) => ( - - ))} - - -
- - - Sangat Baik: {donutData.find((entry) => entry.name === 'sangatbaik')?.value} - - - - Baik: {donutData.find((entry) => entry.name === 'baik')?.value} - - - - Kurang Baik: {donutData.find((entry) => entry.name === 'kurangbaik')?.value} - - - - Tidak Baik: {donutData.find((entry) => entry.name === 'tidakbaik')?.value} - -
- )} -
- ); -} - -export default GrafikBerdasarkanResponden; diff --git a/src/app/darmasaba/(pages)/ppid/ikm-desa-darmasaba/grafik_berdasarkan_umur_responden/page.tsx b/src/app/darmasaba/(pages)/ppid/ikm-desa-darmasaba/grafik_berdasarkan_umur_responden/page.tsx deleted file mode 100644 index a64f9822..00000000 --- a/src/app/darmasaba/(pages)/ppid/ikm-desa-darmasaba/grafik_berdasarkan_umur_responden/page.tsx +++ /dev/null @@ -1,97 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -'use client' -import grafikBerdasarkanUmur from '@/app/admin/(dashboard)/_state/ppid/indeks_kepuasan_masyarakat/grafikBerdasarkanUmur'; -import colors from '@/con/colors'; -import { Box, Center, Flex, Skeleton, Stack, Text, 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 GrafikBerdasarakanUmur() { - const stategrafikBerdasarkanUmur = useProxy(grafikBerdasarkanUmur) - const [donutData, setDonutData] = useState([]); - 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 stategrafikBerdasarkanUmur.findMany.load(); - if (stategrafikBerdasarkanUmur.findMany.data) { - updateChartData(stategrafikBerdasarkanUmur.findMany.data); - } - } - - if(!stategrafikBerdasarkanUmur.findMany.data) return - Grafik Berdasarkan Umur Responden - - - return ( - - Grafik Berdasarkan Umur Responden - {mounted && donutData.length > 0 && ( - -
- - - {donutData.map((entry, index) => ( - - ))} - - -
- - - 17 - 25 tahun: {donutData.find((entry) => entry.name === 'remaja')?.value} - - - - 26 - 45 tahun: {donutData.find((entry) => entry.name === 'dewasa')?.value} - - - - 46 - 60 tahun: {donutData.find((entry) => entry.name === 'orangtua')?.value} - - - - di atas 60 tahun: {donutData.find((entry) => entry.name === 'lansia')?.value} - -
- )} -
- ); -} - -export default GrafikBerdasarakanUmur; diff --git a/src/app/darmasaba/(pages)/ppid/ikm-desa-darmasaba/grafik_hasil_kepuasan_masyarakat/page.tsx b/src/app/darmasaba/(pages)/ppid/ikm-desa-darmasaba/grafik_hasil_kepuasan_masyarakat/page.tsx deleted file mode 100644 index 8dd2c57d..00000000 --- a/src/app/darmasaba/(pages)/ppid/ikm-desa-darmasaba/grafik_hasil_kepuasan_masyarakat/page.tsx +++ /dev/null @@ -1,57 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -'use client' -import grafikHasilKepuasanMasyarakat from '@/app/admin/(dashboard)/_state/ppid/indeks_kepuasan_masyarakat/grafikHasilKepuasan'; -import colors from '@/con/colors'; -import { Box, Skeleton, Stack, Text, Title } from '@mantine/core'; -import { useMediaQuery, useShallowEffect } from '@mantine/hooks'; -import { useEffect, useState } from 'react'; -import { Bar, BarChart, Legend, Tooltip, XAxis, YAxis } from 'recharts'; -import { useProxy } from 'valtio/utils'; - -function GrafikHasilKepuasan() { - const grafikHasilKepuasan = useProxy(grafikHasilKepuasanMasyarakat) - const [chartData, setChartData] = useState([]); - 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(); - }, []); - - if(!grafikHasilKepuasan.findMany.data) return - Grafik Hasil Kepuasan Masyarakat Terhadap Pelayanan Publik - - - - return ( - - - Grafik Hasil Kepuasan Masyarakat Terhadap Pelayanan Publik - - - {mounted && chartData.length > 0 && ( - - - - - - - - )} - - - ); -} - -export default GrafikHasilKepuasan; diff --git a/src/app/darmasaba/(pages)/ppid/ikm-desa-darmasaba/page.tsx b/src/app/darmasaba/(pages)/ppid/ikm-desa-darmasaba/page.tsx index 13ffbee0..a9af79c6 100644 --- a/src/app/darmasaba/(pages)/ppid/ikm-desa-darmasaba/page.tsx +++ b/src/app/darmasaba/(pages)/ppid/ikm-desa-darmasaba/page.tsx @@ -1,47 +1,674 @@ -import colors from '@/con/colors'; -import { Box, Paper, Stack, Text } from '@mantine/core'; -import BackButton from '../../desa/layanan/_com/BackButto'; -import GrafikBerdasarkanJenisKelamin from './grafik_berdasarkan_jenis_kelamin/page'; -import GrafikBerdasarkanResponden from './grafik_berdasarkan_pilihan_responden/page'; -import GrafikBerdasarakanUmur from './grafik_berdasarkan_umur_responden/page'; -import GrafikHasilKepuasan from './grafik_hasil_kepuasan_masyarakat/page'; +/* eslint-disable @typescript-eslint/no-explicit-any */ +"use client"; +import indeksKepuasanState from "@/app/admin/(dashboard)/_state/landing-page/indeks-kepuasan"; +import colors from "@/con/colors"; +import { BarChart, PieChart } from '@mantine/charts'; +import { Box, Button, Center, Container, Flex, Group, Modal, Paper, Select, SimpleGrid, Skeleton, Stack, Text, TextInput, Title } from "@mantine/core"; +import { useDisclosure, useShallowEffect } from "@mantine/hooks"; +import { useState } from "react"; +import { useProxy } from "valtio/utils"; -function Page() { +interface ChartDataItem { + name: string; + value: number; + color: string; + label?: string; +} + + + +function Kepuasan() { + const state = useProxy(indeksKepuasanState.responden); + const { data, loading } = state.findMany; + const [donutDataJenisKelamin, setDonutDataJenisKelamin] = useState([]); + const [donutDataRating, setDonutDataRating] = useState([]); + const [donutDataKelompokUmur, setDonutDataKelompokUmur] = useState([]); + const [barChartData, setBarChartData] = useState>([]); + const [opened, { open, close }] = useDisclosure(false) + + const resetForm = () => { + state.create.form = { + ...state.create.form, + name: "", + tanggal: "", + jenisKelaminId: "", + ratingId: "", + kelompokUmurId: "", + } + } + + useShallowEffect(() => { + indeksKepuasanState.jenisKelaminResponden.findMany.load() + indeksKepuasanState.pilihanRatingResponden.findMany.load() + indeksKepuasanState.kelompokUmurResponden.findMany.load() + }) + + const handleSubmit = async () => { + try { + const id = await state.create.create(); + if (typeof id !== 'undefined') { + const idStr = String(id); + await state.findUnique.load(idStr); + } + resetForm(); + close() + } catch (error) { + console.error('Error submitting form:', error); + } + } + + // Load data on component mount + useShallowEffect(() => { + if (!data && !loading) { + state.findMany.load(1, 1000); // Load first page with a large limit to get all data + return; + } + + if (data && data.length > 0) { + // Hitung total berdasarkan jenis kelamin + const totalLaki = data.filter((item: any) => item.jenisKelamin?.name?.toLowerCase() === 'laki-laki').length; + const totalPerempuan = data.filter((item: any) => item.jenisKelamin?.name?.toLowerCase() === 'perempuan').length; + + // Hitung total berdasarkan rating + const totalSangatBaik = data.filter((item: any) => item.rating?.name?.toLowerCase() === 'sangat baik').length; + const totalBaik = data.filter((item: any) => item.rating?.name?.toLowerCase() === 'baik').length; + const totalKurangBaik = data.filter((item: any) => item.rating?.name?.toLowerCase() === 'kurang baik').length; + const totalSangatKurangBaik = data.filter((item: any) => item.rating?.name?.toLowerCase() === 'sangat kurang baik').length; + + // Hitung total berdasarkan kelompok umur + const totalMuda = data.filter((item: any) => item.kelompokUmur?.name?.toLowerCase() === 'muda').length; + const totalDewasa = data.filter((item: any) => item.kelompokUmur?.name?.toLowerCase() === 'dewasa').length; + const totalLansia = data.filter((item: any) => item.kelompokUmur?.name?.toLowerCase() === 'lansia').length; + + // Update gender chart data + setDonutDataJenisKelamin([ + { name: 'Laki-laki', value: totalLaki, color: colors['blue-button'] }, + { name: 'Perempuan', value: totalPerempuan, color: '#10A85AFF' }, + ]); + + // Update rating chart data + setDonutDataRating([ + { name: 'Sangat Baik', value: totalSangatBaik, color: colors['blue-button'] }, + { name: 'Baik', value: totalBaik, color: '#10A85AFF' }, + { name: 'Kurang Baik', value: totalKurangBaik, color: '#FFA500' }, + { name: 'Sangat Kurang Baik', value: totalSangatKurangBaik, color: '#FF4500' }, + ]); + + // Update age group chart data + setDonutDataKelompokUmur([ + { name: 'Muda', value: totalMuda, color: colors['blue-button'] }, + { name: 'Dewasa', value: totalDewasa, color: '#10A85AFF' }, + { name: 'Lansia', value: totalLansia, color: '#FFA500' }, + ]); + + // Process data for bar chart (group by month) + const monthYearMap = new Map(); + + data.forEach((item: any) => { + // Try both createdAt and tanggal fields + const dateValue = item.tanggal || item.createdAt; + if (!dateValue) return; + + const parsedDate = new Date(dateValue); + if (isNaN(parsedDate.getTime())) return; + + const month = parsedDate.getMonth() + 1; + const year = parsedDate.getFullYear(); + const monthYearKey = `${year}-${String(month).padStart(2, '0')}`; + + monthYearMap.set(monthYearKey, (monthYearMap.get(monthYearKey) || 0) + 1); + }); + + // Convert map to array and sort by date + const barData = Array.from(monthYearMap.entries()) + .map(([key, count]) => { + const [year, month] = key.split('-'); + const monthName = new Date(Number(year), Number(month) - 1, 1) + .toLocaleString('id-ID', { month: 'long' }); + return { + month: `${monthName} ${year}`, + count, + sortKey: parseInt(`${year}${String(month).padStart(2, '0')}`, 10) + }; + }) + .sort((a, b) => a.sortKey - b.sortKey) + .map(({ month, count }) => ({ month, count })); + + setBarChartData(barData); + } + }, [data]); + + if ((loading && !data) || !data) { + return ( + + + + + + + + + ); + } + + if (data.length === 0) { + return ( + + +
+ Indeks Kepuasan Masyarakat +
+
+ +
+
+ + + + + + Pelayanan Terhadap Publik Desa Darmasaba + + Total Responden + + {state.findMany.total.toLocaleString('id-ID')} + + + + + + + + + {/* Chart Jenis Kelamin */} + + + Jenis Kelamin + {donutDataJenisKelamin.every(item => item.value === 0) ? ( + + Belum ada data untuk ditampilkan dalam grafik + + ) : ( + + + +
+ +
+
+ + {donutDataJenisKelamin.map((entry) => ( + + + {entry.name}: {entry.value} + + ))} + +
+
+ )} +
+
+ + {/* Chart Rating */} + + + Pilihan + {donutDataRating.every(item => item.value === 0) ? ( + + Belum ada data untuk ditampilkan dalam grafik + + ) : ( + + + +
+ +
+
+ + + {donutDataRating.map((entry) => ( + + + + {entry.name}: {entry.value} + + + ))} + + +
+
+ )} +
+
+ + {/* Chart Kelompok Umur */} + + + Umur + {donutDataKelompokUmur.every(item => item.value === 0) ? ( + + Belum ada data untuk ditampilkan dalam grafik + + ) : ( + + + +
+ +
+
+ + + {donutDataKelompokUmur.map((entry) => ( + + + + {entry.name}: {entry.value} + + + ))} + + +
+
+ )} +
+
+
+
+
+
+ {/* Modal */} + + + + { + state.create.form.name = val.currentTarget.value; + }} + /> + { + state.create.form.tanggal = val.currentTarget.value; + }} + /> + { + state.create.form.ratingId = val ?? ""; + }} + data={ + (indeksKepuasanState.pilihanRatingResponden.findMany.data || []) + .filter(Boolean) // Hapus null, undefined, dll + .map((item) => ({ + value: item.id, + label: item.name || 'Tanpa Nama', + })) + } + disabled={indeksKepuasanState.pilihanRatingResponden.findMany.loading} + /> + { + state.create.form.jenisKelaminId = val ?? ""; + }} + data={ + (indeksKepuasanState.jenisKelaminResponden.findMany.data || []) + .filter(Boolean) // Hapus null, undefined, dll + .map((item) => ({ + value: item.id, + label: item.name || 'Tanpa Nama', + })) + } + disabled={indeksKepuasanState.jenisKelaminResponden.findMany.loading} + /> + { + state.create.form.kelompokUmurId = val ?? ""; + }} + data={ + (indeksKepuasanState.kelompokUmurResponden.findMany.data || []) + .filter(Boolean) // Hapus null, undefined, dll + .map((item) => ({ + value: item.id, + label: item.name || 'Tanpa Nama', + })) + } + disabled={indeksKepuasanState.kelompokUmurResponden.findMany.loading} + /> + + - - - - - - - - - - - +
); } -export default Page; +export default Kepuasan;