diff --git a/prisma/schema.prisma b/prisma/schema.prisma
index 9cf9c335..4f8cd740 100644
--- a/prisma/schema.prisma
+++ b/prisma/schema.prisma
@@ -1282,25 +1282,22 @@ model DetailDataPengangguran {
// ========================================= 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)
+ 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[]
+ ApbDesa ApbDesa[] @relation("ApbDesaPendapatan")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime @default(now())
@@ -1311,20 +1308,20 @@ 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)
+ ApbDesa ApbDesa[] @relation("ApbDesaBelanja")
}
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)
+ ApbDesa ApbDesa[] @relation("ApbDesaPembiayaan")
}
diff --git a/src/app/admin/(dashboard)/_state/ekonomi/PADesa.ts b/src/app/admin/(dashboard)/_state/ekonomi/PADesa.ts
index 5df47719..1ece171f 100644
--- a/src/app/admin/(dashboard)/_state/ekonomi/PADesa.ts
+++ b/src/app/admin/(dashboard)/_state/ekonomi/PADesa.ts
@@ -6,17 +6,17 @@ import { proxy } from "valtio";
import { z } from "zod";
const templateApbDesa = z.object({
- tahun: z.number(),
- pendapatanId: z.string(),
- belanjaId: z.string(),
- pembiayaanId: z.string(),
+ 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,
- pendapatanId: "",
- belanjaId: "",
- pembiayaanId: "",
+ pendapatanIds: [] as string[],
+ belanjaIds: [] as string[],
+ pembiayaanIds: [] as string[],
};
const ApbDesa = proxy({
@@ -54,13 +54,15 @@ const ApbDesa = proxy({
},
},
findMany: {
- data: [] as Array<{
- id: string;
- tahun: number;
- pendapatanId: string;
- belanjaId: string;
- pembiayaanId: string;
- }>,
+ data: null as
+ | Prisma.ApbDesaGetPayload<{
+ include: {
+ pendapatan: true;
+ belanja: true;
+ pembiayaan: true;
+ };
+ }>[]
+ | null,
loading: false,
async load() {
try {
@@ -105,21 +107,14 @@ const ApbDesa = proxy({
}
const data = await response.json();
this.id = id;
- this.form = data;
+ return data;
} catch (error) {
- console.error("Load error:", error);
- toast.error("Gagal mengambil APB Desa");
+ console.error("Error loading APB Desa:", error);
+ toast.error("Gagal memuat data APB Desa");
+ return null;
}
},
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(
@@ -129,34 +124,19 @@ const ApbDesa = proxy({
headers: {
"Content-Type": "application/json",
},
- body: JSON.stringify({
- tahun: this.form.tahun,
- pendapatanId: this.form.pendapatanId,
- belanjaId: this.form.belanjaId,
- pembiayaanId: this.form.pembiayaanId,
- }),
+ body: JSON.stringify(this.form),
}
);
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");
+ 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(
- error instanceof Error ? error.message : "Gagal mengupdate APB Desa"
- );
- return false;
+ toast.error("Gagal memperbarui APB Desa");
+ throw error;
} finally {
this.loading = false;
}
@@ -169,55 +149,53 @@ const ApbDesa = proxy({
delete: {
loading: false,
async byId(id: string) {
- if (!id) return toast.warn("ID tidak valid");
-
try {
- ApbDesa.delete.loading = true;
-
+ this.loading = true;
const response = await fetch(
- `/api/ekonomi/pendapatanaslidesa/apbdesa/del/${id}`,
+ `/api/ekonomi/pendapatanaslidesa/apbdesa/${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");
+ if (!response.ok) {
+ throw new Error("Gagal menghapus APB Desa");
}
+ toast.success("APB Desa berhasil dihapus");
+ return true;
} catch (error) {
- console.error("Gagal delete:", error);
- toast.error("Terjadi kesalahan saat menghapus APB Desa");
+ console.error("Error deleting APB Desa:", error);
+ toast.error("Gagal menghapus APB Desa");
+ return false;
} finally {
- ApbDesa.delete.loading = false;
+ this.loading = false;
}
},
},
findUnique: {
- data: null as
- | (Prisma.ApbDesaGetPayload<{
- include: { pendapatan: true; belanja: true; pembiayaan: true };
- }> & { isActive: boolean })
- | null,
+ data: null as Prisma.ApbDesaGetPayload<{
+ include: { pendapatan: true; belanja: true; pembiayaan: true };
+ }> | 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;
+ 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;
}
},
},
@@ -533,28 +511,38 @@ const belanja = proxy({
return null;
}
try {
- const response = await fetch(
- `/api/ekonomi/pendapatanaslidesa/belanja/${id}`,
- {
- method: "GET",
- headers: {
- "Content-Type": "application/json",
- },
- }
- );
+ 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");
+ 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");
}
- const data = await response.json();
- this.id = id;
- this.form = data;
} catch (error) {
- console.error("Load error:", error);
- toast.error("Gagal mengambil Belanja");
+ console.error("Error loading belanja:", error);
+ toast.error(
+ error instanceof Error ? error.message : "Gagal memuat data"
+ );
+ return null;
}
},
+
async update() {
- const cek = templateBelanja.safeParse(this.form);
+ const cek = templateBelanja.safeParse(belanja.update.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
@@ -563,20 +551,17 @@ const belanja = proxy({
}
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,
- }),
- }
- );
+ 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(
@@ -585,25 +570,25 @@ const belanja = proxy({
}
const result = await response.json();
if (result.success) {
- toast.success("Berhasil update Belanja");
+ toast.success("Berhasil update belanja");
await belanja.findMany.load(); // refresh list
return true;
} else {
- throw new Error(result.message || "Gagal mengupdate Belanja");
+ throw new Error(result.message || "Gagal mengupdate belanja");
}
} catch (error) {
- console.error("Error updating Belanja:", error);
+ console.error("Error updating belanja:", error);
toast.error(
- error instanceof Error ? error.message : "Gagal mengupdate Belanja"
+ error instanceof Error ? error.message : "Gagal mengupdate belanja"
);
return false;
} finally {
- this.loading = false;
+ belanja.update.loading = false;
}
},
reset() {
- this.id = "";
- this.form = { ...BelanjaDefaultForm };
+ belanja.update.id = "";
+ belanja.update.form = { ...BelanjaDefaultForm };
},
},
delete: {
@@ -741,28 +726,38 @@ const pembiayaan = proxy({
return null;
}
try {
- const response = await fetch(
- `/api/ekonomi/pendapatanaslidesa/pembiayaan/${id}`,
- {
- method: "GET",
- headers: {
- "Content-Type": "application/json",
- },
- }
- );
+ 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");
+ 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");
}
- const data = await response.json();
- this.id = id;
- this.form = data;
} catch (error) {
- console.error("Load error:", error);
- toast.error("Gagal mengambil Pembiayaan");
+ console.error("Error loading pembiayaan:", error);
+ toast.error(
+ error instanceof Error ? error.message : "Gagal memuat data"
+ );
+ return null;
}
},
+
async update() {
- const cek = templatePembiayaan.safeParse(this.form);
+ const cek = templatePembiayaan.safeParse(pembiayaan.update.form);
if (!cek.success) {
const err = `[${cek.error.issues
.map((v) => `${v.path.join(".")}`)
@@ -771,20 +766,17 @@ const pembiayaan = proxy({
}
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,
- }),
- }
- );
+ 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(
@@ -793,25 +785,25 @@ const pembiayaan = proxy({
}
const result = await response.json();
if (result.success) {
- toast.success("Berhasil update Pembiayaan");
+ toast.success("Berhasil update pembiayaan");
await pembiayaan.findMany.load(); // refresh list
return true;
} else {
- throw new Error(result.message || "Gagal mengupdate Pembiayaan");
+ throw new Error(result.message || "Gagal mengupdate pembiayaan");
}
} catch (error) {
- console.error("Error updating Pembiayaan:", error);
+ console.error("Error updating pembiayaan:", error);
toast.error(
- error instanceof Error ? error.message : "Gagal mengupdate Pembiayaan"
+ error instanceof Error ? error.message : "Gagal mengupdate pembiayaan"
);
return false;
} finally {
- this.loading = false;
+ pembiayaan.update.loading = false;
}
},
reset() {
- this.id = "";
- this.form = { ...PembiayaanDefaultForm };
+ pembiayaan.update.id = "";
+ pembiayaan.update.form = { ...PembiayaanDefaultForm };
},
},
delete: {
diff --git a/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/_lib/layoutTabs.tsx b/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/_lib/layoutTabs.tsx
index c20bf497..6358cb46 100644
--- a/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/_lib/layoutTabs.tsx
+++ b/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/_lib/layoutTabs.tsx
@@ -9,25 +9,25 @@ 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"
+ 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"
+ href: "/admin/ekonomi/PADesa-pendapatan-asli-desa/belanja"
},
{
label: "Pembiayaan",
value: "pembiayaan",
- href: "/admin/ekonomi/padesa-pendapatan-asli-desa/pembiayaan"
+ href: "/admin/ekonomi/PADesa-pendapatan-asli-desa/pembiayaan"
},
];
diff --git a/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/apbdesa/[id]/edit/page.tsx b/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/apbdesa/[id]/edit/page.tsx
new file mode 100644
index 00000000..69da2f21
--- /dev/null
+++ b/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/apbdesa/[id]/edit/page.tsx
@@ -0,0 +1,11 @@
+import React from 'react';
+
+function Page() {
+ return (
+
+ Page
+
+ );
+}
+
+export default Page;
diff --git a/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/apbdesa/[id]/page.tsx b/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/apbdesa/[id]/page.tsx
new file mode 100644
index 00000000..c9cd4336
--- /dev/null
+++ b/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/apbdesa/[id]/page.tsx
@@ -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(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 (
+
+
+
+ )
+ }
+
+ return (
+
+
+
+
+
+
+ Detail APB Desa
+ {apbState.findUnique.data ? (
+
+
+
+ Tahun
+ {apbState.findUnique.data?.tahun}
+
+
+
+ Detail Pembiayaan
+ {(apbState.findUnique.data?.pembiayaan || []).map((item) => (
+
+ {item.name}: {formatRupiah(Number(item.value))}
+
+ ))}
+
+ Total: {formatRupiah((apbState.findUnique.data?.pembiayaan || [])
+ .reduce((sum, item) => sum + Number(item.value), 0))}
+
+
+
+
+
+ Detail Belanja
+ {(apbState.findUnique.data?.belanja || []).map((item) => (
+
+ {item.name}: {formatRupiah(Number(item.value))}
+
+ ))}
+
+ Total: {formatRupiah((apbState.findUnique.data?.belanja || [])
+ .reduce((sum, item) => sum + Number(item.value), 0))}
+
+
+
+
+
+ Detail Pendapatan
+ {(apbState.findUnique.data?.pendapatan || []).map((item) => (
+
+ {item.name}: {formatRupiah(Number(item.value))}
+
+ ))}
+
+ Total: {formatRupiah((apbState.findUnique.data?.pendapatan || [])
+ .reduce((sum, item) => sum + Number(item.value), 0))}
+
+
+
+
+
+
+
+
+
+ ) : null}
+
+
+
+ {/* Modal Konfirmasi Hapus */}
+ setModalHapus(false)}
+ onConfirm={handleHapus}
+ text='Apakah anda yakin ingin menghapus APB Desa ini?'
+ />
+
+ );
+}
+
+export default DetailAPBDesa;
diff --git a/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/apbdesa/create/page.tsx b/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/apbdesa/create/page.tsx
new file mode 100644
index 00000000..3ee52586
--- /dev/null
+++ b/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/apbdesa/create/page.tsx
@@ -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 (
+
+
+
+
+
+
+ Create APB Desa
+ {
+ apbDesaState.create.form.tahun = Number(val.target.value);
+ }}
+ label={Tahun}
+ placeholder="masukkan tahun"
+ />
+ {
+ apbDesaState.create.form.pendapatanIds = ids;
+ }}
+ />
+ {
+ apbDesaState.create.form.belanjaIds = ids;
+ }}
+ />
+ {
+ apbDesaState.create.form.pembiayaanIds = ids;
+ }}
+ />
+
+
+
+
+ );
+
+/* 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 ;
+ }
+
+ return (
+ Pendapatan}
+ 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 ;
+ }
+
+ return (
+ Belanja}
+ 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 ;
+ }
+
+ return (
+ Pembiayaan}
+ 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;
diff --git a/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/apbdesa/page.tsx b/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/apbdesa/page.tsx
new file mode 100644
index 00000000..11b7e353
--- /dev/null
+++ b/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/apbdesa/page.tsx
@@ -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 (
+
+ }
+ value={search}
+ onChange={(e) => setSearch(e.currentTarget.value)}
+ />
+
+
+ );
+}
+
+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 (
+
+
+
+ )
+ }
+ return (
+
+
+
+
+
+
+ Tahun
+ Pembiayaan
+ Belanja
+ Pendapatan
+ Detail
+
+
+
+ {filteredData.map((item) => (
+
+ {item.tahun}
+ {formatRupiah(item.pembiayaan.reduce((sum, item) => sum + Number(item.value), 0))}
+ {formatRupiah(item.belanja.reduce((sum, item) => sum + Number(item.value), 0))}
+ {formatRupiah(item.pendapatan.reduce((sum, item) => sum + Number(item.value), 0))}
+
+
+
+
+ ))}
+
+
+
+
+ );
+}
+
+export default APBDesa;
diff --git a/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/belanja/[id]/page.tsx b/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/belanja/[id]/page.tsx
new file mode 100644
index 00000000..33bca9e2
--- /dev/null
+++ b/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/belanja/[id]/page.tsx
@@ -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 (
+
+
+
+
+
+
+
+ Edit Jenis Pendapatan
+ {
+ setFormData({ ...formData, name: val.target.value });
+ }}
+ label={Nama Jenis Pendapatan}
+ placeholder='Masukkan nama Jenis Pendapatan'
+ />
+ Nilai}
+ placeholder='Masukkan nilai'
+ value={formatRupiah(formData.value)}
+ onChange={(val) => {
+ const raw = val.currentTarget.value;
+ const cleanValue = unformatRupiah(raw);
+ setFormData({ ...formData, value: cleanValue });
+ }}
+ />
+
+
+
+
+
+
+ );
+}
+
+export default EditBelanja;
\ No newline at end of file
diff --git a/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/belanja/create/page.tsx b/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/belanja/create/page.tsx
new file mode 100644
index 00000000..c03cb35c
--- /dev/null
+++ b/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/belanja/create/page.tsx
@@ -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 (
+
+
+
+
+
+
+
+ Create Jenis Belanja
+ {
+ belanjaState.create.form.name = val.target.value;
+ }}
+ label={Nama Jenis Belanja}
+ placeholder='Masukkan nama jenis belanja'
+ />
+ {
+ const raw = val.currentTarget.value;
+ const cleanValue = unformatRupiah(raw);
+ belanjaState.create.form.value = cleanValue;
+ }}
+ label={Nilai}
+ placeholder='Masukkan nilai'
+ />
+
+
+
+
+
+
+ );
+}
+
+export default CreateBelanja;
diff --git a/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/belanja/page.tsx b/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/belanja/page.tsx
new file mode 100644
index 00000000..d63f0ce7
--- /dev/null
+++ b/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/belanja/page.tsx
@@ -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 (
+
+ }
+ value={search}
+ onChange={(e) => setSearch(e.currentTarget.value)}
+ />
+
+
+ );
+}
+
+function ListBelanja({ search }: { search: string }) {
+ const belanjaState = useProxy(PendapatanAsliDesa.belanja)
+ const router = useRouter();
+ const [modalHapus, setModalHapus] = useState(false)
+ const [selectedId, setSelectedId] = useState(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 (
+
+
+
+ )
+ }
+ return (
+
+
+
+
+
+
+ Nama
+ Nilai
+ Persentase
+ Edit
+ Delete
+
+
+
+ {filteredData.map((item) => (
+
+ {item.name}
+ {formatRupiah(item.value)}
+ {((item.value / totalBelanja) * 100).toFixed(0)}%
+
+
+
+
+
+
+
+ ))}
+
+
+ Total
+
+
+ {formatRupiah(belanjaState.findMany.data.reduce((total, item) => total + item.value, 0))}
+
+
+
+
+
+
+ {/* Modal Konfirmasi Hapus */}
+ setModalHapus(false)}
+ onConfirm={handleDelete}
+ text='Apakah anda yakin ingin menghapus belanja ini?'
+ />
+
+ );
+}
+
+export default Belanja;
diff --git a/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/pembiayaan/[id]/page.tsx b/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/pembiayaan/[id]/page.tsx
new file mode 100644
index 00000000..79ef3847
--- /dev/null
+++ b/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/pembiayaan/[id]/page.tsx
@@ -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 (
+
+
+
+
+
+
+
+ Edit Jenis Pembiayaan
+ {
+ setFormData({ ...formData, name: val.target.value });
+ }}
+ label={Nama Jenis Pembiayaan}
+ placeholder='Masukkan nama Jenis Pembiayaan'
+ />
+ Nilai}
+ placeholder='Masukkan nilai'
+ value={formatRupiah(formData.value)}
+ onChange={(val) => {
+ const raw = val.currentTarget.value;
+ const cleanValue = unformatRupiah(raw);
+ setFormData({ ...formData, value: cleanValue });
+ }}
+ />
+
+
+
+
+
+
+ );
+}
+
+export default EditPembiayaan;
\ No newline at end of file
diff --git a/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/pembiayaan/create/page.tsx b/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/pembiayaan/create/page.tsx
new file mode 100644
index 00000000..4a81d990
--- /dev/null
+++ b/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/pembiayaan/create/page.tsx
@@ -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 (
+
+
+
+
+
+
+
+ Create Jenis Pembiayaan
+ {
+ pembiayaanState.create.form.name = val.target.value;
+ }}
+ label={Nama Jenis Pembiayaan}
+ placeholder='Masukkan nama jenis pembiayaan'
+ />
+ {
+ const raw = val.currentTarget.value;
+ const cleanValue = unformatRupiah(raw);
+ pembiayaanState.create.form.value = cleanValue;
+ }}
+ label={Nilai}
+ placeholder='Masukkan nilai'
+ />
+
+
+
+
+
+
+ );
+}
+
+export default CreatePembiayaan;
diff --git a/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/pembiayaan/page.tsx b/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/pembiayaan/page.tsx
new file mode 100644
index 00000000..4f31cafc
--- /dev/null
+++ b/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/pembiayaan/page.tsx
@@ -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 (
+
+ }
+ value={search}
+ onChange={(e) => setSearch(e.currentTarget.value)}
+ />
+
+
+ );
+}
+
+function ListPembiayaan({ search }: { search: string }) {
+ const pembiayaanState = useProxy(PendapatanAsliDesa.pembiayaan)
+ const router = useRouter();
+ const [modalHapus, setModalHapus] = useState(false)
+ const [selectedId, setSelectedId] = useState(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 (
+
+
+
+ )
+ }
+ return (
+
+
+
+
+
+
+ Nama
+ Nilai
+ Persentase
+ Edit
+ Delete
+
+
+
+ {filteredData.map((item) => (
+
+ {item.name}
+ {formatRupiah(item.value)}
+ {((item.value / totalPembiayaan) * 100).toFixed(0)}%
+
+
+
+
+
+
+
+ ))}
+
+
+ Total
+
+
+ {formatRupiah(pembiayaanState.findMany.data.reduce((total, item) => total + item.value, 0))}
+
+
+
+
+
+
+ {/* Modal Konfirmasi Hapus */}
+ setModalHapus(false)}
+ onConfirm={handleDelete}
+ text='Apakah anda yakin ingin menghapus pembiayaan ini?'
+ />
+
+ )
+}
+
+export default Pembiayaan;
diff --git a/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/pendapatan/[id]/page.tsx b/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/pendapatan/[id]/page.tsx
index 4c30ab39..2ffc7285 100644
--- a/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/pendapatan/[id]/page.tsx
+++ b/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/pendapatan/[id]/page.tsx
@@ -19,6 +19,19 @@ function EditPendapatan() {
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;
@@ -80,9 +93,11 @@ function EditPendapatan() {
Nilai}
placeholder='Masukkan nilai'
- value={formData.value}
+ value={formatRupiah(formData.value)}
onChange={(val) => {
- setFormData({ ...formData, value: val.target.value });
+ const raw = val.currentTarget.value;
+ const cleanValue = unformatRupiah(raw);
+ setFormData({ ...formData, value: cleanValue });
}}
/>
diff --git a/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/pendapatan/create/page.tsx b/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/pendapatan/create/page.tsx
index 9c7b9040..fdf9a7b0 100644
--- a/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/pendapatan/create/page.tsx
+++ b/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/pendapatan/create/page.tsx
@@ -10,6 +10,19 @@ 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: "",
@@ -42,10 +55,12 @@ function CreatePendapatan() {
placeholder='Masukkan nama jenis pendapatan'
/>
{
- pendapatanState.create.form.value = Number(val.target.value);
+ const raw = val.currentTarget.value;
+ const cleanValue = unformatRupiah(raw);
+ pendapatanState.create.form.value = cleanValue;
}}
label={Nilai}
placeholder='Masukkan nilai'
diff --git a/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/pendapatan/page.tsx b/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/pendapatan/page.tsx
index 7a19003b..0bde96df 100644
--- a/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/pendapatan/page.tsx
+++ b/src/app/admin/(dashboard)/ekonomi/PADesa-pendapatan-asli-desa/pendapatan/page.tsx
@@ -34,6 +34,14 @@ function ListPendapatan({ search }: { search: string }) {
const [modalHapus, setModalHapus] = useState(false)
const [selectedId, setSelectedId] = useState(null)
+ const formatRupiah = (value: number) => {
+ return new Intl.NumberFormat('id-ID', {
+ style: 'currency',
+ currency: 'IDR',
+ minimumFractionDigits: 0,
+ }).format(value);
+ };
+
const handleDelete = () => {
if (selectedId) {
@@ -83,7 +91,7 @@ function ListPendapatan({ search }: { search: string }) {
{filteredData.map((item) => (
{item.name}
- {item.value}
+ {formatRupiah(item.value)}
- {pendapatanState.findMany.data.reduce((total, item) => total + item.value, 0)}
+ {formatRupiah(pendapatanState.findMany.data.reduce((total, item) => total + item.value, 0))}
diff --git a/src/app/admin/_com/list_PageAdmin.tsx b/src/app/admin/_com/list_PageAdmin.tsx
index 594a4bd2..86eb9f81 100644
--- a/src/app/admin/_com/list_PageAdmin.tsx
+++ b/src/app/admin/_com/list_PageAdmin.tsx
@@ -230,7 +230,7 @@ export const navBar = [
{
id: "Ekonomi_4",
name: "PADesa (Pendapatan Asli Desa)",
- path: "/admin/ekonomi/PADesa-pendapatan-asli-desa/pendapatan"
+ path: "/admin/ekonomi/PADesa-pendapatan-asli-desa/apbdesa"
},
{
id: "Ekonomi_5",
diff --git a/src/app/api/[[...slugs]]/_lib/ekonomi/pendapatan-asli-desa/apbDesa/create.ts b/src/app/api/[[...slugs]]/_lib/ekonomi/pendapatan-asli-desa/apbDesa/create.ts
index 76cb8efd..7382162d 100644
--- a/src/app/api/[[...slugs]]/_lib/ekonomi/pendapatan-asli-desa/apbDesa/create.ts
+++ b/src/app/api/[[...slugs]]/_lib/ekonomi/pendapatan-asli-desa/apbDesa/create.ts
@@ -3,9 +3,9 @@ import { Context } from "elysia";
type FormCreate = {
tahun: number;
- pendapatanId: string;
- belanjaId: string;
- pembiayaanId: string;
+ pendapatanIds: string[];
+ belanjaIds: string[];
+ pembiayaanIds: string[];
}
export default async function apbDesaCreate(context: Context) {
@@ -14,16 +14,22 @@ export default async function apbDesaCreate(context: Context) {
const created = await prisma.apbDesa.create({
data: {
tahun: body.tahun,
- pendapatanId: body.pendapatanId,
- belanjaId: body.belanjaId,
- pembiayaanId: body.pembiayaanId,
+ 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,
- pendapatanId: true,
- belanjaId: true,
- pembiayaanId: true,
+ pendapatan: true,
+ belanja: true,
+ pembiayaan: true,
}
});
return {
diff --git a/src/app/api/[[...slugs]]/_lib/ekonomi/pendapatan-asli-desa/apbDesa/findUnique.ts b/src/app/api/[[...slugs]]/_lib/ekonomi/pendapatan-asli-desa/apbDesa/findUnique.ts
index 0d52e807..3be030c3 100644
--- a/src/app/api/[[...slugs]]/_lib/ekonomi/pendapatan-asli-desa/apbDesa/findUnique.ts
+++ b/src/app/api/[[...slugs]]/_lib/ekonomi/pendapatan-asli-desa/apbDesa/findUnique.ts
@@ -26,6 +26,11 @@ export default async function apbDesaFindUnique(
const data = await prisma.apbDesa.findUnique({
where: { id },
+ include: {
+ pendapatan: true,
+ belanja: true,
+ pembiayaan: true,
+ }
});
if (!data) {
diff --git a/src/app/api/[[...slugs]]/_lib/ekonomi/pendapatan-asli-desa/apbDesa/index.ts b/src/app/api/[[...slugs]]/_lib/ekonomi/pendapatan-asli-desa/apbDesa/index.ts
index 920b61dd..bb5ef117 100644
--- a/src/app/api/[[...slugs]]/_lib/ekonomi/pendapatan-asli-desa/apbDesa/index.ts
+++ b/src/app/api/[[...slugs]]/_lib/ekonomi/pendapatan-asli-desa/apbDesa/index.ts
@@ -17,9 +17,9 @@ const APBDesa = new Elysia({
.post("/create", apbDesaCreate, {
body: t.Object({
tahun: t.Number(),
- pendapatanId: t.String(),
- belanjaId: t.String(),
- pembiayaanId: t.String(),
+ pendapatanIds: t.Array(t.String()),
+ belanjaIds: t.Array(t.String()),
+ pembiayaanIds: t.Array(t.String()),
}),
})
.delete("/delete/:id", apbDesaDelete)
@@ -32,9 +32,9 @@ const APBDesa = new Elysia({
{
body: t.Object({
tahun: t.Number(),
- pendapatanId: t.String(),
- belanjaId: t.String(),
- pembiayaanId: t.String(),
+ pendapatanIds: t.Array(t.String()),
+ belanjaIds: t.Array(t.String()),
+ pembiayaanIds: t.Array(t.String()),
}),
}
);
diff --git a/src/app/api/[[...slugs]]/_lib/ekonomi/pendapatan-asli-desa/apbDesa/updt.ts b/src/app/api/[[...slugs]]/_lib/ekonomi/pendapatan-asli-desa/apbDesa/updt.ts
index 86d51c26..643d7fd3 100644
--- a/src/app/api/[[...slugs]]/_lib/ekonomi/pendapatan-asli-desa/apbDesa/updt.ts
+++ b/src/app/api/[[...slugs]]/_lib/ekonomi/pendapatan-asli-desa/apbDesa/updt.ts
@@ -1,26 +1,23 @@
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;
- };
-}>;
+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 Omit;
+ const body = (await context.body) as FormUpdate;
const {
tahun,
- pendapatanId,
- belanjaId,
- pembiayaanId,
+ pendapatanIds,
+ belanjaIds,
+ pembiayaanIds,
} = body;
if (!id) {
@@ -45,9 +42,15 @@ export default async function apbDesaUpdate(context: Context) {
where: { id },
data: {
tahun,
- pendapatanId,
- belanjaId,
- pembiayaanId,
+ pendapatan: {
+ connect: pendapatanIds.map((id) => ({ id })),
+ },
+ belanja: {
+ connect: belanjaIds.map((id) => ({ id })),
+ },
+ pembiayaan: {
+ connect: pembiayaanIds.map((id) => ({ id })),
+ },
}
});
return {
diff --git a/src/app/api/[[...slugs]]/_lib/ekonomi/pendapatan-asli-desa/belanja/index.ts b/src/app/api/[[...slugs]]/_lib/ekonomi/pendapatan-asli-desa/belanja/index.ts
index 1b611648..f3e4fb16 100644
--- a/src/app/api/[[...slugs]]/_lib/ekonomi/pendapatan-asli-desa/belanja/index.ts
+++ b/src/app/api/[[...slugs]]/_lib/ekonomi/pendapatan-asli-desa/belanja/index.ts
@@ -20,7 +20,7 @@ const Belanja = new Elysia({
value: t.Number(),
}),
})
-.delete("/delete/:id", belanjaDelete)
+.delete("/del/:id", belanjaDelete)
.put("/:id", async (context) => {
const response = await belanjaUpdate(context);
return response;
diff --git a/src/app/api/[[...slugs]]/_lib/ekonomi/pendapatan-asli-desa/belanja/updt.ts b/src/app/api/[[...slugs]]/_lib/ekonomi/pendapatan-asli-desa/belanja/updt.ts
index dafbba34..1a2ca0e3 100644
--- a/src/app/api/[[...slugs]]/_lib/ekonomi/pendapatan-asli-desa/belanja/updt.ts
+++ b/src/app/api/[[...slugs]]/_lib/ekonomi/pendapatan-asli-desa/belanja/updt.ts
@@ -1,60 +1,36 @@
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;
-
- 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",
- };
+ 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" };
}
-}
\ No newline at end of file
+
+ 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" };
+ }
+ }
+
\ No newline at end of file
diff --git a/src/app/api/[[...slugs]]/_lib/ekonomi/pendapatan-asli-desa/pembiayaan/updt.ts b/src/app/api/[[...slugs]]/_lib/ekonomi/pendapatan-asli-desa/pembiayaan/updt.ts
index 0ae29c6e..c139abe6 100644
--- a/src/app/api/[[...slugs]]/_lib/ekonomi/pendapatan-asli-desa/pembiayaan/updt.ts
+++ b/src/app/api/[[...slugs]]/_lib/ekonomi/pendapatan-asli-desa/pembiayaan/updt.ts
@@ -1,60 +1,36 @@
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;
-
- 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",
- };
+ 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" };
}
-}
\ No newline at end of file
+
+ 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" };
+ }
+ }
+
\ No newline at end of file