|
|
|
|
|
let monthlyChart;
|
|
|
|
async function renderMonthlyChart(transactionsParam = null) {
|
|
const transactions = transactionsParam || await (await fetch('/api/accounting')).json();
|
|
|
|
const contribution = Array(12).fill(0);
|
|
const donation = Array(12).fill(0);
|
|
const sale = Array(12).fill(0);
|
|
const expenses = Array(12).fill(0);
|
|
|
|
transactions.forEach(tx => {
|
|
if (!tx.month) return;
|
|
const parts = tx.month.split('-');
|
|
if (parts.length !== 2) return;
|
|
const monthIndex = parseInt(parts[1], 10) - 1;
|
|
if (monthIndex < 0 || monthIndex > 11) return;
|
|
|
|
if (tx.category === 'contribution') contribution[monthIndex] += tx.amount;
|
|
else if (tx.category === 'donation') donation[monthIndex] += tx.amount;
|
|
else if (tx.category === 'sale') sale[monthIndex] += tx.amount;
|
|
else if (tx.category === 'expense') expenses[monthIndex] += tx.amount;
|
|
});
|
|
|
|
const ctx = document.getElementById('monthlyChart').getContext('2d');
|
|
if (monthlyChart) monthlyChart.destroy();
|
|
|
|
monthlyChart = new Chart(ctx, {
|
|
type: 'bar',
|
|
data: {
|
|
labels: ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'],
|
|
datasets: [
|
|
{ label: 'Contribution', data: contribution, backgroundColor: 'rgba(75,192,192,0.7)' },
|
|
{ label: 'Donation', data: donation, backgroundColor: 'rgba(54,162,235,0.7)' },
|
|
{ label: 'Sale', data: sale, backgroundColor: 'rgba(255,206,86,0.7)' },
|
|
{ label: 'Expense', data: expenses, backgroundColor: 'rgba(255,99,132,0.7)' }
|
|
]
|
|
},
|
|
options: { responsive: true, plugins: { legend: { position: 'top' }, title: { display: true, text: 'Monthly Income & Expenses by Category' } }, scales: { y: { beginAtZero: true } } }
|
|
});
|
|
}
|
|
|
|
async function loadTransactions() {
|
|
const res = await fetch('/api/accounting', { cache: 'no-store' });
|
|
let transactions = await res.json();
|
|
|
|
if (filters.start || filters.end) {
|
|
const startDate = filters.start ? new Date(filters.start + "-01") : null;
|
|
const endDate = filters.end ? new Date(filters.end + "-01") : null;
|
|
|
|
transactions = transactions.filter(tx => {
|
|
if (!tx.month) return false;
|
|
const txDate = new Date(tx.month + "-01");
|
|
return (!startDate || txDate >= startDate) && (!endDate || txDate <= endDate);
|
|
});
|
|
}
|
|
|
|
const tbody = document.getElementById('accountingBody');
|
|
tbody.innerHTML = transactions.map((t, i) => `
|
|
<tr class="${t.category === 'expense' ? 'expense' : 'income'}">
|
|
<td>${i + 1}</td>
|
|
<td>${t.payerType === 'member' ? t.memberId : t.payerName}</td>
|
|
<td>${t.payerType}</td>
|
|
<td>${t.month}</td>
|
|
<td>${t.amount.toFixed(2)}</td>
|
|
<td>${t.category}</td>
|
|
<td>${t.description || ''}</td>
|
|
</tr>
|
|
`).join('');
|
|
|
|
updateAccountingSummary(transactions);
|
|
renderMonthlyChart(transactions);
|
|
renderIncomeExpenseChart(transactions);
|
|
}
|
|
|
|
|
|
|
|
|
|
renderMonthlyChart();
|
|
|
|
let filters = { start: null, end: null };
|
|
|
|
async function loadTransactions() {
|
|
const res = await fetch('/api/accounting');
|
|
let transactions = await res.json();
|
|
|
|
|
|
if (filters.start || filters.end) {
|
|
transactions = transactions.filter(tx => {
|
|
if (!tx.month) return false;
|
|
const txDate = new Date(tx.month + "-01");
|
|
const startDate = filters.start ? new Date(filters.start + "-01") : null;
|
|
const endDate = filters.end ? new Date(filters.end + "-01") : null;
|
|
|
|
return (!startDate || txDate >= startDate) &&
|
|
(!endDate || txDate <= endDate);
|
|
});
|
|
}
|
|
|
|
const tbody = document.getElementById('accountingBody');
|
|
tbody.innerHTML = transactions
|
|
.map((t, i) => `
|
|
<tr class="${t.category === 'expense' ? 'expense' : 'income'}">
|
|
<td>${i + 1}</td>
|
|
<td>${t.payerType === 'member' ? t.memberId : t.payerName}</td>
|
|
<td>${t.payerType}</td>
|
|
<td>${t.month}</td>
|
|
<td>${t.amount}</td>
|
|
<td>${t.category}</td>
|
|
<td>${t.description || ''}</td>
|
|
</tr>
|
|
`).join('');
|
|
|
|
|
|
updateAccountingSummary(transactions);
|
|
renderMonthlyChart(transactions);
|
|
renderIncomeExpenseChart(transactions);
|
|
}
|
|
|
|
let incomeExpenseChart;
|
|
|
|
async function renderIncomeExpenseChart(transactionsParam = null) {
|
|
const transactions = transactionsParam || await (await fetch('/api/accounting')).json();
|
|
|
|
let totalIncome = 0, totalExpenses = 0;
|
|
|
|
transactions.forEach(tx => {
|
|
if (tx.category === 'expense') totalExpenses += tx.amount;
|
|
else totalIncome += tx.amount;
|
|
});
|
|
|
|
const ctx = document.getElementById('incomeExpenseChart').getContext('2d');
|
|
if (incomeExpenseChart) incomeExpenseChart.destroy();
|
|
|
|
incomeExpenseChart = new Chart(ctx, {
|
|
type: 'pie',
|
|
data: {
|
|
labels: ['Income', 'Expenses'],
|
|
datasets: [{
|
|
data: [totalIncome, totalExpenses],
|
|
backgroundColor: ['rgba(75,192,192,0.7)', 'rgba(255,99,132,0.7)']
|
|
}]
|
|
},
|
|
options: { responsive: true, plugins: { legend: { position: 'top' }, title: { display: true, text: 'Income vs Expenses' } } }
|
|
});
|
|
}
|
|
|
|
|
|
document.getElementById('applyFilters').addEventListener('click', () => {
|
|
filters.start = document.getElementById('filterStart').value || null;
|
|
filters.end = document.getElementById('filterEnd').value || null;
|
|
loadTransactions();
|
|
});
|
|
|
|
document.getElementById('clearFilters').addEventListener('click', () => {
|
|
filters.start = null;
|
|
filters.end = null;
|
|
document.getElementById('filterStart').value = '';
|
|
document.getElementById('filterEnd').value = '';
|
|
loadTransactions();
|
|
});
|
|
|
|
|
|
document.getElementById("exportPdf").addEventListener("click", async () => {
|
|
const { jsPDF } = window.jspdf;
|
|
const pdf = new jsPDF("p", "pt", "a4");
|
|
|
|
|
|
pdf.setFontSize(18);
|
|
pdf.text("Church Accounting Report", 40, 40);
|
|
|
|
|
|
const summaryElement = document.getElementById("summaryTable");
|
|
const summaryCanvas = await html2canvas(summaryElement);
|
|
const summaryImg = summaryCanvas.toDataURL("image/png");
|
|
pdf.addImage(summaryImg, "PNG", 40, 60, 500, 0);
|
|
|
|
pdf.addPage();
|
|
|
|
|
|
const chartCanvas = document.getElementById("monthlyChart");
|
|
const chartImg = chartCanvas.toDataURL("image/png");
|
|
pdf.text("Monthly Breakdown", 40, 40);
|
|
pdf.addImage(chartImg, "PNG", 40, 60, 500, 300);
|
|
|
|
pdf.addPage();
|
|
|
|
|
|
const pieCanvas = document.getElementById("incomeExpenseChart");
|
|
const pieImg = pieCanvas.toDataURL("image/png");
|
|
pdf.text("Income vs Expense", 40, 40);
|
|
pdf.addImage(pieImg, "PNG", 40, 60, 400, 300);
|
|
|
|
|
|
pdf.save("accounting-report.pdf");
|
|
}); |