Fix Menu Ekonomi :
Pasar Desa : Kategorinya ga tampil, Bug inputan edit di submenu : Demografi pekerjaa
This commit is contained in:
@@ -1384,6 +1384,7 @@ model LowonganPekerjaan {
|
|||||||
gaji String
|
gaji String
|
||||||
deskripsi String
|
deskripsi String
|
||||||
kualifikasi String
|
kualifikasi String
|
||||||
|
notelp String
|
||||||
tanggalPosting DateTime @default(now())
|
tanggalPosting DateTime @default(now())
|
||||||
isActive Boolean @default(true)
|
isActive Boolean @default(true)
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ const templateForm = z.object({
|
|||||||
gaji: z.string(),
|
gaji: z.string(),
|
||||||
deskripsi: z.string(),
|
deskripsi: z.string(),
|
||||||
kualifikasi: z.string(),
|
kualifikasi: z.string(),
|
||||||
|
notelp: z.string(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const defaultForm = {
|
const defaultForm = {
|
||||||
@@ -23,6 +24,7 @@ const defaultForm = {
|
|||||||
gaji: "",
|
gaji: "",
|
||||||
deskripsi: "",
|
deskripsi: "",
|
||||||
kualifikasi: "",
|
kualifikasi: "",
|
||||||
|
notelp: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
const lowonganKerjaState = proxy({
|
const lowonganKerjaState = proxy({
|
||||||
@@ -179,6 +181,7 @@ const lowonganKerjaState = proxy({
|
|||||||
gaji: data.gaji,
|
gaji: data.gaji,
|
||||||
deskripsi: data.deskripsi,
|
deskripsi: data.deskripsi,
|
||||||
kualifikasi: data.kualifikasi,
|
kualifikasi: data.kualifikasi,
|
||||||
|
notelp: data.notelp,
|
||||||
};
|
};
|
||||||
return data;
|
return data;
|
||||||
} else {
|
} else {
|
||||||
@@ -218,6 +221,7 @@ const lowonganKerjaState = proxy({
|
|||||||
gaji: this.form.gaji,
|
gaji: this.form.gaji,
|
||||||
deskripsi: this.form.deskripsi,
|
deskripsi: this.form.deskripsi,
|
||||||
kualifikasi: this.form.kualifikasi,
|
kualifikasi: this.form.kualifikasi,
|
||||||
|
notelp: this.form.notelp,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import {
|
|||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { IconArrowBack } from '@tabler/icons-react';
|
import { IconArrowBack } from '@tabler/icons-react';
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState, useCallback } from 'react';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import demografiPekerjaan from '../../../_state/ekonomi/demografi-pekerjaan';
|
import demografiPekerjaan from '../../../_state/ekonomi/demografi-pekerjaan';
|
||||||
@@ -25,59 +25,65 @@ interface FormData {
|
|||||||
perempuan: number;
|
perempuan: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
function EditDemografiPekerjaan() {
|
export default function EditDemografiPekerjaan() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const params = useParams() as { id: string };
|
const { id } = useParams() as { id: string };
|
||||||
const stateDemografi = useProxy(demografiPekerjaan);
|
const stateDemografi = useProxy(demografiPekerjaan);
|
||||||
|
|
||||||
const id = params.id;
|
|
||||||
|
|
||||||
const [formData, setFormData] = useState<FormData>({
|
const [formData, setFormData] = useState<FormData>({
|
||||||
pekerjaan: '',
|
pekerjaan: '',
|
||||||
lakiLaki: 0,
|
lakiLaki: 0,
|
||||||
perempuan: 0,
|
perempuan: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Load data sekali waktu
|
// ✅ Load data hanya sekali di awal (tidak reset form)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!id) return;
|
if (!id) return;
|
||||||
stateDemografi.update.id = id;
|
|
||||||
|
|
||||||
stateDemografi.findUnique
|
const loadData = async () => {
|
||||||
.load(id)
|
try {
|
||||||
.then(() => {
|
stateDemografi.update.id = id;
|
||||||
|
await stateDemografi.findUnique.load(id);
|
||||||
|
|
||||||
const data = stateDemografi.findUnique.data;
|
const data = stateDemografi.findUnique.data;
|
||||||
if (data) {
|
if (data) {
|
||||||
setFormData({
|
setFormData({
|
||||||
pekerjaan: String(data.pekerjaan || ''),
|
pekerjaan: data.pekerjaan ?? '',
|
||||||
lakiLaki: Number(data.lakiLaki || 0),
|
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);
|
console.error('Error loading data:', error);
|
||||||
toast.error('Gagal memuat data');
|
toast.error('Gagal memuat data');
|
||||||
});
|
}
|
||||||
}, [id]);
|
|
||||||
|
|
||||||
const handleChange =
|
|
||||||
(field: keyof FormData) =>
|
|
||||||
(e: React.ChangeEvent<HTMLInputElement>) => {
|
|
||||||
setFormData((prev) => ({
|
|
||||||
...prev,
|
|
||||||
[field]:
|
|
||||||
field === 'lakiLaki' || field === 'perempuan'
|
|
||||||
? Number(e.currentTarget.value)
|
|
||||||
: e.currentTarget.value,
|
|
||||||
}));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
loadData();
|
||||||
|
}, [id]);
|
||||||
|
|
||||||
|
// ✅ Handler input terkontrol (tidak buat re-render berlebihan)
|
||||||
|
const handleChange = useCallback(
|
||||||
|
(field: keyof FormData) =>
|
||||||
|
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const value =
|
||||||
|
field === 'lakiLaki' || field === 'perempuan'
|
||||||
|
? Number(e.currentTarget.value)
|
||||||
|
: e.currentTarget.value;
|
||||||
|
|
||||||
|
setFormData((prev) => ({ ...prev, [field]: value }));
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
// ✅ Submit hanya update global state sekali
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
try {
|
try {
|
||||||
stateDemografi.update.id = id;
|
stateDemografi.update.id = id;
|
||||||
stateDemografi.update.form = { ...formData };
|
stateDemografi.update.form = { ...formData };
|
||||||
|
|
||||||
await stateDemografi.update.submit();
|
await stateDemografi.update.submit();
|
||||||
|
|
||||||
toast.success('Data berhasil diperbarui');
|
toast.success('Data berhasil diperbarui');
|
||||||
router.push('/admin/ekonomi/demografi-pekerjaan');
|
router.push('/admin/ekonomi/demografi-pekerjaan');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -160,5 +166,3 @@ function EditDemografiPekerjaan() {
|
|||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default EditDemografiPekerjaan;
|
|
||||||
|
|||||||
@@ -126,9 +126,9 @@ function ListDemografiPekerjaan({ search }: { search: string }) {
|
|||||||
<Table highlightOnHover>
|
<Table highlightOnHover>
|
||||||
<TableThead>
|
<TableThead>
|
||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTh>Pekerjaan</TableTh>
|
<TableTh style={{ minWidth: 200 }}>Pekerjaan</TableTh>
|
||||||
<TableTh>Laki - Laki</TableTh>
|
<TableTh style={{ minWidth: 200 }}>Laki - Laki</TableTh>
|
||||||
<TableTh>Perempuan</TableTh>
|
<TableTh style={{ minWidth: 200 }}>Perempuan</TableTh>
|
||||||
<TableTh>Edit</TableTh>
|
<TableTh>Edit</TableTh>
|
||||||
<TableTh>Hapus</TableTh>
|
<TableTh>Hapus</TableTh>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
@@ -137,9 +137,9 @@ function ListDemografiPekerjaan({ search }: { search: string }) {
|
|||||||
{filteredData.length > 0 ? (
|
{filteredData.length > 0 ? (
|
||||||
filteredData.map((item) => (
|
filteredData.map((item) => (
|
||||||
<TableTr key={item.id}>
|
<TableTr key={item.id}>
|
||||||
<TableTd>{item.pekerjaan}</TableTd>
|
<TableTd style={{ minWidth: 200 }}>{item.pekerjaan}</TableTd>
|
||||||
<TableTd>{item.lakiLaki}</TableTd>
|
<TableTd style={{ minWidth: 200 }}>{item.lakiLaki}</TableTd>
|
||||||
<TableTd>{item.perempuan}</TableTd>
|
<TableTd style={{ minWidth: 200 }}>{item.perempuan}</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Button
|
<Button
|
||||||
variant="light"
|
variant="light"
|
||||||
|
|||||||
@@ -1,23 +1,43 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Center, Group, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title, Tooltip } from '@mantine/core';
|
import {
|
||||||
import { IconEdit, IconSearch, IconTrash } from '@tabler/icons-react';
|
Box,
|
||||||
import HeaderSearch from '../../_com/header';
|
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 { IconEdit, IconSearch, IconTrash, IconPlus } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useShallowEffect, useMediaQuery } from '@mantine/hooks';
|
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import jumlahPendudukMiskin from '../../_state/ekonomi/jumlah-penduduk-miskin';
|
import HeaderSearch from '../../_com/header';
|
||||||
import { Bar, BarChart, Legend, XAxis, YAxis, Tooltip as RechartsTooltip } from 'recharts';
|
|
||||||
import { ModalKonfirmasiHapus } from '../../_com/modalKonfirmasiHapus';
|
import { ModalKonfirmasiHapus } from '../../_com/modalKonfirmasiHapus';
|
||||||
|
import jumlahPendudukMiskin from '../../_state/ekonomi/jumlah-penduduk-miskin';
|
||||||
|
|
||||||
|
// ✅ BarChart Mantine
|
||||||
|
import { BarChart } from '@mantine/charts';
|
||||||
|
|
||||||
function JumlahPendudukMiskin() {
|
function JumlahPendudukMiskin() {
|
||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState("");
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<HeaderSearch
|
<HeaderSearch
|
||||||
title='Jumlah Penduduk Miskin'
|
title="Jumlah Penduduk Miskin"
|
||||||
placeholder='Cari tahun atau jumlah penduduk miskin...'
|
placeholder="Cari tahun atau jumlah penduduk miskin..."
|
||||||
searchIcon={<IconSearch size={20} />}
|
searchIcon={<IconSearch size={20} />}
|
||||||
value={search}
|
value={search}
|
||||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||||
@@ -28,7 +48,7 @@ function JumlahPendudukMiskin() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function ListJumlahPendudukMiskin({ search }: { search: string }) {
|
function ListJumlahPendudukMiskin({ search }: { search: string }) {
|
||||||
type JPMGrafik = { id: string; year: number; totalPoorPopulation: number }
|
type JPMGrafik = { year: number; totalPoorPopulation: number };
|
||||||
const stateJPM = useProxy(jumlahPendudukMiskin);
|
const stateJPM = useProxy(jumlahPendudukMiskin);
|
||||||
const [chartData, setChartData] = useState<JPMGrafik[]>([]);
|
const [chartData, setChartData] = useState<JPMGrafik[]>([]);
|
||||||
const [mounted, setMounted] = useState(false);
|
const [mounted, setMounted] = useState(false);
|
||||||
@@ -36,33 +56,27 @@ function ListJumlahPendudukMiskin({ search }: { search: string }) {
|
|||||||
const [selectedId, setSelectedId] = useState<string | null>(null);
|
const [selectedId, setSelectedId] = useState<string | null>(null);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const isTablet = useMediaQuery('(max-width:1024px)');
|
const { data, page, loading, load, totalPages } = stateJPM.findMany;
|
||||||
const isMobile = useMediaQuery('(max-width:768px)');
|
|
||||||
|
|
||||||
const {
|
// Load data awal
|
||||||
data,
|
|
||||||
page,
|
|
||||||
loading,
|
|
||||||
load,
|
|
||||||
totalPages,
|
|
||||||
} = stateJPM.findMany;
|
|
||||||
// Load data
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
setMounted(true);
|
setMounted(true);
|
||||||
load(page, 10, search);
|
load(page, 10, search);
|
||||||
}, [page, search]);
|
}, [page, search]);
|
||||||
|
|
||||||
|
// Update chart data
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (stateJPM.findMany.data) {
|
if (stateJPM.findMany.data) {
|
||||||
setChartData(stateJPM.findMany.data.map(item => ({
|
setChartData(
|
||||||
id: item.id,
|
stateJPM.findMany.data.map((item) => ({
|
||||||
year: Number(item.year),
|
year: Number(item.year),
|
||||||
totalPoorPopulation: Number(item.totalPoorPopulation)
|
totalPoorPopulation: Number(item.totalPoorPopulation),
|
||||||
})));
|
}))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}, [stateJPM.findMany.data]);
|
}, [stateJPM.findMany.data]);
|
||||||
|
|
||||||
const filteredData = data || []
|
const filteredData = data || [];
|
||||||
|
|
||||||
const handleDelete = () => {
|
const handleDelete = () => {
|
||||||
if (selectedId) {
|
if (selectedId) {
|
||||||
@@ -71,7 +85,7 @@ function ListJumlahPendudukMiskin({ search }: { search: string }) {
|
|||||||
setSelectedId(null);
|
setSelectedId(null);
|
||||||
stateJPM.findMany.load();
|
stateJPM.findMany.load();
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
if (loading || !data) {
|
if (loading || !data) {
|
||||||
return (
|
return (
|
||||||
@@ -83,15 +97,18 @@ function ListJumlahPendudukMiskin({ search }: { search: string }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Box py={10}>
|
||||||
|
{/* Tabel */}
|
||||||
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
|
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
|
||||||
<Group justify="space-between" mb="md">
|
<Group justify="space-between" mb="md">
|
||||||
<Title order={4}>Daftar Jumlah Penduduk Miskin</Title>
|
<Title order={4}>Daftar Jumlah Penduduk Miskin</Title>
|
||||||
<Tooltip label="Tambah Data" withArrow>
|
<Tooltip label="Tambah Data" withArrow>
|
||||||
<Button
|
<Button
|
||||||
leftSection={<IconEdit size={18} />}
|
leftSection={<IconPlus size={18} />}
|
||||||
color="blue"
|
color="blue"
|
||||||
variant="light"
|
variant="light"
|
||||||
onClick={() => router.push('/admin/ekonomi/jumlah-penduduk-miskin/create')}
|
onClick={() =>
|
||||||
|
router.push('/admin/ekonomi/jumlah-penduduk-miskin/create')
|
||||||
|
}
|
||||||
>
|
>
|
||||||
Tambah Baru
|
Tambah Baru
|
||||||
</Button>
|
</Button>
|
||||||
@@ -109,22 +126,38 @@ function ListJumlahPendudukMiskin({ search }: { search: string }) {
|
|||||||
</TableTr>
|
</TableTr>
|
||||||
</TableThead>
|
</TableThead>
|
||||||
<TableTbody>
|
<TableTbody>
|
||||||
{filteredData.length > 0 ? filteredData.map(item => (
|
{filteredData.length > 0 ? (
|
||||||
<TableTr key={item.id}>
|
filteredData.map((item) => (
|
||||||
<TableTd>{item.year}</TableTd>
|
<TableTr key={item.id}>
|
||||||
<TableTd>{item.totalPoorPopulation}</TableTd>
|
<TableTd>{item.year}</TableTd>
|
||||||
<TableTd>
|
<TableTd>{item.totalPoorPopulation}</TableTd>
|
||||||
<Button variant='light' color="green" onClick={() => router.push(`/admin/ekonomi/jumlah-penduduk-miskin/${item.id}`)}>
|
<TableTd>
|
||||||
<IconEdit size={20} />
|
<Button
|
||||||
</Button>
|
variant="light"
|
||||||
</TableTd>
|
color="green"
|
||||||
<TableTd>
|
onClick={() =>
|
||||||
<Button variant='light' color="red" disabled={stateJPM.delete.loading} onClick={() => { setSelectedId(item.id); setModalHapus(true) }}>
|
router.push(`/admin/ekonomi/jumlah-penduduk-miskin/${item.id}`)
|
||||||
<IconTrash size={20} />
|
}
|
||||||
</Button>
|
>
|
||||||
</TableTd>
|
<IconEdit size={20} />
|
||||||
</TableTr>
|
</Button>
|
||||||
)) : (
|
</TableTd>
|
||||||
|
<TableTd>
|
||||||
|
<Button
|
||||||
|
variant="light"
|
||||||
|
color="red"
|
||||||
|
disabled={stateJPM.delete.loading}
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedId(item.id);
|
||||||
|
setModalHapus(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconTrash size={20} />
|
||||||
|
</Button>
|
||||||
|
</TableTd>
|
||||||
|
</TableTr>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTd colSpan={4}>
|
<TableTd colSpan={4}>
|
||||||
<Center py={20}>
|
<Center py={20}>
|
||||||
@@ -138,6 +171,7 @@ function ListJumlahPendudukMiskin({ search }: { search: string }) {
|
|||||||
</Box>
|
</Box>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
|
{/* Pagination */}
|
||||||
<Center>
|
<Center>
|
||||||
<Pagination
|
<Pagination
|
||||||
value={page}
|
value={page}
|
||||||
@@ -153,33 +187,38 @@ function ListJumlahPendudukMiskin({ search }: { search: string }) {
|
|||||||
/>
|
/>
|
||||||
</Center>
|
</Center>
|
||||||
|
|
||||||
{/* Chart */}
|
{/* Bar Chart */}
|
||||||
<Paper bg={colors['white-1']} p="md" mt="lg" withBorder radius="md">
|
<Paper bg={colors['white-1']} p="md" mt="lg" withBorder radius="md">
|
||||||
<Stack>
|
<Stack>
|
||||||
<Box mt="lg" style={{ width: '100%', minHeight: 350 }}>
|
<Title order={4} mb="sm">
|
||||||
<Title order={4} mb="sm">Grafik Jumlah Penduduk Miskin</Title>
|
Grafik Jumlah Penduduk Miskin
|
||||||
{mounted && chartData.length > 0 ? (
|
</Title>
|
||||||
<BarChart width={isMobile ? 450 : isTablet ? 500 : 550} height={350} data={chartData}>
|
{mounted && chartData.length > 0 ? (
|
||||||
<XAxis dataKey="year" />
|
<BarChart
|
||||||
<YAxis />
|
h={300}
|
||||||
<RechartsTooltip />
|
data={chartData.map((item) => ({
|
||||||
<Legend />
|
name: item.year.toString(),
|
||||||
<Bar dataKey="totalPoorPopulation" fill={colors['blue-button']} name="Jumlah Penduduk Miskin" />
|
value: item.totalPoorPopulation,
|
||||||
</BarChart>
|
}))}
|
||||||
) : (
|
dataKey="name"
|
||||||
<Text c="dimmed">Belum ada data untuk ditampilkan dalam grafik</Text>
|
series={[
|
||||||
)}
|
{ name: 'value', color: colors['blue-button'] },
|
||||||
</Box>
|
]}
|
||||||
|
withTooltip
|
||||||
|
valueFormatter={(v) => `${v.toLocaleString()} jiwa`}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Text c="dimmed">Belum ada data untuk ditampilkan dalam grafik</Text>
|
||||||
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
|
|
||||||
{/* Modal Hapus */}
|
{/* Modal Hapus */}
|
||||||
<ModalKonfirmasiHapus
|
<ModalKonfirmasiHapus
|
||||||
opened={modalHapus}
|
opened={modalHapus}
|
||||||
onClose={() => setModalHapus(false)}
|
onClose={() => setModalHapus(false)}
|
||||||
onConfirm={handleDelete}
|
onConfirm={handleDelete}
|
||||||
text='Apakah anda yakin ingin menghapus data ini?'
|
text="Apakah anda yakin ingin menghapus data ini?"
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,24 +1,42 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Center, Flex, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title, Tooltip } 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 { useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconEdit, IconPlus, IconSearch, IconTrash } from '@tabler/icons-react';
|
import { IconEdit, IconPlus, IconSearch, IconTrash } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { Cell, Pie, PieChart } from 'recharts';
|
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import { DonutChart } from '@mantine/charts';
|
||||||
import HeaderSearch from '../../../_com/header';
|
import HeaderSearch from '../../../_com/header';
|
||||||
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
||||||
import grafikNganggur from '../../../_state/ekonomi/usia-kerja-nganggur';
|
import grafikNganggur from '../../../_state/ekonomi/usia-kerja-nganggur';
|
||||||
|
|
||||||
function GrafikBerdasarkanPendidikan() {
|
function GrafikBerdasarkanPendidikan() {
|
||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState('');
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<HeaderSearch
|
<HeaderSearch
|
||||||
title='Detail Data Pengangguran Berdasarkan Pendidikan'
|
title="Detail Data Pengangguran Berdasarkan Pendidikan"
|
||||||
placeholder='Cari data pendidikan...'
|
placeholder="Cari data pendidikan..."
|
||||||
searchIcon={<IconSearch size={20} />}
|
searchIcon={<IconSearch size={20} />}
|
||||||
value={search}
|
value={search}
|
||||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||||
@@ -31,7 +49,6 @@ function GrafikBerdasarkanPendidikan() {
|
|||||||
function ListGrafikBerdasarkanPendidikan({ search }: { search: string }) {
|
function ListGrafikBerdasarkanPendidikan({ search }: { search: string }) {
|
||||||
const stategrafik = useProxy(grafikNganggur.grafikBerdasarkanPendidikan);
|
const stategrafik = useProxy(grafikNganggur.grafikBerdasarkanPendidikan);
|
||||||
const [donutData, setDonutData] = useState<any[]>([]);
|
const [donutData, setDonutData] = useState<any[]>([]);
|
||||||
const [mounted, setMounted] = useState(false);
|
|
||||||
const [modalHapus, setModalHapus] = useState(false);
|
const [modalHapus, setModalHapus] = useState(false);
|
||||||
const [selectedId, setSelectedId] = useState<string | null>(null);
|
const [selectedId, setSelectedId] = useState<string | null>(null);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@@ -45,37 +62,45 @@ function ListGrafikBerdasarkanPendidikan({ search }: { search: string }) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const {
|
const { data, page, totalPages, loading, load } = stategrafik.findMany;
|
||||||
data,
|
|
||||||
page,
|
|
||||||
totalPages,
|
|
||||||
loading,
|
|
||||||
load,
|
|
||||||
} = stategrafik.findMany;
|
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
setMounted(true);
|
|
||||||
load(page, 10, search);
|
load(page, 10, search);
|
||||||
}, [page, search]);
|
}, [page, search]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (stategrafik.findMany.data) {
|
if (stategrafik.findMany.data) {
|
||||||
const SD = stategrafik.findMany.data.reduce((acc: number, cur: any) => acc + Number(cur.SD || 0), 0);
|
const SD = stategrafik.findMany.data.reduce(
|
||||||
const SMP = stategrafik.findMany.data.reduce((acc: number, cur: any) => acc + Number(cur.SMP || 0), 0);
|
(acc: number, cur: any) => acc + Number(cur.SD || 0),
|
||||||
const SMA = stategrafik.findMany.data.reduce((acc: number, cur: any) => acc + Number(cur.SMA || 0), 0);
|
0,
|
||||||
const D3 = stategrafik.findMany.data.reduce((acc: number, cur: any) => acc + Number(cur.D3 || 0), 0);
|
);
|
||||||
const S1 = stategrafik.findMany.data.reduce((acc: number, cur: any) => acc + Number(cur.S1 || 0), 0);
|
const SMP = stategrafik.findMany.data.reduce(
|
||||||
|
(acc: number, cur: any) => acc + Number(cur.SMP || 0),
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
const SMA = stategrafik.findMany.data.reduce(
|
||||||
|
(acc: number, cur: any) => acc + Number(cur.SMA || 0),
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
const D3 = stategrafik.findMany.data.reduce(
|
||||||
|
(acc: number, cur: any) => acc + Number(cur.D3 || 0),
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
const S1 = stategrafik.findMany.data.reduce(
|
||||||
|
(acc: number, cur: any) => acc + Number(cur.S1 || 0),
|
||||||
|
0,
|
||||||
|
);
|
||||||
setDonutData([
|
setDonutData([
|
||||||
{ name: 'SD', value: SD, color: '#4b6Ef5', key: 'SD' },
|
{ name: 'SD', value: SD, color: '#4b6Ef5' },
|
||||||
{ name: 'SMP', value: SMP, color: '#14b885', key: 'SMP' },
|
{ name: 'SMP', value: SMP, color: '#14b885' },
|
||||||
{ name: 'SMA', value: SMA, color: '#E6A03B', key: 'SMA' },
|
{ name: 'SMA', value: SMA, color: '#E6A03B' },
|
||||||
{ name: 'D3', value: D3, color: '#DB524D', key: 'D3' },
|
{ name: 'D3', value: D3, color: '#DB524D' },
|
||||||
{ name: 'S1', value: S1, color: '#1018A8FF', key: 'S1' },
|
{ name: 'S1', value: S1, color: '#1018A8FF' },
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}, [stategrafik.findMany.data]);
|
}, [stategrafik.findMany.data]);
|
||||||
|
|
||||||
const filteredData = data || []
|
const filteredData = data || [];
|
||||||
|
|
||||||
if (loading || !data) {
|
if (loading || !data) {
|
||||||
return (
|
return (
|
||||||
@@ -87,21 +112,26 @@ function ListGrafikBerdasarkanPendidikan({ search }: { search: string }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Box py={10}>
|
||||||
|
{/* Table Data */}
|
||||||
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
|
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
|
||||||
{/* Header */}
|
|
||||||
<Flex justify="space-between" align="center" mb="md">
|
<Flex justify="space-between" align="center" mb="md">
|
||||||
<Title order={4}>List Pengangguran Berdasarkan Usia Kerja</Title>
|
<Title order={4}>List Pengangguran Berdasarkan Pendidikan</Title>
|
||||||
<Tooltip label="Tambah Data" withArrow>
|
<Tooltip label="Tambah Data" withArrow>
|
||||||
<Button
|
<Button
|
||||||
leftSection={<IconPlus size={18} />}
|
leftSection={<IconPlus size={18} />}
|
||||||
color="blue"
|
color="blue"
|
||||||
variant="light"
|
variant="light"
|
||||||
onClick={() => router.push('/admin/ekonomi/jumlah-penduduk-usia-kerja-yang-menganggur/pengangguran_berdasarkan_pendidikan/create')}
|
onClick={() =>
|
||||||
|
router.push(
|
||||||
|
'/admin/ekonomi/jumlah-penduduk-usia-kerja-yang-menganggur/pengangguran_berdasarkan_pendidikan/create',
|
||||||
|
)
|
||||||
|
}
|
||||||
>
|
>
|
||||||
Tambah Baru
|
Tambah Baru
|
||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
<Box style={{ overflowX: 'auto' }}>
|
<Box style={{ overflowX: 'auto' }}>
|
||||||
<Table highlightOnHover>
|
<Table highlightOnHover>
|
||||||
<TableThead>
|
<TableThead>
|
||||||
@@ -120,7 +150,9 @@ function ListGrafikBerdasarkanPendidikan({ search }: { search: string }) {
|
|||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTd colSpan={7}>
|
<TableTd colSpan={7}>
|
||||||
<Center py={20}>
|
<Center py={20}>
|
||||||
<Text color="dimmed">Belum ada data grafik responden</Text>
|
<Text color="dimmed">
|
||||||
|
Belum ada data grafik responden
|
||||||
|
</Text>
|
||||||
</Center>
|
</Center>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
@@ -134,7 +166,15 @@ function ListGrafikBerdasarkanPendidikan({ search }: { search: string }) {
|
|||||||
<TableTd>{item.S1}</TableTd>
|
<TableTd>{item.S1}</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Tooltip label="Edit Data" withArrow>
|
<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}`)}>
|
<Button
|
||||||
|
color="green"
|
||||||
|
variant="light"
|
||||||
|
onClick={() =>
|
||||||
|
router.push(
|
||||||
|
`/admin/ekonomi/jumlah-penduduk-usia-kerja-yang-menganggur/pengangguran_berdasarkan_pendidikan/${item.id}`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
<IconEdit size={18} />
|
<IconEdit size={18} />
|
||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -148,7 +188,8 @@ function ListGrafikBerdasarkanPendidikan({ search }: { search: string }) {
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
setSelectedId(item.id);
|
setSelectedId(item.id);
|
||||||
setModalHapus(true);
|
setModalHapus(true);
|
||||||
}}>
|
}}
|
||||||
|
>
|
||||||
<IconTrash size={18} />
|
<IconTrash size={18} />
|
||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -161,6 +202,7 @@ function ListGrafikBerdasarkanPendidikan({ search }: { search: string }) {
|
|||||||
</Box>
|
</Box>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
|
{/* Pagination */}
|
||||||
<Center>
|
<Center>
|
||||||
<Pagination
|
<Pagination
|
||||||
value={page}
|
value={page}
|
||||||
@@ -176,51 +218,35 @@ function ListGrafikBerdasarkanPendidikan({ search }: { search: string }) {
|
|||||||
/>
|
/>
|
||||||
</Center>
|
</Center>
|
||||||
|
|
||||||
{/* Chart */}
|
{/* Donut Chart */}
|
||||||
<Box mt="md">
|
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md" mt="md">
|
||||||
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
|
<Stack>
|
||||||
<Stack>
|
<Title order={3} pb={10}>
|
||||||
<Title order={3} pb={10}>Grafik Pengangguran Berdasarkan Pendidikan</Title>
|
Grafik Pengangguran Berdasarkan Pendidikan
|
||||||
{mounted && donutData.length > 0 ? (
|
</Title>
|
||||||
<Box style={{ width: '100%', minHeight: 250 }}>
|
{donutData.length > 0 ? (
|
||||||
<PieChart width={800} height={300} data={donutData}>
|
<DonutChart
|
||||||
<Pie
|
data={donutData}
|
||||||
dataKey="value"
|
withLabels
|
||||||
nameKey="name"
|
withTooltip
|
||||||
data={donutData}
|
tooltipDataSource="segment"
|
||||||
cx={400}
|
size={260}
|
||||||
cy={150}
|
thickness={40}
|
||||||
innerRadius={60}
|
/>
|
||||||
outerRadius={115}
|
) : (
|
||||||
label
|
<Text color="dimmed">
|
||||||
>
|
Belum ada data untuk ditampilkan dalam grafik
|
||||||
{donutData.map((entry, index) => (
|
</Text>
|
||||||
<Cell key={`cell-${index}`} fill={entry.color} />
|
)}
|
||||||
))}
|
</Stack>
|
||||||
</Pie>
|
</Paper>
|
||||||
</PieChart>
|
|
||||||
<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 color="dimmed">Belum ada data untuk ditampilkan dalam grafik</Text>
|
|
||||||
)}
|
|
||||||
</Stack>
|
|
||||||
</Paper>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
{/* Modal Konfirmasi Hapus */}
|
{/* Modal Hapus */}
|
||||||
<ModalKonfirmasiHapus
|
<ModalKonfirmasiHapus
|
||||||
opened={modalHapus}
|
opened={modalHapus}
|
||||||
onClose={() => setModalHapus(false)}
|
onClose={() => setModalHapus(false)}
|
||||||
onConfirm={handleDelete}
|
onConfirm={handleDelete}
|
||||||
text='Apakah anda yakin ingin menghapus grafik pengangguran berdasarkan pendidikan ini?'
|
text="Apakah anda yakin ingin menghapus grafik pengangguran berdasarkan pendidikan ini?"
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,13 +1,30 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Center, Flex, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title, Tooltip } from '@mantine/core';
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Center,
|
||||||
|
Flex,
|
||||||
|
Pagination,
|
||||||
|
Paper,
|
||||||
|
Skeleton,
|
||||||
|
Stack,
|
||||||
|
Table,
|
||||||
|
TableTbody,
|
||||||
|
TableTd,
|
||||||
|
TableTh,
|
||||||
|
TableThead,
|
||||||
|
TableTr,
|
||||||
|
Text,
|
||||||
|
Title,
|
||||||
|
} from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconEdit, IconPlus, IconSearch, IconTrash } from '@tabler/icons-react';
|
import { IconEdit, IconPlus, IconSearch, IconTrash } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { Cell, Pie, PieChart } from 'recharts';
|
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
|
import { DonutChart } from '@mantine/charts';
|
||||||
import HeaderSearch from '../../../_com/header';
|
import HeaderSearch from '../../../_com/header';
|
||||||
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
||||||
import grafikNganggur from '../../../_state/ekonomi/usia-kerja-nganggur';
|
import grafikNganggur from '../../../_state/ekonomi/usia-kerja-nganggur';
|
||||||
@@ -17,8 +34,8 @@ function GrafikBerdasarkanUsiaKerjaYangMenganggur() {
|
|||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<HeaderSearch
|
<HeaderSearch
|
||||||
title='Detail Data Pengangguran'
|
title="Detail Data Pengangguran"
|
||||||
placeholder='Cari usia...'
|
placeholder="Cari usia..."
|
||||||
searchIcon={<IconSearch size={20} />}
|
searchIcon={<IconSearch size={20} />}
|
||||||
value={search}
|
value={search}
|
||||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||||
@@ -31,7 +48,6 @@ function GrafikBerdasarkanUsiaKerjaYangMenganggur() {
|
|||||||
function ListGrafikBerdasarkanUsiaKerjaYangMenganggur({ search }: { search: string }) {
|
function ListGrafikBerdasarkanUsiaKerjaYangMenganggur({ search }: { search: string }) {
|
||||||
const stategrafik = useProxy(grafikNganggur.grafikBerdasarkanUsiaKerjaNganggur);
|
const stategrafik = useProxy(grafikNganggur.grafikBerdasarkanUsiaKerjaNganggur);
|
||||||
const [donutData, setDonutData] = useState<any[]>([]);
|
const [donutData, setDonutData] = useState<any[]>([]);
|
||||||
const [mounted, setMounted] = useState(false);
|
|
||||||
const [modalHapus, setModalHapus] = useState(false);
|
const [modalHapus, setModalHapus] = useState(false);
|
||||||
const [selectedId, setSelectedId] = useState<string | null>(null);
|
const [selectedId, setSelectedId] = useState<string | null>(null);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@@ -45,17 +61,10 @@ function ListGrafikBerdasarkanUsiaKerjaYangMenganggur({ search }: { search: stri
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const {
|
const { data, page, totalPages, loading, load } = stategrafik.findMany;
|
||||||
data,
|
|
||||||
page,
|
|
||||||
totalPages,
|
|
||||||
loading,
|
|
||||||
load,
|
|
||||||
} = stategrafik.findMany;
|
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
setMounted(true);
|
load(page, 10, search);
|
||||||
load(page, 10, search)
|
|
||||||
}, [page, search]);
|
}, [page, search]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -64,16 +73,17 @@ function ListGrafikBerdasarkanUsiaKerjaYangMenganggur({ search }: { search: stri
|
|||||||
const totalUsia26_35 = stategrafik.findMany.data.reduce((acc: number, cur: any) => acc + Number(cur.usia26_35 || 0), 0);
|
const totalUsia26_35 = stategrafik.findMany.data.reduce((acc: number, cur: any) => acc + Number(cur.usia26_35 || 0), 0);
|
||||||
const totalUsia36_45 = stategrafik.findMany.data.reduce((acc: number, cur: any) => acc + Number(cur.usia36_45 || 0), 0);
|
const totalUsia36_45 = stategrafik.findMany.data.reduce((acc: number, cur: any) => acc + Number(cur.usia36_45 || 0), 0);
|
||||||
const totalUsia46_keatas = stategrafik.findMany.data.reduce((acc: number, cur: any) => acc + Number(cur.usia46_keatas || 0), 0);
|
const totalUsia46_keatas = stategrafik.findMany.data.reduce((acc: number, cur: any) => acc + Number(cur.usia46_keatas || 0), 0);
|
||||||
|
|
||||||
setDonutData([
|
setDonutData([
|
||||||
{ name: 'usia18_25', value: totalUsia18_25, color: colors['blue-button'], key: 'usia18_25' },
|
{ name: 'Usia 18-25', value: totalUsia18_25, color: colors['blue-button'] },
|
||||||
{ name: 'usia26_35', value: totalUsia26_35, color: '#10A85AFF', key: 'usia26_35' },
|
{ name: 'Usia 26-35', value: totalUsia26_35, color: '#10A85AFF' },
|
||||||
{ name: 'usia36_45', value: totalUsia36_45, color: '#C07B13FF', key: 'usia36_45' },
|
{ name: 'Usia 36-45', value: totalUsia36_45, color: '#C07B13FF' },
|
||||||
{ name: 'usia46_keatas', value: totalUsia46_keatas, color: '#1094A8FF', key: 'usia46_keatas' },
|
{ name: 'Usia 46+', value: totalUsia46_keatas, color: '#1094A8FF' },
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}, [stategrafik.findMany.data]);
|
}, [stategrafik.findMany.data]);
|
||||||
|
|
||||||
const filteredData = data || []
|
const filteredData = data || [];
|
||||||
|
|
||||||
if (loading || !data) {
|
if (loading || !data) {
|
||||||
return (
|
return (
|
||||||
@@ -85,24 +95,23 @@ function ListGrafikBerdasarkanUsiaKerjaYangMenganggur({ search }: { search: stri
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Box py={10}>
|
||||||
|
{/* Table */}
|
||||||
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
|
<Paper withBorder bg={colors['white-1']} p="lg" shadow="md" radius="md">
|
||||||
<Stack>
|
<Stack>
|
||||||
{/* Header */}
|
|
||||||
<Flex justify="space-between" align="center" mb="md">
|
<Flex justify="space-between" align="center" mb="md">
|
||||||
<Title order={4}>List Pengangguran Berdasarkan Usia Kerja</Title>
|
<Title order={4}>List Pengangguran Berdasarkan Usia Kerja</Title>
|
||||||
<Tooltip label="Tambah Data" withArrow>
|
<Button
|
||||||
<Button
|
leftSection={<IconPlus size={18} />}
|
||||||
leftSection={<IconPlus size={18} />}
|
color="blue"
|
||||||
color="blue"
|
variant="light"
|
||||||
variant="light"
|
onClick={() =>
|
||||||
onClick={() => router.push('/admin/ekonomi/jumlah-penduduk-usia-kerja-yang-menganggur/pengangguran_berdasarkan_usia/create')}
|
router.push('/admin/ekonomi/jumlah-penduduk-usia-kerja-yang-menganggur/pengangguran_berdasarkan_usia/create')
|
||||||
>
|
}
|
||||||
Tambah Baru
|
>
|
||||||
</Button>
|
Tambah Baru
|
||||||
</Tooltip>
|
</Button>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
{/* Table */}
|
|
||||||
<Box style={{ overflowX: 'auto' }}>
|
<Box style={{ overflowX: 'auto' }}>
|
||||||
<Table highlightOnHover>
|
<Table highlightOnHover>
|
||||||
<TableThead>
|
<TableThead>
|
||||||
@@ -110,26 +119,38 @@ function ListGrafikBerdasarkanUsiaKerjaYangMenganggur({ search }: { search: stri
|
|||||||
<TableTh>Usia 18-25</TableTh>
|
<TableTh>Usia 18-25</TableTh>
|
||||||
<TableTh>Usia 26-35</TableTh>
|
<TableTh>Usia 26-35</TableTh>
|
||||||
<TableTh>Usia 36-45</TableTh>
|
<TableTh>Usia 36-45</TableTh>
|
||||||
<TableTh>Usia 46 +</TableTh>
|
<TableTh>Usia 46+</TableTh>
|
||||||
<TableTh>Edit</TableTh>
|
<TableTh>Edit</TableTh>
|
||||||
<TableTh>Delete</TableTh>
|
<TableTh>Delete</TableTh>
|
||||||
</TableTr>
|
</TableTr>
|
||||||
</TableThead>
|
</TableThead>
|
||||||
<TableTbody>
|
<TableTbody>
|
||||||
{filteredData.length > 0 ? (
|
{filteredData.length > 0 ? (
|
||||||
filteredData.map(item => (
|
filteredData.map((item) => (
|
||||||
<TableTr key={item.id}>
|
<TableTr key={item.id}>
|
||||||
<TableTd>{item.usia18_25}</TableTd>
|
<TableTd>{item.usia18_25}</TableTd>
|
||||||
<TableTd>{item.usia26_35}</TableTd>
|
<TableTd>{item.usia26_35}</TableTd>
|
||||||
<TableTd>{item.usia36_45}</TableTd>
|
<TableTd>{item.usia36_45}</TableTd>
|
||||||
<TableTd>{item.usia46_keatas}</TableTd>
|
<TableTd>{item.usia46_keatas}</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Button color="green" onClick={() => router.push(`/admin/ekonomi/jumlah-penduduk-usia-kerja-yang-menganggur/pengangguran_berdasarkan_usia/${item.id}`)}>
|
<Button
|
||||||
|
color="green"
|
||||||
|
onClick={() =>
|
||||||
|
router.push(`/admin/ekonomi/jumlah-penduduk-usia-kerja-yang-menganggur/pengangguran_berdasarkan_usia/${item.id}`)
|
||||||
|
}
|
||||||
|
>
|
||||||
<IconEdit size={20} />
|
<IconEdit size={20} />
|
||||||
</Button>
|
</Button>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Button color="red" disabled={stategrafik.delete.loading} onClick={() => { setSelectedId(item.id); setModalHapus(true); }}>
|
<Button
|
||||||
|
color="red"
|
||||||
|
disabled={stategrafik.delete.loading}
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedId(item.id);
|
||||||
|
setModalHapus(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
<IconTrash size={20} />
|
<IconTrash size={20} />
|
||||||
</Button>
|
</Button>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
@@ -147,10 +168,10 @@ function ListGrafikBerdasarkanUsiaKerjaYangMenganggur({ search }: { search: stri
|
|||||||
</TableTbody>
|
</TableTbody>
|
||||||
</Table>
|
</Table>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
|
{/* Pagination */}
|
||||||
<Center>
|
<Center>
|
||||||
<Pagination
|
<Pagination
|
||||||
value={page}
|
value={page}
|
||||||
@@ -166,48 +187,29 @@ function ListGrafikBerdasarkanUsiaKerjaYangMenganggur({ search }: { search: stri
|
|||||||
/>
|
/>
|
||||||
</Center>
|
</Center>
|
||||||
|
|
||||||
{/* Chart */}
|
{/* Donut Chart */}
|
||||||
<Paper bg={colors['white-1']} p="md" mt="lg" withBorder radius="md">
|
<Paper bg={colors['white-1']} p="md" mt="lg" withBorder radius="md">
|
||||||
<Stack>
|
<Stack>
|
||||||
<Title order={3} pb={10}>Grafik Pengangguran Berdasarkan Usia Kerja</Title>
|
<Title order={3} pb={10}>
|
||||||
{mounted && donutData.length > 0 ? (
|
Grafik Pengangguran Berdasarkan Usia Kerja
|
||||||
<Box style={{ width: '100%', height: 'auto', minHeight: 200 }}>
|
</Title>
|
||||||
<PieChart width={800} height={300} data={donutData}>
|
{donutData.length > 0 ? (
|
||||||
<Pie dataKey="value" nameKey="name" data={donutData} cx={400} cy={150} innerRadius={60} outerRadius={115} label>
|
<Center>
|
||||||
{donutData.map((entry, index) => (
|
<DonutChart
|
||||||
<Cell key={`cell-${index}`} fill={entry.color} />
|
data={donutData}
|
||||||
))}
|
withLabels
|
||||||
</Pie>
|
withTooltip
|
||||||
</PieChart>
|
size={200}
|
||||||
<Stack mt="sm" gap="xs">
|
thickness={40}
|
||||||
<Flex gap={"md"} align={"center"}>
|
tooltipDataSource="segment"
|
||||||
<Box bg={colors['blue-button']} w={20} h={20} />
|
/>
|
||||||
<Text>Usia 18-25 : {donutData.find((entry) => entry.name === 'usia18_25')?.value}</Text>
|
</Center>
|
||||||
</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>
|
|
||||||
</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>
|
|
||||||
</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>
|
|
||||||
</Flex>
|
|
||||||
</Stack>
|
|
||||||
</Box>
|
|
||||||
) : (
|
) : (
|
||||||
<Text c="dimmed">Belum ada data untuk ditampilkan dalam grafik</Text>
|
<Text c="dimmed">Belum ada data untuk ditampilkan dalam grafik</Text>
|
||||||
)}
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
|
|
||||||
{/* Modal Konfirmasi Hapus */}
|
{/* Modal Konfirmasi Hapus */}
|
||||||
<ModalKonfirmasiHapus
|
<ModalKonfirmasiHapus
|
||||||
opened={modalHapus}
|
opened={modalHapus}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import jumlahPengangguranState from '@/app/admin/(dashboard)/_state/ekonomi/jumlah-pengangguran';
|
import jumlahPengangguranState from '@/app/admin/(dashboard)/_state/ekonomi/jumlah-pengangguran';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import {
|
import {
|
||||||
@@ -20,11 +21,18 @@ import { useEffect, useState, useCallback } from 'react';
|
|||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
|
|
||||||
|
// --- Helper konstanta
|
||||||
|
const MONTHS = [
|
||||||
|
'Jan', 'Feb', 'Mar', 'Apr', 'Mei', 'Jun',
|
||||||
|
'Jul', 'Agu', 'Sep', 'Okt', 'Nov', 'Des',
|
||||||
|
];
|
||||||
|
|
||||||
function EditDetailDataPengangguran() {
|
function EditDetailDataPengangguran() {
|
||||||
const stateDetail = useProxy(jumlahPengangguranState.jumlahPengangguran);
|
const stateDetail = useProxy(jumlahPengangguranState.jumlahPengangguran);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
|
|
||||||
|
// --- state lokal form
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
month: '',
|
month: '',
|
||||||
year: new Date().getFullYear(),
|
year: new Date().getFullYear(),
|
||||||
@@ -34,18 +42,13 @@ function EditDetailDataPengangguran() {
|
|||||||
percentageChange: 0,
|
percentageChange: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Hitung total & perubahan otomatis
|
// --- hitung total + persentase perubahan
|
||||||
const calculateTotalAndChange = useCallback(
|
const calculateTotalAndChange = useCallback(
|
||||||
async (data: typeof formData) => {
|
async (data: typeof formData) => {
|
||||||
const total = data.educatedUnemployment + data.uneducatedUnemployment;
|
const total = data.educatedUnemployment + data.uneducatedUnemployment;
|
||||||
|
|
||||||
let percentageChange = 0;
|
let percentageChange = 0;
|
||||||
const monthOrder = [
|
|
||||||
'Jan', 'Feb', 'Mar', 'Apr', 'Mei', 'Jun',
|
|
||||||
'Jul', 'Agu', 'Sep', 'Okt', 'Nov', 'Des',
|
|
||||||
];
|
|
||||||
const currentMonthIndex = monthOrder.indexOf(data.month);
|
|
||||||
|
|
||||||
|
const currentMonthIndex = MONTHS.indexOf(data.month);
|
||||||
if (currentMonthIndex !== -1) {
|
if (currentMonthIndex !== -1) {
|
||||||
let prevMonthIndex = currentMonthIndex - 1;
|
let prevMonthIndex = currentMonthIndex - 1;
|
||||||
let prevYear = data.year;
|
let prevYear = data.year;
|
||||||
@@ -55,17 +58,15 @@ function EditDetailDataPengangguran() {
|
|||||||
prevYear--;
|
prevYear--;
|
||||||
}
|
}
|
||||||
|
|
||||||
const prevMonth = monthOrder[prevMonthIndex];
|
|
||||||
const prevData = await stateDetail.findByMonthYear.load({
|
const prevData = await stateDetail.findByMonthYear.load({
|
||||||
month: prevMonth,
|
month: MONTHS[prevMonthIndex],
|
||||||
year: prevYear,
|
year: prevYear,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (prevData && prevData.totalUnemployment > 0) {
|
if (prevData && prevData.totalUnemployment > 0) {
|
||||||
const change =
|
const change =
|
||||||
((total - prevData.totalUnemployment) /
|
((total - prevData.totalUnemployment) /
|
||||||
prevData.totalUnemployment) *
|
prevData.totalUnemployment) * 100;
|
||||||
100;
|
|
||||||
percentageChange = parseFloat(change.toFixed(1));
|
percentageChange = parseFloat(change.toFixed(1));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -75,67 +76,66 @@ function EditDetailDataPengangguran() {
|
|||||||
[stateDetail.findByMonthYear]
|
[stateDetail.findByMonthYear]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// --- update state lokal
|
||||||
const updateFormData = async (updates: Partial<typeof formData>) => {
|
const updateFormData = async (updates: Partial<typeof formData>) => {
|
||||||
const newData = { ...formData, ...updates };
|
const newData = { ...formData, ...updates };
|
||||||
const { total, percentageChange } = await calculateTotalAndChange(newData);
|
const { total, percentageChange } = await calculateTotalAndChange(newData);
|
||||||
setFormData({
|
setFormData({ ...newData, totalUnemployment: total, percentageChange });
|
||||||
...newData,
|
|
||||||
totalUnemployment: total,
|
|
||||||
percentageChange,
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Load detail hanya sekali
|
// --- load detail by ID (sekali)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadDetail = async () => {
|
const loadDetail = async () => {
|
||||||
const id = params?.id as string;
|
const id = params?.id as string;
|
||||||
if (!id) return;
|
if (!id) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await stateDetail.findUnique.load(id); // ambil by ID
|
await stateDetail.findUnique.load(id);
|
||||||
const data = stateDetail.findUnique.data;
|
const data = stateDetail.findUnique.data;
|
||||||
|
if (!data) return;
|
||||||
|
|
||||||
if (data) {
|
const yearValue =
|
||||||
const yearValue =
|
data.year && typeof data.year === 'object' && 'getFullYear' in data.year
|
||||||
data.year && typeof data.year === 'object' && 'getFullYear' in data.year
|
? (data.year as Date).getFullYear()
|
||||||
? (data.year as Date).getFullYear()
|
: Number(data.year);
|
||||||
: Number(data.year);
|
|
||||||
|
|
||||||
stateDetail.update.id = id; // set ID untuk update
|
stateDetail.update.id = id; // simpan id untuk update
|
||||||
|
|
||||||
setFormData({
|
setFormData({
|
||||||
month: data.month,
|
month: data.month,
|
||||||
year: yearValue,
|
year: yearValue,
|
||||||
totalUnemployment: data.totalUnemployment,
|
educatedUnemployment: data.educatedUnemployment,
|
||||||
educatedUnemployment: data.educatedUnemployment,
|
uneducatedUnemployment: data.uneducatedUnemployment,
|
||||||
uneducatedUnemployment: data.uneducatedUnemployment,
|
totalUnemployment: data.totalUnemployment,
|
||||||
percentageChange: data.percentageChange || 0,
|
percentageChange: data.percentageChange || 0,
|
||||||
});
|
});
|
||||||
}
|
} catch (err) {
|
||||||
} catch (error) {
|
console.error('Error loading detail:', err);
|
||||||
console.error('Error loading detail:', error);
|
|
||||||
toast.error('Gagal memuat data detail');
|
toast.error('Gagal memuat data detail');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
loadDetail();
|
loadDetail();
|
||||||
}, [params?.id, stateDetail.findUnique]);
|
}, [params?.id]);
|
||||||
|
|
||||||
|
// --- submit form
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
const { total, percentageChange } = await calculateTotalAndChange(formData);
|
|
||||||
try {
|
try {
|
||||||
|
const { total, percentageChange } = await calculateTotalAndChange(formData);
|
||||||
|
|
||||||
stateDetail.update.form = {
|
stateDetail.update.form = {
|
||||||
...formData,
|
...formData,
|
||||||
totalUnemployment: total,
|
totalUnemployment: total,
|
||||||
percentageChange,
|
percentageChange,
|
||||||
};
|
};
|
||||||
|
|
||||||
const success = await stateDetail.update.submit();
|
const success = await stateDetail.update.submit();
|
||||||
if (success) {
|
if (success) {
|
||||||
toast.success('Detail data pengangguran berhasil diperbarui!');
|
toast.success('Detail data pengangguran berhasil diperbarui!');
|
||||||
router.push('/admin/ekonomi/jumlah-pengangguran');
|
router.push('/admin/ekonomi/jumlah-pengangguran');
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (err) {
|
||||||
console.error('Error updating:', error);
|
console.error('Error updating:', err);
|
||||||
toast.error('Terjadi kesalahan saat memperbarui data');
|
toast.error('Terjadi kesalahan saat memperbarui data');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -143,12 +143,7 @@ function EditDetailDataPengangguran() {
|
|||||||
return (
|
return (
|
||||||
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
<Box px={{ base: 'sm', md: 'lg' }} py="md">
|
||||||
<Group mb="md">
|
<Group mb="md">
|
||||||
<Button
|
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||||
variant="subtle"
|
|
||||||
onClick={() => router.back()}
|
|
||||||
p="xs"
|
|
||||||
radius="md"
|
|
||||||
>
|
|
||||||
<IconArrowBack color={colors['blue-button']} size={24} />
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
</Button>
|
</Button>
|
||||||
<Title order={4} ml="sm">
|
<Title order={4} ml="sm">
|
||||||
@@ -167,10 +162,7 @@ function EditDetailDataPengangguran() {
|
|||||||
<Stack gap="md">
|
<Stack gap="md">
|
||||||
<Select
|
<Select
|
||||||
label="Bulan"
|
label="Bulan"
|
||||||
data={[
|
data={MONTHS}
|
||||||
'Jan','Feb','Mar','Apr','Mei','Jun',
|
|
||||||
'Jul','Agu','Sep','Okt','Nov','Des',
|
|
||||||
]}
|
|
||||||
value={formData.month}
|
value={formData.month}
|
||||||
onChange={(val) => updateFormData({ month: val || '' })}
|
onChange={(val) => updateFormData({ month: val || '' })}
|
||||||
/>
|
/>
|
||||||
@@ -184,8 +176,10 @@ function EditDetailDataPengangguran() {
|
|||||||
label="Pengangguran Terdidik"
|
label="Pengangguran Terdidik"
|
||||||
type="number"
|
type="number"
|
||||||
value={formData.educatedUnemployment}
|
value={formData.educatedUnemployment}
|
||||||
onChange={(val) =>
|
onChange={(e) =>
|
||||||
updateFormData({ educatedUnemployment: Number(val.currentTarget.value) || 0 })
|
updateFormData({
|
||||||
|
educatedUnemployment: Number(e.currentTarget.value) || 0,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
@@ -193,8 +187,10 @@ function EditDetailDataPengangguran() {
|
|||||||
label="Pengangguran Tidak Terdidik"
|
label="Pengangguran Tidak Terdidik"
|
||||||
type="number"
|
type="number"
|
||||||
value={formData.uneducatedUnemployment}
|
value={formData.uneducatedUnemployment}
|
||||||
onChange={(val) =>
|
onChange={(e) =>
|
||||||
updateFormData({ uneducatedUnemployment: Number(val.currentTarget.value) || 0 })
|
updateFormData({
|
||||||
|
uneducatedUnemployment: Number(e.currentTarget.value) || 0,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ function EditLowonganKerja() {
|
|||||||
gaji: '',
|
gaji: '',
|
||||||
deskripsi: '',
|
deskripsi: '',
|
||||||
kualifikasi: '',
|
kualifikasi: '',
|
||||||
|
notelp: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
// load data sekali aja ketika mount / id berubah
|
// load data sekali aja ketika mount / id berubah
|
||||||
@@ -52,6 +53,7 @@ function EditLowonganKerja() {
|
|||||||
gaji: data.gaji || '',
|
gaji: data.gaji || '',
|
||||||
deskripsi: data.deskripsi || '',
|
deskripsi: data.deskripsi || '',
|
||||||
kualifikasi: data.kualifikasi || '',
|
kualifikasi: data.kualifikasi || '',
|
||||||
|
notelp: data.notelp || '',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -132,6 +134,14 @@ function EditLowonganKerja() {
|
|||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<TextInput
|
||||||
|
label="Nomor Yang Dapat Dihubungi"
|
||||||
|
placeholder="Masukkan nomor yang dapat dihubungi"
|
||||||
|
value={formData.notelp}
|
||||||
|
onChange={(e) => handleChange("notelp", e.target.value)}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
|
||||||
<TextInput
|
<TextInput
|
||||||
label="Tipe Pekerjaan"
|
label="Tipe Pekerjaan"
|
||||||
placeholder="Masukkan tipe pekerjaan"
|
placeholder="Masukkan tipe pekerjaan"
|
||||||
|
|||||||
@@ -82,6 +82,11 @@ function DetailLowonganKerjaLokal() {
|
|||||||
<Text fz="md" c="dimmed">{data.lokasi || '-'}</Text>
|
<Text fz="md" c="dimmed">{data.lokasi || '-'}</Text>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<Text fz="lg" fw="bold">Nomor Yang Dapat Dihubungi</Text>
|
||||||
|
<Text fz="md" c="dimmed">{data.notelp || '-'}</Text>
|
||||||
|
</Box>
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<Text fz="lg" fw="bold">Tipe Pekerjaan</Text>
|
<Text fz="lg" fw="bold">Tipe Pekerjaan</Text>
|
||||||
<Text fz="md" c="dimmed">{data.tipePekerjaan || '-'}</Text>
|
<Text fz="md" c="dimmed">{data.tipePekerjaan || '-'}</Text>
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ function CreateLowonganKerja() {
|
|||||||
gaji: '',
|
gaji: '',
|
||||||
deskripsi: '',
|
deskripsi: '',
|
||||||
kualifikasi: '',
|
kualifikasi: '',
|
||||||
|
notelp: '',
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -86,6 +87,15 @@ function CreateLowonganKerja() {
|
|||||||
placeholder="Masukkan nama perusahaan"
|
placeholder="Masukkan nama perusahaan"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
|
<TextInput
|
||||||
|
defaultValue={lowonganState.create.form.notelp}
|
||||||
|
onChange={(val) =>
|
||||||
|
(lowonganState.create.form.notelp = val.target.value)
|
||||||
|
}
|
||||||
|
label="Nomor Yang Dapat Dihubungi"
|
||||||
|
placeholder="Masukkan nomor yang dapat dihubungi"
|
||||||
|
required
|
||||||
|
/>
|
||||||
<TextInput
|
<TextInput
|
||||||
defaultValue={lowonganState.create.form.lokasi}
|
defaultValue={lowonganState.create.form.lokasi}
|
||||||
onChange={(val) =>
|
onChange={(val) =>
|
||||||
|
|||||||
@@ -18,84 +18,90 @@ import {
|
|||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { IconArrowBack } from '@tabler/icons-react';
|
import { IconArrowBack } from '@tabler/icons-react';
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState, useCallback } from 'react';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
|
type Statistik = {
|
||||||
|
tahun: string;
|
||||||
|
jumlah: string;
|
||||||
|
};
|
||||||
|
|
||||||
type FormData = {
|
type FormData = {
|
||||||
nama: string;
|
nama: string;
|
||||||
deskripsi: string;
|
deskripsi: string;
|
||||||
icon: string;
|
icon: string;
|
||||||
|
statistik: Statistik;
|
||||||
|
};
|
||||||
|
|
||||||
|
const initialForm: FormData = {
|
||||||
|
nama: '',
|
||||||
|
deskripsi: '',
|
||||||
|
icon: '',
|
||||||
statistik: {
|
statistik: {
|
||||||
tahun: string;
|
tahun: '',
|
||||||
jumlah: string;
|
jumlah: '',
|
||||||
};
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
function EditProgramKemiskinan() {
|
function EditProgramKemiskinan() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const params = useParams() as { id: string };
|
const { id } = useParams() as { id: string };
|
||||||
const stateProgram = useProxy(programKemiskinanState);
|
const stateProgram = useProxy(programKemiskinanState);
|
||||||
const id = params.id;
|
|
||||||
|
|
||||||
const [formData, setFormData] = useState<FormData>({
|
const [formData, setFormData] = useState<FormData>(initialForm);
|
||||||
nama: '',
|
|
||||||
deskripsi: '',
|
|
||||||
icon: '',
|
|
||||||
statistik: {
|
|
||||||
tahun: '',
|
|
||||||
jumlah: '',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// load data ke local state sekali aja
|
// Load data 1x dari global state → isi local state
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (id) {
|
if (!id) return;
|
||||||
stateProgram.findUnique
|
|
||||||
.load(id)
|
stateProgram.findUnique
|
||||||
.then(() => {
|
.load(id)
|
||||||
const data = stateProgram.findUnique.data;
|
.then(() => {
|
||||||
if (data) {
|
const data = stateProgram.findUnique.data;
|
||||||
setFormData({
|
if (data) {
|
||||||
nama: data.nama || '',
|
setFormData({
|
||||||
deskripsi: data.deskripsi || '',
|
nama: data.nama ?? '',
|
||||||
icon: data.icon || '',
|
deskripsi: data.deskripsi ?? '',
|
||||||
statistik: {
|
icon: data.icon ?? '',
|
||||||
tahun: data.statistik?.tahun?.toString() || '',
|
statistik: {
|
||||||
jumlah: data.statistik?.jumlah?.toString() || '',
|
tahun: data.statistik?.tahun?.toString() ?? '',
|
||||||
},
|
jumlah: data.statistik?.jumlah?.toString() ?? '',
|
||||||
});
|
},
|
||||||
}
|
});
|
||||||
})
|
}
|
||||||
.catch((err) => {
|
})
|
||||||
console.error('Error load data:', err);
|
.catch((err) => {
|
||||||
toast.error('Gagal mengambil data program');
|
console.error('Error load data:', err);
|
||||||
});
|
toast.error('Gagal mengambil data program');
|
||||||
}
|
});
|
||||||
}, [id, stateProgram.findUnique]);
|
}, [id, stateProgram.findUnique]);
|
||||||
|
|
||||||
const handleChange = (field: keyof FormData, value: string) => {
|
// generic handler untuk field top-level
|
||||||
setFormData((prev) => ({
|
const handleChange = useCallback(
|
||||||
...prev,
|
(field: keyof FormData, value: string) => {
|
||||||
[field]: value,
|
setFormData((prev) => ({ ...prev, [field]: value }));
|
||||||
}));
|
},
|
||||||
};
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
const handleStatistikChange = (field: keyof FormData['statistik'], value: string) => {
|
// khusus nested statistik
|
||||||
setFormData((prev) => ({
|
const handleStatistikChange = useCallback(
|
||||||
...prev,
|
(field: keyof Statistik, value: string) => {
|
||||||
statistik: {
|
setFormData((prev) => ({
|
||||||
...prev.statistik,
|
...prev,
|
||||||
[field]: value,
|
statistik: { ...prev.statistik, [field]: value },
|
||||||
},
|
}));
|
||||||
}));
|
},
|
||||||
};
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
try {
|
try {
|
||||||
stateProgram.update.id = id;
|
stateProgram.update.id = id;
|
||||||
stateProgram.update.form = formData;
|
stateProgram.update.form = formData;
|
||||||
await stateProgram.update.update();
|
await stateProgram.update.update();
|
||||||
|
|
||||||
toast.success('Program berhasil diperbarui!');
|
toast.success('Program berhasil diperbarui!');
|
||||||
router.push('/admin/ekonomi/program-kemiskinan');
|
router.push('/admin/ekonomi/program-kemiskinan');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ const DetailDataPengangguran = new Elysia({
|
|||||||
percentageChange: t.Optional(t.Number()),
|
percentageChange: t.Optional(t.Number()),
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
.delete("/:id", detailDataPengangguranDelete, {
|
.delete("/del/:id", detailDataPengangguranDelete, {
|
||||||
params: t.Object({
|
params: t.Object({
|
||||||
id: t.String(),
|
id: t.String(),
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ type FormCreate = {
|
|||||||
gaji: string;
|
gaji: string;
|
||||||
deskripsi: string;
|
deskripsi: string;
|
||||||
kualifikasi: string;
|
kualifikasi: string;
|
||||||
|
notelp: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function lowonganKerjaCreate(context: Context) {
|
export default async function lowonganKerjaCreate(context: Context) {
|
||||||
@@ -23,6 +24,7 @@ export default async function lowonganKerjaCreate(context: Context) {
|
|||||||
gaji: body.gaji,
|
gaji: body.gaji,
|
||||||
deskripsi: body.deskripsi,
|
deskripsi: body.deskripsi,
|
||||||
kualifikasi: body.kualifikasi,
|
kualifikasi: body.kualifikasi,
|
||||||
|
notelp: body.notelp,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ const LowonganKerja = new Elysia({prefix: "/lowongankerja", tags: ["Ekonomi/Lowo
|
|||||||
gaji: t.String(),
|
gaji: t.String(),
|
||||||
deskripsi: t.String(),
|
deskripsi: t.String(),
|
||||||
kualifikasi: t.String(),
|
kualifikasi: t.String(),
|
||||||
|
notelp: t.String(),
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.get("/find-many", lowonganKerjaFindMany)
|
.get("/find-many", lowonganKerjaFindMany)
|
||||||
@@ -35,6 +36,7 @@ const LowonganKerja = new Elysia({prefix: "/lowongankerja", tags: ["Ekonomi/Lowo
|
|||||||
gaji: t.String(),
|
gaji: t.String(),
|
||||||
deskripsi: t.String(),
|
deskripsi: t.String(),
|
||||||
kualifikasi: t.String(),
|
kualifikasi: t.String(),
|
||||||
|
notelp: t.String(),
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ type FormUpdate = {
|
|||||||
gaji: string;
|
gaji: string;
|
||||||
deskripsi: string;
|
deskripsi: string;
|
||||||
kualifikasi: string;
|
kualifikasi: string;
|
||||||
|
notelp: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function lowonganKerjaUpdate(context: Context){
|
export default async function lowonganKerjaUpdate(context: Context){
|
||||||
@@ -16,7 +17,7 @@ export default async function lowonganKerjaUpdate(context: Context){
|
|||||||
const id = context.params?.id;
|
const id = context.params?.id;
|
||||||
const body = context.body as FormUpdate;
|
const body = context.body as FormUpdate;
|
||||||
|
|
||||||
const { posisi, namaPerusahaan, lokasi, tipePekerjaan, gaji, deskripsi, kualifikasi } = body;
|
const { posisi, namaPerusahaan, lokasi, tipePekerjaan, gaji, deskripsi, kualifikasi, notelp } = body;
|
||||||
|
|
||||||
if (!id) {
|
if (!id) {
|
||||||
return Response.json({
|
return Response.json({
|
||||||
@@ -46,6 +47,7 @@ export default async function lowonganKerjaUpdate(context: Context){
|
|||||||
gaji,
|
gaji,
|
||||||
deskripsi,
|
deskripsi,
|
||||||
kualifikasi,
|
kualifikasi,
|
||||||
|
notelp,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,83 +1,52 @@
|
|||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Stack, Box, Container, Grid, GridCol, Group, Paper, TextInput, Text, Image, Flex, Button } from '@mantine/core';
|
import { Stack, Container, Box, List, ListItem, Text, Image } from '@mantine/core';
|
||||||
import { IconCalendar, IconMapPin, IconSearch, IconUsersGroup } from '@tabler/icons-react';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import BackButton from '../darmasaba/(pages)/desa/layanan/_com/BackButto';
|
||||||
|
|
||||||
import Link from 'next/link';
|
|
||||||
|
|
||||||
function Page() {
|
function Page() {
|
||||||
return (
|
return (
|
||||||
<Stack pos="relative" bg={colors.Bg} py="xl" gap="md">
|
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"22"} px={{ base: "md", md: 0 }}>
|
||||||
{/* Header */}
|
<Box px={{ base: "md", md: 100 }}><BackButton /></Box>
|
||||||
<Container size="lg" px="md">
|
<Container w={{ base: "100%", md: "50%" }} >
|
||||||
<Stack align="center" gap={0} mb="xl">
|
<Box pb={20}>
|
||||||
<Text fz={{ base: "2rem", md: "3.4rem" }} c={colors["blue-button"]} fw="bold" ta="center">
|
<Text ta={"center"} fz={"3.4rem"} c={colors["blue-button"]} fw={"bold"}>
|
||||||
Program Gotong Royong
|
Bumdes Pudak Mesari
|
||||||
</Text>
|
</Text>
|
||||||
<Text fz={{ base: "2rem", md: "3.4rem" }} c={colors["blue-button"]} fw="bold" ta="center">
|
<Text
|
||||||
Desa Darmasaba
|
ta={"center"}
|
||||||
|
fw={"bold"}
|
||||||
|
fz={"1.5rem"}
|
||||||
|
>
|
||||||
|
Informasi dan Pelayanan Administrasi Digital
|
||||||
</Text>
|
</Text>
|
||||||
</Stack>
|
</Box>
|
||||||
|
<Image src="/api/img/ack.png" alt='' w={"100%"} />
|
||||||
</Container>
|
</Container>
|
||||||
|
<Box px={{ base: "md", md: 100 }}>
|
||||||
{/* Tabs Menu */}
|
<Text py={20} fz={{ base: "sm", md: "lg" }} ta={"justify"}>
|
||||||
<Box px={{ base: "md", md: "xl" }} py="md" bg={colors['BG-trans']} mb="md">
|
Badan Usaha Milik Desa (BUMDes) Pudak Mesari adalah lembaga ekonomi desa yang berperan penting dalam pengembangan potensi dan kesejahteraan masyarakat Desa Darmasaba, Kecamatan Abiansemal, Kabupaten Badung, Bali. BUMDes ini berfungsi sebagai motor penggerak perekonomian desa melalui berbagai unit usaha yang dikelola secara profesional.
|
||||||
<Grid align="center" justify="space-between" mb={20}>
|
</Text>
|
||||||
<GridCol span={{ base: 12, md: 8 }}>
|
<Text fz={{ base: "sm", md: "lg" }} ta={"justify"}>
|
||||||
<Group gap="md" wrap="wrap">
|
Potensi dan Peran BUMDes Pudak Mesari:
|
||||||
<Paper bg={colors['blue-button']} radius="xl" py={5} px={20}>
|
</Text>
|
||||||
<Text c={colors['white-1']} size="sm">
|
<List py={20} type='ordered'>
|
||||||
Semua
|
<ListItem fz={{ base: "sm", md: "lg" }} ta={"justify"}>
|
||||||
</Text>
|
<Text fz={{ base: "sm", md: "lg" }} fw={"bold"}>Pengembangan Usaha Mikro dan Kecil:</Text>BUMDes Pudak Mesari menyediakan layanan bagi pelaku usaha mikro dan kecil di desa, seperti penyediaan konsumsi dan snack kotak untuk berbagai acara.
|
||||||
</Paper>
|
</ListItem>
|
||||||
{['Kebersihan', 'Infrastruktur', 'Sosial', 'Lingkungan'].map((kategori) => (
|
<ListItem fz={{ base: "sm", md: "lg" }} ta={"justify"}>
|
||||||
<Paper key={kategori} bg={colors['blue-button-trans']} radius="xl" py={5} px={20}>
|
<Text fz={{ base: "sm", md: "lg" }} fw={"bold"}>Pengelolaan Sampah Berbasis Masyarakat:</Text>Melalui kolaborasi dengan komunitas pemuda peduli lingkungan, BUMDes Pudak Mesari aktif dalam pengelolaan sampah berbasis masyarakat.
|
||||||
<Text size="sm">
|
</ListItem>
|
||||||
{kategori}
|
<ListItem fz={{ base: "sm", md: "lg" }} ta={"justify"}>
|
||||||
</Text>
|
<Text fz={{ base: "sm", md: "lg" }} fw={"bold"}>Peningkatan Kapasitas dan Transparansi:</Text>Untuk memastikan pengelolaan yang akuntabel, BUMDes Pudak Mesari rutin mengadakan rapat koordinasi dan pendampingan penyusunan laporan pertanggungjawaban.
|
||||||
</Paper>
|
</ListItem>
|
||||||
))}
|
<ListItem fz={{ base: "sm", md: "lg" }} ta={"justify"}>
|
||||||
</Group>
|
<Text fz={{ base: "sm", md: "lg" }} fw={"bold"}>Kolaborasi Internasional:</Text>Desa Darmasaba, melalui BUMDes Pudak Mesari, menerima kunjungan dari tim Osaki Jepang untuk memperkuat pengelolaan sampah dan lingkungan.
|
||||||
</GridCol>
|
</ListItem>
|
||||||
<GridCol span={{ base: 12, md: 4 }}>
|
</List>
|
||||||
<TextInput
|
<Text fz={{ base: "sm", md: "lg" }} ta={"justify"}>
|
||||||
radius="lg"
|
Dengan berbagai inisiatif tersebut, BUMDes Pudak Mesari menunjukkan perannya sebagai pilar utama dalam pengembangan ekonomi dan kesejahteraan masyarakat Desa Darmasaba, sekaligus menjaga kelestarian lingkungan melalui program-program inovatif dan kolaboratif.
|
||||||
placeholder="Cari Program Gotong Royong"
|
</Text>
|
||||||
leftSection={<IconSearch size={18} />}
|
|
||||||
w="100%"
|
|
||||||
/>
|
|
||||||
</GridCol>
|
|
||||||
</Grid>
|
|
||||||
<Paper p={"xl"} bg={colors['white-trans-1']} w={{ base: "100%", md: "100%" }}>
|
|
||||||
<Stack gap={'xs'}>
|
|
||||||
<Image radius={20} src={'/api/img/gotong-royong.png'} w={'100%'} alt='' />
|
|
||||||
<Text fw={"bold"} fz={{ base: "h2", md: "h1" }}>Membangun Fasilitas Desa</Text>
|
|
||||||
<Group>
|
|
||||||
<Paper py={5} px={20} bg={colors['blue-button-trans']} radius={20}>
|
|
||||||
<Text c={colors['white-1']}>Sosial</Text>
|
|
||||||
</Paper>
|
|
||||||
</Group>
|
|
||||||
<Text fz={{ base: "h4", md: "h3" }}>
|
|
||||||
Program Pembangunan Fasilitas Desa Maju, Masyarakat Sejahtera.
|
|
||||||
</Text>
|
|
||||||
<Flex gap={5} align={'center'}>
|
|
||||||
<IconCalendar color={colors['blue-button-trans']} size={45} />
|
|
||||||
<Text fz={{ base: "h4", md: "h3" }}>1 April 2025</Text>
|
|
||||||
</Flex>
|
|
||||||
<Flex gap={5} align={'center'}>
|
|
||||||
<IconMapPin color={colors['blue-button-trans']} size={45} />
|
|
||||||
<Text fz={{ base: "h4", md: "h3" }}>Banjar Desa Darmasaba</Text>
|
|
||||||
</Flex>
|
|
||||||
<Flex gap={5} align={'center'}>
|
|
||||||
<IconUsersGroup color={colors['blue-button-trans']} size={45} />
|
|
||||||
<Text fz={{ base: "h4", md: "h3" }}>30 Partisipan</Text>
|
|
||||||
</Flex>
|
|
||||||
<Text fw={'bold'} fz={'md'}>Deskripsi : Program pembangunan Pura sebagai pusat spiritual dan budaya desa, melibatkan gotong royong masyarakat dalam pembangunan struktur utama serta ornamen tradisional.</Text>
|
|
||||||
<Group py={20} justify='center'>
|
|
||||||
<Button component={Link} href={'https://www.whatsapp.com/?lang=id'} bg={colors['blue-button']} >Daftar Sebagai Relawan</Button>
|
|
||||||
</Group>
|
|
||||||
</Stack>
|
|
||||||
</Paper>
|
|
||||||
</Box>
|
</Box>
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -165,7 +165,7 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import PendapatanAsliDesa from '@/app/admin/(dashboard)/_state/ekonomi/PADesa';
|
import PendapatanAsliDesa from '@/app/admin/(dashboard)/_state/ekonomi/PADesa';
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Paper, SimpleGrid, Stack, Table, Text, Title } from '@mantine/core';
|
import { Box, Grid, GridCol, Paper, SimpleGrid, Stack, Table, Text, Title } from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import BackButton from '../../desa/layanan/_com/BackButto';
|
import BackButton from '../../desa/layanan/_com/BackButto';
|
||||||
@@ -206,8 +206,117 @@ function Page() {
|
|||||||
<Stack gap="lg" justify="center">
|
<Stack gap="lg" justify="center">
|
||||||
<Paper bg={colors['white-1']} p="xl">
|
<Paper bg={colors['white-1']} p="xl">
|
||||||
<SimpleGrid cols={{ base: 1, md: 3 }} spacing="md">
|
<SimpleGrid cols={{ base: 1, md: 3 }} spacing="md">
|
||||||
{/* Pendapatan, Belanja, Pembiayaan Card sama seperti sebelumnya */}
|
{/* Pendapatan Card */}
|
||||||
{/* ... */}
|
<Box p="md" style={{ border: '1px solid #e9ecef', borderRadius: '8px' }}>
|
||||||
|
<Stack gap={"xs"}>
|
||||||
|
<Title order={3}>Pendapatan</Title>
|
||||||
|
{PendapatanAsliDesa.pendapatan.findMany.data?.map((item) => (
|
||||||
|
<Box key={item.id}>
|
||||||
|
<Grid>
|
||||||
|
<GridCol span={{ base: 12, md: 6 }}>
|
||||||
|
<Text fz="md" fw={500}>{item.name}</Text>
|
||||||
|
</GridCol>
|
||||||
|
<GridCol span={{ base: 12, md: 6 }}>
|
||||||
|
<Text fz="md" fw={500}>{new Intl.NumberFormat('id-ID', {
|
||||||
|
style: 'currency',
|
||||||
|
currency: 'IDR',
|
||||||
|
minimumFractionDigits: 0
|
||||||
|
}).format(item.value)}</Text>
|
||||||
|
</GridCol>
|
||||||
|
</Grid>
|
||||||
|
</Box>
|
||||||
|
))}
|
||||||
|
<Grid>
|
||||||
|
<GridCol span={{ base: 12, md: 6 }}>
|
||||||
|
<Text fz="lg" fw={600} mb="xs">Total Pendapatan</Text>
|
||||||
|
</GridCol>
|
||||||
|
<GridCol span={{ base: 12, md: 6 }}>
|
||||||
|
<Text fz="xl" fw={700} c={colors['blue-button']}>
|
||||||
|
{new Intl.NumberFormat('id-ID', {
|
||||||
|
style: 'currency',
|
||||||
|
currency: 'IDR',
|
||||||
|
minimumFractionDigits: 0
|
||||||
|
}).format(totalPendapatan)}
|
||||||
|
</Text>
|
||||||
|
</GridCol>
|
||||||
|
</Grid>
|
||||||
|
</Stack>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Belanja Card */}
|
||||||
|
<Box p="md" style={{ border: '1px solid #e9ecef', borderRadius: '8px' }}>
|
||||||
|
<Stack gap={"xs"}>
|
||||||
|
<Title order={3}>Belanja</Title>
|
||||||
|
{PendapatanAsliDesa.belanja.findMany.data?.map((item) => (
|
||||||
|
<Box key={item.id}>
|
||||||
|
<Grid>
|
||||||
|
<GridCol span={{ base: 12, md: 6 }}>
|
||||||
|
<Text fz="md" fw={500}>{item.name}</Text>
|
||||||
|
</GridCol>
|
||||||
|
<GridCol span={{ base: 12, md: 6 }}>
|
||||||
|
<Text fz="md" fw={500}>{new Intl.NumberFormat('id-ID', {
|
||||||
|
style: 'currency',
|
||||||
|
currency: 'IDR',
|
||||||
|
minimumFractionDigits: 0
|
||||||
|
}).format(item.value)}</Text>
|
||||||
|
</GridCol>
|
||||||
|
</Grid>
|
||||||
|
</Box>
|
||||||
|
))}
|
||||||
|
<Grid>
|
||||||
|
<GridCol span={{ base: 12, md: 6 }}>
|
||||||
|
<Text fz="lg" fw={600} mb="xs">Total Belanja</Text>
|
||||||
|
</GridCol>
|
||||||
|
<GridCol span={{ base: 12, md: 6 }}>
|
||||||
|
<Text fz="xl" fw={700} c="orange">
|
||||||
|
{new Intl.NumberFormat('id-ID', {
|
||||||
|
style: 'currency',
|
||||||
|
currency: 'IDR',
|
||||||
|
minimumFractionDigits: 0
|
||||||
|
}).format(totalBelanja)}
|
||||||
|
</Text>
|
||||||
|
</GridCol>
|
||||||
|
</Grid>
|
||||||
|
</Stack>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Pembiayaan Card */}
|
||||||
|
<Box p="md" style={{ border: '1px solid #e9ecef', borderRadius: '8px' }}>
|
||||||
|
<Stack gap={"xs"}>
|
||||||
|
<Title order={3}>Pembiayaan</Title>
|
||||||
|
{PendapatanAsliDesa.pembiayaan.findMany.data?.map((item) => (
|
||||||
|
<Box key={item.id}>
|
||||||
|
<Grid>
|
||||||
|
<GridCol span={{ base: 12, md: 6 }}>
|
||||||
|
<Text fz="md" fw={500}>{item.name}</Text>
|
||||||
|
</GridCol>
|
||||||
|
<GridCol span={{ base: 12, md: 6 }}>
|
||||||
|
<Text fz="md" fw={500}>{new Intl.NumberFormat('id-ID', {
|
||||||
|
style: 'currency',
|
||||||
|
currency: 'IDR',
|
||||||
|
minimumFractionDigits: 0
|
||||||
|
}).format(item.value)}</Text>
|
||||||
|
</GridCol>
|
||||||
|
</Grid>
|
||||||
|
</Box>
|
||||||
|
))}
|
||||||
|
<Grid>
|
||||||
|
<GridCol span={{ base: 12, md: 6 }}>
|
||||||
|
<Text fz="lg" fw={600} mb="xs">Total Pembiayaan</Text>
|
||||||
|
</GridCol>
|
||||||
|
<GridCol span={{ base: 12, md: 6 }}>
|
||||||
|
<Text fz="xl" fw={700} c="green">
|
||||||
|
{new Intl.NumberFormat('id-ID', {
|
||||||
|
style: 'currency',
|
||||||
|
currency: 'IDR',
|
||||||
|
minimumFractionDigits: 0
|
||||||
|
}).format(totalPembiayaan)}
|
||||||
|
</Text>
|
||||||
|
</GridCol>
|
||||||
|
</Grid>
|
||||||
|
</Stack>
|
||||||
|
</Box>
|
||||||
|
|
||||||
</SimpleGrid>
|
</SimpleGrid>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
@@ -218,7 +327,7 @@ function Page() {
|
|||||||
<Table.Thead>
|
<Table.Thead>
|
||||||
<Table.Tr>
|
<Table.Tr>
|
||||||
<Table.Th>Keterangan</Table.Th>
|
<Table.Th>Keterangan</Table.Th>
|
||||||
<Table.Th align="right">Jumlah</Table.Th>
|
<Table.Th ta={"right"}>Jumlah</Table.Th>
|
||||||
</Table.Tr>
|
</Table.Tr>
|
||||||
</Table.Thead>
|
</Table.Thead>
|
||||||
<Table.Tbody>
|
<Table.Tbody>
|
||||||
|
|||||||
@@ -86,38 +86,41 @@ function Page() {
|
|||||||
<Paper p={'lg'}>
|
<Paper p={'lg'}>
|
||||||
<Text fw={'bold'} fz={'h3'}>Pengangguran Berdasarkan Usia</Text>
|
<Text fw={'bold'} fz={'h3'}>Pengangguran Berdasarkan Usia</Text>
|
||||||
{mounted && donutGrafikNganggurData.length > 0 ? (<Box style={{ width: '100%', height: 'auto', minHeight: 200 }}>
|
{mounted && donutGrafikNganggurData.length > 0 ? (<Box style={{ width: '100%', height: 'auto', minHeight: 200 }}>
|
||||||
<PieChart
|
<Box w="100%" style={{ maxWidth: 400, margin: "0 auto" }}>
|
||||||
size={300}
|
<PieChart
|
||||||
withLabelsLine
|
w="100%"
|
||||||
labelsPosition="outside"
|
h={250} // lebih kecil biar aman di mobile
|
||||||
labelsType="percent"
|
withLabelsLine
|
||||||
withLabels
|
labelsPosition="outside"
|
||||||
data={donutGrafikNganggurData}
|
labelsType="percent"
|
||||||
withTooltip
|
withLabels
|
||||||
tooltipDataSource="segment"
|
data={donutGrafikNganggurData}
|
||||||
mx="auto" />
|
withTooltip
|
||||||
|
tooltipDataSource="segment"
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
</Box>) : <Skeleton h={500} />}
|
</Box>) : <Skeleton h={500} />}
|
||||||
<Flex pb={30} justify={'center'} gap={'xl'} align={'center'}>
|
<Flex pb={30} justify={'center'} gap={'xl'} align={'center'} wrap="wrap">
|
||||||
<Box>
|
<Box>
|
||||||
<Flex gap={{ base: 5, md: 8 }} align={'center'}>
|
<Flex gap={{ base: 5, md: 8 }} align={'center'} wrap="wrap">
|
||||||
<Text fw={'bold'} fz={{ base: 'md', md: 'h4' }}>18-25</Text>
|
<Text fw={'bold'} fz={{ base: 'md', md: 'h4' }}>18-25</Text>
|
||||||
<ColorSwatch color="#4b6Ef5" size={30} />
|
<ColorSwatch color="#4b6Ef5" size={30} />
|
||||||
</Flex>
|
</Flex>
|
||||||
</Box>
|
</Box>
|
||||||
<Box>
|
<Box>
|
||||||
<Flex gap={{ base: 5, md: 8 }} align={'center'}>
|
<Flex gap={{ base: 5, md: 8 }} align={'center'} wrap="wrap">
|
||||||
<Text fw={'bold'} fz={{ base: 'md', md: 'h4' }}>26-35</Text>
|
<Text fw={'bold'} fz={{ base: 'md', md: 'h4' }}>26-35</Text>
|
||||||
<ColorSwatch color="#14b885" size={30} />
|
<ColorSwatch color="#14b885" size={30} />
|
||||||
</Flex>
|
</Flex>
|
||||||
</Box>
|
</Box>
|
||||||
<Box>
|
<Box>
|
||||||
<Flex gap={{ base: 5, md: 8 }} align={'center'}>
|
<Flex gap={{ base: 5, md: 8 }} align={'center'} wrap="wrap">
|
||||||
<Text fw={'bold'} fz={{ base: 'md', md: 'h4' }}>36-45</Text>
|
<Text fw={'bold'} fz={{ base: 'md', md: 'h4' }}>36-45</Text>
|
||||||
<ColorSwatch color="#E6A03B" size={30} />
|
<ColorSwatch color="#E6A03B" size={30} />
|
||||||
</Flex>
|
</Flex>
|
||||||
</Box>
|
</Box>
|
||||||
<Box>
|
<Box>
|
||||||
<Flex gap={{ base: 5, md: 8 }} align={'center'}>
|
<Flex gap={{ base: 5, md: 8 }} align={'center'} wrap="wrap">
|
||||||
<Text fw={'bold'} fz={{ base: 'md', md: 'h4' }}>46+</Text>
|
<Text fw={'bold'} fz={{ base: 'md', md: 'h4' }}>46+</Text>
|
||||||
<ColorSwatch color="#DB524D" size={30} />
|
<ColorSwatch color="#DB524D" size={30} />
|
||||||
</Flex>
|
</Flex>
|
||||||
@@ -127,44 +130,47 @@ function Page() {
|
|||||||
<Paper p={'lg'}>
|
<Paper p={'lg'}>
|
||||||
<Text fw={'bold'} fz={'h3'}>Pengangguran Berdasarkan Pendidikan</Text>
|
<Text fw={'bold'} fz={'h3'}>Pengangguran Berdasarkan Pendidikan</Text>
|
||||||
{mounted2 && donutGrafikNganggurDataPendidikan.length > 0 ? (<Center>
|
{mounted2 && donutGrafikNganggurDataPendidikan.length > 0 ? (<Center>
|
||||||
<PieChart
|
<Box w="100%" style={{ maxWidth: 400, margin: "0 auto" }}>
|
||||||
size={300}
|
<PieChart
|
||||||
withLabelsLine
|
w="100%"
|
||||||
labelsPosition="outside"
|
h={250} // lebih kecil biar aman di mobile
|
||||||
labelsType="percent"
|
withLabelsLine
|
||||||
withLabels
|
labelsPosition="outside"
|
||||||
data={donutGrafikNganggurDataPendidikan}
|
labelsType="percent"
|
||||||
withTooltip
|
withLabels
|
||||||
tooltipDataSource="segment"
|
data={donutGrafikNganggurDataPendidikan}
|
||||||
mx="auto" />
|
withTooltip
|
||||||
|
tooltipDataSource="segment"
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
</Center>) : <Skeleton h={500} />}
|
</Center>) : <Skeleton h={500} />}
|
||||||
<Flex pb={30} justify={'center'} gap={'xl'} align={'center'}>
|
<Flex pb={30} justify={'center'} gap={'xl'} align={'center'} wrap="wrap">
|
||||||
<Box>
|
<Box>
|
||||||
<Flex gap={{ base: 5, md: 8 }} align={'center'}>
|
<Flex gap={{ base: 5, md: 8 }} align={'center'} wrap="wrap">
|
||||||
<Text fw={'bold'} fz={{ base: 'md', md: 'h4' }}>SD</Text>
|
<Text fw={'bold'} fz={{ base: 'md', md: 'h4' }}>SD</Text>
|
||||||
<ColorSwatch color="#4b6Ef5" size={30} />
|
<ColorSwatch color="#4b6Ef5" size={30} />
|
||||||
</Flex>
|
</Flex>
|
||||||
</Box>
|
</Box>
|
||||||
<Box>
|
<Box>
|
||||||
<Flex gap={{ base: 5, md: 8 }} align={'center'}>
|
<Flex gap={{ base: 5, md: 8 }} align={'center'} wrap="wrap">
|
||||||
<Text fw={'bold'} fz={{ base: 'md', md: 'h4' }}>SMP</Text>
|
<Text fw={'bold'} fz={{ base: 'md', md: 'h4' }}>SMP</Text>
|
||||||
<ColorSwatch color="#14b885" size={30} />
|
<ColorSwatch color="#14b885" size={30} />
|
||||||
</Flex>
|
</Flex>
|
||||||
</Box>
|
</Box>
|
||||||
<Box>
|
<Box>
|
||||||
<Flex gap={{ base: 5, md: 8 }} align={'center'}>
|
<Flex gap={{ base: 5, md: 8 }} align={'center'} wrap="wrap">
|
||||||
<Text fw={'bold'} fz={{ base: 'md', md: 'h4' }}>SMA/SMK</Text>
|
<Text fw={'bold'} fz={{ base: 'md', md: 'h4' }}>SMA/SMK</Text>
|
||||||
<ColorSwatch color="#E6A03B" size={30} />
|
<ColorSwatch color="#E6A03B" size={30} />
|
||||||
</Flex>
|
</Flex>
|
||||||
</Box>
|
</Box>
|
||||||
<Box>
|
<Box>
|
||||||
<Flex gap={{ base: 5, md: 8 }} align={'center'}>
|
<Flex gap={{ base: 5, md: 8 }} align={'center'} wrap="wrap">
|
||||||
<Text fw={'bold'} fz={{ base: 'md', md: 'h4' }}>D3</Text>
|
<Text fw={'bold'} fz={{ base: 'md', md: 'h4' }}>D3</Text>
|
||||||
<ColorSwatch color="#DB524D" size={30} />
|
<ColorSwatch color="#DB524D" size={30} />
|
||||||
</Flex>
|
</Flex>
|
||||||
</Box>
|
</Box>
|
||||||
<Box>
|
<Box>
|
||||||
<Flex gap={{ base: 5, md: 8 }} align={'center'}>
|
<Flex gap={{ base: 5, md: 8 }} align={'center'} wrap="wrap">
|
||||||
<Text fw={'bold'} fz={{ base: 'md', md: 'h4' }}>S1</Text>
|
<Text fw={'bold'} fz={{ base: 'md', md: 'h4' }}>S1</Text>
|
||||||
<ColorSwatch color="#1018A8FF" size={30} />
|
<ColorSwatch color="#1018A8FF" size={30} />
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ function Page() {
|
|||||||
</Box>
|
</Box>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Box>
|
</Box>
|
||||||
<Button onClick={() => router.push('https://www.whatsapp.com/?lang=id')} bg={colors['blue-button']}>Lamar Sekarang</Button>
|
<Button onClick={() => router.push(`https://wa.me/${v.notelp?.replace(/\D/g, '')}`)}>Lamar Sekarang</Button>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ function Page() {
|
|||||||
<Box>
|
<Box>
|
||||||
<Select
|
<Select
|
||||||
placeholder="Pilih Kategori"
|
placeholder="Pilih Kategori"
|
||||||
data={pasarDesaState.kategoriProduk.findMany.data?.map((v) => ({
|
data={pasarDesaState.kategoriProduk.findManyAll.data?.map((v) => ({
|
||||||
value: v.id,
|
value: v.id,
|
||||||
label: v.nama
|
label: v.nama
|
||||||
})) || []}
|
})) || []}
|
||||||
|
|||||||
@@ -104,9 +104,7 @@ function Potensi() {
|
|||||||
{v.name}
|
{v.name}
|
||||||
</Text>
|
</Text>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Text lineClamp={2} c="gray.2" fz={{ base: "0.8rem", md: "1rem" }}>
|
<Text lineClamp={2} c="gray.2" fz={{ base: "0.8rem", md: "1rem" }} dangerouslySetInnerHTML={{ __html: v.deskripsi }} />
|
||||||
{v.deskripsi}
|
|
||||||
</Text>
|
|
||||||
</Stack>
|
</Stack>
|
||||||
</BackgroundImage>
|
</BackgroundImage>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|||||||
Reference in New Issue
Block a user