API & State Jumlah Pengangguran

This commit is contained in:
2025-07-09 12:00:37 +08:00
parent 124dfb8160
commit 119275b95c
35 changed files with 1635 additions and 18 deletions

View File

@@ -0,0 +1,51 @@
[
{
"month": "Jan",
"year": 2025,
"totalUnemployment": 160,
"educatedUnemployment": 95,
"uneducatedUnemployment": 65,
"percentageChange": null
},
{
"month": "Feb",
"year": 2025,
"totalUnemployment": 155,
"educatedUnemployment": 90,
"uneducatedUnemployment": 65,
"percentageChange": -3.1
},
{
"month": "Mar",
"year": 2025,
"totalUnemployment": 150,
"educatedUnemployment": 88,
"uneducatedUnemployment": 62,
"percentageChange": -3.2
},
{
"month": "Apr",
"year": 2025,
"totalUnemployment": 148,
"educatedUnemployment": 85,
"uneducatedUnemployment": 63,
"percentageChange": -1.3
},
{
"month": "Mei",
"year": 2025,
"totalUnemployment": 145,
"educatedUnemployment": 82,
"uneducatedUnemployment": 63,
"percentageChange": -2.0
},
{
"month": "Jun",
"year": 2025,
"totalUnemployment": 140,
"educatedUnemployment": 80,
"uneducatedUnemployment": 60,
"percentageChange": -3.4
}
]

View File

@@ -0,0 +1,12 @@
[
{
"year": 2025,
"totalUnemployment": 140,
"educatedUnemployment": 80,
"percentageEducatedOfTotal": 57.1,
"productiveAgePopulation": 125,
"percentageProductiveOfTotal": 89.3,
"percentageChangeFromPreviousYear": -12.5
}
]

View File

@@ -0,0 +1,8 @@
[
{
"recordedDate": "2025-06-30",
"count": 95,
"percentageOfTotal": 67.9
}
]

View File

@@ -1262,3 +1262,46 @@ model DataDemografiPekerjaan {
deletedAt DateTime @default(now()) deletedAt DateTime @default(now())
isActive Boolean @default(true) isActive Boolean @default(true)
} }
// ========================================= JUMLAH PENGANGGURAN ========================================= //
model DetailDataPengangguran {
id String @id @default(uuid()) @db.Uuid
month String @db.VarChar(20)
year Int
totalUnemployment Int
educatedUnemployment Int
uneducatedUnemployment Int
percentageChange Float?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
@@unique([month, year])
}
model RingkasanDataPengangguran {
id String @id @default(uuid()) @db.Uuid
year Int @unique
totalUnemployment Int
educatedUnemployment Int
percentageEducatedOfTotal Float?
productiveAgePopulation Int?
percentageProductiveOfTotal Float?
percentageChangeFromPreviousYear Float?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}
model SedangMencariKerja {
id String @id @default(uuid()) @db.Uuid
count Int
percentageOfTotal Float?
recordedDate DateTime @unique @db.Date
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
isActive Boolean @default(true)
}

View File

