import React, { useState, useEffect, useMemo } from 'react'; // === EMBEDDED LIGHTWEIGHT ICONS (SVG) === const HomeIcon = ({ className = "w-5 h-5" }) => ( ); const PlusIcon = ({ className = "w-5 h-5" }) => ( ); const DocumentIcon = ({ className = "w-5 h-5" }) => ( ); const WalletIcon = ({ className = "w-5 h-5" }) => ( ); const BankIcon = ({ className = "w-5 h-5" }) => ( ); const SettingsIcon = ({ className = "w-5 h-5" }) => ( ); const PrinterIcon = ({ className = "w-5 h-5" }) => ( ); const TrashIcon = ({ className = "w-4 h-4" }) => ( ); const EditIcon = ({ className = "w-4 h-4" }) => ( ); const DownloadIcon = ({ className = "w-5 h-5" }) => ( ); const ShieldCheckIcon = ({ className = "w-6 h-6" }) => ( ); // === CONSTANTS & DEFAULTS === const CATEGORY_EXPENSES = [ "Honorarium", "Alat Tulis Kantor & Kertas", "Konsumsi Rapat / Kegiatan", "Perjalanan Dinas / Transportasi", "Sarana Prasarana & Bahan Praktik", "Biaya Komunikasi / Internet", "Pemeliharaan / Perbaikan Gedung", "Administrasi Bank", "Lain-lain" ]; const INITIAL_PROFILE = { namaSekolah: "SMKS Bakti Karya", npsn: "20403021", kabupaten: "Pangandaran", provinsi: "Jawa Barat", bulan: "Mei", tahun: "2026", namaKepala: "Dr. H. Ahmad Yani, M.Pd.", nipKepala: "19750812 200212 1 003", namaKetuaP2sp: "Budi Santoso, S.T.", nipKetuaP2sp: "19810415 200904 1 002", namaBendahara: "Siti Rahma, S.E.", nipBendahara: "19880521 201503 2 001", saldoAwalKas: 1500000, saldoAwalBank: 12500000, tempatPenutupan: "Pangandaran", tanggalPenutupan: "29 Mei 2026" }; const INITIAL_TRANSACTIONS = [ { id: "tx-1", tanggal: "2026-05-02", uraian: "Penerimaan Dana Revitalisasi Tahap 1", noBukti: "001/REV/2026", tipe: "Penerimaan", sumber: "Bank", jenisBiaya: "", jumlah: 25000000 }, { id: "tx-2", tanggal: "2026-05-05", uraian: "Tarik Tunai Bank untuk Kas Operasional Sekolah", noBukti: "NP-01", tipe: "Transfer", sumber: "Bank", // Bank berkurang, Kas Tunai bertambah jenisBiaya: "Pindahan Buku", jumlah: 5000000 }, { id: "tx-3", tanggal: "2026-05-06", uraian: "Pembelian ATK, Kertas Printer, dan Tinta Printer HP", noBukti: "BKT-01", tipe: "Pengeluaran", sumber: "Kas Tunai", jenisBiaya: "Alat Tulis Kantor & Kertas", jumlah: 850000 }, { id: "tx-4", tanggal: "2026-05-10", uraian: "Pembayaran Honorarium Instruktur Pelatihan Praktik Industri", noBukti: "BKB-01", tipe: "Pengeluaran", sumber: "Bank", jenisBiaya: "Honorarium", jumlah: 3500000 }, { id: "tx-5", tanggal: "2026-05-12", uraian: "Pembelian Bahan Praktik Siswa Teknik (Kayu, Paku, Cat Besi)", noBukti: "BKT-02", tipe: "Pengeluaran", sumber: "Kas Tunai", jenisBiaya: "Sarana Prasarana & Bahan Praktik", jumlah: 1200000 }, { id: "tx-6", tanggal: "2026-05-15", uraian: "Penyediaan Konsumsi Rapat Koordinasi Revitalisasi", noBukti: "BKT-03", tipe: "Pengeluaran", sumber: "Kas Tunai", jenisBiaya: "Konsumsi Rapat / Kegiatan", jumlah: 450000 }, { id: "tx-7", tanggal: "2026-05-18", uraian: "Biaya Transportasi Bendahara ke Kantor Dinas Provinsi", noBukti: "BKT-04", tipe: "Pengeluaran", sumber: "Kas Tunai", jenisBiaya: "Perjalanan Dinas / Transportasi", jumlah: 300000 }, { id: "tx-8", tanggal: "2026-05-25", uraian: "Biaya Administrasi Buku Tabungan Bulanan Bank", noBukti: "BKB-02", tipe: "Pengeluaran", sumber: "Bank", jenisBiaya: "Administrasi Bank", jumlah: 15000 } ]; export default function App() { // === STATES === const [activeTab, setActiveTab] = useState('dashboard'); // 'dashboard' | 'transactions' | 'reports' | 'settings' const [activeReportTab, setActiveReportTab] = useState('bku'); // 'bku' | 'kas-tunai' | 'bank' const [profile, setProfile] = useState(() => { const saved = localStorage.getItem('buku_kas_profile'); return saved ? JSON.parse(saved) : INITIAL_PROFILE; }); const [transactions, setTransactions] = useState(() => { const saved = localStorage.getItem('buku_kas_transactions'); return saved ? JSON.parse(saved) : INITIAL_TRANSACTIONS; }); // Modal / Form States const [showFormModal, setShowFormModal] = useState(false); const [editingTransaction, setEditingTransaction] = useState(null); const [formData, setFormData] = useState({ tanggal: '', uraian: '', noBukti: '', tipe: 'Pengeluaran', // 'Penerimaan' | 'Pengeluaran' | 'Transfer' sumber: 'Kas Tunai', // 'Kas Tunai' | 'Bank' (Untuk 'Transfer', ini mewakili sumber asal) jenisBiaya: '', jumlah: '' }); // Search & Filter const [searchQuery, setSearchQuery] = useState(''); const [filterSource, setFilterSource] = useState('Semua'); const [filterType, setFilterType] = useState('Semua'); // Notification Modal (Ganti browser alert) const [notification, setNotification] = useState(null); // Sync with LocalStorage useEffect(() => { localStorage.setItem('buku_kas_profile', JSON.stringify(profile)); }, [profile]); useEffect(() => { localStorage.setItem('buku_kas_transactions', JSON.stringify(transactions)); }, [transactions]); // Form Utility: Reset const resetForm = () => { setFormData({ tanggal: new Date().toISOString().split('T')[0], uraian: '', noBukti: '', tipe: 'Pengeluaran', sumber: 'Kas Tunai', jenisBiaya: '', jumlah: '' }); setEditingTransaction(null); }; // Open Form const openAddModal = () => { resetForm(); setShowFormModal(true); }; const openEditModal = (tx) => { setEditingTransaction(tx); setFormData({ tanggal: tx.tanggal, uraian: tx.uraian, noBukti: tx.noBukti, tipe: tx.tipe, sumber: tx.sumber, jenisBiaya: tx.jenisBiaya || '', jumlah: tx.jumlah.toString() }); setShowFormModal(true); }; // Show message in Custom UI const showMessage = (title, message, type = 'success') => { setNotification({ title, message, type }); }; // Handle Form Submit const handleFormSubmit = (e) => { e.preventDefault(); if (!formData.tanggal || !formData.uraian || !formData.jumlah) { showMessage("Error", "Harap isi kolom Tanggal, Uraian, dan Jumlah!", "error"); return; } const value = parseFloat(formData.jumlah); if (isNaN(value) || value <= 0) { showMessage("Error", "Jumlah nominal harus bernilai positif!", "error"); return; } const txData = { id: editingTransaction ? editingTransaction.id : `tx-${Date.now()}`, tanggal: formData.tanggal, uraian: formData.uraian, noBukti: formData.noBukti, tipe: formData.tipe, sumber: formData.sumber, jenisBiaya: formData.tipe === 'Pengeluaran' ? formData.jenisBiaya : '', jumlah: value }; if (editingTransaction) { setTransactions(transactions.map(t => t.id === editingTransaction.id ? txData : t)); showMessage("Sukses", "Transaksi berhasil diperbarui."); } else { setTransactions([...transactions, txData]); showMessage("Sukses", "Transaksi baru telah berhasil ditambahkan."); } setShowFormModal(false); resetForm(); }; // Handle Delete const handleDeleteTransaction = (id) => { setTransactions(transactions.filter(t => t.id !== id)); showMessage("Dihapus", "Transaksi berhasil dihapus dari sistem."); }; // Sample data reset const handleLoadSampleData = () => { setProfile(INITIAL_PROFILE); setTransactions(INITIAL_TRANSACTIONS); showMessage("Sistem Direset", "Data contoh bawaan telah berhasil dimuat kembali."); }; const handleClearAllData = () => { setTransactions([]); setProfile({ ...INITIAL_PROFILE, saldoAwalKas: 0, saldoAwalBank: 0 }); showMessage("Sistem Dikosongkan", "Semua riwayat transaksi dan saldo awal telah dihapus."); }; // Helper formatting Rupiah const formatRupiah = (num) => { return new Intl.NumberFormat('id-ID', { style: 'currency', currency: 'IDR', minimumFractionDigits: 0 }).format(num || 0); }; // Format Date Indo const formatDateIndo = (dateStr) => { if (!dateStr) return ''; const months = [ "Januari", "Februari", "Maret", "April", "Mei", "Juni", "Juli", "Agustus", "September", "Oktober", "November", "Desember" ]; const parts = dateStr.split('-'); if (parts.length !== 3) return dateStr; const day = parseInt(parts[2], 10); const monthIndex = parseInt(parts[1], 10) - 1; const year = parts[0]; return `${day} ${months[monthIndex]} ${year}`; }; // Sort Transactions Chronologically const sortedTransactions = useMemo(() => { return [...transactions].sort((a, b) => new Date(a.tanggal) - new Date(b.tanggal)); }, [transactions]); // === BOOK LEDGER CALCULATIONS === // 1. Buku Pembantu Kas Tunai const kasTunaiLedger = useMemo(() => { let runningBalance = profile.saldoAwalKas; const ledger = []; sortedTransactions.forEach(tx => { // Menentukan pengaruh terhadap Kas Tunai let debet = 0; let kredit = 0; if (tx.tipe === 'Penerimaan' && tx.sumber === 'Kas Tunai') { debet = tx.jumlah; } else if (tx.tipe === 'Pengeluaran' && tx.sumber === 'Kas Tunai') { kredit = tx.jumlah; } else if (tx.tipe === 'Transfer') { // Transfer: Tarik Tunai atau Setor Tunai // Tarik Tunai (Bank -> Kas Tunai): Kas Tunai bertambah (Debet) // Setor Tunai (Kas Tunai -> Bank): Kas Tunai berkurang (Kredit) if (tx.uraian.toLowerCase().includes('tarik') || tx.uraian.toLowerCase().includes('ambil')) { debet = tx.jumlah; } else { kredit = tx.jumlah; } } if (debet > 0 || kredit > 0) { runningBalance = runningBalance + debet - kredit; ledger.push({ ...tx, debet, kredit, saldo: runningBalance }); } }); return ledger; }, [sortedTransactions, profile.saldoAwalKas]); // 2. Buku Bank const bankLedger = useMemo(() => { let runningBalance = profile.saldoAwalBank; const ledger = []; sortedTransactions.forEach(tx => { // Menentukan pengaruh terhadap Bank let debet = 0; let kredit = 0; if (tx.tipe === 'Penerimaan' && tx.sumber === 'Bank') { debet = tx.jumlah; } else if (tx.tipe === 'Pengeluaran' && tx.sumber === 'Bank') { kredit = tx.jumlah; } else if (tx.tipe === 'Transfer') { // Transfer: Tarik Tunai atau Setor Tunai // Tarik Tunai (Bank -> Kas Tunai): Bank berkurang (Kredit) // Setor Tunai (Kas Tunai -> Bank): Bank bertambah (Debet) if (tx.uraian.toLowerCase().includes('tarik') || tx.uraian.toLowerCase().includes('ambil')) { kredit = tx.jumlah; } else { debet = tx.jumlah; } } if (debet > 0 || kredit > 0) { runningBalance = runningBalance + debet - kredit; ledger.push({ ...tx, debet, kredit, saldo: runningBalance }); } }); return ledger; }, [sortedTransactions, profile.saldoAwalBank]); // 3. Buku Kas Umum (Symmetrical Side-by-Side) const bkuData = useMemo(() => { // Sisi Penerimaan const penerimaanSide = []; // Sisi Pengeluaran const pengeluaranSide = []; // Saldo Awal sebagai baris pertama di Penerimaan penerimaanSide.push({ tanggal: `${profile.tahun}-05-01`, // Awal bulan uraian: "Saldo Awal Kas Tunai & Bank", noBukti: "-", jumlah: profile.saldoAwalKas + profile.saldoAwalBank }); // Proses semua transaksi terurut kronologis sortedTransactions.forEach(tx => { if (tx.tipe === 'Penerimaan') { penerimaanSide.push({ tanggal: tx.tanggal, uraian: `${tx.uraian} (${tx.sumber})`, noBukti: tx.noBukti || "-", jumlah: tx.jumlah }); } else if (tx.tipe === 'Pengeluaran') { pengeluaranSide.push({ tanggal: tx.tanggal, uraian: `${tx.uraian} (${tx.sumber})`, noBukti: tx.noBukti || "-", jenisBiaya: tx.jenisBiaya || "Umum", jumlah: tx.jumlah }); } else if (tx.tipe === 'Transfer') { // Transaksi Transfer Kas memengaruhi kedua sisi di BKU gabungan // Tarik Tunai: Pengeluaran dari Bank (kredit) & Penerimaan di Kas Tunai (debet) const isTarik = tx.uraian.toLowerCase().includes('tarik') || tx.uraian.toLowerCase().includes('ambil'); const deskripsiPenerimaan = isTarik ? `Penerimaan Kas Tunai dari Tarik Tunai Bank` : `Setor Tunai ke Bank dari Kas`; const deskripsiPengeluaran = isTarik ? `Penarikan Bank untuk Kas Tunai` : `Pengeluaran Kas Tunai untuk Setor Bank`; penerimaanSide.push({ tanggal: tx.tanggal, uraian: deskripsiPenerimaan, noBukti: tx.noBukti || "-", jumlah: tx.jumlah }); pengeluaranSide.push({ tanggal: tx.tanggal, uraian: deskripsiPengeluaran, noBukti: tx.noBukti || "-", jenisBiaya: "Pindahan Buku", jumlah: tx.jumlah }); } }); // Samakan baris menggunakan padding agar tampilan sejajar presisi seperti Excel aslinya const maxLength = Math.max(penerimaanSide.length, pengeluaranSide.length); const alignedPenerimaan = [...penerimaanSide]; const alignedPengeluaran = [...pengeluaranSide]; while (alignedPenerimaan.length < maxLength) { alignedPenerimaan.push({ tanggal: "", uraian: "", noBukti: "", jumlah: 0 }); } while (alignedPengeluaran.length < maxLength) { alignedPengeluaran.push({ tanggal: "", uraian: "", noBukti: "", jenisBiaya: "", jumlah: 0 }); } // Totals const totalPenerimaan = penerimaanSide.reduce((sum, item) => sum + item.jumlah, 0); const totalPengeluaran = pengeluaranSide.reduce((sum, item) => sum + item.jumlah, 0); const akhirKasTunai = kasTunaiLedger.length > 0 ? kasTunaiLedger[kasTunaiLedger.length - 1].saldo : profile.saldoAwalKas; const akhirBank = bankLedger.length > 0 ? bankLedger[bankLedger.length - 1].saldo : profile.saldoAwalBank; const saldoKasUmum = akhirKasTunai + akhirBank; return { penerimaanRows: alignedPenerimaan, pengeluaranRows: alignedPengeluaran, totalPenerimaan, totalPengeluaran, akhirKasTunai, akhirBank, saldoKasUmum, perbedaan: saldoKasUmum - (totalPenerimaan - totalPengeluaran) // seharus nya 0 }; }, [sortedTransactions, profile, kasTunaiLedger, bankLedger]); // === DASHBOARD ANALYTICS === const dashboardStats = useMemo(() => { const totalPenerimaanBulanIni = transactions .filter(tx => tx.tipe === 'Penerimaan') .reduce((sum, tx) => sum + tx.jumlah, 0); const totalPengeluaranBulanIni = transactions .filter(tx => tx.tipe === 'Pengeluaran') .reduce((sum, tx) => sum + tx.jumlah, 0); const runningKasTunai = kasTunaiLedger.length > 0 ? kasTunaiLedger[kasTunaiLedger.length - 1].saldo : profile.saldoAwalKas; const runningBank = bankLedger.length > 0 ? bankLedger[bankLedger.length - 1].saldo : profile.saldoAwalBank; const totalKasUmum = runningKasTunai + runningBank; // Hitung Pengeluaran per Kategori const pengeluaranPerKategori = {}; CATEGORY_EXPENSES.forEach(cat => { pengeluaranPerKategori[cat] = 0; }); transactions .filter(tx => tx.tipe === 'Pengeluaran') .forEach(tx => { const cat = tx.jenisBiaya || "Lain-lain"; if (pengeluaranPerKategori[cat] !== undefined) { pengeluaranPerKategori[cat] += tx.jumlah; } else { pengeluaranPerKategori["Lain-lain"] += tx.jumlah; } }); const categoryData = Object.keys(pengeluaranPerKategori) .map(key => ({ kategori: key, jumlah: pengeluaranPerKategori[key], persentase: totalPengeluaranBulanIni > 0 ? (pengeluaranPerKategori[key] / totalPengeluaranBulanIni) * 100 : 0 })) .filter(item => item.jumlah > 0) .sort((a, b) => b.jumlah - a.jumlah); return { totalKasUmum, runningKasTunai, runningBank, penerimaanIni: totalPenerimaanBulanIni, pengeluaranIni: totalPengeluaranBulanIni, categories: categoryData }; }, [transactions, kasTunaiLedger, bankLedger, profile]); // Filtered Transactions for Table const filteredTransactions = useMemo(() => { return transactions.filter(tx => { const matchSearch = tx.uraian.toLowerCase().includes(searchQuery.toLowerCase()) || (tx.noBukti && tx.noBukti.toLowerCase().includes(searchQuery.toLowerCase())); const matchSource = filterSource === 'Semua' || tx.sumber === filterSource; const matchType = filterType === 'Semua' || tx.tipe === filterType; return matchSearch && matchSource && matchType; }); }, [transactions, searchQuery, filterSource, filterType]); // Export to CSV Function const exportToCSV = () => { if (transactions.length === 0) { showMessage("Peringatan", "Tidak ada data transaksi yang bisa diekspor.", "error"); return; } let csvContent = "data:text/csv;charset=utf-8,"; csvContent += "ID,Tanggal,Uraian,No Bukti,Tipe,Metode Pembayaran,Jenis Biaya,Jumlah (Rp)\n"; transactions.forEach(tx => { const row = [ tx.id, tx.tanggal, `"${tx.uraian.replace(/"/g, '""')}"`, tx.noBukti ? `"${tx.noBukti.replace(/"/g, '""')}"` : "-", tx.tipe, tx.sumber, tx.jenisBiaya ? `"${tx.jenisBiaya.replace(/"/g, '""')}"` : "-", tx.jumlah ].join(","); csvContent += row + "\n"; }); const encodedUri = encodeURI(csvContent); const link = document.createElement("a"); link.setAttribute("href", encodedUri); link.setAttribute("download", `Buku_Kas_${profile.namaSekolah.replace(/\s+/g, '_')}_${profile.bulan}_2026.csv`); document.body.appendChild(link); link.click(); document.body.removeChild(link); showMessage("Berhasil", "Data transaksi berhasil diekspor ke CSV."); }; return (
{/* APP NAVBAR (Hidden in Print Mode) */} {/* MOBILE NAV BOTTOM BAR (Hidden in Print Mode) */}
{/* MAIN CONTAINER */}
{/* ======================================================== */} {/* TAB 1: DASHBOARD */} {/* ======================================================== */} {activeTab === 'dashboard' && (
{/* Header Banner */}

Selamat Datang di Portal Buku Kas

Laporan keuangan otomatis disusun secara berkala sesuai standar Buku Kas Umum (BKU), Buku Pembantu Kas Tunai, dan Buku Bank.

{/* Balances Metrics Grid */}
{/* Card 1: Kas Umum */}
Saldo Buku Kas Umum

{formatRupiah(dashboardStats.totalKasUmum)}

Konsolidasi Kas & Bank {profile.bulan} {profile.tahun}
{/* Card 2: Kas Tunai */}
Saldo Pembantu Kas Tunai

{formatRupiah(dashboardStats.runningKasTunai)}

Tunai di Brankas {((dashboardStats.runningKasTunai / (dashboardStats.totalKasUmum || 1)) * 100).toFixed(1)}% porsi
{/* Card 3: Saldo Bank */}
Saldo Rekening Bank (BB)

{formatRupiah(dashboardStats.runningBank)}

Dana di Bank Mandiri/BJB/BPD {((dashboardStats.runningBank / (dashboardStats.totalKasUmum || 1)) * 100).toFixed(1)}% porsi
{/* Cashflow Summary & Expense Chart */}
{/* Left & Center: Cashflow Month & Category Breakdown */}

Analisis Arus Kas Masuk & Keluar

Bulan {profile.bulan}
Total Penerimaan Baru {formatRupiah(dashboardStats.penerimaanIni)}
Total Pengeluaran Baru {formatRupiah(dashboardStats.pengeluaranIni)}
{/* Progress Category */}

Persentase Pengeluaran Berdasarkan Kategori Biaya

{dashboardStats.categories.length === 0 ? (
Belum ada transaksi pengeluaran tercatat di bulan ini.
) : (
{dashboardStats.categories.slice(0, 5).map((cat, idx) => (
{cat.kategori} {formatRupiah(cat.jumlah)} ({cat.persentase.toFixed(1)}%)
))} {dashboardStats.categories.length > 5 && (

+ {dashboardStats.categories.length - 5} kategori lainnya

)}
)}
{/* Right Sidebar: Quick Summary Satuan Pendidikan */}

Profil Pelaporan

Satuan Pendidikan {profile.namaSekolah}
NPSN {profile.npsn}
Periode Buku {profile.bulan} {profile.tahun}
Kepala Sekolah {profile.namaKepala}
Bendahara {profile.namaBendahara}
Status Konsolidasi

Sistem otomatis mencocokkan total Saldo Kas Umum dengan penjumlahan real Saldo Pembantu Kas Tunai dan Buku Bank.

Status Selisih (Selisih): Balance (0)
{/* Quick action tools */}

Butuh bantuan memulai atau membersihkan database?

Anda dapat memuat kembali data contoh simulasi atau menghapus semua data untuk input riil.

)} {/* ======================================================== */} {/* TAB 2: MANAGE TRANSACTIONS */} {/* ======================================================== */} {activeTab === 'transactions' && (
{/* Header actions */}

Manajemen Transaksi Buku Kas

Tambah, ubah, atau hapus transaksi keuangan sekolah di sini.

{/* Filter and Search Panel */}
setSearchQuery(e.target.value)} placeholder="Coba cari: 'ATK', 'Dana Tahap 1', dll..." className="w-full bg-slate-50 border border-slate-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500" />
{/* Transactions Table Card */}
{filteredTransactions.length === 0 ? ( ) : ( filteredTransactions.map((tx) => ( )) )}
Tanggal No. Bukti Uraian Transaksi Kategori Buku Kategori Biaya (Keluar) Nominal Aksi
Tidak ditemukan transaksi yang cocok dengan kriteria saringan Anda.
{tx.tanggal} {tx.noBukti || "-"} {tx.uraian} {tx.tipe} ({tx.sumber}) {tx.tipe === 'Pengeluaran' ? (tx.jenisBiaya || 'Umum') : '-'} {tx.tipe === 'Penerimaan' ? '+' : tx.tipe === 'Pengeluaran' ? '-' : '⇄'} {formatRupiah(tx.jumlah)}
)} {/* ======================================================== */} {/* TAB 3: OFFICIAL REPORTS & PRINT LAYOUTS */} {/* ======================================================== */} {activeTab === 'reports' && (
{/* Navigasi saringan laporan (HANYA MUNCUL DI LAYAR/KOMPUTER - HIDDEN DI PRINT) */}
{/* NOTIFIKASI CETAK UNTUK USER */}
Tips Mencetak: Klik tombol Cetak Buku (PDF) untuk mencetak laporan resmi tanpa menu atas dan samping. Atur orientasi cetak ke Landscape (Lebar) khusus untuk Buku Kas Umum (BKU) agar presisi side-by-side, dan Portrait (Tegak) untuk Buku Pembantu Kas Tunai & Buku Bank.
{/* ======================================================== */} {/* SUB-LAPORAN A: BUKU KAS UMUM (BKU) SIDE BY SIDE */} {/* ======================================================== */} {activeReportTab === 'bku' && (
{/* Kop Laporan */}

Buku Kas Umum (BKU)

Program Revitalisasi {profile.namaSekolah}

Bulan {profile.bulan} {profile.tahun}

{/* Grid Identitas */}
Nama Sekolah
: {profile.namaSekolah}
NPSN
: {profile.npsn}
Kabupaten / Provinsi
: {profile.kabupaten} / {profile.provinsi}
{/* Symmetrical Table (Left: Penerimaan, Right: Pengeluaran) */}
{/* Section Main Header */} {/* Column Header */} {/* Penerimaan */} {/* Pengeluaran */} {bkuData.penerimaanRows.map((_, index) => { const pen = bkuData.penerimaanRows[index]; const peng = bkuData.pengeluaranRows[index]; return ( {/* PENERIMAAN CELLS */} {/* PENGELUARAN CELLS */} ); })} {/* Baris Total Pengeluaran */} {/* Baris Saldo Kas Umum */} {/* Baris Grand Total Penyeimbang (Balancing) */}
PENERIMAAN (DEBET) PENGELUARAN (KREDIT)
Tanggal Uraian No Bukti Jumlah (Rp)Tanggal Uraian Jml (Qty/Unit) No Bukti Jenis Biaya Jumlah (Rp)
{pen.tanggal ? pen.tanggal.split('-')[2] + '/' + pen.tanggal.split('-')[1] : ''} {pen.uraian} {pen.noBukti === "-" ? "" : pen.noBukti} {pen.jumlah > 0 ? formatRupiah(pen.jumlah) : ""} {peng.tanggal ? peng.tanggal.split('-')[2] + '/' + peng.tanggal.split('-')[1] : ''} {peng.uraian} {peng.jumlah > 0 ? "1 Kegiatan" : ""} {peng.noBukti === "-" ? "" : peng.noBukti} {peng.jenisBiaya} {peng.jumlah > 0 ? formatRupiah(peng.jumlah) : ""}
Total Pengeluaran : {formatRupiah(bkuData.totalPengeluaran)}
Saldo Kas Umum : {formatRupiah(bkuData.saldoKasUmum)}
Total Penerimaan (Kiri): {formatRupiah(bkuData.totalPenerimaan)} Total Penerimaan (Kanan): {formatRupiah(bkuData.totalPengeluaran + bkuData.saldoKasUmum)}
{/* Lembar Penutupan Kas (Sesuai format persis di dokumen aslinya) */}

Pada hari ini {profile.tempatPenutupan}, {profile.tanggalPenutupan} Buku Kas Umum ditutup dengan keadaan / posisi buku sebagai berikut:

Saldo Buku Kas Umum : {formatRupiah(bkuData.saldoKasUmum)}
Terdiri dari:
1. Saldo Bank : {formatRupiah(bkuData.akhirBank)}
2. Saldo Kas Tunai : {formatRupiah(bkuData.akhirKasTunai)}
Perbedaan (Selisih) : {formatRupiah(bkuData.perbedaan)}
{/* Official Signatures Section */}

Mengetahui,

Kepala Sekolah

{profile.namaKepala}

NIP. {profile.nipKepala || "-"}

Disetujui,

Ketua P2SP

{profile.namaKetuaP2sp}

NIP. {profile.nipKetuaP2sp || "-"}

{profile.tempatPenutupan}, {profile.tanggalPenutupan}

Bendahara Sekolah

{profile.namaBendahara}

NIP. {profile.nipBendahara || "-"}

)} {/* ======================================================== */} {/* SUB-LAPORAN B: BUKU PEMBANTU KAS TUNAI (BPKT) */} {/* ======================================================== */} {activeReportTab === 'kas-tunai' && (
{/* Kop Laporan */}

Buku Pembantu Kas Tunai

{profile.namaSekolah}

Bulan : {profile.bulan} {profile.tahun}

{/* Grid Identitas */}
Nama Sekolah
: {profile.namaSekolah}
NPSN
: {profile.npsn}
Kabupaten
: {profile.kabupaten}
Provinsi
: {profile.provinsi}
{/* Ledger Table */}
{/* Baris Saldo Awal */} {/* Baris Transaksi Kas */} {kasTunaiLedger.length === 0 ? ( ) : ( kasTunaiLedger.map((row, idx) => ( )) )} {/* Baris Akumulasi / Jumlah total */}
No Tanggal Uraian Transaksi No. Bukti Debet/Penerimaan (Rp.) Kredit/Pengeluaran (Rp.) Saldo (Rp.)
- - Saldo Awal Kas Tunai Brankas - - - {formatRupiah(profile.saldoAwalKas)}
Belum ada transaksi tunai tercatat pada bulan ini.
{idx + 1} {formatDateIndo(row.tanggal)} {row.uraian} {row.noBukti || "-"} {row.debet > 0 ? formatRupiah(row.debet) : "-"} {row.kredit > 0 ? formatRupiah(row.kredit) : "-"} {formatRupiah(row.saldo)}
Jumlah : {formatRupiah(kasTunaiLedger.reduce((sum, r) => sum + r.debet, 0))} {formatRupiah(kasTunaiLedger.reduce((sum, r) => sum + r.kredit, 0))} {formatRupiah(kasTunaiLedger.length > 0 ? kasTunaiLedger[kasTunaiLedger.length - 1].saldo : profile.saldoAwalKas)}
{/* Signatures */}

Mengetahui,

Kepala Sekolah

{profile.namaKepala}

NIP. {profile.nipKepala || "-"}

Disetujui,

Ketua P2SP

{profile.namaKetuaP2sp}

NIP. {profile.nipKetuaP2sp || "-"}

{profile.tempatPenutupan}, {profile.tanggalPenutupan}

Bendahara

{profile.namaBendahara}

NIP. {profile.nipBendahara || "-"}

)} {/* ======================================================== */} {/* SUB-LAPORAN C: BUKU BANK (BB) */} {/* ======================================================== */} {activeReportTab === 'bank' && (
{/* Kop Laporan */}

Buku Bank (BB)

{profile.namaSekolah}

Bulan : {profile.bulan} {profile.tahun}

{/* Grid Identitas */}
Nama Sekolah
: {profile.namaSekolah}
NPSN
: {profile.npsn}
Kabupaten
: {profile.kabupaten}
Provinsi
: {profile.provinsi}
{/* Ledger Table */}
{/* Baris Saldo Awal */} {/* Baris Transaksi Bank */} {bankLedger.length === 0 ? ( ) : ( bankLedger.map((row, idx) => ( )) )} {/* Baris Akumulasi / Jumlah total */}
No Tanggal Uraian Transaksi No. Bukti Debet/Penerimaan (Rp.) Kredit/Pengeluaran (Rp.) Saldo (Rp.)
- - Saldo Awal Giro/Rekening Bank - - - {formatRupiah(profile.saldoAwalBank)}
Belum ada transaksi bank tercatat pada bulan ini.
{idx + 1} {formatDateIndo(row.tanggal)} {row.uraian} {row.noBukti || "-"} {row.debet > 0 ? formatRupiah(row.debet) : "-"} {row.kredit > 0 ? formatRupiah(row.kredit) : "-"} {formatRupiah(row.saldo)}
Jumlah : {formatRupiah(bankLedger.reduce((sum, r) => sum + r.debet, 0))} {formatRupiah(bankLedger.reduce((sum, r) => sum + r.kredit, 0))} {formatRupiah(bankLedger.length > 0 ? bankLedger[bankLedger.length - 1].saldo : profile.saldoAwalBank)}
{/* Signatures */}

Mengetahui,

Kepala Sekolah

{profile.namaKepala}

NIP. {profile.nipKepala || "-"}

Disetujui,

Ketua P2SP

{profile.namaKetuaP2sp}

NIP. {profile.nipKetuaP2sp || "-"}

{profile.tempatPenutupan}, {profile.tanggalPenutupan}

Bendahara

{profile.namaBendahara}

NIP. {profile.nipBendahara || "-"}

)}
)} {/* ======================================================== */} {/* TAB 4: PROFILE SETTINGS */} {/* ======================================================== */} {activeTab === 'settings' && (

Profil & Konfigurasi Buku Kas

Kelola identitas satuan pendidikan, saldo awal, dan otoritas penandatangan laporan keuangan.

{ e.preventDefault(); showMessage("Tersimpan", "Profil satuan pendidikan berhasil diperbarui."); }} className="space-y-6"> {/* Seksi 1: Identitas Sekolah */}

Identitas Sekolah

setProfile({ ...profile, namaSekolah: e.target.value })} className="w-full bg-slate-50 border border-slate-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 font-medium" />
setProfile({ ...profile, npsn: e.target.value })} className="w-full bg-slate-50 border border-slate-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 font-mono" />
setProfile({ ...profile, kabupaten: e.target.value })} className="w-full bg-slate-50 border border-slate-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500" />
setProfile({ ...profile, provinsi: e.target.value })} className="w-full bg-slate-50 border border-slate-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500" />
{/* Seksi 2: Periode & Tanggal Cetak */}

