API & UI Menu Ekonomi, Submenu Jumlah Pengangguran

This commit is contained in:
2025-07-10 00:21:33 +08:00
parent d328f64d86
commit 7b2b306849
55 changed files with 205 additions and 1316 deletions

View File

@@ -15,6 +15,8 @@ type JudulListTabProps = {
}
const JudulListTab = ({
title = "",
href = "#",

View File

@@ -53,7 +53,7 @@ const jumlahPengangguran = proxy({
console.error("Gagal ambil data bulan/tahun:", err);
} finally {
jumlahPengangguran.findByMonthYear.loading = false;
}
}
},
},
create: {
@@ -236,423 +236,8 @@ const jumlahPengangguran = proxy({
},
});
const templateRingkasanData = z.object({
year: z.number().min(1, "Tahun harus diisi"),
totalUnemployment: z.number().min(1, "Total pengangguran harus diisi"),
educatedUnemployment: z
.number()
.min(1, "Pengangguran pendidikan harus diisi"),
percentageEducatedOfTotal: z
.number()
.min(1, "Persentase pendidikan harus diisi"),
productiveAgePopulation: z.number().min(1, "Populasi produktif harus diisi"),
percentageProductiveOfTotal: z
.number()
.min(1, "Persentase produktif harus diisi"),
percentageChangeFromPreviousYear: z
.number()
.min(1, "Persentase perubahan harus diisi"),
});
type RingkasanData = {
year: number;
totalUnemployment: number;
educatedUnemployment: number;
percentageEducatedOfTotal: number;
productiveAgePopulation: number;
percentageProductiveOfTotal: number;
percentageChangeFromPreviousYear: number;
};
const ringkasanDataForm: RingkasanData = {
year: 0,
totalUnemployment: 0,
educatedUnemployment: 0,
percentageEducatedOfTotal: 0,
productiveAgePopulation: 0,
percentageProductiveOfTotal: 0,
percentageChangeFromPreviousYear: 0,
};
const ringkasanData = proxy({
create: {
form: ringkasanDataForm,
loading: false,
async create() {
const cek = templateRingkasanData.safeParse(ringkasanData.create.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
toast.error(err);
return null;
}
try {
ringkasanData.create.loading = true;
const res =
await ApiFetch.api.ekonomi.jumlahpengangguran.ringkasandatapengangguran[
"create"
].post(ringkasanData.create.form);
if (res.status === 200) {
const id = res.data?.data?.id;
if (id) {
toast.success("Success create");
ringkasanData.create.form = { ...ringkasanDataForm };
ringkasanData.findMany.load();
return id;
}
}
toast.error("failed create");
return null;
} catch (error) {
console.log((error as Error).message);
return null;
} finally {
ringkasanData.create.loading = false;
}
},
},
findMany: {
data: null as
| Prisma.RingkasanDataPengangguranGetPayload<{
omit: { isActive: true };
}>[]
| null,
async load() {
const res =
await ApiFetch.api.ekonomi.jumlahpengangguran.ringkasandatapengangguran[
"find-many"
].get();
if (res.status === 200) {
ringkasanData.findMany.data = res.data?.data ?? [];
}
},
},
findUnique: {
data: null as Prisma.RingkasanDataPengangguranGetPayload<{
omit: { isActive: true };
}> | null,
async load(id: string) {
try {
const res = await fetch(
`/api/ekonomi/jumlahpengangguran/ringkasandatapengangguran/${id}`
);
if (res.ok) {
const data = await res.json();
ringkasanData.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch ringkasanData:", res.statusText);
ringkasanData.findUnique.data = null;
}
} catch (error) {
console.error("Error fetching ringkasanData:", error);
ringkasanData.findUnique.data = null;
}
},
},
update: {
id: "",
form: { ...ringkasanDataForm },
loading: false,
async submit() {
const id = this.id;
if (!id) {
toast.warn("ID tidak valid");
return null;
}
const formData = {
year: this.form.year,
totalUnemployment: this.form.totalUnemployment,
educatedUnemployment: this.form.educatedUnemployment,
percentageEducatedOfTotal: this.form.percentageEducatedOfTotal,
productiveAgePopulation: this.form.productiveAgePopulation,
percentageProductiveOfTotal: this.form.percentageProductiveOfTotal,
percentageChangeFromPreviousYear:
this.form.percentageChangeFromPreviousYear,
};
const cek = templateJumlahPengngguran.safeParse(formData);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
toast.error(err);
return null;
}
try {
this.loading = true;
const res = await fetch(
`/api/ekonomi/jumlahpengangguran/ringkasandatapengangguran/${id}`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(formData),
}
);
const result = await res.json();
if (!res.ok || !result?.success) {
throw new Error(result?.message || "Gagal update data");
}
toast.success("Berhasil update data!");
await ringkasanData.findMany.load();
return result.data;
} catch (error) {
console.error("Update error:", error);
toast.error("Gagal update data ringkasan data pengangguran");
throw error;
} finally {
this.loading = false;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
ringkasanData.delete.loading = true;
const response = await fetch(
`/api/ekonomi/jumlahpengangguran/ringkasandatapengangguran/del/${id}`,
{
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
}
);
const result = await response.json();
if (response.ok && result?.success) {
toast.success(
result.message || "Ringkasan data pengangguran berhasil dihapus"
);
await ringkasanData.findMany.load();
} else {
toast.error(
result?.message || "Gagal menghapus ringkasan data pengangguran"
);
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error(
"Terjadi kesalahan saat menghapus ringkasan data pengangguran"
);
} finally {
ringkasanData.delete.loading = false;
}
},
},
});
const templateSedangMencariKerja = z.object({
count: z.number().min(1, "Jumlah harus diisi"),
percentageOfTotal: z.number().min(1, "Persentase harus diisi"),
recordedDate: z.string().min(1, "Tanggal harus diisi"),
});
type SedangMencariKerja = {
count: number;
percentageOfTotal: number;
recordedDate: string;
};
const sedangMencariKerjaForm: SedangMencariKerja = {
count: 0,
percentageOfTotal: 0,
recordedDate: "",
};
const sedangMencariKerja = proxy({
create: {
form: sedangMencariKerjaForm,
loading: false,
async create() {
const cek = templateSedangMencariKerja.safeParse(
sedangMencariKerja.create.form
);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
toast.error(err);
return null;
}
try {
sedangMencariKerja.create.loading = true;
const res =
await ApiFetch.api.ekonomi.jumlahpengangguran.sedangmencarikerja[
"create"
].post(sedangMencariKerja.create.form);
if (res.status === 200) {
const id = res.data?.data?.id;
if (id) {
toast.success("Success create");
sedangMencariKerja.create.form = { ...sedangMencariKerjaForm };
sedangMencariKerja.findMany.load();
return id;
}
}
toast.error("failed create");
return null;
} catch (error) {
console.log((error as Error).message);
return null;
} finally {
sedangMencariKerja.create.loading = false;
}
},
},
findMany: {
data: null as
| Prisma.SedangMencariKerjaGetPayload<{
omit: { isActive: true };
}>[]
| null,
async load() {
const res =
await ApiFetch.api.ekonomi.jumlahpengangguran.sedangmencarikerja[
"find-many"
].get();
if (res.status === 200) {
sedangMencariKerja.findMany.data = res.data?.data ?? [];
}
},
},
findUnique: {
data: null as Prisma.SedangMencariKerjaGetPayload<{
omit: { isActive: true };
}> | null,
async load(id: string) {
try {
const res = await fetch(
`/api/ekonomi/jumlahpengangguran/sedangmencarikerja/${id}`
);
if (res.ok) {
const data = await res.json();
sedangMencariKerja.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch sedangMencariKerja:", res.statusText);
sedangMencariKerja.findUnique.data = null;
}
} catch (error) {
console.error("Error fetching sedangMencariKerja:", error);
sedangMencariKerja.findUnique.data = null;
}
},
},
update: {
id: "",
form: { ...sedangMencariKerjaForm },
loading: false,
async submit() {
const id = this.id;
if (!id) {
toast.warn("ID tidak valid");
return null;
}
const formData = {
count: this.form.count,
percentageOfTotal: this.form.percentageOfTotal,
recordedDate: this.form.recordedDate,
};
const cek = templateSedangMencariKerja.safeParse(formData);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
toast.error(err);
return null;
}
try {
this.loading = true;
const res = await fetch(
`/api/ekonomi/jumlahpengangguran/sedangmencarikerja/${id}`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(formData),
}
);
const result = await res.json();
if (!res.ok || !result?.success) {
throw new Error(result?.message || "Gagal update data");
}
toast.success("Berhasil update data!");
await sedangMencariKerja.findMany.load();
return result.data;
} catch (error) {
console.error("Update error:", error);
toast.error("Gagal update data sedang mencari kerja");
throw error;
} finally {
this.loading = false;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
sedangMencariKerja.delete.loading = true;
const response = await fetch(
`/api/ekonomi/jumlahpengangguran/sedangmencarikerja/del/${id}`,
{
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
}
);
const result = await response.json();
if (response.ok && result?.success) {
toast.success(
result.message || "Sedang mencari kerja berhasil dihapus"
);
await sedangMencariKerja.findMany.load();
} else {
toast.error(
result?.message || "Gagal menghapus sedang mencari kerja"
);
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus sedang mencari kerja");
} finally {
sedangMencariKerja.delete.loading = false;
}
},
},
});
const jumlahPengangguranState = proxy({
jumlahPengangguran,
ringkasanData,
sedangMencariKerja,
});
export default jumlahPengangguranState;

