Spaces:
Running
Running
| ////////////////////////////////////////////////////////// | |
| 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); | |
| } | |
| // Call this on page load and after adding a transaction | |
| renderMonthlyChart(); | |
| ////////////////////////////////////////// | |
| let filters = { start: null, end: null }; | |
| async function loadTransactions() { | |
| const res = await fetch('/api/accounting'); | |
| let transactions = await res.json(); | |
| // Apply filter if set | |
| 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(''); | |
| // Refresh charts/summary with filtered data | |
| 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"); | |
| // Title | |
| pdf.setFontSize(18); | |
| pdf.text("Church Accounting Report", 40, 40); | |
| // Capture summary table | |
| 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(); | |
| // Capture monthly chart | |
| 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(); | |
| // Capture income vs expense pie | |
| 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); | |
| // Save file | |
| pdf.save("accounting-report.pdf"); | |
| }); |