QC Tampilan Admin & User, Api berfungsi
This commit is contained in:
83
src/app/admin/(dashboard)/_com/iconMap.tsx
Normal file
83
src/app/admin/(dashboard)/_com/iconMap.tsx
Normal file
@@ -0,0 +1,83 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
'use client'
|
||||
|
||||
import React from 'react'
|
||||
import {
|
||||
IconLeaf,
|
||||
IconTrophy,
|
||||
IconTent,
|
||||
IconChartLine,
|
||||
IconRecycle,
|
||||
IconTruck,
|
||||
IconScale,
|
||||
IconClipboard,
|
||||
IconTrash,
|
||||
IconHomeEco,
|
||||
IconChristmasTreeFilled,
|
||||
IconTrendingUp,
|
||||
IconShieldFilled,
|
||||
IconHome,
|
||||
IconTree,
|
||||
IconDroplet,
|
||||
IconCash,
|
||||
IconSchool,
|
||||
IconShoppingCart,
|
||||
IconHospital,
|
||||
} from '@tabler/icons-react'
|
||||
|
||||
export type IconKey =
|
||||
| 'ekowisata'
|
||||
| 'kompetisi'
|
||||
| 'wisata'
|
||||
| 'ekonomi'
|
||||
| 'sampah'
|
||||
| 'truck'
|
||||
| 'scale'
|
||||
| 'clipboard'
|
||||
| 'trash'
|
||||
| 'lingkunganSehat'
|
||||
| 'sumberOksigen'
|
||||
| 'ekonomiBerkelanjutan'
|
||||
| 'mencegahBencana'
|
||||
| 'rumah'
|
||||
| 'pohon'
|
||||
| 'air'
|
||||
| 'bantuan'
|
||||
| 'pelatihan'
|
||||
| 'subsidi'
|
||||
| 'layananKesehatan'
|
||||
|
||||
const iconMap: Record<IconKey, React.FC<any>> = {
|
||||
ekowisata: IconLeaf,
|
||||
kompetisi: IconTrophy,
|
||||
wisata: IconTent,
|
||||
ekonomi: IconChartLine,
|
||||
sampah: IconRecycle,
|
||||
truck: IconTruck,
|
||||
scale: IconScale,
|
||||
clipboard: IconClipboard,
|
||||
trash: IconTrash,
|
||||
lingkunganSehat: IconHomeEco,
|
||||
sumberOksigen: IconChristmasTreeFilled,
|
||||
ekonomiBerkelanjutan: IconTrendingUp,
|
||||
mencegahBencana: IconShieldFilled,
|
||||
rumah: IconHome,
|
||||
pohon: IconTree,
|
||||
air: IconDroplet,
|
||||
bantuan: IconCash,
|
||||
pelatihan: IconSchool,
|
||||
subsidi: IconShoppingCart,
|
||||
layananKesehatan: IconHospital,
|
||||
}
|
||||
|
||||
type Props = {
|
||||
name: IconKey
|
||||
size?: number
|
||||
color?: string
|
||||
}
|
||||
|
||||
export const IconMapper: React.FC<Props> = ({ name, size = 24, color }) => {
|
||||
const IconComponent = iconMap[name]
|
||||
if (!IconComponent) return null
|
||||
return <IconComponent size={size} color={color} />
|
||||
}
|
||||
@@ -3,16 +3,20 @@
|
||||
|
||||
import { Box, rem, Select } from '@mantine/core';
|
||||
import {
|
||||
IconCash,
|
||||
IconChartLine,
|
||||
IconChristmasTreeFilled,
|
||||
IconClipboardTextFilled,
|
||||
IconDroplet,
|
||||
IconHome,
|
||||
IconHomeEco,
|
||||
IconHospital,
|
||||
IconLeaf,
|
||||
IconRecycle,
|
||||
IconScale,
|
||||
IconSchool,
|
||||
IconShieldFilled,
|
||||
IconShoppingCart,
|
||||
IconTent,
|
||||
IconTrashFilled,
|
||||
IconTree,
|
||||
@@ -32,13 +36,17 @@ const iconMap = {
|
||||
scale: { label: 'Scale', icon: IconScale },
|
||||
clipboard: { label: 'Clipboard', icon: IconClipboardTextFilled },
|
||||
trash: { label: 'Trash', icon: IconTrashFilled },
|
||||
lingkunganSehat: {label: 'Lingkungan Sehat', icon: IconHomeEco},
|
||||
sumberOksigen: {label: 'Sumber Oksigen', icon: IconChristmasTreeFilled},
|
||||
ekonomiBerkelanjutan: {label: 'Ekonomi Berkelanjutan', icon: IconTrendingUp},
|
||||
mencegahBencana: {label: 'Mencegah Bencana', icon: IconShieldFilled},
|
||||
rumah: {label: 'Rumah', icon: IconHome},
|
||||
pohon: {label: 'Pohon', icon: IconTree},
|
||||
air: {label: 'Air', icon: IconDroplet}
|
||||
lingkunganSehat: { label: 'Lingkungan Sehat', icon: IconHomeEco },
|
||||
sumberOksigen: { label: 'Sumber Oksigen', icon: IconChristmasTreeFilled },
|
||||
ekonomiBerkelanjutan: { label: 'Ekonomi Berkelanjutan', icon: IconTrendingUp },
|
||||
mencegahBencana: { label: 'Mencegah Bencana', icon: IconShieldFilled },
|
||||
rumah: { label: 'Rumah', icon: IconHome },
|
||||
pohon: { label: 'Pohon', icon: IconTree },
|
||||
air: { label: 'Air', icon: IconDroplet },
|
||||
bantuan: { label: 'Bantuan', icon: IconCash },
|
||||
pelatihan: { label: 'Pelatihan', icon: IconSchool },
|
||||
subsidi: { label: 'Subsidi', icon: IconShoppingCart },
|
||||
layananKesehatan: { label: 'Layanan Kesehatan', icon: IconHospital },
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -2,16 +2,20 @@
|
||||
|
||||
import { Box, rem, Select } from '@mantine/core';
|
||||
import {
|
||||
IconCash,
|
||||
IconChartLine,
|
||||
IconChristmasTreeFilled,
|
||||
IconClipboardTextFilled,
|
||||
IconDroplet,
|
||||
IconHome,
|
||||
IconHomeEco,
|
||||
IconHospital,
|
||||
IconLeaf,
|
||||
IconRecycle,
|
||||
IconScale,
|
||||
IconSchool,
|
||||
IconShieldFilled,
|
||||
IconShoppingCart,
|
||||
IconTent,
|
||||
IconTrashFilled,
|
||||
IconTree,
|
||||
@@ -36,7 +40,11 @@ const iconMap = {
|
||||
mencegahBencana: {label: 'Mencegah Bencana', icon: IconShieldFilled},
|
||||
rumah: {label: 'Rumah', icon: IconHome},
|
||||
pohon: {label: 'Pohon', icon: IconTree},
|
||||
air: {label: 'Air', icon: IconDroplet}
|
||||
air: {label: 'Air', icon: IconDroplet},
|
||||
bantuan: {label: 'Bantuan', icon: IconCash},
|
||||
pelatihan: {label: 'Pelatihan', icon: IconSchool},
|
||||
subsidi: {label: 'Subsidi', icon: IconShoppingCart},
|
||||
layananKesehatan: {label: 'Layanan Kesehatan', icon: IconHospital},
|
||||
};
|
||||
|
||||
type IconKey = keyof typeof iconMap;
|
||||
|
||||
@@ -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";
|
||||
@@ -71,12 +72,37 @@ const demografiPekerjaan = proxy({
|
||||
omit: { isActive: true };
|
||||
}>[]
|
||||
| null,
|
||||
async load() {
|
||||
const res = await ApiFetch.api.ekonomi.demografipekerjaan[
|
||||
"find-many"
|
||||
].get();
|
||||
if (res.status === 200) {
|
||||
demografiPekerjaan.findMany.data = res.data?.data ?? [];
|
||||
page: 1,
|
||||
totalPages: 1,
|
||||
loading: false,
|
||||
search: "",
|
||||
load: async (page = 1, limit = 10, search = "") => {
|
||||
demografiPekerjaan.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||
demografiPekerjaan.findMany.page = page;
|
||||
demografiPekerjaan.findMany.search = search;
|
||||
|
||||
try {
|
||||
const query: any = { page, limit };
|
||||
if (search) query.search = search;
|
||||
|
||||
const res = await ApiFetch.api.ekonomi.demografipekerjaan[
|
||||
"find-many"
|
||||
].get({ query });
|
||||
|
||||
if (res.status === 200 && res.data?.success) {
|
||||
demografiPekerjaan.findMany.data = res.data.data ?? [];
|
||||
demografiPekerjaan.findMany.totalPages =
|
||||
res.data.totalPages ?? 1;
|
||||
} else {
|
||||
demografiPekerjaan.findMany.data = [];
|
||||
demografiPekerjaan.findMany.totalPages = 1;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Gagal fetch demografi pekerjaan paginated:", err);
|
||||
demografiPekerjaan.findMany.data = [];
|
||||
demografiPekerjaan.findMany.totalPages = 1;
|
||||
} finally {
|
||||
demografiPekerjaan.findMany.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -194,4 +220,4 @@ const demografiPekerjaan = proxy({
|
||||
},
|
||||
},
|
||||
});
|
||||
export default demografiPekerjaan
|
||||
export default demografiPekerjaan;
|
||||
|
||||
@@ -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";
|
||||
@@ -69,16 +70,37 @@ const jumlahPendudukMiskin = proxy({
|
||||
select: { id: true; year: true; totalPoorPopulation: true };
|
||||
}>[]
|
||||
| null,
|
||||
loading: false,
|
||||
async load() {
|
||||
const res = await ApiFetch.api.ekonomi.jumlahpendudukmiskin[
|
||||
"find-many"
|
||||
].get();
|
||||
if (res.status === 200) {
|
||||
jumlahPendudukMiskin.findMany.data = res.data?.data ?? [];
|
||||
}
|
||||
page: 1,
|
||||
totalPages: 1,
|
||||
loading: false,
|
||||
search: "",
|
||||
load: async (page = 1, limit = 10, search = "") => {
|
||||
jumlahPendudukMiskin.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||
jumlahPendudukMiskin.findMany.page = page;
|
||||
jumlahPendudukMiskin.findMany.search = search;
|
||||
|
||||
try {
|
||||
const query: any = { page, limit };
|
||||
if (search) query.search = search;
|
||||
|
||||
const res = await ApiFetch.api.ekonomi.jumlahpendudukmiskin["find-many"].get({ query });
|
||||
|
||||
if (res.status === 200 && res.data?.success) {
|
||||
jumlahPendudukMiskin.findMany.data = res.data.data ?? [];
|
||||
jumlahPendudukMiskin.findMany.totalPages = res.data.totalPages ?? 1;
|
||||
} else {
|
||||
jumlahPendudukMiskin.findMany.data = [];
|
||||
jumlahPendudukMiskin.findMany.totalPages = 1;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Gagal fetch jumlah penduduk miskin paginated:", err);
|
||||
jumlahPendudukMiskin.findMany.data = [];
|
||||
jumlahPendudukMiskin.findMany.totalPages = 1;
|
||||
} finally {
|
||||
jumlahPendudukMiskin.findMany.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
findUnique: {
|
||||
data: null as Prisma.GrafikJumlahPendudukMiskinGetPayload<{
|
||||
select: { id: true; year: true; totalPoorPopulation: true };
|
||||
|
||||
@@ -8,7 +8,7 @@ import { z } from "zod";
|
||||
const templateForm = z.object({
|
||||
nama: z.string().min(1, "Nama minimal 1 karakter"),
|
||||
deskripsi: z.string().min(1, "Deskripsi minimal 1 karakter"),
|
||||
ikonUrl: z.string().optional(),
|
||||
icon: z.string().min(1, "Icon minimal 1 karakter"),
|
||||
statistik: z.object({
|
||||
tahun: z.string().min(1, "Tahun minimal 1 karakter"),
|
||||
jumlah: z.string().min(1, "Jumlah minimal 1 karakter"),
|
||||
@@ -18,7 +18,7 @@ const templateForm = z.object({
|
||||
const defaultForm = {
|
||||
nama: "",
|
||||
deskripsi: "",
|
||||
ikonUrl: "",
|
||||
icon: "",
|
||||
statistik: {
|
||||
tahun: "",
|
||||
jumlah: "",
|
||||
@@ -148,7 +148,7 @@ const programKemiskinanState = proxy({
|
||||
this.form = {
|
||||
nama: data.nama,
|
||||
deskripsi: data.deskripsi,
|
||||
ikonUrl: data.ikonUrl || "",
|
||||
icon: data.icon,
|
||||
statistik: {
|
||||
tahun: data.statistik.tahun,
|
||||
jumlah: data.statistik.jumlah,
|
||||
@@ -189,7 +189,7 @@ const programKemiskinanState = proxy({
|
||||
body: JSON.stringify({
|
||||
nama: this.form.nama,
|
||||
deskripsi: this.form.deskripsi,
|
||||
ikonUrl: this.form.ikonUrl,
|
||||
icon: this.form.icon,
|
||||
statistik: {
|
||||
tahun: this.form.statistik.tahun,
|
||||
jumlah: this.form.statistik.jumlah,
|
||||
|
||||
@@ -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";
|
||||
@@ -76,13 +77,37 @@ const grafikSektorUnggulan = proxy({
|
||||
};
|
||||
}>[]
|
||||
| null,
|
||||
page: 1,
|
||||
totalPages: 1,
|
||||
loading: false,
|
||||
async load() {
|
||||
const res = await ApiFetch.api.ekonomi.sektourunggulandesa[
|
||||
"find-many"
|
||||
].get();
|
||||
if (res.status === 200) {
|
||||
grafikSektorUnggulan.findMany.data = res.data?.data ?? [];
|
||||
search: "",
|
||||
load: async (page = 1, limit = 10, search = "") => {
|
||||
grafikSektorUnggulan.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||
grafikSektorUnggulan.findMany.page = page;
|
||||
grafikSektorUnggulan.findMany.search = search;
|
||||
|
||||
try {
|
||||
const query: any = { page, limit };
|
||||
if (search) query.search = search;
|
||||
|
||||
const res = await ApiFetch.api.ekonomi.sektourunggulandesa[
|
||||
"find-many"
|
||||
].get({ query });
|
||||
|
||||
if (res.status === 200 && res.data?.success) {
|
||||
grafikSektorUnggulan.findMany.data = res.data.data ?? [];
|
||||
grafikSektorUnggulan.findMany.totalPages =
|
||||
res.data.totalPages ?? 1;
|
||||
} else {
|
||||
grafikSektorUnggulan.findMany.data = [];
|
||||
grafikSektorUnggulan.findMany.totalPages = 1;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Gagal fetch sektor unggulan desa paginated:", err);
|
||||
grafikSektorUnggulan.findMany.data = [];
|
||||
grafikSektorUnggulan.findMany.totalPages = 1;
|
||||
} finally {
|
||||
grafikSektorUnggulan.findMany.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
@@ -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";
|
||||
@@ -75,16 +76,37 @@ const grafikBerdasarkanUsiaKerjaNganggur = proxy({
|
||||
omit: { isActive: true };
|
||||
}>[]
|
||||
| null,
|
||||
loading: false,
|
||||
async load() {
|
||||
const res = await ApiFetch.api.ekonomi.grafikusiakerjayangmenganggur[
|
||||
"find-many"
|
||||
].get();
|
||||
if (res.status === 200) {
|
||||
grafikBerdasarkanUsiaKerjaNganggur.findMany.data = res.data?.data ?? [];
|
||||
}
|
||||
page: 1,
|
||||
totalPages: 1,
|
||||
loading: false,
|
||||
search: "",
|
||||
load: async (page = 1, limit = 10, search = "") => {
|
||||
grafikBerdasarkanUsiaKerjaNganggur.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||
grafikBerdasarkanUsiaKerjaNganggur.findMany.page = page;
|
||||
grafikBerdasarkanUsiaKerjaNganggur.findMany.search = search;
|
||||
|
||||
try {
|
||||
const query: any = { page, limit };
|
||||
if (search) query.search = search;
|
||||
|
||||
const res = await ApiFetch.api.ekonomi.grafikusiakerjayangmenganggur["find-many"].get({ query });
|
||||
|
||||
if (res.status === 200 && res.data?.success) {
|
||||
grafikBerdasarkanUsiaKerjaNganggur.findMany.data = res.data.data ?? [];
|
||||
grafikBerdasarkanUsiaKerjaNganggur.findMany.totalPages = res.data.totalPages ?? 1;
|
||||
} else {
|
||||
grafikBerdasarkanUsiaKerjaNganggur.findMany.data = [];
|
||||
grafikBerdasarkanUsiaKerjaNganggur.findMany.totalPages = 1;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Gagal fetch grafik berdasarkan usia kerja yang menganggur paginated:", err);
|
||||
grafikBerdasarkanUsiaKerjaNganggur.findMany.data = [];
|
||||
grafikBerdasarkanUsiaKerjaNganggur.findMany.totalPages = 1;
|
||||
} finally {
|
||||
grafikBerdasarkanUsiaKerjaNganggur.findMany.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
findUnique: {
|
||||
data: null as Prisma.GrafikMenganggurBerdasarkanUsiaGetPayload<{
|
||||
omit: { isActive: true };
|
||||
@@ -259,15 +281,36 @@ const grafikBerdasarkanPendidikan = proxy({
|
||||
omit: { isActive: true };
|
||||
}>[]
|
||||
| null,
|
||||
loading: false,
|
||||
async load() {
|
||||
const res = await ApiFetch.api.ekonomi.grafikmenganggurberdasarkanpendidikan[
|
||||
"find-many"
|
||||
].get();
|
||||
if (res.status === 200) {
|
||||
grafikBerdasarkanPendidikan.findMany.data = res.data?.data ?? [];
|
||||
}
|
||||
},
|
||||
page: 1,
|
||||
totalPages: 1,
|
||||
loading: false,
|
||||
search: "",
|
||||
load: async (page = 1, limit = 10, search = "") => {
|
||||
grafikBerdasarkanPendidikan.findMany.loading = true; // ✅ Akses langsung via nama path
|
||||
grafikBerdasarkanPendidikan.findMany.page = page;
|
||||
grafikBerdasarkanPendidikan.findMany.search = search;
|
||||
|
||||
try {
|
||||
const query: any = { page, limit };
|
||||
if (search) query.search = search;
|
||||
|
||||
const res = await ApiFetch.api.ekonomi.grafikmenganggurberdasarkanpendidikan["find-many"].get({ query });
|
||||
|
||||
if (res.status === 200 && res.data?.success) {
|
||||
grafikBerdasarkanPendidikan.findMany.data = res.data.data ?? [];
|
||||
grafikBerdasarkanPendidikan.findMany.totalPages = res.data.totalPages ?? 1;
|
||||
} else {
|
||||
grafikBerdasarkanPendidikan.findMany.data = [];
|
||||
grafikBerdasarkanPendidikan.findMany.totalPages = 1;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Gagal fetch grafik berdasarkan pendidikan paginated:", err);
|
||||
grafikBerdasarkanPendidikan.findMany.data = [];
|
||||
grafikBerdasarkanPendidikan.findMany.totalPages = 1;
|
||||
} finally {
|
||||
grafikBerdasarkanPendidikan.findMany.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
findUnique: {
|
||||
data: null as Prisma.GrafikMenganggurBerdasarkanPendidikanGetPayload<{
|
||||
|
||||
@@ -1,8 +1,17 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client'
|
||||
'use client';
|
||||
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Paper, Stack, TextInput, Title } from '@mantine/core';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Group,
|
||||
Paper,
|
||||
Stack,
|
||||
TextInput,
|
||||
Title,
|
||||
Tooltip
|
||||
} from '@mantine/core';
|
||||
import { IconArrowBack } from '@tabler/icons-react';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import { useEffect } from 'react';
|
||||
@@ -11,33 +20,33 @@ import { useProxy } from 'valtio/utils';
|
||||
import demografiPekerjaan from '../../../_state/ekonomi/demografi-pekerjaan';
|
||||
|
||||
function EditDemografiPekerjaan() {
|
||||
const router = useRouter()
|
||||
const params = useParams() as { id: string }
|
||||
const stateDemografi = useProxy(demografiPekerjaan)
|
||||
const router = useRouter();
|
||||
const params = useParams() as { id: string };
|
||||
const stateDemografi = useProxy(demografiPekerjaan);
|
||||
|
||||
const id = params.id
|
||||
const id = params.id;
|
||||
|
||||
useEffect(() => {
|
||||
if (!id) return;
|
||||
stateDemografi.update.id = id;
|
||||
stateDemografi.findUnique.load(id)
|
||||
stateDemografi.findUnique
|
||||
.load(id)
|
||||
.then(() => {
|
||||
const data = stateDemografi.findUnique.data;
|
||||
if (data) {
|
||||
stateDemografi.update.form = {
|
||||
pekerjaan: String(data.pekerjaan || ''),
|
||||
lakiLaki: Number(data.lakiLaki || 0),
|
||||
perempuan: Number(data.perempuan || 0)
|
||||
perempuan: Number(data.perempuan || 0),
|
||||
};
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
.catch((error) => {
|
||||
console.error('Error loading data:', error);
|
||||
toast.error('Gagal memuat data');
|
||||
});
|
||||
}, [id]);
|
||||
|
||||
// Di handleSubmit, ubah menjadi:
|
||||
const handleSubmit = async () => {
|
||||
try {
|
||||
stateDemografi.update.id = id;
|
||||
@@ -51,52 +60,88 @@ function EditDemografiPekerjaan() {
|
||||
};
|
||||
|
||||
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 Demografi Pekerjaan</Title>
|
||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||
{/* Header */}
|
||||
<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 Demografi Pekerjaan
|
||||
</Title>
|
||||
</Group>
|
||||
|
||||
{/* Form Card */}
|
||||
<Paper
|
||||
w={{ base: '100%', md: '50%' }}
|
||||
bg={colors['white-1']}
|
||||
p="lg"
|
||||
radius="md"
|
||||
shadow="sm"
|
||||
style={{ border: '1px solid #e0e0e0' }}
|
||||
>
|
||||
<Stack gap="md">
|
||||
<TextInput
|
||||
label="Pekerjaan"
|
||||
placeholder="masukkan pekerjaan"
|
||||
placeholder="Masukkan jenis pekerjaan"
|
||||
value={stateDemografi.update.form.pekerjaan}
|
||||
onChange={(val) => {
|
||||
stateDemografi.update.form.pekerjaan = val.currentTarget.value;
|
||||
}}
|
||||
onChange={(e) =>
|
||||
(stateDemografi.update.form.pekerjaan = e.currentTarget.value)
|
||||
}
|
||||
required
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
label="Jumlah Pekerja Laki - Laki"
|
||||
label="Jumlah Pekerja Laki-laki"
|
||||
type="number"
|
||||
placeholder="masukkan jumlah pekerja laki - laki"
|
||||
placeholder="Masukkan jumlah pekerja laki-laki"
|
||||
value={stateDemografi.update.form.lakiLaki}
|
||||
onChange={(val) => {
|
||||
stateDemografi.update.form.lakiLaki = Number(val.currentTarget.value);
|
||||
}}
|
||||
onChange={(e) =>
|
||||
(stateDemografi.update.form.lakiLaki = Number(
|
||||
e.currentTarget.value
|
||||
))
|
||||
}
|
||||
required
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
label="Jumlah Pekerja Perempuan"
|
||||
type="number"
|
||||
placeholder="masukkan jumlah pekerja perempuan"
|
||||
placeholder="Masukkan jumlah pekerja perempuan"
|
||||
value={stateDemografi.update.form.perempuan}
|
||||
onChange={(val) => {
|
||||
stateDemografi.update.form.perempuan = Number(val.currentTarget.value);
|
||||
}}
|
||||
onChange={(e) =>
|
||||
(stateDemografi.update.form.perempuan = Number(
|
||||
e.currentTarget.value
|
||||
))
|
||||
}
|
||||
required
|
||||
/>
|
||||
<Button
|
||||
mt={10}
|
||||
bg={colors['blue-button']}
|
||||
onClick={handleSubmit}
|
||||
>
|
||||
Simpan Perubahan
|
||||
</Button>
|
||||
|
||||
<Group justify="flex-end">
|
||||
<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)',
|
||||
}}
|
||||
>
|
||||
Simpan
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export default EditDemografiPekerjaan;
|
||||
|
||||
@@ -1,8 +1,18 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
'use client'
|
||||
'use client';
|
||||
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Group, Paper, Stack, TextInput, Title } from '@mantine/core';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Group,
|
||||
Paper,
|
||||
Stack,
|
||||
TextInput,
|
||||
Title,
|
||||
Tooltip,
|
||||
} from '@mantine/core';
|
||||
import { IconArrowBack } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useState } from 'react';
|
||||
@@ -12,15 +22,15 @@ import demografiPekerjaan from '../../../_state/ekonomi/demografi-pekerjaan';
|
||||
function CreateDemografiPekerjaan() {
|
||||
const stateDemografi = useProxy(demografiPekerjaan);
|
||||
const [chartData, setChartData] = useState<any[]>([]);
|
||||
const router = useRouter()
|
||||
const router = useRouter();
|
||||
|
||||
const resetForm = () => {
|
||||
stateDemografi.create.form = {
|
||||
pekerjaan: "",
|
||||
pekerjaan: '',
|
||||
lakiLaki: 0,
|
||||
perempuan: 0,
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
const id = await stateDemografi.create.create();
|
||||
@@ -32,58 +42,85 @@ function CreateDemografiPekerjaan() {
|
||||
}
|
||||
}
|
||||
resetForm();
|
||||
router.push("/admin/ekonomi/demografi-pekerjaan");
|
||||
}
|
||||
router.push('/admin/ekonomi/demografi-pekerjaan');
|
||||
};
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Box mb={10}>
|
||||
<Button variant="subtle" onClick={() => router.back()}>
|
||||
<IconArrowBack size={20} />
|
||||
</Button>
|
||||
</Box>
|
||||
<Box>
|
||||
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
||||
<Title order={4}>Tambah Demografi Pekerjaan</Title>
|
||||
<Stack gap={"xs"}>
|
||||
<TextInput
|
||||
label="Pekerjaan"
|
||||
type="text"
|
||||
value={stateDemografi.create.form.pekerjaan}
|
||||
placeholder="Masukkan pekerjaan"
|
||||
onChange={(val) => {
|
||||
stateDemografi.create.form.pekerjaan = val.currentTarget.value;
|
||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||
{/* Header */}
|
||||
<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">
|
||||
Tambah Demografi Pekerjaan
|
||||
</Title>
|
||||
</Group>
|
||||
|
||||
{/* Form */}
|
||||
<Paper
|
||||
w={{ base: '100%', md: '50%' }}
|
||||
bg={colors['white-1']}
|
||||
p="lg"
|
||||
radius="md"
|
||||
shadow="sm"
|
||||
style={{ border: '1px solid #e0e0e0' }}
|
||||
>
|
||||
<Stack gap="md">
|
||||
<TextInput
|
||||
label="Pekerjaan"
|
||||
type="text"
|
||||
value={stateDemografi.create.form.pekerjaan}
|
||||
placeholder="Masukkan pekerjaan"
|
||||
onChange={(val) => {
|
||||
stateDemografi.create.form.pekerjaan = val.currentTarget.value;
|
||||
}}
|
||||
required
|
||||
/>
|
||||
<TextInput
|
||||
label="Jumlah Pekerja Laki-Laki"
|
||||
type="number"
|
||||
value={stateDemografi.create.form.lakiLaki}
|
||||
placeholder="Masukkan jumlah pekerja laki-laki"
|
||||
onChange={(val) => {
|
||||
stateDemografi.create.form.lakiLaki = Number(val.currentTarget.value);
|
||||
}}
|
||||
required
|
||||
/>
|
||||
<TextInput
|
||||
label="Jumlah Pekerja Perempuan"
|
||||
type="number"
|
||||
value={stateDemografi.create.form.perempuan}
|
||||
placeholder="Masukkan jumlah pekerja perempuan"
|
||||
onChange={(val) => {
|
||||
stateDemografi.create.form.perempuan = Number(val.currentTarget.value);
|
||||
}}
|
||||
required
|
||||
/>
|
||||
|
||||
<Group justify="right">
|
||||
<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)',
|
||||
}}
|
||||
/>
|
||||
<TextInput
|
||||
label="Jumlah Pekerja Laki - Laki"
|
||||
type="number"
|
||||
value={stateDemografi.create.form.lakiLaki}
|
||||
placeholder="Masukkan jumlah pekerja laki - laki"
|
||||
onChange={(val) => {
|
||||
stateDemografi.create.form.lakiLaki = Number(val.currentTarget.value);
|
||||
}}
|
||||
/>
|
||||
<TextInput
|
||||
label="Jumlah Pekerja Perempuan"
|
||||
type="number"
|
||||
value={stateDemografi.create.form.perempuan}
|
||||
placeholder="Masukkan jumlah pekerja perempuan"
|
||||
onChange={(val) => {
|
||||
stateDemografi.create.form.perempuan = Number(val.currentTarget.value);
|
||||
}}
|
||||
/>
|
||||
<Group>
|
||||
<Button
|
||||
bg={colors['blue-button']}
|
||||
mt={10}
|
||||
onClick={handleSubmit}
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
>
|
||||
Simpan
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,14 +1,32 @@
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { BarChart } from '@mantine/charts';
|
||||
import { Box, Button, Paper, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Center,
|
||||
Group,
|
||||
Paper,
|
||||
Skeleton,
|
||||
Stack,
|
||||
Table,
|
||||
TableTbody,
|
||||
TableTd,
|
||||
TableTh,
|
||||
TableThead,
|
||||
TableTr,
|
||||
Text,
|
||||
Title,
|
||||
Tooltip,
|
||||
Pagination,
|
||||
Flex,
|
||||
} from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { IconEdit, IconSearch, IconTrash } from '@tabler/icons-react';
|
||||
import { IconEdit, IconPlus, IconSearch, IconTrash } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import HeaderSearch from '../../_com/header';
|
||||
import JudulList from '../../_com/judulList';
|
||||
import { ModalKonfirmasiHapus } from '../../_com/modalKonfirmasiHapus';
|
||||
import demografiPekerjaan from '../../_state/ekonomi/demografi-pekerjaan';
|
||||
|
||||
@@ -18,7 +36,7 @@ function DemografiPekerjaan() {
|
||||
<Box>
|
||||
<HeaderSearch
|
||||
title='Demografi Pekerjaan'
|
||||
placeholder='pencarian'
|
||||
placeholder='Cari pekerjaan atau jumlah pekerja...'
|
||||
searchIcon={<IconSearch size={20} />}
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||
@@ -34,131 +52,193 @@ function ListDemografiPekerjaan({ search }: { search: string }) {
|
||||
pekerjaan: string;
|
||||
lakiLaki: number;
|
||||
perempuan: number;
|
||||
}
|
||||
};
|
||||
|
||||
const router = useRouter();
|
||||
const stateDemografi = useProxy(demografiPekerjaan)
|
||||
const stateDemografi = useProxy(demografiPekerjaan);
|
||||
const [chartData, setChartData] = useState<DemografiPekerjaan[]>([]);
|
||||
const [mounted, setMounted] = useState(false); // untuk memastikan DOM sudah ready
|
||||
const [modalHapus, setModalHapus] = useState(false)
|
||||
const [selectedId, setSelectedId] = useState<string | null>(null)
|
||||
const [mounted, setMounted] = useState(false);
|
||||
const [modalHapus, setModalHapus] = useState(false);
|
||||
const [selectedId, setSelectedId] = useState<string | null>(null);
|
||||
|
||||
const {
|
||||
data,
|
||||
page,
|
||||
totalPages,
|
||||
loading,
|
||||
load,
|
||||
} = stateDemografi.findMany;
|
||||
|
||||
const handleDelete = () => {
|
||||
if (selectedId) {
|
||||
stateDemografi.delete.byId(selectedId)
|
||||
setModalHapus(false)
|
||||
setSelectedId(null)
|
||||
|
||||
stateDemografi.findMany.load()
|
||||
stateDemografi.delete.byId(selectedId);
|
||||
setModalHapus(false);
|
||||
setSelectedId(null);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
useShallowEffect(() => {
|
||||
setMounted(true)
|
||||
stateDemografi.findMany.load()
|
||||
}, [])
|
||||
setMounted(true);
|
||||
load(page, 10, search);
|
||||
}, [page, search]);
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
if (stateDemografi.findMany.data) {
|
||||
setChartData(stateDemografi.findMany.data.map((item) => ({
|
||||
id: item.id,
|
||||
pekerjaan: item.pekerjaan,
|
||||
lakiLaki: Number(item.lakiLaki),
|
||||
perempuan: Number(item.perempuan),
|
||||
})));
|
||||
if (data) {
|
||||
setChartData(
|
||||
data.map((item) => ({
|
||||
id: item.id,
|
||||
pekerjaan: item.pekerjaan,
|
||||
lakiLaki: Number(item.lakiLaki),
|
||||
perempuan: Number(item.perempuan),
|
||||
}))
|
||||
);
|
||||
}
|
||||
}, [stateDemografi.findMany.data]);
|
||||
}, [data]);
|
||||
|
||||
const filteredData = (stateDemografi.findMany.data || []).filter(item => {
|
||||
const keyword = search.toLowerCase();
|
||||
const filteredData = data || [];
|
||||
|
||||
if (loading || !data) {
|
||||
return (
|
||||
item.pekerjaan.toLowerCase().includes(keyword) ||
|
||||
item.lakiLaki.toString().toLowerCase().includes(keyword) ||
|
||||
item.perempuan.toString().toLowerCase().includes(keyword)
|
||||
<Stack py={10}>
|
||||
<Skeleton height={600} radius="md" />
|
||||
</Stack>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<Box py={10}>
|
||||
<Paper bg={colors['white-1']} p={'md'}>
|
||||
<JudulList
|
||||
title='List Demografi Pekerjaan'
|
||||
href='/admin/ekonomi/demografi-pekerjaan/create'
|
||||
/>
|
||||
<Table striped withTableBorder withRowBorders>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh>Pekerjaan</TableTh>
|
||||
<TableTh>Jumlah Pekerja Laki - Laki</TableTh>
|
||||
<TableTh>Jumlah Pekerja Perempuan</TableTh>
|
||||
<TableTh>Edit</TableTh>
|
||||
<TableTh>Delete</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{filteredData.map((item) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd>{item.pekerjaan}</TableTd>
|
||||
<TableTd>{item.lakiLaki}</TableTd>
|
||||
<TableTd>{item.perempuan}</TableTd>
|
||||
<TableTd>
|
||||
<Button color='green' onClick={() => router.push(`/admin/ekonomi/demografi-pekerjaan/${item.id}`)}>
|
||||
<IconEdit size={20} />
|
||||
</Button>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Button
|
||||
color='red'
|
||||
disabled={stateDemografi.delete.loading}
|
||||
onClick={() => {
|
||||
setSelectedId(item.id)
|
||||
setModalHapus(true)
|
||||
}}>
|
||||
<IconTrash size={20} />
|
||||
</Button>
|
||||
</TableTd>
|
||||
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
|
||||
<Group justify="space-between" mb="md">
|
||||
<Title order={4}>List Demografi Pekerjaan</Title>
|
||||
<Tooltip label="Tambah Data Pekerjaan" withArrow>
|
||||
<Button
|
||||
leftSection={<IconPlus size={18} />}
|
||||
color="blue"
|
||||
variant="light"
|
||||
onClick={() => router.push('/admin/ekonomi/demografi-pekerjaan/create')}
|
||||
>
|
||||
Tambah Baru
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</Group>
|
||||
|
||||
<Box style={{ overflowX: 'auto' }}>
|
||||
<Table highlightOnHover>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh>Pekerjaan</TableTh>
|
||||
<TableTh>Laki - Laki</TableTh>
|
||||
<TableTh>Perempuan</TableTh>
|
||||
<TableTh>Edit</TableTh>
|
||||
<TableTh>Hapus</TableTh>
|
||||
</TableTr>
|
||||
))}
|
||||
</TableTbody>
|
||||
</Table>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{filteredData.length > 0 ? (
|
||||
filteredData.map((item) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd>{item.pekerjaan}</TableTd>
|
||||
<TableTd>{item.lakiLaki}</TableTd>
|
||||
<TableTd>{item.perempuan}</TableTd>
|
||||
<TableTd>
|
||||
<Button
|
||||
variant="light"
|
||||
color="green"
|
||||
onClick={() =>
|
||||
router.push(`/admin/ekonomi/demografi-pekerjaan/${item.id}`)
|
||||
}
|
||||
>
|
||||
<IconEdit size={18} />
|
||||
</Button>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Button
|
||||
variant="light"
|
||||
color="red"
|
||||
disabled={stateDemografi.delete.loading}
|
||||
onClick={() => {
|
||||
setSelectedId(item.id);
|
||||
setModalHapus(true);
|
||||
}}
|
||||
>
|
||||
<IconTrash size={18} />
|
||||
</Button>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
))
|
||||
) : (
|
||||
<TableTr>
|
||||
<TableTd colSpan={4}>
|
||||
<Center py={20}>
|
||||
<Text color="dimmed">Tidak ada data demografi pekerjaan yang cocok</Text>
|
||||
</Center>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
)}
|
||||
</TableTbody>
|
||||
</Table>
|
||||
</Box>
|
||||
</Paper>
|
||||
|
||||
{/* Pagination */}
|
||||
<Center>
|
||||
<Pagination
|
||||
value={page}
|
||||
onChange={(newPage) => {
|
||||
load(newPage, 10, search);
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
}}
|
||||
total={totalPages}
|
||||
mt="md"
|
||||
mb="md"
|
||||
color="blue"
|
||||
radius="md"
|
||||
/>
|
||||
</Center>
|
||||
|
||||
{/* Chart */}
|
||||
{!mounted && !chartData ? (
|
||||
<Box style={{ width: '100%', minWidth: 300, height: 400, minHeight: 300 }}>
|
||||
<Paper bg={colors['white-1']} p={'md'}>
|
||||
<Title pb={10} order={3}>Data Kelahiran & Kematian</Title>
|
||||
<Text c='dimmed'>Belum ada data untuk ditampilkan dalam grafik</Text>
|
||||
</Paper>
|
||||
</Box>
|
||||
) : (
|
||||
<Box style={{ width: '100%', minWidth: 300, height: 400, minHeight: 300 }}>
|
||||
<Paper bg={colors['white-1']} p={'md'}>
|
||||
<Title pb={10} order={4}>Data Kelahiran & Kematian</Title>
|
||||
{mounted && chartData.length > 0 && (
|
||||
<Box w={{ base: '100%', md: '30%' }}>
|
||||
<BarChart
|
||||
h={450}
|
||||
data={chartData}
|
||||
dataKey="pekerjaan"
|
||||
type="stacked"
|
||||
series={[
|
||||
{ name: 'lakiLaki', color: '#5082EE', label: 'Laki - Laki' },
|
||||
{ name: 'perempuan', color: '#6EDF9C', label: 'Perempuan' },
|
||||
]}
|
||||
/>
|
||||
</Box>
|
||||
<Box mt={30} style={{ width: '100%', minHeight: 400 }}>
|
||||
<Paper bg={colors['white-1']} p="md" radius="md" withBorder>
|
||||
<Stack gap={"xs"}>
|
||||
<Title pb={10} order={4}>
|
||||
Grafik Demografi Pekerjaan
|
||||
</Title>
|
||||
{mounted && chartData.length > 0 ? (
|
||||
<BarChart
|
||||
h={450}
|
||||
data={chartData}
|
||||
dataKey="pekerjaan"
|
||||
type="stacked"
|
||||
series={[
|
||||
{ name: 'lakiLaki', color: '#5082EE', label: 'Laki - Laki' },
|
||||
{ name: 'perempuan', color: '#6EDF9C', label: 'Perempuan' },
|
||||
]}
|
||||
/>
|
||||
) : (
|
||||
<Text c="dimmed">Belum ada data untuk ditampilkan dalam grafik</Text>
|
||||
)}
|
||||
</Paper>
|
||||
</Box>
|
||||
)}
|
||||
<Box py={10}>
|
||||
<Group justify='center'>
|
||||
<Flex align="center" gap={10}>
|
||||
<Box bg="#5082EE" w={20} h={20} />
|
||||
<Text>Laki - Laki</Text>
|
||||
</Flex>
|
||||
<Flex align="center" gap={10}>
|
||||
<Box bg="#6EDF9C" w={20} h={20} />
|
||||
<Text>Perempuan</Text>
|
||||
</Flex>
|
||||
</Group>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
|
||||
{/* Modal Konfirmasi Hapus */}
|
||||
<ModalKonfirmasiHapus
|
||||
opened={modalHapus}
|
||||
onClose={() => setModalHapus(false)}
|
||||
onConfirm={handleDelete}
|
||||
text='Apakah anda yakin ingin menghapus demografi pekerjaan ini?'
|
||||
text="Apakah anda yakin ingin menghapus demografi pekerjaan ini?"
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
|
||||
@@ -3,78 +3,116 @@
|
||||
|
||||
import jumlahPendudukMiskin from '@/app/admin/(dashboard)/_state/ekonomi/jumlah-penduduk-miskin';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Paper, Stack, TextInput, Title } from '@mantine/core';
|
||||
import { Box, Button, Paper, Stack, TextInput, Title, Group, Tooltip } from '@mantine/core';
|
||||
import { IconArrowBack } from '@tabler/icons-react';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import { useEffect } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
function EditJumlahPendudukMiskin() {
|
||||
const router = useRouter()
|
||||
const params = useParams() as { id: string }
|
||||
const stateJPM = useProxy(jumlahPendudukMiskin)
|
||||
const router = useRouter();
|
||||
const params = useParams() as { id: string };
|
||||
const stateJPM = useProxy(jumlahPendudukMiskin);
|
||||
|
||||
const id = params.id
|
||||
const id = params.id;
|
||||
|
||||
// Load data saat komponen mount
|
||||
useEffect(() => {
|
||||
if (id) {
|
||||
stateJPM.findUnique.load(id).then(() => {
|
||||
const data = stateJPM.findUnique.data
|
||||
if (!id) return;
|
||||
|
||||
const loadData = async () => {
|
||||
try {
|
||||
await stateJPM.findUnique.load(id);
|
||||
const data = stateJPM.findUnique.data;
|
||||
if (data) {
|
||||
stateJPM.update.form = {
|
||||
year: data.year || 0,
|
||||
totalPoorPopulation: data.totalPoorPopulation || 0,
|
||||
}
|
||||
};
|
||||
}
|
||||
})
|
||||
}
|
||||
}, [id])
|
||||
} catch (error) {
|
||||
console.error('Gagal memuat data:', error);
|
||||
toast.error('Gagal memuat data jumlah penduduk miskin');
|
||||
}
|
||||
};
|
||||
|
||||
loadData();
|
||||
}, [id]);
|
||||
|
||||
const handleSubmit = async () => {
|
||||
// Set the ID before submitting
|
||||
stateJPM.update.id = id;
|
||||
await stateJPM.update.submit();
|
||||
router.push('/admin/ekonomi/jumlah-penduduk-miskin')
|
||||
}
|
||||
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 Jumlah Penduduk Miskin</Title>
|
||||
try {
|
||||
stateJPM.update.id = id;
|
||||
await stateJPM.update.submit();
|
||||
toast.success('Data jumlah penduduk miskin berhasil diperbarui!');
|
||||
router.push('/admin/ekonomi/jumlah-penduduk-miskin');
|
||||
} catch (error) {
|
||||
console.error('Gagal menyimpan data:', error);
|
||||
toast.error('Terjadi kesalahan saat menyimpan data');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<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 Jumlah Penduduk Miskin
|
||||
</Title>
|
||||
</Group>
|
||||
|
||||
<Paper
|
||||
w={{ base: '100%', md: '50%' }}
|
||||
bg={colors['white-1']}
|
||||
p="lg"
|
||||
radius="md"
|
||||
shadow="sm"
|
||||
style={{ border: '1px solid #e0e0e0' }}
|
||||
>
|
||||
<Stack gap="md">
|
||||
<TextInput
|
||||
label="Tahun"
|
||||
placeholder="masukkan tahun"
|
||||
placeholder="Masukkan tahun"
|
||||
type="number"
|
||||
required
|
||||
value={stateJPM.update.form.year}
|
||||
onChange={(val) => {
|
||||
stateJPM.update.form.year = Number(val.currentTarget.value);
|
||||
}}
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
label="Jumlah Penduduk Miskin"
|
||||
placeholder="Masukkan jumlah penduduk miskin"
|
||||
type="number"
|
||||
placeholder="masukkan jumlah penduduk miskin"
|
||||
required
|
||||
value={stateJPM.update.form.totalPoorPopulation}
|
||||
onChange={(val) => {
|
||||
stateJPM.update.form.totalPoorPopulation = Number(val.currentTarget.value);
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
mt={10}
|
||||
bg={colors['blue-button']}
|
||||
onClick={handleSubmit}
|
||||
>
|
||||
Simpan
|
||||
</Button>
|
||||
|
||||
<Group justify="right">
|
||||
<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)',
|
||||
}}
|
||||
>
|
||||
Simpan
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export default EditJumlahPendudukMiskin;
|
||||
|
||||
@@ -1,26 +1,25 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
'use client'
|
||||
import grafikkepuasan from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/grafikKepuasan';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Group, Paper, Stack, TextInput, Title } from '@mantine/core';
|
||||
'use client';
|
||||
import { Box, Button, Group, Paper, Stack, TextInput, Title, Tooltip } from '@mantine/core';
|
||||
import { IconArrowBack } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import colors from '@/con/colors';
|
||||
import jumlahPendudukMiskin from '../../../_state/ekonomi/jumlah-penduduk-miskin';
|
||||
|
||||
function CreateJumlahPendudukMiskin() {
|
||||
export default function CreateJumlahPendudukMiskin() {
|
||||
const stateJPM = useProxy(jumlahPendudukMiskin);
|
||||
const [chartData, setChartData] = useState<any[]>([]);
|
||||
const router = useRouter()
|
||||
const router = useRouter();
|
||||
|
||||
const resetForm = () => {
|
||||
stateJPM.create.form = {
|
||||
year: new Date().getFullYear(), // Default to current year
|
||||
year: new Date().getFullYear(),
|
||||
totalPoorPopulation: 0,
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
const id = await stateJPM.create.create();
|
||||
@@ -32,52 +31,72 @@ function CreateJumlahPendudukMiskin() {
|
||||
}
|
||||
}
|
||||
resetForm();
|
||||
router.push("/admin/ekonomi/jumlah-penduduk-miskin");
|
||||
}
|
||||
router.push('/admin/ekonomi/jumlah-penduduk-miskin');
|
||||
};
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Box mb={10}>
|
||||
<Button variant="subtle" onClick={() => router.back()}>
|
||||
<IconArrowBack size={20} />
|
||||
</Button>
|
||||
</Box>
|
||||
<Box>
|
||||
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
||||
<Title order={4}>Tambah Grafik Hasil Kepuasan Masyarakat</Title>
|
||||
<Stack gap={"xs"}>
|
||||
<TextInput
|
||||
label="Tahun"
|
||||
type="number"
|
||||
value={stateJPM.create.form.year || ''}
|
||||
placeholder="Masukkan tahun"
|
||||
onChange={(val) => {
|
||||
const value = val.currentTarget.value;
|
||||
stateJPM.create.form.year = value ? Number(value) : 0;
|
||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||
{/* Header */}
|
||||
<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">
|
||||
Tambah Jumlah Penduduk Miskin
|
||||
</Title>
|
||||
</Group>
|
||||
|
||||
{/* Form Paper */}
|
||||
<Paper
|
||||
w={{ base: '100%', md: '50%' }}
|
||||
bg={colors['white-1']}
|
||||
p="lg"
|
||||
radius="md"
|
||||
shadow="sm"
|
||||
style={{ border: '1px solid #e0e0e0' }}
|
||||
>
|
||||
<Stack gap="md">
|
||||
<TextInput
|
||||
label="Tahun"
|
||||
type="number"
|
||||
value={stateJPM.create.form.year || ''}
|
||||
placeholder="Masukkan tahun"
|
||||
onChange={(e) => {
|
||||
const value = e.currentTarget.value;
|
||||
stateJPM.create.form.year = value ? Number(value) : 0;
|
||||
}}
|
||||
required
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
label="Jumlah Penduduk Miskin"
|
||||
type="number"
|
||||
value={stateJPM.create.form.totalPoorPopulation}
|
||||
placeholder="Masukkan jumlah penduduk miskin"
|
||||
onChange={(e) => {
|
||||
stateJPM.create.form.totalPoorPopulation = Number(e.currentTarget.value);
|
||||
}}
|
||||
required
|
||||
/>
|
||||
|
||||
<Group justify="right">
|
||||
<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)',
|
||||
}}
|
||||
/>
|
||||
<TextInput
|
||||
label="Jumlah Penduduk Miskin"
|
||||
type="number"
|
||||
value={stateJPM.create.form.totalPoorPopulation}
|
||||
placeholder="Masukkan jumlah penduduk miskin"
|
||||
onChange={(val) => {
|
||||
stateJPM.create.form.totalPoorPopulation = Number(val.currentTarget.value);
|
||||
}}
|
||||
/>
|
||||
<Group>
|
||||
<Button
|
||||
bg={colors['blue-button']}
|
||||
mt={10}
|
||||
onClick={handleSubmit}
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
>
|
||||
Simpan
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default CreateJumlahPendudukMiskin;
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
'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, Group, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title, Tooltip } from '@mantine/core';
|
||||
import { IconEdit, IconSearch, IconTrash } from '@tabler/icons-react';
|
||||
import HeaderSearch from '../../_com/header';
|
||||
import JudulList from '../../_com/judulList';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useMediaQuery, useShallowEffect } from '@mantine/hooks';
|
||||
import { useShallowEffect, useMediaQuery } from '@mantine/hooks';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import jumlahPendudukMiskin from '../../_state/ekonomi/jumlah-penduduk-miskin';
|
||||
import { Bar, BarChart, Legend, XAxis, YAxis, Tooltip } from 'recharts';
|
||||
import { Bar, BarChart, Legend, XAxis, YAxis, Tooltip as RechartsTooltip } from 'recharts';
|
||||
import { ModalKonfirmasiHapus } from '../../_com/modalKonfirmasiHapus';
|
||||
|
||||
function JumlahPendudukMiskin() {
|
||||
@@ -18,7 +17,7 @@ function JumlahPendudukMiskin() {
|
||||
<Box>
|
||||
<HeaderSearch
|
||||
title='Jumlah Penduduk Miskin'
|
||||
placeholder='pencarian'
|
||||
placeholder='Cari tahun atau jumlah penduduk miskin...'
|
||||
searchIcon={<IconSearch size={20} />}
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||
@@ -29,138 +28,158 @@ function JumlahPendudukMiskin() {
|
||||
}
|
||||
|
||||
function ListJumlahPendudukMiskin({ search }: { search: string }) {
|
||||
type JPMGrafik = {
|
||||
id: string;
|
||||
year: number;
|
||||
totalPoorPopulation: number;
|
||||
}
|
||||
type JPMGrafik = { id: string; year: number; totalPoorPopulation: number }
|
||||
const stateJPM = useProxy(jumlahPendudukMiskin);
|
||||
const [chartData, setChartData] = useState<JPMGrafik[]>([]);
|
||||
const [mounted, setMounted] = useState(false); // untuk memastikan DOM sudah ready
|
||||
const isTablet = useMediaQuery('(max-width: 1024px)')
|
||||
const isMobile = useMediaQuery('(max-width: 768px)')
|
||||
const [modalHapus, setModalHapus] = useState(false)
|
||||
const [selectedId, setSelectedId] = useState<string | null>(null)
|
||||
const [mounted, setMounted] = useState(false);
|
||||
const [modalHapus, setModalHapus] = useState(false);
|
||||
const [selectedId, setSelectedId] = useState<string | null>(null);
|
||||
const router = useRouter();
|
||||
|
||||
const handleDelete = () => {
|
||||
if (selectedId) {
|
||||
stateJPM.delete.byId(selectedId)
|
||||
setModalHapus(false)
|
||||
setSelectedId(null)
|
||||
|
||||
stateJPM.findMany.load()
|
||||
}
|
||||
}
|
||||
const isTablet = useMediaQuery('(max-width:1024px)');
|
||||
const isMobile = useMediaQuery('(max-width:768px)');
|
||||
|
||||
const {
|
||||
data,
|
||||
page,
|
||||
loading,
|
||||
load,
|
||||
totalPages,
|
||||
} = stateJPM.findMany;
|
||||
// Load data
|
||||
useShallowEffect(() => {
|
||||
setMounted(true)
|
||||
stateJPM.findMany.load()
|
||||
}, [])
|
||||
setMounted(true);
|
||||
load(page, 10, search);
|
||||
}, [page, search]);
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
if (stateJPM.findMany.data) {
|
||||
setChartData(stateJPM.findMany.data.map((item) => ({
|
||||
setChartData(stateJPM.findMany.data.map(item => ({
|
||||
id: item.id,
|
||||
year: Number(item.year),
|
||||
totalPoorPopulation: Number(item.totalPoorPopulation),
|
||||
totalPoorPopulation: Number(item.totalPoorPopulation)
|
||||
})));
|
||||
}
|
||||
}, [stateJPM.findMany.data]);
|
||||
|
||||
const filteredData = (stateJPM.findMany.data || []).filter(item => {
|
||||
const keyword = search.toLowerCase();
|
||||
return (
|
||||
item.year.toString().toLowerCase().includes(keyword) ||
|
||||
item.totalPoorPopulation.toString().toLowerCase().includes(keyword)
|
||||
);
|
||||
});
|
||||
const filteredData = data || []
|
||||
|
||||
if (!stateJPM.findMany.data) {
|
||||
const handleDelete = () => {
|
||||
if (selectedId) {
|
||||
stateJPM.delete.byId(selectedId);
|
||||
setModalHapus(false);
|
||||
setSelectedId(null);
|
||||
stateJPM.findMany.load();
|
||||
}
|
||||
}
|
||||
|
||||
if (loading || !data) {
|
||||
return (
|
||||
<Stack>
|
||||
<Skeleton h={500} />
|
||||
<Stack py={10}>
|
||||
<Skeleton height={500} radius="md" />
|
||||
</Stack>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Box py={10}>
|
||||
<Stack gap={'xs'}>
|
||||
<Paper bg={colors['white-1']} p={'md'}>
|
||||
<JudulList
|
||||
title='List Jumlah Penduduk Miskin'
|
||||
href='/admin/ekonomi/jumlah-penduduk-miskin/create'
|
||||
/>
|
||||
<Table striped withTableBorder withRowBorders>
|
||||
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
|
||||
<Group justify="space-between" mb="md">
|
||||
<Title order={4}>Daftar Jumlah Penduduk Miskin</Title>
|
||||
<Tooltip label="Tambah Data" withArrow>
|
||||
<Button
|
||||
leftSection={<IconEdit size={18} />}
|
||||
color="blue"
|
||||
variant="light"
|
||||
onClick={() => router.push('/admin/ekonomi/jumlah-penduduk-miskin/create')}
|
||||
>
|
||||
Tambah Baru
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</Group>
|
||||
|
||||
<Box style={{ overflowX: 'auto' }}>
|
||||
<Table highlightOnHover>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh>Tahun</TableTh>
|
||||
<TableTh>Jumlah Penduduk Miskin</TableTh>
|
||||
<TableTh>Edit</TableTh>
|
||||
<TableTh>Delete</TableTh>
|
||||
<TableTh style={{ width: '25%' }}>Tahun</TableTh>
|
||||
<TableTh style={{ width: '35%' }}>Jumlah Penduduk Miskin</TableTh>
|
||||
<TableTh style={{ width: '20%' }}>Edit</TableTh>
|
||||
<TableTh style={{ width: '20%' }}>Delete</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{filteredData.map((item) => (
|
||||
{filteredData.length > 0 ? filteredData.map(item => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd>{item.year}</TableTd>
|
||||
<TableTd>{item.totalPoorPopulation}</TableTd>
|
||||
<TableTd>
|
||||
<Button color='green' onClick={() => router.push(`/admin/ekonomi/jumlah-penduduk-miskin/${item.id}`)}>
|
||||
<Button variant='light' color="green" onClick={() => router.push(`/admin/ekonomi/jumlah-penduduk-miskin/${item.id}`)}>
|
||||
<IconEdit size={20} />
|
||||
</Button>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Button
|
||||
color='red'
|
||||
disabled={stateJPM.delete.loading}
|
||||
onClick={() => {
|
||||
setSelectedId(item.id)
|
||||
setModalHapus(true)
|
||||
}}>
|
||||
<Button variant='light' color="red" disabled={stateJPM.delete.loading} onClick={() => { setSelectedId(item.id); setModalHapus(true) }}>
|
||||
<IconTrash size={20} />
|
||||
</Button>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
))}
|
||||
)) : (
|
||||
<TableTr>
|
||||
<TableTd colSpan={4}>
|
||||
<Center py={20}>
|
||||
<Text color="dimmed">Tidak ada data yang cocok</Text>
|
||||
</Center>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
)}
|
||||
</TableTbody>
|
||||
</Table>
|
||||
</Paper>
|
||||
</Box>
|
||||
</Paper>
|
||||
|
||||
{/* Chart */}
|
||||
{!mounted && !chartData ? (
|
||||
<Box style={{ width: '100%', minWidth: 300, height: 400, minHeight: 300 }}>
|
||||
<Paper bg={colors['white-1']} p={'md'}>
|
||||
<Title pb={10} order={3}>Grafik Jumlah Penduduk Miskin</Title>
|
||||
<Text c='dimmed'>Belum ada data untuk ditampilkan dalam grafik</Text>
|
||||
</Paper>
|
||||
</Box>
|
||||
) : (
|
||||
<Box style={{ width: '100%', minWidth: 300, height: 400, minHeight: 300 }}>
|
||||
<Paper bg={colors['white-1']} p={'md'}>
|
||||
<Title pb={10} order={4}>Grafik Jumlah Penduduk Miskin</Title>
|
||||
{mounted && chartData.length > 0 && (
|
||||
<BarChart width={isMobile ? 450 : isTablet ? 500 : 550} height={350} data={chartData} >
|
||||
<XAxis dataKey="year" />
|
||||
<YAxis />
|
||||
<Tooltip />
|
||||
<Legend />
|
||||
<Bar dataKey="totalPoorPopulation" fill={colors['blue-button']} name="Jumlah Penduduk Miskin" />
|
||||
</BarChart>
|
||||
)}
|
||||
</Paper>
|
||||
</Box>
|
||||
)}
|
||||
</Stack>
|
||||
<Center>
|
||||
<Pagination
|
||||
value={page}
|
||||
onChange={(newPage) => {
|
||||
load(newPage, 10);
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
}}
|
||||
total={totalPages}
|
||||
mt="md"
|
||||
mb="md"
|
||||
color="blue"
|
||||
radius="md"
|
||||
/>
|
||||
</Center>
|
||||
|
||||
{/* Modal Konfirmasi Hapus */}
|
||||
{/* Chart */}
|
||||
<Paper bg={colors['white-1']} p="md" mt="lg" withBorder radius="md">
|
||||
<Stack>
|
||||
<Box mt="lg" style={{ width: '100%', minHeight: 350 }}>
|
||||
<Title order={4} mb="sm">Grafik Jumlah Penduduk Miskin</Title>
|
||||
{mounted && chartData.length > 0 ? (
|
||||
<BarChart width={isMobile ? 450 : isTablet ? 500 : 550} height={350} data={chartData}>
|
||||
<XAxis dataKey="year" />
|
||||
<YAxis />
|
||||
<RechartsTooltip />
|
||||
<Legend />
|
||||
<Bar dataKey="totalPoorPopulation" fill={colors['blue-button']} name="Jumlah Penduduk Miskin" />
|
||||
</BarChart>
|
||||
) : (
|
||||
<Text c="dimmed">Belum ada data untuk ditampilkan dalam grafik</Text>
|
||||
)}
|
||||
</Box>
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
|
||||
{/* Modal Hapus */}
|
||||
<ModalKonfirmasiHapus
|
||||
opened={modalHapus}
|
||||
onClose={() => setModalHapus(false)}
|
||||
onConfirm={handleDelete}
|
||||
text='Apakah anda yakin ingin menghapus grafik jumlah penduduk miskin ini?'
|
||||
text='Apakah anda yakin ingin menghapus data ini?'
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
|
||||
@@ -1,62 +1,124 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Stack, Tabs, TabsList, TabsPanel, TabsTab, Title } from '@mantine/core';
|
||||
import {
|
||||
Stack,
|
||||
Tabs,
|
||||
TabsList,
|
||||
TabsPanel,
|
||||
TabsTab,
|
||||
Title,
|
||||
Tooltip,
|
||||
ScrollArea,
|
||||
} from '@mantine/core';
|
||||
import { usePathname, useRouter } from 'next/navigation';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { IconUsers, IconSchool } from '@tabler/icons-react';
|
||||
|
||||
function LayoutTabs({ children }: { children: React.ReactNode }) {
|
||||
const router = useRouter()
|
||||
const pathname = usePathname()
|
||||
const tabs = [
|
||||
{
|
||||
label: "Pengangguran Berdasarkan Usia",
|
||||
value: "pengangguranberdasarkanusia",
|
||||
href: "/admin/ekonomi/jumlah-penduduk-usia-kerja-yang-menganggur/pengangguran_berdasarkan_usia"
|
||||
},
|
||||
{
|
||||
label: "Pengangguran Berdasarkan Pendidikan",
|
||||
value: "pengangguranberdasarkanpendidikan",
|
||||
href: "/admin/ekonomi/jumlah-penduduk-usia-kerja-yang-menganggur/pengangguran_berdasarkan_pendidikan"
|
||||
},
|
||||
];
|
||||
const curentTab = tabs.find(tab => tab.href === pathname)
|
||||
const [activeTab, setActiveTab] = useState<string | null>(curentTab?.value || tabs[0].value);
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
|
||||
const handleTabChange = (value: string | null) => {
|
||||
const tab = tabs.find(t => t.value === value)
|
||||
if (tab) {
|
||||
router.push(tab.href)
|
||||
}
|
||||
setActiveTab(value)
|
||||
}
|
||||
const tabs = [
|
||||
{
|
||||
label: "Pengangguran Berdasarkan Usia",
|
||||
value: "pengangguranberdasarkanusia",
|
||||
href: "/admin/ekonomi/jumlah-penduduk-usia-kerja-yang-menganggur/pengangguran_berdasarkan_usia",
|
||||
icon: <IconUsers size={18} stroke={1.8} />,
|
||||
tooltip: "Data pengangguran menurut kelompok usia",
|
||||
},
|
||||
{
|
||||
label: "Pengangguran Berdasarkan Pendidikan",
|
||||
value: "pengangguranberdasarkanpendidikan",
|
||||
href: "/admin/ekonomi/jumlah-penduduk-usia-kerja-yang-menganggur/pengangguran_berdasarkan_pendidikan",
|
||||
icon: <IconSchool size={18} stroke={1.8} />,
|
||||
tooltip: "Data pengangguran menurut tingkat pendidikan",
|
||||
},
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
const match = tabs.find(tab => tab.href === pathname)
|
||||
if (match) {
|
||||
setActiveTab(match.value)
|
||||
}
|
||||
}, [pathname])
|
||||
const currentTab = tabs.find(tab => tab.href === pathname);
|
||||
const [activeTab, setActiveTab] = useState<string | null>(currentTab?.value || tabs[0].value);
|
||||
|
||||
return (
|
||||
<Stack>
|
||||
<Title order={3}>Jumlah Penduduk Usia Kerja yang Menganggur</Title>
|
||||
<Tabs color={colors['blue-button']} variant='pills' value={activeTab} onChange={handleTabChange}>
|
||||
<TabsList p={"xs"} bg={"#BBC8E7FF"}>
|
||||
{tabs.map((e, i) => (
|
||||
<TabsTab key={i} value={e.value}>{e.label}</TabsTab>
|
||||
))}
|
||||
</TabsList>
|
||||
{tabs.map((e, i) => (
|
||||
<TabsPanel key={i} value={e.value}>
|
||||
{/* Konten dummy, bisa diganti tergantung routing */}
|
||||
<></>
|
||||
</TabsPanel>
|
||||
))}
|
||||
</Tabs>
|
||||
const handleTabChange = (value: string | null) => {
|
||||
const tab = tabs.find(t => t.value === value);
|
||||
if (tab) router.push(tab.href);
|
||||
setActiveTab(value);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const match = tabs.find(tab => tab.href === pathname);
|
||||
if (match) setActiveTab(match.value);
|
||||
}, [pathname]);
|
||||
|
||||
return (
|
||||
<Stack gap="lg">
|
||||
<Title order={2} fw={700} style={{ color: "#1A1B1E" }}>
|
||||
Jumlah Penduduk Usia Kerja yang Menganggur
|
||||
</Title>
|
||||
|
||||
<Tabs
|
||||
color={colors['blue-button']}
|
||||
variant="pills"
|
||||
value={activeTab}
|
||||
onChange={handleTabChange}
|
||||
radius="lg"
|
||||
keepMounted={false}
|
||||
>
|
||||
<ScrollArea type="auto" offsetScrollbars>
|
||||
<TabsList
|
||||
p="sm"
|
||||
style={{
|
||||
background: "linear-gradient(135deg, #e7ebf7, #f9faff)",
|
||||
borderRadius: "1rem",
|
||||
boxShadow: "inset 0 0 10px rgba(0,0,0,0.05)",
|
||||
display: "flex",
|
||||
flexWrap: "nowrap",
|
||||
gap: "0.5rem",
|
||||
paddingInline: "0.5rem",
|
||||
}}
|
||||
>
|
||||
{tabs.map((tab, i) => (
|
||||
<Tooltip
|
||||
key={i}
|
||||
label={tab.tooltip}
|
||||
position="bottom"
|
||||
withArrow
|
||||
transitionProps={{ transition: "pop", duration: 200 }}
|
||||
>
|
||||
<TabsTab
|
||||
value={tab.value}
|
||||
leftSection={tab.icon}
|
||||
style={{
|
||||
fontWeight: 600,
|
||||
fontSize: "0.9rem",
|
||||
transition: "all 0.2s ease",
|
||||
flexShrink: 0,
|
||||
}}
|
||||
>
|
||||
{tab.label}
|
||||
</TabsTab>
|
||||
</Tooltip>
|
||||
))}
|
||||
</TabsList>
|
||||
</ScrollArea>
|
||||
|
||||
{tabs.map((tab, i) => (
|
||||
<TabsPanel
|
||||
key={i}
|
||||
value={tab.value}
|
||||
style={{
|
||||
padding: "1.5rem",
|
||||
background: "linear-gradient(180deg, #ffffff, #f5f6fa)",
|
||||
borderRadius: "1rem",
|
||||
boxShadow: "0 4px 16px rgba(0,0,0,0.05)",
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</Stack>
|
||||
);
|
||||
</TabsPanel>
|
||||
))}
|
||||
</Tabs>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
export default LayoutTabs;
|
||||
export default LayoutTabs;
|
||||
|
||||
@@ -1,108 +1,117 @@
|
||||
'use client'
|
||||
'use client';
|
||||
import grafikNganggur from '@/app/admin/(dashboard)/_state/ekonomi/usia-kerja-nganggur';
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Paper, Stack, TextInput, Title } from '@mantine/core';
|
||||
import { Box, Button, Group, Paper, Stack, TextInput, Title, Tooltip } from '@mantine/core';
|
||||
import { IconArrowBack } from '@tabler/icons-react';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import { useEffect } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
function EditGrafikBerdasarkanPendidikan() {
|
||||
const router = useRouter()
|
||||
const params = useParams() as { id: string }
|
||||
const stategrafik = useProxy(grafikNganggur.grafikBerdasarkanPendidikan)
|
||||
const id = params.id
|
||||
const router = useRouter();
|
||||
const params = useParams() as { id: string };
|
||||
const stategrafik = useProxy(grafikNganggur.grafikBerdasarkanPendidikan);
|
||||
const id = params.id;
|
||||
|
||||
useEffect(() => {
|
||||
if(id){
|
||||
if (id) {
|
||||
stategrafik.findUnique.load(id).then(() => {
|
||||
const data = stategrafik.findUnique.data
|
||||
if(data){
|
||||
const data = stategrafik.findUnique.data;
|
||||
if (data) {
|
||||
stategrafik.update.form = {
|
||||
SD: data.SD || '',
|
||||
SMP: data.SMP || '',
|
||||
SMA: data.SMA || '',
|
||||
D3: data.D3 || '',
|
||||
S1: data.S1 || '',
|
||||
}
|
||||
};
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
}, [id])
|
||||
}, [id]);
|
||||
|
||||
const handleSubmit = async () => {
|
||||
stategrafik.update.id = id;
|
||||
await stategrafik.update.submit();
|
||||
router.push('/admin/ekonomi/jumlah-penduduk-usia-kerja-yang-menganggur/pengangguran_berdasarkan_pendidikan')
|
||||
}
|
||||
router.push('/admin/ekonomi/jumlah-penduduk-usia-kerja-yang-menganggur/pengangguran_berdasarkan_pendidikan');
|
||||
};
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Box mb={10}>
|
||||
<Button variant="subtle" onClick={() => router.back()}>
|
||||
<IconArrowBack size={20} />
|
||||
</Button>
|
||||
<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 Grafik Pengangguran Berdasarkan Pendidikan
|
||||
</Title>
|
||||
</Group>
|
||||
|
||||
<Paper
|
||||
w={{ base: '100%', md: '50%' }}
|
||||
bg={colors['white-1']}
|
||||
p="lg"
|
||||
radius="md"
|
||||
shadow="sm"
|
||||
style={{ border: '1px solid #e0e0e0' }}
|
||||
>
|
||||
<Stack gap="md">
|
||||
<TextInput
|
||||
label="SD"
|
||||
type="number"
|
||||
placeholder="Masukkan jumlah"
|
||||
value={stategrafik.update.form.SD}
|
||||
onChange={(val) => (stategrafik.update.form.SD = val.currentTarget.value)}
|
||||
/>
|
||||
<TextInput
|
||||
label="SMP"
|
||||
type="number"
|
||||
placeholder="Masukkan jumlah"
|
||||
value={stategrafik.update.form.SMP}
|
||||
onChange={(val) => (stategrafik.update.form.SMP = val.currentTarget.value)}
|
||||
/>
|
||||
<TextInput
|
||||
label="SMA"
|
||||
type="number"
|
||||
placeholder="Masukkan jumlah"
|
||||
value={stategrafik.update.form.SMA}
|
||||
onChange={(val) => (stategrafik.update.form.SMA = val.currentTarget.value)}
|
||||
/>
|
||||
<TextInput
|
||||
label="D3"
|
||||
type="number"
|
||||
placeholder="Masukkan jumlah"
|
||||
value={stategrafik.update.form.D3}
|
||||
onChange={(val) => (stategrafik.update.form.D3 = val.currentTarget.value)}
|
||||
/>
|
||||
<TextInput
|
||||
label="S1"
|
||||
type="number"
|
||||
placeholder="Masukkan jumlah"
|
||||
value={stategrafik.update.form.S1}
|
||||
onChange={(val) => (stategrafik.update.form.S1 = val.currentTarget.value)}
|
||||
/>
|
||||
|
||||
<Group justify="right">
|
||||
<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)',
|
||||
}}
|
||||
>
|
||||
Simpan
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
<Paper bg={colors['white-1']} w={{ base: '100%', md: '50%' }} p={'md'}>
|
||||
<Stack>
|
||||
<Title order={3}>Edit Grafik Pengangguran Berdasarkan Pendidikan</Title>
|
||||
<TextInput
|
||||
label="SD"
|
||||
type='number'
|
||||
placeholder="masukkan jumlah"
|
||||
value={stategrafik.update.form.SD}
|
||||
onChange={(val) => {
|
||||
stategrafik.update.form.SD = val.currentTarget.value;
|
||||
}}
|
||||
/>
|
||||
<TextInput
|
||||
label="SMP"
|
||||
type="number"
|
||||
placeholder="masukkan jumlah"
|
||||
value={stategrafik.update.form.SMP}
|
||||
onChange={(val) => {
|
||||
stategrafik.update.form.SMP = val.currentTarget.value;
|
||||
}}
|
||||
/>
|
||||
<TextInput
|
||||
label="SMA"
|
||||
type="number"
|
||||
placeholder="masukkan jumlah"
|
||||
value={stategrafik.update.form.SMA}
|
||||
onChange={(val) => {
|
||||
stategrafik.update.form.SMA = val.currentTarget.value;
|
||||
}}
|
||||
/>
|
||||
<TextInput
|
||||
label="D3"
|
||||
type="number"
|
||||
placeholder="masukkan jumlah"
|
||||
value={stategrafik.update.form.D3}
|
||||
onChange={(val) => {
|
||||
stategrafik.update.form.D3 = val.currentTarget.value;
|
||||
}}
|
||||
/>
|
||||
<TextInput
|
||||
label="S1"
|
||||
type="number"
|
||||
placeholder="masukkan jumlah"
|
||||
value={stategrafik.update.form.S1}
|
||||
onChange={(val) => {
|
||||
stategrafik.update.form.S1 = val.currentTarget.value;
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
mt={10}
|
||||
bg={colors['blue-button']}
|
||||
onClick={handleSubmit}
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,32 +1,30 @@
|
||||
'use client'
|
||||
'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 grafikNganggur from '@/app/admin/(dashboard)/_state/ekonomi/usia-kerja-nganggur';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import { useState } from 'react';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Paper, Stack, Title, TextInput } from '@mantine/core';
|
||||
import { Box, Button, Paper, Stack, Title, TextInput, Group, Tooltip } from '@mantine/core';
|
||||
import { IconArrowBack } from '@tabler/icons-react';
|
||||
import grafikNganggur from '@/app/admin/(dashboard)/_state/ekonomi/usia-kerja-nganggur';
|
||||
|
||||
|
||||
function CreateGrafikBerdasarkanPendidikan() {
|
||||
const router = useRouter();
|
||||
const stategrafik = useProxy(grafikNganggur.grafikBerdasarkanPendidikan)
|
||||
const stategrafik = useProxy(grafikNganggur.grafikBerdasarkanPendidikan);
|
||||
const [donutData, setDonutData] = useState<any[]>([]);
|
||||
|
||||
const resetForm = () => {
|
||||
stategrafik.create.form = {
|
||||
...stategrafik.create.form,
|
||||
SD: "",
|
||||
SMP: "",
|
||||
SMA: "",
|
||||
D3: "",
|
||||
S1: "",
|
||||
}
|
||||
}
|
||||
SD: '',
|
||||
SMP: '',
|
||||
SMA: '',
|
||||
D3: '',
|
||||
S1: '',
|
||||
};
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
const id = await stategrafik.create.create();
|
||||
@@ -38,73 +36,91 @@ function CreateGrafikBerdasarkanPendidikan() {
|
||||
}
|
||||
}
|
||||
resetForm();
|
||||
router.push("/admin/ekonomi/jumlah-penduduk-usia-kerja-yang-menganggur/pengangguran_berdasarkan_pendidikan")
|
||||
}
|
||||
router.push(
|
||||
'/admin/ekonomi/jumlah-penduduk-usia-kerja-yang-menganggur/pengangguran_berdasarkan_pendidikan'
|
||||
);
|
||||
};
|
||||
|
||||
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}>Create Grafik Pengangguran Berdasarkan Pendidikan</Title>
|
||||
<TextInput
|
||||
label="SD"
|
||||
type='number'
|
||||
placeholder="masukkan jumlah"
|
||||
value={stategrafik.create.form.SD}
|
||||
onChange={(val) => {
|
||||
stategrafik.create.form.SD = val.currentTarget.value;
|
||||
}}
|
||||
/>
|
||||
<TextInput
|
||||
label="SMP"
|
||||
type="number"
|
||||
placeholder="masukkan jumlah"
|
||||
value={stategrafik.create.form.SMP}
|
||||
onChange={(val) => {
|
||||
stategrafik.create.form.SMP = val.currentTarget.value;
|
||||
}}
|
||||
/>
|
||||
<TextInput
|
||||
label="SMA"
|
||||
type="number"
|
||||
placeholder="masukkan jumlah"
|
||||
value={stategrafik.create.form.SMA}
|
||||
onChange={(val) => {
|
||||
stategrafik.create.form.SMA = val.currentTarget.value;
|
||||
}}
|
||||
/>
|
||||
<TextInput
|
||||
label="D3"
|
||||
type="number"
|
||||
placeholder="masukkan jumlah"
|
||||
value={stategrafik.create.form.D3}
|
||||
onChange={(val) => {
|
||||
stategrafik.create.form.D3 = val.currentTarget.value;
|
||||
}}
|
||||
/>
|
||||
<TextInput
|
||||
label="S1"
|
||||
type="number"
|
||||
placeholder="masukkan jumlah"
|
||||
value={stategrafik.create.form.S1}
|
||||
onChange={(val) => {
|
||||
stategrafik.create.form.S1 = val.currentTarget.value;
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
mt={10}
|
||||
bg={colors['blue-button']}
|
||||
onClick={handleSubmit}
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
<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">
|
||||
Tambah Data Pengangguran Berdasarkan Pendidikan
|
||||
</Title>
|
||||
</Group>
|
||||
|
||||
<Paper
|
||||
w={{ base: '100%', md: '50%' }}
|
||||
bg={colors['white-1']}
|
||||
p="lg"
|
||||
radius="md"
|
||||
shadow="sm"
|
||||
style={{ border: '1px solid #e0e0e0' }}
|
||||
>
|
||||
<Stack gap="md">
|
||||
<TextInput
|
||||
label="SD"
|
||||
type="number"
|
||||
placeholder="Masukkan jumlah"
|
||||
value={stategrafik.create.form.SD}
|
||||
onChange={(val) => (stategrafik.create.form.SD = val.currentTarget.value)}
|
||||
required
|
||||
/>
|
||||
<TextInput
|
||||
label="SMP"
|
||||
type="number"
|
||||
placeholder="Masukkan jumlah"
|
||||
value={stategrafik.create.form.SMP}
|
||||
onChange={(val) => (stategrafik.create.form.SMP = val.currentTarget.value)}
|
||||
required
|
||||
/>
|
||||
<TextInput
|
||||
label="SMA"
|
||||
type="number"
|
||||
placeholder="Masukkan jumlah"
|
||||
value={stategrafik.create.form.SMA}
|
||||
onChange={(val) => (stategrafik.create.form.SMA = val.currentTarget.value)}
|
||||
required
|
||||
/>
|
||||
<TextInput
|
||||
label="D3"
|
||||
type="number"
|
||||
placeholder="Masukkan jumlah"
|
||||
value={stategrafik.create.form.D3}
|
||||
onChange={(val) => (stategrafik.create.form.D3 = val.currentTarget.value)}
|
||||
required
|
||||
/>
|
||||
<TextInput
|
||||
label="S1"
|
||||
type="number"
|
||||
placeholder="Masukkan jumlah"
|
||||
value={stategrafik.create.form.S1}
|
||||
onChange={(val) => (stategrafik.create.form.S1 = val.currentTarget.value)}
|
||||
required
|
||||
/>
|
||||
|
||||
<Group justify="right" mt="md">
|
||||
<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)',
|
||||
}}
|
||||
>
|
||||
Simpan
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,59 +1,62 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Flex, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core';
|
||||
import { Box, Button, Center, Flex, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title, Tooltip } from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { IconEdit, IconSearch, IconTrash } from '@tabler/icons-react';
|
||||
import { IconEdit, IconPlus, IconSearch, IconTrash } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Cell, Pie, PieChart } from 'recharts';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import HeaderSearch from '../../../_com/header';
|
||||
import JudulList from '../../../_com/judulList';
|
||||
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
||||
import grafikNganggur from '../../../_state/ekonomi/usia-kerja-nganggur';
|
||||
|
||||
function GrafikBerdasarkanPendidikan() {
|
||||
const [search, setSearch] = useState("")
|
||||
const [search, setSearch] = useState("");
|
||||
return (
|
||||
<Box>
|
||||
<Stack gap={"xs"}>
|
||||
<HeaderSearch
|
||||
title='Detail Data Pengangguran Berdasarkan Pendidikan'
|
||||
placeholder='pencarian'
|
||||
placeholder='Cari data pendidikan...'
|
||||
searchIcon={<IconSearch size={20} />}
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||
/>
|
||||
<ListGrafikBerdasarkanPendidikan search={search}/>
|
||||
</Stack>
|
||||
<ListGrafikBerdasarkanPendidikan search={search} />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
function ListGrafikBerdasarkanPendidikan({search}: {search: string}) {
|
||||
const stategrafik = useProxy(grafikNganggur.grafikBerdasarkanPendidikan)
|
||||
function ListGrafikBerdasarkanPendidikan({ search }: { search: string }) {
|
||||
const stategrafik = useProxy(grafikNganggur.grafikBerdasarkanPendidikan);
|
||||
const [donutData, setDonutData] = useState<any[]>([]);
|
||||
const [mounted, setMounted] = useState(false);
|
||||
const [modalHapus, setModalHapus] = useState(false)
|
||||
const [selectedId, setSelectedId] = useState<string | null>(null)
|
||||
const [modalHapus, setModalHapus] = useState(false);
|
||||
const [selectedId, setSelectedId] = useState<string | null>(null);
|
||||
const router = useRouter();
|
||||
|
||||
|
||||
const handleDelete = async () => {
|
||||
if (selectedId) {
|
||||
await stategrafik.delete.byId(selectedId)
|
||||
setModalHapus(false)
|
||||
setSelectedId(null)
|
||||
|
||||
stategrafik.findMany.load()
|
||||
await stategrafik.delete.byId(selectedId);
|
||||
setModalHapus(false);
|
||||
setSelectedId(null);
|
||||
stategrafik.findMany.load();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const {
|
||||
data,
|
||||
page,
|
||||
totalPages,
|
||||
loading,
|
||||
load,
|
||||
} = stategrafik.findMany;
|
||||
|
||||
useShallowEffect(() => {
|
||||
setMounted(true);
|
||||
stategrafik.findMany.load()
|
||||
}, []);
|
||||
load(page, 10, search);
|
||||
}, [page, search]);
|
||||
|
||||
useEffect(() => {
|
||||
if (stategrafik.findMany.data) {
|
||||
@@ -70,36 +73,37 @@ function ListGrafikBerdasarkanPendidikan({search}: {search: string}) {
|
||||
{ name: 'S1', value: S1, color: '#1018A8FF', key: 'S1' },
|
||||
]);
|
||||
}
|
||||
}, [stategrafik.findMany.data])
|
||||
}, [stategrafik.findMany.data]);
|
||||
|
||||
const filteredData = (stategrafik.findMany.data || []).filter(item => {
|
||||
const keyword = search.toLowerCase();
|
||||
const filteredData = data || []
|
||||
|
||||
if (loading || !data) {
|
||||
return (
|
||||
item.SD.toString().toLowerCase().includes(keyword) ||
|
||||
item.SMP.toString().toLowerCase().includes(keyword) ||
|
||||
item.SMA.toString().toLowerCase().includes(keyword) ||
|
||||
item.D3.toString().toLowerCase().includes(keyword) ||
|
||||
item.S1.toString().toLowerCase().includes(keyword)
|
||||
<Stack py={10}>
|
||||
<Skeleton height={500} radius="md" />
|
||||
</Stack>
|
||||
);
|
||||
});
|
||||
|
||||
if (!stategrafik.findMany.data) {
|
||||
return (
|
||||
<Box>
|
||||
<Skeleton h={500} />
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Stack gap={"xs"}>
|
||||
<Paper bg={colors['white-1']} p={"md"}>
|
||||
<JudulList
|
||||
title='List Grafik Pengangguran Berdasarkan Pendidikan'
|
||||
href='/admin/ekonomi/jumlah-penduduk-usia-kerja-yang-menganggur/pengangguran_berdasarkan_pendidikan/create'
|
||||
/>
|
||||
<Table striped withTableBorder withRowBorders>
|
||||
<Box py={10}>
|
||||
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
|
||||
{/* Header */}
|
||||
<Flex justify="space-between" align="center" mb="md">
|
||||
<Title order={4}>List Pengangguran Berdasarkan Usia Kerja</Title>
|
||||
<Tooltip label="Tambah Data" withArrow>
|
||||
<Button
|
||||
leftSection={<IconPlus size={18} />}
|
||||
color="blue"
|
||||
variant="light"
|
||||
onClick={() => router.push('/admin/ekonomi/jumlah-penduduk-usia-kerja-yang-menganggur/pengangguran_berdasarkan_usia/create')}
|
||||
>
|
||||
Tambah Baru
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</Flex>
|
||||
<Box style={{ overflowX: 'auto' }}>
|
||||
<Table highlightOnHover>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh>SD</TableTh>
|
||||
@@ -114,8 +118,10 @@ function ListGrafikBerdasarkanPendidikan({search}: {search: string}) {
|
||||
<TableTbody>
|
||||
{filteredData.length === 0 ? (
|
||||
<TableTr>
|
||||
<TableTd colSpan={6}>
|
||||
<Text ta='center' c='dimmed'>Belum ada data grafik responden</Text>
|
||||
<TableTd colSpan={7}>
|
||||
<Center py={20}>
|
||||
<Text color="dimmed">Belum ada data grafik responden</Text>
|
||||
</Center>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
) : (
|
||||
@@ -127,82 +133,87 @@ function ListGrafikBerdasarkanPendidikan({search}: {search: string}) {
|
||||
<TableTd>{item.D3}</TableTd>
|
||||
<TableTd>{item.S1}</TableTd>
|
||||
<TableTd>
|
||||
<Button color='green' onClick={() => router.push(`/admin/ekonomi/jumlah-penduduk-usia-kerja-yang-menganggur/pengangguran_berdasarkan_pendidikan/${item.id}`)}>
|
||||
<IconEdit size={20} />
|
||||
</Button>
|
||||
<Tooltip label="Edit Data" withArrow>
|
||||
<Button color="green" variant="light" onClick={() => router.push(`/admin/ekonomi/jumlah-penduduk-usia-kerja-yang-menganggur/pengangguran_berdasarkan_pendidikan/${item.id}`)}>
|
||||
<IconEdit size={18} />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Button
|
||||
color='red'
|
||||
disabled={stategrafik.delete.loading}
|
||||
onClick={() => {
|
||||
setSelectedId(item.id)
|
||||
setModalHapus(true)
|
||||
}}>
|
||||
<IconTrash size={20} />
|
||||
</Button>
|
||||
<Tooltip label="Hapus Data" withArrow>
|
||||
<Button
|
||||
color="red"
|
||||
variant="light"
|
||||
disabled={stategrafik.delete.loading}
|
||||
onClick={() => {
|
||||
setSelectedId(item.id);
|
||||
setModalHapus(true);
|
||||
}}>
|
||||
<IconTrash size={18} />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
))
|
||||
)}
|
||||
</TableTbody>
|
||||
|
||||
</Table>
|
||||
</Paper>
|
||||
</Box>
|
||||
</Paper>
|
||||
|
||||
{/* Chart */}
|
||||
<Box>
|
||||
<Paper bg={colors['white-1']} p={'md'}>
|
||||
<Stack>
|
||||
<Title pb={10} order={3}>Grafik Pengangguran Berdasarkan Pendidikan</Title>
|
||||
{mounted && donutData.length > 0 ? (<Box style={{ width: '100%', height: 'auto', minHeight: 200 }}>
|
||||
<PieChart
|
||||
width={800} height={300}
|
||||
data={donutData}
|
||||
>
|
||||
<Center>
|
||||
<Pagination
|
||||
value={page}
|
||||
onChange={(newPage) => {
|
||||
load(newPage, 10);
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
}}
|
||||
total={totalPages}
|
||||
mt="md"
|
||||
mb="md"
|
||||
color="blue"
|
||||
radius="md"
|
||||
/>
|
||||
</Center>
|
||||
|
||||
{/* Chart */}
|
||||
<Box mt="md">
|
||||
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
|
||||
<Stack>
|
||||
<Title order={3} pb={10}>Grafik Pengangguran Berdasarkan Pendidikan</Title>
|
||||
{mounted && donutData.length > 0 ? (
|
||||
<Box style={{ width: '100%', minHeight: 250 }}>
|
||||
<PieChart width={800} height={300} data={donutData}>
|
||||
<Pie
|
||||
dataKey="value"
|
||||
nameKey="name"
|
||||
data={donutData}
|
||||
cx={500}
|
||||
cx={400}
|
||||
cy={150}
|
||||
innerRadius={60}
|
||||
outerRadius={115}
|
||||
label={true}
|
||||
label
|
||||
>
|
||||
{donutData.map((entry, index) => (
|
||||
<Cell key={`cell-${index}`} fill={entry.color} />
|
||||
))}
|
||||
</Pie>
|
||||
</PieChart>
|
||||
<Flex gap={"md"} align={"center"}>
|
||||
<Box bg={colors['blue-button']} w={20} h={20} />
|
||||
<Text>SD : {donutData.find((entry) => entry.name === 'SD')?.value}</Text>
|
||||
</Flex>
|
||||
<Flex gap={"md"} align={"center"}>
|
||||
<Box bg={'#10A85AFF'} w={20} h={20} />
|
||||
<Text>SMP : {donutData.find((entry) => entry.name === 'SMP')?.value}</Text>
|
||||
</Flex>
|
||||
<Flex gap={"md"} align={"center"}>
|
||||
<Box bg={'#C07B13FF'} w={20} h={20} />
|
||||
<Text>SMA : {donutData.find((entry) => entry.name === 'SMA')?.value}</Text>
|
||||
</Flex>
|
||||
<Flex gap={"md"} align={"center"}>
|
||||
<Box bg={'#1094A8FF'} w={20} h={20} />
|
||||
<Text>D3 : {donutData.find((entry) => entry.name === 'D3')?.value}</Text>
|
||||
</Flex>
|
||||
<Flex gap={"md"} align={"center"}>
|
||||
<Box bg={'#A83610FF'} w={20} h={20} />
|
||||
<Text>S1 : {donutData.find((entry) => entry.name === 'S1')?.value}</Text>
|
||||
</Flex>
|
||||
<Stack gap="xs" mt="sm">
|
||||
{donutData.map((entry) => (
|
||||
<Flex key={entry.key} gap="sm" align="center">
|
||||
<Box w={20} h={20} bg={entry.color} />
|
||||
<Text>{entry.name} : {entry.value}</Text>
|
||||
</Flex>
|
||||
))}
|
||||
</Stack>
|
||||
</Box>
|
||||
) : (
|
||||
<Text c='dimmed'>Belum ada data untuk ditampilkan dalam grafik</Text>
|
||||
)}
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
</Stack>
|
||||
) : (
|
||||
<Text color="dimmed">Belum ada data untuk ditampilkan dalam grafik</Text>
|
||||
)}
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
|
||||
{/* Modal Konfirmasi Hapus */}
|
||||
<ModalKonfirmasiHapus
|
||||
|
||||
@@ -1,98 +1,130 @@
|
||||
'use client'
|
||||
import grafikNganggur from '@/app/admin/(dashboard)/_state/ekonomi/usia-kerja-nganggur';
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
|
||||
'use client';
|
||||
import grafikNganggur from '@/app/admin/(dashboard)/_state/ekonomi/usia-kerja-nganggur';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Paper, Stack, TextInput, Title } from '@mantine/core';
|
||||
import { Box, Button, Paper, Stack, TextInput, Title, Group, Tooltip } from '@mantine/core';
|
||||
import { IconArrowBack } from '@tabler/icons-react';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import { useEffect } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
function EditGrafikBerdasarkanUsiaKerjaYangMenganggur() {
|
||||
const router = useRouter()
|
||||
const params = useParams() as { id: string }
|
||||
const stategrafik = useProxy(grafikNganggur.grafikBerdasarkanUsiaKerjaNganggur)
|
||||
const id = params.id
|
||||
const router = useRouter();
|
||||
const params = useParams() as { id: string };
|
||||
const stategrafik = useProxy(grafikNganggur.grafikBerdasarkanUsiaKerjaNganggur);
|
||||
const id = params.id;
|
||||
|
||||
useEffect(() => {
|
||||
if(id){
|
||||
if (id) {
|
||||
stategrafik.findUnique.load(id).then(() => {
|
||||
const data = stategrafik.findUnique.data
|
||||
if(data){
|
||||
const data = stategrafik.findUnique.data;
|
||||
if (data) {
|
||||
stategrafik.update.form = {
|
||||
usia18_25: data.usia18_25 || '',
|
||||
usia26_35: data.usia26_35 || '',
|
||||
usia36_45: data.usia36_45 || '',
|
||||
usia46_keatas: data.usia46_keatas || '',
|
||||
}
|
||||
};
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
}, [id])
|
||||
}, [id]);
|
||||
|
||||
const handleSubmit = async () => {
|
||||
stategrafik.update.id = id;
|
||||
await stategrafik.update.submit();
|
||||
router.push('/admin/ekonomi/jumlah-penduduk-usia-kerja-yang-menganggur/pengangguran_berdasarkan_usia')
|
||||
}
|
||||
try {
|
||||
stategrafik.update.id = id;
|
||||
await stategrafik.update.submit();
|
||||
toast.success('Data grafik berhasil diperbarui!');
|
||||
router.push(
|
||||
'/admin/ekonomi/jumlah-penduduk-usia-kerja-yang-menganggur/pengangguran_berdasarkan_usia'
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
toast.error('Terjadi kesalahan saat memperbarui data grafik');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Box mb={10}>
|
||||
<Button variant="subtle" onClick={() => router.back()}>
|
||||
<IconArrowBack size={20} />
|
||||
</Button>
|
||||
<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 Grafik Pengangguran Berdasarkan Usia Kerja
|
||||
</Title>
|
||||
</Group>
|
||||
|
||||
<Paper
|
||||
w={{ base: '100%', md: '50%' }}
|
||||
bg={colors['white-1']}
|
||||
p="lg"
|
||||
radius="md"
|
||||
shadow="sm"
|
||||
style={{ border: '1px solid #e0e0e0' }}
|
||||
>
|
||||
<Stack gap="md">
|
||||
<TextInput
|
||||
label="Usia 18 - 25"
|
||||
type="number"
|
||||
placeholder="Masukkan jumlah"
|
||||
value={stategrafik.update.form.usia18_25}
|
||||
onChange={(val) => {
|
||||
stategrafik.update.form.usia18_25 = val.currentTarget.value;
|
||||
}}
|
||||
required
|
||||
/>
|
||||
<TextInput
|
||||
label="Usia 26 - 35"
|
||||
type="number"
|
||||
placeholder="Masukkan jumlah"
|
||||
value={stategrafik.update.form.usia26_35}
|
||||
onChange={(val) => {
|
||||
stategrafik.update.form.usia26_35 = val.currentTarget.value;
|
||||
}}
|
||||
required
|
||||
/>
|
||||
<TextInput
|
||||
label="Usia 36 - 45"
|
||||
type="number"
|
||||
placeholder="Masukkan jumlah"
|
||||
value={stategrafik.update.form.usia36_45}
|
||||
onChange={(val) => {
|
||||
stategrafik.update.form.usia36_45 = val.currentTarget.value;
|
||||
}}
|
||||
required
|
||||
/>
|
||||
<TextInput
|
||||
label="Usia 46 +"
|
||||
type="number"
|
||||
placeholder="Masukkan jumlah"
|
||||
value={stategrafik.update.form.usia46_keatas}
|
||||
onChange={(val) => {
|
||||
stategrafik.update.form.usia46_keatas = val.currentTarget.value;
|
||||
}}
|
||||
required
|
||||
/>
|
||||
|
||||
<Group justify="right">
|
||||
<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)',
|
||||
}}
|
||||
>
|
||||
Simpan
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
<Paper bg={colors['white-1']} w={{ base: '100%', md: '50%' }} p={'md'}>
|
||||
<Stack>
|
||||
<Title order={3}>Edit Grafik Pengangguran Berdasarkan Usia Kerja</Title>
|
||||
<TextInput
|
||||
label="Usia 18 - 25"
|
||||
type='number'
|
||||
placeholder="masukkan jumlah"
|
||||
value={stategrafik.update.form.usia18_25}
|
||||
onChange={(val) => {
|
||||
stategrafik.update.form.usia18_25 = val.currentTarget.value;
|
||||
}}
|
||||
/>
|
||||
<TextInput
|
||||
label="Usia 26 - 35"
|
||||
type="number"
|
||||
placeholder="masukkan jumlah"
|
||||
value={stategrafik.update.form.usia26_35}
|
||||
onChange={(val) => {
|
||||
stategrafik.update.form.usia26_35 = val.currentTarget.value;
|
||||
}}
|
||||
/>
|
||||
<TextInput
|
||||
label="Usia 36 - 45"
|
||||
type="number"
|
||||
placeholder="masukkan jumlah"
|
||||
value={stategrafik.update.form.usia36_45}
|
||||
onChange={(val) => {
|
||||
stategrafik.update.form.usia36_45 = val.currentTarget.value;
|
||||
}}
|
||||
/>
|
||||
<TextInput
|
||||
label="Usia 46 +"
|
||||
type="number"
|
||||
placeholder="masukkan jumlah"
|
||||
value={stategrafik.update.form.usia46_keatas}
|
||||
onChange={(val) => {
|
||||
stategrafik.update.form.usia46_keatas = val.currentTarget.value;
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
mt={10}
|
||||
bg={colors['blue-button']}
|
||||
onClick={handleSubmit}
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,31 +1,29 @@
|
||||
'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 } from '@mantine/core';
|
||||
import { IconArrowBack } from '@tabler/icons-react';
|
||||
import grafikNganggur from '@/app/admin/(dashboard)/_state/ekonomi/usia-kerja-nganggur';
|
||||
'use client';
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import grafikNganggur from '@/app/admin/(dashboard)/_state/ekonomi/usia-kerja-nganggur';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Paper, Stack, Title, TextInput, Group, Tooltip } from '@mantine/core';
|
||||
import { IconArrowBack } from '@tabler/icons-react';
|
||||
|
||||
function CreateGrafikBerdasarkanUsiaKerjaYangMenganggur() {
|
||||
const router = useRouter();
|
||||
const stategrafik = useProxy(grafikNganggur.grafikBerdasarkanUsiaKerjaNganggur)
|
||||
const stategrafik = useProxy(grafikNganggur.grafikBerdasarkanUsiaKerjaNganggur);
|
||||
const [donutData, setDonutData] = useState<any[]>([]);
|
||||
|
||||
const resetForm = () => {
|
||||
stategrafik.create.form = {
|
||||
...stategrafik.create.form,
|
||||
usia18_25: "",
|
||||
usia26_35: "",
|
||||
usia36_45: "",
|
||||
usia46_keatas: "",
|
||||
}
|
||||
}
|
||||
usia18_25: '',
|
||||
usia26_35: '',
|
||||
usia36_45: '',
|
||||
usia46_keatas: '',
|
||||
};
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
const id = await stategrafik.create.create();
|
||||
@@ -37,64 +35,84 @@ function CreateGrafikBerdasarkanUsiaKerjaYangMenganggur() {
|
||||
}
|
||||
}
|
||||
resetForm();
|
||||
router.push("/admin/ekonomi/jumlah-penduduk-usia-kerja-yang-menganggur/pengangguran_berdasarkan_usia")
|
||||
}
|
||||
router.push('/admin/ekonomi/jumlah-penduduk-usia-kerja-yang-menganggur/pengangguran_berdasarkan_usia');
|
||||
};
|
||||
|
||||
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}>Create Grafik Pengangguran Berdasarkan Usia Kerja</Title>
|
||||
<TextInput
|
||||
label="Usia 18 - 25"
|
||||
type='number'
|
||||
placeholder="masukkan jumlah"
|
||||
value={stategrafik.create.form.usia18_25}
|
||||
onChange={(val) => {
|
||||
stategrafik.create.form.usia18_25 = val.currentTarget.value;
|
||||
}}
|
||||
/>
|
||||
<TextInput
|
||||
label="Usia 26 - 35"
|
||||
type="number"
|
||||
placeholder="masukkan jumlah"
|
||||
value={stategrafik.create.form.usia26_35}
|
||||
onChange={(val) => {
|
||||
stategrafik.create.form.usia26_35 = val.currentTarget.value;
|
||||
}}
|
||||
/>
|
||||
<TextInput
|
||||
label="Usia 36 - 45"
|
||||
type="number"
|
||||
placeholder="masukkan jumlah"
|
||||
value={stategrafik.create.form.usia36_45}
|
||||
onChange={(val) => {
|
||||
stategrafik.create.form.usia36_45 = val.currentTarget.value;
|
||||
}}
|
||||
/>
|
||||
<TextInput
|
||||
label="Usia 46 +"
|
||||
type="number"
|
||||
placeholder="masukkan jumlah"
|
||||
value={stategrafik.create.form.usia46_keatas}
|
||||
onChange={(val) => {
|
||||
stategrafik.create.form.usia46_keatas = val.currentTarget.value;
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
mt={10}
|
||||
bg={colors['blue-button']}
|
||||
onClick={handleSubmit}
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||
{/* Header */}
|
||||
<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">
|
||||
Tambah Data Pengangguran Berdasarkan Usia
|
||||
</Title>
|
||||
</Group>
|
||||
|
||||
{/* Form Paper */}
|
||||
<Paper
|
||||
w={{ base: '100%', md: '50%' }}
|
||||
bg={colors['white-1']}
|
||||
p="lg"
|
||||
radius="md"
|
||||
shadow="sm"
|
||||
style={{ border: '1px solid #e0e0e0' }}
|
||||
>
|
||||
<Stack gap="md">
|
||||
<TextInput
|
||||
label="Usia 18 - 25"
|
||||
type="number"
|
||||
placeholder="Masukkan jumlah"
|
||||
value={stategrafik.create.form.usia18_25}
|
||||
onChange={(val) => (stategrafik.create.form.usia18_25 = val.currentTarget.value)}
|
||||
required
|
||||
/>
|
||||
<TextInput
|
||||
label="Usia 26 - 35"
|
||||
type="number"
|
||||
placeholder="Masukkan jumlah"
|
||||
value={stategrafik.create.form.usia26_35}
|
||||
onChange={(val) => (stategrafik.create.form.usia26_35 = val.currentTarget.value)}
|
||||
required
|
||||
/>
|
||||
<TextInput
|
||||
label="Usia 36 - 45"
|
||||
type="number"
|
||||
placeholder="Masukkan jumlah"
|
||||
value={stategrafik.create.form.usia36_45}
|
||||
onChange={(val) => (stategrafik.create.form.usia36_45 = val.currentTarget.value)}
|
||||
required
|
||||
/>
|
||||
<TextInput
|
||||
label="Usia 46 +"
|
||||
type="number"
|
||||
placeholder="Masukkan jumlah"
|
||||
value={stategrafik.create.form.usia46_keatas}
|
||||
onChange={(val) => (stategrafik.create.form.usia46_keatas = val.currentTarget.value)}
|
||||
required
|
||||
/>
|
||||
|
||||
{/* Submit Button */}
|
||||
<Group justify="right">
|
||||
<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)',
|
||||
}}
|
||||
>
|
||||
Simpan
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,60 +1,62 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Flex, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core';
|
||||
import { Box, Button, Center, Flex, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title, Tooltip } from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { IconEdit, IconSearch, IconTrash } from '@tabler/icons-react';
|
||||
import { IconEdit, IconPlus, IconSearch, IconTrash } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Cell, Pie, PieChart } from 'recharts';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import HeaderSearch from '../../../_com/header';
|
||||
import JudulList from '../../../_com/judulList';
|
||||
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
||||
import grafikNganggur from '../../../_state/ekonomi/usia-kerja-nganggur';
|
||||
|
||||
|
||||
function GrafikBerdasarkanUsiaKerjaYangMenganggur() {
|
||||
const [search, setSearch] = useState("")
|
||||
const [search, setSearch] = useState('');
|
||||
return (
|
||||
<Box>
|
||||
<Stack gap={"xs"}>
|
||||
<HeaderSearch
|
||||
title='Detail Data Pengangguran'
|
||||
placeholder='pencarian'
|
||||
placeholder='Cari usia...'
|
||||
searchIcon={<IconSearch size={20} />}
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||
/>
|
||||
<ListGrafikBerdasarkanUsiaKerjaYangMenganggur search={search} />
|
||||
</Stack>
|
||||
<ListGrafikBerdasarkanUsiaKerjaYangMenganggur search={search} />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
function ListGrafikBerdasarkanUsiaKerjaYangMenganggur({search}: {search: string}) {
|
||||
const stategrafik = useProxy(grafikNganggur.grafikBerdasarkanUsiaKerjaNganggur)
|
||||
function ListGrafikBerdasarkanUsiaKerjaYangMenganggur({ search }: { search: string }) {
|
||||
const stategrafik = useProxy(grafikNganggur.grafikBerdasarkanUsiaKerjaNganggur);
|
||||
const [donutData, setDonutData] = useState<any[]>([]);
|
||||
const [mounted, setMounted] = useState(false);
|
||||
const [modalHapus, setModalHapus] = useState(false)
|
||||
const [selectedId, setSelectedId] = useState<string | null>(null)
|
||||
const [modalHapus, setModalHapus] = useState(false);
|
||||
const [selectedId, setSelectedId] = useState<string | null>(null);
|
||||
const router = useRouter();
|
||||
|
||||
|
||||
const handleDelete = async () => {
|
||||
if (selectedId) {
|
||||
await stategrafik.delete.byId(selectedId)
|
||||
setModalHapus(false)
|
||||
setSelectedId(null)
|
||||
|
||||
stategrafik.findMany.load()
|
||||
await stategrafik.delete.byId(selectedId);
|
||||
setModalHapus(false);
|
||||
setSelectedId(null);
|
||||
stategrafik.findMany.load();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const {
|
||||
data,
|
||||
page,
|
||||
totalPages,
|
||||
loading,
|
||||
load,
|
||||
} = stategrafik.findMany;
|
||||
|
||||
useShallowEffect(() => {
|
||||
setMounted(true);
|
||||
stategrafik.findMany.load()
|
||||
}, []);
|
||||
load(page, 10, search)
|
||||
}, [page, search]);
|
||||
|
||||
useEffect(() => {
|
||||
if (stategrafik.findMany.data) {
|
||||
@@ -69,139 +71,149 @@ function ListGrafikBerdasarkanUsiaKerjaYangMenganggur({search}: {search: string}
|
||||
{ name: 'usia46_keatas', value: totalUsia46_keatas, color: '#1094A8FF', key: 'usia46_keatas' },
|
||||
]);
|
||||
}
|
||||
}, [stategrafik.findMany.data])
|
||||
}, [stategrafik.findMany.data]);
|
||||
|
||||
const filteredData = (stategrafik.findMany.data || []).filter(item => {
|
||||
const keyword = search.toLowerCase();
|
||||
const filteredData = data || []
|
||||
|
||||
if (loading || !data) {
|
||||
return (
|
||||
item.usia18_25.toString().toLowerCase().includes(keyword) ||
|
||||
item.usia26_35.toString().toLowerCase().includes(keyword) ||
|
||||
item.usia36_45.toString().toLowerCase().includes(keyword) ||
|
||||
item.usia46_keatas.toString().toLowerCase().includes(keyword)
|
||||
<Stack py={10}>
|
||||
<Skeleton height={500} radius="md" />
|
||||
</Stack>
|
||||
);
|
||||
});
|
||||
|
||||
if (!stategrafik.findMany.data) {
|
||||
return (
|
||||
<Box>
|
||||
<Skeleton h={500} />
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Stack gap={"xs"}>
|
||||
<Paper bg={colors['white-1']} p={"md"}>
|
||||
<JudulList
|
||||
title='List Pengangguran Berdasarkan Usia Kerja'
|
||||
href='/admin/ekonomi/jumlah-penduduk-usia-kerja-yang-menganggur/pengangguran_berdasarkan_usia/create'
|
||||
/>
|
||||
<Table striped withTableBorder withRowBorders>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh>Usia 18-25</TableTh>
|
||||
<TableTh>Usia 26-35</TableTh>
|
||||
<TableTh>Usia 36-45</TableTh>
|
||||
<TableTh>Usia 46 +</TableTh>
|
||||
<TableTh>Edit</TableTh>
|
||||
<TableTh>Delete</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{filteredData.length === 0 ? (
|
||||
<Box py={10}>
|
||||
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
|
||||
<Stack>
|
||||
{/* Header */}
|
||||
<Flex justify="space-between" align="center" mb="md">
|
||||
<Title order={4}>List Pengangguran Berdasarkan Usia Kerja</Title>
|
||||
<Tooltip label="Tambah Data" withArrow>
|
||||
<Button
|
||||
leftSection={<IconPlus size={18} />}
|
||||
color="blue"
|
||||
variant="light"
|
||||
onClick={() => router.push('/admin/ekonomi/jumlah-penduduk-usia-kerja-yang-menganggur/pengangguran_berdasarkan_usia/create')}
|
||||
>
|
||||
Tambah Baru
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</Flex>
|
||||
|
||||
{/* Table */}
|
||||
<Box style={{ overflowX: 'auto' }}>
|
||||
<Table highlightOnHover>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTd colSpan={6}>
|
||||
<Text ta='center' c='dimmed'>Belum ada data grafik responden</Text>
|
||||
</TableTd>
|
||||
<TableTh>Usia 18-25</TableTh>
|
||||
<TableTh>Usia 26-35</TableTh>
|
||||
<TableTh>Usia 36-45</TableTh>
|
||||
<TableTh>Usia 46 +</TableTh>
|
||||
<TableTh>Edit</TableTh>
|
||||
<TableTh>Delete</TableTh>
|
||||
</TableTr>
|
||||
) : (
|
||||
filteredData.map((item) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd>{item.usia18_25}</TableTd>
|
||||
<TableTd>{item.usia26_35}</TableTd>
|
||||
<TableTd>{item.usia36_45}</TableTd>
|
||||
<TableTd>{item.usia46_keatas}</TableTd>
|
||||
<TableTd>
|
||||
<Button color='green' onClick={() => router.push(`/admin/ekonomi/jumlah-penduduk-usia-kerja-yang-menganggur/pengangguran_berdasarkan_usia/${item.id}`)}>
|
||||
<IconEdit size={20} />
|
||||
</Button>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Button
|
||||
color='red'
|
||||
disabled={stategrafik.delete.loading}
|
||||
onClick={() => {
|
||||
setSelectedId(item.id)
|
||||
setModalHapus(true)
|
||||
}}>
|
||||
<IconTrash size={20} />
|
||||
</Button>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{filteredData.length > 0 ? (
|
||||
filteredData.map(item => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd>{item.usia18_25}</TableTd>
|
||||
<TableTd>{item.usia26_35}</TableTd>
|
||||
<TableTd>{item.usia36_45}</TableTd>
|
||||
<TableTd>{item.usia46_keatas}</TableTd>
|
||||
<TableTd>
|
||||
<Button color="green" onClick={() => router.push(`/admin/ekonomi/jumlah-penduduk-usia-kerja-yang-menganggur/pengangguran_berdasarkan_usia/${item.id}`)}>
|
||||
<IconEdit size={20} />
|
||||
</Button>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Button color="red" disabled={stategrafik.delete.loading} onClick={() => { setSelectedId(item.id); setModalHapus(true); }}>
|
||||
<IconTrash size={20} />
|
||||
</Button>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
))
|
||||
) : (
|
||||
<TableTr>
|
||||
<TableTd colSpan={6}>
|
||||
<Center py={20}>
|
||||
<Text color="dimmed">Belum ada data grafik responden</Text>
|
||||
</Center>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
))
|
||||
)}
|
||||
</TableTbody>
|
||||
)}
|
||||
</TableTbody>
|
||||
</Table>
|
||||
</Box>
|
||||
|
||||
</Table>
|
||||
</Paper>
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
{/* Chart */}
|
||||
<Box>
|
||||
<Paper bg={colors['white-1']} p={'md'}>
|
||||
<Stack>
|
||||
<Title pb={10} order={3}>Grafik Pengangguran Berdasarkan Usia Kerja</Title>
|
||||
{mounted && donutData.length > 0 ? (<Box style={{ width: '100%', height: 'auto', minHeight: 200 }}>
|
||||
<PieChart
|
||||
width={800} height={300}
|
||||
data={donutData}
|
||||
>
|
||||
<Pie
|
||||
dataKey="value"
|
||||
nameKey="name"
|
||||
data={donutData}
|
||||
cx={500}
|
||||
cy={150}
|
||||
innerRadius={60}
|
||||
outerRadius={115}
|
||||
label={true}
|
||||
>
|
||||
{donutData.map((entry, index) => (
|
||||
<Cell key={`cell-${index}`} fill={entry.color} />
|
||||
))}
|
||||
</Pie>
|
||||
</PieChart>
|
||||
<Center>
|
||||
<Pagination
|
||||
value={page}
|
||||
onChange={(newPage) => {
|
||||
load(newPage, 10);
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
}}
|
||||
total={totalPages}
|
||||
mt="md"
|
||||
mb="md"
|
||||
color="blue"
|
||||
radius="md"
|
||||
/>
|
||||
</Center>
|
||||
|
||||
{/* Chart */}
|
||||
<Paper bg={colors['white-1']} p="md" mt="lg" withBorder radius="md">
|
||||
<Stack>
|
||||
<Title order={3} pb={10}>Grafik Pengangguran Berdasarkan Usia Kerja</Title>
|
||||
{mounted && donutData.length > 0 ? (
|
||||
<Box style={{ width: '100%', height: 'auto', minHeight: 200 }}>
|
||||
<PieChart width={800} height={300} data={donutData}>
|
||||
<Pie dataKey="value" nameKey="name" data={donutData} cx={400} cy={150} innerRadius={60} outerRadius={115} label>
|
||||
{donutData.map((entry, index) => (
|
||||
<Cell key={`cell-${index}`} fill={entry.color} />
|
||||
))}
|
||||
</Pie>
|
||||
</PieChart>
|
||||
<Stack mt="sm" gap="xs">
|
||||
<Flex gap={"md"} align={"center"}>
|
||||
<Box bg={colors['blue-button']} w={20} h={20} />
|
||||
<Text>Usia 18-25 : {donutData.find((entry) => entry.name === 'usia18_25')?.value}</Text>
|
||||
</Flex>
|
||||
<Flex gap={"md"} align={"center"}>
|
||||
<Box bg={'#10A85AFF'} w={20} h={20} />
|
||||
<Text>Usia 26-35 : {donutData.find((entry) => entry.name === 'usia26_35')?.value}</Text>
|
||||
<Text>Usia 26-35 : {donutData.find((entry) => entry.name === 'usia26_35')?.value}
|
||||
</Text>
|
||||
</Flex>
|
||||
<Flex gap={"md"} align={"center"}>
|
||||
<Box bg={'#C07B13FF'} w={20} h={20} />
|
||||
<Text>Usia 36-45 : {donutData.find((entry) => entry.name === 'usia36_45')?.value}</Text>
|
||||
<Text>Usia 36-45 : {donutData.find((entry) => entry.name === 'usia36_45')?.value}
|
||||
</Text>
|
||||
</Flex>
|
||||
<Flex gap={"md"} align={"center"}>
|
||||
<Box bg={'#1094A8FF'} w={20} h={20} />
|
||||
<Text>Usia 46 + : {donutData.find((entry) => entry.name === 'usia46_keatas')?.value}</Text>
|
||||
<Text>Usia 46 + : {donutData.find((entry) => entry.name === 'usia46_keatas')?.value}
|
||||
</Text>
|
||||
</Flex>
|
||||
</Box>
|
||||
) : (
|
||||
<Text c='dimmed'>Belum ada data untuk ditampilkan dalam grafik</Text>
|
||||
)}
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Box>
|
||||
) : (
|
||||
<Text c="dimmed">Belum ada data untuk ditampilkan dalam grafik</Text>
|
||||
)}
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
|
||||
{/* Modal Konfirmasi Hapus */}
|
||||
<ModalKonfirmasiHapus
|
||||
opened={modalHapus}
|
||||
onClose={() => setModalHapus(false)}
|
||||
onConfirm={handleDelete}
|
||||
text='Apakah anda yakin ingin menghapus grafik pengangguran berdasarkan usia kerja ini?'
|
||||
text="Apakah anda yakin ingin menghapus grafik pengangguran berdasarkan usia kerja ini?"
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client'
|
||||
'use client';
|
||||
import jumlahPengangguranState from '@/app/admin/(dashboard)/_state/ekonomi/jumlah-pengangguran';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title, Select, NumberInput } from '@mantine/core';
|
||||
@@ -10,19 +10,12 @@ import { toast } from 'react-toastify';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
function EditDetailDataPengangguran() {
|
||||
const stateDetail = useProxy(jumlahPengangguranState.jumlahPengangguran)
|
||||
const stateDetail = useProxy(jumlahPengangguranState.jumlahPengangguran);
|
||||
const router = useRouter();
|
||||
const params = useParams()
|
||||
const params = useParams();
|
||||
|
||||
const [formData, setFormData] = useState<{
|
||||
month: string;
|
||||
year: number;
|
||||
educatedUnemployment: number;
|
||||
uneducatedUnemployment: number;
|
||||
totalUnemployment: number;
|
||||
percentageChange: number | null;
|
||||
}>({
|
||||
month: "",
|
||||
const [formData, setFormData] = useState({
|
||||
month: '',
|
||||
year: new Date().getFullYear(),
|
||||
educatedUnemployment: 0,
|
||||
uneducatedUnemployment: 0,
|
||||
@@ -30,52 +23,41 @@ function EditDetailDataPengangguran() {
|
||||
percentageChange: 0,
|
||||
});
|
||||
|
||||
// Update form data and recalculate totals
|
||||
const updateFormData = async (updates: Partial<typeof formData>) => {
|
||||
const newData = { ...formData, ...updates };
|
||||
const { total, percentageChange } = await calculateTotalAndChange();
|
||||
|
||||
setFormData({
|
||||
...newData,
|
||||
totalUnemployment: total,
|
||||
percentageChange,
|
||||
});
|
||||
};
|
||||
|
||||
// Hitung total & perubahan otomatis
|
||||
const calculateTotalAndChange = async () => {
|
||||
const total = formData.educatedUnemployment + formData.uneducatedUnemployment;
|
||||
|
||||
// Calculate percentage change based on previous month's data
|
||||
|
||||
let percentageChange = 0;
|
||||
const monthOrder = ["Jan", "Feb", "Mar", "Apr", "Mei", "Jun", "Jul", "Agu", "Sep", "Okt", "Nov", "Des"];
|
||||
const monthOrder = ['Jan', 'Feb', 'Mar', 'Apr', 'Mei', 'Jun', 'Jul', 'Agu', 'Sep', 'Okt', 'Nov', 'Des'];
|
||||
const currentMonthIndex = monthOrder.indexOf(formData.month);
|
||||
|
||||
|
||||
if (currentMonthIndex !== -1) {
|
||||
let prevMonthIndex = currentMonthIndex - 1;
|
||||
let prevYear = formData.year;
|
||||
|
||||
|
||||
if (prevMonthIndex < 0) {
|
||||
prevMonthIndex = 11;
|
||||
prevYear--;
|
||||
}
|
||||
|
||||
|
||||
const prevMonth = monthOrder[prevMonthIndex];
|
||||
|
||||
// Get previous month's data
|
||||
const prevData = await stateDetail.findByMonthYear.load({
|
||||
month: prevMonth,
|
||||
year: prevYear,
|
||||
});
|
||||
const prevData = await stateDetail.findByMonthYear.load({ month: prevMonth, year: prevYear });
|
||||
|
||||
if (prevData && prevData.totalUnemployment > 0) {
|
||||
const change = ((total - prevData.totalUnemployment) / prevData.totalUnemployment) * 100;
|
||||
percentageChange = parseFloat(change.toFixed(1));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return { total, percentageChange };
|
||||
};
|
||||
|
||||
const updateFormData = async (updates: Partial<typeof formData>) => {
|
||||
const newData = { ...formData, ...updates };
|
||||
const { total, percentageChange } = await calculateTotalAndChange();
|
||||
setFormData({ ...newData, totalUnemployment: total, percentageChange });
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const loadDetail = async () => {
|
||||
const id = params?.id as string;
|
||||
@@ -124,79 +106,69 @@ function EditDetailDataPengangguran() {
|
||||
const handleSubmit = async () => {
|
||||
const { total, percentageChange } = await calculateTotalAndChange();
|
||||
try {
|
||||
stateDetail.update.form = {
|
||||
...formData,
|
||||
totalUnemployment: total,
|
||||
percentageChange,
|
||||
};
|
||||
|
||||
stateDetail.update.form = { ...formData, totalUnemployment: total, percentageChange };
|
||||
const success = await stateDetail.update.submit();
|
||||
if (success) {
|
||||
toast.success("Detail data pengangguran berhasil diperbarui!");
|
||||
router.push("/admin/ekonomi/jumlah-pengangguran");
|
||||
toast.success('Detail data pengangguran berhasil diperbarui!');
|
||||
router.push('/admin/ekonomi/jumlah-pengangguran');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error updating:", error);
|
||||
toast.error("Terjadi kesalahan saat memperbarui data");
|
||||
console.error('Error updating:', error);
|
||||
toast.error('Terjadi kesalahan saat memperbarui data');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Box mb={10}>
|
||||
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
|
||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||
<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>
|
||||
</Box>
|
||||
<Title order={4} ml="sm">
|
||||
Edit Detail Data Pengangguran
|
||||
</Title>
|
||||
</Group>
|
||||
|
||||
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
||||
<Title order={4}>Edit Detail Data Pengangguran</Title>
|
||||
<Stack gap="xs">
|
||||
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p="lg" radius="md" shadow="sm" style={{ border: '1px solid #e0e0e0' }}>
|
||||
<Stack gap="md">
|
||||
<Select
|
||||
label="Bulan"
|
||||
data={["Jan", "Feb", "Mar", "Apr", "Mei", "Jun", "Jul", "Agu", "Sep", "Okt", "Nov", "Des"]}
|
||||
data={['Jan', 'Feb', 'Mar', 'Apr', 'Mei', 'Jun', 'Jul', 'Agu', 'Sep', 'Okt', 'Nov', 'Des']}
|
||||
value={formData.month}
|
||||
onChange={async (val) => {
|
||||
await updateFormData({ month: val || "" });
|
||||
}}
|
||||
onChange={(val) => updateFormData({ month: val || '' })}
|
||||
/>
|
||||
<NumberInput
|
||||
label="Tahun"
|
||||
value={formData.year}
|
||||
onChange={async (val) => {
|
||||
await updateFormData({ year: Number(val) });
|
||||
}}
|
||||
onChange={(val) => updateFormData({ year: Number(val) })}
|
||||
/>
|
||||
<TextInput
|
||||
label="Pengangguran Terdidik"
|
||||
type="number"
|
||||
value={formData.educatedUnemployment}
|
||||
onChange={async (val) => {
|
||||
const value = Number(val.currentTarget.value) || 0;
|
||||
await updateFormData({ educatedUnemployment: value });
|
||||
}}
|
||||
onChange={(val) => updateFormData({ educatedUnemployment: Number(val.currentTarget.value) || 0 })}
|
||||
/>
|
||||
<TextInput
|
||||
label="Pengangguran Tidak Terdidik"
|
||||
type="number"
|
||||
value={formData.uneducatedUnemployment}
|
||||
onChange={async (val) => {
|
||||
const value = Number(val.currentTarget.value) || 0;
|
||||
await updateFormData({ uneducatedUnemployment: value });
|
||||
}}
|
||||
onChange={(val) => updateFormData({ uneducatedUnemployment: Number(val.currentTarget.value) || 0 })}
|
||||
/>
|
||||
<Text fz="sm" fw={500}>
|
||||
Total Otomatis: {formData.totalUnemployment}
|
||||
</Text>
|
||||
<Text fz="sm" fw={500}>
|
||||
Perubahan Otomatis:{" "}
|
||||
{formData.percentageChange !== null
|
||||
? `${formData.percentageChange}%`
|
||||
: '-'}
|
||||
</Text>
|
||||
<Group>
|
||||
<Button bg={colors['blue-button']} mt={10} onClick={handleSubmit}>
|
||||
Submit
|
||||
<Text fz="sm" fw={500}>Total Otomatis: {formData.totalUnemployment}</Text>
|
||||
<Text fz="sm" fw={500}>Perubahan Otomatis: {formData.percentageChange !== null ? `${formData.percentageChange}%` : '-'}</Text>
|
||||
|
||||
<Group justify="right">
|
||||
<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)',
|
||||
}}
|
||||
>
|
||||
Simpan
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
@@ -206,3 +178,4 @@ function EditDetailDataPengangguran() {
|
||||
}
|
||||
|
||||
export default EditDetailDataPengangguran;
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus';
|
||||
import jumlahPengangguranState from '@/app/admin/(dashboard)/_state/ekonomi/jumlah-pengangguran';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Flex, Paper, Skeleton, Stack, Text } from '@mantine/core';
|
||||
import { Box, Button, Flex, Paper, Skeleton, Stack, Text, Tooltip } from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { IconArrowBack, IconEdit, IconX } from '@tabler/icons-react';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
@@ -11,91 +11,125 @@ import { useProxy } from 'valtio/utils';
|
||||
|
||||
function DetailJumlahPengangguran() {
|
||||
const router = useRouter();
|
||||
const params = useParams()
|
||||
const [modalHapus, setModalHapus] = useState(false)
|
||||
const [selectedId, setSelectedId] = useState<string | null>(null)
|
||||
const stateDetail = useProxy(jumlahPengangguranState.jumlahPengangguran)
|
||||
const params = useParams();
|
||||
const [modalHapus, setModalHapus] = useState(false);
|
||||
const [selectedId, setSelectedId] = useState<string | null>(null);
|
||||
const stateDetail = useProxy(jumlahPengangguranState.jumlahPengangguran);
|
||||
|
||||
useShallowEffect(() => {
|
||||
stateDetail.findUnique.load(params?.id as string)
|
||||
}, [params?.id])
|
||||
stateDetail.findUnique.load(params?.id as string);
|
||||
}, [params?.id]);
|
||||
|
||||
const handleHapus = () => {
|
||||
if (selectedId) {
|
||||
stateDetail.delete.byId(selectedId)
|
||||
setModalHapus(false)
|
||||
setSelectedId(null)
|
||||
router.push("/admin/ekonomi/jumlah-pengangguran")
|
||||
stateDetail.delete.byId(selectedId);
|
||||
setModalHapus(false);
|
||||
setSelectedId(null);
|
||||
router.push("/admin/ekonomi/jumlah-pengangguran");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (!stateDetail.findUnique.data) {
|
||||
return (
|
||||
<Box>
|
||||
<Skeleton h={500} />
|
||||
</Box>
|
||||
)
|
||||
<Stack py={10}>
|
||||
<Skeleton height={500} radius="md" />
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
const data = stateDetail.findUnique.data;
|
||||
|
||||
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 Data Pengangguran</Text>
|
||||
<Paper bg={colors['BG-trans']} p={'md'}>
|
||||
<Stack gap={"xs"}>
|
||||
<Box py={10}>
|
||||
{/* Tombol Kembali */}
|
||||
<Button
|
||||
variant="subtle"
|
||||
onClick={() => router.back()}
|
||||
leftSection={<IconArrowBack size={24} color={colors['blue-button']} />}
|
||||
mb={15}
|
||||
>
|
||||
Kembali
|
||||
</Button>
|
||||
|
||||
{/* Paper Detail */}
|
||||
<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 Data Pengangguran
|
||||
</Text>
|
||||
|
||||
<Paper bg="#ECEEF8" p="md" radius="md" shadow="xs">
|
||||
<Stack gap="sm">
|
||||
<Box>
|
||||
<Text fw={"bold"}>Pengangguran Terdidik</Text>
|
||||
<Text>{stateDetail.findUnique.data?.educatedUnemployment}</Text>
|
||||
<Text fw="bold">Pengangguran Terdidik</Text>
|
||||
<Text c="dimmed">{data.educatedUnemployment || '-'}</Text>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Text fw={"bold"}>Pengangguran Tidak Terdidik</Text>
|
||||
<Text>{stateDetail.findUnique.data?.uneducatedUnemployment}</Text>
|
||||
<Text fw="bold">Pengangguran Tidak Terdidik</Text>
|
||||
<Text c="dimmed">{data.uneducatedUnemployment || '-'}</Text>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Text fw={"bold"}>Perubahan</Text>
|
||||
<Text>
|
||||
{stateDetail.findUnique.data?.percentageChange !== null &&
|
||||
stateDetail.findUnique.data?.percentageChange !== undefined
|
||||
? `${stateDetail.findUnique.data.percentageChange}%`
|
||||
<Text fw="bold">Perubahan</Text>
|
||||
<Text c="dimmed">
|
||||
{data.percentageChange !== null && data.percentageChange !== undefined
|
||||
? `${data.percentageChange}%`
|
||||
: 'Tidak ada data perubahan'}
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Text fw={"bold"}>Tahun</Text>
|
||||
<Text>{stateDetail.findUnique.data?.year || ''}</Text>
|
||||
<Text fw="bold">Tahun</Text>
|
||||
<Text c="dimmed">{data.year || '-'}</Text>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Text fw={"bold"}>Bulan</Text>
|
||||
<Text>{stateDetail.findUnique.data?.month}</Text>
|
||||
<Text fw="bold">Bulan</Text>
|
||||
<Text c="dimmed">{data.month || '-'}</Text>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Text fw={"bold"}>Total Pengangguran</Text>
|
||||
<Text>{stateDetail.findUnique.data?.totalUnemployment}</Text>
|
||||
<Text fw="bold">Total Pengangguran</Text>
|
||||
<Text c="dimmed">{data.totalUnemployment || '-'}</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Flex gap={"xs"}>
|
||||
|
||||
{/* Tombol Edit & Hapus */}
|
||||
<Flex gap="sm">
|
||||
<Tooltip label="Hapus Data Pengangguran" withArrow position="top">
|
||||
<Button
|
||||
onClick={() => {
|
||||
if (stateDetail.findUnique.data) {
|
||||
setSelectedId(stateDetail.findUnique.data.id);
|
||||
setModalHapus(true);
|
||||
}
|
||||
setSelectedId(data.id);
|
||||
setModalHapus(true);
|
||||
}}
|
||||
disabled={stateDetail.delete.loading || !stateDetail.findUnique.data}
|
||||
color={"red"}>
|
||||
color="red"
|
||||
variant="light"
|
||||
radius="md"
|
||||
size="md"
|
||||
>
|
||||
<IconX size={20} />
|
||||
</Button>
|
||||
<Button onClick={() => router.push(`/admin/ekonomi/jumlah-pengangguran/${stateDetail.findUnique.data?.id}/edit`)} color="green">
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip label="Edit Data Pengangguran" withArrow position="top">
|
||||
<Button
|
||||
onClick={() => router.push(`/admin/ekonomi/jumlah-pengangguran/${data.id}/edit`)}
|
||||
color="green"
|
||||
variant="light"
|
||||
radius="md"
|
||||
size="md"
|
||||
>
|
||||
<IconEdit size={20} />
|
||||
</Button>
|
||||
</Flex>
|
||||
</Box>
|
||||
</Tooltip>
|
||||
</Flex>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Stack>
|
||||
@@ -106,7 +140,7 @@ function DetailJumlahPengangguran() {
|
||||
opened={modalHapus}
|
||||
onClose={() => setModalHapus(false)}
|
||||
onConfirm={handleHapus}
|
||||
text="Apakah anda yakin ingin menghapus data ini?"
|
||||
text="Apakah Anda yakin ingin menghapus data ini?"
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
|
||||
@@ -3,14 +3,25 @@
|
||||
'use client'
|
||||
import jumlahPengangguranState from '@/app/admin/(dashboard)/_state/ekonomi/jumlah-pengangguran';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title, Select, NumberInput } from '@mantine/core';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Group,
|
||||
Paper,
|
||||
Stack,
|
||||
Text,
|
||||
NumberInput,
|
||||
Title,
|
||||
Select,
|
||||
Tooltip,
|
||||
} from '@mantine/core';
|
||||
import { IconArrowBack } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
function CreateJumlahPengangguran() {
|
||||
const stateDetail = useProxy(jumlahPengangguranState.jumlahPengangguran)
|
||||
const stateDetail = useProxy(jumlahPengangguranState.jumlahPengangguran);
|
||||
const [chartData, setChartData] = useState<any[]>([]);
|
||||
const router = useRouter();
|
||||
|
||||
@@ -21,14 +32,14 @@ function CreateJumlahPengangguran() {
|
||||
|
||||
const resetForm = () => {
|
||||
stateDetail.create.form = {
|
||||
month: monthOptions[new Date().getMonth()], // Default to current month
|
||||
year: new Date().getFullYear(), // Default to current year
|
||||
month: monthOptions[new Date().getMonth()], // default bulan sekarang
|
||||
year: new Date().getFullYear(), // default tahun sekarang
|
||||
totalUnemployment: 0,
|
||||
educatedUnemployment: 0,
|
||||
uneducatedUnemployment: 0,
|
||||
percentageChange: 0,
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const calculateTotalAndChange = async () => {
|
||||
const total =
|
||||
@@ -37,11 +48,8 @@ function CreateJumlahPengangguran() {
|
||||
|
||||
stateDetail.create.form.totalUnemployment = total;
|
||||
|
||||
// Ambil data bulan sebelumnya
|
||||
const monthOrder = [
|
||||
'Jan', 'Feb', 'Mar', 'Apr', 'Mei', 'Jun',
|
||||
'Jul', 'Agu', 'Sep', 'Okt', 'Nov', 'Des'
|
||||
];
|
||||
// hitung perubahan dibanding bulan sebelumnya
|
||||
const monthOrder = monthOptions;
|
||||
const currentIndex = monthOrder.findIndex(
|
||||
(m) => m.toLowerCase() === stateDetail.create.form.month.toLowerCase()
|
||||
);
|
||||
@@ -78,15 +86,34 @@ function CreateJumlahPengangguran() {
|
||||
};
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Box mb={10}>
|
||||
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
|
||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||
</Button>
|
||||
</Box>
|
||||
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
||||
<Title order={4}>Tambah Data Pengangguran</Title>
|
||||
<Stack gap="xs" mt="md">
|
||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||
{/* Header */}
|
||||
<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">
|
||||
Tambah Data Pengangguran
|
||||
</Title>
|
||||
</Group>
|
||||
|
||||
{/* Form Card */}
|
||||
<Paper
|
||||
w={{ base: '100%', md: '50%' }}
|
||||
bg={colors['white-1']}
|
||||
p="lg"
|
||||
radius="md"
|
||||
shadow="sm"
|
||||
style={{ border: '1px solid #e0e0e0' }}
|
||||
>
|
||||
<Stack gap="md">
|
||||
<Select
|
||||
label="Bulan"
|
||||
placeholder="Pilih bulan"
|
||||
@@ -98,6 +125,7 @@ function CreateJumlahPengangguran() {
|
||||
}}
|
||||
required
|
||||
/>
|
||||
|
||||
<NumberInput
|
||||
label="Tahun"
|
||||
value={stateDetail.create.form.year}
|
||||
@@ -109,6 +137,7 @@ function CreateJumlahPengangguran() {
|
||||
max={2100}
|
||||
required
|
||||
/>
|
||||
|
||||
<NumberInput
|
||||
label="Pengangguran Terdidik"
|
||||
value={stateDetail.create.form.educatedUnemployment}
|
||||
@@ -119,6 +148,7 @@ function CreateJumlahPengangguran() {
|
||||
min={0}
|
||||
required
|
||||
/>
|
||||
|
||||
<NumberInput
|
||||
label="Pengangguran Tidak Terdidik"
|
||||
value={stateDetail.create.form.uneducatedUnemployment}
|
||||
@@ -129,15 +159,36 @@ function CreateJumlahPengangguran() {
|
||||
min={0}
|
||||
required
|
||||
/>
|
||||
<Text fz="sm" fw={500}>
|
||||
Total Otomatis: {stateDetail.create.form.totalUnemployment.toLocaleString()}
|
||||
</Text>
|
||||
<Text fz="sm" fw={500}>
|
||||
Perubahan Otomatis: {stateDetail.create.form.percentageChange.toFixed(1)}%
|
||||
</Text>
|
||||
<Group mt="md">
|
||||
|
||||
<Box>
|
||||
<Text fz="sm" fw={500} mb={4}>
|
||||
Total Otomatis:
|
||||
</Text>
|
||||
<Text fz="sm" c="dimmed">
|
||||
{stateDetail.create.form.totalUnemployment.toLocaleString()}
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Text fz="sm" fw={500} mb={4}>
|
||||
Perubahan Otomatis:
|
||||
</Text>
|
||||
<Text fz="sm" c="dimmed">
|
||||
{stateDetail.create.form.percentageChange.toFixed(1)}%
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
{/* Action Button */}
|
||||
<Group justify="right" mt="md">
|
||||
<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)',
|
||||
}}
|
||||
disabled={!stateDetail.create.form.month || !stateDetail.create.form.year}
|
||||
>
|
||||
Simpan
|
||||
|
||||
@@ -1,150 +1,181 @@
|
||||
/* 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, Group, Pagination, Paper, Skeleton, Stack,
|
||||
Table, TableTbody, TableTd, TableTh, TableThead, TableTr,
|
||||
Text, Title, Tooltip
|
||||
} from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
|
||||
import { IconDeviceImac, IconPlus, IconSearch } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
import { BarChart } from '@mantine/charts';
|
||||
|
||||
import HeaderSearch from '../../_com/header';
|
||||
import JudulList from '../../_com/judulList';
|
||||
import jumlahPengangguranState from '../../_state/ekonomi/jumlah-pengangguran';
|
||||
|
||||
function DetailDataPengangguran() {
|
||||
const [search, setSearch] = useState("")
|
||||
const [search, setSearch] = useState("");
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Stack gap={"xs"}>
|
||||
<HeaderSearch
|
||||
title='Detail Data Pengangguran'
|
||||
placeholder='pencarian'
|
||||
placeholder='Cari bulan atau tahun...'
|
||||
searchIcon={<IconSearch size={20} />}
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||
/>
|
||||
<ListDetailDataPengangguran search={search} />
|
||||
</Stack>
|
||||
<ListDetailDataPengangguran search={search} />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
function ListDetailDataPengangguran({search}: {search: string}) {
|
||||
|
||||
type DetailDataPengangguran = {
|
||||
id: string;
|
||||
month: string;
|
||||
year: number;
|
||||
educatedUnemployment: number;
|
||||
uneducatedUnemployment: number;
|
||||
percentageChange: number;
|
||||
totalUnemployment: number;
|
||||
}
|
||||
const [chartData, setChartData] = useState<DetailDataPengangguran[]>([]);
|
||||
const [mounted, setMounted] = useState(false); // untuk memastikan DOM sudah ready
|
||||
const stateDetail = useProxy(jumlahPengangguranState.jumlahPengangguran)
|
||||
function ListDetailDataPengangguran({ search }: { search: string }) {
|
||||
const [chartData, setChartData] = useState<any[]>([]);
|
||||
const [mounted, setMounted] = useState(false);
|
||||
const stateDetail = useProxy(jumlahPengangguranState.jumlahPengangguran);
|
||||
const router = useRouter();
|
||||
|
||||
const {
|
||||
data,
|
||||
page,
|
||||
totalPages,
|
||||
loading,
|
||||
load,
|
||||
} = stateDetail.findMany;
|
||||
|
||||
useShallowEffect(() => {
|
||||
setMounted(true)
|
||||
stateDetail.findMany.load()
|
||||
}, [])
|
||||
setMounted(true);
|
||||
load(page, 10, search);
|
||||
}, [page, search]);
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
if (stateDetail.findMany.data) {
|
||||
setChartData(stateDetail.findMany.data.map((item) => ({
|
||||
id: item.id,
|
||||
month: item.month,
|
||||
year: item.year && typeof item.year === 'object' && 'getFullYear' in item.year ? (item.year as Date).getFullYear() : Number(item.year),
|
||||
educatedUnemployment: Number(item.educatedUnemployment),
|
||||
uneducatedUnemployment: Number(item.uneducatedUnemployment),
|
||||
percentageChange: Number(item.percentageChange),
|
||||
totalUnemployment: Number(item.totalUnemployment),
|
||||
})));
|
||||
if (data) {
|
||||
setChartData(
|
||||
data.map((item) => ({
|
||||
id: item.id,
|
||||
month: item.month,
|
||||
year: Number(item.year),
|
||||
educatedUnemployment: Number(item.educatedUnemployment),
|
||||
uneducatedUnemployment: Number(item.uneducatedUnemployment),
|
||||
percentageChange: Number(item.percentageChange),
|
||||
totalUnemployment: Number(item.totalUnemployment),
|
||||
}))
|
||||
);
|
||||
}
|
||||
}, [stateDetail.findMany.data]);
|
||||
}, [data]);
|
||||
|
||||
const filteredData = (stateDetail.findMany.data || []).filter(item => {
|
||||
const keyword = search.toLowerCase();
|
||||
const filteredData = data || []
|
||||
|
||||
// Loading state
|
||||
if (loading || !data) {
|
||||
return (
|
||||
item.month.toLowerCase().includes(keyword) ||
|
||||
item.year.toString().toLowerCase().includes(keyword)
|
||||
<Stack py="md">
|
||||
<Skeleton h={500} radius="md" />
|
||||
</Stack>
|
||||
);
|
||||
});
|
||||
|
||||
if (!stateDetail.findMany.data) {
|
||||
return (
|
||||
<Box>
|
||||
<Skeleton h={500} />
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Stack gap={"md"}>
|
||||
<Paper bg={colors['white-1']} p={'md'}>
|
||||
<JudulList
|
||||
title='List Detail Data Pengangguran'
|
||||
href='/admin/ekonomi/jumlah-pengangguran/create'
|
||||
/>
|
||||
<Table striped withTableBorder withRowBorders>
|
||||
<Stack py="md" gap="lg">
|
||||
{/* Table Section */}
|
||||
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
|
||||
<Group justify="space-between" mb="md">
|
||||
<Title order={4}>Daftar Detail Data Pengangguran</Title>
|
||||
<Tooltip label="Tambah Data Baru" withArrow>
|
||||
<Button
|
||||
leftSection={<IconPlus size={18} />}
|
||||
color="blue"
|
||||
variant="light"
|
||||
onClick={() => router.push('/admin/ekonomi/jumlah-pengangguran/create')}
|
||||
>
|
||||
Tambah Baru
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</Group>
|
||||
|
||||
<Box style={{ overflowX: 'auto' }}>
|
||||
<Table highlightOnHover striped withTableBorder withRowBorders>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh>Bulan</TableTh>
|
||||
<TableTh>Terdidik</TableTh>
|
||||
<TableTh>Tidak Terdidik</TableTh>
|
||||
<TableTh>Detail</TableTh>
|
||||
<TableTh style={{ width: '25%' }}>Bulan</TableTh>
|
||||
<TableTh style={{ width: '20%' }}>Terdidik</TableTh>
|
||||
<TableTh style={{ width: '20%' }}>Tidak Terdidik</TableTh>
|
||||
<TableTh style={{ width: '15%' }}>Aksi</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{filteredData.map((item) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd>{item.month}</TableTd>
|
||||
<TableTd>{item.educatedUnemployment}</TableTd>
|
||||
<TableTd>{item.uneducatedUnemployment}</TableTd>
|
||||
<TableTd>
|
||||
<Button onClick={() => router.push(`/admin/ekonomi/jumlah-pengangguran/${item.id}`)}>
|
||||
<IconDeviceImac size={20} />
|
||||
</Button>
|
||||
{filteredData.length > 0 ? (
|
||||
filteredData.map((item) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd>{item.month} {item.year}</TableTd>
|
||||
<TableTd>{item.educatedUnemployment}</TableTd>
|
||||
<TableTd>{item.uneducatedUnemployment}</TableTd>
|
||||
<TableTd>
|
||||
<Button
|
||||
variant="light"
|
||||
color="blue"
|
||||
onClick={() => router.push(`/admin/ekonomi/jumlah-pengangguran/${item.id}`)}
|
||||
>
|
||||
<IconDeviceImac size={20} />
|
||||
<Text ml={5}>Detail</Text>
|
||||
</Button>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
))
|
||||
) : (
|
||||
<TableTr>
|
||||
<TableTd colSpan={4}>
|
||||
<Center py={20}>
|
||||
<Text c="dimmed">Tidak ada data yang cocok</Text>
|
||||
</Center>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
))}
|
||||
)}
|
||||
</TableTbody>
|
||||
</Table>
|
||||
</Paper>
|
||||
</Box>
|
||||
</Paper>
|
||||
<Center>
|
||||
<Pagination
|
||||
value={page}
|
||||
onChange={(newPage) => {
|
||||
load(newPage, 10);
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
}}
|
||||
total={totalPages}
|
||||
mt="md"
|
||||
mb="md"
|
||||
color="blue"
|
||||
radius="md"
|
||||
/>
|
||||
</Center>
|
||||
|
||||
{/* Chart */}
|
||||
{!mounted && !chartData ? (
|
||||
<Box style={{ width: '100%', minWidth: 300, height: 400, minHeight: 300 }}>
|
||||
<Paper bg={colors['white-1']} p={'md'}>
|
||||
<Title pb={10} order={3}>Data Pengangguran Terdidik dan Tidak Terdidik</Title>
|
||||
<Text c='dimmed'>Belum ada data untuk ditampilkan dalam grafik</Text>
|
||||
</Paper>
|
||||
{/* Chart Section */}
|
||||
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
|
||||
<Title order={4} mb="md">
|
||||
Data Pengangguran Terdidik & Tidak Terdidik
|
||||
</Title>
|
||||
{mounted && chartData.length > 0 ? (
|
||||
<Box w={{ base: '100%', md: '70%' }}>
|
||||
<BarChart
|
||||
h={450}
|
||||
data={chartData}
|
||||
dataKey="month"
|
||||
series={[
|
||||
{ name: 'educatedUnemployment', color: 'red.6', label: 'Terdidik' },
|
||||
{ name: 'uneducatedUnemployment', color: 'orange.6', label: 'Tidak Terdidik' },
|
||||
]}
|
||||
/>
|
||||
</Box>
|
||||
) : (
|
||||
<Box style={{ width: '100%', minWidth: 300, height: 550, minHeight: 300 }}>
|
||||
<Paper bg={colors['white-1']} p={'md'}>
|
||||
<Title pb={10} order={4}>Data Pengangguran Terdidik dan Tidak Terdidik</Title>
|
||||
{mounted && chartData.length > 0 && (
|
||||
<Box w={{ base: '100%', md: '70%' }}>
|
||||
<BarChart
|
||||
h={450}
|
||||
data={chartData}
|
||||
dataKey="month"
|
||||
series={[
|
||||
{ name: 'educatedUnemployment', color: 'red.6', label: 'Terdidik' },
|
||||
{ name: 'uneducatedUnemployment', color: 'orange.6', label: 'Tidak Terdidik' },
|
||||
]}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
</Paper>
|
||||
</Box>
|
||||
<Text c="dimmed">Belum ada data untuk ditampilkan dalam grafik</Text>
|
||||
)}
|
||||
</Stack>
|
||||
</Box>
|
||||
</Paper>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
'use client'
|
||||
|
||||
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
|
||||
import { IconKey } from '@/app/admin/(dashboard)/_com/iconMap';
|
||||
import SelectIconProgramEdit from '@/app/admin/(dashboard)/_com/selectIconEdit';
|
||||
import programKemiskinanState from '@/app/admin/(dashboard)/_state/ekonomi/program-kemiskinan';
|
||||
import colors from '@/con/colors';
|
||||
import {
|
||||
@@ -13,11 +15,13 @@ import {
|
||||
Text,
|
||||
TextInput,
|
||||
Title,
|
||||
Tooltip,
|
||||
} from '@mantine/core';
|
||||
import { IconArrowBack } from '@tabler/icons-react';
|
||||
import { useRouter, useParams } from 'next/navigation';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import { useEffect } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
function EditProgramKemiskinan() {
|
||||
const router = useRouter();
|
||||
@@ -33,46 +37,74 @@ function EditProgramKemiskinan() {
|
||||
stateProgram.update.form = {
|
||||
nama: data.nama || '',
|
||||
deskripsi: data.deskripsi || '',
|
||||
ikonUrl: data.ikonUrl || '',
|
||||
icon: data.icon || '',
|
||||
statistik: {
|
||||
tahun: data.statistik?.tahun?.toString() || '',
|
||||
jumlah: data.statistik?.jumlah?.toString() || '',
|
||||
},
|
||||
};
|
||||
}
|
||||
}).catch((err) => {
|
||||
console.error("Error load data:", err);
|
||||
toast.error("Gagal mengambil data program");
|
||||
});
|
||||
}
|
||||
}, [id]);
|
||||
|
||||
const handleSubmit = async () => {
|
||||
stateProgram.update.id = id;
|
||||
await stateProgram.update.update();
|
||||
router.push('/admin/ekonomi/program-kemiskinan');
|
||||
try {
|
||||
stateProgram.update.id = id;
|
||||
await stateProgram.update.update();
|
||||
toast.success("Program berhasil diperbarui!");
|
||||
router.push('/admin/ekonomi/program-kemiskinan');
|
||||
} catch (error) {
|
||||
console.error("Error update program:", error);
|
||||
toast.error("Terjadi kesalahan saat memperbarui program");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Box mb={10}>
|
||||
<Button onClick={() => router.back()} variant="subtle" color="blue">
|
||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p="md">
|
||||
<Stack gap="xs">
|
||||
<Title order={4}>Edit Program Kemiskinan</Title>
|
||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||
{/* Header dengan tombol kembali */}
|
||||
<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 Program Kemiskinan
|
||||
</Title>
|
||||
</Group>
|
||||
|
||||
<Paper
|
||||
w={{ base: '100%', md: '50%' }}
|
||||
bg={colors['white-1']}
|
||||
p="lg"
|
||||
radius="md"
|
||||
shadow="sm"
|
||||
style={{ border: '1px solid #e0e0e0' }}
|
||||
>
|
||||
<Stack gap="md">
|
||||
<TextInput
|
||||
value={stateProgram.update.form.nama}
|
||||
onChange={(e) => {
|
||||
stateProgram.update.form.nama = e.target.value;
|
||||
}}
|
||||
label={<Text fw="bold" fz="md">Judul Program</Text>}
|
||||
label={<Text fw="bold" fz="sm">Judul Program</Text>}
|
||||
placeholder="Masukkan judul program"
|
||||
required
|
||||
/>
|
||||
|
||||
<Box>
|
||||
<Text fw="bold" fz="md">Deskripsi</Text>
|
||||
<Text fw="bold" fz="sm" mb={6}>
|
||||
Deskripsi
|
||||
</Text>
|
||||
<EditEditor
|
||||
value={stateProgram.update.form.deskripsi}
|
||||
onChange={(val) => {
|
||||
@@ -81,40 +113,58 @@ function EditProgramKemiskinan() {
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<TextInput
|
||||
value={stateProgram.update.form.ikonUrl}
|
||||
onChange={(e) => {
|
||||
stateProgram.update.form.ikonUrl = e.target.value;
|
||||
}}
|
||||
label={<Text fw="bold" fz="md">Ikon URL</Text>}
|
||||
placeholder="Masukkan ikon url"
|
||||
/>
|
||||
<Box>
|
||||
<Text fw="bold" fz="sm" mb={6}>
|
||||
Ikon Program Kreatif Desa
|
||||
</Text>
|
||||
<SelectIconProgramEdit
|
||||
value={stateProgram.update.form.icon as IconKey}
|
||||
onChange={(value) => {
|
||||
stateProgram.update.form.icon = value;
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Text fw="bold" fz="md">Statistik Jumlah Masyarakat Miskin</Text>
|
||||
<Box>
|
||||
<Text fw="bold" fz="sm" mb={6}>
|
||||
Statistik Jumlah Masyarakat Miskin
|
||||
</Text>
|
||||
<TextInput
|
||||
type="number"
|
||||
value={stateProgram.update.form.statistik.jumlah}
|
||||
onChange={(e) => {
|
||||
stateProgram.update.form.statistik.jumlah = e.target.value;
|
||||
}}
|
||||
label="Jumlah Masyarakat Miskin"
|
||||
placeholder="Masukkan jumlah masyarakat miskin"
|
||||
required
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
type="number"
|
||||
value={stateProgram.update.form.statistik.jumlah}
|
||||
onChange={(e) => {
|
||||
stateProgram.update.form.statistik.jumlah = e.target.value;
|
||||
}}
|
||||
label={<Text fw="bold" fz="md">Jumlah Masyarakat Miskin</Text>}
|
||||
placeholder="Masukkan jumlah masyarakat miskin"
|
||||
/>
|
||||
<TextInput
|
||||
type="number"
|
||||
value={stateProgram.update.form.statistik.tahun}
|
||||
onChange={(e) => {
|
||||
stateProgram.update.form.statistik.tahun = e.target.value;
|
||||
}}
|
||||
label="Tahun"
|
||||
placeholder="Masukkan tahun"
|
||||
required
|
||||
mt="sm"
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<TextInput
|
||||
type="number"
|
||||
value={stateProgram.update.form.statistik.tahun}
|
||||
onChange={(e) => {
|
||||
stateProgram.update.form.statistik.tahun = e.target.value;
|
||||
}}
|
||||
label={<Text fw="bold" fz="md">Tahun</Text>}
|
||||
placeholder="Masukkan tahun"
|
||||
/>
|
||||
|
||||
<Group>
|
||||
<Button bg={colors['blue-button']} onClick={handleSubmit}>
|
||||
Submit
|
||||
<Group justify="right" mt="md">
|
||||
<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)',
|
||||
}}
|
||||
>
|
||||
Simpan
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
|
||||
@@ -1,115 +1,169 @@
|
||||
'use client'
|
||||
|
||||
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 { IconArrowBack, IconEdit, IconTrash } from '@tabler/icons-react';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import { useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
||||
import programKemiskinanState from '../../../_state/ekonomi/program-kemiskinan';
|
||||
|
||||
import { IconKey, IconMapper } from '../../../_com/iconMap';
|
||||
|
||||
function DetailProgramKemiskinan() {
|
||||
const programState = useProxy(programKemiskinanState)
|
||||
const [modalHapus, setModalHapus] = useState(false)
|
||||
const [selectedId, setSelectedId] = useState<string | null>(null)
|
||||
const programState = useProxy(programKemiskinanState);
|
||||
const [modalHapus, setModalHapus] = useState(false);
|
||||
const [selectedId, setSelectedId] = useState<string | null>(null);
|
||||
const router = useRouter();
|
||||
const params = useParams()
|
||||
const params = useParams();
|
||||
|
||||
useShallowEffect(() => {
|
||||
programState.findUnique.load(params?.id as string)
|
||||
}, [])
|
||||
programState.findUnique.load(params?.id as string);
|
||||
}, []);
|
||||
|
||||
const handleHapus = () => {
|
||||
if (selectedId) {
|
||||
programState.delete.delete(selectedId)
|
||||
setModalHapus(false)
|
||||
setSelectedId(null)
|
||||
router.push("/admin/ekonomi/program-kemiskinan")
|
||||
programState.delete.delete(selectedId);
|
||||
setModalHapus(false);
|
||||
setSelectedId(null);
|
||||
router.push("/admin/ekonomi/program-kemiskinan");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (!programState.findUnique.data) {
|
||||
return (
|
||||
<Stack py={10}>
|
||||
<Skeleton h={40} />
|
||||
<Skeleton height={500} radius="md" />
|
||||
</Stack>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const data = programState.findUnique.data;
|
||||
|
||||
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 Program Kemiskinan</Text>
|
||||
<Paper bg={colors['BG-trans']} p={'md'}>
|
||||
<Stack gap={"xs"}>
|
||||
<Box py={10}>
|
||||
{/* Tombol Kembali */}
|
||||
<Button
|
||||
variant="subtle"
|
||||
onClick={() => router.back()}
|
||||
leftSection={<IconArrowBack size={24} color={colors['blue-button']} />}
|
||||
mb={15}
|
||||
>
|
||||
Kembali
|
||||
</Button>
|
||||
|
||||
{/* Card utama */}
|
||||
<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 Program Kemiskinan
|
||||
</Text>
|
||||
|
||||
{/* Detail Content */}
|
||||
<Paper bg="#ECEEF8" p="md" radius="md" shadow="xs">
|
||||
<Stack gap="sm">
|
||||
<Box>
|
||||
<Text fw={"bold"}>Judul Program</Text>
|
||||
<Text fz={"md"}>{programState.findUnique.data?.nama}</Text>
|
||||
<Text fz="lg" fw="bold">Judul Program</Text>
|
||||
<Text fz="md" c="dimmed">{data.nama || '-'}</Text>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Text fw={"bold"}>Deskripsi Singkat</Text>
|
||||
<Text fz={"md"} dangerouslySetInnerHTML={{ __html: programState.findUnique.data?.deskripsi }}></Text>
|
||||
<Text fz="lg" fw="bold">Deskripsi Singkat</Text>
|
||||
<Text
|
||||
fz="md"
|
||||
c="dimmed"
|
||||
dangerouslySetInnerHTML={{ __html: data.deskripsi || '-' }}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Text fw={"bold"}>Ikon URL</Text>
|
||||
<Text fz={"md"}>{programState.findUnique.data?.ikonUrl}</Text>
|
||||
<Text fz="lg" fw="bold">Ikon Program</Text>
|
||||
{data.icon ? (
|
||||
<IconMapper
|
||||
name={data.icon as IconKey}
|
||||
size={32}
|
||||
color={colors['blue-button']}
|
||||
/>
|
||||
) : (
|
||||
<Text fz="sm" c="dimmed">Tidak ada ikon</Text>
|
||||
)}
|
||||
</Box>
|
||||
<Text fw={"bold"}>Statistik Jumlah Masyarakat Miskin</Text>
|
||||
|
||||
<Box>
|
||||
<Text fw={"bold"}>Jumlah Masyarakat Miskin</Text>
|
||||
<Text fz={"md"}>{programState.findUnique.data?.statistik?.jumlah}</Text>
|
||||
<Text fz="lg" fw="bold">Statistik Jumlah Masyarakat Miskin</Text>
|
||||
<Stack gap="xs" pl="sm">
|
||||
<Box>
|
||||
<Text fw="bold">Jumlah</Text>
|
||||
<Text fz="md" c="dimmed">{data.statistik?.jumlah || '-'}</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fw="bold">Tahun</Text>
|
||||
<Text fz="md" c="dimmed">{data.statistik?.tahun || '-'}</Text>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fw={"bold"}>Tahun</Text>
|
||||
<Text fz={"md"}>{programState.findUnique.data?.statistik?.tahun}</Text>
|
||||
</Box>
|
||||
<Flex gap={"xs"} mt={10}>
|
||||
<Button
|
||||
onClick={() => {
|
||||
if (programState.findUnique.data) {
|
||||
setSelectedId(programState.findUnique.data.id);
|
||||
|
||||
{/* Action Buttons */}
|
||||
<Group gap="sm" mt="sm">
|
||||
<Tooltip label="Hapus Program" withArrow position="top">
|
||||
<Button
|
||||
color="red"
|
||||
onClick={() => {
|
||||
setSelectedId(data.id);
|
||||
setModalHapus(true);
|
||||
}
|
||||
}}
|
||||
disabled={programState.delete.loading || !programState.findUnique.data}
|
||||
color={"red"}
|
||||
>
|
||||
<IconX size={20} />
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
if (programState.findUnique.data) {
|
||||
router.push(`/admin/ekonomi/program-kemiskinan/${programState.findUnique.data.id}/edit`);
|
||||
}
|
||||
}}
|
||||
disabled={!programState.findUnique.data}
|
||||
color={"green"}
|
||||
>
|
||||
<IconEdit size={20} />
|
||||
</Button>
|
||||
</Flex>
|
||||
}}
|
||||
variant="light"
|
||||
radius="md"
|
||||
size="md"
|
||||
disabled={programState.delete.loading}
|
||||
>
|
||||
<IconTrash size={20} />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip label="Edit Program" withArrow position="top">
|
||||
<Button
|
||||
color="green"
|
||||
onClick={() => router.push(`/admin/ekonomi/program-kemiskinan/${data.id}/edit`)}
|
||||
variant="light"
|
||||
radius="md"
|
||||
size="md"
|
||||
>
|
||||
<IconEdit size={20} />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
{/* Modal Hapus */}
|
||||
{/* Modal Konfirmasi Hapus */}
|
||||
<ModalKonfirmasiHapus
|
||||
opened={modalHapus}
|
||||
onClose={() => setModalHapus(false)}
|
||||
onConfirm={handleHapus}
|
||||
text="Apakah anda yakin ingin menghapus program kemiskinan ini?"
|
||||
text="Apakah Anda yakin ingin menghapus program kemiskinan ini?"
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default DetailProgramKemiskinan;
|
||||
|
||||
|
||||
@@ -1,34 +1,50 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
'use client'
|
||||
'use client';
|
||||
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Group,
|
||||
Paper,
|
||||
Stack,
|
||||
Text,
|
||||
TextInput,
|
||||
Title,
|
||||
Tooltip,
|
||||
} from '@mantine/core';
|
||||
import { IconArrowBack } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import programKemiskinanState from '../../../_state/ekonomi/program-kemiskinan';
|
||||
import CreateEditor from '../../../_com/createEditor';
|
||||
import SelectIconProgram from '../../../_com/selectIcon';
|
||||
import { useState } from 'react';
|
||||
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
function CreateProgramKemiskinan() {
|
||||
const programState = useProxy(programKemiskinanState)
|
||||
const programState = useProxy(programKemiskinanState);
|
||||
const router = useRouter();
|
||||
const [lineChart, setLineChart] = useState<any[]>([]);
|
||||
|
||||
const resetForm = () => {
|
||||
programState.create.form = {
|
||||
nama: "",
|
||||
deskripsi: "",
|
||||
ikonUrl: "",
|
||||
nama: '',
|
||||
deskripsi: '',
|
||||
icon: '',
|
||||
statistik: {
|
||||
tahun: "",
|
||||
jumlah: "",
|
||||
}
|
||||
}
|
||||
}
|
||||
tahun: '',
|
||||
jumlah: '',
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (!programState.create.form.nama || !programState.create.form.deskripsi) {
|
||||
return toast.warn('Judul dan deskripsi wajib diisi');
|
||||
}
|
||||
|
||||
const id = await programState.create.create();
|
||||
if (id) {
|
||||
const idStr = String(id);
|
||||
@@ -36,68 +52,116 @@ function CreateProgramKemiskinan() {
|
||||
if (programState.findUnique.data) {
|
||||
setLineChart([programState.findUnique.data]);
|
||||
}
|
||||
toast.success('Program berhasil ditambahkan');
|
||||
} else {
|
||||
toast.error('Gagal menambahkan program, coba lagi');
|
||||
}
|
||||
resetForm()
|
||||
router.push("/admin/ekonomi/program-kemiskinan")
|
||||
}
|
||||
|
||||
resetForm();
|
||||
router.push('/admin/ekonomi/program-kemiskinan');
|
||||
};
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Box mb={10}>
|
||||
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
|
||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||
</Button>
|
||||
</Box>
|
||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||
{/* Header dengan tombol back */}
|
||||
<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">
|
||||
Tambah Program Kemiskinan
|
||||
</Title>
|
||||
</Group>
|
||||
|
||||
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
||||
<Stack gap={"xs"}>
|
||||
<Title order={4}>Create Program Kemiskinan</Title>
|
||||
<Paper
|
||||
w={{ base: '100%', md: '50%' }}
|
||||
bg={colors['white-1']}
|
||||
p="lg"
|
||||
radius="md"
|
||||
shadow="sm"
|
||||
style={{ border: '1px solid #e0e0e0' }}
|
||||
>
|
||||
<Stack gap="md">
|
||||
{/* Judul Program */}
|
||||
<TextInput
|
||||
label="Judul Program"
|
||||
placeholder="Masukkan judul program"
|
||||
value={programState.create.form.nama}
|
||||
onChange={(val) => {
|
||||
programState.create.form.nama = val.target.value;
|
||||
}}
|
||||
label={<Text fw={"bold"} fz={"md"}>Judul Program</Text>}
|
||||
placeholder='Masukkan judul program'
|
||||
onChange={(val) => (programState.create.form.nama = val.target.value)}
|
||||
required
|
||||
/>
|
||||
<Box>
|
||||
<Text fw={"bold"} fz={"md"}>Deskripsi</Text>
|
||||
<CreateEditor
|
||||
value={programState.create.form.deskripsi}
|
||||
onChange={(val) => {
|
||||
programState.create.form.deskripsi = val;
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<TextInput
|
||||
value={programState.create.form.ikonUrl}
|
||||
onChange={(val) => {
|
||||
programState.create.form.ikonUrl = val.target.value;
|
||||
}}
|
||||
label={<Text fw={"bold"} fz={"md"}>Ikon URL</Text>}
|
||||
placeholder='Masukkan ikon url'
|
||||
/>
|
||||
<Text fw={"bold"} fz={"md"}>Statistik Jumlah Masyarakat Miskin</Text>
|
||||
<TextInput
|
||||
type='number'
|
||||
value={programState.create.form.statistik.jumlah}
|
||||
onChange={(val) => {
|
||||
programState.create.form.statistik.jumlah = val.target.value;
|
||||
}}
|
||||
label={<Text fw={"bold"} fz={"md"}>Jumlah Masyarakat Miskin</Text>}
|
||||
placeholder='Masukkan jumlah masyarakat miskin'
|
||||
/>
|
||||
<TextInput
|
||||
type='number'
|
||||
value={programState.create.form.statistik.tahun}
|
||||
onChange={(val) => {
|
||||
programState.create.form.statistik.tahun = val.target.value;
|
||||
}}
|
||||
label={<Text fw={"bold"} fz={"md"}>Tahun</Text>}
|
||||
placeholder='Masukkan tahun'
|
||||
/>
|
||||
<Group>
|
||||
<Button bg={colors['blue-button']} onClick={handleSubmit}>Submit</Button>
|
||||
|
||||
{/* Ikon Program */}
|
||||
<Box>
|
||||
<Text fw="bold" fz="sm" mb={6}>
|
||||
Ikon Program Kreatif Desa
|
||||
</Text>
|
||||
<SelectIconProgram
|
||||
onChange={(value) => (programState.create.form.icon = value)}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{/* Deskripsi */}
|
||||
<Box>
|
||||
<Text fw="bold" fz="sm" mb={6}>
|
||||
Deskripsi
|
||||
</Text>
|
||||
<CreateEditor
|
||||
value={programState.create.form.deskripsi}
|
||||
onChange={(val) => {
|
||||
programState.create.form.deskripsi = val;
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{/* Statistik */}
|
||||
<Text fw="bold" fz="sm">
|
||||
Statistik Jumlah Masyarakat Miskin
|
||||
</Text>
|
||||
<Group grow>
|
||||
<TextInput
|
||||
type="number"
|
||||
value={programState.create.form.statistik.jumlah}
|
||||
onChange={(val) =>
|
||||
(programState.create.form.statistik.jumlah = val.target.value)
|
||||
}
|
||||
label="Jumlah"
|
||||
placeholder="Masukkan jumlah masyarakat miskin"
|
||||
required
|
||||
/>
|
||||
<TextInput
|
||||
type="number"
|
||||
value={programState.create.form.statistik.tahun}
|
||||
onChange={(val) =>
|
||||
(programState.create.form.statistik.tahun = val.target.value)
|
||||
}
|
||||
label="Tahun"
|
||||
placeholder="Masukkan tahun"
|
||||
required
|
||||
/>
|
||||
</Group>
|
||||
|
||||
{/* Tombol Submit */}
|
||||
<Group justify="right" mt="sm">
|
||||
<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)',
|
||||
}}
|
||||
>
|
||||
Simpan
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
'use client'
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
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 { IconDeviceImac, IconSearch } from '@tabler/icons-react';
|
||||
import HeaderSearch from '../../_com/header';
|
||||
import JudulList from '../../_com/judulList';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import programKemiskinanState from '../../_state/ekonomi/program-kemiskinan';
|
||||
import { Box, Button, Center, Group, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title, Tooltip } from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { IconDeviceImac, IconPlus, IconSearch } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { CartesianGrid, Legend, Line, LineChart, Tooltip, XAxis, YAxis } from 'recharts';
|
||||
import { CartesianGrid, Legend, Line, LineChart, Tooltip as RechartTooltip, XAxis, YAxis } from 'recharts';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import HeaderSearch from '../../_com/header';
|
||||
import programKemiskinanState from '../../_state/ekonomi/program-kemiskinan';
|
||||
|
||||
function ProgramKemiskinan() {
|
||||
const [search, setSearch] = useState("");
|
||||
@@ -18,7 +17,7 @@ function ProgramKemiskinan() {
|
||||
<Box>
|
||||
<HeaderSearch
|
||||
title='Program Kemiskinan'
|
||||
placeholder='pencarian'
|
||||
placeholder='Cari judul program atau deskripsi...'
|
||||
searchIcon={<IconSearch size={20} />}
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||
@@ -29,23 +28,17 @@ function ProgramKemiskinan() {
|
||||
}
|
||||
|
||||
function ListProgramKemiskinan({ search }: { search: string }) {
|
||||
const programState = useProxy(programKemiskinanState)
|
||||
const programState = useProxy(programKemiskinanState);
|
||||
const router = useRouter();
|
||||
const [lineChart, setLineChart] = useState<any[]>([]);
|
||||
const [mounted, setMounted] = useState(false);
|
||||
|
||||
const {
|
||||
data,
|
||||
page,
|
||||
totalPages,
|
||||
loading,
|
||||
load,
|
||||
} = programState.findMany;
|
||||
const { data, page, totalPages, loading, load } = programState.findMany;
|
||||
|
||||
useShallowEffect(() => {
|
||||
setMounted(true)
|
||||
load(page, 10, search)
|
||||
}, [])
|
||||
setMounted(true);
|
||||
load(page, 10, search);
|
||||
}, [page, search]);
|
||||
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
@@ -55,103 +48,118 @@ function ListProgramKemiskinan({ search }: { search: string }) {
|
||||
tahun: item.statistik?.tahun,
|
||||
jumlah: Number(item.statistik?.jumlah)
|
||||
}))
|
||||
.sort((a, b) => (a.tahun || 0) - (b.tahun || 0)); // opsional, urutkan tahun
|
||||
.sort((a, b) => (a.tahun || 0) - (b.tahun || 0));
|
||||
|
||||
setLineChart(chartData);
|
||||
|
||||
}
|
||||
}, [data])
|
||||
}, [data]);
|
||||
|
||||
const filteredData = data || []
|
||||
const filteredData = data || [];
|
||||
|
||||
if (loading || !data) {
|
||||
return (
|
||||
<Stack py={10}>
|
||||
<Skeleton h={500} />
|
||||
<Skeleton height={600} radius="md" />
|
||||
</Stack>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Box py={10}>
|
||||
<Paper bg={colors['white-1']} p={'md'}>
|
||||
<JudulList
|
||||
title='List Program Kemiskinan'
|
||||
href='/admin/ekonomi/program-kemiskinan/create'
|
||||
/>
|
||||
<Table striped withTableBorder withRowBorders>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh>Judul Program</TableTh>
|
||||
<TableTh>Deskripsi Singkat</TableTh>
|
||||
<TableTh>Jumlah Masyarakat Miskin</TableTh>
|
||||
<TableTh>Detail</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{filteredData.map((item) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd>{item.nama}</TableTd>
|
||||
<TableTd>
|
||||
<Text fz={'sm'} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
|
||||
</TableTd>
|
||||
<TableTd>{item.statistik?.jumlah}</TableTd>
|
||||
<TableTd>
|
||||
<Button onClick={() => router.push(`/admin/ekonomi/program-kemiskinan/${item.id}`)}>
|
||||
<IconDeviceImac size={20} />
|
||||
</Button>
|
||||
</TableTd>
|
||||
<Paper withBorder bg={colors['white-1']} p={'lg'} shadow="md" radius="md">
|
||||
<Group justify="space-between" mb="md">
|
||||
<Title order={4}>Daftar Program Kemiskinan</Title>
|
||||
<Tooltip label="Tambah Program Kemiskinan" withArrow>
|
||||
<Button leftSection={<IconPlus size={18} />} color="blue" variant="light" onClick={() => router.push('/admin/ekonomi/program-kemiskinan/create')}>
|
||||
Tambah Baru
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</Group>
|
||||
<Box style={{ overflowX: 'auto' }}>
|
||||
<Table highlightOnHover>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh style={{ width: '30%' }}>Judul Program</TableTh>
|
||||
<TableTh style={{ width: '40%' }}>Deskripsi Singkat</TableTh>
|
||||
<TableTh style={{ width: '20%' }}>Jumlah Masyarakat Miskin</TableTh>
|
||||
<TableTh style={{ width: '10%' }}>Aksi</TableTh>
|
||||
</TableTr>
|
||||
))}
|
||||
</TableTbody>
|
||||
</Table>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{filteredData.length > 0 ? (
|
||||
filteredData.map(item => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd>
|
||||
<Text fw={500} truncate lineClamp={1}>{item.nama}</Text>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Text fz="sm" truncate lineClamp={2} dangerouslySetInnerHTML={{ __html: item.deskripsi }} />
|
||||
</TableTd>
|
||||
<TableTd>{item.statistik?.jumlah || '-'}</TableTd>
|
||||
<TableTd>
|
||||
<Button
|
||||
variant="light"
|
||||
color="blue"
|
||||
onClick={() => router.push(`/admin/ekonomi/program-kemiskinan/${item.id}`)}
|
||||
>
|
||||
<IconDeviceImac size={20} />
|
||||
<Text ml={5}>Detail</Text>
|
||||
</Button>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
))
|
||||
) : (
|
||||
<TableTr>
|
||||
<TableTd colSpan={4}>
|
||||
<Center py={20}>
|
||||
<Text color="dimmed">Tidak ada data program kemiskinan yang cocok</Text>
|
||||
</Center>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
)}
|
||||
</TableTbody>
|
||||
</Table>
|
||||
</Box>
|
||||
</Paper>
|
||||
|
||||
{/* Pagination */}
|
||||
<Center>
|
||||
<Pagination
|
||||
value={page}
|
||||
onChange={(newPage) => {
|
||||
load(newPage, 10);
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
}}
|
||||
total={totalPages}
|
||||
mt="md"
|
||||
mb="md"
|
||||
color="blue"
|
||||
radius="md"
|
||||
/>
|
||||
</Center>
|
||||
|
||||
{/* Chart */}
|
||||
<Box>
|
||||
<Paper bg={colors['white-1']} p={'md'} >
|
||||
<Stack>
|
||||
<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' }}>
|
||||
<LineChart
|
||||
width={820}
|
||||
height={300}
|
||||
data={lineChart}
|
||||
>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis dataKey="tahun" />
|
||||
<YAxis />
|
||||
<Tooltip
|
||||
formatter={(value: any, name: string) => [`${value} orang`, name]}
|
||||
labelFormatter={(label: any) => `Tahun: ${label}`}
|
||||
/>
|
||||
<Legend />
|
||||
<Line
|
||||
type="monotone"
|
||||
dataKey="jumlah"
|
||||
name="Jumlah per Tahun"
|
||||
stroke={colors['blue-button']}
|
||||
/>
|
||||
</LineChart>
|
||||
</Box>
|
||||
|
||||
|
||||
</Box>
|
||||
) : (
|
||||
<Text c='dimmed'>Belum ada data untuk ditampilkan dalam grafik</Text>
|
||||
)}
|
||||
<Box py={10}>
|
||||
<Paper withBorder bg={colors['white-1']} p={'lg'} shadow="md" radius="md">
|
||||
<Title pb={10} order={3}>Grafik Berdasarkan Responden</Title>
|
||||
{mounted && lineChart.length > 0 ? (
|
||||
<Box style={{ width: '100%', overflowX: 'auto' }}>
|
||||
<LineChart width={820} height={300} data={lineChart}>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis dataKey="tahun" />
|
||||
<YAxis />
|
||||
<RechartTooltip
|
||||
formatter={(value: any, name: string) => [`${value} orang`, name]}
|
||||
labelFormatter={(label: any) => `Tahun: ${label}`}
|
||||
/>
|
||||
<Legend />
|
||||
<Line type="monotone" dataKey="jumlah" name="Jumlah per Tahun" stroke={colors['blue-button']} />
|
||||
</LineChart>
|
||||
</Box>
|
||||
</Stack>
|
||||
) : (
|
||||
<Text c='dimmed'>Belum ada data untuk ditampilkan dalam grafik</Text>
|
||||
)}
|
||||
</Paper>
|
||||
<Center>
|
||||
<Pagination
|
||||
value={page}
|
||||
onChange={(newPage) => load(newPage)}
|
||||
total={totalPages}
|
||||
my={"md"}
|
||||
/>
|
||||
</Center>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
|
||||
@@ -3,87 +3,138 @@
|
||||
|
||||
import grafikSektorUnggulan from '@/app/admin/(dashboard)/_state/ekonomi/sektor-unggulan-desa';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Paper, Stack, TextInput, Title } from '@mantine/core';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Group,
|
||||
Paper,
|
||||
Stack,
|
||||
Text,
|
||||
TextInput,
|
||||
Title,
|
||||
Tooltip,
|
||||
} from '@mantine/core';
|
||||
import { IconArrowBack } from '@tabler/icons-react';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import { useEffect } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import { toast } from 'react-toastify';
|
||||
import EditEditor from '@/app/admin/(dashboard)/_com/editEditor';
|
||||
|
||||
function EditSektorUnggulanDesa() {
|
||||
const router = useRouter()
|
||||
const params = useParams() as { id: string }
|
||||
const stateGrafik = useProxy(grafikSektorUnggulan)
|
||||
const router = useRouter();
|
||||
const params = useParams() as { id: string };
|
||||
const stateGrafik = useProxy(grafikSektorUnggulan);
|
||||
|
||||
const id = params.id
|
||||
const id = params.id;
|
||||
|
||||
// Load data saat komponen mount
|
||||
useEffect(() => {
|
||||
if (id) {
|
||||
stateGrafik.findUnique.load(id).then(() => {
|
||||
const data = stateGrafik.findUnique.data
|
||||
const data = stateGrafik.findUnique.data;
|
||||
if (data) {
|
||||
stateGrafik.update.form = {
|
||||
name: data.name || '',
|
||||
description: data.description || '',
|
||||
value: data.value || 0,
|
||||
}
|
||||
};
|
||||
}
|
||||
})
|
||||
}).catch((err) => {
|
||||
console.error('Error load sektor unggulan:', err);
|
||||
toast.error('Gagal mengambil data sektor unggulan');
|
||||
});
|
||||
}
|
||||
}, [id])
|
||||
}, [id]);
|
||||
|
||||
const handleSubmit = async () => {
|
||||
// Set the ID before submitting
|
||||
stateGrafik.update.id = id;
|
||||
await stateGrafik.update.submit();
|
||||
router.push('/admin/ekonomi/sektor-unggulan-desa')
|
||||
}
|
||||
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 Sektor Unggulan Desa</Title>
|
||||
try {
|
||||
stateGrafik.update.id = id;
|
||||
await stateGrafik.update.submit();
|
||||
toast.success('Sektor unggulan berhasil diperbarui!');
|
||||
router.push('/admin/ekonomi/sektor-unggulan-desa');
|
||||
} catch (error) {
|
||||
console.error('Error update sektor unggulan:', error);
|
||||
toast.error('Terjadi kesalahan saat memperbarui sektor unggulan');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<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={22} />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Title order={4} ml="sm" c="dark">
|
||||
Edit Sektor Unggulan Desa
|
||||
</Title>
|
||||
</Group>
|
||||
|
||||
<Paper
|
||||
w={{ base: '100%', md: '50%' }}
|
||||
bg={colors['white-1']}
|
||||
p="lg"
|
||||
radius="md"
|
||||
shadow="sm"
|
||||
style={{ border: '1px solid #e0e0e0' }}
|
||||
>
|
||||
<Stack gap="md">
|
||||
<TextInput
|
||||
label="Nama Sektor Unggulan"
|
||||
placeholder="masukkan nama sektor unggulan"
|
||||
placeholder="Masukkan nama sektor unggulan"
|
||||
value={stateGrafik.update.form.name}
|
||||
onChange={(val) => {
|
||||
stateGrafik.update.form.name = val.currentTarget.value;
|
||||
}}
|
||||
required
|
||||
/>
|
||||
<TextInput
|
||||
label="Deskripsi Sektor Unggulan"
|
||||
placeholder="masukkan deskripsi sektor unggulan"
|
||||
value={stateGrafik.update.form.description}
|
||||
onChange={(val) => {
|
||||
stateGrafik.update.form.description = val.currentTarget.value;
|
||||
}}
|
||||
/>
|
||||
<Box>
|
||||
<Text fz="sm" fw="bold">
|
||||
Konten
|
||||
</Text>
|
||||
<EditEditor
|
||||
value={stateGrafik.update.form.description}
|
||||
onChange={(htmlContent) => {
|
||||
stateGrafik.update.form.description = htmlContent;
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<TextInput
|
||||
label="Jumlah"
|
||||
type="number"
|
||||
placeholder="masukkan jumlah"
|
||||
placeholder="Masukkan jumlah"
|
||||
value={stateGrafik.update.form.value}
|
||||
onChange={(val) => {
|
||||
stateGrafik.update.form.value = Number(val.currentTarget.value);
|
||||
}}
|
||||
required
|
||||
/>
|
||||
<Button
|
||||
mt={10}
|
||||
bg={colors['blue-button']}
|
||||
onClick={handleSubmit}
|
||||
>
|
||||
Simpan
|
||||
</Button>
|
||||
|
||||
<Group justify="right">
|
||||
<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)',
|
||||
}}
|
||||
>
|
||||
Simpan
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export default EditSektorUnggulanDesa;
|
||||
|
||||
@@ -1,97 +1,134 @@
|
||||
'use client'
|
||||
import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus';
|
||||
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 { IconArrowBack, IconEdit, IconTrash } from '@tabler/icons-react';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import { useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import grafikSektorUnggulan from '../../../_state/ekonomi/sektor-unggulan-desa';
|
||||
|
||||
function DetailSektorUnggulanDesa() {
|
||||
const stateGrafik = useProxy(grafikSektorUnggulan)
|
||||
const [modalHapus, setModalHapus] = useState(false)
|
||||
const [selectedId, setSelectedId] = useState<string | null>(null)
|
||||
const params = useParams()
|
||||
const stateGrafik = useProxy(grafikSektorUnggulan);
|
||||
const [modalHapus, setModalHapus] = useState(false);
|
||||
const [selectedId, setSelectedId] = useState<string | null>(null);
|
||||
const params = useParams();
|
||||
const router = useRouter();
|
||||
|
||||
useShallowEffect(() => {
|
||||
stateGrafik.findUnique.load(params?.id as string)
|
||||
}, [])
|
||||
stateGrafik.findUnique.load(params?.id as string);
|
||||
}, []);
|
||||
|
||||
const handleHapus = () => {
|
||||
if (selectedId) {
|
||||
stateGrafik.delete.byId(selectedId)
|
||||
setModalHapus(false)
|
||||
setSelectedId(null)
|
||||
router.push("/admin/ekonomi/sektor-unggulan-desa")
|
||||
stateGrafik.delete.byId(selectedId);
|
||||
setModalHapus(false);
|
||||
setSelectedId(null);
|
||||
router.push('/admin/ekonomi/sektor-unggulan-desa');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (!stateGrafik.findUnique.data) {
|
||||
return (
|
||||
<Stack py={10}>
|
||||
<Skeleton h={500} />
|
||||
<Skeleton height={500} radius="md" />
|
||||
</Stack>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const data = stateGrafik.findUnique.data;
|
||||
|
||||
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 Sektor Unggulan Desa</Text>
|
||||
<Paper bg={colors['BG-trans']} p={'md'}>
|
||||
<Stack gap={"xs"}>
|
||||
<Box py={10}>
|
||||
{/* Tombol kembali */}
|
||||
<Button
|
||||
variant="subtle"
|
||||
onClick={() => router.back()}
|
||||
leftSection={<IconArrowBack size={24} color={colors['blue-button']} />}
|
||||
mb={15}
|
||||
>
|
||||
Kembali
|
||||
</Button>
|
||||
|
||||
<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 Sektor Unggulan Desa
|
||||
</Text>
|
||||
|
||||
<Paper bg="#ECEEF8" p="md" radius="md" shadow="xs">
|
||||
<Stack gap="sm">
|
||||
<Box>
|
||||
<Text fz={"lg"} fw={"bold"}>Nama Sektor Unggulan</Text>
|
||||
<Text fz={"lg"}>{stateGrafik.findUnique.data?.name}</Text>
|
||||
<Text fz="lg" fw="bold">Nama Sektor Unggulan</Text>
|
||||
<Text fz="md" c="dimmed">{data.name || '-'}</Text>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Text fz={"lg"} fw={"bold"}>Deskripsi Sektor Unggulan</Text>
|
||||
<Text fz={"lg"}>{stateGrafik.findUnique.data?.description}</Text>
|
||||
<Text fz="lg" fw="bold">Deskripsi</Text>
|
||||
<Text ta={"justify"} fz="md" c="dimmed" dangerouslySetInnerHTML={{ __html: data.description || '-' }} />
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Text fz={"lg"} fw={"bold"}>Jumlah Sektor Unggulan</Text>
|
||||
<Text fz={"lg"}>{stateGrafik.findUnique.data?.value}</Text>
|
||||
<Text fz="lg" fw="bold">Jumlah</Text>
|
||||
<Text fz="md" c="dimmed">{data.value ?? '-'}</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Flex gap={"xs"}>
|
||||
|
||||
{/* Tombol Aksi */}
|
||||
<Group gap="sm">
|
||||
<Tooltip label="Hapus Sektor Unggulan" withArrow position="top">
|
||||
<Button
|
||||
color="red"
|
||||
onClick={() => {
|
||||
if (stateGrafik.findUnique.data) {
|
||||
setSelectedId(stateGrafik.findUnique.data.id);
|
||||
setModalHapus(true);
|
||||
}
|
||||
setSelectedId(data.id);
|
||||
setModalHapus(true);
|
||||
}}
|
||||
disabled={!stateGrafik.findUnique.data}
|
||||
color="red">
|
||||
<IconX size={20} />
|
||||
variant="light"
|
||||
radius="md"
|
||||
size="md"
|
||||
>
|
||||
<IconTrash size={20} />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip label="Edit Sektor Unggulan" withArrow position="top">
|
||||
<Button
|
||||
onClick={() => {
|
||||
if (stateGrafik.findUnique.data) {
|
||||
router.push(`/admin/ekonomi/sektor-unggulan-desa/${stateGrafik.findUnique.data.id}/edit`);
|
||||
}
|
||||
}}
|
||||
disabled={!stateGrafik.findUnique.data}
|
||||
color="green">
|
||||
color="green"
|
||||
onClick={() =>
|
||||
router.push(
|
||||
`/admin/ekonomi/sektor-unggulan-desa/${data.id}/edit`
|
||||
)
|
||||
}
|
||||
variant="light"
|
||||
radius="md"
|
||||
size="md"
|
||||
>
|
||||
<IconEdit size={20} />
|
||||
</Button>
|
||||
</Flex>
|
||||
</Box>
|
||||
</Tooltip>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
{/* Modal Hapus */}
|
||||
{/* Modal Konfirmasi Hapus */}
|
||||
<ModalKonfirmasiHapus
|
||||
opened={modalHapus}
|
||||
onClose={() => setModalHapus(false)}
|
||||
|
||||
@@ -1,8 +1,18 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
'use client'
|
||||
'use client';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Group,
|
||||
Paper,
|
||||
Stack,
|
||||
Text,
|
||||
TextInput,
|
||||
Title,
|
||||
Tooltip,
|
||||
} from '@mantine/core';
|
||||
import { IconArrowBack } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useState } from 'react';
|
||||
@@ -13,15 +23,15 @@ import CreateEditor from '../../../_com/createEditor';
|
||||
function CreateSektorUnggulanDesa() {
|
||||
const stateGrafik = useProxy(grafikSektorUnggulan);
|
||||
const [chartData, setChartData] = useState<any[]>([]);
|
||||
const router = useRouter()
|
||||
const router = useRouter();
|
||||
|
||||
const resetForm = () => {
|
||||
stateGrafik.create.form = {
|
||||
name: "",
|
||||
description: "",
|
||||
name: '',
|
||||
description: '',
|
||||
value: 0,
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
const id = await stateGrafik.create.create();
|
||||
@@ -33,58 +43,87 @@ function CreateSektorUnggulanDesa() {
|
||||
}
|
||||
}
|
||||
resetForm();
|
||||
router.push("/admin/ekonomi/sektor-unggulan-desa");
|
||||
}
|
||||
router.push('/admin/ekonomi/sektor-unggulan-desa');
|
||||
};
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Box mb={10}>
|
||||
<Button variant="subtle" onClick={() => router.back()}>
|
||||
<IconArrowBack size={20} />
|
||||
</Button>
|
||||
</Box>
|
||||
<Box>
|
||||
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
||||
<Title order={4}>Tambah Grafik Hasil Kepuasan Masyarakat</Title>
|
||||
<Stack gap={"xs"}>
|
||||
<TextInput
|
||||
label="Nama Sektor Unggulan"
|
||||
type="text"
|
||||
value={stateGrafik.create.form.name}
|
||||
placeholder="Masukkan nama sektor unggulan"
|
||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||
{/* Header dengan back button */}
|
||||
<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">
|
||||
Tambah Sektor Unggulan Desa
|
||||
</Title>
|
||||
</Group>
|
||||
|
||||
{/* Form */}
|
||||
<Paper
|
||||
w={{ base: '100%', md: '50%' }}
|
||||
bg={colors['white-1']}
|
||||
p="lg"
|
||||
radius="md"
|
||||
shadow="sm"
|
||||
style={{ border: '1px solid #e0e0e0' }}
|
||||
>
|
||||
<Stack gap="md">
|
||||
<TextInput
|
||||
label="Nama Sektor Unggulan"
|
||||
placeholder="Masukkan nama sektor unggulan"
|
||||
value={stateGrafik.create.form.name}
|
||||
onChange={(e) => {
|
||||
stateGrafik.create.form.name = e.currentTarget.value;
|
||||
}}
|
||||
required
|
||||
/>
|
||||
|
||||
<Box>
|
||||
<Text fw="bold" fz="sm" mb={6}>
|
||||
Deskripsi Sektor Unggulan
|
||||
</Text>
|
||||
<CreateEditor
|
||||
value={stateGrafik.create.form.description}
|
||||
onChange={(val) => {
|
||||
stateGrafik.create.form.name = val.currentTarget.value;
|
||||
stateGrafik.create.form.description = val;
|
||||
}}
|
||||
/>
|
||||
<Box>
|
||||
<Text fw={"bold"} fz={"sm"}>Deskripsi Sektor Ungggulan</Text>
|
||||
<CreateEditor
|
||||
value={stateGrafik.create.form.description}
|
||||
onChange={(val) => {
|
||||
stateGrafik.create.form.description = val;
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<TextInput
|
||||
label="Jumlah"
|
||||
type="number"
|
||||
value={stateGrafik.create.form.value}
|
||||
placeholder="Masukkan jumlah"
|
||||
onChange={(val) => {
|
||||
stateGrafik.create.form.value = Number(val.currentTarget.value);
|
||||
</Box>
|
||||
|
||||
<TextInput
|
||||
label="Jumlah"
|
||||
type="number"
|
||||
placeholder="Masukkan jumlah"
|
||||
value={stateGrafik.create.form.value}
|
||||
onChange={(e) => {
|
||||
stateGrafik.create.form.value = Number(e.currentTarget.value);
|
||||
}}
|
||||
required
|
||||
/>
|
||||
|
||||
<Group justify="right">
|
||||
<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)',
|
||||
}}
|
||||
/>
|
||||
<Group>
|
||||
<Button
|
||||
bg={colors['blue-button']}
|
||||
mt={10}
|
||||
onClick={handleSubmit}
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
>
|
||||
Simpan
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,23 +1,40 @@
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Paper, 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';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Center,
|
||||
Group,
|
||||
Pagination,
|
||||
Paper,
|
||||
Skeleton,
|
||||
Stack,
|
||||
Table,
|
||||
TableTbody,
|
||||
TableTd,
|
||||
TableTh,
|
||||
TableThead,
|
||||
TableTr,
|
||||
Text,
|
||||
Title,
|
||||
Tooltip,
|
||||
} from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { IconDeviceImac, IconPlus, IconSearch } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useEffect, useState } from 'react';
|
||||
import grafikSektorUnggulan from '../../_state/ekonomi/sektor-unggulan-desa';
|
||||
import { Bar, BarChart, Legend, ResponsiveContainer, Tooltip as ReTooltip, XAxis, YAxis } from 'recharts';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import { useMediaQuery, useShallowEffect } from '@mantine/hooks';
|
||||
import { Bar, BarChart, Legend, Tooltip, XAxis, YAxis } from 'recharts';
|
||||
import HeaderSearch from '../../_com/header';
|
||||
import grafikSektorUnggulan from '../../_state/ekonomi/sektor-unggulan-desa';
|
||||
|
||||
function SektorUnggulanDesa() {
|
||||
const [search, setSearch] = useState('');
|
||||
return (
|
||||
<Box>
|
||||
<HeaderSearch
|
||||
title='Sektor Unggulan Desa'
|
||||
placeholder='pencarian'
|
||||
title="Sektor Unggulan Desa"
|
||||
placeholder="Cari nama sektor atau nilai..."
|
||||
searchIcon={<IconSearch size={20} />}
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||
@@ -30,96 +47,155 @@ function SektorUnggulanDesa() {
|
||||
function ListSektorUnggulanDesa({ search }: { search: string }) {
|
||||
const router = useRouter();
|
||||
const state = useProxy(grafikSektorUnggulan);
|
||||
const [chartData, setChartData] = useState<{ id: string; name: string; description: string | null; value: number | null }[]>([]);
|
||||
const [mounted, setMounted] = useState(false); // untuk memastikan DOM sudah ready
|
||||
const isTablet = useMediaQuery('(max-width: 1024px)')
|
||||
const isMobile = useMediaQuery('(max-width: 768px)')
|
||||
const [chartData, setChartData] = useState<
|
||||
{ id: string; name: string; description: string | null; value: number | null }[]
|
||||
>([]);
|
||||
|
||||
const filteredData = (state.findMany.data || []).filter(item => {
|
||||
const keyword = search.toLowerCase();
|
||||
return (
|
||||
item.name.toLowerCase().includes(keyword) ||
|
||||
(item.value?.toString() || '').toLowerCase().includes(keyword)
|
||||
);
|
||||
});
|
||||
|
||||
useShallowEffect(() => {
|
||||
setMounted(true)
|
||||
state.findMany.load()
|
||||
}, [])
|
||||
const {
|
||||
data,
|
||||
page,
|
||||
totalPages,
|
||||
loading,
|
||||
load,
|
||||
} = state.findMany;
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
if (state.findMany.data) {
|
||||
setChartData(state.findMany.data.map((item) => ({
|
||||
id: item.id,
|
||||
name: item.name,
|
||||
description: item.description,
|
||||
value: Number(item.value),
|
||||
})));
|
||||
setChartData(
|
||||
state.findMany.data.map((item) => ({
|
||||
id: item.id,
|
||||
name: item.name,
|
||||
description: item.description,
|
||||
value: Number(item.value),
|
||||
}))
|
||||
);
|
||||
}
|
||||
}, [state.findMany.data]);
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Stack gap={"xs"}>
|
||||
<Paper bg={colors['white-1']} p={'md'}>
|
||||
<JudulList
|
||||
title='List Sektor Unggulan Desa'
|
||||
href='/admin/ekonomi/sektor-unggulan-desa/create'
|
||||
/>
|
||||
<Table striped withTableBorder withRowBorders>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh>Nama Sektor Unggulan</TableTh>
|
||||
<TableTh>Deskripsi Sektor Unggulan</TableTh>
|
||||
<TableTh>Detail</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{filteredData.map((item) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd>{item.name}</TableTd>
|
||||
<TableTd>
|
||||
<Text truncate={"end"} fz={'sm'} lineClamp={1} dangerouslySetInnerHTML={{ __html: item.description || '' }}></Text>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Button onClick={() => router.push(`/admin/ekonomi/sektor-unggulan-desa/${item.id}`)}>
|
||||
<IconDeviceImac size={20} />
|
||||
</Button>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
))}
|
||||
</TableTbody>
|
||||
</Table>
|
||||
</Paper>
|
||||
useShallowEffect(() => {
|
||||
load(page, 10, search)
|
||||
}, [page, search])
|
||||
|
||||
{/* Chart */}
|
||||
{!mounted && !chartData ? (
|
||||
<Box style={{ width: '100%', minWidth: 300, height: 400, minHeight: 300 }}>
|
||||
<Paper bg={colors['white-1']} p={'md'}>
|
||||
<Title pb={10} order={3}>Grafik Hasil Kepuasan Masyarakat</Title>
|
||||
<Text c='dimmed'>Belum ada data untuk ditampilkan dalam grafik</Text>
|
||||
</Paper>
|
||||
</Box>
|
||||
const filteredData = data || []
|
||||
|
||||
if (loading || !data) {
|
||||
return (
|
||||
<Stack py={10}>
|
||||
<Skeleton height={600} radius="md" />
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack gap="md" py="md">
|
||||
{/* List Table */}
|
||||
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
|
||||
<Group justify="space-between" mb="md">
|
||||
<Title order={4}>List Sektor Unggulan Desa</Title>
|
||||
<Tooltip label="Tambah Sektor Unggulan Desa" withArrow>
|
||||
<Button leftSection={<IconPlus size={18} />} color="blue" variant="light" onClick={() => router.push('/admin/ekonomi/sektor-unggulan-desa/create')}>
|
||||
Tambah Baru
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</Group>
|
||||
{loading ? (
|
||||
<Skeleton height={300} radius="md" />
|
||||
) : (
|
||||
<Box style={{ width: '100%', minWidth: 300, height: 400, minHeight: 300 }}>
|
||||
<Paper bg={colors['white-1']} p={'md'}>
|
||||
<Title pb={10} order={4}>Grafik Sektor Unggulan Desa</Title>
|
||||
{mounted && chartData.length > 0 && (
|
||||
<BarChart width={isMobile ? 450 : isTablet ? 500 : 550} height={350} data={chartData} >
|
||||
<XAxis dataKey="name" />
|
||||
<YAxis />
|
||||
<Tooltip />
|
||||
<Legend />
|
||||
<Bar dataKey="value" fill={colors['blue-button']} name="Jumlah" />
|
||||
</BarChart>
|
||||
)}
|
||||
</Paper>
|
||||
<Box style={{ overflowX: 'auto' }}>
|
||||
<Table highlightOnHover>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh style={{ width: '30%' }}>Nama Sektor</TableTh>
|
||||
<TableTh style={{ width: '40%' }}>Deskripsi</TableTh>
|
||||
<TableTh style={{ width: '15%' }}>Detail</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{filteredData.length > 0 ? (
|
||||
filteredData.map((item) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd>
|
||||
<Box w={200}>
|
||||
<Text fw={500} truncate="end" lineClamp={1}>
|
||||
{item.name}
|
||||
</Text>
|
||||
</Box>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Box w={200}>
|
||||
<Text truncate="end" fz="sm" c="dimmed" lineClamp={1} dangerouslySetInnerHTML={{ __html: item.description || '-' }} />
|
||||
</Box>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Tooltip label="Lihat detail sektor" withArrow>
|
||||
<Button
|
||||
variant="light"
|
||||
color="blue"
|
||||
onClick={() => router.push(`/admin/ekonomi/sektor-unggulan-desa/${item.id}`)}
|
||||
>
|
||||
<IconDeviceImac size={20} />
|
||||
<Text ml={6}>Detail</Text>
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
))
|
||||
) : (
|
||||
<TableTr>
|
||||
<TableTd colSpan={3}>
|
||||
<Center py={20}>
|
||||
<Text color="dimmed">Tidak ada data sektor unggulan yang cocok</Text>
|
||||
</Center>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
)}
|
||||
</TableTbody>
|
||||
</Table>
|
||||
</Box>
|
||||
)}
|
||||
</Stack>
|
||||
</Box>
|
||||
</Paper>
|
||||
|
||||
<Center>
|
||||
<Pagination
|
||||
value={page}
|
||||
onChange={(newPage) => {
|
||||
load(newPage, 10);
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
}}
|
||||
total={totalPages}
|
||||
mt="md"
|
||||
mb="md"
|
||||
color="blue"
|
||||
radius="md"
|
||||
/>
|
||||
</Center>
|
||||
|
||||
{/* Chart */}
|
||||
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
|
||||
<Title order={4} pb="sm">
|
||||
Grafik Sektor Unggulan Desa
|
||||
</Title>
|
||||
{loading ? (
|
||||
<Skeleton height={350} radius="md" />
|
||||
) : chartData.length > 0 ? (
|
||||
<Box style={{ width: '100%', height: 400 }}>
|
||||
<ResponsiveContainer>
|
||||
<BarChart data={chartData}>
|
||||
<XAxis dataKey="name" />
|
||||
<YAxis />
|
||||
<ReTooltip />
|
||||
<Legend />
|
||||
<Bar dataKey="value" fill={colors['blue-button']} name="Jumlah" />
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
</Box>
|
||||
) : (
|
||||
<Center py={50}>
|
||||
<Text c="dimmed">Belum ada data untuk ditampilkan dalam grafik</Text>
|
||||
</Center>
|
||||
)}
|
||||
</Paper>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import { useEffect, useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import SelectIconProgramEdit from '../../../../_com/selectIconEdit';
|
||||
import { IconKey } from '@/app/admin/(dashboard)/_com/iconMap';
|
||||
|
||||
interface FormProgramKreatif {
|
||||
name: string;
|
||||
@@ -18,8 +19,6 @@ interface FormProgramKreatif {
|
||||
icon: string;
|
||||
}
|
||||
|
||||
type IconKey = 'ekowisata' | 'kompetisi' | 'wisata' | 'ekonomi' | 'sampah';
|
||||
|
||||
|
||||
function EditProgramKreatifDesa() {
|
||||
const stateProgramKreatif = useProxy(programKreatifState)
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Flex, Paper, Skeleton, Stack, Text } from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { IconArrowBack, IconChartLine, IconChristmasTreeFilled, IconClipboard, IconEdit, IconHomeEco, IconLeaf, IconRecycle, IconScale, IconShieldFilled, IconTent, IconTrash, IconTrendingUp, IconTrophy, IconTruck, IconX } from '@tabler/icons-react';
|
||||
import { IconArrowBack, IconEdit, IconX } from '@tabler/icons-react';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import React, { useState } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import programKreatifState from '../../../_state/inovasi/program-kreatif';
|
||||
import { IconKey, IconMapper } from '../../../_com/iconMap';
|
||||
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
||||
import programKreatifState from '../../../_state/inovasi/program-kreatif';
|
||||
|
||||
// import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
||||
|
||||
@@ -19,22 +19,6 @@ function DetailProgramKreatifDesa() {
|
||||
const params = useParams()
|
||||
const [selectedId, setSelectedId] = useState<string | null>(null)
|
||||
|
||||
const iconMap: Record<string, React.FC<any>> = {
|
||||
ekowisata: IconLeaf,
|
||||
kompetisi: IconTrophy,
|
||||
wisata: IconTent,
|
||||
ekonomi: IconChartLine,
|
||||
sampah: IconRecycle,
|
||||
truck: IconTruck,
|
||||
scale: IconScale,
|
||||
clipboard: IconClipboard,
|
||||
trash: IconTrash,
|
||||
lingkunganSehat: IconHomeEco,
|
||||
sumberOksigen: IconChristmasTreeFilled,
|
||||
ekonomiBerkelanjutan: IconTrendingUp,
|
||||
mencegahBencana: IconShieldFilled,
|
||||
};
|
||||
|
||||
useShallowEffect(() => {
|
||||
stateProgramKreatif.findUnique.load(params?.id as string)
|
||||
}, [params?.id])
|
||||
@@ -75,10 +59,12 @@ function DetailProgramKreatifDesa() {
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fz={"lg"} fw={"bold"}>Ikon Program Kreatif Desa</Text>
|
||||
{iconMap[stateProgramKreatif.findUnique.data?.icon] && (
|
||||
<Box title={stateProgramKreatif.findUnique.data?.icon}>
|
||||
{React.createElement(iconMap[stateProgramKreatif.findUnique.data?.icon], { size: 24 })}
|
||||
</Box>
|
||||
{stateProgramKreatif.findUnique.data?.icon && (
|
||||
<IconMapper
|
||||
name={stateProgramKreatif.findUnique.data?.icon as IconKey}
|
||||
size={32}
|
||||
color={colors['blue-button']}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
<Box>
|
||||
@@ -127,7 +113,7 @@ function DetailProgramKreatifDesa() {
|
||||
onClose={() => setModalHapus(false)}
|
||||
onConfirm={handleHapus}
|
||||
text="Apakah anda yakin ingin menghapus program kreatif desa ini?"
|
||||
/>
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
import React from 'react';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Center, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
||||
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
|
||||
import { IconCash, IconChristmasTreeFilled, IconClipboard, IconDeviceImac, IconDroplet, IconHome, IconHomeEco, IconHospital, IconScale, IconSchool, IconSearch, IconShieldFilled, IconShoppingCart, IconTrash, IconTree, IconTrendingUp, IconTruck } from '@tabler/icons-react';
|
||||
import HeaderSearch from '../../_com/header';
|
||||
import JudulList from '../../_com/judulList';
|
||||
import { useRouter } from 'next/navigation';
|
||||
@@ -60,6 +60,21 @@ function ListProgramKreatifDesa({ search }: { search: string }) {
|
||||
wisata: IconTent,
|
||||
ekonomi: IconChartLine,
|
||||
sampah: IconRecycle,
|
||||
truck: IconTruck,
|
||||
scale: IconScale,
|
||||
clipboard: IconClipboard,
|
||||
trash: IconTrash,
|
||||
lingkunganSehat: IconHomeEco,
|
||||
sumberOksigen: IconChristmasTreeFilled,
|
||||
ekonomiBerkelanjutan: IconTrendingUp,
|
||||
mencegahBencana: IconShieldFilled,
|
||||
rumah: IconHome,
|
||||
pohon: IconTree,
|
||||
air: IconDroplet,
|
||||
bantuan: IconCash,
|
||||
pelatihan: IconSchool,
|
||||
subsidi: IconShoppingCart,
|
||||
layananKesehatan: IconHospital
|
||||
};
|
||||
|
||||
if (loading || !data) {
|
||||
|
||||
@@ -1,8 +1,49 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
export default async function demografiPekerjaanFindMany(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;
|
||||
|
||||
const where: any = { isActive: true };
|
||||
|
||||
// Tambahkan pencarian (jika ada)
|
||||
if (search) {
|
||||
where.OR = [
|
||||
{ pekerjaan: { contains: search, mode: "insensitive" } },
|
||||
];
|
||||
}
|
||||
|
||||
try {
|
||||
const [data, total] = await Promise.all([
|
||||
prisma.dataDemografiPekerjaan.findMany({
|
||||
where,
|
||||
skip,
|
||||
take: limit,
|
||||
orderBy: { createdAt: "desc" },
|
||||
}),
|
||||
prisma.dataDemografiPekerjaan.count({
|
||||
where,
|
||||
}),
|
||||
]);
|
||||
|
||||
export default async function demografiPekerjaanFindMany() {
|
||||
const res = await prisma.dataDemografiPekerjaan.findMany();
|
||||
return {
|
||||
data: res
|
||||
}
|
||||
}
|
||||
success: true,
|
||||
message: "Success fetch demografi pekerjaan with pagination",
|
||||
data,
|
||||
page,
|
||||
totalPages: Math.ceil(total / limit),
|
||||
total,
|
||||
};
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return {
|
||||
success: false,
|
||||
message: "Failed to fetch demografi pekerjaan with pagination",
|
||||
data: null,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,54 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
export default async function grafikJumlahPendudukMiskinFindMany(
|
||||
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;
|
||||
|
||||
const where: any = { isActive: true };
|
||||
|
||||
// Tambahkan pencarian (jika ada)
|
||||
if (search) {
|
||||
const yearSearch = Number(search);
|
||||
|
||||
where.OR = [
|
||||
// kalau search bisa diparse jadi number → cocokin year
|
||||
...(isNaN(yearSearch) ? [] : [{ year: yearSearch }]),
|
||||
];
|
||||
}
|
||||
|
||||
try {
|
||||
const [data, total] = await Promise.all([
|
||||
prisma.grafikJumlahPendudukMiskin.findMany({
|
||||
where,
|
||||
skip,
|
||||
take: limit,
|
||||
orderBy: { createdAt: "desc" },
|
||||
}),
|
||||
prisma.grafikJumlahPendudukMiskin.count({
|
||||
where,
|
||||
}),
|
||||
]);
|
||||
|
||||
export default async function grafikJumlahPendudukMiskinFindMany() {
|
||||
const res = await prisma.grafikJumlahPendudukMiskin.findMany();
|
||||
return {
|
||||
data: res,
|
||||
success: true,
|
||||
message: "Success fetch grafik jumlah penduduk miskin with pagination",
|
||||
data,
|
||||
page,
|
||||
totalPages: Math.ceil(total / limit),
|
||||
total,
|
||||
};
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return {
|
||||
success: false,
|
||||
message: "Failed to fetch grafik jumlah penduduk miskin with pagination",
|
||||
data: null,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,27 +12,33 @@ export default async function detailDataPengangguranFindMany(context: Context) {
|
||||
|
||||
// Tambahkan pencarian (jika ada)
|
||||
if (search) {
|
||||
const yearSearch = Number(search);
|
||||
|
||||
where.OR = [
|
||||
{ year: { contains: search, mode: "insensitive" } },
|
||||
// kalau search bisa diparse jadi number → cocokin year
|
||||
...(isNaN(yearSearch) ? [] : [{ year: yearSearch }]),
|
||||
// selalu cocokin month pakai contains
|
||||
{ month: { contains: search, mode: "insensitive" } },
|
||||
];
|
||||
}
|
||||
try {
|
||||
const monthOrder = ["Jan","Feb","Mar","Apr","Mei","Jun","Jul","Agu","Sep","Okt","Nov","Des"];
|
||||
const [data, total] = await Promise.all([
|
||||
prisma.detailDataPengangguran.findMany({
|
||||
where,
|
||||
skip,
|
||||
take: limit,
|
||||
orderBy: [{ year: "desc" }, { month: "asc" }],
|
||||
orderBy: { year: "asc" }, // urut tahun dulu
|
||||
}),
|
||||
prisma.detailDataPengangguran.count({
|
||||
where,
|
||||
}),
|
||||
]);
|
||||
|
||||
data.sort((a, b) => monthOrder.indexOf(a.month) - monthOrder.indexOf(b.month));
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Success fetch apb desa with pagination",
|
||||
message: "Success fetch detail data pengangguran with pagination",
|
||||
data,
|
||||
page,
|
||||
totalPages: Math.ceil(total / limit),
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Context } from "elysia";
|
||||
type FormCreate = {
|
||||
nama: string;
|
||||
deskripsi: string;
|
||||
ikonUrl?: string; // optional karena boleh null
|
||||
icon: string;
|
||||
statistik?: {
|
||||
tahun: number;
|
||||
jumlah: number;
|
||||
@@ -18,7 +18,7 @@ export default async function programKemiskinanCreate(context: Context) {
|
||||
data: {
|
||||
nama: body.nama,
|
||||
deskripsi: body.deskripsi,
|
||||
ikonUrl: body.ikonUrl,
|
||||
icon: body.icon,
|
||||
statistik: body.statistik
|
||||
? {
|
||||
create: {
|
||||
|
||||
@@ -12,7 +12,7 @@ const ProgramKemiskinan = new Elysia({
|
||||
body: t.Object({
|
||||
nama: t.String(),
|
||||
deskripsi: t.String(),
|
||||
ikonUrl: t.String(),
|
||||
icon: t.String(),
|
||||
statistik: t.Object({
|
||||
tahun: t.String(),
|
||||
jumlah: t.String(),
|
||||
@@ -32,7 +32,7 @@ const ProgramKemiskinan = new Elysia({
|
||||
body: t.Object({
|
||||
nama: t.String(),
|
||||
deskripsi: t.String(),
|
||||
ikonUrl: t.String(),
|
||||
icon: t.String(),
|
||||
statistik: t.Object({
|
||||
tahun: t.String(),
|
||||
jumlah: t.String(),
|
||||
|
||||
@@ -5,6 +5,7 @@ type FormUpdate = {
|
||||
nama: string;
|
||||
deskripsi: string;
|
||||
ikonUrl?: string;
|
||||
icon: string;
|
||||
statistik?: {
|
||||
tahun: number;
|
||||
jumlah: number;
|
||||
@@ -51,7 +52,7 @@ export default async function programKemiskinanUpdate(context: Context) {
|
||||
data: {
|
||||
nama: body.nama,
|
||||
deskripsi: body.deskripsi,
|
||||
ikonUrl: body.ikonUrl,
|
||||
icon: body.icon,
|
||||
statistik: {
|
||||
connect: { id: statistikUpdate.id }, // konek ke statistik baru atau yang diperbarui
|
||||
},
|
||||
|
||||
@@ -1,8 +1,50 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
export default async function sektorUnggulanDesaFindMany(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;
|
||||
|
||||
const where: any = { isActive: true };
|
||||
|
||||
// Tambahkan pencarian (jika ada)
|
||||
if (search) {
|
||||
where.OR = [
|
||||
{ name: { contains: search, mode: "insensitive" } },
|
||||
{ description: { contains: search, mode: "insensitive" } },
|
||||
];
|
||||
}
|
||||
|
||||
try {
|
||||
const [data, total] = await Promise.all([
|
||||
prisma.sektorUnggulanDesa.findMany({
|
||||
where,
|
||||
skip,
|
||||
take: limit,
|
||||
orderBy: { createdAt: "desc" },
|
||||
}),
|
||||
prisma.sektorUnggulanDesa.count({
|
||||
where,
|
||||
}),
|
||||
]);
|
||||
|
||||
export default async function sektorUnggulanDesaFindMany() {
|
||||
const res = await prisma.sektorUnggulanDesa.findMany();
|
||||
return {
|
||||
data: res,
|
||||
success: true,
|
||||
message: "Success fetch sektor unggulan desa with pagination",
|
||||
data,
|
||||
page,
|
||||
totalPages: Math.ceil(total / limit),
|
||||
total,
|
||||
};
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return {
|
||||
success: false,
|
||||
message: "Failed to fetch sektor unggulan desa with pagination",
|
||||
data: null,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,56 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
export default async function grafikMenganggurBerdasarkanUsiaFindMany(
|
||||
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;
|
||||
|
||||
const where: any = { isActive: true };
|
||||
|
||||
// Tambahkan pencarian (jika ada)
|
||||
if (search) {
|
||||
where.OR = [
|
||||
{ usia18_25: { contains: search, mode: "insensitive" } },
|
||||
{ usia26_35: { contains: search, mode: "insensitive" } },
|
||||
{ usia36_45: { contains: search, mode: "insensitive" } },
|
||||
{ usia46_keatas: { contains: search, mode: "insensitive" } },
|
||||
];
|
||||
}
|
||||
|
||||
try {
|
||||
const [data, total] = await Promise.all([
|
||||
prisma.grafikMenganggurBerdasarkanUsia.findMany({
|
||||
where,
|
||||
skip,
|
||||
take: limit,
|
||||
orderBy: { createdAt: "desc" },
|
||||
}),
|
||||
prisma.grafikMenganggurBerdasarkanUsia.count({
|
||||
where,
|
||||
}),
|
||||
]);
|
||||
|
||||
export default async function grafikMenganggurBerdasarkanUsiaFindMany() {
|
||||
const res = await prisma.grafikMenganggurBerdasarkanUsia.findMany();
|
||||
return {
|
||||
data: res
|
||||
}
|
||||
}
|
||||
success: true,
|
||||
message:
|
||||
"Success fetch grafik menganggur berdasarkan usia with pagination",
|
||||
data,
|
||||
page,
|
||||
totalPages: Math.ceil(total / limit),
|
||||
total,
|
||||
};
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return {
|
||||
success: false,
|
||||
message:
|
||||
"Failed to fetch grafik menganggur berdasarkan usia with pagination",
|
||||
data: null,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,53 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
export default async function grafikPengangguranBerdasarkanPendidikanFindMany(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;
|
||||
|
||||
const where: any = { isActive: true };
|
||||
|
||||
// Tambahkan pencarian (jika ada)
|
||||
if (search) {
|
||||
where.OR = [
|
||||
{ SD: { contains: search, mode: "insensitive" } },
|
||||
{ SMP: { contains: search, mode: "insensitive" } },
|
||||
{ SMA: { contains: search, mode: "insensitive" } },
|
||||
{ D3: { contains: search, mode: "insensitive" } },
|
||||
{ S1: { contains: search, mode: "insensitive" } },
|
||||
];
|
||||
}
|
||||
|
||||
try {
|
||||
const [data, total] = await Promise.all([
|
||||
prisma.grafikMenganggurBerdasarkanPendidikan.findMany({
|
||||
where,
|
||||
skip,
|
||||
take: limit,
|
||||
orderBy: { createdAt: "asc" },
|
||||
}),
|
||||
prisma.grafikMenganggurBerdasarkanPendidikan.count({
|
||||
where,
|
||||
}),
|
||||
]);
|
||||
|
||||
export default async function grafikMenganggurBerdasarkanPendidikanFindMany() {
|
||||
const res = await prisma.grafikMenganggurBerdasarkanPendidikan.findMany();
|
||||
return {
|
||||
data: res
|
||||
}
|
||||
}
|
||||
success: true,
|
||||
message: "Success fetch grafik menganggur berdasarkan pendidikan with pagination",
|
||||
data,
|
||||
page,
|
||||
totalPages: Math.ceil(total / limit),
|
||||
total,
|
||||
};
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return {
|
||||
success: false,
|
||||
message: "Failed to fetch grafik menganggur berdasarkan pendidikan with pagination",
|
||||
data: null,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,37 +41,39 @@ function Page() {
|
||||
<Box px={{ base: "md", md: 100 }}>
|
||||
<Stack gap={'lg'} justify='center'>
|
||||
<Paper p={'xl'}>
|
||||
<Text fw={'bold'} fz={'h4'}>Statistik Demografi Pekerjaan Di Desa Darmasaba</Text>
|
||||
<BarChart
|
||||
type='stacked'
|
||||
p={10}
|
||||
mb={50}
|
||||
h={400}
|
||||
w={150}
|
||||
data={data.map((item) => ({
|
||||
id: item.id,
|
||||
Pekerjaan: item.pekerjaan,
|
||||
laki: item.lakiLaki,
|
||||
perempuan: item.perempuan,
|
||||
}))}
|
||||
dataKey="Pekerjaan"
|
||||
series={[
|
||||
{ name: 'laki', color: '#5082EE' },
|
||||
{ name: 'perempuan', color: '#6EDF9C' },
|
||||
]}
|
||||
tickLine="y"
|
||||
xAxisProps={{
|
||||
angle: -45, // Rotate labels by -45 degrees
|
||||
textAnchor: 'end', // Anchor text to the end for better alignment
|
||||
height: 100, // Increase height for rotated labels
|
||||
interval: 0, // Show all labels
|
||||
style: {
|
||||
fontSize: '12px', // Adjust font size if needed
|
||||
overflow: 'visible',
|
||||
whiteSpace: 'nowrap'
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Box style={{overflowX: 'scroll'}}>
|
||||
<Text pb={5} fw={'bold'} fz={'h4'}>Statistik Demografi Pekerjaan Di Desa Darmasaba</Text>
|
||||
<BarChart
|
||||
type='stacked'
|
||||
p={10}
|
||||
mb={50}
|
||||
h={400}
|
||||
w={150}
|
||||
data={data.map((item) => ({
|
||||
id: item.id,
|
||||
Pekerjaan: item.pekerjaan,
|
||||
laki: item.lakiLaki,
|
||||
perempuan: item.perempuan,
|
||||
}))}
|
||||
dataKey="Pekerjaan"
|
||||
series={[
|
||||
{ name: 'laki', color: '#5082EE' },
|
||||
{ name: 'perempuan', color: '#6EDF9C' },
|
||||
]}
|
||||
tickLine="y"
|
||||
xAxisProps={{
|
||||
angle: -45, // Rotate labels by -45 degrees
|
||||
textAnchor: 'end', // Anchor text to the end for better alignment
|
||||
height: 100, // Increase height for rotated labels
|
||||
interval: 0, // Show all labels
|
||||
style: {
|
||||
fontSize: '12px', // Adjust font size if needed
|
||||
overflow: 'visible',
|
||||
whiteSpace: 'nowrap'
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<Flex pb={30} justify={'center'} gap={'xl'} align={'center'}>
|
||||
<Box>
|
||||
<Flex gap={{ base: 0, md: 5 }} align={'center'}>
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Stack, Box, Text, SimpleGrid, Paper, Skeleton, Center, Pagination, Grid, GridCol, TextInput } from '@mantine/core';
|
||||
import React, { useState } from 'react';
|
||||
import BackButton from '../../desa/layanan/_com/BackButto';
|
||||
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 colors from '@/con/colors';
|
||||
import { Box, Center, Grid, GridCol, Pagination, Paper, SimpleGrid, Skeleton, Stack, Text, TextInput } from '@mantine/core';
|
||||
import { useDebouncedValue, useShallowEffect } from '@mantine/hooks';
|
||||
import { IconSearch } from '@tabler/icons-react';
|
||||
import { useState } from 'react';
|
||||
import { CartesianGrid, Line, LineChart as RechartsLineChart, Tooltip, XAxis, YAxis } from 'recharts';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import BackButton from '../../desa/layanan/_com/BackButto';
|
||||
|
||||
interface StatistikData {
|
||||
id: string;
|
||||
tahun: number;
|
||||
jumlah: number;
|
||||
icon: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
@@ -21,7 +22,7 @@ interface ProgramKemiskinanData {
|
||||
id: string;
|
||||
nama: string;
|
||||
deskripsi: string;
|
||||
ikonUrl: string | null;
|
||||
icon: string;
|
||||
statistik: StatistikData | null;
|
||||
isActive: boolean;
|
||||
statistikId: string | null;
|
||||
@@ -34,6 +35,20 @@ function Page() {
|
||||
const [debouncedSearch] = useDebouncedValue(search, 500); // 500ms delay
|
||||
const state = useProxy(programKemiskinanState)
|
||||
|
||||
// 🔧 Get valid statistics data with proper type checking
|
||||
const statistikData = state.findMany.data
|
||||
.filter((item): item is ProgramKemiskinanData & { statistik: StatistikData } => {
|
||||
return !!item?.statistik &&
|
||||
item.statistik.tahun !== undefined &&
|
||||
item.statistik.jumlah !== undefined;
|
||||
})
|
||||
.map(item => ({
|
||||
tahun: Number(item.statistik.tahun) || 0, // Ensure tahun is a number
|
||||
jumlah: Number(item.statistik.jumlah) || 0, // Ensure jumlah is a number
|
||||
}))
|
||||
.sort((a, b) => a.tahun - b.tahun)
|
||||
.filter(item => !isNaN(item.tahun) && !isNaN(item.jumlah)); // Remove any invalid entries
|
||||
|
||||
const {
|
||||
data,
|
||||
page,
|
||||
@@ -88,9 +103,9 @@ function Page() {
|
||||
md: 2
|
||||
}}
|
||||
>
|
||||
{state.findMany.data.map((v, k) => {
|
||||
{state.findMany.data.map(v => {
|
||||
return (
|
||||
<Paper p={'xl'} key={k}>
|
||||
<Paper p={'xl'} key={v.id}>
|
||||
<Text fz={'h3'} fw={'bold'} c={colors['blue-button']}>{v.nama}</Text>
|
||||
<Text fz={'lg'} c={'black'} dangerouslySetInnerHTML={{ __html: v.deskripsi }}></Text>
|
||||
</Paper>
|
||||
@@ -100,7 +115,10 @@ function Page() {
|
||||
<Center my={10}>
|
||||
<Pagination
|
||||
value={page}
|
||||
onChange={(newPage) => load(newPage)}
|
||||
onChange={(newPage) => {
|
||||
load(newPage)
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' })
|
||||
}}
|
||||
total={totalPages}
|
||||
my={"md"}
|
||||
/>
|
||||
@@ -108,44 +126,48 @@ function Page() {
|
||||
<Paper p={'xl'}>
|
||||
<Text fz={'h3'} fw={'bold'} c={colors['blue-button']} mb="md">Statistik Kemiskinan Masyarakat</Text>
|
||||
<Box style={{ width: '100%', height: 'auto' }}>
|
||||
{data.length > 0 && data[0]?.statistik ? (
|
||||
{statistikData.length > 0 ? (
|
||||
<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)
|
||||
}
|
||||
<RechartsLineChart
|
||||
width={Math.min(800, window.innerWidth - 100)}
|
||||
height={400}
|
||||
data={statistikData}
|
||||
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}`}
|
||||
<XAxis
|
||||
dataKey="tahun"
|
||||
label={{ value: 'Tahun', position: 'insideBottomRight', offset: -5 }}
|
||||
/>
|
||||
<Legend />
|
||||
<Line
|
||||
type="monotone"
|
||||
dataKey="jumlah"
|
||||
name="Jumlah Masyarakat Miskin"
|
||||
stroke={colors['blue-button']}
|
||||
activeDot={{ r: 8 }}
|
||||
<YAxis
|
||||
label={{ value: 'Jumlah', angle: -90, position: 'insideLeft' }}
|
||||
domain={[0, 'auto']}
|
||||
/>
|
||||
<Tooltip
|
||||
formatter={(value) => [`${value} orang`, 'Jumlah']}
|
||||
labelFormatter={(label) => `Tahun ${label}`}
|
||||
/>
|
||||
<Line
|
||||
type="monotone"
|
||||
dataKey="jumlah"
|
||||
name="Jumlah"
|
||||
stroke="#228be6"
|
||||
strokeWidth={2}
|
||||
dot={{ r: 4 }}
|
||||
activeDot={{ r: 6 }}
|
||||
/>
|
||||
</RechartsLineChart>
|
||||
</Center>
|
||||
</Box>
|
||||
) : (
|
||||
<Text c="dimmed">Belum ada data statistik yang tersedia</Text>
|
||||
<Box p="md" ta="center" bg="gray.0" style={{ borderRadius: '8px' }}>
|
||||
<Text c="dimmed">
|
||||
{state.findMany.loading
|
||||
? 'Memuat data statistik...'
|
||||
: 'Belum ada data statistik yang tersedia atau data tidak valid'}
|
||||
</Text>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
</Paper>
|
||||
|
||||
Reference in New Issue
Block a user