Compare commits
6 Commits
nico/9-jul
...
nico/10-ju
| Author | SHA1 | Date | |
|---|---|---|---|
| 4baffe95f3 | |||
| cb52701f47 | |||
| 2bc9b2f3c6 | |||
| 7b2b306849 | |||
| d328f64d86 | |||
| 119275b95c |
@@ -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
|
||||
}
|
||||
]
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "DetailDataPengangguran" (
|
||||
"id" UUID NOT NULL,
|
||||
"month" VARCHAR(20) NOT NULL,
|
||||
"year" INTEGER NOT NULL,
|
||||
"totalUnemployment" INTEGER NOT NULL,
|
||||
"educatedUnemployment" INTEGER NOT NULL,
|
||||
"uneducatedUnemployment" INTEGER NOT NULL,
|
||||
"percentageChange" DOUBLE PRECISION,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
"deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||
|
||||
CONSTRAINT "DetailDataPengangguran_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "DetailDataPengangguran_month_year_key" ON "DetailDataPengangguran"("month", "year");
|
||||
133
prisma/migrations/20250710032516_nico_10_jul_25_1/migration.sql
Normal file
133
prisma/migrations/20250710032516_nico_10_jul_25_1/migration.sql
Normal file
@@ -0,0 +1,133 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "ApbDesa" (
|
||||
"id" TEXT NOT NULL,
|
||||
"tahun" INTEGER NOT NULL,
|
||||
"pendapatanId" TEXT NOT NULL,
|
||||
"belanjaId" TEXT NOT NULL,
|
||||
"pembiayaanId" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "ApbDesa_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Pendapatan" (
|
||||
"id" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"value" INTEGER NOT NULL,
|
||||
|
||||
CONSTRAINT "Pendapatan_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Belanja" (
|
||||
"id" TEXT NOT NULL,
|
||||
"penyelenggaraan" INTEGER NOT NULL,
|
||||
"pelaksanaanPembangunan" INTEGER NOT NULL,
|
||||
"pembinaanMasyarakat" INTEGER NOT NULL,
|
||||
"pemberdayaanMasyarakat" INTEGER NOT NULL,
|
||||
"penanggulanganBencana" INTEGER NOT NULL,
|
||||
"total" INTEGER NOT NULL,
|
||||
|
||||
CONSTRAINT "Belanja_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Pembiayaan" (
|
||||
"id" TEXT NOT NULL,
|
||||
"silpa" INTEGER NOT NULL,
|
||||
|
||||
CONSTRAINT "Pembiayaan_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "KlasifikasiBelanja" (
|
||||
"id" TEXT NOT NULL,
|
||||
"jenis" TEXT NOT NULL,
|
||||
"persen" DOUBLE PRECISION NOT NULL,
|
||||
"total" INTEGER NOT NULL,
|
||||
"apbDesaId" TEXT NOT NULL,
|
||||
|
||||
CONSTRAINT "KlasifikasiBelanja_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "RincianBelanja" (
|
||||
"id" TEXT NOT NULL,
|
||||
"nama" TEXT NOT NULL,
|
||||
"jumlah" INTEGER NOT NULL,
|
||||
"klasifikasiBelanjaId" TEXT NOT NULL,
|
||||
|
||||
CONSTRAINT "RincianBelanja_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "KegiatanSubak" (
|
||||
"id" TEXT NOT NULL,
|
||||
"nama" TEXT NOT NULL,
|
||||
"jumlah" INTEGER NOT NULL,
|
||||
"apbDesaId" TEXT NOT NULL,
|
||||
|
||||
CONSTRAINT "KegiatanSubak_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "_ApbDesaToKegiatanSubak" (
|
||||
"A" TEXT NOT NULL,
|
||||
"B" TEXT NOT NULL,
|
||||
|
||||
CONSTRAINT "_ApbDesaToKegiatanSubak_AB_pkey" PRIMARY KEY ("A","B")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "_BelanjaToKlasifikasiBelanja" (
|
||||
"A" TEXT NOT NULL,
|
||||
"B" TEXT NOT NULL,
|
||||
|
||||
CONSTRAINT "_BelanjaToKlasifikasiBelanja_AB_pkey" PRIMARY KEY ("A","B")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "_KlasifikasiBelanjaToRincianBelanja" (
|
||||
"A" TEXT NOT NULL,
|
||||
"B" TEXT NOT NULL,
|
||||
|
||||
CONSTRAINT "_KlasifikasiBelanjaToRincianBelanja_AB_pkey" PRIMARY KEY ("A","B")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "_ApbDesaToKegiatanSubak_B_index" ON "_ApbDesaToKegiatanSubak"("B");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "_BelanjaToKlasifikasiBelanja_B_index" ON "_BelanjaToKlasifikasiBelanja"("B");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "_KlasifikasiBelanjaToRincianBelanja_B_index" ON "_KlasifikasiBelanjaToRincianBelanja"("B");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "ApbDesa" ADD CONSTRAINT "ApbDesa_pendapatanId_fkey" FOREIGN KEY ("pendapatanId") REFERENCES "Pendapatan"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "ApbDesa" ADD CONSTRAINT "ApbDesa_belanjaId_fkey" FOREIGN KEY ("belanjaId") REFERENCES "Belanja"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "ApbDesa" ADD CONSTRAINT "ApbDesa_pembiayaanId_fkey" FOREIGN KEY ("pembiayaanId") REFERENCES "Pembiayaan"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "_ApbDesaToKegiatanSubak" ADD CONSTRAINT "_ApbDesaToKegiatanSubak_A_fkey" FOREIGN KEY ("A") REFERENCES "ApbDesa"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "_ApbDesaToKegiatanSubak" ADD CONSTRAINT "_ApbDesaToKegiatanSubak_B_fkey" FOREIGN KEY ("B") REFERENCES "KegiatanSubak"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "_BelanjaToKlasifikasiBelanja" ADD CONSTRAINT "_BelanjaToKlasifikasiBelanja_A_fkey" FOREIGN KEY ("A") REFERENCES "Belanja"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "_BelanjaToKlasifikasiBelanja" ADD CONSTRAINT "_BelanjaToKlasifikasiBelanja_B_fkey" FOREIGN KEY ("B") REFERENCES "KlasifikasiBelanja"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "_KlasifikasiBelanjaToRincianBelanja" ADD CONSTRAINT "_KlasifikasiBelanjaToRincianBelanja_A_fkey" FOREIGN KEY ("A") REFERENCES "KlasifikasiBelanja"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "_KlasifikasiBelanjaToRincianBelanja" ADD CONSTRAINT "_KlasifikasiBelanjaToRincianBelanja_B_fkey" FOREIGN KEY ("B") REFERENCES "RincianBelanja"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the column `pelaksanaanPembangunan` on the `Belanja` table. All the data in the column will be lost.
|
||||
- You are about to drop the column `pemberdayaanMasyarakat` on the `Belanja` table. All the data in the column will be lost.
|
||||
- You are about to drop the column `pembinaanMasyarakat` on the `Belanja` table. All the data in the column will be lost.
|
||||
- You are about to drop the column `penanggulanganBencana` on the `Belanja` table. All the data in the column will be lost.
|
||||
- You are about to drop the column `penyelenggaraan` on the `Belanja` table. All the data in the column will be lost.
|
||||
- Added the required column `name` to the `Belanja` table without a default value. This is not possible if the table is not empty.
|
||||
- Added the required column `updatedAt` to the `Belanja` table without a default value. This is not possible if the table is not empty.
|
||||
- Added the required column `value` to the `Belanja` table without a default value. This is not possible if the table is not empty.
|
||||
- Added the required column `total` to the `Pendapatan` table without a default value. This is not possible if the table is not empty.
|
||||
- Added the required column `updatedAt` to the `Pendapatan` table without a default value. This is not possible if the table is not empty.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE "Belanja" DROP COLUMN "pelaksanaanPembangunan",
|
||||
DROP COLUMN "pemberdayaanMasyarakat",
|
||||
DROP COLUMN "pembinaanMasyarakat",
|
||||
DROP COLUMN "penanggulanganBencana",
|
||||
DROP COLUMN "penyelenggaraan",
|
||||
ADD COLUMN "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
ADD COLUMN "deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
ADD COLUMN "isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||
ADD COLUMN "name" TEXT NOT NULL,
|
||||
ADD COLUMN "updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
ADD COLUMN "value" INTEGER NOT NULL;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "Pendapatan" ADD COLUMN "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
ADD COLUMN "deletedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
ADD COLUMN "isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||
ADD COLUMN "total" INTEGER NOT NULL,
|
||||
ADD COLUMN "updatedAt" TIMESTAMP(3) NOT NULL;
|
||||
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the column `total` on the `Belanja` table. All the data in the column will be lost.
|
||||
- You are about to drop the column `total` on the `Pendapatan` table. All the data in the column will be lost.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE "Belanja" DROP COLUMN "total";
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "Pendapatan" DROP COLUMN "total";
|
||||
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the column `silpa` on the `Pembiayaan` table. All the data in the column will be lost.
|
||||
- Added the required column `name` to the `Pembiayaan` table without a default value. This is not possible if the table is not empty.
|
||||
- Added the required column `value` to the `Pembiayaan` table without a default value. This is not possible if the table is not empty.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE "Pembiayaan" DROP COLUMN "silpa",
|
||||
ADD COLUMN "name" TEXT NOT NULL,
|
||||
ADD COLUMN "value" INTEGER NOT NULL;
|
||||
@@ -1262,3 +1262,66 @@ model DataDemografiPekerjaan {
|
||||
deletedAt DateTime @default(now())
|
||||
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])
|
||||
}
|
||||
|
||||
// ========================================= PADESA PENDAPATAN ASLI DESA ========================================= //
|
||||
model ApbDesa {
|
||||
id String @id @default(uuid())
|
||||
tahun Int
|
||||
pembiayaan Pembiayaan[] @relation("ApbDesaPembiayaan")
|
||||
belanja Belanja[] @relation("ApbDesaBelanja")
|
||||
pendapatan Pendapatan[] @relation("ApbDesaPendapatan")
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime @default(now())
|
||||
isActive Boolean @default(true)
|
||||
}
|
||||
|
||||
model Pendapatan {
|
||||
id String @id @default(uuid())
|
||||
name String
|
||||
value Int
|
||||
ApbDesa ApbDesa[] @relation("ApbDesaPendapatan")
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime @default(now())
|
||||
isActive Boolean @default(true)
|
||||
}
|
||||
|
||||
model Belanja {
|
||||
id String @id @default(uuid())
|
||||
name String
|
||||
value Int
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime @default(now())
|
||||
isActive Boolean @default(true)
|
||||
ApbDesa ApbDesa[] @relation("ApbDesaBelanja")
|
||||
}
|
||||
|
||||
model Pembiayaan {
|
||||
id String @id @default(uuid())
|
||||
name String
|
||||
value Int
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime @default(now())
|
||||
isActive Boolean @default(true)
|
||||
ApbDesa ApbDesa[] @relation("ApbDesaPembiayaan")
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import kategoriProduk from "./data/ekonomi/pasar-desa/kategori-produk.json";
|
||||
import hubunganOrganisasi from "./data/ekonomi/struktur-organisasi/hubungan-organisasi.json";
|
||||
import posisiOrganisasi from "./data/ekonomi/struktur-organisasi/posisi-organisasi.json";
|
||||
import pegawai from "./data/ekonomi/struktur-organisasi/pegawai.json";
|
||||
import detailDataPengangguran from './data/ekonomi/jumlah-pengangguran/detail-data-pengangguran.json';
|
||||
|
||||
(async () => {
|
||||
for (const l of layanan) {
|
||||
@@ -431,6 +432,29 @@ import pegawai from "./data/ekonomi/struktur-organisasi/pegawai.json";
|
||||
});
|
||||
}
|
||||
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 ...");
|
||||
})()
|
||||
.then(() => prisma.$disconnect())
|
||||
.catch((e) => {
|
||||
|
||||
BIN
public/pa-desa.png
Normal file
BIN
public/pa-desa.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.0 MiB |
@@ -15,6 +15,8 @@ type JudulListTabProps = {
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
const JudulListTab = ({
|
||||
title = "",
|
||||
href = "#",
|
||||
@@ -80,13 +80,33 @@ const berita = proxy({
|
||||
};
|
||||
}>[]
|
||||
| null,
|
||||
async load() {
|
||||
const res = await ApiFetch.api.desa.berita["find-many"].get();
|
||||
if (res.status === 200) {
|
||||
berita.findMany.data = (res.data?.data ) ?? [];
|
||||
page: 1,
|
||||
totalPages: 1,
|
||||
loading: false,
|
||||
|
||||
async load(page = 1, limit = 10) {
|
||||
berita.findMany.loading = true;
|
||||
berita.findMany.page = page;
|
||||
try {
|
||||
const res = await ApiFetch.api.desa.berita["find-many"].get({
|
||||
query: {
|
||||
page,
|
||||
limit,
|
||||
},
|
||||
});
|
||||
|
||||
if (res.status === 200 && res.data?.success) {
|
||||
berita.findMany.data = res.data.data ?? [];
|
||||
berita.findMany.totalPages = res.data.totalPages ?? 1;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Gagal fetch berita paginated:", err);
|
||||
} finally {
|
||||
berita.findMany.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
findUnique: {
|
||||
data: null as
|
||||
| Prisma.BeritaGetPayload<{
|
||||
|
||||
873
src/app/admin/(dashboard)/_state/ekonomi/PADesa.ts
Normal file
873
src/app/admin/(dashboard)/_state/ekonomi/PADesa.ts
Normal file
@@ -0,0 +1,873 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
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 templateApbDesa = z.object({
|
||||
tahun: z.number().min(4, "Tahun minimal 4 karakter"),
|
||||
pembiayaanIds: z.array(z.string().uuid()).nonempty("Pilih minimal 1 pembiayaan"),
|
||||
belanjaIds: z.array(z.string().uuid()).nonempty("Pilih minimal 1 belanja"),
|
||||
pendapatanIds: z.array(z.string().uuid()).nonempty("Pilih minimal 1 pendapatan"),
|
||||
});
|
||||
|
||||
const ApbDesaDefaultForm = {
|
||||
tahun: 0,
|
||||
pendapatanIds: [] as string[],
|
||||
belanjaIds: [] as string[],
|
||||
pembiayaanIds: [] as string[],
|
||||
};
|
||||
|
||||
const ApbDesa = proxy({
|
||||
create: {
|
||||
form: { ...ApbDesaDefaultForm },
|
||||
loading: false,
|
||||
async submit() {
|
||||
const cek = templateApbDesa.safeParse(this.form);
|
||||
if (!cek.success) {
|
||||
const err = cek.error.issues.map((v) => v.message).join("\n");
|
||||
return toast.error(err);
|
||||
}
|
||||
|
||||
try {
|
||||
this.loading = true;
|
||||
const res = await ApiFetch.api.ekonomi.pendapatanaslidesa.apbdesa[
|
||||
"create"
|
||||
].post(this.form);
|
||||
if (res.status === 200) {
|
||||
toast.success("Berhasil menambahkan APB Desa");
|
||||
ApbDesa.findMany.load();
|
||||
this.reset();
|
||||
} else {
|
||||
toast.error(res.data?.message || "Gagal menambahkan APB Desa");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Create error:", error);
|
||||
toast.error("Gagal menambahkan APB Desa");
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
reset() {
|
||||
this.form = { ...ApbDesaDefaultForm };
|
||||
},
|
||||
},
|
||||
findMany: {
|
||||
data: null as
|
||||
| Prisma.ApbDesaGetPayload<{
|
||||
include: {
|
||||
pendapatan: true;
|
||||
belanja: true;
|
||||
pembiayaan: true;
|
||||
};
|
||||
}>[]
|
||||
| null,
|
||||
loading: false,
|
||||
async load() {
|
||||
try {
|
||||
this.loading = true;
|
||||
const res = await ApiFetch.api.ekonomi.pendapatanaslidesa.apbdesa[
|
||||
"find-many"
|
||||
].get();
|
||||
if (res.status === 200) {
|
||||
this.data = res.data?.data ?? [];
|
||||
} else {
|
||||
toast.error(res.data?.message || "Gagal mengambil APB Desa");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Find many error:", error);
|
||||
toast.error("Gagal mengambil APB Desa");
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
update: {
|
||||
id: "",
|
||||
form: { ...ApbDesaDefaultForm },
|
||||
loading: false,
|
||||
async load(id: string) {
|
||||
if (!id) {
|
||||
toast.warn("ID tidak valid");
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
const response = await fetch(
|
||||
`/api/ekonomi/pendapatanaslidesa/apbdesa/${id}`,
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
if (!response.ok) {
|
||||
throw new Error("Gagal mengambil APB Desa");
|
||||
}
|
||||
const data = await response.json();
|
||||
this.id = id;
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error("Error loading APB Desa:", error);
|
||||
toast.error("Gagal memuat data APB Desa");
|
||||
return null;
|
||||
}
|
||||
},
|
||||
async update() {
|
||||
try {
|
||||
this.loading = true;
|
||||
const response = await fetch(
|
||||
`/api/ekonomi/pendapatanaslidesa/apbdesa/${this.id}`,
|
||||
{
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(this.form),
|
||||
}
|
||||
);
|
||||
if (!response.ok) {
|
||||
throw new Error("Gagal memperbarui APB Desa");
|
||||
}
|
||||
const data = await response.json();
|
||||
toast.success("APB Desa berhasil diperbarui");
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error("Error updating APB Desa:", error);
|
||||
toast.error("Gagal memperbarui APB Desa");
|
||||
throw error;
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
reset() {
|
||||
this.id = "";
|
||||
this.form = { ...ApbDesaDefaultForm };
|
||||
},
|
||||
},
|
||||
delete: {
|
||||
loading: false,
|
||||
async byId(id: string) {
|
||||
try {
|
||||
this.loading = true;
|
||||
const response = await fetch(
|
||||
`/api/ekonomi/pendapatanaslidesa/apbdesa/${id}`,
|
||||
{
|
||||
method: "DELETE",
|
||||
}
|
||||
);
|
||||
if (!response.ok) {
|
||||
throw new Error("Gagal menghapus APB Desa");
|
||||
}
|
||||
toast.success("APB Desa berhasil dihapus");
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error("Error deleting APB Desa:", error);
|
||||
toast.error("Gagal menghapus APB Desa");
|
||||
return false;
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
findUnique: {
|
||||
data: null as Prisma.ApbDesaGetPayload<{
|
||||
include: { pendapatan: true; belanja: true; pembiayaan: true };
|
||||
}> | null,
|
||||
|
||||
async load(id: string) {
|
||||
try {
|
||||
const response = await fetch(
|
||||
`/api/ekonomi/pendapatanaslidesa/apbdesa/${id}`
|
||||
);
|
||||
if (!response.ok) {
|
||||
throw new Error("Gagal mengambil detail APB Desa");
|
||||
}
|
||||
const result = await response.json();
|
||||
|
||||
if (!result.success) {
|
||||
throw new Error(result.message || "Gagal mengambil data");
|
||||
}
|
||||
|
||||
this.data = result.data; // ✅ fix utama di sini
|
||||
return result.data;
|
||||
} catch (error) {
|
||||
console.error("Error loading APB Desa detail:", error);
|
||||
toast.error("Gagal memuat detail APB Desa");
|
||||
return null;
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const templatePendapatan = z.object({
|
||||
name: z.string().min(2, "Nama harus diisi"),
|
||||
value: z.number().int().positive("Nilai harus angka positif"),
|
||||
});
|
||||
|
||||
const PendapatanDefaultForm = {
|
||||
name: "",
|
||||
value: 0,
|
||||
};
|
||||
|
||||
const pendapatan = proxy({
|
||||
create: {
|
||||
form: { ...PendapatanDefaultForm },
|
||||
loading: false,
|
||||
async submit() {
|
||||
const cek = templatePendapatan.safeParse(this.form);
|
||||
if (!cek.success) {
|
||||
const err = cek.error.issues.map((v) => v.message).join("\n");
|
||||
return toast.error(err);
|
||||
}
|
||||
|
||||
try {
|
||||
this.loading = true;
|
||||
const res =
|
||||
await ApiFetch.api.ekonomi.pendapatanaslidesa.pendapatanasli[
|
||||
"create"
|
||||
].post(this.form);
|
||||
if (res.status === 200) {
|
||||
toast.success("Berhasil menambahkan Pendapatan Asli");
|
||||
pendapatan.findMany.load();
|
||||
this.reset();
|
||||
} else {
|
||||
toast.error(res.data?.message || "Gagal menambahkan Pendapatan Asli");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Create error:", error);
|
||||
toast.error("Gagal menambahkan Pendapatan Asli");
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
reset() {
|
||||
this.form = { ...PendapatanDefaultForm };
|
||||
},
|
||||
},
|
||||
findMany: {
|
||||
data: null as any[] | null,
|
||||
page: 1,
|
||||
totalPages: 1,
|
||||
total: 0,
|
||||
loading: false,
|
||||
load: async (page = 1, limit = 10) => {
|
||||
// Change to arrow function
|
||||
pendapatan.findMany.loading = true; // Use the full path to access the property
|
||||
pendapatan.findMany.page = page;
|
||||
try {
|
||||
const res =
|
||||
await ApiFetch.api.ekonomi.pendapatanaslidesa.pendapatanasli[
|
||||
"find-many"
|
||||
].get({
|
||||
query: { page, limit },
|
||||
});
|
||||
|
||||
if (res.status === 200 && res.data?.success) {
|
||||
pendapatan.findMany.data = res.data.data || [];
|
||||
pendapatan.findMany.total = res.data.total || 0;
|
||||
pendapatan.findMany.totalPages = res.data.totalPages || 1;
|
||||
} else {
|
||||
console.error("Failed to load pendapatan:", res.data?.message);
|
||||
pendapatan.findMany.data = [];
|
||||
pendapatan.findMany.total = 0;
|
||||
pendapatan.findMany.totalPages = 1;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading pendapatan:", error);
|
||||
pendapatan.findMany.data = [];
|
||||
pendapatan.findMany.total = 0;
|
||||
pendapatan.findMany.totalPages = 1;
|
||||
} finally {
|
||||
pendapatan.findMany.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
update: {
|
||||
id: "",
|
||||
form: { ...PendapatanDefaultForm },
|
||||
loading: false,
|
||||
async load(id: string) {
|
||||
if (!id) {
|
||||
toast.warn("ID tidak valid");
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
const response = await fetch(`/api/ekonomi/pendapatanaslidesa/pendapatanasli/${id}`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
const result = await response.json();
|
||||
if (result?.success) {
|
||||
const data = result.data;
|
||||
this.id = data.id;
|
||||
this.form = {
|
||||
name: data.name,
|
||||
value: data.value,
|
||||
};
|
||||
return data;
|
||||
} else {
|
||||
throw new Error(result?.message || "Gagal memuat data");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading pendapatan:", error);
|
||||
toast.error(
|
||||
error instanceof Error ? error.message : "Gagal memuat data"
|
||||
);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
async update() {
|
||||
const cek = templatePendapatan.safeParse(pendapatan.update.form);
|
||||
if (!cek.success) {
|
||||
const err = `[${cek.error.issues
|
||||
.map((v) => `${v.path.join(".")}`)
|
||||
.join("\n")}] required`;
|
||||
return toast.error(err);
|
||||
}
|
||||
|
||||
try {
|
||||
pendapatan.update.loading = true;
|
||||
const response = await fetch(`/api/ekonomi/pendapatanaslidesa/pendapatanasli/${this.id}`, {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name: this.form.name,
|
||||
value: this.form.value,
|
||||
}),
|
||||
});
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json().catch(() => ({}));
|
||||
throw new Error(
|
||||
errorData.message || `HTTP error! status: ${response.status}`
|
||||
);
|
||||
}
|
||||
const result = await response.json();
|
||||
if (result.success) {
|
||||
toast.success("Berhasil update pendapatan");
|
||||
await pendapatan.findMany.load(); // refresh list
|
||||
return true;
|
||||
} else {
|
||||
throw new Error(result.message || "Gagal mengupdate pendapatan");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error updating pendapatan:", error);
|
||||
toast.error(
|
||||
error instanceof Error ? error.message : "Gagal mengupdate pendapatan"
|
||||
);
|
||||
return false;
|
||||
} finally {
|
||||
pendapatan.update.loading = false;
|
||||
}
|
||||
},
|
||||
reset() {
|
||||
pendapatan.update.id = "";
|
||||
pendapatan.update.form = { ...PendapatanDefaultForm };
|
||||
},
|
||||
},
|
||||
delete: {
|
||||
loading: false,
|
||||
async byId(id: string) {
|
||||
if (!id) return toast.warn("ID tidak valid");
|
||||
|
||||
try {
|
||||
pendapatan.delete.loading = true;
|
||||
|
||||
const response = await fetch(
|
||||
`/api/ekonomi/pendapatanaslidesa/pendapatanasli/del/${id}`,
|
||||
{
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (response.ok && result?.success) {
|
||||
toast.success(result.message || "Pendapatan Asli berhasil dihapus");
|
||||
await pendapatan.findMany.load(); // refresh list
|
||||
} else {
|
||||
toast.error(result?.message || "Gagal menghapus Pendapatan Asli");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Gagal delete:", error);
|
||||
toast.error("Terjadi kesalahan saat menghapus Pendapatan Asli");
|
||||
} finally {
|
||||
pendapatan.delete.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
findUnique: {
|
||||
data: null as Prisma.PendapatanGetPayload<{
|
||||
select: { isActive: boolean };
|
||||
}> | null,
|
||||
async load(id: string) {
|
||||
const res = await fetch(
|
||||
`/api/ekonomi/pendapatanaslidesa/pendapatanasli/${id}`
|
||||
);
|
||||
if (res.ok) {
|
||||
const json = await res.json();
|
||||
pendapatan.findUnique.data = json.data
|
||||
? {
|
||||
...json.data,
|
||||
isActive: json.data.isActive ?? true, // Fallback ke aktif:true jika tidak ada data
|
||||
}
|
||||
: null;
|
||||
} else {
|
||||
pendapatan.findUnique.data = null;
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const templateBelanja = z.object({
|
||||
name: z.string().min(2, "Nama harus diisi"),
|
||||
value: z.number().int().positive("Nilai harus angka positif"),
|
||||
});
|
||||
|
||||
const BelanjaDefaultForm = {
|
||||
name: "",
|
||||
value: 0,
|
||||
};
|
||||
|
||||
const belanja = proxy({
|
||||
create: {
|
||||
form: { ...BelanjaDefaultForm },
|
||||
loading: false,
|
||||
async submit() {
|
||||
const cek = templateBelanja.safeParse(this.form);
|
||||
if (!cek.success) {
|
||||
const err = cek.error.issues.map((v) => v.message).join("\n");
|
||||
return toast.error(err);
|
||||
}
|
||||
|
||||
try {
|
||||
this.loading = true;
|
||||
const res = await ApiFetch.api.ekonomi.pendapatanaslidesa.belanja[
|
||||
"create"
|
||||
].post(this.form);
|
||||
if (res.status === 200) {
|
||||
toast.success("Berhasil menambahkan Belanja");
|
||||
belanja.findMany.load();
|
||||
this.reset();
|
||||
} else {
|
||||
toast.error(res.data?.message || "Gagal menambahkan Belanja");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Create error:", error);
|
||||
toast.error("Gagal menambahkan Belanja");
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
reset() {
|
||||
this.form = { ...BelanjaDefaultForm };
|
||||
},
|
||||
},
|
||||
findMany: {
|
||||
data: [] as Array<{
|
||||
id: string;
|
||||
name: string;
|
||||
value: number;
|
||||
}>,
|
||||
loading: false,
|
||||
async load() {
|
||||
try {
|
||||
this.loading = true;
|
||||
const res = await ApiFetch.api.ekonomi.pendapatanaslidesa.belanja[
|
||||
"find-many"
|
||||
].get();
|
||||
if (res.status === 200) {
|
||||
this.data = res.data?.data ?? [];
|
||||
} else {
|
||||
toast.error(res.data?.message || "Gagal mengambil Belanja");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Find many error:", error);
|
||||
toast.error("Gagal mengambil Belanja");
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
update: {
|
||||
id: "",
|
||||
form: { ...BelanjaDefaultForm },
|
||||
loading: false,
|
||||
async load(id: string) {
|
||||
if (!id) {
|
||||
toast.warn("ID tidak valid");
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
const response = await fetch(`/api/ekonomi/pendapatanaslidesa/belanja/${id}`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
const result = await response.json();
|
||||
if (result?.success) {
|
||||
const data = result.data;
|
||||
this.id = data.id;
|
||||
this.form = {
|
||||
name: data.name,
|
||||
value: data.value,
|
||||
};
|
||||
return data;
|
||||
} else {
|
||||
throw new Error(result?.message || "Gagal memuat data");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading belanja:", error);
|
||||
toast.error(
|
||||
error instanceof Error ? error.message : "Gagal memuat data"
|
||||
);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
async update() {
|
||||
const cek = templateBelanja.safeParse(belanja.update.form);
|
||||
if (!cek.success) {
|
||||
const err = `[${cek.error.issues
|
||||
.map((v) => `${v.path.join(".")}`)
|
||||
.join("\n")}] required`;
|
||||
return toast.error(err);
|
||||
}
|
||||
|
||||
try {
|
||||
belanja.update.loading = true;
|
||||
const response = await fetch(`/api/ekonomi/pendapatanaslidesa/belanja/${this.id}`, {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name: this.form.name,
|
||||
value: this.form.value,
|
||||
}),
|
||||
});
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json().catch(() => ({}));
|
||||
throw new Error(
|
||||
errorData.message || `HTTP error! status: ${response.status}`
|
||||
);
|
||||
}
|
||||
const result = await response.json();
|
||||
if (result.success) {
|
||||
toast.success("Berhasil update belanja");
|
||||
await belanja.findMany.load(); // refresh list
|
||||
return true;
|
||||
} else {
|
||||
throw new Error(result.message || "Gagal mengupdate belanja");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error updating belanja:", error);
|
||||
toast.error(
|
||||
error instanceof Error ? error.message : "Gagal mengupdate belanja"
|
||||
);
|
||||
return false;
|
||||
} finally {
|
||||
belanja.update.loading = false;
|
||||
}
|
||||
},
|
||||
reset() {
|
||||
belanja.update.id = "";
|
||||
belanja.update.form = { ...BelanjaDefaultForm };
|
||||
},
|
||||
},
|
||||
delete: {
|
||||
loading: false,
|
||||
async byId(id: string) {
|
||||
if (!id) return toast.warn("ID tidak valid");
|
||||
|
||||
try {
|
||||
belanja.delete.loading = true;
|
||||
|
||||
const response = await fetch(
|
||||
`/api/ekonomi/pendapatanaslidesa/belanja/del/${id}`,
|
||||
{
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (response.ok && result?.success) {
|
||||
toast.success(result.message || "Belanja berhasil dihapus");
|
||||
await belanja.findMany.load(); // refresh list
|
||||
} else {
|
||||
toast.error(result?.message || "Gagal menghapus Belanja");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Gagal delete:", error);
|
||||
toast.error("Terjadi kesalahan saat menghapus Belanja");
|
||||
} finally {
|
||||
belanja.delete.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
findUnique: {
|
||||
data: null as Prisma.BelanjaGetPayload<{
|
||||
select: { isActive: boolean };
|
||||
}> | null,
|
||||
async load(id: string) {
|
||||
const res = await fetch(`/api/ekonomi/pendapatanaslidesa/belanja/${id}`);
|
||||
if (res.ok) {
|
||||
const json = await res.json();
|
||||
belanja.findUnique.data = json.data
|
||||
? {
|
||||
...json.data,
|
||||
isActive: json.data.isActive ?? true, // Fallback ke aktif:true jika tidak ada data
|
||||
}
|
||||
: null;
|
||||
} else {
|
||||
belanja.findUnique.data = null;
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const templatePembiayaan = z.object({
|
||||
name: z.string().min(2, "Nama harus diisi"),
|
||||
value: z.number().int().positive("Nilai harus angka positif"),
|
||||
});
|
||||
|
||||
const PembiayaanDefaultForm = {
|
||||
name: "",
|
||||
value: 0,
|
||||
};
|
||||
|
||||
const pembiayaan = proxy({
|
||||
create: {
|
||||
form: { ...PembiayaanDefaultForm },
|
||||
loading: false,
|
||||
async submit() {
|
||||
const cek = templatePembiayaan.safeParse(this.form);
|
||||
if (!cek.success) {
|
||||
const err = cek.error.issues.map((v) => v.message).join("\n");
|
||||
return toast.error(err);
|
||||
}
|
||||
|
||||
try {
|
||||
this.loading = true;
|
||||
const res = await ApiFetch.api.ekonomi.pendapatanaslidesa.pembiayaan[
|
||||
"create"
|
||||
].post(this.form);
|
||||
if (res.status === 200) {
|
||||
toast.success("Berhasil menambahkan Pembiayaan");
|
||||
pembiayaan.findMany.load();
|
||||
this.reset();
|
||||
} else {
|
||||
toast.error(res.data?.message || "Gagal menambahkan Pembiayaan");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Create error:", error);
|
||||
toast.error("Gagal menambahkan Pembiayaan");
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
reset() {
|
||||
this.form = { ...PembiayaanDefaultForm };
|
||||
},
|
||||
},
|
||||
findMany: {
|
||||
data: [] as Array<{
|
||||
id: string;
|
||||
name: string;
|
||||
value: number;
|
||||
}>,
|
||||
loading: false,
|
||||
async load() {
|
||||
try {
|
||||
this.loading = true;
|
||||
const res = await ApiFetch.api.ekonomi.pendapatanaslidesa.pembiayaan[
|
||||
"find-many"
|
||||
].get();
|
||||
if (res.status === 200) {
|
||||
this.data = res.data?.data ?? [];
|
||||
} else {
|
||||
toast.error(res.data?.message || "Gagal mengambil Pembiayaan");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Find many error:", error);
|
||||
toast.error("Gagal mengambil Pembiayaan");
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
update: {
|
||||
id: "",
|
||||
form: { ...PembiayaanDefaultForm },
|
||||
loading: false,
|
||||
async load(id: string) {
|
||||
if (!id) {
|
||||
toast.warn("ID tidak valid");
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
const response = await fetch(`/api/ekonomi/pendapatanaslidesa/pembiayaan/${id}`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
const result = await response.json();
|
||||
if (result?.success) {
|
||||
const data = result.data;
|
||||
this.id = data.id;
|
||||
this.form = {
|
||||
name: data.name,
|
||||
value: data.value,
|
||||
};
|
||||
return data;
|
||||
} else {
|
||||
throw new Error(result?.message || "Gagal memuat data");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading pembiayaan:", error);
|
||||
toast.error(
|
||||
error instanceof Error ? error.message : "Gagal memuat data"
|
||||
);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
async update() {
|
||||
const cek = templatePembiayaan.safeParse(pembiayaan.update.form);
|
||||
if (!cek.success) {
|
||||
const err = `[${cek.error.issues
|
||||
.map((v) => `${v.path.join(".")}`)
|
||||
.join("\n")}] required`;
|
||||
return toast.error(err);
|
||||
}
|
||||
|
||||
try {
|
||||
pembiayaan.update.loading = true;
|
||||
const response = await fetch(`/api/ekonomi/pendapatanaslidesa/pembiayaan/${this.id}`, {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name: this.form.name,
|
||||
value: this.form.value,
|
||||
}),
|
||||
});
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json().catch(() => ({}));
|
||||
throw new Error(
|
||||
errorData.message || `HTTP error! status: ${response.status}`
|
||||
);
|
||||
}
|
||||
const result = await response.json();
|
||||
if (result.success) {
|
||||
toast.success("Berhasil update pembiayaan");
|
||||
await pembiayaan.findMany.load(); // refresh list
|
||||
return true;
|
||||
} else {
|
||||
throw new Error(result.message || "Gagal mengupdate pembiayaan");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error updating pembiayaan:", error);
|
||||
toast.error(
|
||||
error instanceof Error ? error.message : "Gagal mengupdate pembiayaan"
|
||||
);
|
||||
return false;
|
||||
} finally {
|
||||
pembiayaan.update.loading = false;
|
||||
}
|
||||
},
|
||||
reset() {
|
||||
pembiayaan.update.id = "";
|
||||
pembiayaan.update.form = { ...PembiayaanDefaultForm };
|
||||
},
|
||||
},
|
||||
delete: {
|
||||
loading: false,
|
||||
async byId(id: string) {
|
||||
if (!id) return toast.warn("ID tidak valid");
|
||||
|
||||
try {
|
||||
pembiayaan.delete.loading = true;
|
||||
|
||||
const response = await fetch(
|
||||
`/api/ekonomi/pendapatanaslidesa/pembiayaan/del/${id}`,
|
||||
{
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (response.ok && result?.success) {
|
||||
toast.success(result.message || "Pembiayaan berhasil dihapus");
|
||||
await pembiayaan.findMany.load(); // refresh list
|
||||
} else {
|
||||
toast.error(result?.message || "Gagal menghapus Pembiayaan");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Gagal delete:", error);
|
||||
toast.error("Terjadi kesalahan saat menghapus Pembiayaan");
|
||||
} finally {
|
||||
pembiayaan.delete.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
findUnique: {
|
||||
data: null as Prisma.PembiayaanGetPayload<{
|
||||
select: { isActive: boolean };
|
||||
}> | null,
|
||||
async load(id: string) {
|
||||
const res = await fetch(
|
||||
`/api/ekonomi/pendapatanaslidesa/pembiayaan/${id}`
|
||||
);
|
||||
if (res.ok) {
|
||||
const json = await res.json();
|
||||
pembiayaan.findUnique.data = json.data
|
||||
? {
|
||||
...json.data,
|
||||
isActive: json.data.isActive ?? true, // Fallback ke aktif:true jika tidak ada data
|
||||
}
|
||||
: null;
|
||||
} else {
|
||||
pembiayaan.findUnique.data = null;
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const PendapatanAsliDesa = proxy({
|
||||
ApbDesa,
|
||||
belanja,
|
||||
pembiayaan,
|
||||
pendapatan,
|
||||
});
|
||||
|
||||
export default PendapatanAsliDesa;
|
||||
243
src/app/admin/(dashboard)/_state/ekonomi/jumlah-pengangguran.ts
Normal file
243
src/app/admin/(dashboard)/_state/ekonomi/jumlah-pengangguran.ts
Normal file
@@ -0,0 +1,243 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
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({
|
||||
findByMonthYear: {
|
||||
data: null as any,
|
||||
loading: false,
|
||||
load: async ({ month, year }: { month: string; year: number }) => {
|
||||
jumlahPengangguran.findByMonthYear.loading = true;
|
||||
try {
|
||||
const res = await fetch(
|
||||
`/api/ekonomi/jumlahpengangguran/detaildatapengangguran/month/${month}/year/${year}`
|
||||
);
|
||||
const json = await res.json();
|
||||
jumlahPengangguran.findByMonthYear.data = json.data;
|
||||
return json.data;
|
||||
} catch (err) {
|
||||
console.error("Gagal ambil data bulan/tahun:", err);
|
||||
} finally {
|
||||
jumlahPengangguran.findByMonthYear.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
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 jumlahPengangguranState = proxy({
|
||||
jumlahPengangguran,
|
||||
});
|
||||
|
||||
export default jumlahPengangguranState;
|
||||
@@ -30,9 +30,9 @@ const posisiOrganisasi = proxy({
|
||||
|
||||
try {
|
||||
this.loading = true;
|
||||
const res = await ApiFetch.api.ekonomi["struktur-organisasi"]["posisi-organisasi"]["create"].post(
|
||||
this.form
|
||||
);
|
||||
const res = await ApiFetch.api.ekonomi["struktur-organisasi"][
|
||||
"posisi-organisasi"
|
||||
]["create"].post(this.form);
|
||||
if (res.status === 200) {
|
||||
toast.success("Berhasil menambahkan posisi organisasi");
|
||||
posisiOrganisasi.findMany.load();
|
||||
@@ -62,12 +62,15 @@ const posisiOrganisasi = proxy({
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
const response = await fetch(`/api/ekonomi/struktur-organisasi/posisi-organisasi/${id}`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
const response = await fetch(
|
||||
`/api/ekonomi/struktur-organisasi/posisi-organisasi/${id}`,
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
@@ -160,7 +163,9 @@ const posisiOrganisasi = proxy({
|
||||
}>,
|
||||
async load() {
|
||||
try {
|
||||
const res = await ApiFetch.api.ekonomi["struktur-organisasi"]["posisi-organisasi"]["find-many"].get();
|
||||
const res = await ApiFetch.api.ekonomi["struktur-organisasi"][
|
||||
"posisi-organisasi"
|
||||
]["find-many"].get();
|
||||
if (res.status === 200) {
|
||||
// The API now returns the id field, so we can use it directly
|
||||
this.data = res.data?.data ?? [];
|
||||
@@ -209,238 +214,278 @@ const posisiOrganisasi = proxy({
|
||||
});
|
||||
|
||||
const templatePegawai = z.object({
|
||||
namaLengkap: z.string().min(1, "Nama wajib diisi"),
|
||||
gelarAkademik: z.string().optional(),
|
||||
imageId: z.string().nullable().optional(),
|
||||
tanggalMasuk: z.string().optional(), // ISO format
|
||||
email: z.string().email("Email tidak valid").optional(),
|
||||
telepon: z.string().optional(),
|
||||
alamat: z.string().optional(),
|
||||
posisiId: z.string().min(1, "Posisi wajib diisi"),
|
||||
isActive: z.boolean().default(true),
|
||||
});
|
||||
|
||||
const pegawaiDefaultForm = {
|
||||
namaLengkap: "",
|
||||
gelarAkademik: "",
|
||||
imageId: "",
|
||||
tanggalMasuk: "",
|
||||
email: "",
|
||||
telepon: "",
|
||||
alamat: "",
|
||||
posisiId: "",
|
||||
isActive: true,
|
||||
};
|
||||
|
||||
const pegawai = proxy({
|
||||
create: {
|
||||
form: { ...pegawaiDefaultForm },
|
||||
loading: false,
|
||||
async submit() {
|
||||
const cek = templatePegawai.safeParse(pegawai.create.form);
|
||||
if (!cek.success) {
|
||||
const err = cek.error.issues.map(i => i.message).join("\n");
|
||||
toast.error(err);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
pegawai.create.loading = true;
|
||||
const res = await ApiFetch.api.ekonomi["struktur-organisasi"]["pegawai"]["create"].post(
|
||||
pegawai.create.form
|
||||
);
|
||||
if (res.status === 200) {
|
||||
toast.success("Pegawai berhasil ditambahkan");
|
||||
await pegawai.findMany.load();
|
||||
} else {
|
||||
toast.error(res.data?.message ?? "Gagal tambah pegawai");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Gagal create:", error);
|
||||
toast.error("Terjadi kesalahan saat menambahkan pegawai");
|
||||
} finally {
|
||||
pegawai.create.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
findMany: {
|
||||
data: null as (Prisma.PegawaiGetPayload<{ include: { posisi: true, image: true } }> & { isActive: boolean })[] | null,
|
||||
async load() {
|
||||
try {
|
||||
const res = await ApiFetch.api.ekonomi["struktur-organisasi"]["pegawai"]["find-many"].get();
|
||||
if (res.status === 200) {
|
||||
pegawai.findMany.data = (res.data?.data ?? []).map((item: any) => ({
|
||||
...item,
|
||||
posisi: item.posisi || { id: '', nama: '' }, // Ensure posisi exists with required fields
|
||||
isActive: item.isActive ?? true // Default to true if not provided
|
||||
}));
|
||||
} else {
|
||||
console.error('Failed to load pegawai:', res.data?.message);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading pegawai:', error);
|
||||
pegawai.findMany.data = [];
|
||||
}
|
||||
},
|
||||
},
|
||||
findUnique: {
|
||||
data: null as (Prisma.PegawaiGetPayload<{ include: { posisi: true, image: true } }> & { isActive: boolean }) | null,
|
||||
async load(id: string) {
|
||||
const res = await fetch(`/api/ekonomi/struktur-organisasi/pegawai/${id}`);
|
||||
if (res.ok) {
|
||||
const json = await res.json();
|
||||
pegawai.findUnique.data = json.data ? {
|
||||
...json.data,
|
||||
isActive: json.data.isActive ?? json.data.aktif ?? true // Fallback ke aktif:true jika tidak ada data
|
||||
} : null;
|
||||
} else {
|
||||
pegawai.findUnique.data = null;
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
delete: {
|
||||
loading: false,
|
||||
async byId(id: string) {
|
||||
if (!id) return toast.warn("ID tidak valid");
|
||||
try {
|
||||
pegawai.delete.loading = true;
|
||||
const res = await fetch(`/api/ekonomi/struktur-organisasi/pegawai/del/${id}`, {
|
||||
method: "DELETE",
|
||||
});
|
||||
const json = await res.json();
|
||||
if (res.ok) {
|
||||
toast.success(json.message ?? "Berhasil hapus pegawai");
|
||||
await pegawai.findMany.load();
|
||||
} else {
|
||||
toast.error(json.message ?? "Gagal hapus pegawai");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Gagal delete:", error);
|
||||
toast.error("Terjadi kesalahan saat menghapus");
|
||||
} finally {
|
||||
pegawai.delete.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
edit: {
|
||||
id: "",
|
||||
form: { ...pegawaiDefaultForm },
|
||||
loading: false,
|
||||
|
||||
async load(id: string) {
|
||||
if (!id) {
|
||||
toast.warn("ID tidak valid");
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/ekonomi/struktur-organisasi/pegawai/${id}`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result?.success) {
|
||||
const data = result.data;
|
||||
this.id = data.id;
|
||||
this.form = {
|
||||
namaLengkap: data.namaLengkap,
|
||||
gelarAkademik: data.gelarAkademik,
|
||||
imageId: data.imageId,
|
||||
tanggalMasuk: data.tanggalMasuk,
|
||||
email: data.email,
|
||||
telepon: data.telepon,
|
||||
alamat: data.alamat,
|
||||
posisiId: data.posisiId,
|
||||
isActive: data.isActive,
|
||||
};
|
||||
return data; // Return the loaded data
|
||||
} else {
|
||||
throw new Error(result?.message || "Gagal memuat data");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading berita:", error);
|
||||
toast.error(error instanceof Error ? error.message : "Gagal memuat data");
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
async submit() {
|
||||
const cek = templatePegawai.safeParse(pegawai.edit.form);
|
||||
if (!cek.success) {
|
||||
const err = `[${cek.error.issues
|
||||
.map((v) => `${v.path.join(".")}`)
|
||||
.join("\n")}] required`;
|
||||
toast.error(err);
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
pegawai.edit.loading = true;
|
||||
|
||||
// Format tanggalMasuk to ISO string if it exists
|
||||
const formattedTanggalMasuk = this.form.tanggalMasuk
|
||||
? new Date(this.form.tanggalMasuk).toISOString()
|
||||
: undefined;
|
||||
|
||||
const response = await fetch(`/api/ekonomi/struktur-organisasi/pegawai/${this.id}`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
id: this.id,
|
||||
namaLengkap: this.form.namaLengkap,
|
||||
gelarAkademik: this.form.gelarAkademik,
|
||||
imageId: this.form.imageId || null,
|
||||
tanggalMasuk: formattedTanggalMasuk,
|
||||
email: this.form.email,
|
||||
telepon: this.form.telepon,
|
||||
alamat: this.form.alamat,
|
||||
posisiId: this.form.posisiId,
|
||||
isActive: this.form.isActive,
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json().catch(() => ({}));
|
||||
throw new Error(errorData.message || `HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
toast.success("Berhasil update pegawai");
|
||||
await pegawai.findMany.load(); // refresh list
|
||||
return true;
|
||||
} else {
|
||||
throw new Error(result.message || "Gagal update pegawai");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error updating pegawai:", error);
|
||||
toast.error(error instanceof Error ? error.message : "Terjadi kesalahan saat update pegawai");
|
||||
return false;
|
||||
} finally {
|
||||
pegawai.edit.loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
reset() {
|
||||
pegawai.edit.id = "";
|
||||
pegawai.edit.form = { ...pegawaiDefaultForm };
|
||||
},
|
||||
},
|
||||
});
|
||||
namaLengkap: z.string().min(1, "Nama wajib diisi"),
|
||||
gelarAkademik: z.string().optional(),
|
||||
imageId: z.string().nullable().optional(),
|
||||
tanggalMasuk: z.string().optional(), // ISO format
|
||||
email: z.string().email("Email tidak valid").optional(),
|
||||
telepon: z.string().optional(),
|
||||
alamat: z.string().optional(),
|
||||
posisiId: z.string().min(1, "Posisi wajib diisi"),
|
||||
isActive: z.boolean().default(true),
|
||||
});
|
||||
|
||||
const pegawaiDefaultForm = {
|
||||
namaLengkap: "",
|
||||
gelarAkademik: "",
|
||||
imageId: "",
|
||||
tanggalMasuk: "",
|
||||
email: "",
|
||||
telepon: "",
|
||||
alamat: "",
|
||||
posisiId: "",
|
||||
isActive: true,
|
||||
};
|
||||
|
||||
const pegawai = proxy({
|
||||
create: {
|
||||
form: { ...pegawaiDefaultForm },
|
||||
loading: false,
|
||||
async submit() {
|
||||
const cek = templatePegawai.safeParse(pegawai.create.form);
|
||||
if (!cek.success) {
|
||||
const err = cek.error.issues.map((i) => i.message).join("\n");
|
||||
toast.error(err);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
pegawai.create.loading = true;
|
||||
const res = await ApiFetch.api.ekonomi["struktur-organisasi"][
|
||||
"pegawai"
|
||||
]["create"].post(pegawai.create.form);
|
||||
if (res.status === 200) {
|
||||
toast.success("Pegawai berhasil ditambahkan");
|
||||
await pegawai.findMany.load();
|
||||
} else {
|
||||
toast.error(res.data?.message ?? "Gagal tambah pegawai");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Gagal create:", error);
|
||||
toast.error("Terjadi kesalahan saat menambahkan pegawai");
|
||||
} finally {
|
||||
pegawai.create.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
// In struktur-organisasi.ts
|
||||
findMany: {
|
||||
data: null as any[] | null,
|
||||
page: 1,
|
||||
totalPages: 1,
|
||||
total: 0,
|
||||
loading: false,
|
||||
load: async (page = 1, limit = 10) => { // Change to arrow function
|
||||
pegawai.findMany.loading = true; // Use the full path to access the property
|
||||
pegawai.findMany.page = page;
|
||||
try {
|
||||
const res = await ApiFetch.api.ekonomi["struktur-organisasi"][
|
||||
"pegawai"
|
||||
]["find-many"].get({
|
||||
query: { page, limit },
|
||||
});
|
||||
|
||||
if (res.status === 200 && res.data?.success) {
|
||||
pegawai.findMany.data = res.data.data || [];
|
||||
pegawai.findMany.total = res.data.total || 0;
|
||||
pegawai.findMany.totalPages = res.data.totalPages || 1;
|
||||
} else {
|
||||
console.error("Failed to load pegawai:", res.data?.message);
|
||||
pegawai.findMany.data = [];
|
||||
pegawai.findMany.total = 0;
|
||||
pegawai.findMany.totalPages = 1;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading pegawai:", error);
|
||||
pegawai.findMany.data = [];
|
||||
pegawai.findMany.total = 0;
|
||||
pegawai.findMany.totalPages = 1;
|
||||
} finally {
|
||||
pegawai.findMany.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
findUnique: {
|
||||
data: null as
|
||||
| (Prisma.PegawaiGetPayload<{
|
||||
include: { posisi: true; image: true };
|
||||
}> & { isActive: boolean })
|
||||
| null,
|
||||
async load(id: string) {
|
||||
const res = await fetch(`/api/ekonomi/struktur-organisasi/pegawai/${id}`);
|
||||
if (res.ok) {
|
||||
const json = await res.json();
|
||||
pegawai.findUnique.data = json.data
|
||||
? {
|
||||
...json.data,
|
||||
isActive: json.data.isActive ?? json.data.aktif ?? true, // Fallback ke aktif:true jika tidak ada data
|
||||
}
|
||||
: null;
|
||||
} else {
|
||||
pegawai.findUnique.data = null;
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
delete: {
|
||||
loading: false,
|
||||
async byId(id: string) {
|
||||
if (!id) return toast.warn("ID tidak valid");
|
||||
try {
|
||||
pegawai.delete.loading = true;
|
||||
const res = await fetch(
|
||||
`/api/ekonomi/struktur-organisasi/pegawai/del/${id}`,
|
||||
{
|
||||
method: "DELETE",
|
||||
}
|
||||
);
|
||||
const json = await res.json();
|
||||
if (res.ok) {
|
||||
toast.success(json.message ?? "Berhasil hapus pegawai");
|
||||
await pegawai.findMany.load();
|
||||
} else {
|
||||
toast.error(json.message ?? "Gagal hapus pegawai");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Gagal delete:", error);
|
||||
toast.error("Terjadi kesalahan saat menghapus");
|
||||
} finally {
|
||||
pegawai.delete.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
edit: {
|
||||
id: "",
|
||||
form: { ...pegawaiDefaultForm },
|
||||
loading: false,
|
||||
|
||||
async load(id: string) {
|
||||
if (!id) {
|
||||
toast.warn("ID tidak valid");
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(
|
||||
`/api/ekonomi/struktur-organisasi/pegawai/${id}`,
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result?.success) {
|
||||
const data = result.data;
|
||||
this.id = data.id;
|
||||
this.form = {
|
||||
namaLengkap: data.namaLengkap,
|
||||
gelarAkademik: data.gelarAkademik,
|
||||
imageId: data.imageId,
|
||||
tanggalMasuk: data.tanggalMasuk,
|
||||
email: data.email,
|
||||
telepon: data.telepon,
|
||||
alamat: data.alamat,
|
||||
posisiId: data.posisiId,
|
||||
isActive: data.isActive,
|
||||
};
|
||||
return data; // Return the loaded data
|
||||
} else {
|
||||
throw new Error(result?.message || "Gagal memuat data");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading berita:", error);
|
||||
toast.error(
|
||||
error instanceof Error ? error.message : "Gagal memuat data"
|
||||
);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
async submit() {
|
||||
const cek = templatePegawai.safeParse(pegawai.edit.form);
|
||||
if (!cek.success) {
|
||||
const err = `[${cek.error.issues
|
||||
.map((v) => `${v.path.join(".")}`)
|
||||
.join("\n")}] required`;
|
||||
toast.error(err);
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
pegawai.edit.loading = true;
|
||||
|
||||
// Format tanggalMasuk to ISO string if it exists
|
||||
const formattedTanggalMasuk = this.form.tanggalMasuk
|
||||
? new Date(this.form.tanggalMasuk).toISOString()
|
||||
: undefined;
|
||||
|
||||
const response = await fetch(
|
||||
`/api/ekonomi/struktur-organisasi/pegawai/${this.id}`,
|
||||
{
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
id: this.id,
|
||||
namaLengkap: this.form.namaLengkap,
|
||||
gelarAkademik: this.form.gelarAkademik,
|
||||
imageId: this.form.imageId || null,
|
||||
tanggalMasuk: formattedTanggalMasuk,
|
||||
email: this.form.email,
|
||||
telepon: this.form.telepon,
|
||||
alamat: this.form.alamat,
|
||||
posisiId: this.form.posisiId,
|
||||
isActive: this.form.isActive,
|
||||
}),
|
||||
}
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json().catch(() => ({}));
|
||||
throw new Error(
|
||||
errorData.message || `HTTP error! status: ${response.status}`
|
||||
);
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
toast.success("Berhasil update pegawai");
|
||||
await pegawai.findMany.load(); // refresh list
|
||||
return true;
|
||||
} else {
|
||||
throw new Error(result.message || "Gagal update pegawai");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error updating pegawai:", error);
|
||||
toast.error(
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: "Terjadi kesalahan saat update pegawai"
|
||||
);
|
||||
return false;
|
||||
} finally {
|
||||
pegawai.edit.loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
reset() {
|
||||
pegawai.edit.id = "";
|
||||
pegawai.edit.form = { ...pegawaiDefaultForm };
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Schema Zod untuk form validasi
|
||||
const templateHubunganOrganisasiForm = z.object({
|
||||
@@ -474,7 +519,9 @@ const hubunganOrganisasi = proxy({
|
||||
|
||||
try {
|
||||
hubunganOrganisasi.create.loading = true;
|
||||
const res = await ApiFetch.api.ekonomi["struktur-organisasi"]["hubungan-organisasi"]["create"].post(hubunganOrganisasi.create.form);
|
||||
const res = await ApiFetch.api.ekonomi["struktur-organisasi"][
|
||||
"hubungan-organisasi"
|
||||
]["create"].post(hubunganOrganisasi.create.form);
|
||||
|
||||
if (res.status === 200 && res.data?.success) {
|
||||
hubunganOrganisasi.findMany.load();
|
||||
@@ -482,7 +529,7 @@ const hubunganOrganisasi = proxy({
|
||||
} else {
|
||||
return toast.error(res.data?.message || "Gagal menambahkan data");
|
||||
}
|
||||
} catch (error) {
|
||||
} catch (error) {
|
||||
console.error("Gagal create:", error);
|
||||
toast.error("Terjadi kesalahan saat menambahkan");
|
||||
} finally {
|
||||
@@ -490,7 +537,7 @@ const hubunganOrganisasi = proxy({
|
||||
}
|
||||
},
|
||||
},
|
||||
findMany: {
|
||||
findMany: {
|
||||
data: null as Array<{
|
||||
id: string;
|
||||
atasanId: string;
|
||||
@@ -528,20 +575,29 @@ const hubunganOrganisasi = proxy({
|
||||
|
||||
async load() {
|
||||
try {
|
||||
const res = await ApiFetch.api.ekonomi["struktur-organisasi"]["hubungan-organisasi"]["find-many"].get();
|
||||
const res = await ApiFetch.api.ekonomi["struktur-organisasi"][
|
||||
"hubungan-organisasi"
|
||||
]["find-many"].get();
|
||||
|
||||
if (res.status === 200) {
|
||||
hubunganOrganisasi.findMany.data = (res.data?.data ?? []).map((item: any) => ({
|
||||
...item,
|
||||
atasan: item.atasan ? {
|
||||
...item.atasan,
|
||||
isActive: item.atasan.isActive ?? item.atasan.aktif ?? true
|
||||
} : null,
|
||||
bawahan: item.bawahan ? {
|
||||
...item.bawahan,
|
||||
isActive: item.bawahan.isActive ?? item.bawahan.aktif ?? true
|
||||
} : null
|
||||
}));
|
||||
hubunganOrganisasi.findMany.data = (res.data?.data ?? []).map(
|
||||
(item: any) => ({
|
||||
...item,
|
||||
atasan: item.atasan
|
||||
? {
|
||||
...item.atasan,
|
||||
isActive: item.atasan.isActive ?? item.atasan.aktif ?? true,
|
||||
}
|
||||
: null,
|
||||
bawahan: item.bawahan
|
||||
? {
|
||||
...item.bawahan,
|
||||
isActive:
|
||||
item.bawahan.isActive ?? item.bawahan.aktif ?? true,
|
||||
}
|
||||
: null,
|
||||
})
|
||||
);
|
||||
} else {
|
||||
hubunganOrganisasi.findMany.data = [];
|
||||
}
|
||||
@@ -591,7 +647,9 @@ const hubunganOrganisasi = proxy({
|
||||
|
||||
async load(id: string) {
|
||||
try {
|
||||
const res = await fetch(`/api/ekonomi/struktur-organisasi/hubungan-organisasi/${id}`);
|
||||
const res = await fetch(
|
||||
`/api/ekonomi/struktur-organisasi/hubungan-organisasi/${id}`
|
||||
);
|
||||
const result = await res.json();
|
||||
|
||||
if (res.ok && result?.success) {
|
||||
@@ -616,7 +674,9 @@ const hubunganOrganisasi = proxy({
|
||||
if (!id) return toast.warn("ID tidak valid");
|
||||
|
||||
try {
|
||||
const res = await fetch(`/api/ekonomi/struktur-organisasi/hubungan-organisasi/${id}`);
|
||||
const res = await fetch(
|
||||
`/api/ekonomi/struktur-organisasi/hubungan-organisasi/${id}`
|
||||
);
|
||||
const result = await res.json();
|
||||
|
||||
if (res.ok && result?.success) {
|
||||
@@ -633,7 +693,9 @@ const hubunganOrganisasi = proxy({
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading:", error);
|
||||
toast.error(error instanceof Error ? error.message : "Gagal memuat data");
|
||||
toast.error(
|
||||
error instanceof Error ? error.message : "Gagal memuat data"
|
||||
);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
@@ -690,9 +752,12 @@ const hubunganOrganisasi = proxy({
|
||||
|
||||
try {
|
||||
hubunganOrganisasi.delete.loading = true;
|
||||
const res = await fetch(`/api/ekonomi/struktur-organisasi/hubungan-organisasi/del/${id}`, {
|
||||
method: "DELETE",
|
||||
});
|
||||
const res = await fetch(
|
||||
`/api/ekonomi/struktur-organisasi/hubungan-organisasi/del/${id}`,
|
||||
{
|
||||
method: "DELETE",
|
||||
}
|
||||
);
|
||||
|
||||
const result = await res.json();
|
||||
if (res.ok && result?.success) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Grid, GridCol, Image, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
||||
import { Box, Button, Grid, GridCol, Image, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { IconCircleDashedPlus, IconDeviceImacCog, IconSearch } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
@@ -29,12 +29,21 @@ function Berita() {
|
||||
function ListBerita({ search }: { search: string }) {
|
||||
const beritaState = useProxy(stateDashboardBerita)
|
||||
const router = useRouter()
|
||||
const {
|
||||
data,
|
||||
page,
|
||||
totalPages,
|
||||
loading,
|
||||
load,
|
||||
} = beritaState.berita.findMany;
|
||||
|
||||
|
||||
// Fetch pertama kali
|
||||
useShallowEffect(() => {
|
||||
beritaState.berita.findMany.load()
|
||||
}, [])
|
||||
load(page); // awal page = 1
|
||||
}, []);
|
||||
|
||||
const filteredData = (beritaState.berita.findMany.data || []).filter(item => {
|
||||
const filteredData = (data || []).filter((item) => {
|
||||
const keyword = search.toLowerCase();
|
||||
return (
|
||||
item.judul.toLowerCase().includes(keyword) ||
|
||||
@@ -42,67 +51,84 @@ function ListBerita({ search }: { search: string }) {
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
if (!beritaState.berita.findMany.data) {
|
||||
return (
|
||||
<Stack py={10}>
|
||||
<Skeleton h={500} />
|
||||
</Stack>
|
||||
)
|
||||
if (loading || !data) {
|
||||
return <Skeleton h={500} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Box py={10}>
|
||||
<Paper bg={colors['white-1']} p={'md'}>
|
||||
<Pagination
|
||||
value={page}
|
||||
onChange={(newPage) => load(newPage)} // ini penting!
|
||||
total={totalPages}
|
||||
mt="md"
|
||||
mb="md"
|
||||
/>
|
||||
|
||||
<Paper bg={colors["white-1"]} p={"md"}>
|
||||
<Stack>
|
||||
<Grid>
|
||||
<GridCol span={{ base: 12, md: 11 }}>
|
||||
<Text fz={"xl"} fw={"bold"}>List Berita</Text>
|
||||
<Text fz={"xl"} fw={"bold"}>
|
||||
List Berita
|
||||
</Text>
|
||||
</GridCol>
|
||||
<GridCol span={{ base: 12, md: 1 }}>
|
||||
<Button onClick={() => router.push("/admin/desa/berita/create")} bg={colors['blue-button']}>
|
||||
<Button
|
||||
onClick={() => router.push("/admin/desa/berita/create")}
|
||||
bg={colors["blue-button"]}
|
||||
>
|
||||
<IconCircleDashedPlus size={25} />
|
||||
</Button>
|
||||
</GridCol>
|
||||
</Grid>
|
||||
<Box style={{ overflowX: "auto" }}>
|
||||
<Table striped withRowBorders withTableBorder style={{ minWidth: '700px' }}>
|
||||
<Table
|
||||
striped
|
||||
withRowBorders
|
||||
withTableBorder
|
||||
style={{ minWidth: "700px" }}
|
||||
>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh w={250}>Judul</TableTh>
|
||||
<TableTh w={250}>Kategori</TableTh>
|
||||
<TableTh w={250}>Image</TableTh>
|
||||
<TableTh w={200}>Detail</TableTh>
|
||||
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody >
|
||||
<TableTbody>
|
||||
{filteredData.map((item) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd >
|
||||
<TableTd>
|
||||
<Box w={100}>
|
||||
<Text truncate="end" fz={"sm"}>{item.judul}</Text>
|
||||
<Text truncate="end" fz={"sm"}>
|
||||
{item.judul}
|
||||
</Text>
|
||||
</Box>
|
||||
</TableTd>
|
||||
<TableTd >{item.kategoriBerita?.name}</TableTd>
|
||||
<TableTd>{item.kategoriBerita?.name}</TableTd>
|
||||
<TableTd>
|
||||
<Image w={100} src={item.image?.link} alt="gambar" />
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Button bg={"green"} onClick={() => router.push(`/admin/desa/berita/${item.id}`)}>
|
||||
<Button
|
||||
bg={"green"}
|
||||
onClick={() =>
|
||||
router.push(`/admin/desa/berita/${item.id}`)
|
||||
}
|
||||
>
|
||||
<IconDeviceImacCog size={25} />
|
||||
</Button>
|
||||
</TableTd>
|
||||
|
||||
</TableTr>
|
||||
))}
|
||||
</TableTbody>
|
||||
</Table> </Box>
|
||||
</Table>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
</Box>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export default Berita;
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
/* 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: "APB Desa",
|
||||
value: "apbdesa",
|
||||
href: "/admin/ekonomi/PADesa-pendapatan-asli-desa/apbdesa"
|
||||
},
|
||||
{
|
||||
label: "Pendapatan",
|
||||
value: "pendapatan",
|
||||
href: "/admin/ekonomi/PADesa-pendapatan-asli-desa/pendapatan"
|
||||
},
|
||||
{
|
||||
label: "Belanja",
|
||||
value: "belanja",
|
||||
href: "/admin/ekonomi/PADesa-pendapatan-asli-desa/belanja"
|
||||
},
|
||||
{
|
||||
label: "Pembiayaan",
|
||||
value: "pembiayaan",
|
||||
href: "/admin/ekonomi/PADesa-pendapatan-asli-desa/pembiayaan"
|
||||
},
|
||||
|
||||
];
|
||||
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}>Pendapatan Asli Desa</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;
|
||||
@@ -3,7 +3,7 @@ import React from 'react';
|
||||
function Page() {
|
||||
return (
|
||||
<div>
|
||||
PADesa-pendapatan-asli-desa
|
||||
Page
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
'use client'
|
||||
import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus';
|
||||
import PendapatanAsliDesa from '@/app/admin/(dashboard)/_state/ekonomi/PADesa';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Flex, Paper, Skeleton, Stack, Text } from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { IconArrowBack, IconEdit, IconX } from '@tabler/icons-react';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import { useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
|
||||
|
||||
function DetailAPBDesa() {
|
||||
const apbState = useProxy(PendapatanAsliDesa.ApbDesa)
|
||||
const [modalHapus, setModalHapus] = useState(false)
|
||||
const [selectedId, setSelectedId] = useState<string | null>(null)
|
||||
const params = useParams()
|
||||
const router = useRouter()
|
||||
|
||||
useShallowEffect(() => {
|
||||
console.log("PARAM ID:", params?.id)
|
||||
apbState.findUnique.load(params?.id as string)
|
||||
}, [])
|
||||
|
||||
const formatRupiah = (value: number) => {
|
||||
return new Intl.NumberFormat('id-ID', {
|
||||
style: 'currency',
|
||||
currency: 'IDR',
|
||||
minimumFractionDigits: 0,
|
||||
}).format(value);
|
||||
};
|
||||
|
||||
const handleHapus = () => {
|
||||
if (selectedId) {
|
||||
apbState.delete.byId(selectedId)
|
||||
setModalHapus(false)
|
||||
setSelectedId(null)
|
||||
router.push("/admin/ekonomi/PADesa-pendapatan-asli-desa/apbdesa")
|
||||
}
|
||||
}
|
||||
|
||||
if (!apbState.findUnique.data) {
|
||||
return (
|
||||
<Stack py={10}>
|
||||
<Skeleton h={40} />
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Box mb={10}>
|
||||
<Button variant="subtle" onClick={() => router.back()}>
|
||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||
</Button>
|
||||
</Box>
|
||||
<Paper bg={colors['white-1']} w={{ base: "100%", md: "100%", lg: "50%" }} p={'md'}>
|
||||
<Stack>
|
||||
<Text fz={"xl"} fw={"bold"}>Detail APB Desa</Text>
|
||||
{apbState.findUnique.data ? (
|
||||
<Paper key={apbState.findUnique.data.id} bg={colors['BG-trans']} p={'md'}>
|
||||
<Stack gap={"xs"}>
|
||||
<Box>
|
||||
<Text fw={"bold"} fz={"lg"}>Tahun</Text>
|
||||
<Text fz={"lg"}>{apbState.findUnique.data?.tahun}</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Stack gap={"xs"}>
|
||||
<Text fw={"bold"} fz={"lg"}>Detail Pembiayaan</Text>
|
||||
{(apbState.findUnique.data?.pembiayaan || []).map((item) => (
|
||||
<Text fz={"lg"} key={item.id}>
|
||||
{item.name}: {formatRupiah(Number(item.value))}
|
||||
</Text>
|
||||
))}
|
||||
<Text fz={"lg"} fw={"bold"}>
|
||||
Total: {formatRupiah((apbState.findUnique.data?.pembiayaan || [])
|
||||
.reduce((sum, item) => sum + Number(item.value), 0))}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Box>
|
||||
<Box>
|
||||
<Stack gap={"xs"}>
|
||||
<Text fw={"bold"} fz={"lg"}>Detail Belanja</Text>
|
||||
{(apbState.findUnique.data?.belanja || []).map((item) => (
|
||||
<Text fz={"lg"} key={item.id}>
|
||||
{item.name}: {formatRupiah(Number(item.value))}
|
||||
</Text>
|
||||
))}
|
||||
<Text fz={"lg"} fw={"bold"}>
|
||||
Total: {formatRupiah((apbState.findUnique.data?.belanja || [])
|
||||
.reduce((sum, item) => sum + Number(item.value), 0))}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Box>
|
||||
<Box>
|
||||
<Stack gap={"xs"}>
|
||||
<Text fw={"bold"} fz={"lg"}>Detail Pendapatan</Text>
|
||||
{(apbState.findUnique.data?.pendapatan || []).map((item) => (
|
||||
<Text fz={"lg"} key={item.id}>
|
||||
{item.name}: {formatRupiah(Number(item.value))}
|
||||
</Text>
|
||||
))}
|
||||
<Text fz={"lg"} fw={"bold"}>
|
||||
Total: {formatRupiah((apbState.findUnique.data?.pendapatan || [])
|
||||
.reduce((sum, item) => sum + Number(item.value), 0))}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Box>
|
||||
<Flex gap={"xs"} mt={10}>
|
||||
<Button
|
||||
onClick={() => {
|
||||
if (apbState.findUnique.data) {
|
||||
setSelectedId(apbState.findUnique.data.id);
|
||||
setModalHapus(true);
|
||||
}
|
||||
}}
|
||||
disabled={apbState.delete.loading || !apbState.findUnique.data}
|
||||
color={"red"}
|
||||
>
|
||||
<IconX size={20} />
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
if (apbState.findUnique.data) {
|
||||
router.push(`/admin/ekonomi/PADesa-pendapatan-asli-desa/apbdesa/${apbState.findUnique.data.id}/edit`);
|
||||
}
|
||||
}}
|
||||
disabled={!apbState.findUnique.data}
|
||||
color={"green"}
|
||||
>
|
||||
<IconEdit size={20} />
|
||||
</Button>
|
||||
</Flex>
|
||||
</Stack>
|
||||
</Paper>
|
||||
) : null}
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
{/* Modal Konfirmasi Hapus */}
|
||||
<ModalKonfirmasiHapus
|
||||
opened={modalHapus}
|
||||
onClose={() => setModalHapus(false)}
|
||||
onConfirm={handleHapus}
|
||||
text='Apakah anda yakin ingin menghapus APB Desa ini?'
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default DetailAPBDesa;
|
||||
@@ -0,0 +1,190 @@
|
||||
'use client'
|
||||
import PendapatanAsliDesa from '@/app/admin/(dashboard)/_state/ekonomi/PADesa';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, MultiSelect, Paper, Skeleton, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { IconArrowBack } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
function CreateAPBDesa() {
|
||||
const apbDesaState = useProxy(PendapatanAsliDesa.ApbDesa)
|
||||
const router = useRouter()
|
||||
|
||||
const resetForm = () => {
|
||||
apbDesaState.create.form = {
|
||||
tahun: 0,
|
||||
pendapatanIds: [],
|
||||
belanjaIds: [],
|
||||
pembiayaanIds: [],
|
||||
}
|
||||
}
|
||||
|
||||
const handleSubmit = async () => {
|
||||
await apbDesaState.create.submit()
|
||||
resetForm()
|
||||
router.push("/admin/ekonomi/PADesa-pendapatan-asli-desa/apbdesa")
|
||||
}
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Box mb={10}>
|
||||
<Button variant="subtle" onClick={() => router.back()}>
|
||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||
</Button>
|
||||
</Box>
|
||||
<Paper bg={colors["white-1"]} p={"md"} w={{ base: "100%", md: "50%" }}>
|
||||
<Stack gap={"xs"}>
|
||||
<Title order={3}>Create APB Desa</Title>
|
||||
<TextInput
|
||||
type='number'
|
||||
value={apbDesaState.create.form.tahun}
|
||||
onChange={(val) => {
|
||||
apbDesaState.create.form.tahun = Number(val.target.value);
|
||||
}}
|
||||
label={<Text fz={"sm"} fw={"bold"}>Tahun</Text>}
|
||||
placeholder="masukkan tahun"
|
||||
/>
|
||||
<SelectPendapatan
|
||||
selectedIds={apbDesaState.create.form.pendapatanIds}
|
||||
onSelectionChange={(ids) => {
|
||||
apbDesaState.create.form.pendapatanIds = ids;
|
||||
}}
|
||||
/>
|
||||
<SelectBelanja
|
||||
selectedIds={apbDesaState.create.form.belanjaIds}
|
||||
onSelectionChange={(ids) => {
|
||||
apbDesaState.create.form.belanjaIds = ids;
|
||||
}}
|
||||
/>
|
||||
<SelectPembiayaan
|
||||
selectedIds={apbDesaState.create.form.pembiayaanIds}
|
||||
onSelectionChange={(ids) => {
|
||||
apbDesaState.create.form.pembiayaanIds = ids;
|
||||
}}
|
||||
/>
|
||||
<Button bg={colors['blue-button']} onClick={handleSubmit}>Simpan</Button>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
|
||||
/* Select Pendapatan */
|
||||
interface SelectPendapatanProps {
|
||||
selectedIds: string[];
|
||||
onSelectionChange: (ids: string[]) => void;
|
||||
}
|
||||
|
||||
function SelectPendapatan({
|
||||
selectedIds = [],
|
||||
onSelectionChange,
|
||||
}: SelectPendapatanProps) {
|
||||
const pendapatanState = useProxy(PendapatanAsliDesa.pendapatan);
|
||||
|
||||
useShallowEffect(() => {
|
||||
pendapatanState.findMany.load().then(() => {
|
||||
console.log("Pendapatan berhasil dimuat:", pendapatanState.findMany.data);
|
||||
});
|
||||
}, []);
|
||||
|
||||
if (!pendapatanState.findMany.data) {
|
||||
return <Skeleton height={38} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<MultiSelect
|
||||
label={<Text fz={"sm"} fw={"bold"}>Pendapatan</Text>}
|
||||
data={pendapatanState.findMany.data.map(p => ({
|
||||
value: p.id,
|
||||
label: p.name
|
||||
}))}
|
||||
value={selectedIds}
|
||||
onChange={onSelectionChange}
|
||||
searchable
|
||||
clearable
|
||||
placeholder="Pilih pendapatan..."
|
||||
nothingFoundMessage="Tidak ditemukan"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
/* Select Belanja */
|
||||
interface SelectBelanjaProps {
|
||||
selectedIds: string[];
|
||||
onSelectionChange: (ids: string[]) => void;
|
||||
}
|
||||
|
||||
function SelectBelanja({
|
||||
selectedIds = [],
|
||||
onSelectionChange,
|
||||
}: SelectBelanjaProps) {
|
||||
const belanjaState = useProxy(PendapatanAsliDesa.belanja);
|
||||
|
||||
useShallowEffect(() => {
|
||||
belanjaState.findMany.load().then(() => {
|
||||
console.log("Belanja berhasil dimuat:", belanjaState.findMany.data);
|
||||
});
|
||||
}, []);
|
||||
|
||||
if (!belanjaState.findMany.data) {
|
||||
return <Skeleton height={38} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<MultiSelect
|
||||
label={<Text fz={"sm"} fw={"bold"}>Belanja</Text>}
|
||||
data={belanjaState.findMany.data.map(b => ({
|
||||
value: b.id,
|
||||
label: b.name
|
||||
}))}
|
||||
value={selectedIds}
|
||||
onChange={onSelectionChange}
|
||||
searchable
|
||||
clearable
|
||||
placeholder="Pilih belanja..."
|
||||
nothingFoundMessage="Tidak ditemukan"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
/* Select Pembiayaan */
|
||||
interface SelectPembiayaanProps {
|
||||
selectedIds: string[];
|
||||
onSelectionChange: (ids: string[]) => void;
|
||||
}
|
||||
|
||||
function SelectPembiayaan({
|
||||
selectedIds = [],
|
||||
onSelectionChange,
|
||||
}: SelectPembiayaanProps) {
|
||||
const pembiayaanState = useProxy(PendapatanAsliDesa.pembiayaan);
|
||||
|
||||
useShallowEffect(() => {
|
||||
pembiayaanState.findMany.load().then(() => {
|
||||
console.log("Pembiayaan berhasil dimuat:", pembiayaanState.findMany.data);
|
||||
});
|
||||
}, []);
|
||||
|
||||
if (!pembiayaanState.findMany.data) {
|
||||
return <Skeleton height={38} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<MultiSelect
|
||||
label={<Text fz={"sm"} fw={"bold"}>Pembiayaan</Text>}
|
||||
data={pembiayaanState.findMany.data.map(b => ({
|
||||
value: b.id,
|
||||
label: b.name
|
||||
}))}
|
||||
value={selectedIds}
|
||||
onChange={onSelectionChange}
|
||||
searchable
|
||||
clearable
|
||||
placeholder="Pilih pembiayaan..."
|
||||
nothingFoundMessage="Tidak ditemukan"
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default CreateAPBDesa;
|
||||
@@ -0,0 +1,106 @@
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { 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 { useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import HeaderSearch from '../../../_com/header';
|
||||
import JudulList from '../../../_com/judulList';
|
||||
import PendapatanAsliDesa from '../../../_state/ekonomi/PADesa';
|
||||
|
||||
|
||||
function APBDesa() {
|
||||
const [search, setSearch] = useState("");
|
||||
return (
|
||||
<Box>
|
||||
<HeaderSearch
|
||||
title='APB Desa'
|
||||
placeholder='pencarian'
|
||||
searchIcon={<IconSearch size={20} />}
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||
/>
|
||||
<ListAPBDesa search={search} />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
function ListAPBDesa({ search }: { search: string }) {
|
||||
const apbDesaState = useProxy(PendapatanAsliDesa.ApbDesa)
|
||||
const router = useRouter();
|
||||
|
||||
const formatRupiah = (value: number) => {
|
||||
return new Intl.NumberFormat('id-ID', {
|
||||
style: 'currency',
|
||||
currency: 'IDR',
|
||||
minimumFractionDigits: 0,
|
||||
}).format(value);
|
||||
};
|
||||
|
||||
useShallowEffect(() => {
|
||||
apbDesaState.findMany.load();
|
||||
}, [])
|
||||
|
||||
const filteredData = (apbDesaState.findMany.data || []).filter(item => {
|
||||
const keyword = search.toLowerCase();
|
||||
return (
|
||||
item.tahun.toString().toLowerCase().includes(keyword) ||
|
||||
item.pembiayaan.map((item) => item.value.toString()).includes(keyword) ||
|
||||
item.belanja.map((item) => item.value.toString()).includes(keyword) ||
|
||||
item.pendapatan.map((item) => item.value.toString()).includes(keyword)
|
||||
);
|
||||
});
|
||||
|
||||
if (!apbDesaState.findMany.data) {
|
||||
return (
|
||||
<Stack py={10}>
|
||||
<Skeleton h={500} />
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<Box py={10}>
|
||||
<Paper bg={colors['white-1']} p={'md'}>
|
||||
<JudulList
|
||||
title='List APB Desa'
|
||||
href='/admin/ekonomi/PADesa-pendapatan-asli-desa/apbdesa/create'
|
||||
/>
|
||||
<Table striped withTableBorder withRowBorders>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh>Tahun</TableTh>
|
||||
<TableTh>Pembiayaan</TableTh>
|
||||
<TableTh>Belanja</TableTh>
|
||||
<TableTh>Pendapatan</TableTh>
|
||||
<TableTh>Detail</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{filteredData.map((item) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd>{item.tahun}</TableTd>
|
||||
<TableTd>{formatRupiah(item.pembiayaan.reduce((sum, item) => sum + Number(item.value), 0))}</TableTd>
|
||||
<TableTd>{formatRupiah(item.belanja.reduce((sum, item) => sum + Number(item.value), 0))}</TableTd>
|
||||
<TableTd>{formatRupiah(item.pendapatan.reduce((sum, item) => sum + Number(item.value), 0))}</TableTd>
|
||||
<TableTd>
|
||||
<Button
|
||||
bg={"green"}
|
||||
onClick={() =>
|
||||
router.push(`/admin/ekonomi/PADesa-pendapatan-asli-desa/apbdesa/${item.id}`)
|
||||
}
|
||||
>
|
||||
<IconDeviceImacCog size={25} />
|
||||
</Button>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
))}
|
||||
</TableTbody>
|
||||
</Table>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default APBDesa;
|
||||
@@ -0,0 +1,112 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client'
|
||||
import PendapatanAsliDesa from '@/app/admin/(dashboard)/_state/ekonomi/PADesa';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||
import { IconArrowBack } from '@tabler/icons-react';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
function EditBelanja() {
|
||||
const belanjaState = useProxy(PendapatanAsliDesa.belanja);
|
||||
const router = useRouter();
|
||||
const params = useParams();
|
||||
|
||||
const [formData, setFormData] = useState({
|
||||
name: belanjaState.update.form.name || '',
|
||||
value: belanjaState.update.form.value || '',
|
||||
});
|
||||
|
||||
const formatRupiah = (value: number | string) => {
|
||||
const number = typeof value === 'number' ? value : Number(value.replace(/\D/g, ''));
|
||||
return new Intl.NumberFormat('id-ID', {
|
||||
style: 'currency',
|
||||
currency: 'IDR',
|
||||
minimumFractionDigits: 0,
|
||||
}).format(number);
|
||||
};
|
||||
|
||||
const unformatRupiah = (value: string) => {
|
||||
return Number(value.replace(/\D/g, ''));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const loadBelanja = async () => {
|
||||
const id = params?.id as string;
|
||||
if (!id) return;
|
||||
|
||||
try {
|
||||
const data = await belanjaState.update.load(id);
|
||||
if (data) {
|
||||
setFormData({
|
||||
name: data.name || '',
|
||||
value: data.value || '',
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading belanja:", error);
|
||||
toast.error("Gagal memuat data belanja");
|
||||
}
|
||||
};
|
||||
|
||||
loadBelanja();
|
||||
}, [params?.id]);
|
||||
|
||||
const handleSubmit = async () => {
|
||||
try {
|
||||
belanjaState.update.form = {
|
||||
...belanjaState.update.form,
|
||||
name: formData.name,
|
||||
value: Number(formData.value),
|
||||
}
|
||||
|
||||
await belanjaState.update.update();
|
||||
toast.success("Jenis Belanja berhasil diperbarui!");
|
||||
router.push("/admin/ekonomi/PADesa-pendapatan-asli-desa/belanja");
|
||||
} catch (error) {
|
||||
console.error("Error updating jenis belanja:", error);
|
||||
toast.error("Terjadi kesalahan saat memperbarui jenis belanja");
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Box mb={10}>
|
||||
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
|
||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
||||
<Stack gap={"xs"}>
|
||||
<Title order={4}>Edit Jenis Pendapatan</Title>
|
||||
<TextInput
|
||||
value={formData.name}
|
||||
onChange={(val) => {
|
||||
setFormData({ ...formData, name: val.target.value });
|
||||
}}
|
||||
label={<Text fw={"bold"} fz={"sm"}>Nama Jenis Pendapatan</Text>}
|
||||
placeholder='Masukkan nama Jenis Pendapatan'
|
||||
/>
|
||||
<TextInput
|
||||
label={<Text fw={"bold"} fz={"sm"}>Nilai</Text>}
|
||||
placeholder='Masukkan nilai'
|
||||
value={formatRupiah(formData.value)}
|
||||
onChange={(val) => {
|
||||
const raw = val.currentTarget.value;
|
||||
const cleanValue = unformatRupiah(raw);
|
||||
setFormData({ ...formData, value: cleanValue });
|
||||
}}
|
||||
/>
|
||||
<Group>
|
||||
<Button bg={colors['blue-button']} onClick={handleSubmit}>Submit</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default EditBelanja;
|
||||
@@ -0,0 +1,77 @@
|
||||
'use client'
|
||||
import PendapatanAsliDesa from '@/app/admin/(dashboard)/_state/ekonomi/PADesa';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||
import { IconArrowBack } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
function CreateBelanja() {
|
||||
const belanjaState = useProxy(PendapatanAsliDesa.belanja)
|
||||
const router = useRouter()
|
||||
|
||||
const formatRupiah = (value: number | string) => {
|
||||
const number = typeof value === 'number' ? value : Number(value.replace(/\D/g, ''));
|
||||
return new Intl.NumberFormat('id-ID', {
|
||||
style: 'currency',
|
||||
currency: 'IDR',
|
||||
minimumFractionDigits: 0,
|
||||
}).format(number);
|
||||
};
|
||||
|
||||
const unformatRupiah = (value: string) => {
|
||||
return Number(value.replace(/\D/g, ''));
|
||||
};
|
||||
|
||||
const resetForm = () => {
|
||||
belanjaState.create.form = {
|
||||
name: "",
|
||||
value: 0,
|
||||
}
|
||||
}
|
||||
|
||||
const handleSubmit = async () => {
|
||||
await belanjaState.create.submit();
|
||||
resetForm()
|
||||
router.push("/admin/ekonomi/PADesa-pendapatan-asli-desa/belanja")
|
||||
}
|
||||
return (
|
||||
<Box>
|
||||
<Box mb={10}>
|
||||
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
|
||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
||||
<Stack gap={"xs"}>
|
||||
<Title order={4}>Create Jenis Belanja</Title>
|
||||
<TextInput
|
||||
value={belanjaState.create.form.name}
|
||||
onChange={(val) => {
|
||||
belanjaState.create.form.name = val.target.value;
|
||||
}}
|
||||
label={<Text fw={"bold"} fz={"sm"}>Nama Jenis Belanja</Text>}
|
||||
placeholder='Masukkan nama jenis belanja'
|
||||
/>
|
||||
<TextInput
|
||||
type='text'
|
||||
value={formatRupiah(belanjaState.create.form.value)}
|
||||
onChange={(val) => {
|
||||
const raw = val.currentTarget.value;
|
||||
const cleanValue = unformatRupiah(raw);
|
||||
belanjaState.create.form.value = cleanValue;
|
||||
}}
|
||||
label={<Text fw={"bold"} fz={"sm"}>Nilai</Text>}
|
||||
placeholder='Masukkan nilai'
|
||||
/>
|
||||
<Group>
|
||||
<Button bg={colors['blue-button']} onClick={handleSubmit}>Submit</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default CreateBelanja;
|
||||
@@ -0,0 +1,139 @@
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { IconEdit, IconSearch, IconTrash } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
||||
import PendapatanAsliDesa from '../../../_state/ekonomi/PADesa';
|
||||
import HeaderSearch from '../../../_com/header';
|
||||
import JudulList from '../../../_com/judulList';
|
||||
|
||||
|
||||
function Belanja() {
|
||||
const [search, setSearch] = useState("");
|
||||
return (
|
||||
<Box>
|
||||
<HeaderSearch
|
||||
title='Belanja'
|
||||
placeholder='pencarian'
|
||||
searchIcon={<IconSearch size={20} />}
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||
/>
|
||||
<ListBelanja search={search} />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
function ListBelanja({ search }: { search: string }) {
|
||||
const belanjaState = useProxy(PendapatanAsliDesa.belanja)
|
||||
const router = useRouter();
|
||||
const [modalHapus, setModalHapus] = useState(false)
|
||||
const [selectedId, setSelectedId] = useState<string | null>(null)
|
||||
|
||||
const formatRupiah = (value: number) => {
|
||||
return new Intl.NumberFormat('id-ID', {
|
||||
style: 'currency',
|
||||
currency: 'IDR',
|
||||
minimumFractionDigits: 0,
|
||||
}).format(value);
|
||||
};
|
||||
|
||||
const totalBelanja = belanjaState.findMany.data.reduce((sum, item) => sum + item.value, 0);
|
||||
|
||||
const handleDelete = () => {
|
||||
if (selectedId) {
|
||||
belanjaState.delete.byId(selectedId)
|
||||
setModalHapus(false)
|
||||
setSelectedId(null)
|
||||
belanjaState.findMany.load()
|
||||
}
|
||||
}
|
||||
|
||||
useShallowEffect(() => {
|
||||
belanjaState.findMany.load();
|
||||
}, [])
|
||||
|
||||
const filteredData = (belanjaState.findMany.data || []).filter(item => {
|
||||
const keyword = search.toLowerCase();
|
||||
return (
|
||||
item.name.toLowerCase().includes(keyword) ||
|
||||
item.value.toString().toLowerCase().includes(keyword)
|
||||
);
|
||||
});
|
||||
|
||||
if (!belanjaState.findMany.data) {
|
||||
return (
|
||||
<Stack py={10}>
|
||||
<Skeleton h={500} />
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<Box py={10}>
|
||||
<Paper bg={colors['white-1']} p={'md'}>
|
||||
<JudulList
|
||||
title='List Belanja'
|
||||
href='/admin/ekonomi/PADesa-pendapatan-asli-desa/belanja/create'
|
||||
/>
|
||||
<Table striped withTableBorder withRowBorders>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh>Nama</TableTh>
|
||||
<TableTh>Nilai</TableTh>
|
||||
<TableTh>Persentase</TableTh>
|
||||
<TableTh>Edit</TableTh>
|
||||
<TableTh>Delete</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{filteredData.map((item) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd>{item.name}</TableTd>
|
||||
<TableTd>{formatRupiah(item.value)}</TableTd>
|
||||
<TableTd>{((item.value / totalBelanja) * 100).toFixed(0)}%</TableTd>
|
||||
<TableTd>
|
||||
<Button color='green' onClick={() => router.push(`/admin/ekonomi/PADesa-pendapatan-asli-desa/belanja/${item.id}`)}>
|
||||
<IconEdit size={20} />
|
||||
</Button>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Button
|
||||
color='red'
|
||||
disabled={belanjaState.delete.loading}
|
||||
onClick={() => {
|
||||
setSelectedId(item.id)
|
||||
setModalHapus(true)
|
||||
}}>
|
||||
<IconTrash size={20} />
|
||||
</Button>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
))}
|
||||
<TableTr>
|
||||
<TableTd colSpan={4}>
|
||||
<Text fw={'bold'}>Total</Text>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
{formatRupiah(belanjaState.findMany.data.reduce((total, item) => total + item.value, 0))}
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
</TableTbody>
|
||||
</Table>
|
||||
</Paper>
|
||||
|
||||
{/* Modal Konfirmasi Hapus */}
|
||||
<ModalKonfirmasiHapus
|
||||
opened={modalHapus}
|
||||
onClose={() => setModalHapus(false)}
|
||||
onConfirm={handleDelete}
|
||||
text='Apakah anda yakin ingin menghapus belanja ini?'
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default Belanja;
|
||||
@@ -0,0 +1,12 @@
|
||||
import React from 'react';
|
||||
import LayoutTabs from './_lib/layoutTabs';
|
||||
|
||||
function Layout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<LayoutTabs>
|
||||
{children}
|
||||
</LayoutTabs>
|
||||
);
|
||||
}
|
||||
|
||||
export default Layout;
|
||||
@@ -0,0 +1,112 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client'
|
||||
import PendapatanAsliDesa from '@/app/admin/(dashboard)/_state/ekonomi/PADesa';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||
import { IconArrowBack } from '@tabler/icons-react';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
function EditPembiayaan() {
|
||||
const pembiayaanState = useProxy(PendapatanAsliDesa.pembiayaan);
|
||||
const router = useRouter();
|
||||
const params = useParams();
|
||||
|
||||
const [formData, setFormData] = useState({
|
||||
name: pembiayaanState.update.form.name || '',
|
||||
value: pembiayaanState.update.form.value || '',
|
||||
});
|
||||
|
||||
const formatRupiah = (value: number | string) => {
|
||||
const number = typeof value === 'number' ? value : Number(value.replace(/\D/g, ''));
|
||||
return new Intl.NumberFormat('id-ID', {
|
||||
style: 'currency',
|
||||
currency: 'IDR',
|
||||
minimumFractionDigits: 0,
|
||||
}).format(number);
|
||||
};
|
||||
|
||||
const unformatRupiah = (value: string) => {
|
||||
return Number(value.replace(/\D/g, ''));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const loadPembiayaan = async () => {
|
||||
const id = params?.id as string;
|
||||
if (!id) return;
|
||||
|
||||
try {
|
||||
const data = await pembiayaanState.update.load(id);
|
||||
if (data) {
|
||||
setFormData({
|
||||
name: data.name || '',
|
||||
value: data.value || '',
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading pembiayaan:", error);
|
||||
toast.error("Gagal memuat data pembiayaan");
|
||||
}
|
||||
};
|
||||
|
||||
loadPembiayaan();
|
||||
}, [params?.id]);
|
||||
|
||||
const handleSubmit = async () => {
|
||||
try {
|
||||
pembiayaanState.update.form = {
|
||||
...pembiayaanState.update.form,
|
||||
name: formData.name,
|
||||
value: Number(formData.value),
|
||||
}
|
||||
|
||||
await pembiayaanState.update.update();
|
||||
toast.success("Jenis Pembiayaan berhasil diperbarui!");
|
||||
router.push("/admin/ekonomi/PADesa-pendapatan-asli-desa/pembiayaan");
|
||||
} catch (error) {
|
||||
console.error("Error updating jenis pembiayaan:", error);
|
||||
toast.error("Terjadi kesalahan saat memperbarui jenis pembiayaan");
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Box mb={10}>
|
||||
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
|
||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
||||
<Stack gap={"xs"}>
|
||||
<Title order={4}>Edit Jenis Pembiayaan</Title>
|
||||
<TextInput
|
||||
value={formData.name}
|
||||
onChange={(val) => {
|
||||
setFormData({ ...formData, name: val.target.value });
|
||||
}}
|
||||
label={<Text fw={"bold"} fz={"sm"}>Nama Jenis Pembiayaan</Text>}
|
||||
placeholder='Masukkan nama Jenis Pembiayaan'
|
||||
/>
|
||||
<TextInput
|
||||
label={<Text fw={"bold"} fz={"sm"}>Nilai</Text>}
|
||||
placeholder='Masukkan nilai'
|
||||
value={formatRupiah(formData.value)}
|
||||
onChange={(val) => {
|
||||
const raw = val.currentTarget.value;
|
||||
const cleanValue = unformatRupiah(raw);
|
||||
setFormData({ ...formData, value: cleanValue });
|
||||
}}
|
||||
/>
|
||||
<Group>
|
||||
<Button bg={colors['blue-button']} onClick={handleSubmit}>Submit</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default EditPembiayaan;
|
||||
@@ -0,0 +1,78 @@
|
||||
'use client'
|
||||
import React from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import PendapatanAsliDesa from '@/app/admin/(dashboard)/_state/ekonomi/PADesa';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Paper, Stack, Title, TextInput, Group, Text } from '@mantine/core';
|
||||
import { IconArrowBack } from '@tabler/icons-react';
|
||||
|
||||
function CreatePembiayaan() {
|
||||
const pembiayaanState = useProxy(PendapatanAsliDesa.pembiayaan)
|
||||
const router = useRouter()
|
||||
|
||||
const formatRupiah = (value: number | string) => {
|
||||
const number = typeof value === 'number' ? value : Number(value.replace(/\D/g, ''));
|
||||
return new Intl.NumberFormat('id-ID', {
|
||||
style: 'currency',
|
||||
currency: 'IDR',
|
||||
minimumFractionDigits: 0,
|
||||
}).format(number);
|
||||
};
|
||||
|
||||
const unformatRupiah = (value: string) => {
|
||||
return Number(value.replace(/\D/g, ''));
|
||||
};
|
||||
|
||||
const resetForm = () => {
|
||||
pembiayaanState.create.form = {
|
||||
name: "",
|
||||
value: 0,
|
||||
}
|
||||
}
|
||||
|
||||
const handleSubmit = async () => {
|
||||
await pembiayaanState.create.submit();
|
||||
resetForm()
|
||||
router.push("/admin/ekonomi/PADesa-pendapatan-asli-desa/pembiayaan")
|
||||
}
|
||||
return (
|
||||
<Box>
|
||||
<Box mb={10}>
|
||||
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
|
||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
||||
<Stack gap={"xs"}>
|
||||
<Title order={4}>Create Jenis Pembiayaan</Title>
|
||||
<TextInput
|
||||
value={pembiayaanState.create.form.name}
|
||||
onChange={(val) => {
|
||||
pembiayaanState.create.form.name = val.target.value;
|
||||
}}
|
||||
label={<Text fw={"bold"} fz={"sm"}>Nama Jenis Pembiayaan</Text>}
|
||||
placeholder='Masukkan nama jenis pembiayaan'
|
||||
/>
|
||||
<TextInput
|
||||
type='text'
|
||||
value={formatRupiah(pembiayaanState.create.form.value)}
|
||||
onChange={(val) => {
|
||||
const raw = val.currentTarget.value;
|
||||
const cleanValue = unformatRupiah(raw);
|
||||
pembiayaanState.create.form.value = cleanValue;
|
||||
}}
|
||||
label={<Text fw={"bold"} fz={"sm"}>Nilai</Text>}
|
||||
placeholder='Masukkan nilai'
|
||||
/>
|
||||
<Group>
|
||||
<Button bg={colors['blue-button']} onClick={handleSubmit}>Submit</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default CreatePembiayaan;
|
||||
@@ -0,0 +1,138 @@
|
||||
'use client'
|
||||
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
||||
import React, { useState } from 'react';
|
||||
import HeaderSearch from '../../../_com/header';
|
||||
import { IconEdit, IconSearch, IconTrash } from '@tabler/icons-react';
|
||||
import PendapatanAsliDesa from '../../../_state/ekonomi/PADesa';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import colors from '@/con/colors';
|
||||
import JudulList from '../../../_com/judulList';
|
||||
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
||||
|
||||
function Pembiayaan() {
|
||||
const [search, setSearch] = useState("");
|
||||
return (
|
||||
<Box>
|
||||
<HeaderSearch
|
||||
title='Pembiayaan'
|
||||
placeholder='pencarian'
|
||||
searchIcon={<IconSearch size={20} />}
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||
/>
|
||||
<ListPembiayaan search={search} />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
function ListPembiayaan({ search }: { search: string }) {
|
||||
const pembiayaanState = useProxy(PendapatanAsliDesa.pembiayaan)
|
||||
const router = useRouter();
|
||||
const [modalHapus, setModalHapus] = useState(false)
|
||||
const [selectedId, setSelectedId] = useState<string | null>(null)
|
||||
|
||||
const formatRupiah = (value: number) => {
|
||||
return new Intl.NumberFormat('id-ID', {
|
||||
style: 'currency',
|
||||
currency: 'IDR',
|
||||
minimumFractionDigits: 0,
|
||||
}).format(value);
|
||||
};
|
||||
|
||||
const totalPembiayaan = pembiayaanState.findMany.data.reduce((sum, item) => sum + item.value, 0);
|
||||
|
||||
const handleDelete = () => {
|
||||
if (selectedId) {
|
||||
pembiayaanState.delete.byId(selectedId)
|
||||
setModalHapus(false)
|
||||
setSelectedId(null)
|
||||
pembiayaanState.findMany.load()
|
||||
}
|
||||
}
|
||||
|
||||
useShallowEffect(() => {
|
||||
pembiayaanState.findMany.load();
|
||||
}, [])
|
||||
|
||||
const filteredData = (pembiayaanState.findMany.data || []).filter(item => {
|
||||
const keyword = search.toLowerCase();
|
||||
return (
|
||||
item.name.toLowerCase().includes(keyword) ||
|
||||
item.value.toString().toLowerCase().includes(keyword)
|
||||
);
|
||||
});
|
||||
|
||||
if (!pembiayaanState.findMany.data) {
|
||||
return (
|
||||
<Stack py={10}>
|
||||
<Skeleton h={500} />
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<Box py={10}>
|
||||
<Paper bg={colors['white-1']} p={'md'}>
|
||||
<JudulList
|
||||
title='List Pembiayaan'
|
||||
href='/admin/ekonomi/PADesa-pendapatan-asli-desa/pembiayaan/create'
|
||||
/>
|
||||
<Table striped withTableBorder withRowBorders>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh>Nama</TableTh>
|
||||
<TableTh>Nilai</TableTh>
|
||||
<TableTh>Persentase</TableTh>
|
||||
<TableTh>Edit</TableTh>
|
||||
<TableTh>Delete</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{filteredData.map((item) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd>{item.name}</TableTd>
|
||||
<TableTd>{formatRupiah(item.value)}</TableTd>
|
||||
<TableTd>{((item.value / totalPembiayaan) * 100).toFixed(0)}%</TableTd>
|
||||
<TableTd>
|
||||
<Button color='green' onClick={() => router.push(`/admin/ekonomi/PADesa-pendapatan-asli-desa/pembiayaan/${item.id}`)}>
|
||||
<IconEdit size={20} />
|
||||
</Button>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Button
|
||||
color='red'
|
||||
disabled={pembiayaanState.delete.loading}
|
||||
onClick={() => {
|
||||
setSelectedId(item.id)
|
||||
setModalHapus(true)
|
||||
}}>
|
||||
<IconTrash size={20} />
|
||||
</Button>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
))}
|
||||
<TableTr>
|
||||
<TableTd colSpan={4}>
|
||||
<Text fw={'bold'}>Total</Text>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
{formatRupiah(pembiayaanState.findMany.data.reduce((total, item) => total + item.value, 0))}
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
</TableTbody>
|
||||
</Table>
|
||||
</Paper>
|
||||
|
||||
{/* Modal Konfirmasi Hapus */}
|
||||
<ModalKonfirmasiHapus
|
||||
opened={modalHapus}
|
||||
onClose={() => setModalHapus(false)}
|
||||
onConfirm={handleDelete}
|
||||
text='Apakah anda yakin ingin menghapus pembiayaan ini?'
|
||||
/>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
export default Pembiayaan;
|
||||
@@ -0,0 +1,112 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client'
|
||||
import PendapatanAsliDesa from '@/app/admin/(dashboard)/_state/ekonomi/PADesa';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||
import { IconArrowBack } from '@tabler/icons-react';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
function EditPendapatan() {
|
||||
const pendapatanState = useProxy(PendapatanAsliDesa.pendapatan);
|
||||
const router = useRouter();
|
||||
const params = useParams();
|
||||
|
||||
const [formData, setFormData] = useState({
|
||||
name: pendapatanState.update.form.name || '',
|
||||
value: pendapatanState.update.form.value || '',
|
||||
});
|
||||
|
||||
const formatRupiah = (value: number | string) => {
|
||||
const number = typeof value === 'number' ? value : Number(value.replace(/\D/g, ''));
|
||||
return new Intl.NumberFormat('id-ID', {
|
||||
style: 'currency',
|
||||
currency: 'IDR',
|
||||
minimumFractionDigits: 0,
|
||||
}).format(number);
|
||||
};
|
||||
|
||||
const unformatRupiah = (value: string) => {
|
||||
return Number(value.replace(/\D/g, ''));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const loadPendapatan = async () => {
|
||||
const id = params?.id as string;
|
||||
if (!id) return;
|
||||
|
||||
try {
|
||||
const data = await pendapatanState.update.load(id);
|
||||
if (data) {
|
||||
setFormData({
|
||||
name: data.name || '',
|
||||
value: data.value || '',
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading pendapatan:", error);
|
||||
toast.error("Gagal memuat data pendapatan");
|
||||
}
|
||||
};
|
||||
|
||||
loadPendapatan();
|
||||
}, [params?.id]);
|
||||
|
||||
const handleSubmit = async () => {
|
||||
try {
|
||||
pendapatanState.update.form = {
|
||||
...pendapatanState.update.form,
|
||||
name: formData.name,
|
||||
value: Number(formData.value),
|
||||
}
|
||||
|
||||
await pendapatanState.update.update();
|
||||
toast.success("Jenis Pendapatan berhasil diperbarui!");
|
||||
router.push("/admin/ekonomi/PADesa-pendapatan-asli-desa/pendapatan");
|
||||
} catch (error) {
|
||||
console.error("Error updating jenis pendapatan:", error);
|
||||
toast.error("Terjadi kesalahan saat memperbarui jenis pendapatan");
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Box mb={10}>
|
||||
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
|
||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
||||
<Stack gap={"xs"}>
|
||||
<Title order={4}>Edit Jenis Pendapatan</Title>
|
||||
<TextInput
|
||||
value={formData.name}
|
||||
onChange={(val) => {
|
||||
setFormData({ ...formData, name: val.target.value });
|
||||
}}
|
||||
label={<Text fw={"bold"} fz={"sm"}>Nama Jenis Pendapatan</Text>}
|
||||
placeholder='Masukkan nama Jenis Pendapatan'
|
||||
/>
|
||||
<TextInput
|
||||
label={<Text fw={"bold"} fz={"sm"}>Nilai</Text>}
|
||||
placeholder='Masukkan nilai'
|
||||
value={formatRupiah(formData.value)}
|
||||
onChange={(val) => {
|
||||
const raw = val.currentTarget.value;
|
||||
const cleanValue = unformatRupiah(raw);
|
||||
setFormData({ ...formData, value: cleanValue });
|
||||
}}
|
||||
/>
|
||||
<Group>
|
||||
<Button bg={colors['blue-button']} onClick={handleSubmit}>Submit</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default EditPendapatan;
|
||||
@@ -0,0 +1,77 @@
|
||||
'use client'
|
||||
import PendapatanAsliDesa from '@/app/admin/(dashboard)/_state/ekonomi/PADesa';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||
import { IconArrowBack } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
function CreatePendapatan() {
|
||||
const pendapatanState = useProxy(PendapatanAsliDesa.pendapatan)
|
||||
const router = useRouter()
|
||||
|
||||
const formatRupiah = (value: number | string) => {
|
||||
const number = typeof value === 'number' ? value : Number(value.replace(/\D/g, ''));
|
||||
return new Intl.NumberFormat('id-ID', {
|
||||
style: 'currency',
|
||||
currency: 'IDR',
|
||||
minimumFractionDigits: 0,
|
||||
}).format(number);
|
||||
};
|
||||
|
||||
const unformatRupiah = (value: string) => {
|
||||
return Number(value.replace(/\D/g, ''));
|
||||
};
|
||||
|
||||
const resetForm = () => {
|
||||
pendapatanState.create.form = {
|
||||
name: "",
|
||||
value: 0,
|
||||
}
|
||||
}
|
||||
|
||||
const handleSubmit = async () => {
|
||||
await pendapatanState.create.submit();
|
||||
resetForm()
|
||||
router.push("/admin/ekonomi/PADesa-pendapatan-asli-desa/pendapatan")
|
||||
}
|
||||
return (
|
||||
<Box>
|
||||
<Box mb={10}>
|
||||
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
|
||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
||||
<Stack gap={"xs"}>
|
||||
<Title order={4}>Create Jenis Pendapatan</Title>
|
||||
<TextInput
|
||||
value={pendapatanState.create.form.name}
|
||||
onChange={(val) => {
|
||||
pendapatanState.create.form.name = val.target.value;
|
||||
}}
|
||||
label={<Text fw={"bold"} fz={"sm"}>Nama Jenis Pendapatan</Text>}
|
||||
placeholder='Masukkan nama jenis pendapatan'
|
||||
/>
|
||||
<TextInput
|
||||
type='text'
|
||||
value={formatRupiah(pendapatanState.create.form.value)}
|
||||
onChange={(val) => {
|
||||
const raw = val.currentTarget.value;
|
||||
const cleanValue = unformatRupiah(raw);
|
||||
pendapatanState.create.form.value = cleanValue;
|
||||
}}
|
||||
label={<Text fw={"bold"} fz={"sm"}>Nilai</Text>}
|
||||
placeholder='Masukkan nilai'
|
||||
/>
|
||||
<Group>
|
||||
<Button bg={colors['blue-button']} onClick={handleSubmit}>Submit</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default CreatePendapatan;
|
||||
@@ -0,0 +1,136 @@
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text } from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { IconEdit, IconSearch, IconTrash } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
||||
import PendapatanAsliDesa from '../../../_state/ekonomi/PADesa';
|
||||
import HeaderSearch from '../../../_com/header';
|
||||
import JudulList from '../../../_com/judulList';
|
||||
|
||||
|
||||
function Pendapatan() {
|
||||
const [search, setSearch] = useState("");
|
||||
return (
|
||||
<Box>
|
||||
<HeaderSearch
|
||||
title='Pendapatan'
|
||||
placeholder='pencarian'
|
||||
searchIcon={<IconSearch size={20} />}
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||
/>
|
||||
<ListPendapatan search={search} />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
function ListPendapatan({ search }: { search: string }) {
|
||||
const pendapatanState = useProxy(PendapatanAsliDesa.pendapatan)
|
||||
const router = useRouter();
|
||||
const [modalHapus, setModalHapus] = useState(false)
|
||||
const [selectedId, setSelectedId] = useState<string | null>(null)
|
||||
|
||||
const formatRupiah = (value: number) => {
|
||||
return new Intl.NumberFormat('id-ID', {
|
||||
style: 'currency',
|
||||
currency: 'IDR',
|
||||
minimumFractionDigits: 0,
|
||||
}).format(value);
|
||||
};
|
||||
|
||||
|
||||
const handleDelete = () => {
|
||||
if (selectedId) {
|
||||
pendapatanState.delete.byId(selectedId)
|
||||
setModalHapus(false)
|
||||
setSelectedId(null)
|
||||
pendapatanState.findMany.load()
|
||||
}
|
||||
}
|
||||
|
||||
useShallowEffect(() => {
|
||||
pendapatanState.findMany.load();
|
||||
}, [])
|
||||
|
||||
const filteredData = (pendapatanState.findMany.data || []).filter(item => {
|
||||
const keyword = search.toLowerCase();
|
||||
return (
|
||||
item.name.toLowerCase().includes(keyword) ||
|
||||
item.value.toString().toLowerCase().includes(keyword)
|
||||
);
|
||||
});
|
||||
|
||||
if (!pendapatanState.findMany.data) {
|
||||
return (
|
||||
<Stack py={10}>
|
||||
<Skeleton h={500} />
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<Box py={10}>
|
||||
<Paper bg={colors['white-1']} p={'md'}>
|
||||
<JudulList
|
||||
title='List Pendapatan'
|
||||
href='/admin/ekonomi/PADesa-pendapatan-asli-desa/pendapatan/create'
|
||||
/>
|
||||
<Table striped withTableBorder withRowBorders>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh>Nama</TableTh>
|
||||
<TableTh>Nilai</TableTh>
|
||||
<TableTh>Edit</TableTh>
|
||||
<TableTh>Delete</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{filteredData.map((item) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd>{item.name}</TableTd>
|
||||
<TableTd>{formatRupiah(item.value)}</TableTd>
|
||||
<TableTd>
|
||||
<Button color='green' onClick={() => router.push(`/admin/ekonomi/PADesa-pendapatan-asli-desa/pendapatan/${item.id}`)}>
|
||||
<IconEdit size={20} />
|
||||
</Button>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Button
|
||||
color='red'
|
||||
disabled={pendapatanState.delete.loading}
|
||||
onClick={() => {
|
||||
setSelectedId(item.id)
|
||||
setModalHapus(true)
|
||||
}}>
|
||||
<IconTrash size={20} />
|
||||
</Button>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
))}
|
||||
<TableTr>
|
||||
<TableTd colSpan={4}>
|
||||
<Text fw={'bold'}>Total</Text>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
{formatRupiah(pendapatanState.findMany.data.reduce((total, item) => total + item.value, 0))}
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
</TableTbody>
|
||||
</Table>
|
||||
</Paper>
|
||||
|
||||
{/* Modal Konfirmasi Hapus */}
|
||||
<ModalKonfirmasiHapus
|
||||
opened={modalHapus}
|
||||
onClose={() => setModalHapus(false)}
|
||||
onConfirm={handleDelete}
|
||||
text='Apakah anda yakin ingin menghapus pendapatan ini?'
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default Pendapatan;
|
||||
@@ -1,11 +1,11 @@
|
||||
'use client'
|
||||
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 { useMediaQuery, useShallowEffect } from '@mantine/hooks';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { IconEdit, IconSearch, IconTrash } from '@tabler/icons-react';
|
||||
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 HeaderSearch from '../../_com/header';
|
||||
import JudulList from '../../_com/judulList';
|
||||
@@ -40,8 +40,6 @@ function ListDemografiPekerjaan({ search }: { search: string }) {
|
||||
const stateDemografi = useProxy(demografiPekerjaan)
|
||||
const [chartData, setChartData] = useState<DemografiPekerjaan[]>([]);
|
||||
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 [selectedId, setSelectedId] = useState<string | null>(null)
|
||||
|
||||
@@ -54,7 +52,7 @@ function ListDemografiPekerjaan({ search }: { search: string }) {
|
||||
stateDemografi.findMany.load()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
useShallowEffect(() => {
|
||||
setMounted(true)
|
||||
stateDemografi.findMany.load()
|
||||
@@ -104,7 +102,7 @@ function ListDemografiPekerjaan({ search }: { search: string }) {
|
||||
<TableTd>{item.lakiLaki}</TableTd>
|
||||
<TableTd>{item.perempuan}</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} />
|
||||
</Button>
|
||||
</TableTd>
|
||||
@@ -138,14 +136,18 @@ function ListDemografiPekerjaan({ search }: { search: string }) {
|
||||
<Paper bg={colors['white-1']} p={'md'}>
|
||||
<Title pb={10} order={4}>Data Kelahiran & Kematian</Title>
|
||||
{mounted && chartData.length > 0 && (
|
||||
<BarChart width={isMobile ? 450 : isTablet ? 500 : 550} height={350} data={chartData} >
|
||||
<XAxis dataKey="pekerjaan" />
|
||||
<YAxis />
|
||||
<Tooltip />
|
||||
<Legend />
|
||||
<Bar dataKey="lakiLaki" fill="#f03e3e" name="Laki - Laki" />
|
||||
<Bar dataKey="perempuan" fill="#ff922b" name="Perempuan" />
|
||||
</BarChart>
|
||||
<Box w={{ base: '100%', md: '30%' }}>
|
||||
<BarChart
|
||||
h={450}
|
||||
data={chartData}
|
||||
dataKey="pekerjaan"
|
||||
type="stacked"
|
||||
series={[
|
||||
{ name: 'lakiLaki', color: 'red.6', label: 'Laki - Laki' },
|
||||
{ name: 'perempuan', color: 'orange.6', label: 'Perempuan' },
|
||||
]}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
</Paper>
|
||||
</Box>
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||
import { IconArrowBack } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
function CreateJumlahPengangguran() {
|
||||
const router = useRouter();
|
||||
return (
|
||||
<Box>
|
||||
<Box mb={10}>
|
||||
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
|
||||
<IconArrowBack color={colors['blue-button']} size={25}/>
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
<Paper w={{base: '100%', md: '50%'}} bg={colors['white-1']} p={'md'}>
|
||||
<Stack gap={"xs"}>
|
||||
<Title order={4}>Create Jumlah Pengangguran</Title>
|
||||
<TextInput
|
||||
label={<Text fw={"bold"} fz={"sm"}>Pengangguran Terdidik</Text>}
|
||||
placeholder='Masukkan pengangguran terdidik'
|
||||
/>
|
||||
<TextInput
|
||||
label={<Text fw={"bold"} fz={"sm"}>Usia Produktif</Text>}
|
||||
placeholder='Masukkan usia produktif'
|
||||
/>
|
||||
<TextInput
|
||||
label={<Text fw={"bold"} fz={"sm"}>Sedang Mencari Kerja</Text>}
|
||||
placeholder='Masukkan sedang mencari kerja'
|
||||
/>
|
||||
<TextInput
|
||||
label={<Text fw={"bold"} fz={"sm"}>Pengangguran Tidak Terdidik</Text>}
|
||||
placeholder='Masukkan pengangguran tidak terdidik'
|
||||
/>
|
||||
<Group>
|
||||
<Button bg={colors['blue-button']}>Submit</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default CreateJumlahPengangguran;
|
||||
@@ -1,66 +0,0 @@
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Flex, Paper, Stack, Text } from '@mantine/core';
|
||||
import { IconArrowBack, IconEdit, IconX } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
// import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
||||
|
||||
function DetailJumlahPengangguran() {
|
||||
const router = useRouter();
|
||||
return (
|
||||
<Box>
|
||||
<Box mb={10}>
|
||||
<Button variant="subtle" onClick={() => router.back()}>
|
||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||
</Button>
|
||||
</Box>
|
||||
<Paper w={{ base: "100%", md: "50%" }} bg={colors['white-1']} p={'md'}>
|
||||
<Stack>
|
||||
<Text fz={"xl"} fw={"bold"}>Detail Jumlah Pengangguran</Text>
|
||||
|
||||
<Paper bg={colors['BG-trans']} p={'md'}>
|
||||
<Stack gap={"xs"}>
|
||||
<Box>
|
||||
<Text fw={"bold"}>Pengangguran Terdidik</Text>
|
||||
<Text>100</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fw={"bold"}>Usia Produktif</Text>
|
||||
<Text>200</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fw={"bold"}>Sedang Mencari Kerja</Text>
|
||||
<Text>300</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fw={"bold"}>Pengangguran Tidak Terdidik</Text>
|
||||
<Text>30</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Flex gap={"xs"}>
|
||||
<Button color="red">
|
||||
<IconX size={20} />
|
||||
</Button>
|
||||
<Button onClick={() => router.push('/admin/ekonomi/jumlah-pengangguran-2024-2025/edit')} color="green">
|
||||
<IconEdit size={20} />
|
||||
</Button>
|
||||
</Flex>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
{/* Modal Hapus
|
||||
<ModalKonfirmasiHapus
|
||||
opened={modalHapus}
|
||||
onClose={() => setModalHapus(false)}
|
||||
onConfirm={handleHapus}
|
||||
text="Apakah anda yakin ingin menghapus potensi ini?"
|
||||
/> */}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default DetailJumlahPengangguran;
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||
import { IconArrowBack } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
function EditJumlahPengangguran() {
|
||||
const router = useRouter();
|
||||
return (
|
||||
<Box>
|
||||
<Box mb={10}>
|
||||
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
|
||||
<IconArrowBack color={colors['blue-button']} size={25}/>
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
<Paper w={{base: '100%', md: '50%'}} bg={colors['white-1']} p={'md'}>
|
||||
<Stack gap={"xs"}>
|
||||
<Title order={4}>Edit Jumlah Pengangguran</Title>
|
||||
<TextInput
|
||||
label={<Text fw={"bold"} fz={"sm"}>Pengangguran Terdidik</Text>}
|
||||
placeholder='Masukkan pengangguran terdidik'
|
||||
/>
|
||||
<TextInput
|
||||
label={<Text fw={"bold"} fz={"sm"}>Usia Produktif</Text>}
|
||||
placeholder='Masukkan usia produktif'
|
||||
/>
|
||||
<TextInput
|
||||
label={<Text fw={"bold"} fz={"sm"}>Sedang Mencari Kerja</Text>}
|
||||
placeholder='Masukkan sedang mencari kerja'
|
||||
/>
|
||||
<TextInput
|
||||
label={<Text fw={"bold"} fz={"sm"}>Pengangguran Tidak Terdidik</Text>}
|
||||
placeholder='Masukkan pengangguran tidak terdidik'
|
||||
/>
|
||||
<Group>
|
||||
<Button bg={colors['blue-button']}>Submit</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default EditJumlahPengangguran;
|
||||
@@ -1,58 +0,0 @@
|
||||
'use client'
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Paper, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } from '@mantine/core';
|
||||
import { IconDeviceImac, IconSearch } from '@tabler/icons-react';
|
||||
import HeaderSearch from '../../_com/header';
|
||||
import JudulList from '../../_com/judulList';
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
function JumlahPengangguran() {
|
||||
return (
|
||||
<Box>
|
||||
<HeaderSearch
|
||||
title='Jumlah Pengangguran 2024-2025'
|
||||
placeholder='pencarian'
|
||||
searchIcon={<IconSearch size={20} />}
|
||||
/>
|
||||
<ListJumlahPengangguran/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
function ListJumlahPengangguran() {
|
||||
const router = useRouter();
|
||||
return (
|
||||
<Box py={10}>
|
||||
<Paper bg={colors['white-1']} p={'md'}>
|
||||
<JudulList
|
||||
title='List Jumlah Pengangguran 2024-2025'
|
||||
href='/admin/ekonomi/jumlah-pengangguran-2024-2025/create'
|
||||
/>
|
||||
<Table striped withTableBorder withRowBorders>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh>Pengangguran Terdidik</TableTh>
|
||||
<TableTh>Usia Produktif</TableTh>
|
||||
<TableTh>Sedang Mencari Kerja</TableTh>
|
||||
<TableTh>Detail</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
<TableTr>
|
||||
<TableTd>100</TableTd>
|
||||
<TableTd>200</TableTd>
|
||||
<TableTd>300</TableTd>
|
||||
<TableTd>
|
||||
<Button onClick={() => router.push('/admin/ekonomi/jumlah-pengangguran-2024-2025/detail')}>
|
||||
<IconDeviceImac size={20} />
|
||||
</Button>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
</TableTbody>
|
||||
</Table>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default JumlahPengangguran;
|
||||
@@ -0,0 +1,185 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
'use client'
|
||||
import jumlahPengangguranState from '@/app/admin/(dashboard)/_state/ekonomi/jumlah-pengangguran';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||
import { IconArrowBack } from '@tabler/icons-react';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
function EditDetailDataPengangguran() {
|
||||
const stateDetail = useProxy(jumlahPengangguranState.jumlahPengangguran)
|
||||
const router = useRouter();
|
||||
const params = useParams()
|
||||
|
||||
const [formData, setFormData] = useState({
|
||||
month: stateDetail.update.form.month,
|
||||
year: stateDetail.update.form.year,
|
||||
totalUnemployment: stateDetail.update.form.totalUnemployment,
|
||||
educatedUnemployment: stateDetail.update.form.educatedUnemployment,
|
||||
uneducatedUnemployment: stateDetail.update.form.uneducatedUnemployment,
|
||||
percentageChange: stateDetail.update.form.percentageChange || 0, // Ensure it's always a number
|
||||
})
|
||||
|
||||
const calculateTotalAndChange = async () => {
|
||||
const total = formData.educatedUnemployment + formData.uneducatedUnemployment;
|
||||
|
||||
// Ambil data bulan sebelumnya
|
||||
const monthOrder = ['Jan', 'Feb', 'Mar', 'Apr', 'Mei', 'Jun', 'Jul', 'Agu', 'Sep', 'Okt', 'Nov', 'Des'];
|
||||
const currentIndex = monthOrder.findIndex(
|
||||
(m) => m.toLowerCase() === formData.month.toLowerCase()
|
||||
);
|
||||
|
||||
let percentageChange = 0;
|
||||
if (currentIndex > 0) {
|
||||
const prevMonth = monthOrder[currentIndex - 1];
|
||||
const prev = await stateDetail.findByMonthYear.load({
|
||||
month: prevMonth,
|
||||
year: formData.year,
|
||||
});
|
||||
|
||||
if (prev?.totalUnemployment) {
|
||||
percentageChange = Number(
|
||||
(((total - prev.totalUnemployment) / prev.totalUnemployment) * 100).toFixed(1)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
setFormData({
|
||||
...formData,
|
||||
totalUnemployment: total,
|
||||
percentageChange,
|
||||
});
|
||||
|
||||
return { total, percentageChange };
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const loadDetail = async () => {
|
||||
const id = params?.id as string;
|
||||
if (!id) return;
|
||||
|
||||
try {
|
||||
await stateDetail.findUnique.load(id); // ambil by ID
|
||||
const data = stateDetail.findUnique.data;
|
||||
|
||||
if (data) {
|
||||
// Set the ID for update
|
||||
stateDetail.update.id = id;
|
||||
|
||||
// Isi state Valtio untuk update
|
||||
stateDetail.update.form = {
|
||||
...data,
|
||||
percentageChange: data.percentageChange || 0 // Ensure it's always a number
|
||||
};
|
||||
|
||||
// Isi local formData supaya input bisa dikontrol
|
||||
setFormData({
|
||||
month: data.month,
|
||||
year: data.year,
|
||||
totalUnemployment: data.totalUnemployment,
|
||||
educatedUnemployment: data.educatedUnemployment,
|
||||
uneducatedUnemployment: data.uneducatedUnemployment,
|
||||
percentageChange: data.percentageChange || 0, // Ensure it's always a number
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error loading detail:", error);
|
||||
toast.error("Gagal memuat data detail");
|
||||
}
|
||||
};
|
||||
|
||||
loadDetail();
|
||||
}, [params?.id]);
|
||||
|
||||
|
||||
const handleSubmit = async () => {
|
||||
const { total, percentageChange } = await calculateTotalAndChange();
|
||||
try {
|
||||
stateDetail.update.form = {
|
||||
...formData,
|
||||
totalUnemployment: total,
|
||||
percentageChange,
|
||||
};
|
||||
|
||||
const success = await stateDetail.update.submit();
|
||||
if (success) {
|
||||
toast.success("Detail data pengangguran berhasil diperbarui!");
|
||||
router.push("/admin/ekonomi/jumlah-pengangguran/detail-data-pengangguran");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error updating:", error);
|
||||
toast.error("Terjadi kesalahan saat memperbarui data");
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Box mb={10}>
|
||||
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
|
||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
||||
<Title order={4}>Edit Detail Data Pengangguran</Title>
|
||||
<Stack gap="xs">
|
||||
<TextInput
|
||||
label="Bulan"
|
||||
value={formData.month}
|
||||
placeholder="Contoh: Jan, Feb, Mar"
|
||||
onChange={(val) => (setFormData({
|
||||
...formData,
|
||||
month: val.currentTarget.value
|
||||
}))}
|
||||
/>
|
||||
<TextInput
|
||||
label="Tahun"
|
||||
type="number"
|
||||
value={formData.year}
|
||||
onChange={(val) => (setFormData({
|
||||
...formData,
|
||||
year: Number(val.currentTarget.value)
|
||||
}))}
|
||||
/>
|
||||
<TextInput
|
||||
label="Pengangguran Terdidik"
|
||||
type="number"
|
||||
value={formData.educatedUnemployment}
|
||||
onChange={(val) => (setFormData({
|
||||
...formData,
|
||||
educatedUnemployment: Number(val.currentTarget.value)
|
||||
}))}
|
||||
/>
|
||||
<TextInput
|
||||
label="Pengangguran Tidak Terdidik"
|
||||
type="number"
|
||||
value={formData.uneducatedUnemployment}
|
||||
onChange={(val) => (setFormData({
|
||||
...formData,
|
||||
uneducatedUnemployment: Number(val.currentTarget.value)
|
||||
}))}
|
||||
/>
|
||||
<Text fz="sm" fw={500}>
|
||||
Total Otomatis: {formData.totalUnemployment}
|
||||
</Text>
|
||||
<Text fz="sm" fw={500}>
|
||||
Perubahan Otomatis:{" "}
|
||||
{formData.percentageChange !== null
|
||||
? `${formData.percentageChange}%`
|
||||
: '-'}
|
||||
</Text>
|
||||
<Group>
|
||||
<Button bg={colors['blue-button']} mt={10} onClick={handleSubmit}>
|
||||
Submit
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default EditDetailDataPengangguran;
|
||||
@@ -0,0 +1,111 @@
|
||||
'use client'
|
||||
import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus';
|
||||
import jumlahPengangguranState from '@/app/admin/(dashboard)/_state/ekonomi/jumlah-pengangguran';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Flex, Paper, Skeleton, Stack, Text } from '@mantine/core';
|
||||
import { useShallowEffect } from '@mantine/hooks';
|
||||
import { IconArrowBack, IconEdit, IconX } from '@tabler/icons-react';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import { useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
function DetailJumlahPengangguran() {
|
||||
const router = useRouter();
|
||||
const params = useParams()
|
||||
const [modalHapus, setModalHapus] = useState(false)
|
||||
const [selectedId, setSelectedId] = useState<string | null>(null)
|
||||
const stateDetail = useProxy(jumlahPengangguranState.jumlahPengangguran)
|
||||
|
||||
useShallowEffect(() => {
|
||||
stateDetail.findUnique.load(params?.id as string)
|
||||
}, [params?.id])
|
||||
|
||||
const handleHapus = () => {
|
||||
if (selectedId) {
|
||||
stateDetail.delete.byId(selectedId)
|
||||
setModalHapus(false)
|
||||
setSelectedId(null)
|
||||
router.push("/admin/ekonomi/jumlah-pengangguran/detail-data-pengangguran")
|
||||
}
|
||||
}
|
||||
|
||||
if (!stateDetail.findUnique.data) {
|
||||
return (
|
||||
<Box>
|
||||
<Skeleton h={500} />
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Box mb={10}>
|
||||
<Button variant="subtle" onClick={() => router.back()}>
|
||||
<IconArrowBack color={colors['blue-button']} size={25} />
|
||||
</Button>
|
||||
</Box>
|
||||
<Paper w={{ base: "100%", md: "50%" }} bg={colors['white-1']} p={'md'}>
|
||||
<Stack>
|
||||
<Text fz={"xl"} fw={"bold"}>Detail Data Pengangguran</Text>
|
||||
<Paper bg={colors['BG-trans']} p={'md'}>
|
||||
<Stack gap={"xs"}>
|
||||
<Box>
|
||||
<Text fw={"bold"}>Pengangguran Terdidik</Text>
|
||||
<Text>{stateDetail.findUnique.data?.educatedUnemployment}</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fw={"bold"}>Pengangguran Tidak Terdidik</Text>
|
||||
<Text>{stateDetail.findUnique.data?.uneducatedUnemployment}</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fw={"bold"}>Perubahan</Text>
|
||||
<Text>{stateDetail.findUnique.data?.percentageChange}</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fw={"bold"}>Tahun</Text>
|
||||
<Text>{stateDetail.findUnique.data?.year}</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fw={"bold"}>Bulan</Text>
|
||||
<Text>{stateDetail.findUnique.data?.month}</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text fw={"bold"}>Total Pengangguran</Text>
|
||||
<Text>{stateDetail.findUnique.data?.totalUnemployment}</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Flex gap={"xs"}>
|
||||
<Button
|
||||
onClick={() => {
|
||||
if (stateDetail.findUnique.data) {
|
||||
setSelectedId(stateDetail.findUnique.data.id);
|
||||
setModalHapus(true);
|
||||
}
|
||||
}}
|
||||
disabled={stateDetail.delete.loading || !stateDetail.findUnique.data}
|
||||
color={"red"}>
|
||||
<IconX size={20} />
|
||||
</Button>
|
||||
<Button onClick={() => router.push(`/admin/ekonomi/jumlah-pengangguran/detail-data-pengangguran/${stateDetail.findUnique.data?.id}/edit`)} color="green">
|
||||
<IconEdit size={20} />
|
||||
</Button>
|
||||
</Flex>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
{/* Modal Hapus */}
|
||||
<ModalKonfirmasiHapus
|
||||
opened={modalHapus}
|
||||
onClose={() => setModalHapus(false)}
|
||||
onConfirm={handleHapus}
|
||||
text="Apakah anda yakin ingin menghapus data ini?"
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default DetailJumlahPengangguran;
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
'use client'
|
||||
import jumlahPengangguranState from '@/app/admin/(dashboard)/_state/ekonomi/jumlah-pengangguran';
|
||||
import colors from '@/con/colors';
|
||||
import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||
import { IconArrowBack } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useState } from 'react';
|
||||
import { useProxy } from 'valtio/utils';
|
||||
|
||||
function CreateJumlahPengangguran() {
|
||||
const stateDetail = useProxy(jumlahPengangguranState.jumlahPengangguran)
|
||||
const [chartData, setChartData] = useState<any[]>([]);
|
||||
const router = useRouter();
|
||||
|
||||
const resetForm = () => {
|
||||
stateDetail.create.form = {
|
||||
month: "",
|
||||
year: 0,
|
||||
totalUnemployment: 0,
|
||||
educatedUnemployment: 0,
|
||||
uneducatedUnemployment: 0,
|
||||
percentageChange: 0,
|
||||
}
|
||||
}
|
||||
|
||||
const calculateTotalAndChange = async () => {
|
||||
const total =
|
||||
stateDetail.create.form.educatedUnemployment +
|
||||
stateDetail.create.form.uneducatedUnemployment;
|
||||
|
||||
stateDetail.create.form.totalUnemployment = total;
|
||||
|
||||
// Ambil data bulan sebelumnya
|
||||
const monthOrder = [
|
||||
'Jan', 'Feb', 'Mar', 'Apr', 'Mei', 'Jun',
|
||||
'Jul', 'Agu', 'Sep', 'Okt', 'Nov', 'Des'
|
||||
];
|
||||
const currentIndex = monthOrder.findIndex(
|
||||
(m) => m.toLowerCase() === stateDetail.create.form.month.toLowerCase()
|
||||
);
|
||||
|
||||
if (currentIndex > 0) {
|
||||
const prevMonth = monthOrder[currentIndex - 1];
|
||||
const prev = await stateDetail.findByMonthYear.load({
|
||||
month: prevMonth,
|
||||
year: stateDetail.create.form.year,
|
||||
});
|
||||
|
||||
if (prev?.totalUnemployment) {
|
||||
const change = ((total - prev.totalUnemployment) / prev.totalUnemployment) * 100;
|
||||
stateDetail.create.form.percentageChange = Number(change.toFixed(1));
|
||||
} else {
|
||||
stateDetail.create.form.percentageChange = 0;
|
||||
}
|
||||
} else {
|
||||
stateDetail.create.form.percentageChange = 0;
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
await calculateTotalAndChange();
|
||||
const id = await stateDetail.create.create();
|
||||
if (id) {
|
||||
await stateDetail.findUnique.load(String(id));
|
||||
if (stateDetail.findUnique.data) {
|
||||
setChartData([stateDetail.findUnique.data]);
|
||||
}
|
||||
resetForm();
|
||||
router.push('/admin/ekonomi/jumlah-pengangguran/detail-data-pengangguran');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Box mb={10}>
|
||||
<Button onClick={() => router.back()} variant='subtle' color={'blue'}>
|
||||
<IconArrowBack color={colors['blue-button']} size={25}/>
|
||||
</Button>
|
||||
</Box>
|
||||
<Paper w={{ base: '100%', md: '50%' }} bg={colors['white-1']} p={'md'}>
|
||||
<Title order={4}>Tambah Detail Data Pengangguran</Title>
|
||||
<Stack gap="xs">
|
||||
<TextInput
|
||||
label="Bulan"
|
||||
value={stateDetail.create.form.month}
|
||||
placeholder="Contoh: Jan, Feb, Mar"
|
||||
onChange={(e) => (stateDetail.create.form.month = e.currentTarget.value)}
|
||||
/>
|
||||
<TextInput
|
||||
label="Tahun"
|
||||
type="number"
|
||||
value={stateDetail.create.form.year}
|
||||
onChange={(e) =>
|
||||
(stateDetail.create.form.year = Number(e.currentTarget.value))
|
||||
}
|
||||
/>
|
||||
<TextInput
|
||||
label="Pengangguran Terdidik"
|
||||
type="number"
|
||||
value={stateDetail.create.form.educatedUnemployment}
|
||||
onChange={(e) => {
|
||||
stateDetail.create.form.educatedUnemployment = Number(
|
||||
e.currentTarget.value,
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<TextInput
|
||||
label="Pengangguran Tidak Terdidik"
|
||||
type="number"
|
||||
value={stateDetail.create.form.uneducatedUnemployment}
|
||||
onChange={(e) => {
|
||||
stateDetail.create.form.uneducatedUnemployment = Number(
|
||||
e.currentTarget.value,
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<Text fz="sm" fw={500}>
|
||||
Total Otomatis: {stateDetail.create.form.totalUnemployment}
|
||||
</Text>
|
||||
<Text fz="sm" fw={500}>
|
||||
Perubahan Otomatis:{" "}
|
||||
{stateDetail.create.form.percentageChange !== null
|
||||
? `${stateDetail.create.form.percentageChange}%`
|
||||
: '-'}
|
||||
</Text>
|
||||
<Group>
|
||||
<Button bg={colors['blue-button']} mt={10} onClick={handleSubmit}>
|
||||
Submit
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default CreateJumlahPengangguran;
|
||||
148
src/app/admin/(dashboard)/ekonomi/jumlah-pengangguran/page.tsx
Normal file
148
src/app/admin/(dashboard)/ekonomi/jumlah-pengangguran/page.tsx
Normal 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;
|
||||
@@ -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 { Badge, Box, Button, Center, Group, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, ThemeIcon } from '@mantine/core';
|
||||
import { IconCheck, IconDeviceImacCog, IconSearch, IconX } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useEffect, useMemo, 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,46 +30,33 @@ function ListPegawai({ search }: { search: string }) {
|
||||
const stateOrganisasi = useProxy(strukturorganisasiState.pegawai);
|
||||
const router = useRouter();
|
||||
|
||||
useShallowEffect(() => {
|
||||
const loadData = async () => {
|
||||
try {
|
||||
// Clear existing data to ensure we see the loading state
|
||||
stateOrganisasi.findMany.data = [];
|
||||
const {
|
||||
data,
|
||||
page,
|
||||
totalPages,
|
||||
loading,
|
||||
load,
|
||||
} = stateOrganisasi.findMany;
|
||||
|
||||
// Load new data
|
||||
await stateOrganisasi.findMany.load();
|
||||
useEffect(() => {
|
||||
load(page, 10);
|
||||
}, [page]);
|
||||
|
||||
// Type guard to ensure data is an array
|
||||
const data = stateOrganisasi.findMany.data || [];
|
||||
if (data.length > 0) {
|
||||
console.log('4. First record sample:', data[0]);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading pegawai data:', error);
|
||||
stateOrganisasi.findMany.data = [];
|
||||
}
|
||||
};
|
||||
|
||||
loadData();
|
||||
|
||||
// Cleanup function
|
||||
return () => {
|
||||
console.log('Cleanup: Unmounting component');
|
||||
};
|
||||
}, []);
|
||||
|
||||
const filteredData = (stateOrganisasi.findMany.data || []).filter(item => {
|
||||
const keyword = search.toLowerCase();
|
||||
return (
|
||||
item.namaLengkap?.toLowerCase().includes(keyword) ||
|
||||
item.gelarAkademik?.toLowerCase().includes(keyword) ||
|
||||
item.telepon?.toLowerCase().includes(keyword) ||
|
||||
item.posisi?.nama?.toLowerCase().includes(keyword)
|
||||
);
|
||||
});
|
||||
const filteredData = useMemo(() => {
|
||||
if (!data) return [];
|
||||
return data.filter(item => {
|
||||
const keyword = search.toLowerCase();
|
||||
return (
|
||||
item.namaLengkap?.toLowerCase().includes(keyword) ||
|
||||
item.gelarAkademik?.toLowerCase().includes(keyword) ||
|
||||
item.telepon?.toLowerCase().includes(keyword) ||
|
||||
item.posisi?.nama?.toLowerCase().includes(keyword)
|
||||
);
|
||||
});
|
||||
}, [data, search]);
|
||||
|
||||
// Handle loading state
|
||||
if (stateOrganisasi.findMany.data === null) {
|
||||
if (loading || !data) {
|
||||
return (
|
||||
<Stack py={10}>
|
||||
<Skeleton height={300} />
|
||||
@@ -77,8 +64,6 @@ function ListPegawai({ search }: { search: string }) {
|
||||
);
|
||||
}
|
||||
|
||||
// Check if data is an empty array
|
||||
const data = stateOrganisasi.findMany.data || [];
|
||||
if (data.length === 0) {
|
||||
return (
|
||||
<Box py={10}>
|
||||
@@ -90,53 +75,84 @@ function ListPegawai({ search }: { search: string }) {
|
||||
}
|
||||
return (
|
||||
<Box py={10}>
|
||||
<Paper bg={colors['white-1']} p={'md'}>
|
||||
<Paper bg={colors['white-1']} p={'md'} h={{base: 770, md: 650}}>
|
||||
<JudulList
|
||||
title='List Pegawai'
|
||||
href='/admin/ekonomi/struktur-organisasi-dan-sk-pengurus-bumdesa/pegawai/create'
|
||||
/>
|
||||
<Table striped withTableBorder withRowBorders>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh>Nama</TableTh>
|
||||
<TableTh>Gelar Akademik</TableTh>
|
||||
<TableTh>Telepon</TableTh>
|
||||
<TableTh>Posisi</TableTh>
|
||||
<TableTh>Aktif</TableTh>
|
||||
<TableTh>Detail</TableTh>
|
||||
</TableTr>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{(() => {
|
||||
console.log('Rendering table with items:', stateOrganisasi.findMany.data);
|
||||
return null;
|
||||
})()}
|
||||
{([...filteredData]
|
||||
.sort((a, b) => {
|
||||
if (a.isActive === b.isActive) {
|
||||
return a.namaLengkap.localeCompare(b.namaLengkap); // kalau status sama, urut nama
|
||||
}
|
||||
return Number(b.isActive) - Number(a.isActive); // aktif duluan
|
||||
}) // Aktif di atas
|
||||
).map((item) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd>{item.namaLengkap}</TableTd>
|
||||
<TableTd>{item.gelarAkademik}</TableTd>
|
||||
<TableTd>{item.telepon}</TableTd>
|
||||
<TableTd>{item.posisi?.nama}</TableTd>
|
||||
<TableTd>
|
||||
<Badge color={item.isActive ? "green" : "red"}>{item.isActive ? "Aktif" : "Tidak Aktif"}</Badge>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Button bg={"green"} onClick={() => router.push(`/admin/ekonomi/struktur-organisasi-dan-sk-pengurus-bumdesa/pegawai/${item.id}`)}>
|
||||
<IconDeviceImacCog size={25} />
|
||||
</Button>
|
||||
</TableTd>
|
||||
<Box style={{ overflowX: "auto" }}>
|
||||
<Table striped withTableBorder withRowBorders>
|
||||
<TableThead>
|
||||
<TableTr>
|
||||
<TableTh>Nama</TableTh>
|
||||
<TableTh>Gelar Akademik</TableTh>
|
||||
<TableTh>Telepon</TableTh>
|
||||
<TableTh>Posisi</TableTh>
|
||||
<TableTh>Aktif</TableTh>
|
||||
<TableTh>Detail</TableTh>
|
||||
</TableTr>
|
||||
))}
|
||||
</TableTbody>
|
||||
</Table>
|
||||
</TableThead>
|
||||
<TableTbody>
|
||||
{(() => {
|
||||
console.log('Rendering table with items:', stateOrganisasi.findMany.data);
|
||||
return null;
|
||||
})()}
|
||||
{([...filteredData]
|
||||
.sort((a, b) => {
|
||||
if (a.isActive === b.isActive) {
|
||||
return a.namaLengkap.localeCompare(b.namaLengkap); // kalau status sama, urut nama
|
||||
}
|
||||
return Number(b.isActive) - Number(a.isActive); // aktif duluan
|
||||
}) // Aktif di atas
|
||||
).map((item) => (
|
||||
<TableTr key={item.id}>
|
||||
<TableTd>{item.namaLengkap}</TableTd>
|
||||
<TableTd>{item.gelarAkademik}</TableTd>
|
||||
<TableTd>{item.telepon}</TableTd>
|
||||
<TableTd>{item.posisi?.nama}</TableTd>
|
||||
<TableTd>
|
||||
<Group gap="xs" wrap="nowrap">
|
||||
<Box visibleFrom="sm">
|
||||
<Badge color={item.isActive ? "green" : "red"}>
|
||||
{item.isActive ? "Aktif" : "Tidak Aktif"}
|
||||
</Badge>
|
||||
</Box>
|
||||
<Box hiddenFrom="sm">
|
||||
{item.isActive ? (
|
||||
<ThemeIcon color="green" variant="light" size="sm">
|
||||
<IconCheck size={16} />
|
||||
</ThemeIcon>
|
||||
) : (
|
||||
<ThemeIcon color="red" variant="light" size="sm">
|
||||
<IconX size={16} />
|
||||
</ThemeIcon>
|
||||
)}
|
||||
</Box>
|
||||
</Group>
|
||||
</TableTd>
|
||||
<TableTd>
|
||||
<Button bg={"green"} onClick={() => router.push(`/admin/ekonomi/struktur-organisasi-dan-sk-pengurus-bumdesa/pegawai/${item.id}`)}>
|
||||
<IconDeviceImacCog size={25} />
|
||||
</Button>
|
||||
</TableTd>
|
||||
</TableTr>
|
||||
))}
|
||||
</TableTbody>
|
||||
</Table>
|
||||
</Box>
|
||||
</Paper>
|
||||
<Center>
|
||||
<Pagination
|
||||
value={page}
|
||||
onChange={(newPage) => {
|
||||
load(newPage, 10);
|
||||
window.scrollTo(0, 0);
|
||||
}}
|
||||
total={totalPages}
|
||||
mt="md"
|
||||
mb="md"
|
||||
/>
|
||||
</Center>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}, [])
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -230,12 +230,12 @@ export const navBar = [
|
||||
{
|
||||
id: "Ekonomi_4",
|
||||
name: "PADesa (Pendapatan Asli Desa)",
|
||||
path: "/admin/ekonomi/PADesa-pendapatan-asli-desa"
|
||||
path: "/admin/ekonomi/PADesa-pendapatan-asli-desa/apbdesa"
|
||||
},
|
||||
{
|
||||
id: "Ekonomi_5",
|
||||
name: "Jumlah Pengangguran 2024-2025",
|
||||
path: "/admin/ekonomi/jumlah-pengangguran-2024-2025"
|
||||
name: "Jumlah Pengangguran",
|
||||
path: "/admin/ekonomi/jumlah-pengangguran"
|
||||
},
|
||||
{
|
||||
id: "Ekonomi_6",
|
||||
@@ -244,8 +244,8 @@ export const navBar = [
|
||||
},
|
||||
{
|
||||
id: "Ekonomi_7",
|
||||
name: "Jumlah Penduduk Miskin 2024-2025",
|
||||
path: "/admin/ekonomi/jumlah-penduduk-miskin-2024-2025"
|
||||
name: "Jumlah Penduduk Miskin",
|
||||
path: "/admin/ekonomi/jumlah-penduduk-miskin"
|
||||
},
|
||||
{
|
||||
id: "Ekonomi_8",
|
||||
|
||||
@@ -1,25 +1,42 @@
|
||||
// /api/berita/findManyPaginated.ts
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
async function beritaFindMany(context: Context) {
|
||||
const page = Number(context.query.page) || 1;
|
||||
const limit = Number(context.query.limit) || 10;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
async function beritaFindMany() {
|
||||
try {
|
||||
const data = await prisma.berita.findMany({
|
||||
where: { isActive: true },
|
||||
include: {
|
||||
image: true,
|
||||
kategoriBerita: true,
|
||||
},
|
||||
});
|
||||
const [data, total] = await Promise.all([
|
||||
prisma.berita.findMany({
|
||||
where: { isActive: true },
|
||||
include: {
|
||||
image: true,
|
||||
kategoriBerita: true,
|
||||
},
|
||||
skip,
|
||||
take: limit,
|
||||
orderBy: { createdAt: 'desc' }, // opsional, kalau mau urut berdasarkan waktu
|
||||
}),
|
||||
prisma.berita.count({
|
||||
where: { isActive: true }
|
||||
})
|
||||
]);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Success fetch berita",
|
||||
message: "Success fetch berita with pagination",
|
||||
data,
|
||||
page,
|
||||
totalPages: Math.ceil(total / limit),
|
||||
total,
|
||||
};
|
||||
} catch (e) {
|
||||
console.error("Find many error:", e);
|
||||
console.error("Find many paginated error:", e);
|
||||
return {
|
||||
success: false,
|
||||
message: "Failed fetch berita",
|
||||
message: "Failed fetch berita with pagination",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ import GrafikMenganggurBerdasarkanPendidikan from "./usia-kerja-yang-menganggur/
|
||||
import JumlahPendudukMiskin from "./jumlah-penduduk-miskin";
|
||||
import SektorUnggulanDesa from "./sektor-unggulan-desa";
|
||||
import DemografiPekerjaan from "./demografi-pekerjaan";
|
||||
import JumlahPengangguran from "./jumlah-pengangguran";
|
||||
import PendapatanAsliDesa from "./pendapatan-asli-desa";
|
||||
|
||||
const Ekonomi = new Elysia({
|
||||
prefix: "/api/ekonomi",
|
||||
@@ -24,5 +26,7 @@ const Ekonomi = new Elysia({
|
||||
.use(JumlahPendudukMiskin)
|
||||
.use(SektorUnggulanDesa)
|
||||
.use(DemografiPekerjaan)
|
||||
.use(JumlahPengangguran)
|
||||
.use(PendapatanAsliDesa)
|
||||
|
||||
export default Ekonomi
|
||||
@@ -0,0 +1,139 @@
|
||||
// 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;
|
||||
};
|
||||
}>;
|
||||
|
||||
const monthOrder = ["Jan", "Feb", "Mar", "Apr", "Mei", "Jun", "Jul", "Agu", "Sep", "Okt", "Nov", "Des"];
|
||||
|
||||
export default async function detailDataPengangguranCreate(context: Context) {
|
||||
const body = context.body as FormCreate;
|
||||
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
// Cari bulan sebelumnya
|
||||
const currentMonthIndex = monthOrder.indexOf(body.month);
|
||||
const prevMonth = currentMonthIndex > 0 ? monthOrder[currentMonthIndex - 1] : "Des";
|
||||
const prevYear = currentMonthIndex > 0 ? body.year : body.year - 1;
|
||||
|
||||
const prevData = await prisma.detailDataPengangguran.findFirst({
|
||||
where: {
|
||||
month: prevMonth,
|
||||
year: prevYear,
|
||||
},
|
||||
});
|
||||
|
||||
let percentageChange: number | null = null;
|
||||
if (prevData) {
|
||||
const change = ((body.totalUnemployment - prevData.totalUnemployment) / prevData.totalUnemployment) * 100;
|
||||
percentageChange = parseFloat(change.toFixed(1));
|
||||
}
|
||||
|
||||
const created = await prisma.detailDataPengangguran.create({
|
||||
data: {
|
||||
month: body.month,
|
||||
year: body.year,
|
||||
totalUnemployment: body.totalUnemployment,
|
||||
educatedUnemployment: body.educatedUnemployment,
|
||||
uneducatedUnemployment: body.uneducatedUnemployment,
|
||||
percentageChange,
|
||||
},
|
||||
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,
|
||||
};
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
export default async function findByMonthYear(context: Context) {
|
||||
const { month, year } = context.params as { month: string; year: string };
|
||||
|
||||
if (!month || !year) {
|
||||
return {
|
||||
success: false,
|
||||
message: "Bulan dan tahun wajib diisi",
|
||||
data: null,
|
||||
};
|
||||
}
|
||||
|
||||
const data = await prisma.detailDataPengangguran.findFirst({
|
||||
where: {
|
||||
month,
|
||||
year: Number(year),
|
||||
},
|
||||
});
|
||||
|
||||
if (!data) {
|
||||
return {
|
||||
success: false,
|
||||
message: `Data untuk bulan ${month} tahun ${year} tidak ditemukan`,
|
||||
data: null,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Data ditemukan",
|
||||
data,
|
||||
};
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
@@ -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 });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import Elysia, { t } from "elysia";
|
||||
import detailDataPengangguranCreate from "./create";
|
||||
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",
|
||||
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(),
|
||||
}),
|
||||
})
|
||||
.get("/month/:month/year/:year", findByMonthYear, {
|
||||
params: t.Object({
|
||||
month: t.String(),
|
||||
year: t.String(),
|
||||
}),
|
||||
})
|
||||
|
||||
export default DetailDataPengangguran;
|
||||
@@ -0,0 +1,145 @@
|
||||
// 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";
|
||||
|
||||
|
||||
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, totalUnemployment, educatedUnemployment, uneducatedUnemployment } = context.body as {
|
||||
month: string;
|
||||
year: number;
|
||||
totalUnemployment: number;
|
||||
educatedUnemployment: number;
|
||||
uneducatedUnemployment: 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 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 prevYear = currentMonthIndex > 0 ? year : year - 1;
|
||||
|
||||
const prevData = await prisma.detailDataPengangguran.findFirst({
|
||||
where: {
|
||||
month: prevMonth,
|
||||
year: prevYear,
|
||||
},
|
||||
});
|
||||
|
||||
let percentageChange: number | null = null;
|
||||
if (prevData) {
|
||||
const change = ((totalUnemployment - prevData.totalUnemployment) / prevData.totalUnemployment) * 100;
|
||||
percentageChange = parseFloat(change.toFixed(1));
|
||||
}
|
||||
|
||||
const updated = await prisma.detailDataPengangguran.update({
|
||||
where: { id },
|
||||
data: {
|
||||
month,
|
||||
year,
|
||||
totalUnemployment,
|
||||
educatedUnemployment,
|
||||
uneducatedUnemployment,
|
||||
percentageChange,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Data berhasil diupdate",
|
||||
data: updated,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
import Elysia from "elysia";
|
||||
import DetailDataPengangguran from "./detail-data-pengangguran";
|
||||
|
||||
const JumlahPengangguran = new Elysia({
|
||||
prefix: "/jumlahpengangguran",
|
||||
})
|
||||
.use(DetailDataPengangguran)
|
||||
|
||||
|
||||
export default JumlahPengangguran;
|
||||
@@ -0,0 +1,40 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
type FormCreate = {
|
||||
tahun: number;
|
||||
pendapatanIds: string[];
|
||||
belanjaIds: string[];
|
||||
pembiayaanIds: string[];
|
||||
}
|
||||
|
||||
export default async function apbDesaCreate(context: Context) {
|
||||
const body = context.body as FormCreate;
|
||||
|
||||
const created = await prisma.apbDesa.create({
|
||||
data: {
|
||||
tahun: body.tahun,
|
||||
pendapatan: {
|
||||
connect: body.pendapatanIds.map((id) => ({ id })),
|
||||
},
|
||||
belanja: {
|
||||
connect: body.belanjaIds.map((id) => ({ id })),
|
||||
},
|
||||
pembiayaan: {
|
||||
connect: body.pembiayaanIds.map((id) => ({ id })),
|
||||
},
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
tahun: true,
|
||||
pendapatan: true,
|
||||
belanja: true,
|
||||
pembiayaan: true,
|
||||
}
|
||||
});
|
||||
return {
|
||||
success: true,
|
||||
message: "Success create apb desa",
|
||||
data: created,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
export default async function apbDesaDelete(context: Context) {
|
||||
const { params } = context;
|
||||
const id = params?.id as string;
|
||||
|
||||
if (!id) {
|
||||
throw new Error("ID tidak ditemukan dalam parameter");
|
||||
}
|
||||
|
||||
const deleted = await prisma.apbDesa.delete({
|
||||
where: { id },
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Berhasil menghapus apb desa",
|
||||
data: deleted,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
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;
|
||||
|
||||
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 }
|
||||
})
|
||||
]);
|
||||
|
||||
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,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
|
||||
export default async function apbDesaFindUnique(
|
||||
request: Request
|
||||
) {
|
||||
// Extract the ID from the URL path
|
||||
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 {
|
||||
// Validate that the ID is a valid UUID or whatever format you're using
|
||||
if (typeof id !== 'string') {
|
||||
return Response.json({
|
||||
success: false,
|
||||
message: "ID tidak valid",
|
||||
}, { status: 400 });
|
||||
}
|
||||
|
||||
const data = await prisma.apbDesa.findUnique({
|
||||
where: { id },
|
||||
include: {
|
||||
pendapatan: true,
|
||||
belanja: true,
|
||||
pembiayaan: true,
|
||||
}
|
||||
});
|
||||
|
||||
if (!data) {
|
||||
return Response.json({
|
||||
success: false,
|
||||
message: "Apb desa tidak ditemukan",
|
||||
}, { status: 404 });
|
||||
}
|
||||
|
||||
// Ensure we're returning a proper Response object
|
||||
return Response.json({
|
||||
success: true,
|
||||
message: "Success fetch apb desa by ID",
|
||||
data,
|
||||
}, {
|
||||
status: 200,
|
||||
});
|
||||
} catch (e) {
|
||||
console.error("Find by ID error:", e);
|
||||
return Response.json({
|
||||
success: false,
|
||||
message: "Gagal mengambil apb desa: " + (e instanceof Error ? e.message : 'Unknown error'),
|
||||
}, {
|
||||
status: 500,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
import Elysia, { t } from "elysia";
|
||||
import apbDesaFindMany from "./findMany";
|
||||
import apbDesaFindUnique from "./findUnique";
|
||||
import apbDesaCreate from "./create";
|
||||
import apbDesaDelete from "./del";
|
||||
import apbDesaUpdate from "./updt";
|
||||
|
||||
const APBDesa = new Elysia({
|
||||
prefix: "/apbdesa",
|
||||
tags: ["Ekonomi/Pendapatan Asli Desa/APB Desa"],
|
||||
})
|
||||
.get("/find-many", apbDesaFindMany)
|
||||
.get("/:id", async (context) => {
|
||||
const response = await apbDesaFindUnique(new Request(context.request));
|
||||
return response;
|
||||
})
|
||||
.post("/create", apbDesaCreate, {
|
||||
body: t.Object({
|
||||
tahun: t.Number(),
|
||||
pendapatanIds: t.Array(t.String()),
|
||||
belanjaIds: t.Array(t.String()),
|
||||
pembiayaanIds: t.Array(t.String()),
|
||||
}),
|
||||
})
|
||||
.delete("/delete/:id", apbDesaDelete)
|
||||
.put(
|
||||
"/:id",
|
||||
async (context) => {
|
||||
const response = await apbDesaUpdate(context);
|
||||
return response;
|
||||
},
|
||||
{
|
||||
body: t.Object({
|
||||
tahun: t.Number(),
|
||||
pendapatanIds: t.Array(t.String()),
|
||||
belanjaIds: t.Array(t.String()),
|
||||
pembiayaanIds: t.Array(t.String()),
|
||||
}),
|
||||
}
|
||||
);
|
||||
export default APBDesa;
|
||||
@@ -0,0 +1,67 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
type FormUpdate = {
|
||||
id: string;
|
||||
tahun: number;
|
||||
pendapatanIds: string[];
|
||||
belanjaIds: string[];
|
||||
pembiayaanIds: string[];
|
||||
};
|
||||
export default async function apbDesaUpdate(context: Context) {
|
||||
try {
|
||||
const id = context.params?.id as string;
|
||||
const body = (await context.body) as FormUpdate;
|
||||
|
||||
const {
|
||||
tahun,
|
||||
pendapatanIds,
|
||||
belanjaIds,
|
||||
pembiayaanIds,
|
||||
} = body;
|
||||
|
||||
if (!id) {
|
||||
return {
|
||||
success: false,
|
||||
message: "ID tidak boleh kosong",
|
||||
};
|
||||
}
|
||||
|
||||
const existing = await prisma.apbDesa.findUnique({
|
||||
where: { id },
|
||||
});
|
||||
|
||||
if (!existing) {
|
||||
return {
|
||||
success: false,
|
||||
message: "APB Desa tidak ditemukan",
|
||||
};
|
||||
}
|
||||
|
||||
const updated = await prisma.apbDesa.update({
|
||||
where: { id },
|
||||
data: {
|
||||
tahun,
|
||||
pendapatan: {
|
||||
connect: pendapatanIds.map((id) => ({ id })),
|
||||
},
|
||||
belanja: {
|
||||
connect: belanjaIds.map((id) => ({ id })),
|
||||
},
|
||||
pembiayaan: {
|
||||
connect: pembiayaanIds.map((id) => ({ id })),
|
||||
},
|
||||
}
|
||||
});
|
||||
return {
|
||||
success: true,
|
||||
message: "Success update APB Desa",
|
||||
data: updated,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error updating APB Desa:', error);
|
||||
context.set.status = 500;
|
||||
return { error: 'Failed to update APB Desa'};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
type FormCreate = {
|
||||
name: string;
|
||||
value: number;
|
||||
}
|
||||
|
||||
export default async function belanjaCreate(context: Context) {
|
||||
const body = context.body as FormCreate;
|
||||
|
||||
const created = await prisma.belanja.create({
|
||||
data: {
|
||||
name: body.name,
|
||||
value: body.value,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
value: true,
|
||||
}
|
||||
});
|
||||
return {
|
||||
success: true,
|
||||
message: "Success create belanja",
|
||||
data: created,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
export default async function belanjaDelete(context: Context) {
|
||||
const { params } = context;
|
||||
const id = params?.id as string;
|
||||
|
||||
if (!id) {
|
||||
throw new Error("ID tidak ditemukan dalam parameter");
|
||||
}
|
||||
|
||||
const deleted = await prisma.belanja.delete({
|
||||
where: { id },
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Berhasil menghapus belanja",
|
||||
data: deleted,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
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 skip = (page - 1) * limit;
|
||||
|
||||
try {
|
||||
const [data, total] = await Promise.all([
|
||||
prisma.belanja.findMany({
|
||||
where: { isActive: true },
|
||||
skip,
|
||||
take: limit,
|
||||
orderBy: { createdAt: "desc" },
|
||||
}),
|
||||
prisma.belanja.count({
|
||||
where: { isActive: true },
|
||||
}),
|
||||
]);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Success fetch berita with pagination",
|
||||
data,
|
||||
page,
|
||||
totalPages: Math.ceil(total / limit),
|
||||
total,
|
||||
};
|
||||
} catch (e) {
|
||||
console.error("Find many paginated error:", e);
|
||||
return {
|
||||
success: false,
|
||||
message: "Failed fetch berita with pagination",
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
|
||||
export default async function belanjaFindUnique(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 {
|
||||
// Validate that the ID is a valid UUID or whatever format you're using
|
||||
if (typeof id !== "string") {
|
||||
return Response.json(
|
||||
{
|
||||
success: false,
|
||||
message: "ID tidak valid",
|
||||
},
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
const data = await prisma.belanja.findUnique({
|
||||
where: { id },
|
||||
});
|
||||
|
||||
if (!data) {
|
||||
return Response.json(
|
||||
{
|
||||
success: false,
|
||||
message: "Belanja tidak ditemukan",
|
||||
},
|
||||
{ status: 404 }
|
||||
);
|
||||
}
|
||||
|
||||
// Ensure we're returning a proper Response object
|
||||
return Response.json(
|
||||
{
|
||||
success: true,
|
||||
message: "Success fetch belanja by ID",
|
||||
data,
|
||||
},
|
||||
{
|
||||
status: 200,
|
||||
}
|
||||
);
|
||||
} catch (e) {
|
||||
console.error("Find by ID error:", e);
|
||||
return Response.json(
|
||||
{
|
||||
success: false,
|
||||
message:
|
||||
"Gagal mengambil belanja: " +
|
||||
(e instanceof Error ? e.message : "Unknown error"),
|
||||
},
|
||||
{
|
||||
status: 500,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import Elysia, { t } from "elysia";
|
||||
import belanjaFindMany from "./findMany";
|
||||
import belanjaFindUnique from "./findUnique";
|
||||
import belanjaCreate from "./create";
|
||||
import belanjaDelete from "./del";
|
||||
import belanjaUpdate from "./updt";
|
||||
|
||||
const Belanja = new Elysia({
|
||||
prefix: "/belanja",
|
||||
tags: ["Ekonomi/Pendapatan Asli Desa/Belanja"],
|
||||
})
|
||||
.get("/find-many", belanjaFindMany)
|
||||
.get("/:id", async (context) => {
|
||||
const response = await belanjaFindUnique(new Request(context.request));
|
||||
return response;
|
||||
})
|
||||
.post("/create", belanjaCreate, {
|
||||
body: t.Object({
|
||||
name: t.String(),
|
||||
value: t.Number(),
|
||||
}),
|
||||
})
|
||||
.delete("/del/:id", belanjaDelete)
|
||||
.put("/:id", async (context) => {
|
||||
const response = await belanjaUpdate(context);
|
||||
return response;
|
||||
}, {
|
||||
body: t.Object({
|
||||
name: t.String(),
|
||||
value: t.Number(),
|
||||
}),
|
||||
});
|
||||
export default Belanja;
|
||||
@@ -0,0 +1,36 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
export default async function belanjaUpdate(context: Context) {
|
||||
const id = context.params?.id as string;
|
||||
const body = context.body as { name: string; value: number };
|
||||
|
||||
if (!id) {
|
||||
return { success: false, message: "ID tidak boleh kosong" };
|
||||
}
|
||||
|
||||
try {
|
||||
const existing = await prisma.belanja.findUnique({ where: { id } });
|
||||
if (!existing) {
|
||||
return { success: false, message: "Data tidak ditemukan" };
|
||||
}
|
||||
|
||||
const updated = await prisma.belanja.update({
|
||||
where: { id },
|
||||
data: {
|
||||
name: body.name,
|
||||
value: body.value,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Berhasil update belanja",
|
||||
data: updated,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Update error:", error);
|
||||
return { success: false, message: "Gagal update belanja" };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import Elysia from "elysia";
|
||||
import PendapatanAsli from "./pendapatan";
|
||||
import Belanja from "./belanja";
|
||||
import Pembiayaan from "./pembiayaan";
|
||||
import APBDesa from "./apbDesa";
|
||||
|
||||
const PendapatanAsliDesa = new Elysia({
|
||||
prefix: "/pendapatanaslidesa",
|
||||
tags: ["Ekonomi/Pendapatan Asli Desa"],
|
||||
})
|
||||
.use(PendapatanAsli)
|
||||
.use(Belanja)
|
||||
.use(Pembiayaan)
|
||||
.use(APBDesa)
|
||||
export default PendapatanAsliDesa;
|
||||
@@ -0,0 +1,28 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
type FormCreate = {
|
||||
name: string;
|
||||
value: number;
|
||||
}
|
||||
|
||||
export default async function pembiayaanCreate(context: Context) {
|
||||
const body = context.body as FormCreate;
|
||||
|
||||
const created = await prisma.pembiayaan.create({
|
||||
data: {
|
||||
name: body.name,
|
||||
value: body.value,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
value: true,
|
||||
}
|
||||
});
|
||||
return {
|
||||
success: true,
|
||||
message: "Success create pembiayaan",
|
||||
data: created,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
export default async function pembiayaanDelete(context: Context) {
|
||||
const { params } = context;
|
||||
const id = params?.id as string;
|
||||
|
||||
if (!id) {
|
||||
throw new Error("ID tidak ditemukan dalam parameter");
|
||||
}
|
||||
|
||||
const deleted = await prisma.pembiayaan.delete({
|
||||
where: { id },
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Berhasil menghapus pembiayaan",
|
||||
data: deleted,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
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 skip = (page - 1) * limit;
|
||||
|
||||
try {
|
||||
const [data, total] = await Promise.all([
|
||||
prisma.pembiayaan.findMany({
|
||||
where: { isActive: true },
|
||||
skip,
|
||||
take: limit,
|
||||
orderBy: { createdAt: 'desc' },
|
||||
}),
|
||||
prisma.pembiayaan.count({
|
||||
where: { isActive: true }
|
||||
})
|
||||
]);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Success fetch pembiayaan with pagination",
|
||||
data,
|
||||
page,
|
||||
totalPages: Math.ceil(total / limit),
|
||||
total,
|
||||
};
|
||||
} catch (e) {
|
||||
console.error("Find many paginated error:", e);
|
||||
return {
|
||||
success: false,
|
||||
message: "Failed fetch pembiayaan with pagination",
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
|
||||
export default async function pembiayaanFindUnique(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.pembiayaan.findUnique({
|
||||
where: { id },
|
||||
});
|
||||
|
||||
if (!data) {
|
||||
return Response.json(
|
||||
{
|
||||
success: false,
|
||||
message: "Pembiayaan tidak ditemukan",
|
||||
},
|
||||
{ status: 404 }
|
||||
);
|
||||
}
|
||||
|
||||
return Response.json(
|
||||
{
|
||||
success: true,
|
||||
message: "Success fetch pembiayaan by ID",
|
||||
data,
|
||||
},
|
||||
{
|
||||
status: 200,
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("Find by ID error:", error);
|
||||
return Response.json(
|
||||
{
|
||||
success: false,
|
||||
message:
|
||||
"Gagal mengambil pembiayaan: " +
|
||||
(error instanceof Error ? error.message : "Unknown error"),
|
||||
},
|
||||
{
|
||||
status: 500,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import Elysia, { t } from "elysia";
|
||||
import pembiayaanFindMany from "./findMany";
|
||||
import pembiayaanFindUnique from "./findUnique";
|
||||
import pembiayaanCreate from "./create";
|
||||
import pembiayaanDelete from "./del";
|
||||
import pembiayaanUpdate from "./updt";
|
||||
|
||||
const Pembiayaan = new Elysia({
|
||||
prefix: "/pembiayaan",
|
||||
tags: ["Ekonomi/Pendapatan Asli Desa/Pembiayaan"],
|
||||
})
|
||||
.get("/find-many", pembiayaanFindMany)
|
||||
.get("/:id", async (context) => {
|
||||
const response = await pembiayaanFindUnique(new Request(context.request));
|
||||
return response;
|
||||
})
|
||||
.post("/create", pembiayaanCreate, {
|
||||
body: t.Object({
|
||||
name: t.String(),
|
||||
value: t.Number(),
|
||||
}),
|
||||
})
|
||||
.delete("/delete/:id", pembiayaanDelete)
|
||||
.put(
|
||||
"/:id",
|
||||
async (context) => {
|
||||
const response = await pembiayaanUpdate(context);
|
||||
return response;
|
||||
},
|
||||
{
|
||||
body: t.Object({
|
||||
name: t.String(),
|
||||
value: t.Number(),
|
||||
}),
|
||||
}
|
||||
);
|
||||
export default Pembiayaan;
|
||||
@@ -0,0 +1,36 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
export default async function pembiayaanUpdate(context: Context) {
|
||||
const id = context.params?.id as string;
|
||||
const body = context.body as { name: string; value: number };
|
||||
|
||||
if (!id) {
|
||||
return { success: false, message: "ID tidak boleh kosong" };
|
||||
}
|
||||
|
||||
try {
|
||||
const existing = await prisma.pembiayaan.findUnique({ where: { id } });
|
||||
if (!existing) {
|
||||
return { success: false, message: "Data tidak ditemukan" };
|
||||
}
|
||||
|
||||
const updated = await prisma.pembiayaan.update({
|
||||
where: { id },
|
||||
data: {
|
||||
name: body.name,
|
||||
value: body.value,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Berhasil update pembiayaan",
|
||||
data: updated,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Update error:", error);
|
||||
return { success: false, message: "Gagal update pembiayaan" };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
type FormCreate = {
|
||||
name: string;
|
||||
value: number;
|
||||
|
||||
}
|
||||
|
||||
export default async function pendapatanAsliCreate(context: Context) {
|
||||
const body = context.body as FormCreate;
|
||||
|
||||
const created = await prisma.pendapatan.create({
|
||||
data: {
|
||||
name: body.name,
|
||||
value: body.value,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
value: true,
|
||||
}
|
||||
});
|
||||
return {
|
||||
success: true,
|
||||
message: "Success create pendapatan asli desa",
|
||||
data: created,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
export default async function pendapatanAsliDelete(context: Context) {
|
||||
const { params } = context;
|
||||
const id = params?.id as string;
|
||||
|
||||
if (!id) {
|
||||
throw new Error("ID tidak ditemukan dalam parameter");
|
||||
}
|
||||
|
||||
const deleted = await prisma.pendapatan.delete({
|
||||
where: { id },
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Berhasil menghapus pendapatan asli desa",
|
||||
data: deleted,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
async function pendapatanAsliFindMany(context: Context) {
|
||||
const page = Number(context.query.page) || 1;
|
||||
const limit = Number(context.query.limit) || 10;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
try {
|
||||
const [data, total] = await Promise.all([
|
||||
prisma.pendapatan.findMany({
|
||||
where: { isActive: true },
|
||||
skip,
|
||||
take: limit,
|
||||
orderBy: { createdAt: 'desc' },
|
||||
}),
|
||||
prisma.pendapatan.count({
|
||||
where: { isActive: true }
|
||||
})
|
||||
]);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Success fetch berita with pagination",
|
||||
data,
|
||||
page,
|
||||
totalPages: Math.ceil(total / limit),
|
||||
total,
|
||||
};
|
||||
} catch (e) {
|
||||
console.error("Find many paginated error:", e);
|
||||
return {
|
||||
success: false,
|
||||
message: "Failed fetch berita with pagination",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default pendapatanAsliFindMany;
|
||||
@@ -0,0 +1,56 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
|
||||
export default async function pendapatanAsliFindUnique(
|
||||
request: Request
|
||||
) {
|
||||
// Extract the ID from the URL path
|
||||
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 {
|
||||
// Validate that the ID is a valid UUID or whatever format you're using
|
||||
if (typeof id !== 'string') {
|
||||
return Response.json({
|
||||
success: false,
|
||||
message: "ID tidak valid",
|
||||
}, { status: 400 });
|
||||
}
|
||||
|
||||
const data = await prisma.pendapatan.findUnique({
|
||||
where: { id },
|
||||
});
|
||||
|
||||
if (!data) {
|
||||
return Response.json({
|
||||
success: false,
|
||||
message: "Pendapatan tidak ditemukan",
|
||||
}, { status: 404 });
|
||||
}
|
||||
|
||||
// Ensure we're returning a proper Response object
|
||||
return Response.json({
|
||||
success: true,
|
||||
message: "Success fetch pendapatan asli desa by ID",
|
||||
data,
|
||||
}, {
|
||||
status: 200,
|
||||
});
|
||||
} catch (e) {
|
||||
console.error("Find by ID error:", e);
|
||||
return Response.json({
|
||||
success: false,
|
||||
message: "Gagal mengambil pendapatan asli desa: " + (e instanceof Error ? e.message : 'Unknown error'),
|
||||
}, {
|
||||
status: 500,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import Elysia, { t } from "elysia";
|
||||
import pendapatanAsliFindMany from "./findMany";
|
||||
import pendapatanAsliFindUnique from "./findUnique";
|
||||
import pendapatanAsliCreate from "./create";
|
||||
import pendapatanAsliDelete from "./del";
|
||||
import pendapatanAsliUpdate from "./updt";
|
||||
|
||||
|
||||
const PendapatanAsli = new Elysia({
|
||||
prefix: "/pendapatanasli",
|
||||
tags: ["Ekonomi/Pendapatan Asli Desa/Pendapatan"],
|
||||
})
|
||||
.get("/find-many", pendapatanAsliFindMany)
|
||||
.get("/:id", async (context) => {
|
||||
const response = await pendapatanAsliFindUnique(
|
||||
new Request(context.request)
|
||||
);
|
||||
return response;
|
||||
})
|
||||
.post("/create", pendapatanAsliCreate, {
|
||||
body: t.Object({
|
||||
name: t.String(),
|
||||
value: t.Number(),
|
||||
}),
|
||||
})
|
||||
.delete("/del/:id", pendapatanAsliDelete)
|
||||
.put(
|
||||
"/:id",
|
||||
async (context) => {
|
||||
return await pendapatanAsliUpdate(context);
|
||||
},
|
||||
{
|
||||
params: t.Object({
|
||||
id: t.String(),
|
||||
}),
|
||||
body: t.Object({
|
||||
name: t.String(),
|
||||
value: t.Number(),
|
||||
}),
|
||||
}
|
||||
)
|
||||
|
||||
export default PendapatanAsli;
|
||||
@@ -0,0 +1,36 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
export default async function pendapatanAsliUpdate(context: Context) {
|
||||
const id = context.params?.id as string;
|
||||
const body = context.body as { name: string; value: number };
|
||||
|
||||
if (!id) {
|
||||
return { success: false, message: "ID tidak boleh kosong" };
|
||||
}
|
||||
|
||||
try {
|
||||
const existing = await prisma.pendapatan.findUnique({ where: { id } });
|
||||
if (!existing) {
|
||||
return { success: false, message: "Data tidak ditemukan" };
|
||||
}
|
||||
|
||||
const updated = await prisma.pendapatan.update({
|
||||
where: { id },
|
||||
data: {
|
||||
name: body.name,
|
||||
value: body.value,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Berhasil update pendapatan",
|
||||
data: updated,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Update error:", error);
|
||||
return { success: false, message: "Gagal update pendapatan" };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,26 +1,48 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import prisma from "@/lib/prisma";
|
||||
import { Context } from "elysia";
|
||||
|
||||
// Di findMany.ts
|
||||
export default async function pegawaiFindMany(context: Context) {
|
||||
const page = Number(context.query.page) || 1;
|
||||
const limit = Number(context.query.limit) || 10;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
export default async function pegawaiFindMany() {
|
||||
try {
|
||||
const pegawaiList = await prisma.pegawai.findMany({
|
||||
orderBy: { createdAt: "desc" },
|
||||
include: {
|
||||
posisi: true,
|
||||
image: true,
|
||||
},
|
||||
});
|
||||
const [data, total] = await Promise.all([
|
||||
prisma.pegawai.findMany({
|
||||
where: { isActive: true },
|
||||
include: {
|
||||
posisi: true,
|
||||
image: true,
|
||||
},
|
||||
skip,
|
||||
take: limit,
|
||||
orderBy: { createdAt: 'desc' },
|
||||
}),
|
||||
prisma.pegawai.count({
|
||||
where: { isActive: true }
|
||||
})
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: pegawaiList,
|
||||
message: "Success fetch pegawai with pagination",
|
||||
data,
|
||||
page,
|
||||
totalPages,
|
||||
total,
|
||||
};
|
||||
} catch (error: any) {
|
||||
console.error("Error findMany pegawai:", error);
|
||||
} catch (e) {
|
||||
console.error("Find many paginated error:", e);
|
||||
return {
|
||||
success: false,
|
||||
message: "Gagal mengambil data pegawai",
|
||||
error: error.message,
|
||||
message: "Failed fetch pegawai with pagination",
|
||||
data: [],
|
||||
page: 1,
|
||||
totalPages: 1,
|
||||
total: 0,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import colors from '@/con/colors';
|
||||
import { Stack, Box, Text, Image } from '@mantine/core';
|
||||
import { Stack, Box, Text, Image, Paper } from '@mantine/core';
|
||||
import React from 'react';
|
||||
import BackButton from '../../desa/layanan/_com/BackButto';
|
||||
|
||||
@@ -14,7 +14,9 @@ function Page() {
|
||||
</Text>
|
||||
<Box px={{ base: "md", md: 100 }}>
|
||||
<Stack gap={'lg'} justify='center'>
|
||||
<Image src={'/api/img/pa-desa.png'} alt=''/>
|
||||
<Paper bg={colors['white-1']} p={"xl"}>
|
||||
<Image src="/pa-desa.png" alt=''/>
|
||||
</Paper>
|
||||
</Stack>
|
||||
</Box>
|
||||
</Stack>
|
||||
|
||||
@@ -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>
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user