bank-sampah/Views/Main/LaporanKeuangan/Index.cshtml

288 lines
11 KiB
Plaintext

@{
ViewData["Title"] = "Laporan Keuangan";
}
<div class="flex flex-col gap-2 md:flex-row md:justify-between md:gap-0">
<div class="prose">
<span class="text-xl font-semibold text-black">
Laporan Keuangan
</span>
<p class="text-sm text-gray-500">Rekap otomatis pembelian, penjualan, dan pengeluaran BSU per bulan.</p>
</div>
<div class="flex flex-col gap-2 md:flex-row items-center">
<select id="filter_tahun" class="select select-bordered w-full md:w-48">
</select>
<button class="btn btn-sm bg-green-800 max-w-full rounded-full text-white hover:bg-green-900" onclick="modal_pengeluaran.showModal()">
Tambah Pengeluaran Lain-lain
<i class="ph ph-plus"></i>
</button>
</div>
</div>
<div class="h-6"></div>
<div class="card bg-white">
<div class="card-body p-2">
<div class="w-full overflow-x-auto">
<table class="table-zebra table" id="laporanTable">
<thead>
<tr>
<th class="w-[5%]">No</th>
<th class="w-[20%]">Bulan</th>
<th class="w-[15%]">Pembelian (Rp)</th>
<th class="w-[15%]">Penjualan (Rp)</th>
<th class="w-[15%]">Pengeluaran Lain-lain (Rp)</th>
<th class="w-[15%]">Laba/Rugi Bulanan (Rp)</th>
<th class="w-[15%]">Saldo Akumulatif (Rp)</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
</div>
</div>
<div class="mt-4 rounded-2xl bg-gray-50 p-4 flex flex-col md:flex-row md:items-center md:justify-between gap-2">
<div>
<p class="text-sm text-gray-500">Saldo akumulatif tahun berjalan</p>
<p class="text-xl font-semibold text-gray-900" id="totalSaldoLabel">Rp 0</p>
</div>
<div class="text-sm text-gray-500">
Data dihitung otomatis dari transaksi pembelian, penjualan, dan pengeluaran manual.
</div>
</div>
<!-- Modal Pengeluaran -->
<dialog id="modal_pengeluaran" class="modal modal-bottom sm:modal-middle">
<div class="modal-box w-full max-w-2xl p-6 bg-white rounded-2xl">
<h3 class="text-gray-900 text-xl font-semibold leading-8 mb-4">Tambah Pengeluaran Lain-lain</h3>
<form id="formPengeluaran">
<div class="flex flex-col gap-4">
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<fieldset class="fieldset">
<legend class="fieldset-legend">Tanggal<span class="text-red-500">*</span></legend>
<input type="date" name="Tanggal" class="input w-full" required />
</fieldset>
</div>
<div>
<fieldset class="fieldset">
<legend class="fieldset-legend">Nama Pengeluaran<span class="text-red-500">*</span></legend>
<input type="text" name="NamaPengeluaran" class="input w-full" placeholder="Contoh: Biaya Operasional" required />
</fieldset>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<fieldset class="fieldset">
<legend class="fieldset-legend">Nominal (Rp)<span class="text-red-500">*</span></legend>
<input type="number" min="0" step="1000" name="Nominal" class="input w-full" placeholder="0" required />
</fieldset>
</div>
<div>
<fieldset class="fieldset">
<legend class="fieldset-legend">Keterangan</legend>
<input type="text" name="Keterangan" class="input w-full" placeholder="Opsional" />
</fieldset>
</div>
</div>
<div class="flex justify-end gap-3">
<button type="button" class="btn bg-white rounded-full" onclick="closePengeluaranModal()">Batal</button>
<button type="submit" class="btn bg-green-800 text-white rounded-full">Simpan</button>
</div>
</div>
</form>
</div>
<form method="dialog" class="modal-backdrop">
<button>close</button>
</form>
</dialog>
<!-- /modal -->
@section Scripts {
<script type="text/javascript">
const monthlyTransactions = [
{ year: 2025, month: 1, pembelian: 12500000, penjualan: 18200000 },
{ year: 2025, month: 2, pembelian: 10350000, penjualan: 16750000 },
{ year: 2025, month: 3, pembelian: 9800000, penjualan: 17150000 },
{ year: 2025, month: 4, pembelian: 11200000, penjualan: 15600000 },
{ year: 2025, month: 5, pembelian: 12200000, penjualan: 18900000 },
{ year: 2025, month: 6, pembelian: 10450000, penjualan: 14800000 },
{ year: 2025, month: 7, pembelian: 9900000, penjualan: 19750000 },
{ year: 2025, month: 8, pembelian: 11650000, penjualan: 17500000 },
{ year: 2025, month: 9, pembelian: 12400000, penjualan: 18350000 },
{ year: 2024, month: 10, pembelian: 10050000, penjualan: 15500000 },
{ year: 2024, month: 11, pembelian: 11250000, penjualan: 16200000 },
{ year: 2024, month: 12, pembelian: 12800000, penjualan: 18600000 }
];
let manualExpenses = [
{ id: 1, tanggal: '2025-01-12', title: 'Transport & Operasional', amount: 1200000, notes: 'Pengiriman sampah ke offtaker' },
{ id: 2, tanggal: '2025-03-08', title: 'Perawatan Timbangan', amount: 800000, notes: 'Kalibrasi timbangan BSU' },
{ id: 3, tanggal: '2025-05-20', title: 'Konsumsi Sosialisasi', amount: 450000, notes: 'Kegiatan edukasi warga' },
{ id: 4, tanggal: '2024-11-14', title: 'Biaya Listrik Gudang', amount: 600000, notes: 'Tagihan bulan berjalan' }
];
let latestExpenseId = manualExpenses.length;
let currentYear = getLatestYear();
let laporanTable;
$(document).ready(function () {
populateYearFilter();
laporanTable = new DataTable('#laporanTable', {
data: [],
scrollX: true,
autoWidth: false,
columns: [
{ data: 'no' },
{ data: 'bulan' },
{ data: 'pembelian', render: (d) => formatCurrency(d) },
{ data: 'penjualan', render: (d) => formatCurrency(d) },
{ data: 'pengeluaran', render: (d) => formatCurrency(d) },
{
data: 'labaRugi',
render: (d, t, row) => {
const color = row.isProfit ? 'text-green-600' : 'text-red-600';
return `<span class="${color} font-semibold">${formatCurrency(d)}</span>`;
}
},
{ data: 'saldo', render: (d) => formatCurrency(d) }
]
});
refreshTable();
$('#filter_tahun').on('change', function () {
currentYear = parseInt($(this).val(), 10) || getLatestYear();
refreshTable();
});
$('#formPengeluaran').on('submit', function (e) {
e.preventDefault();
const formData = new FormData(this);
const tanggal = formData.get('Tanggal');
const nama = formData.get('NamaPengeluaran')?.trim();
const nominal = Number(formData.get('Nominal'));
const keterangan = formData.get('Keterangan')?.trim() || '';
if (!tanggal || !nama || isNaN(nominal) || nominal <= 0) {
Swal.fire('Gagal', 'Mohon lengkapi semua data pengeluaran.', 'error');
return;
}
manualExpenses.push({
id: ++latestExpenseId,
tanggal,
title: nama,
amount: nominal,
notes: keterangan
});
populateYearFilter();
refreshTable();
Swal.fire({
title: 'Berhasil',
text: 'Pengeluaran berhasil ditambahkan',
icon: 'success',
confirmButtonText: 'OK',
buttonsStyling: false,
customClass: {
confirmButton: 'btn bg-green-800 text-white hover:bg-green-900 rounded-full'
}
}).then(() => {
document.getElementById('formPengeluaran').reset();
closePengeluaranModal();
});
});
});
function refreshTable() {
const rows = buildRows(currentYear);
laporanTable.clear();
laporanTable.rows.add(rows);
laporanTable.draw();
updateTotalSaldo(rows.length ? rows[rows.length - 1].saldo : 0);
}
function buildRows(year) {
const formatter = new Intl.DateTimeFormat('id-ID', { month: 'long' });
let saldoAkumulatif = 0;
const rows = [];
for (let month = 1; month <= 12; month++) {
const baseData = monthlyTransactions.find((t) => t.year === year && t.month === month);
const pembelian = baseData ? baseData.pembelian : 0;
const penjualan = baseData ? baseData.penjualan : 0;
const pengeluaran = manualExpenses
.filter((e) => new Date(e.tanggal).getFullYear() === year && new Date(e.tanggal).getMonth() + 1 === month)
.reduce((sum, e) => sum + e.amount, 0);
const labaRugi = penjualan - (pembelian + pengeluaran);
saldoAkumulatif += labaRugi;
rows.push({
no: month,
bulan: `${formatter.format(new Date(year, month - 1, 1))} ${year}`,
pembelian,
penjualan,
pengeluaran,
labaRugi,
saldo: saldoAkumulatif,
isProfit: labaRugi >= 0
});
}
return rows;
}
function populateYearFilter() {
const select = $('#filter_tahun');
const years = Array.from(new Set([
...monthlyTransactions.map((t) => t.year),
...manualExpenses.map((e) => new Date(e.tanggal).getFullYear())
])).sort((a, b) => b - a);
if (!years.includes(currentYear)) {
currentYear = years[0] || new Date().getFullYear();
}
select.empty();
years.forEach((year) => {
const option = $('<option></option>').attr('value', year).text(year);
if (year === currentYear) {
option.attr('selected', 'selected');
}
select.append(option);
});
}
function getLatestYear() {
const allYears = [
...monthlyTransactions.map((t) => t.year),
...manualExpenses.map((e) => new Date(e.tanggal).getFullYear())
];
return allYears.length ? Math.max(...allYears) : new Date().getFullYear();
}
function formatCurrency(value) {
const number = Number(value) || 0;
return new Intl.NumberFormat('id-ID', { style: 'currency', currency: 'IDR', minimumFractionDigits: 0 }).format(number);
}
function updateTotalSaldo(value) {
$('#totalSaldoLabel').text(formatCurrency(value));
}
function closePengeluaranModal() {
document.getElementById('formPengeluaran').reset();
modal_pengeluaran.close();
}
</script>
}