diff --git a/prisma/schema.prisma b/prisma/schema.prisma
index 5c12fa2c..4519bad8 100644
--- a/prisma/schema.prisma
+++ b/prisma/schema.prisma
@@ -238,19 +238,21 @@ model APBDesItem {
// Model baru untuk multiple realisasi per item
model RealisasiItem {
id String @id @default(cuid())
+ kode String? // Kode realisasi, mirip dengan APBDesItem
apbdesItemId String
apbdesItem APBDesItem @relation(fields: [apbdesItemId], references: [id], onDelete: Cascade)
-
+
jumlah Float // Jumlah realisasi dalam Rupiah
tanggal DateTime @db.Date // Tanggal realisasi
keterangan String? @db.Text // Keterangan tambahan (opsional)
buktiFileId String? // FileStorage ID untuk bukti/foto (opsional)
-
+
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
isActive Boolean @default(true)
+ @@index([kode])
@@index([apbdesItemId])
@@index([tanggal])
}
diff --git a/src/app/admin/(dashboard)/_state/landing-page/apbdes.ts b/src/app/admin/(dashboard)/_state/landing-page/apbdes.ts
index c858fba5..7e05b86e 100644
--- a/src/app/admin/(dashboard)/_state/landing-page/apbdes.ts
+++ b/src/app/admin/(dashboard)/_state/landing-page/apbdes.ts
@@ -14,14 +14,6 @@ const ApbdesItemSchema = z.object({
tipe: z.enum(['pendapatan', 'belanja', 'pembiayaan']).nullable().optional(),
});
-// --- Zod Schema untuk Realisasi Item ---
-const RealisasiItemSchema = z.object({
- jumlah: z.number().min(0, "Jumlah tidak boleh negatif"),
- tanggal: z.string(),
- keterangan: z.string().optional(),
- buktiFileId: z.string().optional(),
-});
-
const ApbdesFormSchema = z.object({
tahun: z.number().int().min(2000, "Tahun tidak valid"),
name: z.string().optional(),
@@ -157,33 +149,37 @@ const apbdes = proxy({
findUnique: {
data: null as
| Prisma.APBDesGetPayload<{
- include: { image: true; file: true; items: true };
+ include: { image: true; file: true; items: { include: { realisasiItems: true } } };
}>
| null,
loading: false,
error: null as string | null,
-
+
async load(id: string) {
if (!id || id.trim() === '') {
this.data = null;
this.error = "ID tidak valid";
return;
}
-
+
+ // Prevent multiple simultaneous loads
+ if (this.loading) {
+ console.log("⚠️ Already loading, skipping...");
+ return;
+ }
+
this.loading = true;
this.error = null;
-
+
try {
- // Pastikan URL-nya benar
const url = `/api/landingpage/apbdes/${id}`;
console.log("🌐 Fetching:", url);
-
- // Gunakan fetch biasa atau ApiFetch dengan cara yang benar
+
const response = await fetch(url);
const res = await response.json();
-
+
console.log("📦 Response:", res);
-
+
if (res.success && res.data) {
this.data = res.data;
} else {
@@ -322,15 +318,16 @@ const apbdes = proxy({
// =========================================
realisasi: {
// Create realisasi
- async create(itemId: string, data: { jumlah: number; tanggal: string; keterangan?: string; buktiFileId?: string }) {
+ async create(itemId: string, data: { kode: string; jumlah: number; tanggal: string; keterangan?: string; buktiFileId?: string }) {
try {
const res = await (ApiFetch.api.landingpage.apbdes as any)[itemId].realisasi.post(data);
if (res.data?.success) {
toast.success("Realisasi berhasil ditambahkan");
// Reload findUnique untuk update data
- if (apbdes.findUnique.data) {
- await apbdes.findUnique.load(apbdes.findUnique.data.id);
+ const currentId = apbdes.findUnique.data?.id;
+ if (currentId) {
+ await apbdes.findUnique.load(currentId);
}
return true;
} else {
@@ -345,15 +342,16 @@ const apbdes = proxy({
},
// Update realisasi
- async update(realisasiId: string, data: { jumlah?: number; tanggal?: string; keterangan?: string; buktiFileId?: string }) {
+ async update(realisasiId: string, data: { kode?: string; jumlah?: number; tanggal?: string; keterangan?: string; buktiFileId?: string }) {
try {
const res = await (ApiFetch.api.landingpage.apbdes as any).realisasi[realisasiId].put(data);
if (res.data?.success) {
toast.success("Realisasi berhasil diperbarui");
// Reload findUnique untuk update data
- if (apbdes.findUnique.data) {
- await apbdes.findUnique.load(apbdes.findUnique.data.id);
+ const currentId = apbdes.findUnique.data?.id;
+ if (currentId) {
+ await apbdes.findUnique.load(currentId);
}
return true;
} else {
diff --git a/src/app/admin/(dashboard)/landing-page/apbdes/[id]/RealisasiManager.tsx b/src/app/admin/(dashboard)/landing-page/apbdes/[id]/RealisasiManager.tsx
index 07c84f1a..26602c1d 100644
--- a/src/app/admin/(dashboard)/landing-page/apbdes/[id]/RealisasiManager.tsx
+++ b/src/app/admin/(dashboard)/landing-page/apbdes/[id]/RealisasiManager.tsx
@@ -3,6 +3,8 @@
import { useProxy } from 'valtio/utils';
import apbdes from '@/app/admin/(dashboard)/_state/landing-page/apbdes';
+import { useState } from 'react';
+import { toast } from 'react-toastify';
import {
Box,
Button,
@@ -23,7 +25,6 @@ import {
Badge,
Modal,
Divider,
- Loader,
Center,
} from '@mantine/core';
import {
@@ -33,9 +34,6 @@ import {
IconCalendar,
IconCoin,
} from '@tabler/icons-react';
-import { useState } from 'react';
-import { toast } from 'react-toastify';
-import colors from '@/con/colors';
interface RealisasiManagerProps {
itemId: string;
@@ -63,6 +61,7 @@ export default function RealisasiManager({
// Form state
const [formData, setFormData] = useState({
+ kode: '',
jumlah: 0,
tanggal: new Date().toISOString().split('T')[0], // YYYY-MM-DD format for input
keterangan: '',
@@ -70,6 +69,7 @@ export default function RealisasiManager({
const resetForm = () => {
setFormData({
+ kode: '',
jumlah: 0,
tanggal: new Date().toISOString().split('T')[0],
keterangan: '',
@@ -85,8 +85,9 @@ export default function RealisasiManager({
const handleOpenEdit = (realisasi: any) => {
const tanggal = new Date(realisasi.tanggal);
const tanggalStr = tanggal.toISOString().split('T')[0]; // YYYY-MM-DD
-
+
setFormData({
+ kode: realisasi.kode || '',
jumlah: realisasi.jumlah,
tanggal: tanggalStr,
keterangan: realisasi.keterangan || '',
@@ -100,12 +101,17 @@ export default function RealisasiManager({
return toast.warn('Jumlah realisasi harus lebih dari 0');
}
+ if (!formData.kode || formData.kode.trim() === '') {
+ return toast.warn('Kode realisasi wajib diisi');
+ }
+
try {
setLoading(true);
if (editingId) {
// Update existing realisasi
const success = await state.realisasi.update(editingId, {
+ kode: formData.kode,
jumlah: formData.jumlah,
tanggal: new Date(formData.tanggal).toISOString(),
keterangan: formData.keterangan,
@@ -117,6 +123,7 @@ export default function RealisasiManager({
} else {
// Create new realisasi
const success = await state.realisasi.create(itemId, {
+ kode: formData.kode,
jumlah: formData.jumlah,
tanggal: new Date(formData.tanggal).toISOString(),
keterangan: formData.keterangan,
@@ -257,6 +264,7 @@ export default function RealisasiManager({
+ Kode
Tanggal
Uraian
Jumlah
@@ -266,6 +274,11 @@ export default function RealisasiManager({
{realisasiItems.map((realisasi) => (
+
+
+ {realisasi.kode || '-'}
+
+
@@ -314,7 +327,7 @@ export default function RealisasiManager({
Belum ada realisasi untuk item ini
- Klik tombol "Tambah Realisasi" untuk menambahkan
+ Klik tombol "Tambah Realisasi" untuk menambahkan
@@ -349,6 +362,15 @@ export default function RealisasiManager({
+ setFormData({ ...formData, kode: e.target.value })}
+ description="Kode unik untuk realisasi ini"
+ required
+ />
+
{/* Realisasi Manager untuk setiap item */}
- {data.items.map((item: any) => (
+ {data.items.map((item) => (
{
}
// Jika bukan lagu baru, jangan reset currentTime (biar seek tidak kembali ke 0)
}
- }, [currentSong?.id]); // Intentional: hanya depend on song ID, bukan isPlaying
+ }, [currentSong?.id]); // eslint-disable-line react-hooks/exhaustive-deps -- Intentional: hanya depend on song ID, bukan isPlaying
// Sync duration dari audio element jika berbeda signifikan (> 1 detik)
useEffect(() => {
@@ -155,7 +155,7 @@ const MusicPlayer = () => {
audio.addEventListener('loadedmetadata', handleLoadedMetadata);
return () => audio.removeEventListener('loadedmetadata', handleLoadedMetadata);
- }, [currentSong?.id]); // Intentional: hanya depend on song ID
+ }, [currentSong?.id]); // eslint-disable-line react-hooks/exhaustive-deps -- Intentional: hanya depend on song ID
const formatTime = (seconds: number) => {
const mins = Math.floor(seconds / 60);
diff --git a/src/app/darmasaba/_com/main-page/apbdes/index.tsx b/src/app/darmasaba/_com/main-page/apbdes/index.tsx
index 80caaca8..bf0f9748 100644
--- a/src/app/darmasaba/_com/main-page/apbdes/index.tsx
+++ b/src/app/darmasaba/_com/main-page/apbdes/index.tsx
@@ -4,29 +4,25 @@
import apbdes from '@/app/admin/(dashboard)/_state/landing-page/apbdes'
import colors from '@/con/colors'
import {
- ActionIcon,
Box,
Button,
- Center,
+ Divider,
Group,
- Loader,
Select,
SimpleGrid,
Stack,
Text,
- Title,
+ Title
} from '@mantine/core'
-import { IconDownload } from '@tabler/icons-react'
import Link from 'next/link'
import { useEffect, useState } from 'react'
import { useProxy } from 'valtio/utils'
+import GrafikRealisasi from './lib/grafikRealisasi'
import PaguTable from './lib/paguTable'
import RealisasiTable from './lib/realisasiTable'
-import GrafikRealisasi from './lib/grafikRealisasi'
function Apbdes() {
const state = useProxy(apbdes)
- const [loading, setLoading] = useState(false)
const [selectedYear, setSelectedYear] = useState(null)
const textHeading = {
@@ -37,12 +33,9 @@ function Apbdes() {
useEffect(() => {
const loadData = async () => {
try {
- setLoading(true)
await state.findMany.load()
} catch (error) {
console.error('Error loading data:', error)
- } finally {
- setLoading(false)
}
}
loadData()
@@ -73,10 +66,12 @@ function Apbdes() {
? dataAPBDes.find((item: any) => item?.tahun?.toString() === selectedYear) || dataAPBDes[0]
: null
- const data = (state.findMany.data || []).slice(0, 3)
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ const previewData = (state.findMany.data || []).slice(0, 3)
return (
+
{/* 📌 HEADING */}
@@ -116,7 +111,7 @@ function Apbdes() {
{/* COMBOBOX */}
-
+