231 lines
10 KiB
PHP
231 lines
10 KiB
PHP
<?= $this->extend('layout/management_layout') ?>
|
|
<?= $this->section('content') ?>
|
|
|
|
<div class="container-fluid mt-4">
|
|
<h2 class="text-center mt-4 mb-3">Financial Report Summary</h2>
|
|
<!-- Toolbar: controls above the table -->
|
|
<div class="d-flex align-items-center gap-2 my-3 flex-nowrap w-100 overflow-auto" style="white-space: nowrap;">
|
|
<!-- School year filter -->
|
|
<form id="summaryYearFilter" class="d-flex align-items-center gap-2 flex-nowrap me-auto" method="get" action="<?= site_url('financial-report/financialReportSummary') ?>" style="white-space: nowrap;">
|
|
<label class="form-label mb-0 me-2">School year</label>
|
|
<select name="school_year" class="form-select form-select-sm rounded-pill px-3" style="min-width: 180px;">
|
|
<?php foreach (($schoolYears ?? []) as $sy): ?>
|
|
<option value="<?= esc($sy) ?>" <?= (string)$schoolYear === (string)$sy ? 'selected' : '' ?>><?= esc($sy) ?></option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</form>
|
|
<!-- Action buttons -->
|
|
<div class="d-flex align-items-center gap-2 flex-nowrap flex-shrink-0" style="white-space: nowrap;">
|
|
<a href="<?= base_url('reports/downloadFinancialReport') ?>" class="btn btn-primary">Download PDF</a>
|
|
<a href="<?= base_url('/payment/financial_report') ?>" class="btn btn-secondary">Back To Detailed Report</a>
|
|
</div>
|
|
</div>
|
|
<p id="summaryPeriod"></p>
|
|
|
|
<div class="table-responsive">
|
|
<table class="table w-100" id="reportTable">
|
|
<thead>
|
|
<tr>
|
|
<th>Description</th>
|
|
<th class="text-right">Amount</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="summaryBody">
|
|
<tr><td>Total Charges</td><td class="text-right" id="sumCharges">$0.00</td></tr>
|
|
<tr><td>Event Fees</td><td class="text-right" id="sumEventFees">$0.00</td></tr>
|
|
<tr><td>Total Extra Charges</td><td class="text-right" id="sumExtraCharges">$0.00</td></tr>
|
|
<tr><td>Total Discounts</td><td class="text-right" id="sumDiscounts">$0.00</td></tr>
|
|
<tr><td>Total Refunds</td><td class="text-right" id="sumRefunds">$0.00</td></tr>
|
|
<tr><td>Total Expenses</td><td class="text-right" id="sumExpenses">$0.00</td></tr>
|
|
<tr><td>Total Reimbursements</td><td class="text-right" id="sumReimb">$0.00</td></tr>
|
|
<tr><td>Donation to School (Masjid & Donation)</td><td class="text-right" id="sumDonationToSchool">$0.00</td></tr>
|
|
<tr class="table-success"><td>Net Amount (Earned Income)</td><td class="text-right font-weight-bold" id="sumNet">$0.00</td></tr>
|
|
<tr class="table-info"><td>Amount Collected (Paid)</td><td class="text-right font-weight-bold" id="sumCollected">$0.00</td></tr>
|
|
<tr class="table-warning"><td>Amount Unpaid (Outstanding)</td><td class="text-right font-weight-bold" id="sumUnpaid">$0.00</td></tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<div class="my-4">
|
|
<h3>Summary Graph</h3>
|
|
<canvas id="summaryChart" style="max-height:300px;"></canvas>
|
|
</div>
|
|
|
|
<div class="my-4">
|
|
<div class="row g-3">
|
|
<div class="col-12 col-lg-6">
|
|
<div class="h-100 p-3 border rounded">
|
|
<h3 class="h5">Collected vs Outstanding</h3>
|
|
<canvas id="collectedChart" style="max-height:320px; width:100%;"></canvas>
|
|
</div>
|
|
</div>
|
|
<div class="col-12 col-lg-6">
|
|
<div class="h-100 p-3 border rounded">
|
|
<h3 class="h5">Expenses out of Net Amount</h3>
|
|
<canvas id="netExpenseChart" style="max-height:320px; width:100%;"></canvas>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
<?= $this->endSection() ?>
|
|
|
|
<?= $this->section('scripts') ?>
|
|
|
|
<script>
|
|
function fmt(n){ return '$' + Number(n||0).toFixed(2); }
|
|
const initialSummaryData = {
|
|
schoolYear: <?= json_encode((string)($schoolYear ?? '')) ?>,
|
|
totalCharges: <?= json_encode((float)($totalCharges ?? 0)) ?>,
|
|
totalEventFees: <?= json_encode((float)($totalEventFees ?? 0)) ?>,
|
|
totalExtraCharges: <?= json_encode((float)($totalExtraCharges ?? 0)) ?>,
|
|
totalDiscounts: <?= json_encode((float)($totalDiscounts ?? 0)) ?>,
|
|
totalRefunds: <?= json_encode((float)($totalRefunds ?? 0)) ?>,
|
|
totalExpenses: <?= json_encode((float)($totalExpenses ?? 0)) ?>,
|
|
totalReimbursements: <?= json_encode((float)($totalReimbursements ?? 0)) ?>,
|
|
donationToSchool: <?= json_encode((float)($donationToSchool ?? 0)) ?>,
|
|
totalPaid: <?= json_encode((float)($totalPaid ?? 0)) ?>,
|
|
amountCollected: <?= json_encode((float)($amountCollected ?? 0)) ?>,
|
|
totalUnpaid: <?= json_encode((float)($totalUnpaid ?? 0)) ?>,
|
|
netAmount: <?= json_encode((float)($netAmount ?? 0)) ?>
|
|
};
|
|
|
|
function applySummaryData(d) {
|
|
document.getElementById('summaryPeriod').textContent = 'Report for School Year: ' + (d.schoolYear || '');
|
|
document.getElementById('sumCharges').textContent = fmt(d.totalCharges);
|
|
if (document.getElementById('sumEventFees')) {
|
|
document.getElementById('sumEventFees').textContent = fmt(d.totalEventFees || 0);
|
|
}
|
|
if (document.getElementById('sumExtraCharges')) {
|
|
document.getElementById('sumExtraCharges').textContent = fmt(d.totalExtraCharges || 0);
|
|
}
|
|
document.getElementById('sumDiscounts').textContent = fmt(d.totalDiscounts);
|
|
document.getElementById('sumRefunds').textContent = fmt(d.totalRefunds);
|
|
document.getElementById('sumExpenses').textContent = fmt(d.totalExpenses);
|
|
document.getElementById('sumReimb').textContent = fmt(d.totalReimbursements);
|
|
if (document.getElementById('sumDonationToSchool')) {
|
|
document.getElementById('sumDonationToSchool').textContent = fmt(d.donationToSchool);
|
|
}
|
|
document.getElementById('sumNet').textContent = fmt(d.netAmount);
|
|
document.getElementById('sumCollected').textContent = fmt(d.amountCollected || d.totalPaid || 0);
|
|
document.getElementById('sumUnpaid').textContent = fmt(d.totalUnpaid);
|
|
}
|
|
|
|
// Auto-submit school year on change and hydrate via API
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
const sel = document.querySelector('#summaryYearFilter select[name="school_year"]');
|
|
if (sel) sel.addEventListener('change', function(){ loadSummary(); });
|
|
applySummaryData(initialSummaryData);
|
|
renderCharts(initialSummaryData);
|
|
loadSummary();
|
|
});
|
|
|
|
let __sumSeq = 0;
|
|
function loadSummary(){
|
|
const mySeq = ++__sumSeq;
|
|
const sel = document.querySelector('#summaryYearFilter select[name="school_year"]');
|
|
const params = new URLSearchParams();
|
|
if (sel && sel.value) params.append('school_year', sel.value);
|
|
const baseUrl = '<?= site_url('financial-report/financialReportSummary') ?>';
|
|
const url = baseUrl + (params.toString() ? ('?' + params.toString() + '&format=json') : '?format=json');
|
|
fetch(url, { headers: { 'Accept': 'application/json' }})
|
|
.then(r => r.json())
|
|
.then(d => {
|
|
if (mySeq !== __sumSeq) return; // stale response
|
|
if (!d || d.ok !== true) return;
|
|
applySummaryData(d);
|
|
renderCharts(d);
|
|
})
|
|
.catch(()=>{});
|
|
}
|
|
|
|
function renderCharts(d){
|
|
if (!window.Chart) return;
|
|
// Destroy existing if present
|
|
if (window._summaryChart) { window._summaryChart.destroy(); }
|
|
if (window._collectedChart) { window._collectedChart.destroy(); }
|
|
if (window._netExpenseChart) { window._netExpenseChart.destroy(); }
|
|
const summaryCtx = document.getElementById('summaryChart').getContext('2d');
|
|
window._summaryChart = new Chart(summaryCtx, {
|
|
type: 'bar',
|
|
data: { labels: ['Charges','Event Fees','Paid','Unpaid','Discounts','Refunds','Expenses','Reimbursements','Net'],
|
|
datasets: [{ label:'Amount (USD)', data:[
|
|
d.totalCharges||0, d.totalEventFees||0, d.totalPaid||0, d.totalUnpaid||0, d.totalDiscounts||0, d.totalRefunds||0, d.totalExpenses||0, d.totalReimbursements||0, d.netAmount||0
|
|
], backgroundColor: ['#007bff','#6610f2','#28a745','#ffc107','#17a2b8','#ffc107','#dc3545','#6f42c1','#20c997']}] },
|
|
options: { responsive:true, scales:{ y:{ beginAtZero:true }}}
|
|
});
|
|
|
|
const collectedCtx = document.getElementById('collectedChart').getContext('2d');
|
|
window._collectedChart = new Chart(collectedCtx, {
|
|
type: 'pie',
|
|
data: {
|
|
labels: ['Amount Collected (Paid)', 'Amount Outstanding (Unpaid)'],
|
|
datasets: [{
|
|
data: [ d.amountCollected || d.totalPaid || 0, d.totalUnpaid || 0 ],
|
|
backgroundColor: ['#28a745', '#ffc107'],
|
|
borderWidth: 1
|
|
}]
|
|
},
|
|
options: { responsive:true, plugins:{ legend:{ position:'bottom' } } }
|
|
});
|
|
|
|
const netExpenseCtx = document.getElementById('netExpenseChart').getContext('2d');
|
|
const netAmount = Number(d.netAmount || 0);
|
|
const totalExpenses = Number(d.totalExpenses || 0);
|
|
const expensePortion = Math.max(0, Math.min(totalExpenses, netAmount));
|
|
const remainingNet = Math.max(0, netAmount - expensePortion);
|
|
const expensePercent = netAmount > 0 ? ((totalExpenses / netAmount) * 100).toFixed(1) : '0.0';
|
|
const remainingPercent = netAmount > 0 ? ((remainingNet / netAmount) * 100).toFixed(1) : '0.0';
|
|
const netExpenseValues = [expensePortion, remainingNet];
|
|
const netExpenseLabels = [
|
|
'Expenses (' + expensePercent + '%)',
|
|
'Remaining Net (' + remainingPercent + '%)'
|
|
];
|
|
window._netExpenseChart = new Chart(netExpenseCtx, {
|
|
type: 'pie',
|
|
data: {
|
|
labels: netExpenseLabels,
|
|
datasets: [{
|
|
data: netExpenseValues,
|
|
backgroundColor: ['#20c997', '#dc3545'],
|
|
borderWidth: 1
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
plugins: {
|
|
legend: { position: 'bottom' },
|
|
tooltip: {
|
|
callbacks: {
|
|
label: function(context) {
|
|
if (context.dataIndex === 0) {
|
|
return 'Expenses: ' + fmt(totalExpenses) + ' (' + expensePercent + '% of net amount)';
|
|
}
|
|
return 'Remaining Net: ' + fmt(remainingNet) + ' (' + remainingPercent + '%)';
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
</script>
|
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
|
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
if (window.jQuery && jQuery.fn && jQuery.fn.DataTable) {
|
|
jQuery('#reportTable').DataTable({
|
|
paging: false,
|
|
searching: false,
|
|
info: false,
|
|
ordering: false
|
|
});
|
|
}
|
|
});
|
|
</script>
|
|
<?= $this->endSection() ?>
|