Spaces:
Running
Running
| // ======================= | |
| // Load members into accounting dropdown | |
| // ======================= | |
| async function loadMembersForAccounting() { | |
| try { | |
| const res = await fetch('/api/members'); | |
| if (!res.ok) throw new Error('Failed to fetch members'); | |
| const members = await res.json(); | |
| const select = document.getElementById('memberSelect'); | |
| select.innerHTML = '<option value="">Select Member</option>'; | |
| members.forEach(m => { | |
| const opt = document.createElement('option'); | |
| opt.value = m.id; | |
| opt.textContent = `${m.name} (#${m.id})`; | |
| select.appendChild(opt); | |
| }); | |
| } catch (error) { | |
| console.error(error); | |
| } | |
| } | |
| // ======================= | |
| // Show/hide payerName input based on payer type | |
| // ======================= | |
| document.getElementById('payerType').addEventListener('change', (e) => { | |
| const isNonMember = e.target.value === 'non-member'; | |
| document.getElementById('payerName').style.display = isNonMember ? 'inline-block' : 'none'; | |
| }); | |
| // ======================= | |
| // Load transactions and apply filters | |
| // ======================= | |
| async function loadTransactions() { | |
| try { | |
| const res = await fetch('/api/accounting'); | |
| if (!res.ok) throw new Error('Failed to fetch transactions'); | |
| let transactions = await res.json(); | |
| // Apply filters (assumes filters object exists with start/end properties) | |
| if (filters?.start || filters?.end) { | |
| transactions = transactions.filter(tx => { | |
| const txDate = new Date(tx.month + "-01"); | |
| const start = filters.start ? new Date(filters.start + "-01") : null; | |
| const end = filters.end ? new Date(filters.end + "-01") : null; | |
| return (!start || txDate >= start) && (!end || txDate <= end); | |
| }); | |
| } | |
| 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> | |
| <td> | |
| <button class="btn btn-sm btn-primary" onclick="openActionModal(${t.id})">Actions</button> | |
| </td> | |
| </tr> | |
| `).join(''); | |
| // Update summary and charts with the filtered transactions | |
| updateAccountingSummary(transactions); | |
| renderMonthlyChart(transactions); | |
| renderIncomeExpenseChart(transactions); | |
| } catch (error) { | |
| console.error(error); | |
| } | |
| } | |
| // ======================= | |
| // Update accounting summary table | |
| // ======================= | |
| async function updateAccountingSummary(transactionsParam = null) { | |
| try { | |
| const transactions = transactionsParam || await (await fetch('/api/accounting')).json(); | |
| const summary = { | |
| contribution: 0, | |
| donation: 0, | |
| sale: 0, | |
| expense: 0, | |
| memberIncome: 0, | |
| nonMemberIncome: 0 | |
| }; | |
| transactions.forEach(tx => { | |
| if (tx.category === 'contribution') summary.contribution += tx.amount; | |
| else if (tx.category === 'donation') summary.donation += tx.amount; | |
| else if (tx.category === 'sale') summary.sale += tx.amount; | |
| else if (tx.category === 'expense') summary.expense += tx.amount; | |
| if (tx.payerType === 'member' && tx.category !== 'expense') summary.memberIncome += tx.amount; | |
| if (tx.payerType === 'non-member' && tx.category !== 'expense') summary.nonMemberIncome += tx.amount; | |
| }); | |
| const tbody = document.getElementById('summaryBody'); | |
| tbody.innerHTML = ` | |
| <tr class="income-summary"><td>Contribution (Income)</td><td>${summary.contribution}</td></tr> | |
| <tr class="income-summary"><td>Donation (Income)</td><td>${summary.donation}</td></tr> | |
| <tr class="income-summary"><td>Sales (Income)</td><td>${summary.sale}</td></tr> | |
| <tr class="income-summary"><td>Total Income from Members</td><td>${summary.memberIncome}</td></tr> | |
| <tr class="income-summary"><td>Total Income from Non-Members</td><td>${summary.nonMemberIncome}</td></tr> | |
| <tr class="expense-summary"><td>Total Expenses</td><td>${summary.expense}</td></tr> | |
| <tr class="income-summary"><td>Net Total</td><td>${(summary.contribution + summary.donation + summary.sale) - summary.expense}</td></tr> | |
| `; | |
| } catch (error) { | |
| console.error(error); | |
| } | |
| } | |
| // ======================= | |
| // Handle transaction form submission | |
| // ======================= | |
| document.getElementById('transactionForm').addEventListener('submit', async (e) => { | |
| e.preventDefault(); | |
| const category = document.getElementById('transactionCategory').value; | |
| const transaction = { | |
| payerType: document.getElementById('payerType').value, | |
| memberId: document.getElementById('memberSelect').value || null, | |
| payerName: document.getElementById('payerName').value || null, | |
| month: document.getElementById('transactionMonth').value, | |
| amount: parseFloat(document.getElementById('transactionAmount').value), | |
| category, | |
| type: category === 'expense' ? 'expense' : 'income', // additional field for convenience | |
| description: document.getElementById('transactionDesc').value.trim() | |
| }; | |
| try { | |
| const res = await fetch('/api/accounting', { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify(transaction) | |
| }); | |
| if (!res.ok) throw new Error('Failed to add transaction'); | |
| document.getElementById('transactionForm').reset(); | |
| loadTransactions(); | |
| updateAccountingSummary(); | |
| renderMonthlyChart(); | |
| } catch (error) { | |
| console.error(error); | |
| alert('Failed to save transaction'); | |
| } | |
| }); | |
| // ======================= | |
| // Export accounting data as JSON | |
| // ======================= | |
| document.getElementById('exportAccounting').addEventListener('click', () => { | |
| window.location.href = '/api/accounting/export'; | |
| }); | |
| // ======================= | |
| // Initial data load | |
| // ======================= | |
| loadMembersForAccounting(); | |
| loadTransactions(); | |
| updateAccountingSummary(); | |
| let selectedTransaction = null; | |
| // Open modal and load transaction data | |
| window.openActionModal = async function (id) { | |
| const res = await fetch('/api/accounting'); | |
| const transactions = await res.json(); | |
| selectedTransaction = transactions.find(t => t.id === id); | |
| if (!selectedTransaction) return alert('Transaction not found'); | |
| // Fill the form | |
| document.getElementById('editTransactionId').value = selectedTransaction.id; | |
| document.getElementById('editTransactionMonth').value = selectedTransaction.month; | |
| document.getElementById('editTransactionAmount').value = selectedTransaction.amount; | |
| document.getElementById('editTransactionCategory').value = selectedTransaction.category; | |
| document.getElementById('editTransactionDesc').value = selectedTransaction.description || ''; | |
| // Show modal | |
| document.getElementById('transactionModal').classList.remove('hidden'); | |
| }; | |
| window.closeModal = function () { | |
| document.getElementById('transactionModal').classList.add('hidden'); | |
| selectedTransaction = null; | |
| }; | |
| // Save edited transaction | |
| document.getElementById('editTransactionForm').addEventListener('submit', async function (e) { | |
| e.preventDefault(); | |
| const id = document.getElementById('editTransactionId').value; | |
| const updatedTx = { | |
| ...selectedTransaction, | |
| month: document.getElementById('editTransactionMonth').value, | |
| amount: parseFloat(document.getElementById('editTransactionAmount').value), | |
| category: document.getElementById('editTransactionCategory').value, | |
| description: document.getElementById('editTransactionDesc').value | |
| }; | |
| const res = await fetch(`/api/accounting/${id}`, { | |
| method: 'PUT', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify(updatedTx) | |
| }); | |
| if (!res.ok) { | |
| alert('Failed to update transaction'); | |
| return; | |
| } | |
| closeModal(); | |
| loadTransactions(); | |
| }); | |
| // Delete transaction | |
| window.deleteTransaction = async function () { | |
| const id = document.getElementById('editTransactionId').value; | |
| if (!confirm('Are you sure you want to delete this transaction?')) return; | |
| const res = await fetch(`/api/accounting/${id}`, { | |
| method: 'DELETE' | |
| }); | |
| if (!res.ok) { | |
| alert('Failed to delete transaction'); | |
| return; | |
| } | |
| closeModal(); | |
| loadTransactions(); | |
| }; | |