From 0777b00a7dd07bff03a59b345742c1faf3d4402b Mon Sep 17 00:00:00 2001 From: nico Date: Wed, 13 Aug 2025 10:51:17 +0800 Subject: [PATCH] Sinkronisasi UI & API Admin - User Menu Landing Page Submenu Indeks Kepuasan Masyarakat --- .../_com/main-page/kepuasan/index.tsx | 524 +++++++++++++----- 1 file changed, 386 insertions(+), 138 deletions(-) diff --git a/src/app/darmasaba/_com/main-page/kepuasan/index.tsx b/src/app/darmasaba/_com/main-page/kepuasan/index.tsx index 56796d46..27d434c6 100644 --- a/src/app/darmasaba/_com/main-page/kepuasan/index.tsx +++ b/src/app/darmasaba/_com/main-page/kepuasan/index.tsx @@ -1,109 +1,201 @@ +/* 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, Center, Container, Flex, Paper, SimpleGrid, Stack, Text } from "@mantine/core"; -import { useMediaQuery } from "@mantine/hooks"; +import { Box, Button, Center, Container, Flex, 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"; -const dataBarChart = [ - { - bulan: "Januari", - responden: "480" - }, - { - bulan: "Februari", - responden: "730" - }, - { - bulan: "Maret", - responden: "740" - }, - { - bulan: "April", - responden: "80" - }, - { - bulan: "Mei", - responden: "250" - }, - { - bulan: "Juni", - responden: "900" - }, - { - bulan: "Juli", - responden: "230" - }, - { - bulan: "Agustus", - responden: "255" - }, - { - bulan: "September", - responden: "650" - }, - { - bulan: "Oktober", - responden: "730" - }, - { - bulan: "November", - responden: "800" - }, - { - bulan: "Desember", - responden: "1000" - }, +interface ChartDataItem { + name: string; + value: number; + color: string; + label?: string; +} -] -const dataPieChart = [ - { name: "Laki-laki", value: 70, color: colors["blue-button"] }, - { name: "Perempuan", value: 30, color: colors.orange }, -] - -const dataPieChart2 = [ - { name: "Sangat Baik", value: 75, color: colors["blue-button"] }, - { name: "Buruk", value: 25, color: colors.orange }, -] - -const dataPieChart3 = [ - { name: "Umur 18-44", value: 65, color: colors["blue-button"] }, - { name: "Umur 44-60+", value: 35, color: colors.orange }, -] function Kepuasan() { - const isMobile = useMediaQuery('(max-width: 768px)'); + 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 ( + + + Belum ada data untuk ditampilkan + + + ); + } return (
- Indeks Kepuasan Masyarakat + Indeks Kepuasan Masyarakat +
+ Ukur kebahagiaan warga, tingkatkan layanan desa! Dengan partisipasi aktif masyarakat, kami berkomitmen untuk terus memperbaiki layanan agar lebih transparan, efektif, dan sesuai dengan kebutuhan warga. Kepuasan Anda adalah prioritas utama kami dalam membangun desa yang lebih baik! +
+
- Ukur kebahagiaan warga, tingkatkan layanan desa! Dengan partisipasi aktif masyarakat, kami berkomitmen untuk terus memperbaiki layanan agar lebih transparan, efektif, dan sesuai dengan kebutuhan warga. Kepuasan Anda adalah prioritas utama kami dalam membangun desa yang lebih baik!
- - Pelayanan Terhadap Publik Desa Darmasaba - - Total Responden - 2500 - - - + + + Pelayanan Terhadap Publik Desa Darmasaba + + Total Responden + + {state.findMany.total.toLocaleString('id-ID')} + + + + + - - - Jenis Kelamin - - + + Jenis Kelamin + {donutDataJenisKelamin.every(item => item.value === 0) ? ( + + Belum ada data untuk ditampilkan dalam grafik + + ) : ( + + + +
+ +
+
+ + {donutDataJenisKelamin.map((entry) => ( + + + {entry.name}: {entry.value} + + ))} + +
+
+ )} +
+
- /> -
-
-
- - - Pilihan - - + + Pilihan + {donutDataRating.every(item => item.value === 0) ? ( + + Belum ada data untuk ditampilkan dalam grafik + + ) : ( + + + +
+ +
+
+ + + {donutDataRating.map((entry) => ( + + + + {entry.name}: {entry.value} + + + ))} + + +
+
+ )} +
+
- /> -
- - - - - Umur - - - - - + {/* 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} + /> +