From a035039b2cb2da77b8600f540a0a3b0996882f38 Mon Sep 17 00:00:00 2001 From: nico Date: Wed, 13 Aug 2025 09:47:21 +0800 Subject: [PATCH] Admin Fix Chart Bar PerMonth Submenu IKM, Menu PPID --- .../_state/landing-page/indeks-kepuasan.ts | 44 +++- .../indeks-kepuasan-masyarakat/page.tsx | 245 +++++++++++------- .../responden/[id]/edit/page.tsx | 153 +++++++---- .../responden/[id]/page.tsx | 45 +++- 4 files changed, 329 insertions(+), 158 deletions(-) diff --git a/src/app/admin/(dashboard)/_state/landing-page/indeks-kepuasan.ts b/src/app/admin/(dashboard)/_state/landing-page/indeks-kepuasan.ts index d503b30a..504f0c11 100644 --- a/src/app/admin/(dashboard)/_state/landing-page/indeks-kepuasan.ts +++ b/src/app/admin/(dashboard)/_state/landing-page/indeks-kepuasan.ts @@ -117,8 +117,48 @@ const responden = proxy({ id: "", form: { ...defaultFormResponden }, loading: false, - async byId() { - // Method implementation if needed + async load(id: string) { + if (!id) { + toast.warn("ID tidak valid"); + return null; + } + + try { + responden.update.loading = true; + + const response = await fetch(`/api/landingpage/responden/${id}`, { + method: "GET", + headers: { + "Content-Type": "application/json", + }, + }); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + const result = await response.json(); + if (result?.success) { + const data = result.data; + this.id = data.id; + this.form = { + name: data.name, + tanggal: data.tanggal, + jenisKelaminId: data.jenisKelaminId, + ratingId: data.ratingId, + kelompokUmurId: data.kelompokUmurId, + }; + return data; + } else { + throw new Error(result?.message || "Gagal memuat data"); + } + } catch (error) { + console.error("Error loading responden:", error); + toast.error( + error instanceof Error ? error.message : "Gagal memuat data" + ); + return null; + } finally { + responden.update.loading = false; + } }, async submit() { const id = this.id; diff --git a/src/app/admin/(dashboard)/ppid/ikm-desa-darmasaba/indeks-kepuasan-masyarakat/page.tsx b/src/app/admin/(dashboard)/ppid/ikm-desa-darmasaba/indeks-kepuasan-masyarakat/page.tsx index 6ab35792..1f466e3c 100644 --- a/src/app/admin/(dashboard)/ppid/ikm-desa-darmasaba/indeks-kepuasan-masyarakat/page.tsx +++ b/src/app/admin/(dashboard)/ppid/ikm-desa-darmasaba/indeks-kepuasan-masyarakat/page.tsx @@ -1,7 +1,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ 'use client'; import colors from '@/con/colors'; -import { PieChart } from '@mantine/charts'; // ✅ Ganti recharts dengan Mantine +import { PieChart, BarChart } from '@mantine/charts'; // ✅ Ganti recharts dengan Mantine import { Box, Center, @@ -25,12 +25,15 @@ interface ChartDataItem { label?: string; } + + function Page() { 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>([]); useShallowEffect(() => { if (data) { @@ -69,6 +72,41 @@ function Page() { { 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]); @@ -92,105 +130,120 @@ function Page() { return ( - - + {/* Bar Chart - Data per Tanggal */} + + Jumlah Responden per Bulan + + + + - {/* 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 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 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} - - ))} - -
- )} -
-
+ {/* Chart Kelompok Umur */} + + + Umur + {donutDataKelompokUmur.every(item => item.value === 0) ? ( + + Belum ada data untuk ditampilkan dalam grafik + + ) : ( + +
+ +
+ + {donutDataKelompokUmur.map((entry) => ( + + + {entry.name}: {entry.value} + + ))} + +
+ )} +
+
); diff --git a/src/app/admin/(dashboard)/ppid/ikm-desa-darmasaba/responden/[id]/edit/page.tsx b/src/app/admin/(dashboard)/ppid/ikm-desa-darmasaba/responden/[id]/edit/page.tsx index ecf1674a..8c153eaa 100644 --- a/src/app/admin/(dashboard)/ppid/ikm-desa-darmasaba/responden/[id]/edit/page.tsx +++ b/src/app/admin/(dashboard)/ppid/ikm-desa-darmasaba/responden/[id]/edit/page.tsx @@ -1,35 +1,78 @@ 'use client' /* eslint-disable react-hooks/exhaustive-deps */ -import React, { useEffect } from 'react'; +import React, { useEffect, useState } from 'react'; import { useRouter, useParams } from 'next/navigation'; import { useProxy } from 'valtio/utils'; import colors from '@/con/colors'; import { Box, Button, Paper, Stack, Title, TextInput, Text, Select } from '@mantine/core'; import { IconArrowBack } from '@tabler/icons-react'; import indeksKepuasanState from '@/app/admin/(dashboard)/_state/landing-page/indeks-kepuasan'; +import { toast } from 'react-toastify'; + +interface FormResponden { + name: string; + tanggal: string; + jenisKelaminId: string; + ratingId: string; + kelompokUmurId: string; +} function EditResponden() { const router = useRouter() const params = useParams() as { id: string } const state = useProxy(indeksKepuasanState.responden) const id = params.id - + const [formData, setFormData] = useState({ + name: '', + tanggal: '', + jenisKelaminId: '', + ratingId: '', + kelompokUmurId: '', + }) useEffect(() => { - if (id) { - state.findUnique.load(id).then(() => { - const data = state.findUnique.data + indeksKepuasanState.jenisKelaminResponden.findMany.load(); + indeksKepuasanState.pilihanRatingResponden.findMany.load(); + indeksKepuasanState.kelompokUmurResponden.findMany.load(); + + const loadResponden = async () => { + const id = params?.id as string; + if (!id) return; + + 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: data.tanggal ? new Date(data.tanggal).toISOString() : new Date().toISOString(), - jenisKelaminId: data.jenisKelaminId || '', - ratingId: data.ratingId || '', - kelompokUmurId: data.kelompokUmurId || '', - } + name: data.name, + tanggal: formattedDate, + jenisKelaminId: data.jenisKelaminId, + ratingId: data.ratingId, + kelompokUmurId: data.kelompokUmurId, + }; + + setFormData({ + name: data.name, + tanggal: data.tanggal, + jenisKelaminId: data.jenisKelaminId, + ratingId: data.ratingId, + kelompokUmurId: data.kelompokUmurId, + }); + + } - }) + } catch (error) { + console.error("Error loading program penghijauan:", error); + toast.error("Gagal memuat data program penghijauan"); + } } - }, [id]) + + loadResponden(); + }, [params?.id]); const handleSubmit = async () => { state.update.id = id; @@ -51,74 +94,84 @@ function EditResponden() { label="Nama" type='text' placeholder="masukkan nama" - value={state.update.form.name} + value={formData.name} onChange={(val) => { - state.update.form.name = val.currentTarget.value; + setFormData({ + ...formData, + name: val.currentTarget.value + }) }} /> { - state.update.form.tanggal = val.currentTarget.value; + value={formData.tanggal} + onChange={(e) => { + setFormData({ + ...formData, + tanggal: e.currentTarget.value, // ✅ sudah format YYYY-MM-DD + }); }} /> { - state.update.form.ratingId = val || ""; - }} + { - state.update.form.kelompokUmurId = val || ""; - }} + key={"kelompokUmur"} + value={formData.kelompokUmurId} + onChange={(val) => setFormData({ ...formData, kelompokUmurId: val || "" })} label={Kelompok Umur} placeholder='Pilih kelompok umur' data={ - indeksKepuasanState.kelompokUmurResponden.findMany.data?.map((v) => ({ - value: v.id, - label: v.nama - })) || [] + (indeksKepuasanState.kelompokUmurResponden.findMany.data || []) + .filter(Boolean) + .map((v) => ({ + value: v.id || '', + label: typeof v.name === 'string' ? v.name : 'Tanpa Nama' + })) } + disabled={indeksKepuasanState.kelompokUmurResponden.findMany.loading} clearable searchable required - error={!state.update.form.kelompokUmurId ? "Pilih kelompok umur" : undefined} + error={!formData.kelompokUmurId ? "Pilih kelompok umur" : undefined} /> @@ -57,9 +57,9 @@ export default function DetailResponden(){ Tanggal { - stateDetail.findUnique.data?.tanggal + stateDetail.findUnique.data?.tanggal ? new Date(stateDetail.findUnique.data.tanggal).toLocaleDateString('id-ID') - : '-' + : '-' } @@ -74,6 +74,31 @@ export default function DetailResponden(){ Kelompok Umur {stateDetail.findUnique.data?.kelompokUmur?.name} + + + + @@ -85,7 +110,7 @@ export default function DetailResponden(){ onClose={() => setModalHapus(false)} onConfirm={handleHapus} text="Apakah anda yakin ingin menghapus responden ini?" - /> + /> ) } \ No newline at end of file