Fix UI Admin Menu Landing Page & PPID

This commit is contained in:
2025-09-01 16:14:28 +08:00
parent 22ec8d942d
commit 7ae83788b4
74 changed files with 4312 additions and 2798 deletions

View File

@@ -4,8 +4,8 @@ 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 { Box, Button, Group, Paper, Stack, Title, TextInput, Text, Select, Tooltip } from '@mantine/core';
import { IconArrowBack, IconDeviceFloppy } from '@tabler/icons-react';
import indeksKepuasanState from '@/app/admin/(dashboard)/_state/landing-page/indeks-kepuasan';
import { toast } from 'react-toastify';
@@ -74,23 +74,39 @@ function EditResponden() {
state.update.id = id;
state.update.form = { ...formData }; // <-- sinkronisasi manual
await state.update.submit();
router.push('/admin/ppid/ikm-desa-darmasaba/responden')
router.push('/admin/landing-page/indeks-kepuasan-masyarakat/responden')
}
return (
<Box>
<Box mb={10}>
<Button variant="subtle" onClick={() => router.back()}>
<IconArrowBack size={20} />
</Button>
</Box>
<Paper bg={colors['white-1']} w={{ base: '100%', md: '50%' }} p={'md'}>
<Stack>
<Title order={3}>Edit Responden</Title>
<Box px={{ base: 'sm', md: 'lg' }} py="md">
<Group mb="md">
<Tooltip label="Kembali ke halaman sebelumnya" withArrow>
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
<IconArrowBack color={colors['blue-button']} size={24} />
</Button>
</Tooltip>
<Title order={4} ml="sm" c="dark">
Edit Responden
</Title>
</Group>
<Paper
withBorder
w={{ base: '100%', md: '50%' }}
bg={colors['white-1']}
p="lg"
radius="md"
shadow="sm"
>
<Stack gap="md">
<TextInput
label="Nama"
label={
<Text fw="bold" fz="sm" mb={4}>
Nama Responden
</Text>
}
type='text'
placeholder="masukkan nama"
placeholder="Masukkan nama responden"
value={formData.name}
onChange={(val) => {
setFormData({
@@ -98,9 +114,15 @@ function EditResponden() {
name: val.currentTarget.value
})
}}
radius="md"
required
/>
<TextInput
label="Tanggal"
label={
<Text fw="bold" fz="sm" mb={4}>
Tanggal
</Text>
}
type="date"
placeholder='Pilih tanggal'
value={formData.tanggal ? new Date(formData.tanggal).toISOString().split('T')[0] : ''}
@@ -111,32 +133,43 @@ function EditResponden() {
tanggal: selectedDate,
});
}}
radius="md"
required
/>
<Select
key={"jenisKelamin"}
label={<Text fw="bold" fz="sm">Jenis Kelamin</Text>}
key="jenisKelamin"
label={
<Text fw="bold" fz="sm" mb={4}>
Jenis Kelamin
</Text>
}
placeholder="Pilih jenis kelamin"
value={formData.jenisKelaminId}
onChange={(val) => setFormData({ ...formData, jenisKelaminId: val || "" })}
data={
(indeksKepuasanState.jenisKelaminResponden.findMany.data || [])
.filter(Boolean) // Hapus null/undefined
.filter(Boolean)
.map((v) => ({
value: v.id || '',
label: typeof v.name === 'string' ? v.name : 'Tanpa Nama'
}))
}
disabled={indeksKepuasanState.jenisKelaminResponden.findMany.loading} // ✅ disable saat loading
disabled={indeksKepuasanState.jenisKelaminResponden.findMany.loading}
clearable
searchable
required
radius="md"
error={!formData.jenisKelaminId ? "Pilih jenis kelamin" : undefined}
/>
<Select
key={"rating"}
key="rating"
value={formData.ratingId}
onChange={(val) => setFormData({ ...formData, ratingId: val || "" })}
label={<Text fw={"bold"} fz={"sm"}>Rating</Text>}
label={
<Text fw="bold" fz="sm" mb={4}>
Rating
</Text>
}
placeholder='Pilih rating'
data={
(indeksKepuasanState.pilihanRatingResponden.findMany.data || [])
@@ -150,6 +183,7 @@ function EditResponden() {
clearable
searchable
required
radius="md"
error={!formData.ratingId ? "Pilih rating" : undefined}
/>
@@ -173,14 +207,24 @@ function EditResponden() {
required
error={!formData.kelompokUmurId ? "Pilih kelompok umur" : undefined}
/>
<Button
mt={10}
bg={colors['blue-button']}
onClick={handleSubmit}
>
Submit
</Button>
<Group justify="flex-end" mt="md">
<Button
variant="light"
color="red"
onClick={() => router.back()}
>
Batal
</Button>
<Button
leftSection={<IconDeviceFloppy size={20} />}
onClick={handleSubmit}
loading={state.update.loading}
color={colors['blue-button']}
>
Simpan
</Button>
</Group>
</Stack>
</Paper>
</Box>

View File

@@ -3,10 +3,10 @@
import { ModalKonfirmasiHapus } from "@/app/admin/(dashboard)/_com/modalKonfirmasiHapus"
import indeksKepuasanState from "@/app/admin/(dashboard)/_state/landing-page/indeks-kepuasan"
import colors from "@/con/colors"
import { Box, Button, Flex, Paper, Skeleton, Stack, Text } from "@mantine/core"
import { Box, Button, Group, Paper, Skeleton, Stack, Text, Tooltip } from "@mantine/core"
import { useShallowEffect } from "@mantine/hooks"
import { IconArrowBack, IconEdit, IconX } from "@tabler/icons-react"
import { useRouter, useParams } from "next/navigation"
import { IconArrowBack, IconEdit, IconTrash } from "@tabler/icons-react"
import { useParams, useRouter } from "next/navigation"
import { useState } from "react"
import { useProxy } from "valtio/utils"
@@ -38,67 +38,89 @@ export default function DetailResponden() {
)
}
return (
<Box>
<Box mb={10}>
<Button variant="subtle" onClick={() => router.back()}>
<IconArrowBack color={colors['blue-button']} size={25} />
</Button>
</Box>
<Paper w={{ base: "100%", md: "50%" }} bg={colors['white-1']} p={'md'}>
<Stack>
<Text fz={"xl"} fw={"bold"}>Detail Responden</Text>
<Box py={10}>
<Button
variant="subtle"
onClick={() => router.back()}
leftSection={<IconArrowBack size={24} color={colors['blue-button']} />}
mb={15}
>
Kembali
</Button>
<Paper bg={colors['BG-trans']} p={'md'}>
<Stack gap={"xs"}>
<Paper
withBorder
w={{ base: "100%", md: "60%" }}
bg={colors['white-1']}
p="lg"
radius="md"
shadow="sm"
>
<Stack gap="md">
<Text fz="2xl" fw="bold" c={colors['blue-button']}>
Detail Responden
</Text>
<Paper bg="#ECEEF8" p="md" radius="md" shadow="xs">
<Stack gap="sm">
<Box>
<Text fz={"lg"} fw={"bold"}>Nama Responden</Text>
<Text fz={"lg"}>{stateDetail.findUnique.data?.name}</Text>
<Text fz="lg" fw="bold">Nama Responden</Text>
<Text fz="md" c="dimmed">{stateDetail.findUnique.data?.name || '-'}</Text>
</Box>
<Box>
<Text fz={"lg"} fw={"bold"}>Tanggal</Text>
<Text fz={"lg"}>{
<Text fz="lg" fw="bold">Tanggal</Text>
<Text fz="md" c="dimmed">{
stateDetail.findUnique.data?.tanggal
? new Date(stateDetail.findUnique.data.tanggal).toLocaleDateString('id-ID')
: '-'
}</Text>
</Box>
<Box>
<Text fz={"lg"} fw={"bold"}>Jenis Kelamin</Text>
<Text fz={"lg"}>{stateDetail.findUnique.data?.jenisKelamin?.name}</Text>
<Text fz="lg" fw="bold">Jenis Kelamin</Text>
<Text fz="md" c="dimmed">{stateDetail.findUnique.data?.jenisKelamin?.name || '-'}</Text>
</Box>
<Box>
<Text fz={"lg"} fw={"bold"}>Rating</Text>
<Text fz={"lg"}>{stateDetail.findUnique.data?.rating?.name}</Text>
<Text fz="lg" fw="bold">Rating</Text>
<Text fz="md" c="dimmed">{stateDetail.findUnique.data?.rating?.name || '-'}</Text>
</Box>
<Box>
<Text fz={"lg"} fw={"bold"}>Kelompok Umur</Text>
<Text fz={"lg"}>{stateDetail.findUnique.data?.kelompokUmur?.name}</Text>
<Text fz="lg" fw="bold">Kelompok Umur</Text>
<Text fz="md" c="dimmed">{stateDetail.findUnique.data?.kelompokUmur?.name || '-'}</Text>
</Box>
<Flex gap={"xs"} mt={10}>
<Button
onClick={() => {
if (stateDetail.findUnique.data) {
setSelectedId(stateDetail.findUnique.data.id);
setModalHapus(true);
}
}}
disabled={stateDetail.delete.loading || !stateDetail.findUnique.data}
color={"red"}
>
<IconX size={20} />
</Button>
<Button
onClick={() => {
if (stateDetail.findUnique.data) {
router.push(`/admin/ppid/ikm-desa-darmasaba/responden/${stateDetail.findUnique.data.id}/edit`);
}
}}
disabled={!stateDetail.findUnique.data}
color={"green"}
>
<IconEdit size={20} />
</Button>
</Flex>
<Group gap="sm" mt="md">
<Tooltip label="Hapus Responden" withArrow position="top">
<Button
color="red"
variant="light"
onClick={() => {
if (stateDetail.findUnique.data) {
setSelectedId(stateDetail.findUnique.data.id);
setModalHapus(true);
}
}}
disabled={stateDetail.delete.loading || !stateDetail.findUnique.data}
leftSection={<IconTrash size={20} />}
>
Hapus
</Button>
</Tooltip>
<Tooltip label="Edit Responden" withArrow position="top">
<Button
color="green"
variant="light"
onClick={() => {
if (stateDetail.findUnique.data) {
router.push(`/admin/landing-page/indeks-kepuasan-masyarakat/responden/${stateDetail.findUnique.data.id}/edit`);
}
}}
disabled={!stateDetail.findUnique.data}
leftSection={<IconEdit size={20} />}
>
Edit
</Button>
</Tooltip>
</Group>
</Stack>
</Paper>
</Stack>

View File

@@ -1,22 +1,37 @@
'use client';
import colors from '@/con/colors';
import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useState } from 'react';
import { useRouter } from 'next/navigation';
import { useProxy } from 'valtio/utils';
import { useShallowEffect } from '@mantine/hooks';
import {
Box,
Button,
Center,
Pagination,
Paper,
Skeleton,
Stack,
Table,
TableTbody,
TableTd,
TableTh,
TableThead,
TableTr,
Text,
Title,
} from '@mantine/core';
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
import HeaderSearch from '../../../_com/header';
import indeksKepuasanState from '../../../_state/landing-page/indeks-kepuasan';
function Responden() {
const [search, setSearch] = useState("");
const [search, setSearch] = useState('');
return (
<Box>
<HeaderSearch
title='Responden'
placeholder='pencarian'
searchIcon={<IconSearch size={20} />}
title="Data Responden"
placeholder="Cari nama responden..."
searchIcon={<IconSearch size={18} />}
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
/>
@@ -34,84 +49,99 @@ function ListResponden({ search }: ListRespondenProps) {
const router = useRouter();
const { data, page, totalPages, loading, load } = state.findMany;
useShallowEffect(() => {
load(page, 10)
load(page, 10);
}, [page]);
const filteredData = (data || []).filter(item => {
const filteredData = (data || []).filter((item) => {
const keyword = search.toLowerCase();
return (
item.name.toLowerCase().includes(keyword)
);
return item.name.toLowerCase().includes(keyword);
});
if (loading || !data) {
return (
<Stack py={10}>
<Skeleton height={730} />
<Stack py="md">
<Skeleton height={650} radius="lg" />
</Stack>
);
}
if (data.length === 0) {
return (
<Box py={10}>
<Paper p="md">
<Stack>
<Title>Responden</Title>
<Table striped withTableBorder withRowBorders>
<TableThead>
<TableTr>
<TableTh>No</TableTh>
<TableTh>Nama</TableTh>
<TableTh>Tanggal</TableTh>
<TableTh>Jenis Kelamin</TableTh>
<TableTh>Detail</TableTh>
</TableTr>
</TableThead>
</Table>
<Text ta="center">Tidak ada data berdasarkan jenis kelamin responden yang tersedia</Text>
<Box py="md">
<Paper p="lg" radius="lg" shadow="md" withBorder>
<Stack align="center" gap="sm">
<Title order={4}>Data Responden</Title>
<Text c="dimmed" ta="center">
Belum ada data responden yang tersedia
</Text>
</Stack>
</Paper>
</Box >
</Box>
);
}
return (
<Box>
<Stack gap="xs">
<Paper bg={colors['white-1']} p="md" h={{ base: 730, md: 650 }}>
<Title mb={10} order={3}>List Responden</Title>
<Table striped withTableBorder withRowBorders>
<Stack gap="md">
<Paper p="lg" radius="lg" shadow="md" withBorder>
<Title order={4} mb="sm">
Daftar Responden
</Title>
<Table
striped
highlightOnHover
withTableBorder
withRowBorders
verticalSpacing="sm"
>
<TableThead>
<TableTr>
<TableTh style={{ width: '5%', textAlign: 'center' }}>No</TableTh>
<TableTh style={{ width: '20%', textAlign: 'center' }}>Nama</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' }}>Detail</TableTh>
<TableTh style={{ width: '15%', textAlign: 'center' }}>Aksi</TableTh>
</TableTr>
</TableThead>
<TableTbody>
{filteredData.length === 0 ? (
<TableTr>
<TableTd colSpan={6}>
<Text ta='center' c='dimmed'>Belum ada data responden</Text>
<TableTd colSpan={5}>
<Text ta="center" c="dimmed">
Tidak ditemukan data dengan kata kunci pencarian
</Text>
</TableTd>
</TableTr>
) : (
filteredData.map((item, index) => (
<TableTr key={item.id}>
<TableTd style={{ width: '5%', textAlign: 'center' }}>{index + 1}</TableTd>
<TableTd style={{ width: '20%', textAlign: 'center' }}>{item.name}</TableTd>
<TableTd style={{ width: '20%', textAlign: 'center' }}>{item.tanggal
? new Date(item.tanggal).toLocaleDateString('id-ID')
: '-'}</TableTd>
<TableTd style={{ width: '20%', textAlign: 'center' }}>{item.jenisKelamin.name}</TableTd>
<TableTd style={{ width: '15%', textAlign: 'center' }}>
<Button color='green' onClick={() => router.push(`/admin/ppid/ikm-desa-darmasaba/responden/${item.id}`)}>
<IconDeviceImac size={20} />
<TableTd ta="center">{index + 1}</TableTd>
<TableTd ta="center">{item.name}</TableTd>
<TableTd ta="center">
{item.tanggal
? new Date(item.tanggal).toLocaleDateString('id-ID', {
day: '2-digit',
month: 'long',
year: 'numeric',
})
: '-'}
</TableTd>
<TableTd ta="center">{item.jenisKelamin.name}</TableTd>
<TableTd ta="center">
<Button
size="xs"
radius="md"
variant="light"
color="blue"
leftSection={<IconDeviceImac size={16} />}
onClick={() =>
router.push(
`/admin/landing-page/indeks-kepuasan-masyarakat/responden/${item.id}`
)
}
>
Detail
</Button>
</TableTd>
</TableTr>
@@ -123,22 +153,19 @@ function ListResponden({ search }: ListRespondenProps) {
<Center>
<Pagination
value={page}
total={totalPages}
onChange={(newPage) => {
load(newPage, 10);
window.scrollTo(0, 0);
window.scrollTo({ top: 0, behavior: 'smooth' });
}}
total={totalPages}
size="md"
radius="md"
mt="md"
mb="md"
/>
</Center>
</Stack>
</Box>
);
}
export default Responden;