QC Tampilan Admin & User, Api berfungsi

This commit is contained in:
2025-09-16 16:47:12 +08:00
parent 4ceea5203f
commit 39e1e7b575
48 changed files with 3250 additions and 1916 deletions

View File

@@ -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;

View File

@@ -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>
);