Fix QC Kak Inno Admin, Fix QC Keano UI User, Fix QC Pak jun tabel apbdes

This commit is contained in:
2025-11-12 17:42:31 +08:00
parent 417a8937f5
commit 9622eb5a9a
354 changed files with 11444 additions and 4012 deletions

View File

@@ -1,19 +1,21 @@
'use client'
/* eslint-disable react-hooks/exhaustive-deps */
/* eslint-disable @typescript-eslint/no-unused-vars */
'use client';
import indeksKepuasanState from '@/app/admin/(dashboard)/_state/landing-page/indeks-kepuasan';
import colors from '@/con/colors';
import {
Box,
Button,
Group,
Loader,
Paper,
Select,
Stack,
Text,
TextInput,
Title
Title,
} from '@mantine/core';
import { IconArrowBack, IconDeviceFloppy } from '@tabler/icons-react';
import { IconArrowBack } from '@tabler/icons-react';
import { useParams, useRouter } from 'next/navigation';
import { useCallback, useEffect, useState } from 'react';
import { toast } from 'react-toastify';
@@ -30,9 +32,13 @@ interface FormResponden {
function EditResponden() {
const router = useRouter();
const params = useParams() as { id: string };
const state = useProxy(indeksKepuasanState.responden);
const id = params.id;
// ✅ proxy asli untuk mutasi
const state = indeksKepuasanState.responden;
// ✅ snapshot untuk re-render (read-only)
const snapshot = useProxy(indeksKepuasanState.responden);
const [formData, setFormData] = useState<FormResponden>({
name: '',
tanggal: '',
@@ -41,31 +47,43 @@ function EditResponden() {
kelompokUmurId: '',
});
// Helper untuk load pilihan select
const [originalData, setOriginalData] = useState<FormResponden>({
name: '',
tanggal: '',
jenisKelaminId: '',
ratingId: '',
kelompokUmurId: '',
});
const [isSubmitting, setIsSubmitting] = useState(false);
// 🔹 Load data pilihan select
const loadSelectOptions = useCallback(() => {
indeksKepuasanState.jenisKelaminResponden.findMany.load();
indeksKepuasanState.pilihanRatingResponden.findMany.load();
indeksKepuasanState.kelompokUmurResponden.findMany.load();
}, []);
// Load data responden
// 🔹 Load data responden by ID
const loadResponden = useCallback(async () => {
if (!id) return;
try {
const data = await state.update.load(id);
if (!data) return;
setFormData({
name: data.name,
tanggal: data.tanggal,
jenisKelaminId: data.jenisKelaminId,
ratingId: data.ratingId,
kelompokUmurId: data.kelompokUmurId,
});
const newForm = {
name: data.name || '',
tanggal: data.tanggal || '',
jenisKelaminId: data.jenisKelaminId || '',
ratingId: data.ratingId || '',
kelompokUmurId: data.kelompokUmurId || '',
};
setFormData(newForm);
setOriginalData(newForm);
} catch (error) {
console.error("Error loading responden:", error);
toast.error("Gagal memuat data responden");
console.error('Error loading responden:', error);
toast.error('Gagal memuat data responden');
}
}, [id]);
@@ -74,14 +92,30 @@ function EditResponden() {
loadResponden();
}, [loadSelectOptions, loadResponden]);
// 🔹 Submit data
const handleSubmit = async () => {
state.update.id = id;
state.update.form = { ...formData }; // sinkronisasi manual
await state.update.submit();
router.push('/admin/landing-page/indeks-kepuasan-masyarakat/responden');
try {
setIsSubmitting(true);
state.update.id = id;
state.update.form = { ...formData }; // mutasi proxy asli ✅
await state.update.submit();
toast.success('Responden berhasil diperbarui!');
router.push('/admin/landing-page/indeks-kepuasan-masyarakat/responden');
} catch (error) {
console.error('Error updating responden:', error);
toast.error('Gagal memperbarui responden');
} finally {
setIsSubmitting(false);
}
};
// Reusable Select component
// 🔹 Reset form ke data awal
const handleResetForm = () => {
setFormData({ ...originalData });
toast.info('Form dikembalikan ke data awal');
};
// 🔹 Reusable Select component
const ControlledSelect = ({
label,
value,
@@ -98,30 +132,28 @@ function EditResponden() {
error?: string;
placeholder?: string;
loading?: boolean;
}) => {
return (
<Select
label={<Text fw="bold" fz="sm" mb={4}>{label}</Text>}
value={value}
onChange={(val) => onChange(val || '')}
data={options}
placeholder={placeholder}
disabled={loading}
clearable
searchable
required
radius="md"
error={error}
/>
);
};
}) => (
<Select
label={<Text fw="bold" fz="sm" mb={4}>{label}</Text>}
value={value}
onChange={(val) => onChange(val || '')}
data={options}
placeholder={placeholder}
disabled={loading}
clearable
searchable
required
radius="md"
error={error}
/>
);
return (
<Box px={{ base: 'sm', md: 'lg' }} py="md">
<Group mb="md">
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
<IconArrowBack color={colors['blue-button']} size={24} />
</Button>
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
<IconArrowBack color={colors['blue-button']} size={24} />
</Button>
<Title order={4} ml="sm" c="dark">
Edit Responden
</Title>
@@ -144,6 +176,7 @@ function EditResponden() {
radius="md"
required
/>
<TextInput
label="Tanggal"
type="date"
@@ -158,7 +191,6 @@ function EditResponden() {
value={formData.jenisKelaminId}
onChange={(val) => setFormData({ ...formData, jenisKelaminId: val })}
options={(indeksKepuasanState.jenisKelaminResponden.findMany.data || [])
.filter(Boolean)
.map((v) => ({ value: v.id || '', label: v.name || 'Tanpa Nama' }))}
loading={indeksKepuasanState.jenisKelaminResponden.findMany.loading}
error={!formData.jenisKelaminId ? 'Pilih jenis kelamin' : undefined}
@@ -169,7 +201,6 @@ function EditResponden() {
value={formData.ratingId}
onChange={(val) => setFormData({ ...formData, ratingId: val })}
options={(indeksKepuasanState.pilihanRatingResponden.findMany.data || [])
.filter(Boolean)
.map((v) => ({ value: v.id || '', label: v.name || 'Tanpa Nama' }))}
loading={indeksKepuasanState.pilihanRatingResponden.findMany.loading}
error={!formData.ratingId ? 'Pilih rating' : undefined}
@@ -180,23 +211,33 @@ function EditResponden() {
value={formData.kelompokUmurId}
onChange={(val) => setFormData({ ...formData, kelompokUmurId: val })}
options={(indeksKepuasanState.kelompokUmurResponden.findMany.data || [])
.filter(Boolean)
.map((v) => ({ value: v.id || '', label: v.name || 'Tanpa Nama' }))}
loading={indeksKepuasanState.kelompokUmurResponden.findMany.loading}
error={!formData.kelompokUmurId ? 'Pilih kelompok umur' : undefined}
/>
<Group justify="flex-end" mt="md">
<Button variant="light" color="red" onClick={() => router.back()}>
<Group justify="right">
<Button
variant="outline"
color="gray"
radius="md"
size="md"
onClick={handleResetForm}
>
Batal
</Button>
<Button
leftSection={<IconDeviceFloppy size={20} />}
onClick={handleSubmit}
loading={state.update.loading}
color={colors['blue-button']}
radius="md"
size="md"
style={{
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
color: '#fff',
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
}}
>
Simpan
{isSubmitting ? <Loader size="sm" color="white" /> : 'Simpan'}
</Button>
</Group>
</Stack>

View File

@@ -1,21 +1,20 @@
'use client'
/* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable @typescript-eslint/no-explicit-any */
import React from 'react';
import { useRouter } from 'next/navigation';
import grafikBerdasarkanJenisKelamin from '@/app/admin/(dashboard)/_state/ppid/indeks_kepuasan_masyarakat/grafikBerdasarkanJenisKelamin';
import { useProxy } from 'valtio/utils';
import { useState } from 'react';
import colors from '@/con/colors';
import { Box, Button, Paper, Stack, Title, TextInput, Select, Text } from '@mantine/core';
import { IconArrowBack } from '@tabler/icons-react';
import indeksKepuasanState from '@/app/admin/(dashboard)/_state/landing-page/indeks-kepuasan';
import colors from '@/con/colors';
import { Box, Button, Group, Loader, Paper, Select, Stack, TextInput, Title } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconArrowBack } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useState } from 'react';
import { useProxy } from 'valtio/utils';
function RespondenCreate() {
const router = useRouter();
const stategrafikBerdasarkanResponden = useProxy(indeksKepuasanState.responden)
const [donutData, setDonutData] = useState<any[]>([]);
const [isSubmitting, setIsSubmitting] = useState(false);
const resetForm = () => {
stategrafikBerdasarkanResponden.create.form = {
@@ -35,6 +34,7 @@ function RespondenCreate() {
})
const handleSubmit = async () => {
setIsSubmitting(true);
try {
const id = await stategrafikBerdasarkanResponden.create.create();
if (typeof id !== 'undefined') {
@@ -45,9 +45,11 @@ function RespondenCreate() {
}
}
resetForm();
router.push("/admin/ppid/ikm-desa-darmasaba/responden");
router.push("/admin/landing-page/indeks-kepuasan-masyarakat/responden");
} catch (error) {
console.error('Error submitting form:', error);
} finally {
setIsSubmitting(false);
}
}
return (
@@ -64,7 +66,7 @@ function RespondenCreate() {
label="Nama"
type='text'
placeholder="masukkan nama"
defaultValue={stategrafikBerdasarkanResponden.create.form.name}
value={stategrafikBerdasarkanResponden.create.form.name}
onChange={(val) => {
stategrafikBerdasarkanResponden.create.form.name = val.currentTarget.value;
}}
@@ -73,7 +75,7 @@ function RespondenCreate() {
label="Tanggal"
type="date"
placeholder="masukkan tanggal"
defaultValue={stategrafikBerdasarkanResponden.create.form.tanggal}
value={stategrafikBerdasarkanResponden.create.form.tanggal}
onChange={(val) => {
stategrafikBerdasarkanResponden.create.form.tanggal = val.currentTarget.value;
}}
@@ -96,24 +98,24 @@ function RespondenCreate() {
}
disabled={indeksKepuasanState.jenisKelaminResponden.findMany.loading}
/>
<Select
<Select
key={"rating_responden"}
label={"Rating"}
placeholder={indeksKepuasanState.pilihanRatingResponden.findMany.loading ? 'Memuat...' : 'Pilih rating'}
value={stategrafikBerdasarkanResponden.create.form.ratingId || ""}
onChange={(val) => {
stategrafikBerdasarkanResponden.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}
/>
label={"Rating"}
placeholder={indeksKepuasanState.pilihanRatingResponden.findMany.loading ? 'Memuat...' : 'Pilih rating'}
value={stategrafikBerdasarkanResponden.create.form.ratingId || ""}
onChange={(val) => {
stategrafikBerdasarkanResponden.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}
/>
<Select
key={"kelompokUmur"}
label={"Kelompok Umur"}
@@ -132,13 +134,32 @@ function RespondenCreate() {
}
disabled={indeksKepuasanState.kelompokUmurResponden.findMany.loading}
/>
<Button
mt={10}
bg={colors['blue-button']}
onClick={handleSubmit}
>
Submit
</Button>
<Group justify="right">
{/* Tombol Batal */}
<Button
variant="outline"
color="gray"
radius="md"
size="md"
onClick={resetForm}
>
Reset
</Button>
{/* Tombol Simpan */}
<Button
onClick={handleSubmit}
radius="md"
size="md"
style={{
background: `linear-gradient(135deg, ${colors['blue-button']}, #4facfe)`,
color: '#fff',
boxShadow: '0 4px 15px rgba(79, 172, 254, 0.4)',
}}
>
{isSubmitting ? <Loader size="sm" color="white" /> : 'Simpan'}
</Button>
</Group>
</Stack>
</Paper>
</Box>

View File

@@ -97,11 +97,11 @@ function ListResponden({ search }: ListRespondenProps) {
>
<TableThead>
<TableTr>
<TableTh style={{ width: '5%', textAlign: 'center' }}>No</TableTh>
<TableTh style={{ width: '25%', textAlign: 'center' }}>Nama</TableTh>
<TableTh style={{ width: '20%', textAlign: 'center' }}>Tanggal</TableTh>
<TableTh style={{ width: '20%', textAlign: 'center' }}>Jenis Kelamin</TableTh>
<TableTh style={{ width: '15%', textAlign: 'center' }}>Aksi</TableTh>
<TableTh style={{ width: '5%' }}>No</TableTh>
<TableTh style={{ width: '25%' }}>Nama</TableTh>
<TableTh style={{ width: '20%' }}>Tanggal</TableTh>
<TableTh style={{ width: '20%' }}>Jenis Kelamin</TableTh>
<TableTh style={{ width: '15%' }}>Aksi</TableTh>
</TableTr>
</TableThead>
<TableTbody>
@@ -116,9 +116,9 @@ function ListResponden({ search }: ListRespondenProps) {
) : (
filteredData.map((item, index) => (
<TableTr key={item.id}>
<TableTd ta="center">{index + 1}</TableTd>
<TableTd ta="center">{item.name}</TableTd>
<TableTd ta="center">
<TableTd>{index + 1}</TableTd>
<TableTd>{item.name}</TableTd>
<TableTd>
<Box w={150}>
{item.tanggal
? new Date(item.tanggal).toLocaleDateString('id-ID', {
@@ -129,12 +129,12 @@ function ListResponden({ search }: ListRespondenProps) {
: '-'}
</Box>
</TableTd>
<TableTd ta="center">
<TableTd>
<Box w={100}>
{item.jenisKelamin.name}
</Box>
</TableTd>
<TableTd ta="center">
<TableTd>
<Button
size="xs"
radius="md"