Online Legal Expense Tracker

Online Legal Expense Tracker

Monitor, categorize, and analyze your legal spending with ease.

Total Spend (YTD)

$0

Active Matters

0

Avg. Cost / Matter

$0

Highest Expense

$0

Recent Expenses

Spend by Category

Date Matter Category Amount Actions

Expense Categories

    Legal Matters

      Service Providers

        ${e.matter}

        ${e.description}

        ${formatCurrency(e.amount)}

        `; }); if(recentList.innerHTML === '') recentList.innerHTML = `

        No recent expenses logged.

        `; renderCategoryChart(); } function renderCategoryChart() { const ctx = document.getElementById('categoryChart').getContext('2d'); const spendByCategory = state.categories.reduce((acc, cat) => { acc[cat] = state.expenses.filter(e => e.category === cat).reduce((sum, e) => sum + e.amount, 0); return acc; }, {}); const chartData = { labels: Object.keys(spendByCategory), datasets: [{ data: Object.values(spendByCategory), backgroundColor: ['#3B82F6', '#10B981', '#F59E0B', '#EF4444', '#8B5CF6'], borderColor: '#FFFFFF', borderWidth: 2 }] }; if (categoryChart) { categoryChart.data = chartData; categoryChart.update(); } else { categoryChart = new Chart(ctx, { type: 'doughnut', data: chartData, options: { responsive: true, plugins: { legend: { position: 'bottom' } } } }); } } function renderExpenseTable() { const tableBody = document.getElementById('expense-table-body'); tableBody.innerHTML = ''; if(state.expenses.length === 0) { tableBody.innerHTML = `No expenses logged.`; return; } state.expenses.sort((a,b) => new Date(b.date) - new Date(a.date)).forEach(e => { tableBody.innerHTML += ` ${e.date} ${e.matter} ${e.category} ${formatCurrency(e.amount)} `; }); } function renderConfigLists() { const configMap = { category: state.categories, matter: state.matters, provider: state.providers }; for (const [type, items] of Object.entries(configMap)) { document.getElementById(`${type}-list`).innerHTML = items.map(item => `
      • ${item}
      • `).join(''); } } function updateSelectOptions() { const selects = { 'expense-category': state.categories, 'expense-matter': state.matters, 'expense-provider': state.providers }; for (const [id, options] of Object.entries(selects)) { const el = document.getElementById(id); if(el) el.innerHTML = options.map(o => ``).join(''); } } // --- EVENT HANDLERS --- Object.keys({category:0, matter:0, provider:0}).forEach(type => { document.getElementById(`${type}-form`).addEventListener('submit', (e) => { e.preventDefault(); const input = e.target.querySelector('input'); const value = input.value.trim(); if (value && !state[`${type}s`].includes(value)) { state[`${type}s`].push(value); input.value = ''; saveState(); renderAll(); } }); }); document.getElementById('expense-form').addEventListener('submit', (e) => { e.preventDefault(); const id = document.getElementById('expense-id').value; const data = { date: document.getElementById('expense-date').value, amount: parseFloat(document.getElementById('expense-amount').value), matter: document.getElementById('expense-matter').value, category: document.getElementById('expense-category').value, provider: document.getElementById('expense-provider').value, description: document.getElementById('expense-description').value, }; if (id) { const index = state.expenses.findIndex(ex => ex.id == id); if (index > -1) state.expenses[index] = { ...state.expenses[index], ...data }; } else { data.id = state.nextExpenseId++; state.expenses.push(data); } saveState(); renderAll(); app.closeExpenseModal(); }); // --- GLOBAL APP OBJECT --- window.app = { switchTab: (tabName) => { state.currentTab = tabName; document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active', 'border-blue-500', 'text-blue-600')); document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active')); document.getElementById(`tab-btn-${tabName}`).classList.add('active', 'border-blue-500', 'text-blue-600'); document.getElementById(`tab-content-${tabName}`).classList.add('active'); }, navigateTabs: (dir) => { const i = tabOrder.indexOf(state.currentTab); const nextI = dir === 'next' ? (i + 1) % tabOrder.length : (i - 1 + tabOrder.length) % tabOrder.length; app.switchTab(tabOrder[nextI]); }, openExpenseModal: (id = null) => { document.getElementById('expense-form').reset(); document.getElementById('expense-id').value = ''; if (id) { const expense = state.expenses.find(e => e.id === id); if(expense) { document.getElementById('modal-title').textContent = 'Edit Expense'; Object.keys(expense).forEach(key => { const el = document.getElementById(`expense-${key === 'id' ? 'id' : key}`); if (el) el.value = expense[key]; }); } } else { document.getElementById('modal-title').textContent = 'Add New Expense'; } document.getElementById('expense-modal').classList.remove('hidden'); }, closeExpenseModal: () => document.getElementById('expense-modal').classList.add('hidden'), editExpense: (id) => app.openExpenseModal(id), deleteExpense: (id) => { if (confirm('Are you sure you want to delete this expense?')) { state.expenses = state.expenses.filter(e => e.id !== id); saveState(); renderAll(); } }, deleteConfigItem: (type, value) => { const keyMap = { category: 'category', matter: 'matter', provider: 'provider' }; if (state.expenses.some(e => e[keyMap[type]] === value)) { alert(`Cannot delete. This ${type} is in use.`); return; } state[`${type}s`] = state[`${type}s`].filter(item => item !== value); saveState(); renderAll(); }, generatePDF: () => { const { jsPDF } = window.jspdf; const doc = new jsPDF(); if (typeof doc.autoTable !== 'function') { console.error("jsPDF-AutoTable not loaded."); return; } doc.setFontSize(18); doc.text("Legal Expense Report", 14, 22); doc.setFontSize(11); doc.text(`Generated: ${new Date().toLocaleDateString()}`, 14, 30); doc.autoTable({ startY: 40, theme: 'striped', body: [ ['Total Spend (YTD)', document.getElementById('stat-total-spend').textContent], ['Active Matters', document.getElementById('stat-active-matters').textContent], ['Average Cost per Matter', document.getElementById('stat-avg-cost').textContent], ] }); const tableData = state.expenses.map(e => [ e.date, e.matter, e.category, e.provider, formatCurrency(e.amount) ]); doc.autoTable({ startY: doc.lastAutoTable.finalY + 15, theme: 'grid', head: [['Date', 'Matter', 'Category', 'Provider', 'Amount']], body: tableData, }); doc.save('Legal-Expense-Report.pdf'); } }; // Initial Load loadState(); renderAll(); app.switchTab(state.currentTab); });
        Scroll to Top