API & UI Tabs SubMenu Pendapatan, Menu PADesa

This commit is contained in:
2025-07-10 16:52:26 +08:00
parent 2bc9b2f3c6
commit cb52701f47
43 changed files with 2501 additions and 17 deletions

View 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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -1,11 +0,0 @@
import React from 'react';
function Page() {
return (
<div>
PADesa-pendapatan-asli-desa
</div>
);
}
export default Page;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -75,7 +75,7 @@ function ListPegawai({ search }: { search: string }) {
}
return (
<Box py={10}>
<Paper bg={colors['white-1']} p={'md'}>
<Paper bg={colors['white-1']} p={'md'} h={{base: 770, md: 650}}>
<JudulList
title='List Pegawai'
href='/admin/ekonomi/struktur-organisasi-dan-sk-pengurus-bumdesa/pegawai/create'

View File

@@ -230,7 +230,7 @@ export const navBar = [
{
id: "Ekonomi_4",
name: "PADesa (Pendapatan Asli Desa)",
path: "/admin/ekonomi/PADesa-pendapatan-asli-desa"
path: "/admin/ekonomi/PADesa-pendapatan-asli-desa/pendapatan"
},
{
id: "Ekonomi_5",

View File

@@ -2,7 +2,7 @@
import prisma from "@/lib/prisma";
import { Context } from "elysia";
async function beritaFindManyPaginated(context: Context) {
async function beritaFindMany(context: Context) {
const page = Number(context.query.page) || 1;
const limit = Number(context.query.limit) || 10;
const skip = (page - 1) * limit;
@@ -41,4 +41,4 @@ async function beritaFindManyPaginated(context: Context) {
}
}
export default beritaFindManyPaginated;
export default beritaFindMany;

View File

@@ -10,6 +10,7 @@ import JumlahPendudukMiskin from "./jumlah-penduduk-miskin";
import SektorUnggulanDesa from "./sektor-unggulan-desa";
import DemografiPekerjaan from "./demografi-pekerjaan";
import JumlahPengangguran from "./jumlah-pengangguran";
import PendapatanAsliDesa from "./pendapatan-asli-desa";
const Ekonomi = new Elysia({
prefix: "/api/ekonomi",
@@ -26,5 +27,6 @@ const Ekonomi = new Elysia({
.use(SektorUnggulanDesa)
.use(DemografiPekerjaan)
.use(JumlahPengangguran)
.use(PendapatanAsliDesa)
export default Ekonomi

View File

@@ -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,
};
}

View File

@@ -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,
};
}

View File

@@ -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,
};
}
}

View File

@@ -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,
});
}
}

View File

@@ -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;

View File

@@ -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'};
}
}

View File

@@ -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,
};
}

View File

@@ -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,
};
}

View File

@@ -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",
};
}
}

View File

@@ -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,
}
);
}
}

View File

@@ -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;

View File

@@ -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",
};
}
}

View File

@@ -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;

View File

@@ -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,
};
}

View File

@@ -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,
};
}

View File

@@ -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",
};
}
}

View File

@@ -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,
}
);
}
}

View File

@@ -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;

View File

@@ -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",
};
}
}

View File

@@ -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,
};
}

View File

@@ -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,
};
}

View File

@@ -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;

View File

@@ -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,
});
}
}

View File

@@ -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;

View File

@@ -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" };
}
}

View File

@@ -1,5 +1,5 @@
import colors from '@/con/colors';
import { Stack, Box, Text, Image } from '@mantine/core';
import { Stack, Box, Text, Image, Paper } from '@mantine/core';
import React from 'react';
import BackButton from '../../desa/layanan/_com/BackButto';
@@ -14,7 +14,9 @@ function Page() {
</Text>
<Box px={{ base: "md", md: 100 }}>
<Stack gap={'lg'} justify='center'>
<Image src={'/api/img/pa-desa.png'} alt=''/>
<Paper bg={colors['white-1']} p={"xl"}>
<Image src="/pa-desa.png" alt=''/>
</Paper>
</Stack>
</Box>
</Stack>