Sinkronisasi Sinkronisasi UI & API Admin - User Submenu Program Kemiskinan
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import ApiFetch from "@/lib/api-fetch";
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { toast } from "react-toastify";
|
||||
@@ -11,8 +12,7 @@ const templateForm = z.object({
|
||||
statistik: z.object({
|
||||
tahun: z.string().min(1, "Tahun minimal 1 karakter"),
|
||||
jumlah: z.string().min(1, "Jumlah minimal 1 karakter"),
|
||||
})
|
||||
|
||||
}),
|
||||
});
|
||||
|
||||
const defaultForm = {
|
||||
@@ -21,8 +21,8 @@ const defaultForm = {
|
||||
ikonUrl: "",
|
||||
statistik: {
|
||||
tahun: "",
|
||||
jumlah: ""
|
||||
}
|
||||
jumlah: "",
|
||||
},
|
||||
};
|
||||
|
||||
const programKemiskinanState = proxy({
|
||||
@@ -64,12 +64,35 @@ const programKemiskinanState = proxy({
|
||||
};
|
||||
}>[],
|
||||
loading: false,
|
||||
async load() {
|
||||
const res = await ApiFetch.api.ekonomi.programkemiskinan[
|
||||
"find-many"
|
||||
].get();
|
||||
if (res.status === 200) {
|
||||
programKemiskinanState.findMany.data = res.data?.data ?? [];
|
||||
page: 1,
|
||||
totalPages: 1,
|
||||
search: "",
|
||||
load: async (page = 1, limit = 10, search = "") => {
|
||||
programKemiskinanState.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||
programKemiskinanState.findMany.page = page;
|
||||
programKemiskinanState.findMany.search = search;
|
||||
|
||||
try {
|
||||
const query: any = { page, limit };
|
||||
if (search) query.search = search;
|
||||
|
||||
const res = await ApiFetch.api.ekonomi.programkemiskinan[
|
||||
"find-many"
|
||||
].get({ query });
|
||||
|
||||
if (res.status === 200 && res.data?.success) {
|
||||
programKemiskinanState.findMany.data = res.data.data ?? [];
|
||||
programKemiskinanState.findMany.totalPages = res.data.totalPages ?? 1;
|
||||
} else {
|
||||
programKemiskinanState.findMany.data = [];
|
||||
programKemiskinanState.findMany.totalPages = 1;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Gagal fetch program kemiskinan paginated:", err);
|
||||
programKemiskinanState.findMany.data = [];
|
||||
programKemiskinanState.findMany.totalPages = 1;
|
||||
} finally {
|
||||
programKemiskinanState.findMany.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core';
|
||||
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 JudulList from '../../_com/judulList';
|
||||
@@ -23,7 +23,7 @@ function ProgramKemiskinan() {
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||
/>
|
||||
<ListProgramKemiskinan search={search}/>
|
||||
<ListProgramKemiskinan search={search} />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -34,14 +34,22 @@ function ListProgramKemiskinan({ search }: { search: string }) {
|
||||
const [lineChart, setLineChart] = useState<any[]>([]);
|
||||
const [mounted, setMounted] = useState(false);
|
||||
|
||||
const {
|
||||
data,
|
||||
page,
|
||||
totalPages,
|
||||
loading,
|
||||
load,
|
||||
} = programState.findMany;
|
||||
|
||||
useShallowEffect(() => {
|
||||
setMounted(true)
|
||||
programState.findMany.load()
|
||||
load(page, 10, search)
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (programState.findMany.data) {
|
||||
const chartData = programState.findMany.data
|
||||
if (data) {
|
||||
const chartData = data
|
||||
.filter(item => item.statistik)
|
||||
.map(item => ({
|
||||
tahun: item.statistik?.tahun,
|
||||
@@ -52,18 +60,11 @@ function ListProgramKemiskinan({ search }: { search: string }) {
|
||||
setLineChart(chartData);
|
||||
|
||||
}
|
||||
}, [programState.findMany.data])
|
||||
}, [data])
|
||||
|
||||
const filteredData = (programState.findMany.data || []).filter(item => {
|
||||
const keyword = search.toLowerCase();
|
||||
return (
|
||||
item.nama.toLowerCase().includes(keyword) ||
|
||||
item.deskripsi.toLowerCase().includes(keyword) ||
|
||||
item.statistik?.tahun.toString().includes(keyword)
|
||||
);
|
||||
});
|
||||
const filteredData = data || []
|
||||
|
||||
if (!programState.findMany.data) {
|
||||
if (loading || !data) {
|
||||
return (
|
||||
<Stack py={10}>
|
||||
<Skeleton h={500} />
|
||||
@@ -112,7 +113,7 @@ function ListProgramKemiskinan({ search }: { search: string }) {
|
||||
<Box >
|
||||
<Title pb={10} order={3}>Grafik Berdasarkan Responden</Title>
|
||||
{mounted && lineChart.length > 0 ? (<Box style={{ width: '100%', height: 'auto', }}>
|
||||
<Box w={"100%"} style={{overflowX: 'auto'}}>
|
||||
<Box w={"100%"} style={{ overflowX: 'auto' }}>
|
||||
<LineChart
|
||||
width={820}
|
||||
height={300}
|
||||
@@ -143,6 +144,14 @@ function ListProgramKemiskinan({ search }: { search: string }) {
|
||||
</Box>
|
||||
</Stack>
|
||||
</Paper>
|
||||
<Center>
|
||||
<Pagination
|
||||
value={page}
|
||||
onChange={(newPage) => load(newPage)}
|
||||
total={totalPages}
|
||||
my={"md"}
|
||||
/>
|
||||
</Center>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
|
||||
@@ -1,18 +1,53 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
export default async function programKemiskinanFindMany() {
|
||||
const data = await prisma.programKemiskinan.findMany({
|
||||
include: {
|
||||
statistik: true, // ikut sertakan relasinya
|
||||
},
|
||||
orderBy: {
|
||||
createdAt: "desc",
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Success get all program layanan",
|
||||
data,
|
||||
};
|
||||
export default async function programKemiskinanFindMany(context: Context) {
|
||||
const page = Number(context.query.page) || 1;
|
||||
const limit = Number(context.query.limit) || 10;
|
||||
const search = (context.query.search as string) || '';
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
// Buat where clause
|
||||
const where: any = { isActive: true };
|
||||
|
||||
// Tambahkan pencarian (jika ada)
|
||||
if (search) {
|
||||
where.OR = [
|
||||
{ nama: { contains: search, mode: 'insensitive' } },
|
||||
{ deskripsi: { contains: search, mode: 'insensitive' } },
|
||||
];
|
||||
}
|
||||
|
||||
try {
|
||||
// Ambil data dan total count secara paralel
|
||||
const [data, total] = await Promise.all([
|
||||
prisma.programKemiskinan.findMany({
|
||||
where,
|
||||
include: {
|
||||
statistik: true,
|
||||
},
|
||||
skip,
|
||||
take: limit,
|
||||
orderBy: { createdAt: 'desc' },
|
||||
}),
|
||||
prisma.programKemiskinan.count({ where }),
|
||||
]);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Berhasil ambil program kemiskinan dengan pagination",
|
||||
data,
|
||||
page,
|
||||
limit,
|
||||
total,
|
||||
totalPages: Math.ceil(total / limit),
|
||||
};
|
||||
} catch (e) {
|
||||
console.error("Error di findMany paginated:", e);
|
||||
return {
|
||||
success: false,
|
||||
message: "Gagal mengambil data program kemiskinan",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,14 +50,6 @@ function Page() {
|
||||
</Text>
|
||||
<Group py={20} align='center' justify='space-between'>
|
||||
<Text fz={'h4'} fw={"bold"}>DATA PENGANGGURAN DESA</Text>
|
||||
{/* <Flex gap={'xl'}>
|
||||
<Select
|
||||
value={selectedYear || state.findMany.data?.[0]?.year.toString()}
|
||||
placeholder="Pilih Tahun"
|
||||
data={Array.from(new Set(state.findMany.data?.map(item => item.year.toString()) || []))}
|
||||
onChange={setSelectedYear}
|
||||
/>
|
||||
</Flex> */}
|
||||
</Group>
|
||||
</Box>
|
||||
<Box px={{ base: "md", md: 100 }}>
|
||||
|
||||
@@ -1,54 +1,50 @@
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Stack, Box, Text, SimpleGrid, Paper } from '@mantine/core';
|
||||
import { Stack, Box, Text, SimpleGrid, Paper, Skeleton, Center } from '@mantine/core';
|
||||
import React from 'react';
|
||||
import BackButton from '../../desa/layanan/_com/BackButto';
|
||||
import { LineChart } from '@mantine/charts';
|
||||
import { CartesianGrid, Legend, Line, LineChart as RechartsLineChart, Tooltip, XAxis, YAxis } from 'recharts';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import programKemiskinanState from '@/app/admin/(dashboard)/_state/ekonomi/program-kemiskinan';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
|
||||
interface StatistikData {
|
||||
id: string;
|
||||
tahun: number;
|
||||
jumlah: number;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
interface ProgramKemiskinanData {
|
||||
id: string;
|
||||
nama: string;
|
||||
deskripsi: string;
|
||||
ikonUrl: string | null;
|
||||
statistik: StatistikData | null;
|
||||
isActive: boolean;
|
||||
statistikId: string | null;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
const data = [
|
||||
{
|
||||
id: 1,
|
||||
judul: 'Bantuan Tunai',
|
||||
deskripsi: 'Bantuan keuangan langsung bagi keluarga kurang mampu'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
judul: 'Pelatihan Kerja',
|
||||
deskripsi: 'Program pelatihan keterampilan untuk meningkatkan peluang kerja'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
judul: 'Subsidi Pangan',
|
||||
deskripsi: 'Distribusi bahan pangan bersubsidi bagi masyarakat kurang mampu'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
judul: 'Layanan Kesehatan Gratis',
|
||||
deskripsi: 'Akses kesehatan gratis bagi masyarakat kurang mampu'
|
||||
},
|
||||
]
|
||||
const dataStatistik = [
|
||||
{
|
||||
id: 1,
|
||||
tahun: '2022',
|
||||
Kemiskinan: 400000
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
tahun: '2023',
|
||||
Kemiskinan: 450000
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
tahun: '2024',
|
||||
Kemiskinan: 500000
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
tahun: '2025',
|
||||
Kemiskinan: 400000
|
||||
},
|
||||
]
|
||||
function Page() {
|
||||
const state = useProxy(programKemiskinanState)
|
||||
|
||||
useShallowEffect(() => {
|
||||
state.findMany.load()
|
||||
}, [])
|
||||
|
||||
const data = programKemiskinanState.findMany.data
|
||||
|
||||
if (!data) {
|
||||
return (
|
||||
<Stack py={10}>
|
||||
<Skeleton h={500} />
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"}>
|
||||
<Box px={{ base: 'md', md: 100 }}>
|
||||
@@ -69,26 +65,58 @@ function Page() {
|
||||
md: 2
|
||||
}}
|
||||
>
|
||||
{data.map((v, k) => {
|
||||
{state.findMany.data.map((v, k) => {
|
||||
return (
|
||||
<Paper p={'xl'} key={k}>
|
||||
<Text fz={'h3'} fw={'bold'} c={colors['blue-button']}>{v.judul}</Text>
|
||||
<Text fz={'lg'} c={'black'}>{v.deskripsi}</Text>
|
||||
<Text fz={'h3'} fw={'bold'} c={colors['blue-button']}>{v.nama}</Text>
|
||||
<Text fz={'lg'} c={'black'} dangerouslySetInnerHTML={{ __html: v.deskripsi }}></Text>
|
||||
</Paper>
|
||||
)
|
||||
})}
|
||||
</SimpleGrid>
|
||||
<Paper p={'xl'}>
|
||||
<Text fz={'h4'} fw={'bold'} c={colors['blue-button']}>Statistik Kemiskinan Masyarakat</Text>
|
||||
<LineChart
|
||||
h={300}
|
||||
data={dataStatistik}
|
||||
dataKey="tahun"
|
||||
series={[
|
||||
{ name: 'Kemiskinan', color: colors['blue-button'] },
|
||||
]}
|
||||
curveType="linear"
|
||||
/>
|
||||
<Text fz={'h3'} fw={'bold'} c={colors['blue-button']} mb="md">Statistik Kemiskinan Masyarakat</Text>
|
||||
<Box style={{ width: '100%', height: 'auto' }}>
|
||||
{state.findMany.data.length > 0 && state.findMany.data[0]?.statistik ? (
|
||||
<Box w="100%" style={{ overflowX: 'auto' }}>
|
||||
<Center>
|
||||
<RechartsLineChart
|
||||
width={800}
|
||||
height={300}
|
||||
data={state.findMany.data
|
||||
.filter((item): item is ProgramKemiskinanData & { statistik: StatistikData } =>
|
||||
item.statistik !== null
|
||||
)
|
||||
.map(item => ({
|
||||
tahun: item.statistik.tahun,
|
||||
jumlah: item.statistik.jumlah
|
||||
}))
|
||||
.sort((a, b) => a.tahun - b.tahun)
|
||||
}
|
||||
margin={{ top: 5, right: 30, left: 20, bottom: 5 }}
|
||||
>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis dataKey="tahun" />
|
||||
<YAxis />
|
||||
<Tooltip
|
||||
formatter={(value: number, name: string) => [`${value} orang`, name]}
|
||||
labelFormatter={(label: number) => `Tahun: ${label}`}
|
||||
/>
|
||||
<Legend />
|
||||
<Line
|
||||
type="monotone"
|
||||
dataKey="jumlah"
|
||||
name="Jumlah Masyarakat Miskin"
|
||||
stroke={colors['blue-button']}
|
||||
activeDot={{ r: 8 }}
|
||||
/>
|
||||
</RechartsLineChart>
|
||||
</Center>
|
||||
</Box>
|
||||
) : (
|
||||
<Text c="dimmed">Belum ada data statistik yang tersedia</Text>
|
||||
)}
|
||||
</Box>
|
||||
</Paper>
|
||||
</Stack>
|
||||
</Box>
|
||||
|
||||
Reference in New Issue
Block a user