@@ -21,6 +21,9 @@ import kategoriProduk from "./data/ekonomi/pasar-desa/kategori-produk.json";
import hubunganOrganisasi from "./data/ekonomi/struktur-organisasi/hubungan-organisasi.json"; import hubunganOrganisasi from "./data/ekonomi/struktur-organisasi/hubungan-organisasi.json";
import posisiOrganisasi from "./data/ekonomi/struktur-organisasi/posisi-organisasi.json"; import posisiOrganisasi from "./data/ekonomi/struktur-organisasi/posisi-organisasi.json";
import pegawai from "./data/ekonomi/struktur-organisasi/pegawai.json"; import pegawai from "./data/ekonomi/struktur-organisasi/pegawai.json";
import detailDataPengangguran from './data/ekonomi/jumlah-pengangguran/detail-data-pengangguran.json';
import ringkasanDataPengangguran from './data/ekonomi/jumlah-pengangguran/ringkasan-data-pengangguran.json';
import sedangMencariKerja from './data/ekonomi/jumlah-pengangguran/sedang-mencari-kerja.json';
(async () => { (async () => {
for (const l of layanan) { for (const l of layanan) {
@@ -431,6 +434,75 @@ import pegawai from "./data/ekonomi/struktur-organisasi/pegawai.json";
}); });
} }
console.log("hubungan organisasi success ..."); console.log("hubungan organisasi success ...");
for (const d of detailDataPengangguran) {
await prisma.detailDataPengangguran.upsert({
where: {
month_year: { month: d.month, year: d.year },
},
update: {
totalUnemployment: d.totalUnemployment,
educatedUnemployment: d.educatedUnemployment,
uneducatedUnemployment: d.uneducatedUnemployment,
percentageChange: d.percentageChange,
},
create: {
month: d.month,
year: d.year,
totalUnemployment: d.totalUnemployment,
educatedUnemployment: d.educatedUnemployment,
uneducatedUnemployment: d.uneducatedUnemployment,
percentageChange: d.percentageChange,
},
});
}
console.log("📊 detailDataPengangguran success ...");
// RingkasanDataPengangguran
for (const r of ringkasanDataPengangguran) {
await prisma.ringkasanDataPengangguran.upsert({
where: {
year: r.year,
},
update: {
totalUnemployment: r.totalUnemployment,
educatedUnemployment: r.educatedUnemployment,
percentageEducatedOfTotal: r.percentageEducatedOfTotal,
productiveAgePopulation: r.productiveAgePopulation,
percentageProductiveOfTotal: r.percentageProductiveOfTotal,
percentageChangeFromPreviousYear: r.percentageChangeFromPreviousYear,
},
create: {
year: r.year,
totalUnemployment: r.totalUnemployment,
educatedUnemployment: r.educatedUnemployment,
percentageEducatedOfTotal: r.percentageEducatedOfTotal,
productiveAgePopulation: r.productiveAgePopulation,
percentageProductiveOfTotal: r.percentageProductiveOfTotal,
percentageChangeFromPreviousYear: r.percentageChangeFromPreviousYear,
},
});
}
console.log("📈 ringkasanDataPengangguran success ...");
// SedangMencariKerja
for (const s of sedangMencariKerja) {
await prisma.sedangMencariKerja.upsert({
where: {
recordedDate: new Date(s.recordedDate),
},
update: {
count: s.count,
percentageOfTotal: s.percentageOfTotal,
},
create: {
recordedDate: new Date(s.recordedDate),
count: s.count,
percentageOfTotal: s.percentageOfTotal,
},
});
}
console.log("💼 sedangMencariKerja success ...");
})() })()
.then(() => prisma.$disconnect()) .then(() => prisma.$disconnect())
.catch((e) => { .catch((e) => {

View File

@@ -0,0 +1,638 @@
import ApiFetch from "@/lib/api-fetch";
import { Prisma } from "@prisma/client";
import { toast } from "react-toastify";
import { proxy } from "valtio";
import { z } from "zod";
const templateJumlahPengngguran = z.object({
month: z.string().min(1, "Bulan harus diisi"),
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"),
uneducatedUnemployment: z
.number()
.min(1, "Pengangguran tidak pendidikan harus diisi"),
percentageChange: z.number().min(0, "Persentase perubahan harus diisi"),
});
type JumlahPengangguran = {
month: string;
year: number;
totalUnemployment: number;
educatedUnemployment: number;
uneducatedUnemployment: number;
percentageChange: number;
};
const jumlahPengangguranForm: JumlahPengangguran = {
month: "",
year: 0,
totalUnemployment: 0,
educatedUnemployment: 0,
uneducatedUnemployment: 0,
percentageChange: 0,
};
const jumlahPengangguran = proxy({
create: {
form: jumlahPengangguranForm,
loading: false,
async create() {
const cek = templateJumlahPengngguran.safeParse(
jumlahPengangguran.create.form
);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
.join("\n")}] required`;
toast.error(err);
return null;
}
try {
jumlahPengangguran.create.loading = true;
const res =
await ApiFetch.api.ekonomi.jumlahpengangguran.detaildatapengangguran[
"create"
].post(jumlahPengangguran.create.form);
if (res.status === 200) {
const id = res.data?.data?.id;
if (id) {
toast.success("Success create");
jumlahPengangguran.create.form = { ...jumlahPengangguranForm };
jumlahPengangguran.findMany.load();
return id;
}
}
toast.error("failed create");
return null;
} catch (error) {
console.log((error as Error).message);
return null;
} finally {
jumlahPengangguran.create.loading = false;
}
},
},
findMany: {
data: null as
| Prisma.DetailDataPengangguranGetPayload<{
omit: { isActive: true };
}>[]
| null,
async load() {
const res =
await ApiFetch.api.ekonomi.jumlahpengangguran.detaildatapengangguran[
"find-many"
].get();
if (res.status === 200) {
jumlahPengangguran.findMany.data = res.data?.data ?? [];
}
},
},
findUnique: {
data: null as Prisma.DetailDataPengangguranGetPayload<{
omit: { isActive: true };
}> | null,
async load(id: string) {
try {
const res = await fetch(
`/api/ekonomi/jumlahpengangguran/detaildatapengangguran/${id}`
);
if (res.ok) {
const data = await res.json();
jumlahPengangguran.findUnique.data = data.data ?? null;
} else {
console.error("Failed to fetch jumlahPengangguran:", res.statusText);
jumlahPengangguran.findUnique.data = null;
}
} catch (error) {
console.error("Error fetching jumlahPengangguran:", error);
jumlahPengangguran.findUnique.data = null;
}
},
},
update: {
id: "",
form: { ...jumlahPengangguranForm },
loading: false,
async submit() {
const id = this.id;
if (!id) {
toast.warn("ID tidak valid");
return null;
}
const formData = {
month: this.form.month,
year: this.form.year,
totalUnemployment: this.form.totalUnemployment,
educatedUnemployment: this.form.educatedUnemployment,
uneducatedUnemployment: this.form.uneducatedUnemployment,
percentageChange: this.form.percentageChange,
};
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/detaildatapengangguran/${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 jumlahPengangguran.findMany.load();
return result.data;
} catch (error) {
console.error("Update error:", error);
toast.error("Gagal update data jumlah pengangguran");
throw error;
} finally {
this.loading = false;
}
},
},
delete: {
loading: false,
async byId(id: string) {
if (!id) return toast.warn("ID tidak valid");
try {
jumlahPengangguran.delete.loading = true;
const response = await fetch(
`/api/ekonomi/jumlahpengangguran/detaildatapengangguran/del/${id}`,
{
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
}
);
const result = await response.json();
if (response.ok && result?.success) {
toast.success(
result.message || "Jumlah pengangguran berhasil dihapus"
);
await jumlahPengangguran.findMany.load();
} else {
toast.error(result?.message || "Gagal menghapus jumlah pengangguran");
}
} catch (error) {
console.error("Gagal delete:", error);
toast.error("Terjadi kesalahan saat menghapus jumlah pengangguran");
} finally {
jumlahPengangguran.delete.loading = false;
}
},
},
});
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

@@ -1,11 +1,11 @@
'use client' 'use client'
import colors from '@/con/colors'; import colors from '@/con/colors';
import { BarChart } from '@mantine/charts';
import { Box, Button, Paper, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core'; import { Box, Button, Paper, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core';
import { useMediaQuery, useShallowEffect } from '@mantine/hooks'; import { useShallowEffect } from '@mantine/hooks';
import { IconEdit, IconSearch, IconTrash } from '@tabler/icons-react'; import { IconEdit, 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 { Bar, BarChart, Legend, Tooltip, XAxis, YAxis } from 'recharts';
import { useProxy } from 'valtio/utils'; import { useProxy } from 'valtio/utils';
import HeaderSearch from '../../_com/header'; import HeaderSearch from '../../_com/header';
import JudulList from '../../_com/judulList'; import JudulList from '../../_com/judulList';
@@ -40,8 +40,6 @@ function ListDemografiPekerjaan({ search }: { search: string }) {
const stateDemografi = useProxy(demografiPekerjaan) const stateDemografi = useProxy(demografiPekerjaan)
const [chartData, setChartData] = useState<DemografiPekerjaan[]>([]); const [chartData, setChartData] = useState<DemografiPekerjaan[]>([]);
const [mounted, setMounted] = useState(false); // untuk memastikan DOM sudah ready const [mounted, setMounted] = useState(false); // untuk memastikan DOM sudah ready
const isTablet = useMediaQuery('(max-width: 1024px)')
const isMobile = useMediaQuery('(max-width: 768px)')
const [modalHapus, setModalHapus] = useState(false) const [modalHapus, setModalHapus] = useState(false)
const [selectedId, setSelectedId] = useState<string | null>(null) const [selectedId, setSelectedId] = useState<string | null>(null)
@@ -54,7 +52,7 @@ function ListDemografiPekerjaan({ search }: { search: string }) {
stateDemografi.findMany.load() stateDemografi.findMany.load()
} }
} }
useShallowEffect(() => { useShallowEffect(() => {
setMounted(true) setMounted(true)
stateDemografi.findMany.load() stateDemografi.findMany.load()
@@ -104,7 +102,7 @@ function ListDemografiPekerjaan({ search }: { search: string }) {
<TableTd>{item.lakiLaki}</TableTd> <TableTd>{item.lakiLaki}</TableTd>
<TableTd>{item.perempuan}</TableTd> <TableTd>{item.perempuan}</TableTd>
<TableTd> <TableTd>
<Button color='green' onClick={() => router.push(`/admin/kesehatan/data-kesehatan-warga/persentase_data_kelahiran_kematian/${item.id}`)}> <Button color='green' onClick={() => router.push(`/admin/ekonomi/demografi-pekerjaan/${item.id}`)}>
<IconEdit size={20} /> <IconEdit size={20} />
</Button> </Button>
</TableTd> </TableTd>
@@ -138,14 +136,18 @@ function ListDemografiPekerjaan({ search }: { search: string }) {
<Paper bg={colors['white-1']} p={'md'}> <Paper bg={colors['white-1']} p={'md'}>
<Title pb={10} order={4}>Data Kelahiran & Kematian</Title> <Title pb={10} order={4}>Data Kelahiran & Kematian</Title>
{mounted && chartData.length > 0 && ( {mounted && chartData.length > 0 && (
<BarChart width={isMobile ? 450 : isTablet ? 500 : 550} height={350} data={chartData} > <Box w={{ base: '100%', md: '30%' }}>
<XAxis dataKey="pekerjaan" /> <BarChart
<YAxis /> h={450}
<Tooltip /> data={chartData}
<Legend /> dataKey="pekerjaan"
<Bar dataKey="lakiLaki" fill="#f03e3e" name="Laki - Laki" /> type="stacked"
<Bar dataKey="perempuan" fill="#ff922b" name="Perempuan" /> series={[
</BarChart> { name: 'lakiLaki', color: 'red.6', label: 'Laki - Laki' },
{ name: 'perempuan', color: 'orange.6', label: 'Perempuan' },
]}
/>
</Box>
)} )}
</Paper> </Paper>
</Box> </Box>

View File

@@ -234,8 +234,8 @@ export const navBar = [
}, },
{ {
id: "Ekonomi_5", id: "Ekonomi_5",
name: "Jumlah Pengangguran 2024-2025", name: "Jumlah Pengangguran",
path: "/admin/ekonomi/jumlah-pengangguran-2024-2025" path: "/admin/ekonomi/jumlah-pengangguran"
}, },
{ {
id: "Ekonomi_6", id: "Ekonomi_6",
@@ -244,8 +244,8 @@ export const navBar = [
}, },
{ {
id: "Ekonomi_7", id: "Ekonomi_7",
name: "Jumlah Penduduk Miskin 2024-2025", name: "Jumlah Penduduk Miskin",
path: "/admin/ekonomi/jumlah-penduduk-miskin-2024-2025" path: "/admin/ekonomi/jumlah-penduduk-miskin"
}, },
{ {
id: "Ekonomi_8", id: "Ekonomi_8",

View File

@@ -9,6 +9,7 @@ import GrafikMenganggurBerdasarkanPendidikan from "./usia-kerja-yang-menganggur/
import JumlahPendudukMiskin from "./jumlah-penduduk-miskin"; import JumlahPendudukMiskin from "./jumlah-penduduk-miskin";
import SektorUnggulanDesa from "./sektor-unggulan-desa"; import SektorUnggulanDesa from "./sektor-unggulan-desa";
import DemografiPekerjaan from "./demografi-pekerjaan"; import DemografiPekerjaan from "./demografi-pekerjaan";
import JumlahPengangguran from "./jumlah-pengangguran";
const Ekonomi = new Elysia({ const Ekonomi = new Elysia({
prefix: "/api/ekonomi", prefix: "/api/ekonomi",
@@ -24,5 +25,6 @@ const Ekonomi = new Elysia({
.use(JumlahPendudukMiskin) .use(JumlahPendudukMiskin)
.use(SektorUnggulanDesa) .use(SektorUnggulanDesa)
.use(DemografiPekerjaan) .use(DemografiPekerjaan)
.use(JumlahPengangguran)
export default Ekonomi export default Ekonomi

View File

@@ -0,0 +1,60 @@
import prisma from "@/lib/prisma";
import { Prisma } from "@prisma/client";
import { Context } from "elysia";
type FormCreate = Prisma.DetailDataPengangguranGetPayload<{
select: {
month: true;
year: true;
totalUnemployment: true;
educatedUnemployment: true;
uneducatedUnemployment: true;
percentageChange: true;
};
}>;
export default async function detailDataPengangguranCreate(context: Context) {
const body = context.body as FormCreate;
// Cek apakah data untuk bulan & tahun tersebut sudah ada
const existing = await prisma.detailDataPengangguran.findFirst({
where: {
month: body.month,
year: body.year,
},
});
if (existing) {
return {
success: false,
message: `Data bulan ${body.month} ${body.year} sudah ada.`,
data: null,
};
}
const created = await prisma.detailDataPengangguran.create({
data: {
month: body.month,
year: body.year,
totalUnemployment: body.totalUnemployment,
educatedUnemployment: body.educatedUnemployment,
uneducatedUnemployment: body.uneducatedUnemployment,
percentageChange: body.percentageChange ?? null,
},
select: {
id: true,
month: true,
year: true,
totalUnemployment: true,
educatedUnemployment: true,
uneducatedUnemployment: true,
percentageChange: true,
},
});
return {
success: true,
message: "Berhasil menambahkan data pengangguran bulanan",
data: created,
};
}

View File

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

View File

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

View File

@@ -0,0 +1,46 @@
import prisma from "@/lib/prisma";
export default async function detailDataPengangguranFindUnique(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.detailDataPengangguran.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: 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

@@ -0,0 +1,48 @@
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";
const DetailDataPengangguran = new Elysia({
prefix: "/detaildatapengangguran",
tags: ["Ekonomi/Jumlah Pengangguran/Detail Data Pengangguran"],
})
.get("/:id", async (context) => {
const response = await detailDataPengangguranFindUnique(
new Request(context.request)
);
return response;
})
.get("/find-many", detailDataPengangguranFindMany)
.post("/create", detailDataPengangguranCreate, {
body: t.Object({
month: t.String(),
year: t.Number(),
totalUnemployment: t.Number(),
educatedUnemployment: t.Number(),
uneducatedUnemployment: t.Number(),
percentageChange: t.Number(),
}),
})
.put("/:id", detailDataPengangguranUpdate, {
params: t.Object({
id: t.String(),
}),
body: t.Object({
month: t.String(),
year: t.Number(),
totalUnemployment: t.Number(),
educatedUnemployment: t.Number(),
uneducatedUnemployment: t.Number(),
percentageChange: t.Number(),
}),
})
.delete("/del/:id", detailDataPengangguranDelete, {
params: t.Object({
id: t.String(),
}),
});
export default DetailDataPengangguran;

View File

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

View File

@@ -0,0 +1,13 @@
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

@@ -0,0 +1,61 @@
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

@@ -0,0 +1,38 @@
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

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

View File

@@ -0,0 +1,45 @@
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

@@ -0,0 +1,50 @@
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

@@ -0,0 +1,70 @@
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

@@ -0,0 +1,49 @@
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

@@ -0,0 +1,38 @@
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

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

View File

@@ -0,0 +1,46 @@
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

@@ -0,0 +1,42 @@
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

@@ -0,0 +1,49 @@
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,
}
}