View File

@@ -3,7 +3,7 @@ import colors from '@/con/colors';
import { Box, Button, Paper, Skeleton, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import JudulListTab from '../../../_com/jusulListTab';
import JudulListTab from '../../../_com/judulListTab';
import { useProxy } from 'valtio/utils';
import stateGallery from '../../../_state/desa/gallery';
import { useShallowEffect } from '@mantine/hooks';

View File

@@ -3,7 +3,7 @@ import colors from '@/con/colors';
import { Box, Button, Paper, Skeleton, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import JudulListTab from '../../../_com/jusulListTab';
import JudulListTab from '../../../_com/judulListTab';
import { useProxy } from 'valtio/utils';
import stateGallery from '../../../_state/desa/gallery';
import { useShallowEffect } from '@mantine/hooks';

View File

@@ -1,5 +1,5 @@
'use client'
import JudulListTab from '@/app/admin/(dashboard)/_com/jusulListTab';
import JudulListTab from '@/app/admin/(dashboard)/_com/judulListTab';
import colors from '@/con/colors';
import { Box, Button, Image, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';

View File

@@ -1,5 +1,5 @@
'use client'
import JudulListTab from '@/app/admin/(dashboard)/_com/jusulListTab';
import JudulListTab from '@/app/admin/(dashboard)/_com/judulListTab';
import colors from '@/con/colors';
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';

View File

@@ -6,7 +6,7 @@ import { useShallowEffect } from '@mantine/hooks';
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useProxy } from 'valtio/utils';
import JudulListTab from '../../_com/jusulListTab';
import JudulListTab from '../../_com/judulListTab';
import { useState } from 'react';
import HeaderSearch from '../../_com/header';

View File

@@ -8,7 +8,7 @@ import { useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';
import { Cell, Pie, PieChart } from 'recharts';
import { useProxy } from 'valtio/utils';
import JudulListTab from '../../../_com/jusulListTab';
import JudulListTab from '../../../_com/judulListTab';
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
import grafikNganggur from '../../../_state/ekonomi/usia-kerja-nganggur';

View File

@@ -8,7 +8,7 @@ import { useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';
import { Cell, Pie, PieChart } from 'recharts';
import { useProxy } from 'valtio/utils';
import JudulListTab from '../../../_com/jusulListTab';
import JudulListTab from '../../../_com/judulListTab';
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
import grafikNganggur from '../../../_state/ekonomi/usia-kerja-nganggur';

View File

@@ -1,67 +0,0 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import colors from '@/con/colors';
import { Stack, Tabs, TabsList, TabsPanel, TabsTab, Title } from '@mantine/core';
import { usePathname, useRouter } from 'next/navigation';
import React, { useEffect, useState } from 'react';
function LayoutTabs({ children }: { children: React.ReactNode }) {
const router = useRouter()
const pathname = usePathname()
const tabs = [
{
label: "Detail Data Pengangguran",
value: "detaildatapengangguran",
href: "/admin/ekonomi/jumlah-pengangguran/detail-data-pengangguran"
},
{
label: "Ringkasan Data Pengangguran",
value: "ringkasandatapengangguran",
href: "/admin/ekonomi/jumlah-pengangguran/ringkasan-data-pengangguran"
},
{
label: "Sedang Mencari Kerja",
value: "sedangmencarikerja",
href: "/admin/ekonomi/jumlah-pengangguran/sedang-mencari-kerja"
}
];
const curentTab = tabs.find(tab => tab.href === pathname)
const [activeTab, setActiveTab] = useState<string | null>(curentTab?.value || tabs[0].value);
const handleTabChange = (value: string | null) => {
const tab = tabs.find(t => t.value === value)
if (tab) {
router.push(tab.href)
}
setActiveTab(value)
}
useEffect(() => {
const match = tabs.find(tab => tab.href === pathname)
if (match) {
setActiveTab(match.value)
}
}, [pathname])
return (
<Stack>
<Title order={3}>Jumlah Penduduk Usia Kerja yang Menganggur</Title>
<Tabs color={colors['blue-button']} variant='pills' value={activeTab} onChange={handleTabChange}>
<TabsList p={"xs"} bg={"#BBC8E7FF"}>
{tabs.map((e, i) => (
<TabsTab key={i} value={e.value}>{e.label}</TabsTab>
))}
</TabsList>
{tabs.map((e, i) => (
<TabsPanel key={i} value={e.value}>
{/* Konten dummy, bisa diganti tergantung routing */}
<></>
</TabsPanel>
))}
</Tabs>
{children}
</Stack>
);
}
export default LayoutTabs;

View File

@@ -1,87 +0,0 @@
'use client'
import colors from '@/con/colors';
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Title } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useState } from 'react';
import { useProxy } from 'valtio/utils';
import JudulListTab from '../../../_com/jusulListTab';
import jumlahPengangguranState from '../../../_state/ekonomi/jumlah-pengangguran';
function DetailDataPengangguran() {
return (
<Box>
<Stack gap={"xs"}>
<Title order={3}>Detail Data Pengangguran</Title>
<ListDetailDataPengangguran/>
</Stack>
</Box>
);
}
function ListDetailDataPengangguran() {
const stateDetail = useProxy(jumlahPengangguranState.jumlahPengangguran)
const router = useRouter();
const [search, setSearch] = useState("")
useShallowEffect(() => {
stateDetail.findMany.load()
}, [])
const filteredData = (stateDetail.findMany.data || []).filter(item => {
const keyword = search.toLowerCase();
return (
item.month.toLowerCase().includes(keyword) ||
item.year.toString().toLowerCase().includes(keyword)
);
});
if (!stateDetail.findMany.data) {
return (
<Box>
<Skeleton h={500} />
</Box>
)
}
return (
<Box>
<Paper bg={colors['white-1']} p={'md'}>
<JudulListTab
title='List Detail Data Pengangguran'
href='/admin/ekonomi/jumlah-pengangguran/detail-data-pengangguran/create'
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
placeholder='pencarian'
searchIcon={<IconSearch size={16} />}
/>
<Table striped withTableBorder withRowBorders>
<TableThead>
<TableTr>
<TableTh>Bulan</TableTh>
<TableTh>Terdidik</TableTh>
<TableTh>Tidak Terdidik</TableTh>
<TableTh>Detail</TableTh>
</TableTr>
</TableThead>
<TableTbody>
{filteredData.map((item) => (
<TableTr key={item.id}>
<TableTd>{item.month}</TableTd>
<TableTd>{item.educatedUnemployment}</TableTd>
<TableTd>{item.uneducatedUnemployment}</TableTd>
<TableTd>
<Button onClick={() => router.push(`/admin/ekonomi/jumlah-pengangguran/detail-data-pengangguran/${item.id}`)}>
<IconDeviceImac size={20} />
</Button>
</TableTd>
</TableTr>
))}
</TableTbody>
</Table>
</Paper>
</Box>
);
}
export default DetailDataPengangguran;

View File

@@ -1,9 +0,0 @@
import LayoutTabs from "./_lib/layoutTabs";
export default function Layout({ children }: { children: React.ReactNode }) {
return (
<LayoutTabs>
{children}
</LayoutTabs>
);
}

View File

@@ -0,0 +1,148 @@
'use client'
import colors from '@/con/colors';
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';
import { useProxy } from 'valtio/utils';
import { BarChart } from '@mantine/charts';
import jumlahPengangguranState from '../../_state/ekonomi/jumlah-pengangguran';
import JudulListTab from '../../_com/judulListTab';
function DetailDataPengangguran() {
return (
<Box>
<Stack gap={"xs"}>
<Title order={3}>Detail Data Pengangguran</Title>
<ListDetailDataPengangguran />
</Stack>
</Box>
);
}
function ListDetailDataPengangguran() {
type DetailDataPengangguran = {
id: string;
month: string;
year: number;
educatedUnemployment: number;
uneducatedUnemployment: number;
percentageChange: number;
totalUnemployment: number;
}
const [chartData, setChartData] = useState<DetailDataPengangguran[]>([]);
const [mounted, setMounted] = useState(false); // untuk memastikan DOM sudah ready
const stateDetail = useProxy(jumlahPengangguranState.jumlahPengangguran)
const router = useRouter();
const [search, setSearch] = useState("")
useShallowEffect(() => {
setMounted(true)
stateDetail.findMany.load()
}, [])
useEffect(() => {
setMounted(true);
if (stateDetail.findMany.data) {
setChartData(stateDetail.findMany.data.map((item) => ({
id: item.id,
month: item.month,
year: item.year,
educatedUnemployment: Number(item.educatedUnemployment),
uneducatedUnemployment: Number(item.uneducatedUnemployment),
percentageChange: Number(item.percentageChange),
totalUnemployment: Number(item.totalUnemployment),
})));
}
}, [stateDetail.findMany.data]);
const filteredData = (stateDetail.findMany.data || []).filter(item => {
const keyword = search.toLowerCase();
return (
item.month.toLowerCase().includes(keyword) ||
item.year.toString().toLowerCase().includes(keyword)
);
});
if (!stateDetail.findMany.data) {
return (
<Box>
<Skeleton h={500} />
</Box>
)
}
return (
<Box>
<Stack gap={"md"}>
<Paper bg={colors['white-1']} p={'md'}>
<JudulListTab
title='List Detail Data Pengangguran'
href='/admin/ekonomi/jumlah-pengangguran/detail-data-pengangguran/create'
value={search}
onChange={(e) => setSearch(e.currentTarget.value)}
placeholder='pencarian'
searchIcon={<IconSearch size={16} />}
/>
<Table striped withTableBorder withRowBorders>
<TableThead>
<TableTr>
<TableTh>Bulan</TableTh>
<TableTh>Terdidik</TableTh>
<TableTh>Tidak Terdidik</TableTh>
<TableTh>Detail</TableTh>
</TableTr>
</TableThead>
<TableTbody>
{filteredData.map((item) => (
<TableTr key={item.id}>
<TableTd>{item.month}</TableTd>
<TableTd>{item.educatedUnemployment}</TableTd>
<TableTd>{item.uneducatedUnemployment}</TableTd>
<TableTd>
<Button onClick={() => router.push(`/admin/ekonomi/jumlah-pengangguran/detail-data-pengangguran/${item.id}`)}>
<IconDeviceImac size={20} />
</Button>
</TableTd>
</TableTr>
))}
</TableTbody>
</Table>
</Paper>
{/* Chart */}
{!mounted && !chartData ? (
<Box style={{ width: '100%', minWidth: 300, height: 400, minHeight: 300 }}>
<Paper bg={colors['white-1']} p={'md'}>
<Title pb={10} order={3}>Data Kelahiran & Kematian</Title>
<Text c='dimmed'>Belum ada data untuk ditampilkan dalam grafik</Text>
</Paper>
</Box>
) : (
<Box style={{ width: '100%', minWidth: 300, height: 550, minHeight: 300 }}>
<Paper bg={colors['white-1']} p={'md'}>
<Title pb={10} order={4}>Data Kelahiran & Kematian</Title>
{mounted && chartData.length > 0 && (
<Box w={{ base: '100%', md: '70%' }}>
<BarChart
h={450}
data={chartData}
dataKey="month"
series={[
{ name: 'educatedUnemployment', color: 'red.6', label: 'Terdidik' },
{ name: 'uneducatedUnemployment', color: 'orange.6', label: 'Tidak Terdidik' },
]}
/>
</Box>
)}
</Paper>
</Box>
)}
</Stack>
</Box>
);
}
export default DetailDataPengangguran;

View File

@@ -1,11 +0,0 @@
import React from 'react';
function Page() {
return (
<div>
Page
</div>
);
}
export default Page;

View File

@@ -1,11 +0,0 @@
import React from 'react';
function Page() {
return (
<div>
Page
</div>
);
}
export default Page;

View File

@@ -1,11 +0,0 @@
import React from 'react';
function Page() {
return (
<div>
Page
</div>
);
}
export default Page;

View File

@@ -1,11 +0,0 @@
import React from 'react';
function Page() {
return (
<div>
Page
</div>
);
}
export default Page;

View File

@@ -1,11 +0,0 @@
import React from 'react';
function Page() {
return (
<div>
Page
</div>
);
}
export default Page;

View File

@@ -1,11 +0,0 @@
import React from 'react';
function Page() {
return (
<div>
Page
</div>
);
}
export default Page;

View File

@@ -1,11 +0,0 @@
import React from 'react';
function Page() {
return (
<div>
Page
</div>
);
}
export default Page;

View File

@@ -1,11 +0,0 @@
import React from 'react';
function Page() {
return (
<div>
Page
</div>
);
}
export default Page;

View File

@@ -1,14 +1,14 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import colors from '@/con/colors';
import { Badge, Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } from '@mantine/core';
import { useShallowEffect } from '@mantine/hooks';
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';
import { useProxy } from 'valtio/utils';
import HeaderSearch from '../../../_com/header';
import JudulList from '../../../_com/judulList';
import strukturorganisasiState from '../../../_state/ekonomi/struktur-organisasi/struktur-organisasi';
import { useState } from 'react';
function Pegawai() {
const [search, setSearch] = useState("");
@@ -30,7 +30,7 @@ function ListPegawai({ search }: { search: string }) {
const stateOrganisasi = useProxy(strukturorganisasiState.pegawai);
const router = useRouter();
useShallowEffect(() => {
useEffect(() => {
const loadData = async () => {
try {
// Clear existing data to ensure we see the loading state

View File

@@ -1,15 +1,15 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import colors from '@/con/colors';
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { IconEdit, IconSearch, IconTrash } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';
import { useProxy } from 'valtio/utils';
import HeaderSearch from '../../../_com/header';
import JudulList from '../../../_com/judulList';
import { useProxy } from 'valtio/utils';
import { useShallowEffect } from '@mantine/hooks';
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
import strukturorganisasiState from '../../../_state/ekonomi/struktur-organisasi/struktur-organisasi';
import { useState } from 'react';
function PosisiOrganisasi() {
const [search, setSearch] = useState("");
@@ -33,7 +33,7 @@ function ListPosisiOrganisasi({ search }: { search: string }) {
const [modalHapus, setModalHapus] = useState(false)
const [selectedId, setSelectedId] = useState<string | null>(null)
useShallowEffect(() => {
useEffect(() => {
stateOrganisasi.findMany.load()
}, [])

View File

@@ -7,7 +7,7 @@ import { useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';
import { Bar, BarChart, Legend, Tooltip, XAxis, YAxis } from 'recharts';
import { useProxy } from 'valtio/utils';
import JudulListTab from '../../../_com/jusulListTab';
import JudulListTab from '../../../_com/judulListTab';
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
import grafikkepuasan from '../../../_state/kesehatan/data_kesehatan_warga/grafikKepuasan';
import HeaderSearch from '../../../_com/header';

View File

@@ -8,7 +8,7 @@ import { useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';
import { Bar, BarChart, Legend, Tooltip, XAxis, YAxis } from 'recharts';
import { useProxy } from 'valtio/utils';
import JudulListTab from '../../../_com/jusulListTab';
import JudulListTab from '../../../_com/judulListTab';
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
import HeaderSearch from '../../../_com/header';

View File

@@ -3,7 +3,7 @@ import colors from '@/con/colors';
import { Box, Button, Image, Paper, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core';
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import JudulListTab from '../../../_com/jusulListTab';
import JudulListTab from '../../../_com/judulListTab';
function KeteranganBankSampahTerdekat() {
const router = useRouter();

View File

@@ -3,7 +3,7 @@ import colors from '@/con/colors';
import { Box, Button, Image, Paper, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core';
import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
import JudulListTab from '../../../_com/jusulListTab';
import JudulListTab from '../../../_com/judulListTab';
function ListPengelolaanSampahBankSampah() {
const router = useRouter();

View File

@@ -9,7 +9,7 @@ import { useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';
import { Cell, Pie, PieChart } from 'recharts';
import { useProxy } from 'valtio/utils';
import JudulListTab from '../../../_com/jusulListTab';
import JudulListTab from '../../../_com/judulListTab';
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
import HeaderSearch from '../../../_com/header';

View File

@@ -8,7 +8,7 @@ import { useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';
import { Cell, Pie, PieChart } from 'recharts';
import { useSnapshot } from 'valtio';
import JudulListTab from '../../../_com/jusulListTab';
import JudulListTab from '../../../_com/judulListTab';
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
import grafikBerdasarkanResponden from '../../../_state/ppid/indeks_kepuasan_masyarakat/grafikBerdasarkanResponden';
import HeaderSearch from '../../../_com/header';

View File

@@ -9,7 +9,7 @@ import { useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';
import { Cell, Pie, PieChart } from 'recharts';
import { useProxy } from 'valtio/utils';
import JudulListTab from '../../../_com/jusulListTab';
import JudulListTab from '../../../_com/judulListTab';
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
import HeaderSearch from '../../../_com/header';

View File

@@ -1,5 +1,5 @@
'use client'
import JudulListTab from '@/app/admin/(dashboard)/_com/jusulListTab';
import JudulListTab from '@/app/admin/(dashboard)/_com/judulListTab';
import colors from '@/con/colors';
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core';
import { useMediaQuery, useShallowEffect } from '@mantine/hooks';

View File

@@ -235,7 +235,7 @@ export const navBar = [
{
id: "Ekonomi_5",
name: "Jumlah Pengangguran",
path: "/admin/ekonomi/jumlah-pengangguran/detail-data-pengangguran"
path: "/admin/ekonomi/jumlah-pengangguran"
},
{
id: "Ekonomi_6",

View File

@@ -1,10 +1,10 @@
import Elysia, { t } from "elysia";
import detailDataPengangguranFindMany from "./findMany";
import detailDataPengangguranCreate from "./create";
import detailDataPengangguranUpdate from "./updt";
import detailDataPengangguranFindUnique from "./findUnique";
import detailDataPengangguranDelete from "./del";
import findByMonthYear from "./findByMonthYear";
import detailDataPengangguranFindMany from "./findMany";
import detailDataPengangguranFindUnique from "./findUnique";
import detailDataPengangguranUpdate from "./updt";
const DetailDataPengangguran = new Elysia({
prefix: "/detaildatapengangguran",
@@ -50,6 +50,6 @@ const DetailDataPengangguran = new Elysia({
month: t.String(),
year: t.String(),
}),
});
})
export default DetailDataPengangguran;

View File

@@ -1,13 +1,10 @@
import Elysia from "elysia";
import DetailDataPengangguran from "./detail-data-pengangguran";
import RingkasanDataPengangguran from "./ringkasan-data-pengangguran";
import SedangMencariKerja from "./sedang-mencari-kerja";
const JumlahPengangguran = new Elysia({
prefix: "/jumlahpengangguran",
})
.use(DetailDataPengangguran)
.use(RingkasanDataPengangguran)
.use(SedangMencariKerja)
export default JumlahPengangguran;

View File

@@ -1,61 +0,0 @@
import prisma from "@/lib/prisma";
import { Prisma } from "@prisma/client";
import { Context } from "elysia";
type FormCreate = Prisma.RingkasanDataPengangguranGetPayload<{
select: {
year: true;
totalUnemployment: true;
educatedUnemployment: true;
percentageEducatedOfTotal: true;
productiveAgePopulation: true;
percentageProductiveOfTotal: true;
percentageChangeFromPreviousYear: true;
};
}>;
export default async function ringkasanDataPengangguranCreate(context: Context) {
const body = context.body as FormCreate;
const existing = await prisma.ringkasanDataPengangguran.findFirst({
where: {
year: body.year,
},
});
if (existing) {
return {
success: false,
message: `Data tahun ${body.year} sudah ada.`,
data: null,
};
}
const created = await prisma.ringkasanDataPengangguran.create({
data: {
year: body.year,
totalUnemployment: body.totalUnemployment,
educatedUnemployment: body.educatedUnemployment,
percentageEducatedOfTotal: body.percentageEducatedOfTotal,
productiveAgePopulation: body.productiveAgePopulation,
percentageProductiveOfTotal: body.percentageProductiveOfTotal,
percentageChangeFromPreviousYear: body.percentageChangeFromPreviousYear,
},
select: {
id: true,
year: true,
totalUnemployment: true,
educatedUnemployment: true,
percentageEducatedOfTotal: true,
productiveAgePopulation: true,
percentageProductiveOfTotal: true,
percentageChangeFromPreviousYear: true,
},
});
return {
success: true,
message: "Berhasil menambahkan data ringkasan pengangguran",
data: created,
};
}

View File

@@ -1,38 +0,0 @@
import prisma from "@/lib/prisma";
import { Context } from "elysia";
export default async function ringkasanDataPengangguranDelete(context: Context) {
const id = context.params?.id;
if (!id) {
return {
success: false,
message: "ID tidak ditemukan",
}
}
const existing = await prisma.ringkasanDataPengangguran.findUnique({
where: {
id: id,
},
})
if (!existing) {
return {
success: false,
message: "Data tidak ditemukan",
}
}
const deleted = await prisma.ringkasanDataPengangguran.delete({
where: {
id: id,
},
})
return {
success: true,
message: "Berhasil menghapus data ringkasan pengangguran",
data: deleted,
}
}

View File

@@ -1,10 +0,0 @@
import prisma from "@/lib/prisma";
export default async function ringkasanDataPengangguranFindMany() {
const res = await prisma.ringkasanDataPengangguran.findMany({
orderBy: [{ year: "desc" }],
});
return {
data: res,
};
}

View File

@@ -1,45 +0,0 @@
import prisma from "@/lib/prisma";
export default async function ringkasanDataPengangguranFindUnique(request: Request) {
const url = new URL(request.url);
const pathSegments = url.pathname.split('/');
const id = pathSegments[pathSegments.length - 1];
if (!id) {
return Response.json({
success: false,
message: "ID tidak boleh kosong",
}, { status: 400 });
}
try {
if (typeof id !== 'string') {
return Response.json({
success: false,
message: "ID tidak valid",
}, { status: 400 });
}
const data = await prisma.ringkasanDataPengangguran.findUnique({
where: { id },
});
if (!data) {
return Response.json({
success: false,
message: "Data tidak ditemukan",
}, { status: 404 });
}
return Response.json({
success: true,
data: data,
}, { status: 200 });
} catch (error) {
console.error("Error fetching data:", error);
return Response.json({
success: false,
message: "Terjadi kesalahan saat mengambil data",
}, { status: 500 });
}
}

View File

@@ -1,50 +0,0 @@
import Elysia, { t } from "elysia";
import ringkasanDataPengangguranFindUnique from "./findUnique";
import ringkasanDataPengangguranFindMany from "./findMany";
import ringkasanDataPengangguranCreate from "./create";
import ringkasanDataPengangguranUpdate from "./updt";
import ringkasanDataPengangguranDelete from "./del";
const RingkasanDataPengangguran = new Elysia({
prefix: "/ringkasandatapengangguran",
tags: ["Ekonomi/Jumlah Pengangguran/Ringkasan Data Pengangguran"],
})
.get("/:id", async (context) => {
const response = await ringkasanDataPengangguranFindUnique(
new Request(context.request)
);
return response;
})
.get("/find-many", ringkasanDataPengangguranFindMany)
.post("/create", ringkasanDataPengangguranCreate, {
body: t.Object({
year: t.Number(),
totalUnemployment: t.Number(),
educatedUnemployment: t.Number(),
percentageEducatedOfTotal: t.Number(),
productiveAgePopulation: t.Number(),
percentageProductiveOfTotal: t.Number(),
percentageChangeFromPreviousYear: t.Number(),
}),
})
.put("/:id", ringkasanDataPengangguranUpdate, {
params: t.Object({
id: t.String(),
}),
body: t.Object({
year: t.Number(),
totalUnemployment: t.Number(),
educatedUnemployment: t.Number(),
percentageEducatedOfTotal: t.Number(),
productiveAgePopulation: t.Number(),
percentageProductiveOfTotal: t.Number(),
percentageChangeFromPreviousYear: t.Number(),
}),
})
.delete("/del/:id", ringkasanDataPengangguranDelete, {
params: t.Object({
id: t.String(),
}),
});
export default RingkasanDataPengangguran;

View File

@@ -1,70 +0,0 @@
import prisma from "@/lib/prisma";
import { Context } from "elysia";
export default async function ringkasanDataPengangguranUpdate(context: Context) {
const id = context.params?.id;
if (!id) {
return {
success: false,
message: "ID tidak ditemukan",
}
}
const { year, totalUnemployment, educatedUnemployment, percentageEducatedOfTotal, productiveAgePopulation, percentageProductiveOfTotal, percentageChangeFromPreviousYear } = context.body as {
year: number;
totalUnemployment: number;
educatedUnemployment: number;
percentageEducatedOfTotal: number;
productiveAgePopulation: number;
percentageProductiveOfTotal: number;
percentageChangeFromPreviousYear: number;
}
const duplicate = await prisma.ringkasanDataPengangguran.findFirst({
where: {
year,
NOT: { id },
},
});
if (duplicate) {
return {
success: false,
message: `Data tahun ${year} sudah ada.`,
};
}
const existing = await prisma.ringkasanDataPengangguran.findUnique({
where: {
id: id,
},
})
if (!existing) {
return {
success: false,
message: "Data tidak ditemukan",
}
}
const updated = await prisma.ringkasanDataPengangguran.update({
where: { id },
data: {
year,
totalUnemployment,
educatedUnemployment,
percentageEducatedOfTotal,
productiveAgePopulation,
percentageProductiveOfTotal,
percentageChangeFromPreviousYear,
},
})
return {
success: true,
message: "Data berhasil diupdate",
data: updated,
}
}

View File

@@ -1,49 +0,0 @@
import prisma from "@/lib/prisma";
import { Prisma } from "@prisma/client";
import { Context } from "elysia";
type FormCreate = Prisma.SedangMencariKerjaGetPayload<{
select: {
count: true;
percentageOfTotal: true;
recordedDate: true;
};
}>;
export default async function sedangMencariKerjaCreate(context: Context) {
const body = context.body as FormCreate;
const existing = await prisma.sedangMencariKerja.findFirst({
where: {
recordedDate: body.recordedDate,
},
});
if (existing) {
return {
success: false,
message: "Data sudah ada",
data: null,
};
}
const created = await prisma.sedangMencariKerja.create({
data: {
count: body.count,
percentageOfTotal: body.percentageOfTotal,
recordedDate: body.recordedDate,
},
select: {
id: true,
count: true,
percentageOfTotal: true,
recordedDate: true,
},
});
return {
success: true,
message: "Berhasil menambahkan data sedang mencari kerja",
data: created,
};
}

View File

@@ -1,38 +0,0 @@
import prisma from "@/lib/prisma";
import { Context } from "elysia";
export default async function sedangMencariKerjaDelete(context: Context) {
const id = context.params?.id;
if (!id) {
return {
success: false,
message: "ID tidak ditemukan",
}
}
const existing = await prisma.sedangMencariKerja.findUnique({
where: {
id: id,
},
})
if (!existing) {
return {
success: false,
message: "Data tidak ditemukan",
}
}
const deleted = await prisma.sedangMencariKerja.delete({
where: {
id: id,
},
})
return {
success: true,
message: "Berhasil menghapus data sedang mencari kerja",
data: deleted,
}
}

View File

@@ -1,10 +0,0 @@
import prisma from "@/lib/prisma";
export default async function sedangMencariKerjaFindMany() {
const res = await prisma.sedangMencariKerja.findMany({
orderBy: [{ recordedDate: "desc" }],
});
return {
data: res,
};
}

View File

@@ -1,46 +0,0 @@
import prisma from "@/lib/prisma";
export default async function sedangMencariKerjaFindUnique(request: Request) {
const url = new URL(request.url);
const pathSegments = url.pathname.split('/');
const id = pathSegments[pathSegments.length - 1];
if (!id) {
return Response.json({
success: false,
message: "ID tidak boleh kosong",
}, { status: 400 });
}
try {
if (typeof id !== 'string') {
return Response.json({
success: false,
message: "ID tidak valid",
}, { status: 400 });
}
const data = await prisma.sedangMencariKerja.findUnique({
where: { id },
});
if (!data) {
return Response.json({
success: false,
message: "Data tidak ditemukan",
}, { status: 404 });
}
return Response.json({
success: true,
message: "Data ditemukan",
data,
}, { status: 200 });
} catch (error) {
console.error("Error fetching data:", error);
return Response.json({
success: false,
message: "Terjadi kesalahan saat mengambil data",
}, { status: 500 });
}
}

View File

@@ -1,42 +0,0 @@
import Elysia, { t } from "elysia";
import sedangMencariKerjaFindUnique from "./findUnique";
import sedangMencariKerjaFindMany from "./findMany";
import sedangMencariKerjaCreate from "./create";
import sedangMencariKerjaUpdate from "./updt";
import sedangMencariKerjaDelete from "./del";
const SedangMencariKerja = new Elysia({
prefix: "/sedangmencarikerja",
tags: ["Ekonomi/Jumlah Pengangguran/Sedang Mencari Kerja"],
})
.get("/:id", async (context) => {
const response = await sedangMencariKerjaFindUnique(
new Request(context.request)
);
return response;
})
.get("/find-many", sedangMencariKerjaFindMany)
.post("/create", sedangMencariKerjaCreate, {
body: t.Object({
count: t.Number(),
percentageOfTotal: t.Number(),
recordedDate: t.String(),
}),
})
.put("/:id", sedangMencariKerjaUpdate, {
params: t.Object({
id: t.String(),
}),
body: t.Object({
count: t.Number(),
percentageOfTotal: t.Number(),
recordedDate: t.String(),
}),
})
.delete("/del/:id", sedangMencariKerjaDelete, {
params: t.Object({
id: t.String(),
}),
});
export default SedangMencariKerja;

View File

@@ -1,49 +0,0 @@
import prisma from "@/lib/prisma";
import { Context } from "elysia";
export default async function sedangMencariKerjaUpdate(context: Context) {
const id = context.params?.id;
if (!id) {
return {
success: false,
message: "ID tidak ditemukan",
}
}
const { count, percentageOfTotal, recordedDate } = context.body as {
count: number;
percentageOfTotal: number;
recordedDate: string;
}
const existing = await prisma.sedangMencariKerja.findUnique({
where: {
id: id,
},
})
if (!existing) {
return {
success: false,
message: "Data tidak ditemukan",
}
}
const updated = await prisma.sedangMencariKerja.update({
where: {
id: id,
},
data: {
count: count,
percentageOfTotal: percentageOfTotal,
recordedDate: recordedDate,
},
})
return {
success: true,
message: "Berhasil mengupdate data sedang mencari kerja",
data: updated,
}
}

View File

@@ -1,9 +1,8 @@
import colors from '@/con/colors';
import { Stack, Box, Text, Group, Flex, Button, SimpleGrid, Paper, Center, ColorSwatch, TableTd, TableTr, Table, TableTbody, TableTh, TableThead } from '@mantine/core';
import React from 'react';
import BackButton from '../../desa/layanan/_com/BackButto';
import { IconBriefcase, IconChevronDown, IconDownload, IconSchool, IconUserOff, IconUsersGroup } from '@tabler/icons-react';
import { BarChart } from '@mantine/charts';
import { Box, Button, Center, ColorSwatch, Flex, Group, Paper, SimpleGrid, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
import { IconChevronDown, IconDownload, IconSchool, IconSchoolOff, IconUserOff } from '@tabler/icons-react';
import BackButton from '../../desa/layanan/_com/BackButto';
const data1 = [
{
@@ -22,17 +21,10 @@ const data1 = [
},
{
id: 3,
icon: <IconUsersGroup size={35} />,
judul: 'Usia Produktif',
jumlah: '125',
persentase: <Text fz={'h4'} c={'gray'}>89.3% dari total</Text>
},
{
id: 4,
icon: <IconBriefcase size={35} />,
judul: 'Sedang Mencari Kerja',
jumlah: '95',
persentase: <Text fz={'h4'} c={'red'}>67.9% dari total</Text>
icon: <IconSchoolOff size={35} />,
judul: 'Pengangguran Tidak Terdidik',
jumlah: '60',
persentase: <Text fz={'h4'} c={'gray'}>42.9% dari total</Text>
},
]