API & UI Tabs SubMenu Pendapatan, Menu PADesa
This commit is contained in:
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;
|
||||||
@@ -1279,3 +1279,52 @@ model DetailDataPengangguran {
|
|||||||
|
|
||||||
@@unique([month, year])
|
@@unique([month, year])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ========================================= PADESA PENDAPATAN ASLI DESA ========================================= //
|
||||||
|
model ApbDesa {
|
||||||
|
id String @id @default(uuid())
|
||||||
|
tahun Int
|
||||||
|
pendapatan Pendapatan @relation(fields: [pendapatanId], references: [id])
|
||||||
|
pendapatanId String
|
||||||
|
belanja Belanja @relation(fields: [belanjaId], references: [id])
|
||||||
|
belanjaId String
|
||||||
|
pembiayaan Pembiayaan @relation(fields: [pembiayaanId], references: [id])
|
||||||
|
pembiayaanId String
|
||||||
|
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[]
|
||||||
|
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
|
||||||
|
ApbDesa ApbDesa[]
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
deletedAt DateTime @default(now())
|
||||||
|
isActive Boolean @default(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
model Pembiayaan {
|
||||||
|
id String @id @default(uuid())
|
||||||
|
name String
|
||||||
|
value Int
|
||||||
|
ApbDesa ApbDesa[]
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
deletedAt DateTime @default(now())
|
||||||
|
isActive Boolean @default(true)
|
||||||
|
}
|
||||||
|
|||||||
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 |
881
src/app/admin/(dashboard)/_state/ekonomi/PADesa.ts
Normal file
881
src/app/admin/(dashboard)/_state/ekonomi/PADesa.ts
Normal file
@@ -0,0 +1,881 @@
|
|||||||
|
/* 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(),
|
||||||
|
pendapatanId: z.string(),
|
||||||
|
belanjaId: z.string(),
|
||||||
|
pembiayaanId: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const ApbDesaDefaultForm = {
|
||||||
|
tahun: 0,
|
||||||
|
pendapatanId: "",
|
||||||
|
belanjaId: "",
|
||||||
|
pembiayaanId: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
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: [] as Array<{
|
||||||
|
id: string;
|
||||||
|
tahun: number;
|
||||||
|
pendapatanId: string;
|
||||||
|
belanjaId: string;
|
||||||
|
pembiayaanId: string;
|
||||||
|
}>,
|
||||||
|
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;
|
||||||
|
this.form = data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Load error:", error);
|
||||||
|
toast.error("Gagal mengambil APB Desa");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async update() {
|
||||||
|
const cek = templateApbDesa.safeParse(this.form);
|
||||||
|
if (!cek.success) {
|
||||||
|
const err = `[${cek.error.issues
|
||||||
|
.map((v) => `${v.path.join(".")}`)
|
||||||
|
.join("\n")}] required`;
|
||||||
|
return toast.error(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.loading = true;
|
||||||
|
const response = await fetch(
|
||||||
|
`/api/ekonomi/pendapatanaslidesa/apbdesa/${this.id}`,
|
||||||
|
{
|
||||||
|
method: "PUT",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
tahun: this.form.tahun,
|
||||||
|
pendapatanId: this.form.pendapatanId,
|
||||||
|
belanjaId: this.form.belanjaId,
|
||||||
|
pembiayaanId: this.form.pembiayaanId,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
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 APB Desa");
|
||||||
|
await ApbDesa.findMany.load(); // refresh list
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
throw new Error(result.message || "Gagal mengupdate APB Desa");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error updating APB Desa:", error);
|
||||||
|
toast.error(
|
||||||
|
error instanceof Error ? error.message : "Gagal mengupdate APB Desa"
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
this.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
reset() {
|
||||||
|
this.id = "";
|
||||||
|
this.form = { ...ApbDesaDefaultForm };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
delete: {
|
||||||
|
loading: false,
|
||||||
|
async byId(id: string) {
|
||||||
|
if (!id) return toast.warn("ID tidak valid");
|
||||||
|
|
||||||
|
try {
|
||||||
|
ApbDesa.delete.loading = true;
|
||||||
|
|
||||||
|
const response = await fetch(
|
||||||
|
`/api/ekonomi/pendapatanaslidesa/apbdesa/del/${id}`,
|
||||||
|
{
|
||||||
|
method: "DELETE",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (response.ok && result?.success) {
|
||||||
|
toast.success(result.message || "APB Desa berhasil dihapus");
|
||||||
|
await ApbDesa.findMany.load(); // refresh list
|
||||||
|
} else {
|
||||||
|
toast.error(result?.message || "Gagal menghapus APB Desa");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Gagal delete:", error);
|
||||||
|
toast.error("Terjadi kesalahan saat menghapus APB Desa");
|
||||||
|
} finally {
|
||||||
|
ApbDesa.delete.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
findUnique: {
|
||||||
|
data: null as
|
||||||
|
| (Prisma.ApbDesaGetPayload<{
|
||||||
|
include: { pendapatan: true; belanja: true; pembiayaan: true };
|
||||||
|
}> & { isActive: boolean })
|
||||||
|
| null,
|
||||||
|
async load(id: string) {
|
||||||
|
const res = await fetch(`/api/ekonomi/pendapatanaslidesa/apbdesa/${id}`);
|
||||||
|
if (res.ok) {
|
||||||
|
const json = await res.json();
|
||||||
|
ApbDesa.findUnique.data = json.data
|
||||||
|
? {
|
||||||
|
...json.data,
|
||||||
|
isActive: json.data.isActive ?? json.data.aktif ?? true, // Fallback ke aktif:true jika tidak ada data
|
||||||
|
}
|
||||||
|
: null;
|
||||||
|
} else {
|
||||||
|
ApbDesa.findUnique.data = 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("Gagal mengambil Belanja");
|
||||||
|
}
|
||||||
|
const data = await response.json();
|
||||||
|
this.id = id;
|
||||||
|
this.form = data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Load error:", error);
|
||||||
|
toast.error("Gagal mengambil Belanja");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async update() {
|
||||||
|
const cek = templateBelanja.safeParse(this.form);
|
||||||
|
if (!cek.success) {
|
||||||
|
const err = `[${cek.error.issues
|
||||||
|
.map((v) => `${v.path.join(".")}`)
|
||||||
|
.join("\n")}] required`;
|
||||||
|
return toast.error(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.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 {
|
||||||
|
this.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
reset() {
|
||||||
|
this.id = "";
|
||||||
|
this.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("Gagal mengambil Pembiayaan");
|
||||||
|
}
|
||||||
|
const data = await response.json();
|
||||||
|
this.id = id;
|
||||||
|
this.form = data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Load error:", error);
|
||||||
|
toast.error("Gagal mengambil Pembiayaan");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async update() {
|
||||||
|
const cek = templatePembiayaan.safeParse(this.form);
|
||||||
|
if (!cek.success) {
|
||||||
|
const err = `[${cek.error.issues
|
||||||
|
.map((v) => `${v.path.join(".")}`)
|
||||||
|
.join("\n")}] required`;
|
||||||
|
return toast.error(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.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 {
|
||||||
|
this.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
reset() {
|
||||||
|
this.id = "";
|
||||||
|
this.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;
|
||||||
@@ -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: "Pendapatan",
|
||||||
|
value: "pendapatan",
|
||||||
|
href: "/admin/ekonomi/padesa-pendapatan-asli-desa/pendapatan"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "APB Desa",
|
||||||
|
value: "apbdesa",
|
||||||
|
href: "/admin/ekonomi/padesa-pendapatan-asli-desa/apbdesa"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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;
|
||||||
@@ -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;
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
|
|
||||||
function Page() {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
PADesa-pendapatan-asli-desa
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Page;
|
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
/* 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 || '',
|
||||||
|
});
|
||||||
|
|
||||||
|
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={formData.value}
|
||||||
|
onChange={(val) => {
|
||||||
|
setFormData({ ...formData, value: val.target.value });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Group>
|
||||||
|
<Button bg={colors['blue-button']} onClick={handleSubmit}>Submit</Button>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default EditPendapatan;
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
'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 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='number'
|
||||||
|
value={pendapatanState.create.form.value}
|
||||||
|
onChange={(val) => {
|
||||||
|
pendapatanState.create.form.value = Number(val.target.value);
|
||||||
|
}}
|
||||||
|
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,128 @@
|
|||||||
|
'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 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>{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>
|
||||||
|
{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;
|
||||||
@@ -75,7 +75,7 @@ function ListPegawai({ search }: { search: string }) {
|
|||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Box py={10}>
|
<Box py={10}>
|
||||||
<Paper bg={colors['white-1']} p={'md'}>
|
<Paper bg={colors['white-1']} p={'md'} h={{base: 770, md: 650}}>
|
||||||
<JudulList
|
<JudulList
|
||||||
title='List Pegawai'
|
title='List Pegawai'
|
||||||
href='/admin/ekonomi/struktur-organisasi-dan-sk-pengurus-bumdesa/pegawai/create'
|
href='/admin/ekonomi/struktur-organisasi-dan-sk-pengurus-bumdesa/pegawai/create'
|
||||||
|
|||||||
@@ -230,7 +230,7 @@ export const navBar = [
|
|||||||
{
|
{
|
||||||
id: "Ekonomi_4",
|
id: "Ekonomi_4",
|
||||||
name: "PADesa (Pendapatan Asli Desa)",
|
name: "PADesa (Pendapatan Asli Desa)",
|
||||||
path: "/admin/ekonomi/PADesa-pendapatan-asli-desa"
|
path: "/admin/ekonomi/PADesa-pendapatan-asli-desa/pendapatan"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "Ekonomi_5",
|
id: "Ekonomi_5",
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
import { Context } from "elysia";
|
import { Context } from "elysia";
|
||||||
|
|
||||||
async function beritaFindManyPaginated(context: Context) {
|
async function beritaFindMany(context: Context) {
|
||||||
const page = Number(context.query.page) || 1;
|
const page = Number(context.query.page) || 1;
|
||||||
const limit = Number(context.query.limit) || 10;
|
const limit = Number(context.query.limit) || 10;
|
||||||
const skip = (page - 1) * limit;
|
const skip = (page - 1) * limit;
|
||||||
@@ -41,4 +41,4 @@ async function beritaFindManyPaginated(context: Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default beritaFindManyPaginated;
|
export default beritaFindMany;
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import JumlahPendudukMiskin from "./jumlah-penduduk-miskin";
|
|||||||
import SektorUnggulanDesa from "./sektor-unggulan-desa";
|
import SektorUnggulanDesa from "./sektor-unggulan-desa";
|
||||||
import DemografiPekerjaan from "./demografi-pekerjaan";
|
import DemografiPekerjaan from "./demografi-pekerjaan";
|
||||||
import JumlahPengangguran from "./jumlah-pengangguran";
|
import JumlahPengangguran from "./jumlah-pengangguran";
|
||||||
|
import PendapatanAsliDesa from "./pendapatan-asli-desa";
|
||||||
|
|
||||||
const Ekonomi = new Elysia({
|
const Ekonomi = new Elysia({
|
||||||
prefix: "/api/ekonomi",
|
prefix: "/api/ekonomi",
|
||||||
@@ -26,5 +27,6 @@ const Ekonomi = new Elysia({
|
|||||||
.use(SektorUnggulanDesa)
|
.use(SektorUnggulanDesa)
|
||||||
.use(DemografiPekerjaan)
|
.use(DemografiPekerjaan)
|
||||||
.use(JumlahPengangguran)
|
.use(JumlahPengangguran)
|
||||||
|
.use(PendapatanAsliDesa)
|
||||||
|
|
||||||
export default Ekonomi
|
export default Ekonomi
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import prisma from "@/lib/prisma";
|
||||||
|
import { Context } from "elysia";
|
||||||
|
|
||||||
|
type FormCreate = {
|
||||||
|
tahun: number;
|
||||||
|
pendapatanId: string;
|
||||||
|
belanjaId: string;
|
||||||
|
pembiayaanId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function apbDesaCreate(context: Context) {
|
||||||
|
const body = context.body as FormCreate;
|
||||||
|
|
||||||
|
const created = await prisma.apbDesa.create({
|
||||||
|
data: {
|
||||||
|
tahun: body.tahun,
|
||||||
|
pendapatanId: body.pendapatanId,
|
||||||
|
belanjaId: body.belanjaId,
|
||||||
|
pembiayaanId: body.pembiayaanId,
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
tahun: true,
|
||||||
|
pendapatanId: true,
|
||||||
|
belanjaId: true,
|
||||||
|
pembiayaanId: 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,56 @@
|
|||||||
|
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 },
|
||||||
|
});
|
||||||
|
|
||||||
|
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(),
|
||||||
|
pendapatanId: t.String(),
|
||||||
|
belanjaId: t.String(),
|
||||||
|
pembiayaanId: t.String(),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
.delete("/delete/:id", apbDesaDelete)
|
||||||
|
.put(
|
||||||
|
"/:id",
|
||||||
|
async (context) => {
|
||||||
|
const response = await apbDesaUpdate(context);
|
||||||
|
return response;
|
||||||
|
},
|
||||||
|
{
|
||||||
|
body: t.Object({
|
||||||
|
tahun: t.Number(),
|
||||||
|
pendapatanId: t.String(),
|
||||||
|
belanjaId: t.String(),
|
||||||
|
pembiayaanId: t.String(),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
export default APBDesa;
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
import prisma from "@/lib/prisma";
|
||||||
|
import { Prisma } from "@prisma/client";
|
||||||
|
import { Context } from "elysia";
|
||||||
|
|
||||||
|
type FormUpdate = Prisma.ApbDesaGetPayload<{
|
||||||
|
select: {
|
||||||
|
id: true;
|
||||||
|
tahun: true;
|
||||||
|
pendapatanId: true;
|
||||||
|
belanjaId: true;
|
||||||
|
pembiayaanId: true;
|
||||||
|
};
|
||||||
|
}>;
|
||||||
|
export default async function apbDesaUpdate(context: Context) {
|
||||||
|
try {
|
||||||
|
const id = context.params?.id as string;
|
||||||
|
const body = (await context.body) as Omit<FormUpdate, "id">;
|
||||||
|
|
||||||
|
const {
|
||||||
|
tahun,
|
||||||
|
pendapatanId,
|
||||||
|
belanjaId,
|
||||||
|
pembiayaanId,
|
||||||
|
} = 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,
|
||||||
|
pendapatanId,
|
||||||
|
belanjaId,
|
||||||
|
pembiayaanId,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
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("/delete/: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,60 @@
|
|||||||
|
import prisma from "@/lib/prisma";
|
||||||
|
import { Prisma } from "@prisma/client";
|
||||||
|
import { Context } from "elysia";
|
||||||
|
|
||||||
|
type FormUpdate = Prisma.BelanjaGetPayload<{
|
||||||
|
select: {
|
||||||
|
id: true;
|
||||||
|
name: true;
|
||||||
|
value: true;
|
||||||
|
};
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export default async function belanjaUpdate(context: Context) {
|
||||||
|
try {
|
||||||
|
const id = context.params?.id as string;
|
||||||
|
const body = (await context.body) as Omit<FormUpdate, "id">;
|
||||||
|
|
||||||
|
const {
|
||||||
|
name,
|
||||||
|
value,
|
||||||
|
} = body;
|
||||||
|
|
||||||
|
if (!id) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: "ID tidak boleh kosong",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const existing = await prisma.belanja.findUnique({
|
||||||
|
where: { id },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!existing) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: "Belanja tidak ditemukan",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const updated = await prisma.belanja.update({
|
||||||
|
where: { id },
|
||||||
|
data: {
|
||||||
|
name,
|
||||||
|
value,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: "Success update belanja",
|
||||||
|
data: updated,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Update error:", error);
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: "Failed 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,60 @@
|
|||||||
|
import prisma from "@/lib/prisma";
|
||||||
|
import { Prisma } from "@prisma/client";
|
||||||
|
import { Context } from "elysia";
|
||||||
|
|
||||||
|
type FormUpdate = Prisma.PembiayaanGetPayload<{
|
||||||
|
select: {
|
||||||
|
id: true;
|
||||||
|
name: true;
|
||||||
|
value: true;
|
||||||
|
};
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export default async function pembiayaanUpdate(context: Context) {
|
||||||
|
try {
|
||||||
|
const id = context.params?.id as string;
|
||||||
|
const body = (await context.body) as Omit<FormUpdate, "id">;
|
||||||
|
|
||||||
|
const {
|
||||||
|
name,
|
||||||
|
value,
|
||||||
|
} = body;
|
||||||
|
|
||||||
|
if (!id) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: "ID tidak boleh kosong",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const existing = await prisma.pembiayaan.findUnique({
|
||||||
|
where: { id },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!existing) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: "Pembiayaan tidak ditemukan",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const updated = await prisma.pembiayaan.update({
|
||||||
|
where: { id },
|
||||||
|
data: {
|
||||||
|
name,
|
||||||
|
value,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: "Success update pembiayaan",
|
||||||
|
data: updated,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Update error:", error);
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: "Failed 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,5 +1,5 @@
|
|||||||
import colors from '@/con/colors';
|
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 React from 'react';
|
||||||
import BackButton from '../../desa/layanan/_com/BackButto';
|
import BackButton from '../../desa/layanan/_com/BackButto';
|
||||||
|
|
||||||
@@ -14,7 +14,9 @@ function Page() {
|
|||||||
</Text>
|
</Text>
|
||||||
<Box px={{ base: "md", md: 100 }}>
|
<Box px={{ base: "md", md: 100 }}>
|
||||||
<Stack gap={'lg'} justify='center'>
|
<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>
|
</Stack>
|
||||||
</Box>
|
</Box>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
Reference in New Issue
Block a user