Periode Pelaporan & Lokasi

setProfile({ ...profile, bulan: e.target.value })} className="w-full bg-slate-50 border border-slate-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500" />
setProfile({ ...profile, tahun: e.target.value })} className="w-full bg-slate-50 border border-slate-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 font-mono" />
setProfile({ ...profile, tempatPenutupan: e.target.value })} className="w-full bg-slate-50 border border-slate-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500" />
setProfile({ ...profile, tanggalPenutupan: e.target.value })} className="w-full bg-slate-50 border border-slate-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500" />
{/* Seksi 3: Saldo Awal */}

Saldo Awal (Kas / Bank)

setProfile({ ...profile, saldoAwalKas: parseFloat(e.target.value) || 0 })} className="w-full bg-slate-50 border border-slate-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 font-mono" />
setProfile({ ...profile, saldoAwalBank: parseFloat(e.target.value) || 0 })} className="w-full bg-slate-50 border border-slate-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 font-mono" />
{/* Seksi 4: Aparat Penandatangan */}

Pejabat Penandatangan Laporan

{/* Kepala Sekolah */}
Kategori 1: Kepala Sekolah
setProfile({ ...profile, namaKepala: e.target.value })} className="w-full bg-white border border-slate-200 rounded-md px-2 py-1 text-xs focus:outline-none focus:ring-1 focus:ring-indigo-500" />
setProfile({ ...profile, nipKepala: e.target.value })} className="w-full bg-white border border-slate-200 rounded-md px-2 py-1 text-xs focus:outline-none focus:ring-1 focus:ring-indigo-500" />
{/* Ketua P2SP */}
Kategori 2: Ketua P2SP
setProfile({ ...profile, namaKetuaP2sp: e.target.value })} className="w-full bg-white border border-slate-200 rounded-md px-2 py-1 text-xs focus:outline-none focus:ring-1 focus:ring-indigo-500" />
setProfile({ ...profile, nipKetuaP2sp: e.target.value })} className="w-full bg-white border border-slate-200 rounded-md px-2 py-1 text-xs focus:outline-none focus:ring-1 focus:ring-indigo-500" />
{/* Bendahara */}
Kategori 3: Bendahara
setProfile({ ...profile, namaBendahara: e.target.value })} className="w-full bg-white border border-slate-200 rounded-md px-2 py-1 text-xs focus:outline-none focus:ring-1 focus:ring-indigo-500" />
setProfile({ ...profile, nipBendahara: e.target.value })} className="w-full bg-white border border-slate-200 rounded-md px-2 py-1 text-xs focus:outline-none focus:ring-1 focus:ring-indigo-500" />
)}
{/* ======================================================== */} {/* FORM MODAL: ADD / EDIT TRANSACTION */} {/* ======================================================== */} {showFormModal && (
{/* Modal Header */}

{editingTransaction ? 'Ubah Riwayat Transaksi' : 'Catat Transaksi Baru'}

{/* Modal Body */}
{/* Row 1: Tanggal & No Bukti */}
setFormData({ ...formData, tanggal: e.target.value })} className="w-full bg-slate-50 border border-slate-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 font-medium" />
setFormData({ ...formData, noBukti: e.target.value })} className="w-full bg-slate-50 border border-slate-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 font-mono text-xs" />
{/* Row 2: Tipe Aliran */}
{/* Conditional Row: Jika jenis === Transfer Kas */} {formData.tipe === 'Transfer' && (
Informasi Transfer Kas: Sistem otomatis mengonversi transaksi ini:
  • Jika asal Bank: Maka dicatat sebagai Tarik Tunai (Bank berkurang, Kas Tunai bertambah).
  • Jika asal Kas Tunai: Maka dicatat sebagai Setor Tunai (Kas Tunai berkurang, Bank bertambah).
)} {/* Row 3: Uraian */}