/* 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"; // --- Zod Schema --- const ApbdesItemSchema = z.object({ kode: z.string().min(1, "Kode wajib diisi"), uraian: z.string().min(1, "Uraian wajib diisi"), anggaran: z.number().min(0), realisasi: z.number().min(0), selisih: z.number(), persentase: z.number(), level: z.number().int().min(1).max(3), tipe: z.enum(['pendapatan', 'belanja', 'pembiayaan']).nullable().optional(), }); const ApbdesFormSchema = z.object({ tahun: z.number().int().min(2000, "Tahun tidak valid"), imageId: z.string().min(1, "Gambar wajib diunggah"), fileId: z.string().min(1, "File wajib diunggah"), items: z.array(ApbdesItemSchema).min(1, "Minimal ada 1 item"), }); // --- Default Form --- const defaultApbdesForm = { tahun: new Date().getFullYear(), imageId: "", fileId: "", items: [] as z.infer[], }; // --- Helper: hitung selisih & persentase otomatis (opsional di frontend) --- // --- Helper: hitung selisih & persentase otomatis (opsional di frontend) --- function normalizeItem(item: Partial>): z.infer { const anggaran = item.anggaran ?? 0; const realisasi = item.realisasi ?? 0; // ✅ Formula yang benar const selisih = realisasi - anggaran; // positif = sisa anggaran, negatif = over budget const persentase = anggaran > 0 ? (realisasi / anggaran) * 100 : 0; // persentase realisasi terhadap anggaran return { kode: item.kode || "", uraian: item.uraian || "", anggaran, realisasi, selisih, persentase, level: item.level || 1, tipe: item.tipe, // biarkan null jika memang null }; } // --- State Utama --- const apbdes = proxy({ create: { form: { ...defaultApbdesForm }, loading: false, addItem(item: Partial>) { const normalized = normalizeItem(item); this.form.items.push(normalized); }, removeItem(index: number) { this.form.items.splice(index, 1); }, updateItem(index: number, updates: Partial>) { const current = this.form.items[index]; if (current) { const updated = normalizeItem({ ...current, ...updates }); this.form.items[index] = updated; } }, reset() { this.form = { ...defaultApbdesForm }; }, async create() { const parsed = ApbdesFormSchema.safeParse(this.form); if (!parsed.success) { const errors = parsed.error.issues.map((issue) => `${issue.path.join(".")} - ${issue.message}`); toast.error(`Validasi gagal:\n${errors.join("\n")}`); return; } try { this.loading = true; const res = await ApiFetch.api.landingpage.apbdes["create"].post(parsed.data); if (res.data?.success) { toast.success("APBDes berhasil dibuat"); apbdes.findMany.load(); this.reset(); } else { toast.error(res.data?.message || "Gagal membuat APBDes"); } } catch (error: any) { console.error("Create APBDes error:", error); toast.error(error?.message || "Terjadi kesalahan saat membuat APBDes"); } finally { this.loading = false; } }, }, findMany: { data: null as | Prisma.APBDesGetPayload<{ include: { image: true; file: true; items: true }; }>[] | null, page: 1, totalPages: 1, total: 0, loading: false, search: "", load: async (page = 1, limit = 10, search = "") => { apbdes.findMany.loading = true; apbdes.findMany.page = page; apbdes.findMany.search = search; try { const query: Record = { page: String(page), limit: String(limit) }; if (search) query.search = search; const res = await ApiFetch.api.landingpage.apbdes["findMany"].get({ query }); if (res.data?.success) { apbdes.findMany.data = res.data.data || []; apbdes.findMany.total = res.data.meta?.total || 0; apbdes.findMany.totalPages = res.data.meta?.totalPages || 1; } else { apbdes.findMany.data = []; apbdes.findMany.total = 0; apbdes.findMany.totalPages = 1; toast.error(res.data?.message || "Gagal memuat data"); } } catch (error) { console.error("FindMany error:", error); apbdes.findMany.data = []; apbdes.findMany.total = 0; apbdes.findMany.totalPages = 1; toast.error("Gagal memuat daftar APBDes"); } finally { apbdes.findMany.loading = false; } }, }, findUnique: { data: null as | Prisma.APBDesGetPayload<{ include: { image: true; file: true; items: 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; } 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 { this.data = null; this.error = res.message || "Gagal memuat detail APBDes"; toast.error(this.error); } } catch (error) { console.error("❌ FindUnique error:", error); this.data = null; this.error = "Gagal memuat detail APBDes"; toast.error(this.error); } finally { this.loading = false; } } }, delete: { loading: false, async byId(id: string) { if (!id) return toast.warn("ID tidak valid"); try { this.loading = true; const res = await (ApiFetch.api.landingpage.apbdes as any)["del"][id].delete(); if (res.data?.success) { toast.success("APBDes berhasil dihapus"); apbdes.findMany.load(); } else { toast.error(res.data?.message || "Gagal menghapus APBDes"); } } catch (error: any) { console.error("Delete error:", error); toast.error(error?.message || "Terjadi kesalahan saat menghapus"); } finally { this.loading = false; } }, }, edit: { id: "", form: { ...defaultApbdesForm }, loading: false, async load(id: string) { if (!id) return toast.warn("ID tidak valid"); try { this.loading = true; const res = await (ApiFetch.api.landingpage.apbdes as any)[id].get(); if (res.data?.success) { const data = res.data.data; this.id = data.id; this.form = { tahun: data.tahun || new Date().getFullYear(), imageId: data.imageId || "", fileId: data.fileId || "", items: (data.items || []).map((item: any) => ({ kode: item.kode, uraian: item.uraian, anggaran: item.anggaran, realisasi: item.realisasi, selisih: item.selisih, persentase: item.persentase, level: item.level, tipe: item.tipe || 'pendapatan', })), }; return data; } else { throw new Error(res.data?.message || "Gagal memuat data"); } } catch (error: any) { console.error("Edit load error:", error); toast.error(error.message || "Gagal memuat data untuk diedit"); } finally { this.loading = false; } }, async update() { const parsed = ApbdesFormSchema.safeParse(this.form); if (!parsed.success) { const errors = parsed.error.issues.map((issue) => `${issue.path.join(".")} - ${issue.message}`); toast.error(`Validasi gagal:\n${errors.join("\n")}`); return false; } try { this.loading = true; // Include the ID in the request body const requestData = { ...parsed.data, id: this.id, // Add the ID to the request body }; const res = await (ApiFetch.api.landingpage.apbdes as any)[this.id].put(requestData); if (res.data?.success) { toast.success("APBDes berhasil diperbarui"); apbdes.findMany.load(); return true; } else { throw new Error(res.data?.message || "Gagal memperbarui APBDes"); } } catch (error: any) { console.error("Update error:", error); toast.error(error.message || "Gagal memperbarui APBDes"); return false; } finally { this.loading = false; } }, addItem(item: Partial>) { const normalized = normalizeItem(item); this.form.items.push(normalized); }, removeItem(index: number) { this.form.items.splice(index, 1); }, reset() { this.id = ""; this.form = { ...defaultApbdesForm }; }, }, }); export default apbdes;