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) */}
BK
Buku Kas Satuan Pendidikan
{profile.namaSekolah} (NPSN: {profile.npsn})
{/* Nav Items Desktop */}
setActiveTab('dashboard')}
className={`px-3 py-2 rounded-md text-sm font-medium flex items-center transition-all ${activeTab === 'dashboard' ? 'bg-indigo-800 text-white shadow-md' : 'text-indigo-100 hover:bg-indigo-600 hover:text-white'}`}
>
Dasbor
setActiveTab('transactions')}
className={`px-3 py-2 rounded-md text-sm font-medium flex items-center transition-all ${activeTab === 'transactions' ? 'bg-indigo-800 text-white shadow-md' : 'text-indigo-100 hover:bg-indigo-600 hover:text-white'}`}
>
Kelola Transaksi
setActiveTab('reports')}
className={`px-3 py-2 rounded-md text-sm font-medium flex items-center transition-all ${activeTab === 'reports' ? 'bg-indigo-800 text-white shadow-md' : 'text-indigo-100 hover:bg-indigo-600 hover:text-white'}`}
>
Laporan Buku Kas
setActiveTab('settings')}
className={`px-3 py-2 rounded-md text-sm font-medium flex items-center transition-all ${activeTab === 'settings' ? 'bg-indigo-800 text-white shadow-md' : 'text-indigo-100 hover:bg-indigo-600 hover:text-white'}`}
>
Pengaturan Profil
{/* Quick Stats in Navbar */}
Total Kas Umum:
{formatRupiah(dashboardStats.totalKasUmum)}
{/* MOBILE NAV BOTTOM BAR (Hidden in Print Mode) */}
setActiveTab('dashboard')}
className={`flex flex-col items-center p-1 text-xs font-medium ${activeTab === 'dashboard' ? 'text-indigo-600' : 'text-slate-500'}`}
>
Dasbor
setActiveTab('transactions')}
className={`flex flex-col items-center p-1 text-xs font-medium ${activeTab === 'transactions' ? 'text-indigo-600' : 'text-slate-500'}`}
>
Transaksi
setActiveTab('reports')}
className={`flex flex-col items-center p-1 text-xs font-medium ${activeTab === 'reports' ? 'text-indigo-600' : 'text-slate-500'}`}
>
Laporan
setActiveTab('settings')}
className={`flex flex-col items-center p-1 text-xs font-medium ${activeTab === 'settings' ? 'text-indigo-600' : 'text-slate-500'}`}
>
Profil
{/* 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.
Transaksi Baru
{ setActiveTab('reports'); setActiveReportTab('bku'); }}
className="bg-indigo-600 hover:bg-indigo-500 text-white font-semibold px-4 py-2 rounded-xl text-sm flex items-center border border-indigo-500 shadow-md transition-all"
>
Lihat BKU
{/* 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.
Muat Data Contoh
Hapus Semua Data
)}
{/* ======================================================== */}
{/* TAB 2: MANAGE TRANSACTIONS */}
{/* ======================================================== */}
{activeTab === 'transactions' && (
{/* Header actions */}
Manajemen Transaksi Buku Kas
Tambah, ubah, atau hapus transaksi keuangan sekolah di sini.
Ekspor CSV
Tambah Transaksi
{/* Filter and Search Panel */}
Cari Keterangan / No. Bukti
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"
/>
Saringan Alur
setFilterSource(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"
>
Semua Saluran
Hanya Kas Tunai
Hanya Bank
Saringan Tipe
setFilterType(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"
>
Semua Tipe
Penerimaan
Pengeluaran
Transfer Kas
{/* Transactions Table Card */}
Tanggal
No. Bukti
Uraian Transaksi
Kategori Buku
Kategori Biaya (Keluar)
Nominal
Aksi
{filteredTransactions.length === 0 ? (
Tidak ditemukan transaksi yang cocok dengan kriteria saringan Anda.
) : (
filteredTransactions.map((tx) => (
{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)}
openEditModal(tx)}
className="text-slate-400 hover:text-indigo-600 p-1 rounded-md hover:bg-slate-100 transition-colors"
title="Ubah data"
>
handleDeleteTransaction(tx.id)}
className="text-slate-400 hover:text-rose-600 p-1 rounded-md hover:bg-slate-100 transition-colors"
title="Hapus"
>
))
)}
)}
{/* ======================================================== */}
{/* TAB 3: OFFICIAL REPORTS & PRINT LAYOUTS */}
{/* ======================================================== */}
{activeTab === 'reports' && (
{/* Navigasi saringan laporan (HANYA MUNCUL DI LAYAR/KOMPUTER - HIDDEN DI PRINT) */}
setActiveReportTab('bku')}
className={`px-4 py-2 rounded-lg text-xs sm:text-sm font-bold transition-all ${activeReportTab === 'bku' ? 'bg-white text-indigo-700 shadow-sm' : 'text-slate-500 hover:text-slate-800'}`}
>
Buku Kas Umum (BKU)
setActiveReportTab('kas-tunai')}
className={`px-4 py-2 rounded-lg text-xs sm:text-sm font-bold transition-all ${activeReportTab === 'kas-tunai' ? 'bg-white text-indigo-700 shadow-sm' : 'text-slate-500 hover:text-slate-800'}`}
>
Buku Pembantu Kas Tunai
setActiveReportTab('bank')}
className={`px-4 py-2 rounded-lg text-xs sm:text-sm font-bold transition-all ${activeReportTab === 'bank' ? 'bg-white text-indigo-700 shadow-sm' : 'text-slate-500 hover:text-slate-800'}`}
>
Buku Bank
window.print()}
className="bg-indigo-600 hover:bg-indigo-700 text-white font-bold px-4 py-2.5 rounded-xl text-sm flex items-center shadow-md transition-colors"
>
Cetak Buku (PDF)
{/* 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 */}
PENERIMAAN (DEBET)
PENGELUARAN (KREDIT)
{/* Column Header */}
{/* Penerimaan */}
Tanggal
Uraian
No Bukti
Jumlah (Rp)
{/* Pengeluaran */}
Tanggal
Uraian
Jml (Qty/Unit)
No Bukti
Jenis Biaya
Jumlah (Rp)
{bkuData.penerimaanRows.map((_, index) => {
const pen = bkuData.penerimaanRows[index];
const peng = bkuData.pengeluaranRows[index];
return (
{/* PENERIMAAN CELLS */}
{pen.tanggal ? pen.tanggal.split('-')[2] + '/' + pen.tanggal.split('-')[1] : ''}
{pen.uraian}
{pen.noBukti === "-" ? "" : pen.noBukti}
{pen.jumlah > 0 ? formatRupiah(pen.jumlah) : ""}
{/* PENGELUARAN CELLS */}
{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) : ""}
);
})}
{/* Baris Total Pengeluaran */}
Total Pengeluaran :
{formatRupiah(bkuData.totalPengeluaran)}
{/* Baris Saldo Kas Umum */}
Saldo Kas Umum :
{formatRupiah(bkuData.saldoKasUmum)}
{/* Baris Grand Total Penyeimbang (Balancing) */}
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 */}
No
Tanggal
Uraian Transaksi
No. Bukti
Debet/Penerimaan (Rp.)
Kredit/Pengeluaran (Rp.)
Saldo (Rp.)
{/* Baris Saldo Awal */}
-
-
Saldo Awal Kas Tunai Brankas
-
-
-
{formatRupiah(profile.saldoAwalKas)}
{/* Baris Transaksi Kas */}
{kasTunaiLedger.length === 0 ? (
Belum ada transaksi tunai tercatat pada bulan ini.
) : (
kasTunaiLedger.map((row, idx) => (
{idx + 1}
{formatDateIndo(row.tanggal)}
{row.uraian}
{row.noBukti || "-"}
{row.debet > 0 ? formatRupiah(row.debet) : "-"}
{row.kredit > 0 ? formatRupiah(row.kredit) : "-"}
{formatRupiah(row.saldo)}
))
)}
{/* Baris Akumulasi / Jumlah total */}
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 */}
No
Tanggal
Uraian Transaksi
No. Bukti
Debet/Penerimaan (Rp.)
Kredit/Pengeluaran (Rp.)
Saldo (Rp.)
{/* Baris Saldo Awal */}
-
-
Saldo Awal Giro/Rekening Bank
-
-
-
{formatRupiah(profile.saldoAwalBank)}
{/* Baris Transaksi Bank */}
{bankLedger.length === 0 ? (
Belum ada transaksi bank tercatat pada bulan ini.
) : (
bankLedger.map((row, idx) => (
{idx + 1}
{formatDateIndo(row.tanggal)}
{row.uraian}
{row.noBukti || "-"}
{row.debet > 0 ? formatRupiah(row.debet) : "-"}
{row.kredit > 0 ? formatRupiah(row.kredit) : "-"}
{formatRupiah(row.saldo)}
))
)}
{/* Baris Akumulasi / Jumlah total */}
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.
)}
{/* ======================================================== */}
{/* FORM MODAL: ADD / EDIT TRANSACTION */}
{/* ======================================================== */}
{showFormModal && (
{/* Modal Header */}
{editingTransaction ? 'Ubah Riwayat Transaksi' : 'Catat Transaksi Baru'}
setShowFormModal(false)}
className="text-indigo-200 hover:text-white p-1 rounded-full hover:bg-indigo-800 transition-colors"
>
{/* Modal Body */}
)}
{/* ======================================================== */}
{/* NOTIFICATION MODAL (REPLACEMENT FOR ALERT) */}
{/* ======================================================== */}
{notification && (
{notification.type === 'error' ? (
) : (
)}
{notification.title}
{notification.message}
setNotification(null)}
className="w-full bg-slate-100 hover:bg-slate-200 text-slate-700 font-bold py-2 rounded-xl text-sm transition-colors"
>
Tutup
)}
{/* FOOTER WATERMARK IN SCREEN VIEW (HIDDEN IN PRINT) */}
Aplikasi Buku Kas Umum Sekolah © 2026 • Dirancang Sesuai Format Excel Revitalisasi Pendidikan
);
}