QC Tampilan Admin & User, Api berfungsi
This commit is contained in:
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user