QC Admin - User Menu Ekonomi : Jumlah Pengangguran

This commit is contained in:
2025-09-16 10:11:54 +08:00
parent a5d841bb6b
commit 4ceea5203f
97 changed files with 6023 additions and 3481 deletions

View File

@@ -27,6 +27,9 @@ export default async function penghargaanFindMany(context: Context) {
skip,
take: limit,
orderBy: { createdAt: "desc" },
include: {
image: true,
},
}),
prisma.penghargaan.count({ where }),
]);

View File

@@ -1,132 +1,71 @@
// 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,
// };
// }
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;
};
}>;
// Define month order for calculation
const monthOrder = ["Jan", "Feb", "Mar", "Apr", "Mei", "Jun", "Jul", "Agu", "Sep", "Okt", "Nov", "Des"];
export default async function detailDataPengangguranCreate(context: Context) {
type FormCreate = {
month: string;
year: number;
totalUnemployment: number;
educatedUnemployment: number;
uneducatedUnemployment: number;
percentageChange?: number; // Make it optional since we'll calculate it
};
export default async function createDetailDataPengangguran(context: Context) {
const body = context.body as FormCreate;
const yearNumber = parseInt(body.year.toString(), 10);
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,
};
if (isNaN(yearNumber)) {
throw new Error("Year harus berupa angka");
}
// Cari bulan sebelumnya
// Find current month's index
const currentMonthIndex = monthOrder.indexOf(body.month);
const prevMonth = currentMonthIndex > 0 ? monthOrder[currentMonthIndex - 1] : "Des";
if (currentMonthIndex === -1) {
throw new Error("Bulan tidak valid");
}
// Calculate previous month and year
let prevMonthIndex = currentMonthIndex - 1;
let prevYear = yearNumber;
// Handle year as number for calculations
const currentYear = typeof body.year === 'number' ? body.year : new Date(body.year).getFullYear();
const prevYear = currentMonthIndex > 0 ? currentYear : currentYear - 1;
if (prevMonthIndex < 0) {
prevMonthIndex = 11; // December of previous year
prevYear--; // Decrement year
}
// Create date objects for Prisma
const currentYearDate = new Date(currentYear, 0, 1);
const prevYearDate = new Date(prevYear, 0, 1);
const prevData = await prisma.detailDataPengangguran.findFirst({
const prevMonth = monthOrder[prevMonthIndex];
// Find previous month's data
const prevMonthData = await prisma.detailDataPengangguran.findFirst({
where: {
month: prevMonth,
year: prevYearDate,
year: prevYear,
},
orderBy: { year: 'desc' },
});
let percentageChange: number | null = null;
if (prevData) {
const change = ((Number(body.totalUnemployment) - Number(prevData.totalUnemployment)) / Number(prevData.totalUnemployment)) * 100;
percentageChange = parseFloat(change.toFixed(1));
// Calculate percentage change if previous data exists
let percentageChange = 0;
if (prevMonthData && prevMonthData.totalUnemployment > 0) {
const change = ((body.totalUnemployment - prevMonthData.totalUnemployment) /
prevMonthData.totalUnemployment) * 100;
percentageChange = parseFloat(change.toFixed(1)); // Round to 1 decimal place
} else if (body.percentageChange !== undefined) {
// Fallback to provided value if no previous data
percentageChange = Number(body.percentageChange) || 0;
}
// Create the new record with properly typed values
const created = await prisma.detailDataPengangguran.create({
data: {
month: body.month,
year: currentYearDate,
year: yearNumber,
totalUnemployment: Number(body.totalUnemployment),
educatedUnemployment: Number(body.educatedUnemployment),
uneducatedUnemployment: Number(body.uneducatedUnemployment),
percentageChange,
percentageChange, // This is now calculated based on previous month's data
},
select: {
id: true,
@@ -136,12 +75,9 @@ export default async function detailDataPengangguranCreate(context: Context) {
educatedUnemployment: true,
uneducatedUnemployment: true,
percentageChange: true,
createdAt: true,
},
});
return {
success: true,
message: "Berhasil menambahkan data pengangguran bulanan",
data: created,
};
return created;
}

View File

@@ -15,7 +15,7 @@ export default async function findByMonthYear(context: Context) {
const data = await prisma.detailDataPengangguran.findFirst({
where: {
month,
year: new Date(Number(year), 0, 1), // Convert year number to Date object for Prisma
year: Number(year),
},
});

View File

@@ -1,10 +1,49 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import prisma from "@/lib/prisma";
import { Context } from "elysia";
export default async function detailDataPengangguranFindMany() {
const res = await prisma.detailDataPengangguran.findMany({
orderBy: [{ year: "desc" }, { month: "asc" }],
});
return {
data: res,
};
export default async function detailDataPengangguranFindMany(context: Context) {
const page = Number(context.query.page) || 1;
const limit = Number(context.query.limit) || 10;
const search = (context.query.search as string) || '';
const skip = (page - 1) * limit;
const where: any = { isActive: true };
// Tambahkan pencarian (jika ada)
if (search) {
where.OR = [
{ year: { contains: search, mode: "insensitive" } },
{ month: { contains: search, mode: "insensitive" } },
];
}
try {
const [data, total] = await Promise.all([
prisma.detailDataPengangguran.findMany({
where,
skip,
take: limit,
orderBy: [{ year: "desc" }, { month: "asc" }],
}),
prisma.detailDataPengangguran.count({
where,
}),
]);
return {
success: true,
message: "Success fetch apb desa with pagination",
data,
page,
totalPages: Math.ceil(total / limit),
total,
};
} catch (e) {
console.error(e);
return {
success: false,
message: "Failed to fetch apb desa with pagination",
data: null,
};
}
}

View File

@@ -10,13 +10,13 @@ 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)
.get("/month/:month/year/:year", findByMonthYear, {
params: t.Object({
month: t.String(),
year: t.Number(),
}),
})
.post("/create", detailDataPengangguranCreate, {
body: t.Object({
month: t.String(),
@@ -24,7 +24,7 @@ const DetailDataPengangguran = new Elysia({
totalUnemployment: t.Number(),
educatedUnemployment: t.Number(),
uneducatedUnemployment: t.Number(),
percentageChange: t.Number(),
percentageChange: t.Optional(t.Number()),
}),
})
.put("/:id", detailDataPengangguranUpdate, {
@@ -37,19 +37,19 @@ const DetailDataPengangguran = new Elysia({
totalUnemployment: t.Number(),
educatedUnemployment: t.Number(),
uneducatedUnemployment: t.Number(),
percentageChange: t.Number(),
percentageChange: t.Optional(t.Number()),
}),
})
.delete("/del/:id", detailDataPengangguranDelete, {
.delete("/:id", detailDataPengangguranDelete, {
params: t.Object({
id: t.String(),
}),
})
.get("/month/:month/year/:year", findByMonthYear, {
params: t.Object({
month: t.String(),
year: t.String(),
}),
})
.get("/:id", async (context) => {
const response = await detailDataPengangguranFindUnique(
new Request(context.request)
);
return response;
});
export default DetailDataPengangguran;

View File

@@ -1,151 +1,97 @@
// 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,
// }
// }
import prisma from "@/lib/prisma";
import { Context } from "elysia";
type FormUpdate = {
month: string;
year: number;
totalUnemployment: number;
educatedUnemployment: number;
uneducatedUnemployment: number;
percentageChange?: number;
};
const monthOrder = ["Jan", "Feb", "Mar", "Apr", "Mei", "Jun", "Jul", "Agu", "Sep", "Okt", "Nov", "Des"];
const monthOrder = [
"Jan", "Feb", "Mar", "Apr", "Mei", "Jun",
"Jul", "Agu", "Sep", "Okt", "Nov", "Des"
];
export default async function detailDataPengangguranUpdate(context: Context) {
const id = context.params?.id;
if (!id) {
return { success: false, message: "ID tidak ditemukan" };
}
const { month, year: yearInput, totalUnemployment, educatedUnemployment, uneducatedUnemployment } = context.body as {
month: string;
year: number | string | Date;
totalUnemployment: number | string;
educatedUnemployment: number | string;
uneducatedUnemployment: number | string;
};
// Normalize year to Date object
const year = typeof yearInput === 'number' ? new Date(yearInput, 0, 1) :
yearInput instanceof Date ? yearInput :
new Date(parseInt(yearInput as string), 0, 1);
const duplicate = await prisma.detailDataPengangguran.findFirst({
where: {
month,
year,
NOT: { id },
},
});
if (duplicate) {
return { success: false, message: `Data bulan ${month} ${year} sudah ada.` };
}
const current = await prisma.detailDataPengangguran.findUnique({ where: { id } });
if (!current) {
return { success: false, message: "Data tidak ditemukan" };
}
const currentMonthIndex = monthOrder.indexOf(month);
const prevMonth = currentMonthIndex > 0 ? monthOrder[currentMonthIndex - 1] : "Des";
const currentYear = year.getFullYear();
const prevYear = currentMonthIndex > 0 ? currentYear : currentYear - 1;
const prevData = await prisma.detailDataPengangguran.findFirst({
where: {
month: prevMonth,
year: new Date(prevYear, 0, 1),
},
});
let percentageChange: number | null = null;
if (prevData) {
const change = ((Number(totalUnemployment) - Number(prevData.totalUnemployment)) / Number(prevData.totalUnemployment)) * 100;
percentageChange = parseFloat(change.toFixed(1));
}
const updated = await prisma.detailDataPengangguran.update({
where: { id },
data: {
month,
year: new Date(year), // Ensure it's a new Date instance
totalUnemployment: Number(totalUnemployment),
educatedUnemployment: Number(educatedUnemployment),
uneducatedUnemployment: Number(uneducatedUnemployment),
percentageChange,
},
});
const id = context.params?.id as string;
if (!id) {
return { success: false, message: "ID tidak ditemukan" };
}
const body = context.body as FormUpdate;
const {
month,
year: yearInput,
totalUnemployment,
educatedUnemployment,
uneducatedUnemployment,
} = body;
// 🔍 Cek duplikat (kecuali current id)
const duplicate = await prisma.detailDataPengangguran.findFirst({
where: {
month,
year: Number(yearInput),
NOT: { id },
},
});
if (duplicate) {
return {
success: true,
message: "Data berhasil diupdate",
data: updated,
success: false,
message: `Data bulan ${month} ${yearInput} sudah ada.`,
};
}
// 🔍 Cek data sekarang
const current = await prisma.detailDataPengangguran.findUnique({ where: { id } });
if (!current) {
return { success: false, message: "Data tidak ditemukan" };
}
// 🔢 Cari data bulan sebelumnya
const currentMonthIndex = monthOrder.indexOf(month);
const prevMonth = currentMonthIndex > 0 ? monthOrder[currentMonthIndex - 1] : "Des";
const prevYear = currentMonthIndex > 0 ? yearInput : yearInput - 1;
const prevData = await prisma.detailDataPengangguran.findFirst({
where: {
month: prevMonth,
year: prevYear,
},
});
// 📊 Hitung percentageChange
let percentageChange: number | null = null;
if (prevData && prevData.totalUnemployment > 0) {
const change =
((Number(totalUnemployment) - Number(prevData.totalUnemployment)) /
Number(prevData.totalUnemployment)) *
100;
percentageChange = parseFloat(change.toFixed(1));
}
// ✏️ Update data
const updated = await prisma.detailDataPengangguran.update({
where: { id },
data: {
month,
year: yearInput,
totalUnemployment: Number(totalUnemployment),
educatedUnemployment: Number(educatedUnemployment),
uneducatedUnemployment: Number(uneducatedUnemployment),
percentageChange,
},
});
return {
success: true,
message: "Data berhasil diupdate",
data: updated,
};
}

View File

@@ -1,43 +1,55 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import prisma from "@/lib/prisma";
import { Context } from "elysia";
export default async function apbDesaFindMany(context: Context) {
const page = Number(context.query.page) || 1;
const limit = Number(context.query.limit) || 10;
const skip = (page - 1) * limit;
const page = Number(context.query.page) || 1;
const limit = Number(context.query.limit) || 10;
const search = (context.query.search as string) || '';
const skip = (page - 1) * limit;
try {
const [data, total] = await Promise.all([
prisma.apbDesa.findMany({
where: { isActive: true },
include: {
pendapatan: true,
belanja: true,
pembiayaan: true,
},
skip,
take: limit,
orderBy: { createdAt: 'desc' },
}),
prisma.apbDesa.count({
where: { isActive: true }
})
]);
const where: any = { isActive: true };
return {
success: true,
message: "Success fetch apb desa with pagination",
data,
page,
totalPages: Math.ceil(total / limit),
total,
};
} catch (e) {
console.error(e);
return {
success: false,
message: "Failed to fetch apb desa with pagination",
data: null,
};
}
}
// Tambahkan pencarian (jika ada)
if (search) {
where.OR = [
{ name: { contains: search, mode: "insensitive" } },
{ jumlah: { contains: search, mode: "insensitive" } },
];
}
try {
const [data, total] = await Promise.all([
prisma.apbDesa.findMany({
where,
include: {
pendapatan: true,
belanja: true,
pembiayaan: true,
},
skip,
take: limit,
orderBy: { createdAt: "desc" },
}),
prisma.apbDesa.count({
where,
}),
]);
return {
success: true,
message: "Success fetch apb desa with pagination",
data,
page,
totalPages: Math.ceil(total / limit),
total,
};
} catch (e) {
console.error(e);
return {
success: false,
message: "Failed to fetch apb desa with pagination",
data: null,
};
}
}

View File

@@ -1,21 +1,32 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import prisma from "@/lib/prisma";
import { Context } from "elysia";
export default async function belanjaFindMany(context: Context) {
const page = Number(context.query.page) || 1;
const limit = Number(context.query.limit) || 10;
const search = (context.query.search as string) || '';
const skip = (page - 1) * limit;
const where: any = { isActive: true };
// Tambahkan pencarian (jika ada)
if (search) {
where.OR = [
{ name: { contains: search, mode: "insensitive" } },
];
}
try {
const [data, total] = await Promise.all([
prisma.belanja.findMany({
where: { isActive: true },
where,
skip,
take: limit,
orderBy: { createdAt: "desc" },
}),
prisma.belanja.count({
where: { isActive: true },
where,
}),
]);

View File

@@ -1,21 +1,32 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import prisma from "@/lib/prisma";
import { Context } from "elysia";
export default async function pembiayaanFindMany(context: Context) {
const page = Number(context.query.page) || 1;
const limit = Number(context.query.limit) || 10;
const search = (context.query.search as string) || '';
const skip = (page - 1) * limit;
const where: any = { isActive: true };
// Tambahkan pencarian (jika ada)
if (search) {
where.OR = [
{ name: { contains: search, mode: "insensitive" } },
];
}
try {
const [data, total] = await Promise.all([
prisma.pembiayaan.findMany({
where: { isActive: true },
where,
skip,
take: limit,
orderBy: { createdAt: 'desc' },
}),
prisma.pembiayaan.count({
where: { isActive: true }
where
})
]);

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import prisma from "@/lib/prisma";
import { Context } from "elysia";
@@ -5,24 +6,34 @@ import { Context } from "elysia";
async function pendapatanAsliFindMany(context: Context) {
const page = Number(context.query.page) || 1;
const limit = Number(context.query.limit) || 10;
const search = (context.query.search as string) || '';
const skip = (page - 1) * limit;
const where: any = { isActive: true };
// Tambahkan pencarian (jika ada)
if (search) {
where.OR = [
{ name: { contains: search, mode: "insensitive" } },
];
}
try {
const [data, total] = await Promise.all([
prisma.pendapatan.findMany({
where: { isActive: true },
where,
skip,
take: limit,
orderBy: { createdAt: 'desc' },
}),
prisma.pendapatan.count({
where: { isActive: true }
where
})
]);
return {
success: true,
message: "Success fetch berita with pagination",
message: "Success fetch pendapatan asli desa with pagination",
data,
page,
totalPages: Math.ceil(total / limit),

View File

@@ -1,17 +1,56 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import prisma from "@/lib/prisma";
import { Context } from "elysia";
export default async function posisiOrganisasiFindMany() {
const data = await prisma.posisiOrganisasi.findMany();
export default async function posisiOrganisasiFindMany(context: Context) {
// Ambil parameter dari query
const page = Number(context.query.page) || 1;
const limit = Number(context.query.limit) || 10;
const search = (context.query.search as string) || "";
const skip = (page - 1) * limit;
// Buat where clause
const where: any = { isActive: true };
// Tambahkan pencarian (jika ada)
if (search) {
where.OR = [
{ nama: { contains: search, mode: "insensitive" } },
{ deskripsi: { contains: search, mode: "insensitive" } },
];
}
try {
// Ambil data dan total count secara paralel
const [data, total] = await Promise.all([
prisma.posisiOrganisasi.findMany({
where,
skip,
take: limit,
orderBy: { createdAt: "asc" },
}),
prisma.posisiOrganisasi.count({ where }),
]);
return {
success: true,
message: "Berhasil mengambil semua data posisi organisasi",
data: data.map((item: any) => ({
id: item.id,
nama: item.nama,
deskripsi: item.deskripsi,
hierarki: item.hierarki,
})),
success: true,
message: "Berhasil mengambil semua data posisi organisasi",
data: data.map((item: any) => ({
id: item.id,
nama: item.nama,
deskripsi: item.deskripsi,
hierarki: item.hierarki,
})),
page,
limit,
total,
totalPages: Math.ceil(total / limit),
};
}
} catch (e) {
console.error("Error di findMany paginated:", e);
return {
success: false,
message: "Gagal mengambil data posisi organisasi",
};
}
}

View File

@@ -55,14 +55,18 @@ const fileStorageCreate = async (context: Context) => {
finalName = desktopName;
finalMimeType = "image/webp";
} else {
// Kompres dokumen (opsional pakai gzip)
const gzBuffer = zlib.gzipSync(buffer);
const docName = `${finalName}.gz`;
await fs.writeFile(path.join(rootPath, docName), gzBuffer);
finalName = docName;
finalMimeType = "application/gzip";
// Jika file adalah PDF, simpan tanpa kompresi
if (file.type === "application/pdf") {
await fs.writeFile(path.join(rootPath, finalName), buffer);
}
// Jika file lain, kompres dengan gzip
else {
const gzBuffer = zlib.gzipSync(buffer);
const compressedName = `${finalName}.gz`;
await fs.writeFile(path.join(rootPath, compressedName), gzBuffer);
finalName = compressedName;
finalMimeType = "application/gzip";
}
}
const data = await prisma.fileStorage.create({

View File

@@ -1,20 +1,59 @@
import prisma from "@/lib/prisma"
/* eslint-disable @typescript-eslint/no-explicit-any */
// /api/berita/findManyPaginated.ts
import prisma from "@/lib/prisma";
import { Context } from "elysia";
const laporanPublikFindMany = async () => {
const laporanPublik = await prisma.laporanPublik.findMany({
async function laporanPublikFindMany(context: Context) {
// Ambil parameter dari query
const page = Number(context.query.page) || 1;
const limit = Number(context.query.limit) || 10;
const search = (context.query.search as string) || "";
const skip = (page - 1) * limit;
// Buat where clause
const where: any = { isActive: true };
// Tambahkan pencarian (jika ada)
if (search) {
where.OR = [
{ judul: { contains: search, mode: "insensitive" } },
{ lokasi: { contains: search, mode: "insensitive" } },
];
}
try {
// Ambil data dan total count secara paralel
const [data, total] = await Promise.all([
prisma.laporanPublik.findMany({
where,
include: {
penanganan: true
penanganan: true,
},
skip,
take: limit,
orderBy: {
tanggalWaktu: 'desc'
}
})
return {
success: true,
data: laporanPublik,
}
tanggalWaktu: "desc",
},
}),
prisma.laporanPublik.count({ where }),
]);
return {
success: true,
message: "Berhasil ambil laporan publik dengan pagination",
data,
page,
limit,
total,
totalPages: Math.ceil(total / limit),
};
} catch (e) {
console.error("Error di findMany paginated:", e);
return {
success: false,
message: "Gagal mengambil data laporan publik",
};
}
}
export default laporanPublikFindMany;