From 9d493ba82f79b6d1e8771387785fbd2a35bfd184 Mon Sep 17 00:00:00 2001 From: Alexander Zinn Date: Sun, 7 Dec 2025 12:19:38 -0500 Subject: [PATCH] Ran prettier --- frontend-web/README.md | 4 + .../src/components/AddAccountDialog.tsx | 34 +--- frontend-web/src/components/ErrorBoundary.tsx | 8 +- frontend-web/src/components/Layout.tsx | 13 +- .../src/components/dialogs/AddAssetDialog.tsx | 56 +++++-- .../components/dialogs/AddClientDialog.tsx | 64 ++++++-- .../components/dialogs/AddExpenseDialog.tsx | 81 ++++++--- .../components/dialogs/AddIncomeDialog.tsx | 72 +++++--- .../components/dialogs/AddInvoiceDialog.tsx | 93 +++++++---- .../components/dialogs/AddLiabilityDialog.tsx | 56 +++++-- .../dialogs/AddTransactionDialog.tsx | 62 +++++-- .../components/dialogs/EditAssetDialog.tsx | 32 ++-- .../components/dialogs/EditClientDialog.tsx | 67 +++----- .../dialogs/EditLiabilityDialog.tsx | 26 +-- .../dialogs/InvoiceDetailsDialog.tsx | 14 +- frontend-web/src/components/ui/button.tsx | 61 +++---- frontend-web/src/components/ui/card.tsx | 86 ++-------- frontend-web/src/components/ui/dialog.tsx | 115 ++++--------- frontend-web/src/components/ui/input.tsx | 16 +- frontend-web/src/components/ui/label.tsx | 17 +- frontend-web/src/components/ui/select.tsx | 143 +++++----------- frontend-web/src/components/ui/separator.tsx | 19 +-- frontend-web/src/components/ui/tooltip.tsx | 49 ++---- frontend-web/src/fonts.d.ts | 1 - frontend-web/src/index.css | 9 +- frontend-web/src/lib/formatters.ts | 2 +- frontend-web/src/lib/utils.ts | 6 +- frontend-web/src/pages/CashflowPage.tsx | 89 ++++++---- frontend-web/src/pages/ClientsPage.tsx | 28 ++-- frontend-web/src/pages/DebtsPage.tsx | 31 +++- frontend-web/src/pages/InvoicesPage.tsx | 43 ++--- frontend-web/src/pages/NetWorthPage.tsx | 50 ++++-- frontend-web/src/store/index.ts | 8 +- .../src/store/slices/cashflowSlice.ts | 155 +++++++++++++++--- frontend-web/src/store/slices/debtsSlice.ts | 26 +-- .../src/store/slices/invoicesSlice.ts | 50 +++--- .../src/store/slices/netWorthSlice.ts | 27 +-- frontend-web/src/store/store.ts | 4 +- 38 files changed, 934 insertions(+), 783 deletions(-) diff --git a/frontend-web/README.md b/frontend-web/README.md index 6979821..13b512f 100644 --- a/frontend-web/README.md +++ b/frontend-web/README.md @@ -5,24 +5,28 @@ A modern personal finance management application for tracking net worth, managin ## Features ### Net Worth Tracking + - Track assets (cash, investments, property, vehicles) - Track liabilities (mortgages, loans, credit cards) - Visualize net worth over time with charts - View asset allocation breakdown ### Debt Management + - Organize debts by custom categories - Track multiple accounts per category - Monitor paydown progress with visual indicators - View by category or individual account ### Invoicing + - Create and manage invoices - Track invoice status (draft, sent, paid, overdue) - Manage client database - View outstanding and paid totals ### Cashflow + - Track income sources with various frequencies - Categorize expenses (essential vs discretionary) - Monitor savings rate diff --git a/frontend-web/src/components/AddAccountDialog.tsx b/frontend-web/src/components/AddAccountDialog.tsx index 0d4b597..f09be4d 100644 --- a/frontend-web/src/components/AddAccountDialog.tsx +++ b/frontend-web/src/components/AddAccountDialog.tsx @@ -1,19 +1,6 @@ import {useState} from 'react'; -import { - Dialog, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, -} from '@/components/ui/dialog'; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from '@/components/ui/select'; +import {Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle} from '@/components/ui/dialog'; +import {Select, SelectContent, SelectItem, SelectTrigger, SelectValue} from '@/components/ui/select'; import {Button} from '@/components/ui/button'; import {Input} from '@/components/ui/input'; import {Label} from '@/components/ui/label'; @@ -38,7 +25,7 @@ export default function AddAccountDialog({open, onOpenChange}: AddAccountDialogP interestRate: '', minimumPayment: '', dueDay: '', - notes: '', + notes: '' }); const handleSubmit = (e: React.FormEvent) => { @@ -58,7 +45,7 @@ export default function AddAccountDialog({open, onOpenChange}: AddAccountDialogP dueDay: parseInt(formData.dueDay) || 1, notes: formData.notes || undefined, createdAt: now, - updatedAt: now, + updatedAt: now }; dispatch(addAccount(account)); @@ -73,7 +60,7 @@ export default function AddAccountDialog({open, onOpenChange}: AddAccountDialogP interestRate: '', minimumPayment: '', dueDay: '', - notes: '', + notes: '' }); }; @@ -86,9 +73,7 @@ export default function AddAccountDialog({open, onOpenChange}: AddAccountDialogP Add Debt Account - - Add a new debt account to track your payoff progress - + Add a new debt account to track your payoff progress
@@ -106,11 +91,7 @@ export default function AddAccountDialog({open, onOpenChange}: AddAccountDialogP
- updateField('categoryId', value)} required> @@ -236,4 +217,3 @@ export default function AddAccountDialog({open, onOpenChange}: AddAccountDialogP ); } - diff --git a/frontend-web/src/components/ErrorBoundary.tsx b/frontend-web/src/components/ErrorBoundary.tsx index cd5db21..b511d94 100644 --- a/frontend-web/src/components/ErrorBoundary.tsx +++ b/frontend-web/src/components/ErrorBoundary.tsx @@ -47,14 +47,10 @@ export class ErrorBoundary extends Component { Something went wrong -

- An unexpected error occurred. Please try refreshing the page. -

+

An unexpected error occurred. Please try refreshing the page.

{import.meta.env.DEV && this.state.error && (
-

- {this.state.error.message} -

+

{this.state.error.message}

)}
diff --git a/frontend-web/src/components/Layout.tsx b/frontend-web/src/components/Layout.tsx index 707a9cf..3f7ecd7 100644 --- a/frontend-web/src/components/Layout.tsx +++ b/frontend-web/src/components/Layout.tsx @@ -6,7 +6,7 @@ const navItems = [ {to: '/cashflow', label: 'Cashflow', icon: ArrowLeftRight}, {to: '/debts', label: 'Debts', icon: CreditCard}, {to: '/invoices', label: 'Invoices', icon: FileText}, - {to: '/clients', label: 'Clients', icon: Users}, + {to: '/clients', label: 'Clients', icon: Users} ]; export default function Layout() { @@ -27,16 +27,11 @@ export default function Layout() { to={item.to} className={({isActive}) => `flex h-9 items-center gap-3 rounded-lg px-2.5 transition-colors ${ - isActive - ? 'bg-accent text-foreground' - : 'text-muted-foreground hover:bg-accent/50 hover:text-foreground' + isActive ? 'bg-accent text-foreground' : 'text-muted-foreground hover:bg-accent/50 hover:text-foreground' }` - } - > + }> - - {item.label} - + {item.label} ))} diff --git a/frontend-web/src/components/dialogs/AddAssetDialog.tsx b/frontend-web/src/components/dialogs/AddAssetDialog.tsx index 83949c7..2d05a00 100644 --- a/frontend-web/src/components/dialogs/AddAssetDialog.tsx +++ b/frontend-web/src/components/dialogs/AddAssetDialog.tsx @@ -45,13 +45,15 @@ export default function AddAssetDialog({open, onOpenChange}: Props) { const valueNum = validatePositiveNumber(form.value); if (valueNum === null) return; - dispatch(addAsset({ - id: crypto.randomUUID(), - name: sanitizeString(form.name), - type: form.type as typeof assetTypes[number], - value: valueNum, - updatedAt: new Date().toISOString(), - })); + dispatch( + addAsset({ + id: crypto.randomUUID(), + name: sanitizeString(form.name), + type: form.type as (typeof assetTypes)[number], + value: valueNum, + updatedAt: new Date().toISOString() + }) + ); onOpenChange(false); setForm({name: '', type: '', value: ''}); setErrors({name: '', value: ''}); @@ -68,31 +70,57 @@ export default function AddAssetDialog({open, onOpenChange}: Props) {
- setForm({...form, name: e.target.value})} className="input-depth" required /> + setForm({...form, name: e.target.value})} + className="input-depth" + required + /> {errors.name &&

{errors.name}

}
- setForm({...form, value: e.target.value})} className="input-depth" required /> + setForm({...form, value: e.target.value})} + className="input-depth" + required + /> {errors.value &&

{errors.value}

}
- - + + ); } - diff --git a/frontend-web/src/components/dialogs/AddClientDialog.tsx b/frontend-web/src/components/dialogs/AddClientDialog.tsx index ffae686..8d51b07 100644 --- a/frontend-web/src/components/dialogs/AddClientDialog.tsx +++ b/frontend-web/src/components/dialogs/AddClientDialog.tsx @@ -16,15 +16,17 @@ export default function AddClientDialog({open, onOpenChange}: Props) { const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); - dispatch(addClient({ - id: crypto.randomUUID(), - name: form.name, - email: form.email, - phone: form.phone || undefined, - company: form.company || undefined, - address: form.address || undefined, - createdAt: new Date().toISOString(), - })); + dispatch( + addClient({ + id: crypto.randomUUID(), + name: form.name, + email: form.email, + phone: form.phone || undefined, + company: form.company || undefined, + address: form.address || undefined, + createdAt: new Date().toISOString() + }) + ); onOpenChange(false); setForm({name: '', email: '', phone: '', company: '', address: ''}); }; @@ -40,15 +42,36 @@ export default function AddClientDialog({open, onOpenChange}: Props) {
- setForm({...form, name: e.target.value})} className="input-depth" required /> + setForm({...form, name: e.target.value})} + className="input-depth" + required + />
- setForm({...form, email: e.target.value})} className="input-depth" required /> + setForm({...form, email: e.target.value})} + className="input-depth" + required + />
- setForm({...form, company: e.target.value})} className="input-depth" /> + setForm({...form, company: e.target.value})} + className="input-depth" + />
@@ -56,16 +79,25 @@ export default function AddClientDialog({open, onOpenChange}: Props) {
- setForm({...form, address: e.target.value})} className="input-depth" /> + setForm({...form, address: e.target.value})} + className="input-depth" + />
- - + + ); } - diff --git a/frontend-web/src/components/dialogs/AddExpenseDialog.tsx b/frontend-web/src/components/dialogs/AddExpenseDialog.tsx index 3dd1154..810920d 100644 --- a/frontend-web/src/components/dialogs/AddExpenseDialog.tsx +++ b/frontend-web/src/components/dialogs/AddExpenseDialog.tsx @@ -20,17 +20,19 @@ export default function AddExpenseDialog({open, onOpenChange}: Props) { const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); - dispatch(addExpense({ - id: crypto.randomUUID(), - name: form.name, - amount: parseFloat(form.amount) || 0, - frequency: form.frequency as typeof frequencies[number], - category: form.category, - nextDate: new Date().toISOString().split('T')[0], - isActive: true, - isEssential: form.isEssential, - createdAt: new Date().toISOString(), - })); + dispatch( + addExpense({ + id: crypto.randomUUID(), + name: form.name, + amount: parseFloat(form.amount) || 0, + frequency: form.frequency as (typeof frequencies)[number], + category: form.category, + nextDate: new Date().toISOString().split('T')[0], + isActive: true, + isEssential: form.isEssential, + createdAt: new Date().toISOString() + }) + ); onOpenChange(false); setForm({name: '', amount: '', frequency: '', category: '', isEssential: false}); }; @@ -46,19 +48,42 @@ export default function AddExpenseDialog({open, onOpenChange}: Props) {
- setForm({...form, name: e.target.value})} className="input-depth" required /> + setForm({...form, name: e.target.value})} + className="input-depth" + required + />
- setForm({...form, amount: e.target.value})} className="input-depth" required /> + setForm({...form, amount: e.target.value})} + className="input-depth" + required + />
@@ -66,24 +91,38 @@ export default function AddExpenseDialog({open, onOpenChange}: Props) {
- - + + ); } - diff --git a/frontend-web/src/components/dialogs/AddIncomeDialog.tsx b/frontend-web/src/components/dialogs/AddIncomeDialog.tsx index 1895147..1a6ef91 100644 --- a/frontend-web/src/components/dialogs/AddIncomeDialog.tsx +++ b/frontend-web/src/components/dialogs/AddIncomeDialog.tsx @@ -20,16 +20,18 @@ export default function AddIncomeDialog({open, onOpenChange}: Props) { const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); - dispatch(addIncomeSource({ - id: crypto.randomUUID(), - name: form.name, - amount: parseFloat(form.amount) || 0, - frequency: form.frequency as typeof frequencies[number], - category: form.category, - nextDate: new Date().toISOString().split('T')[0], - isActive: true, - createdAt: new Date().toISOString(), - })); + dispatch( + addIncomeSource({ + id: crypto.randomUUID(), + name: form.name, + amount: parseFloat(form.amount) || 0, + frequency: form.frequency as (typeof frequencies)[number], + category: form.category, + nextDate: new Date().toISOString().split('T')[0], + isActive: true, + createdAt: new Date().toISOString() + }) + ); onOpenChange(false); setForm({name: '', amount: '', frequency: '', category: ''}); }; @@ -45,19 +47,42 @@ export default function AddIncomeDialog({open, onOpenChange}: Props) {
- setForm({...form, name: e.target.value})} className="input-depth" required /> + setForm({...form, name: e.target.value})} + className="input-depth" + required + />
- setForm({...form, amount: e.target.value})} className="input-depth" required /> + setForm({...form, amount: e.target.value})} + className="input-depth" + required + />
@@ -65,20 +90,29 @@ export default function AddIncomeDialog({open, onOpenChange}: Props) {
- - + + ); } - diff --git a/frontend-web/src/components/dialogs/AddInvoiceDialog.tsx b/frontend-web/src/components/dialogs/AddInvoiceDialog.tsx index 06364b6..fee82e7 100644 --- a/frontend-web/src/components/dialogs/AddInvoiceDialog.tsx +++ b/frontend-web/src/components/dialogs/AddInvoiceDialog.tsx @@ -18,7 +18,7 @@ export default function AddInvoiceDialog({open, onOpenChange}: Props) { clientId: '', description: '', amount: '', - dueDate: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString().split('T')[0], + dueDate: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString().split('T')[0] }); const handleSubmit = (e: React.FormEvent) => { @@ -26,27 +26,31 @@ export default function AddInvoiceDialog({open, onOpenChange}: Props) { const now = new Date().toISOString(); const invoiceNumber = `INV-${new Date().getFullYear()}-${String(Math.floor(Math.random() * 1000)).padStart(3, '0')}`; const amount = parseFloat(form.amount) || 0; - - dispatch(addInvoice({ - id: crypto.randomUUID(), - invoiceNumber, - clientId: form.clientId, - status: 'draft', - issueDate: now.split('T')[0], - dueDate: form.dueDate, - lineItems: [{ + + dispatch( + addInvoice({ id: crypto.randomUUID(), - description: form.description, - quantity: 1, - unitPrice: amount, + invoiceNumber, + clientId: form.clientId, + status: 'draft', + issueDate: now.split('T')[0], + dueDate: form.dueDate, + lineItems: [ + { + id: crypto.randomUUID(), + description: form.description, + quantity: 1, + unitPrice: amount, + total: amount + } + ], + subtotal: amount, + tax: 0, total: amount, - }], - subtotal: amount, - tax: 0, - total: amount, - createdAt: now, - updatedAt: now, - })); + createdAt: now, + updatedAt: now + }) + ); onOpenChange(false); setForm({clientId: '', description: '', amount: '', dueDate: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString().split('T')[0]}); }; @@ -63,34 +67,67 @@ export default function AddInvoiceDialog({open, onOpenChange}: Props) {
- setForm({...form, description: e.target.value})} className="input-depth" required /> + setForm({...form, description: e.target.value})} + className="input-depth" + required + />
- setForm({...form, amount: e.target.value})} className="input-depth" required /> + setForm({...form, amount: e.target.value})} + className="input-depth" + required + />
- setForm({...form, dueDate: e.target.value})} className="input-depth" required /> + setForm({...form, dueDate: e.target.value})} + className="input-depth" + required + />
- - + + ); } - diff --git a/frontend-web/src/components/dialogs/AddLiabilityDialog.tsx b/frontend-web/src/components/dialogs/AddLiabilityDialog.tsx index e33238d..ec7d14d 100644 --- a/frontend-web/src/components/dialogs/AddLiabilityDialog.tsx +++ b/frontend-web/src/components/dialogs/AddLiabilityDialog.tsx @@ -19,13 +19,15 @@ export default function AddLiabilityDialog({open, onOpenChange}: Props) { const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); - dispatch(addLiability({ - id: crypto.randomUUID(), - name: form.name, - type: form.type as typeof liabilityTypes[number], - balance: parseFloat(form.balance) || 0, - updatedAt: new Date().toISOString(), - })); + dispatch( + addLiability({ + id: crypto.randomUUID(), + name: form.name, + type: form.type as (typeof liabilityTypes)[number], + balance: parseFloat(form.balance) || 0, + updatedAt: new Date().toISOString() + }) + ); onOpenChange(false); setForm({name: '', type: '', balance: ''}); }; @@ -41,29 +43,55 @@ export default function AddLiabilityDialog({open, onOpenChange}: Props) {
- setForm({...form, name: e.target.value})} className="input-depth" required /> + setForm({...form, name: e.target.value})} + className="input-depth" + required + />
- setForm({...form, balance: e.target.value})} className="input-depth" required /> + setForm({...form, balance: e.target.value})} + className="input-depth" + required + />
- - + + ); } - diff --git a/frontend-web/src/components/dialogs/AddTransactionDialog.tsx b/frontend-web/src/components/dialogs/AddTransactionDialog.tsx index b2b94db..b27469f 100644 --- a/frontend-web/src/components/dialogs/AddTransactionDialog.tsx +++ b/frontend-web/src/components/dialogs/AddTransactionDialog.tsx @@ -18,14 +18,16 @@ export default function AddTransactionDialog({open, onOpenChange}: Props) { const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); - dispatch(addTransaction({ - id: crypto.randomUUID(), - type: form.type, - name: form.name, - amount: parseFloat(form.amount) || 0, - category: form.category, - date: form.date, - })); + dispatch( + addTransaction({ + id: crypto.randomUUID(), + type: form.type, + name: form.name, + amount: parseFloat(form.amount) || 0, + category: form.category, + date: form.date + }) + ); onOpenChange(false); setForm({type: 'expense', name: '', amount: '', category: '', date: new Date().toISOString().split('T')[0]}); }; @@ -44,7 +46,9 @@ export default function AddTransactionDialog({open, onOpenChange}: Props) {
setForm({...form, name: e.target.value})} className="input-depth" required /> + setForm({...form, name: e.target.value})} + className="input-depth" + required + />
- setForm({...form, amount: e.target.value})} className="input-depth" required /> + setForm({...form, amount: e.target.value})} + className="input-depth" + required + />
@@ -68,20 +89,29 @@ export default function AddTransactionDialog({open, onOpenChange}: Props) {
- - + + ); } - diff --git a/frontend-web/src/components/dialogs/EditAssetDialog.tsx b/frontend-web/src/components/dialogs/EditAssetDialog.tsx index 318e1aa..dbcc45c 100644 --- a/frontend-web/src/components/dialogs/EditAssetDialog.tsx +++ b/frontend-web/src/components/dialogs/EditAssetDialog.tsx @@ -25,7 +25,7 @@ export default function EditAssetDialog({open, onOpenChange, asset}: Props) { setForm({ name: asset.name, type: asset.type, - value: asset.value.toString(), + value: asset.value.toString() }); setErrors({name: '', value: ''}); } @@ -57,13 +57,15 @@ export default function EditAssetDialog({open, onOpenChange, asset}: Props) { const valueNum = validatePositiveNumber(form.value); if (valueNum === null) return; - dispatch(updateAsset({ - id: asset.id, - name: form.name.trim(), - type: form.type as typeof assetTypes[number], - value: valueNum, - updatedAt: new Date().toISOString(), - })); + dispatch( + updateAsset({ + id: asset.id, + name: form.name.trim(), + type: form.type as (typeof assetTypes)[number], + value: valueNum, + updatedAt: new Date().toISOString() + }) + ); onOpenChange(false); }; @@ -101,9 +103,15 @@ export default function EditAssetDialog({open, onOpenChange, asset}: Props) {
@@ -128,7 +136,9 @@ export default function EditAssetDialog({open, onOpenChange, asset}: Props) { Delete
- +
diff --git a/frontend-web/src/components/dialogs/EditClientDialog.tsx b/frontend-web/src/components/dialogs/EditClientDialog.tsx index 52b9a2b..3f73f4c 100644 --- a/frontend-web/src/components/dialogs/EditClientDialog.tsx +++ b/frontend-web/src/components/dialogs/EditClientDialog.tsx @@ -20,7 +20,7 @@ export default function EditClientDialog({open, onOpenChange, client}: Props) { phone: '', company: '', address: '', - notes: '', + notes: '' }); const [errors, setErrors] = useState({name: '', email: '', phone: ''}); @@ -32,7 +32,7 @@ export default function EditClientDialog({open, onOpenChange, client}: Props) { phone: client.phone || '', company: client.company || '', address: client.address || '', - notes: client.notes || '', + notes: client.notes || '' }); setErrors({name: '', email: '', phone: ''}); } @@ -65,16 +65,18 @@ export default function EditClientDialog({open, onOpenChange, client}: Props) { e.preventDefault(); if (!client || !validate()) return; - dispatch(updateClient({ - id: client.id, - name: sanitizeString(form.name), - email: sanitizeString(form.email), - phone: form.phone ? sanitizeString(form.phone) : undefined, - company: form.company ? sanitizeString(form.company) : undefined, - address: form.address ? sanitizeString(form.address) : undefined, - notes: form.notes ? sanitizeString(form.notes) : undefined, - createdAt: client.createdAt, - })); + dispatch( + updateClient({ + id: client.id, + name: sanitizeString(form.name), + email: sanitizeString(form.email), + phone: form.phone ? sanitizeString(form.phone) : undefined, + company: form.company ? sanitizeString(form.company) : undefined, + address: form.address ? sanitizeString(form.address) : undefined, + notes: form.notes ? sanitizeString(form.notes) : undefined, + createdAt: client.createdAt + }) + ); onOpenChange(false); }; @@ -99,13 +101,7 @@ export default function EditClientDialog({open, onOpenChange, client}: Props) {
- setForm({...form, name: e.target.value})} - className="input-depth" - required - /> + setForm({...form, name: e.target.value})} className="input-depth" required /> {errors.name &&

{errors.name}

}
@@ -122,41 +118,20 @@ export default function EditClientDialog({open, onOpenChange, client}: Props) {
- setForm({...form, phone: e.target.value})} - className="input-depth" - /> + setForm({...form, phone: e.target.value})} className="input-depth" /> {errors.phone &&

{errors.phone}

}
- setForm({...form, company: e.target.value})} - className="input-depth" - /> + setForm({...form, company: e.target.value})} className="input-depth" />
- setForm({...form, address: e.target.value})} - className="input-depth" - /> + setForm({...form, address: e.target.value})} className="input-depth" />
- setForm({...form, notes: e.target.value})} - className="input-depth" - /> + setForm({...form, notes: e.target.value})} className="input-depth" />
@@ -164,7 +139,9 @@ export default function EditClientDialog({open, onOpenChange, client}: Props) { Delete
- +
diff --git a/frontend-web/src/components/dialogs/EditLiabilityDialog.tsx b/frontend-web/src/components/dialogs/EditLiabilityDialog.tsx index 96c3856..404b045 100644 --- a/frontend-web/src/components/dialogs/EditLiabilityDialog.tsx +++ b/frontend-web/src/components/dialogs/EditLiabilityDialog.tsx @@ -25,7 +25,7 @@ export default function EditLiabilityDialog({open, onOpenChange, liability}: Pro setForm({ name: liability.name, type: liability.type, - balance: liability.balance.toString(), + balance: liability.balance.toString() }); setErrors({name: '', balance: ''}); } @@ -57,13 +57,15 @@ export default function EditLiabilityDialog({open, onOpenChange, liability}: Pro const balanceNum = validatePositiveNumber(form.balance); if (balanceNum === null) return; - dispatch(updateLiability({ - id: liability.id, - name: form.name.trim(), - type: form.type as typeof liabilityTypes[number], - balance: balanceNum, - updatedAt: new Date().toISOString(), - })); + dispatch( + updateLiability({ + id: liability.id, + name: form.name.trim(), + type: form.type as (typeof liabilityTypes)[number], + balance: balanceNum, + updatedAt: new Date().toISOString() + }) + ); onOpenChange(false); }; @@ -101,7 +103,9 @@ export default function EditLiabilityDialog({open, onOpenChange, liability}: Pro
setSelectedStatus(v as Invoice['status'])}> + - ) + ); } -export { Input } +export {Input}; diff --git a/frontend-web/src/components/ui/label.tsx b/frontend-web/src/components/ui/label.tsx index ef7133a..0571019 100644 --- a/frontend-web/src/components/ui/label.tsx +++ b/frontend-web/src/components/ui/label.tsx @@ -1,22 +1,19 @@ -import * as React from "react" -import * as LabelPrimitive from "@radix-ui/react-label" +import * as React from 'react'; +import * as LabelPrimitive from '@radix-ui/react-label'; -import { cn } from "@/lib/utils" +import {cn} from '@/lib/utils'; -function Label({ - className, - ...props -}: React.ComponentProps) { +function Label({className, ...props}: React.ComponentProps) { return ( - ) + ); } -export { Label } +export {Label}; diff --git a/frontend-web/src/components/ui/select.tsx b/frontend-web/src/components/ui/select.tsx index 25e5439..22bf3e2 100644 --- a/frontend-web/src/components/ui/select.tsx +++ b/frontend-web/src/components/ui/select.tsx @@ -1,36 +1,30 @@ -"use client" +'use client'; -import * as React from "react" -import * as SelectPrimitive from "@radix-ui/react-select" -import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react" +import * as React from 'react'; +import * as SelectPrimitive from '@radix-ui/react-select'; +import {CheckIcon, ChevronDownIcon, ChevronUpIcon} from 'lucide-react'; -import { cn } from "@/lib/utils" +import {cn} from '@/lib/utils'; -function Select({ - ...props -}: React.ComponentProps) { - return +function Select({...props}: React.ComponentProps) { + return ; } -function SelectGroup({ - ...props -}: React.ComponentProps) { - return +function SelectGroup({...props}: React.ComponentProps) { + return ; } -function SelectValue({ - ...props -}: React.ComponentProps) { - return +function SelectValue({...props}: React.ComponentProps) { + return ; } function SelectTrigger({ className, - size = "default", + size = 'default', children, ...props }: React.ComponentProps & { - size?: "sm" | "default" + size?: 'sm' | 'default'; }) { return ( + {...props}> {children} - ) + ); } -function SelectContent({ - className, - children, - position = "popper", - align = "center", - ...props -}: React.ComponentProps) { +function SelectContent({className, children, position = 'popper', align = 'center', ...props}: React.ComponentProps) { return ( + {...props}> + className={cn('p-1', position === 'popper' && 'h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1')}> {children} - ) + ); } -function SelectLabel({ - className, - ...props -}: React.ComponentProps) { - return ( - - ) +function SelectLabel({className, ...props}: React.ComponentProps) { + return ; } -function SelectItem({ - className, - children, - ...props -}: React.ComponentProps) { +function SelectItem({className, children, ...props}: React.ComponentProps) { return ( + {...props}> @@ -121,67 +88,33 @@ function SelectItem({ {children} - ) + ); } -function SelectSeparator({ - className, - ...props -}: React.ComponentProps) { - return ( - - ) +function SelectSeparator({className, ...props}: React.ComponentProps) { + return ; } -function SelectScrollUpButton({ - className, - ...props -}: React.ComponentProps) { +function SelectScrollUpButton({className, ...props}: React.ComponentProps) { return ( + className={cn('flex cursor-default items-center justify-center py-1', className)} + {...props}> - ) + ); } -function SelectScrollDownButton({ - className, - ...props -}: React.ComponentProps) { +function SelectScrollDownButton({className, ...props}: React.ComponentProps) { return ( + className={cn('flex cursor-default items-center justify-center py-1', className)} + {...props}> - ) + ); } -export { - Select, - SelectContent, - SelectGroup, - SelectItem, - SelectLabel, - SelectScrollDownButton, - SelectScrollUpButton, - SelectSeparator, - SelectTrigger, - SelectValue, -} +export {Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue}; diff --git a/frontend-web/src/components/ui/separator.tsx b/frontend-web/src/components/ui/separator.tsx index bb3ad74..6d8dd47 100644 --- a/frontend-web/src/components/ui/separator.tsx +++ b/frontend-web/src/components/ui/separator.tsx @@ -1,26 +1,21 @@ -import * as React from "react" -import * as SeparatorPrimitive from "@radix-ui/react-separator" +import * as React from 'react'; +import * as SeparatorPrimitive from '@radix-ui/react-separator'; -import { cn } from "@/lib/utils" +import {cn} from '@/lib/utils'; -function Separator({ - className, - orientation = "horizontal", - decorative = true, - ...props -}: React.ComponentProps) { +function Separator({className, orientation = 'horizontal', decorative = true, ...props}: React.ComponentProps) { return ( - ) + ); } -export { Separator } +export {Separator}; diff --git a/frontend-web/src/components/ui/tooltip.tsx b/frontend-web/src/components/ui/tooltip.tsx index a4e90d4..4f63d2c 100644 --- a/frontend-web/src/components/ui/tooltip.tsx +++ b/frontend-web/src/components/ui/tooltip.tsx @@ -1,61 +1,42 @@ -"use client" +'use client'; -import * as React from "react" -import * as TooltipPrimitive from "@radix-ui/react-tooltip" +import * as React from 'react'; +import * as TooltipPrimitive from '@radix-ui/react-tooltip'; -import { cn } from "@/lib/utils" +import {cn} from '@/lib/utils'; -function TooltipProvider({ - delayDuration = 0, - ...props -}: React.ComponentProps) { - return ( - - ) +function TooltipProvider({delayDuration = 0, ...props}: React.ComponentProps) { + return ; } -function Tooltip({ - ...props -}: React.ComponentProps) { +function Tooltip({...props}: React.ComponentProps) { return ( - ) + ); } -function TooltipTrigger({ - ...props -}: React.ComponentProps) { - return +function TooltipTrigger({...props}: React.ComponentProps) { + return ; } -function TooltipContent({ - className, - sideOffset = 0, - children, - ...props -}: React.ComponentProps) { +function TooltipContent({className, sideOffset = 0, children, ...props}: React.ComponentProps) { return ( + {...props}> {children} - ) + ); } -export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } +export {Tooltip, TooltipTrigger, TooltipContent, TooltipProvider}; diff --git a/frontend-web/src/fonts.d.ts b/frontend-web/src/fonts.d.ts index e48ea3d..04fe3ef 100644 --- a/frontend-web/src/fonts.d.ts +++ b/frontend-web/src/fonts.d.ts @@ -2,4 +2,3 @@ declare module '@fontsource-variable/geist'; declare module '@fontsource-variable/oxanium'; declare module '@fontsource-variable/funnel-sans'; declare module '@fontsource-variable/inter'; - diff --git a/frontend-web/src/index.css b/frontend-web/src/index.css index 8479ad3..87f418f 100644 --- a/frontend-web/src/index.css +++ b/frontend-web/src/index.css @@ -1,5 +1,5 @@ @import 'tailwindcss'; -@import "tw-animate-css"; +@import 'tw-animate-css'; @custom-variant dark (&:is(.dark *)); @@ -124,7 +124,12 @@ -moz-osx-font-smoothing: grayscale; letter-spacing: -0.01em; } - h1, h2, h3, h4, h5, h6 { + h1, + h2, + h3, + h4, + h5, + h6 { font-family: 'Funnel Sans Variable', system-ui, sans-serif; letter-spacing: -0.02em; } diff --git a/frontend-web/src/lib/formatters.ts b/frontend-web/src/lib/formatters.ts index a5f0de8..2d9f207 100644 --- a/frontend-web/src/lib/formatters.ts +++ b/frontend-web/src/lib/formatters.ts @@ -6,7 +6,7 @@ export const formatCurrency = (value: number, options?: {maximumFractionDigits?: return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', - maximumFractionDigits: options?.maximumFractionDigits ?? 0, + maximumFractionDigits: options?.maximumFractionDigits ?? 0 }).format(value); }; diff --git a/frontend-web/src/lib/utils.ts b/frontend-web/src/lib/utils.ts index bd0c391..1b288f0 100644 --- a/frontend-web/src/lib/utils.ts +++ b/frontend-web/src/lib/utils.ts @@ -1,6 +1,6 @@ -import { clsx, type ClassValue } from "clsx" -import { twMerge } from "tailwind-merge" +import {clsx, type ClassValue} from 'clsx'; +import {twMerge} from 'tailwind-merge'; export function cn(...inputs: ClassValue[]) { - return twMerge(clsx(inputs)) + return twMerge(clsx(inputs)); } diff --git a/frontend-web/src/pages/CashflowPage.tsx b/frontend-web/src/pages/CashflowPage.tsx index 9129237..1449c86 100644 --- a/frontend-web/src/pages/CashflowPage.tsx +++ b/frontend-web/src/pages/CashflowPage.tsx @@ -14,12 +14,18 @@ export default function CashflowPage() { const getMonthlyAmount = (amount: number, frequency: string) => { switch (frequency) { - case 'weekly': return amount * 4.33; - case 'biweekly': return amount * 2.17; - case 'monthly': return amount; - case 'quarterly': return amount / 3; - case 'yearly': return amount / 12; - default: return 0; + case 'weekly': + return amount * 4.33; + case 'biweekly': + return amount * 2.17; + case 'monthly': + return amount; + case 'quarterly': + return amount / 3; + case 'yearly': + return amount / 12; + default: + return 0; } }; @@ -30,11 +36,16 @@ export default function CashflowPage() { const fmt = (value: number) => new Intl.NumberFormat('en-US', {style: 'currency', currency: 'USD', maximumFractionDigits: 0}).format(value); - const expensesByCategory = expenses.filter(e => e.isActive).reduce((acc, e) => { - const monthly = getMonthlyAmount(e.amount, e.frequency); - acc[e.category] = (acc[e.category] || 0) + monthly; - return acc; - }, {} as Record); + const expensesByCategory = expenses + .filter(e => e.isActive) + .reduce( + (acc, e) => { + const monthly = getMonthlyAmount(e.amount, e.frequency); + acc[e.category] = (acc[e.category] || 0) + monthly; + return acc; + }, + {} as Record + ); const sortedCategories = Object.entries(expensesByCategory).sort((a, b) => b[1] - a[1]); const topExpenses = expenses.filter(e => e.isActive).sort((a, b) => getMonthlyAmount(b.amount, b.frequency) - getMonthlyAmount(a.amount, a.frequency)); @@ -46,15 +57,29 @@ export default function CashflowPage() {

Cashflow

-
Income {fmt(monthlyIncome)}
-
Expenses {fmt(monthlyExpenses)}
-
Net = 0 ? 'text-green-400' : 'text-red-400'}`}>{fmt(monthlySavings)}
-
Savings = 20 ? 'text-green-400' : ''}`}>{savingsRate.toFixed(0)}%
+
+ Income {fmt(monthlyIncome)} +
+
+ Expenses {fmt(monthlyExpenses)} +
+
+ Net{' '} + = 0 ? 'text-green-400' : 'text-red-400'}`}>{fmt(monthlySavings)} +
+
+ Savings{' '} + = 20 ? 'text-green-400' : ''}`}>{savingsRate.toFixed(0)}% +
- - + +
@@ -66,15 +91,17 @@ export default function CashflowPage() {
- {incomeSources.filter(i => i.isActive).map(income => ( -
-
-

{income.name}

-

{income.frequency}

+ {incomeSources + .filter(i => i.isActive) + .map(income => ( +
+
+

{income.name}

+

{income.frequency}

+
+

{fmt(income.amount)}

-

{fmt(income.amount)}

-
- ))} + ))}
@@ -106,7 +133,9 @@ export default function CashflowPage() { Recent Activity - +
@@ -117,7 +146,8 @@ export default function CashflowPage() {

{tx.date}

- {tx.type === 'income' ? '+' : '−'}{fmt(tx.amount)} + {tx.type === 'income' ? '+' : '−'} + {fmt(tx.amount)}
))} @@ -168,7 +198,10 @@ export default function CashflowPage() { = 20 ? 'text-green-400' : ''}`}>{savingsRate.toFixed(1)}%
-
= 20 ? 'bg-green-400/50' : 'bg-foreground/30'}`} style={{width: `${Math.min(savingsRate, 100)}%`}} /> +
= 20 ? 'bg-green-400/50' : 'bg-foreground/30'}`} + style={{width: `${Math.min(savingsRate, 100)}%`}} + />
diff --git a/frontend-web/src/pages/ClientsPage.tsx b/frontend-web/src/pages/ClientsPage.tsx index cf901c9..f226341 100644 --- a/frontend-web/src/pages/ClientsPage.tsx +++ b/frontend-web/src/pages/ClientsPage.tsx @@ -34,12 +34,20 @@ export default function ClientsPage() {

Clients

-
Total Clients {clients.length}
-
Total Billed {formatCurrency(totalBilled)}
-
Outstanding {formatCurrency(totalOutstanding)}
+
+ Total Clients {clients.length} +
+
+ Total Billed {formatCurrency(totalBilled)} +
+
+ Outstanding {formatCurrency(totalOutstanding)} +
- +
{/* Clients Grid */} @@ -47,20 +55,14 @@ export default function ClientsPage() { {clients.map(client => { const stats = getClientStats(client.id); return ( - handleEditClient(client)} - > + handleEditClient(client)}>

{client.name}

{client.company || client.email}

- {stats.outstanding > 0 && ( - {formatCurrency(stats.outstanding)} due - )} + {stats.outstanding > 0 && {formatCurrency(stats.outstanding)} due}
@@ -76,7 +78,7 @@ export default function ClientsPage() { ); })} - + {/* Add client card */} setDialogOpen(true)}> diff --git a/frontend-web/src/pages/DebtsPage.tsx b/frontend-web/src/pages/DebtsPage.tsx index e84999d..b137fe7 100644 --- a/frontend-web/src/pages/DebtsPage.tsx +++ b/frontend-web/src/pages/DebtsPage.tsx @@ -22,7 +22,8 @@ export default function DebtsPage() { const getCategoryById = (id: string) => categories.find(c => c.id === id); const getAccountsByCategory = (categoryId: string) => accounts.filter(a => a.categoryId === categoryId); const getCategoryTotal = (categoryId: string) => getAccountsByCategory(categoryId).reduce((sum, a) => sum + a.currentBalance, 0); - const getProgress = (account: DebtAccount) => account.originalBalance > 0 ? ((account.originalBalance - account.currentBalance) / account.originalBalance) * 100 : 0; + const getProgress = (account: DebtAccount) => + account.originalBalance > 0 ? ((account.originalBalance - account.currentBalance) / account.originalBalance) * 100 : 0; const categoriesWithDebt = categories.filter(c => getCategoryTotal(c.id) > 0); @@ -33,9 +34,15 @@ export default function DebtsPage() {

Debts

-
Total {fmt(totalDebt)}
-
Paid {fmt(totalPaidDown)}
-
Monthly {fmt(totalMinPayment)}
+
+ Total {fmt(totalDebt)} +
+
+ Paid {fmt(totalPaidDown)} +
+
+ Monthly {fmt(totalMinPayment)} +
@@ -46,10 +53,16 @@ export default function DebtsPage() {
- - + +
- +
@@ -107,7 +120,9 @@ export default function DebtsPage() {

{account.name}

-

{account.institution} · {category?.name}

+

+ {account.institution} · {category?.name} +

{account.interestRate}%
diff --git a/frontend-web/src/pages/InvoicesPage.tsx b/frontend-web/src/pages/InvoicesPage.tsx index fc28d78..3ce34f1 100644 --- a/frontend-web/src/pages/InvoicesPage.tsx +++ b/frontend-web/src/pages/InvoicesPage.tsx @@ -25,7 +25,7 @@ export default function InvoicesPage() { overdue: invoices.filter(i => i.status === 'overdue'), sent: invoices.filter(i => i.status === 'sent'), draft: invoices.filter(i => i.status === 'draft'), - paid: invoices.filter(i => i.status === 'paid').slice(0, 5), + paid: invoices.filter(i => i.status === 'paid').slice(0, 5) }; const handleViewInvoice = (invoice: Invoice) => { @@ -40,14 +40,26 @@ export default function InvoicesPage() {

Invoices

-
Outstanding {formatCurrency(totalOutstanding)}
-
Paid {formatCurrency(totalPaid)}
- {overdueCount > 0 &&
{overdueCount} overdue
} +
+ Outstanding {formatCurrency(totalOutstanding)} +
+
+ Paid {formatCurrency(totalPaid)} +
+ {overdueCount > 0 && ( +
+ {overdueCount} overdue +
+ )}
- - + +
@@ -66,8 +78,7 @@ export default function InvoicesPage() {
handleViewInvoice(inv)} - > + onClick={() => handleViewInvoice(inv)}>
{inv.invoiceNumber} {formatCurrency(inv.total)} @@ -94,8 +105,7 @@ export default function InvoicesPage() {
handleViewInvoice(inv)} - > + onClick={() => handleViewInvoice(inv)}>
{inv.invoiceNumber} {formatCurrency(inv.total)} @@ -125,8 +135,7 @@ export default function InvoicesPage() {
handleViewInvoice(inv)} - > + onClick={() => handleViewInvoice(inv)}>
{inv.invoiceNumber} {formatCurrency(inv.total)} @@ -153,8 +162,7 @@ export default function InvoicesPage() {
handleViewInvoice(inv)} - > + onClick={() => handleViewInvoice(inv)}>
{inv.invoiceNumber} {formatCurrency(inv.total)} @@ -170,12 +178,7 @@ export default function InvoicesPage() { - +
); } diff --git a/frontend-web/src/pages/NetWorthPage.tsx b/frontend-web/src/pages/NetWorthPage.tsx index 67301c8..c86049b 100644 --- a/frontend-web/src/pages/NetWorthPage.tsx +++ b/frontend-web/src/pages/NetWorthPage.tsx @@ -23,7 +23,7 @@ export default function NetWorthPage() { const chartData = snapshots.map(s => ({ month: format(new Date(s.date), 'MMM'), - netWorth: s.netWorth, + netWorth: s.netWorth })); const totalAssets = assets.reduce((sum, a) => sum + a.value, 0); @@ -39,7 +39,7 @@ export default function NetWorthPage() { date: new Date().toISOString().split('T')[0], totalAssets, totalLiabilities, - netWorth, + netWorth }; dispatch(addSnapshot(snapshot)); }; @@ -61,12 +61,20 @@ export default function NetWorthPage() {

Net Worth

-
Assets {formatCurrency(totalAssets)}
-
Liabilities {formatCurrency(totalLiabilities)}
-
Net {formatCurrency(netWorth)}
+
+ Assets {formatCurrency(totalAssets)} +
+
+ Liabilities {formatCurrency(totalLiabilities)} +
+
+ Net {formatCurrency(netWorth)} +
- +
@@ -83,7 +91,14 @@ export default function NetWorthPage() { - `$${v / 1000}k`} axisLine={false} tickLine={false} width={45} /> + `$${v / 1000}k`} + axisLine={false} + tickLine={false} + width={45} + />

Monthly Change

= 0 ? 'text-green-400' : 'text-red-400'}`}> - {monthlyChange >= 0 ? '+' : ''}{formatCurrency(monthlyChange)} + {monthlyChange >= 0 ? '+' : ''} + {formatCurrency(monthlyChange)}

YTD Growth

-

= 0 ? 'text-green-400' : 'text-red-400'}`}> - {formatPercentage(ytdGrowth)} -

+

= 0 ? 'text-green-400' : 'text-red-400'}`}>{formatPercentage(ytdGrowth)}

@@ -126,7 +140,9 @@ export default function NetWorthPage() { Assets - +
@@ -134,8 +150,7 @@ export default function NetWorthPage() {
handleEditAsset(asset)} - > + onClick={() => handleEditAsset(asset)}>
{asset.name} {asset.type} @@ -151,7 +166,9 @@ export default function NetWorthPage() { Liabilities - +
@@ -159,8 +176,7 @@ export default function NetWorthPage() {
handleEditLiability(liability)} - > + onClick={() => handleEditLiability(liability)}>
{liability.name} {liability.type.replace('_', ' ')} diff --git a/frontend-web/src/store/index.ts b/frontend-web/src/store/index.ts index a23d3f4..d8d249c 100644 --- a/frontend-web/src/store/index.ts +++ b/frontend-web/src/store/index.ts @@ -20,7 +20,7 @@ export { updateLiability, removeLiability, addSnapshot, - setSnapshots, + setSnapshots } from './slices/netWorthSlice'; export type {Asset, Liability, NetWorthSnapshot, NetWorthState} from './slices/netWorthSlice'; @@ -38,7 +38,7 @@ export { setAccounts, addPayment, removePayment, - setPayments, + setPayments } from './slices/debtsSlice'; export type {DebtCategory, DebtAccount, DebtPayment, DebtsState} from './slices/debtsSlice'; @@ -54,7 +54,7 @@ export { updateInvoice, removeInvoice, setInvoices, - updateInvoiceStatus, + updateInvoiceStatus } from './slices/invoicesSlice'; export type {Client, Invoice, InvoiceLineItem, InvoicesState} from './slices/invoicesSlice'; @@ -69,6 +69,6 @@ export { updateExpense, removeExpense, addTransaction, - removeTransaction, + removeTransaction } from './slices/cashflowSlice'; export type {IncomeSource, Expense, Transaction, CashflowState} from './slices/cashflowSlice'; diff --git a/frontend-web/src/store/slices/cashflowSlice.ts b/frontend-web/src/store/slices/cashflowSlice.ts index 734f859..8deaca2 100644 --- a/frontend-web/src/store/slices/cashflowSlice.ts +++ b/frontend-web/src/store/slices/cashflowSlice.ts @@ -44,27 +44,145 @@ export interface CashflowState { const defaultCategories = { income: ['Salary', 'Freelance', 'Investments', 'Rental', 'Side Business', 'Other'], - expense: ['Housing', 'Utilities', 'Transportation', 'Food', 'Insurance', 'Healthcare', 'Subscriptions', 'Entertainment', 'Shopping', 'Savings', 'Other'], + expense: ['Housing', 'Utilities', 'Transportation', 'Food', 'Insurance', 'Healthcare', 'Subscriptions', 'Entertainment', 'Shopping', 'Savings', 'Other'] }; // Mock data const mockIncomeSources: IncomeSource[] = [ - {id: 'i1', name: 'Software Engineer Salary', amount: 8500, frequency: 'monthly', category: 'Salary', nextDate: '2024-12-15', isActive: true, createdAt: '2024-01-01'}, + { + id: 'i1', + name: 'Software Engineer Salary', + amount: 8500, + frequency: 'monthly', + category: 'Salary', + nextDate: '2024-12-15', + isActive: true, + createdAt: '2024-01-01' + }, {id: 'i2', name: 'Consulting', amount: 2000, frequency: 'monthly', category: 'Freelance', nextDate: '2024-12-20', isActive: true, createdAt: '2024-03-01'}, - {id: 'i3', name: 'Dividend Income', amount: 450, frequency: 'quarterly', category: 'Investments', nextDate: '2024-12-31', isActive: true, createdAt: '2024-01-01'}, + { + id: 'i3', + name: 'Dividend Income', + amount: 450, + frequency: 'quarterly', + category: 'Investments', + nextDate: '2024-12-31', + isActive: true, + createdAt: '2024-01-01' + } ]; const mockExpenses: Expense[] = [ - {id: 'e1', name: 'Mortgage', amount: 2200, frequency: 'monthly', category: 'Housing', nextDate: '2024-12-01', isActive: true, isEssential: true, createdAt: '2024-01-01'}, - {id: 'e2', name: 'Car Payment', amount: 450, frequency: 'monthly', category: 'Transportation', nextDate: '2024-12-05', isActive: true, isEssential: true, createdAt: '2024-01-01'}, - {id: 'e3', name: 'Car Insurance', amount: 180, frequency: 'monthly', category: 'Insurance', nextDate: '2024-12-10', isActive: true, isEssential: true, createdAt: '2024-01-01'}, - {id: 'e4', name: 'Utilities', amount: 250, frequency: 'monthly', category: 'Utilities', nextDate: '2024-12-15', isActive: true, isEssential: true, createdAt: '2024-01-01'}, - {id: 'e5', name: 'Groceries', amount: 600, frequency: 'monthly', category: 'Food', nextDate: '2024-12-01', isActive: true, isEssential: true, createdAt: '2024-01-01'}, - {id: 'e6', name: 'Gym Membership', amount: 50, frequency: 'monthly', category: 'Healthcare', nextDate: '2024-12-01', isActive: true, isEssential: false, createdAt: '2024-01-01'}, - {id: 'e7', name: 'Netflix', amount: 15, frequency: 'monthly', category: 'Subscriptions', nextDate: '2024-12-08', isActive: true, isEssential: false, createdAt: '2024-01-01'}, - {id: 'e8', name: 'Spotify', amount: 12, frequency: 'monthly', category: 'Subscriptions', nextDate: '2024-12-12', isActive: true, isEssential: false, createdAt: '2024-01-01'}, - {id: 'e9', name: 'Health Insurance', amount: 350, frequency: 'monthly', category: 'Insurance', nextDate: '2024-12-01', isActive: true, isEssential: true, createdAt: '2024-01-01'}, - {id: 'e10', name: '401k Contribution', amount: 1500, frequency: 'monthly', category: 'Savings', nextDate: '2024-12-15', isActive: true, isEssential: true, createdAt: '2024-01-01'}, + { + id: 'e1', + name: 'Mortgage', + amount: 2200, + frequency: 'monthly', + category: 'Housing', + nextDate: '2024-12-01', + isActive: true, + isEssential: true, + createdAt: '2024-01-01' + }, + { + id: 'e2', + name: 'Car Payment', + amount: 450, + frequency: 'monthly', + category: 'Transportation', + nextDate: '2024-12-05', + isActive: true, + isEssential: true, + createdAt: '2024-01-01' + }, + { + id: 'e3', + name: 'Car Insurance', + amount: 180, + frequency: 'monthly', + category: 'Insurance', + nextDate: '2024-12-10', + isActive: true, + isEssential: true, + createdAt: '2024-01-01' + }, + { + id: 'e4', + name: 'Utilities', + amount: 250, + frequency: 'monthly', + category: 'Utilities', + nextDate: '2024-12-15', + isActive: true, + isEssential: true, + createdAt: '2024-01-01' + }, + { + id: 'e5', + name: 'Groceries', + amount: 600, + frequency: 'monthly', + category: 'Food', + nextDate: '2024-12-01', + isActive: true, + isEssential: true, + createdAt: '2024-01-01' + }, + { + id: 'e6', + name: 'Gym Membership', + amount: 50, + frequency: 'monthly', + category: 'Healthcare', + nextDate: '2024-12-01', + isActive: true, + isEssential: false, + createdAt: '2024-01-01' + }, + { + id: 'e7', + name: 'Netflix', + amount: 15, + frequency: 'monthly', + category: 'Subscriptions', + nextDate: '2024-12-08', + isActive: true, + isEssential: false, + createdAt: '2024-01-01' + }, + { + id: 'e8', + name: 'Spotify', + amount: 12, + frequency: 'monthly', + category: 'Subscriptions', + nextDate: '2024-12-12', + isActive: true, + isEssential: false, + createdAt: '2024-01-01' + }, + { + id: 'e9', + name: 'Health Insurance', + amount: 350, + frequency: 'monthly', + category: 'Insurance', + nextDate: '2024-12-01', + isActive: true, + isEssential: true, + createdAt: '2024-01-01' + }, + { + id: 'e10', + name: '401k Contribution', + amount: 1500, + frequency: 'monthly', + category: 'Savings', + nextDate: '2024-12-15', + isActive: true, + isEssential: true, + createdAt: '2024-01-01' + } ]; const mockTransactions: Transaction[] = [ @@ -73,7 +191,7 @@ const mockTransactions: Transaction[] = [ {id: 't3', type: 'expense', name: 'Groceries', amount: 145, category: 'Food', date: '2024-11-28'}, {id: 't4', type: 'expense', name: 'Gas', amount: 55, category: 'Transportation', date: '2024-11-25'}, {id: 't5', type: 'income', name: 'Consulting Payment', amount: 2000, category: 'Freelance', date: '2024-11-20'}, - {id: 't6', type: 'expense', name: 'Restaurant', amount: 85, category: 'Food', date: '2024-11-22'}, + {id: 't6', type: 'expense', name: 'Restaurant', amount: 85, category: 'Food', date: '2024-11-22'} ]; const initialState: CashflowState = { @@ -82,7 +200,7 @@ const initialState: CashflowState = { transactions: mockTransactions, categories: defaultCategories, isLoading: false, - error: null, + error: null }; const cashflowSlice = createSlice({ @@ -123,8 +241,8 @@ const cashflowSlice = createSlice({ }, removeTransaction: (state, action: PayloadAction) => { state.transactions = state.transactions.filter(t => t.id !== action.payload); - }, - }, + } + } }); export const { @@ -137,8 +255,7 @@ export const { updateExpense, removeExpense, addTransaction, - removeTransaction, + removeTransaction } = cashflowSlice.actions; export default cashflowSlice.reducer; - diff --git a/frontend-web/src/store/slices/debtsSlice.ts b/frontend-web/src/store/slices/debtsSlice.ts index e81d3b2..1daef3b 100644 --- a/frontend-web/src/store/slices/debtsSlice.ts +++ b/frontend-web/src/store/slices/debtsSlice.ts @@ -47,7 +47,7 @@ const defaultCategories: DebtCategory[] = [ {id: 'student-loans', name: 'Student Loans', color: '#6b7280', createdAt: new Date().toISOString()}, {id: 'mortgage', name: 'Mortgage', color: '#6b7280', createdAt: new Date().toISOString()}, {id: 'medical', name: 'Medical', color: '#6b7280', createdAt: new Date().toISOString()}, - {id: 'other', name: 'Other', color: '#6b7280', createdAt: new Date().toISOString()}, + {id: 'other', name: 'Other', color: '#6b7280', createdAt: new Date().toISOString()} ]; // Mock data for development @@ -64,7 +64,7 @@ const mockAccounts: DebtAccount[] = [ minimumPayment: 95, dueDay: 15, createdAt: '2024-01-15', - updatedAt: '2024-12-01', + updatedAt: '2024-12-01' }, { id: 'cc2', @@ -78,7 +78,7 @@ const mockAccounts: DebtAccount[] = [ minimumPayment: 55, dueDay: 22, createdAt: '2024-02-10', - updatedAt: '2024-12-01', + updatedAt: '2024-12-01' }, { id: 'cc3', @@ -92,7 +92,7 @@ const mockAccounts: DebtAccount[] = [ minimumPayment: 35, dueDay: 8, createdAt: '2024-03-05', - updatedAt: '2024-12-01', + updatedAt: '2024-12-01' }, { id: 'al1', @@ -106,7 +106,7 @@ const mockAccounts: DebtAccount[] = [ minimumPayment: 650, dueDay: 1, createdAt: '2021-06-15', - updatedAt: '2024-12-01', + updatedAt: '2024-12-01' }, { id: 'sl1', @@ -119,7 +119,7 @@ const mockAccounts: DebtAccount[] = [ minimumPayment: 320, dueDay: 25, createdAt: '2018-09-01', - updatedAt: '2024-12-01', + updatedAt: '2024-12-01' }, { id: 'pl1', @@ -133,15 +133,15 @@ const mockAccounts: DebtAccount[] = [ minimumPayment: 285, dueDay: 12, createdAt: '2023-08-20', - updatedAt: '2024-12-01', - }, + updatedAt: '2024-12-01' + } ]; const mockPayments: DebtPayment[] = [ {id: 'p1', accountId: 'cc1', amount: 500, date: '2024-11-15', note: 'Extra payment'}, {id: 'p2', accountId: 'cc2', amount: 200, date: '2024-11-22'}, {id: 'p3', accountId: 'al1', amount: 650, date: '2024-12-01'}, - {id: 'p4', accountId: 'sl1', amount: 320, date: '2024-11-25'}, + {id: 'p4', accountId: 'sl1', amount: 320, date: '2024-11-25'} ]; const initialState: DebtsState = { @@ -149,7 +149,7 @@ const initialState: DebtsState = { accounts: mockAccounts, payments: mockPayments, isLoading: false, - error: null, + error: null }; const debtsSlice = createSlice({ @@ -219,8 +219,8 @@ const debtsSlice = createSlice({ }, setPayments: (state, action: PayloadAction) => { state.payments = action.payload; - }, - }, + } + } }); export const { @@ -236,7 +236,7 @@ export const { setAccounts, addPayment, removePayment, - setPayments, + setPayments } = debtsSlice.actions; export default debtsSlice.reducer; diff --git a/frontend-web/src/store/slices/invoicesSlice.ts b/frontend-web/src/store/slices/invoicesSlice.ts index 036d8c1..305eaf1 100644 --- a/frontend-web/src/store/slices/invoicesSlice.ts +++ b/frontend-web/src/store/slices/invoicesSlice.ts @@ -51,7 +51,7 @@ const mockClients: Client[] = [ phone: '555-0100', company: 'Acme Corporation', address: '123 Business Ave, Suite 400, San Francisco, CA 94102', - createdAt: '2024-01-10', + createdAt: '2024-01-10' }, { id: 'c2', @@ -60,14 +60,14 @@ const mockClients: Client[] = [ phone: '555-0200', company: 'TechStart Inc', address: '456 Innovation Blvd, Austin, TX 78701', - createdAt: '2024-02-15', + createdAt: '2024-02-15' }, { id: 'c3', name: 'Sarah Mitchell', email: 'sarah@mitchell.design', company: 'Mitchell Design Studio', - createdAt: '2024-03-22', + createdAt: '2024-03-22' }, { id: 'c4', @@ -76,8 +76,8 @@ const mockClients: Client[] = [ phone: '555-0400', company: 'Global Media LLC', address: '789 Media Row, New York, NY 10001', - createdAt: '2024-04-08', - }, + createdAt: '2024-04-08' + } ]; const mockInvoices: Invoice[] = [ @@ -90,13 +90,13 @@ const mockInvoices: Invoice[] = [ dueDate: '2024-10-31', lineItems: [ {id: 'li1', description: 'Web Development - October', quantity: 80, unitPrice: 150, total: 12000}, - {id: 'li2', description: 'Hosting & Maintenance', quantity: 1, unitPrice: 500, total: 500}, + {id: 'li2', description: 'Hosting & Maintenance', quantity: 1, unitPrice: 500, total: 500} ], subtotal: 12500, tax: 0, total: 12500, createdAt: '2024-10-01', - updatedAt: '2024-10-15', + updatedAt: '2024-10-15' }, { id: 'inv2', @@ -105,14 +105,12 @@ const mockInvoices: Invoice[] = [ status: 'paid', issueDate: '2024-10-15', dueDate: '2024-11-14', - lineItems: [ - {id: 'li3', description: 'Mobile App Development', quantity: 120, unitPrice: 175, total: 21000}, - ], + lineItems: [{id: 'li3', description: 'Mobile App Development', quantity: 120, unitPrice: 175, total: 21000}], subtotal: 21000, tax: 0, total: 21000, createdAt: '2024-10-15', - updatedAt: '2024-11-10', + updatedAt: '2024-11-10' }, { id: 'inv3', @@ -123,13 +121,13 @@ const mockInvoices: Invoice[] = [ dueDate: '2024-12-01', lineItems: [ {id: 'li4', description: 'Web Development - November', quantity: 60, unitPrice: 150, total: 9000}, - {id: 'li5', description: 'API Integration', quantity: 20, unitPrice: 175, total: 3500}, + {id: 'li5', description: 'API Integration', quantity: 20, unitPrice: 175, total: 3500} ], subtotal: 12500, tax: 0, total: 12500, createdAt: '2024-11-01', - updatedAt: '2024-11-01', + updatedAt: '2024-11-01' }, { id: 'inv4', @@ -138,14 +136,12 @@ const mockInvoices: Invoice[] = [ status: 'overdue', issueDate: '2024-10-20', dueDate: '2024-11-20', - lineItems: [ - {id: 'li6', description: 'Brand Identity Design', quantity: 1, unitPrice: 4500, total: 4500}, - ], + lineItems: [{id: 'li6', description: 'Brand Identity Design', quantity: 1, unitPrice: 4500, total: 4500}], subtotal: 4500, tax: 0, total: 4500, createdAt: '2024-10-20', - updatedAt: '2024-10-20', + updatedAt: '2024-10-20' }, { id: 'inv5', @@ -156,21 +152,21 @@ const mockInvoices: Invoice[] = [ dueDate: '2024-12-31', lineItems: [ {id: 'li7', description: 'Video Production', quantity: 5, unitPrice: 2000, total: 10000}, - {id: 'li8', description: 'Motion Graphics', quantity: 10, unitPrice: 500, total: 5000}, + {id: 'li8', description: 'Motion Graphics', quantity: 10, unitPrice: 500, total: 5000} ], subtotal: 15000, tax: 0, total: 15000, createdAt: '2024-12-01', - updatedAt: '2024-12-01', - }, + updatedAt: '2024-12-01' + } ]; const initialState: InvoicesState = { clients: mockClients, invoices: mockInvoices, isLoading: false, - error: null, + error: null }; const invoicesSlice = createSlice({ @@ -211,17 +207,14 @@ const invoicesSlice = createSlice({ setInvoices: (state, action: PayloadAction) => { state.invoices = action.payload; }, - updateInvoiceStatus: ( - state, - action: PayloadAction<{id: string; status: Invoice['status']}> - ) => { + updateInvoiceStatus: (state, action: PayloadAction<{id: string; status: Invoice['status']}>) => { const invoice = state.invoices.find(i => i.id === action.payload.id); if (invoice) { invoice.status = action.payload.status; invoice.updatedAt = new Date().toISOString(); } - }, - }, + } + } }); export const { @@ -235,8 +228,7 @@ export const { updateInvoice, removeInvoice, setInvoices, - updateInvoiceStatus, + updateInvoiceStatus } = invoicesSlice.actions; export default invoicesSlice.reducer; - diff --git a/frontend-web/src/store/slices/netWorthSlice.ts b/frontend-web/src/store/slices/netWorthSlice.ts index fd39c7e..25c15a9 100644 --- a/frontend-web/src/store/slices/netWorthSlice.ts +++ b/frontend-web/src/store/slices/netWorthSlice.ts @@ -39,13 +39,13 @@ const mockAssets: Asset[] = [ {id: 'a3', name: 'Fidelity 401k', type: 'investment', value: 145000, updatedAt: '2024-12-01'}, {id: 'a4', name: 'Vanguard Brokerage', type: 'investment', value: 52000, updatedAt: '2024-12-01'}, {id: 'a5', name: 'Primary Residence', type: 'property', value: 425000, updatedAt: '2024-12-01'}, - {id: 'a6', name: '2021 Tesla Model 3', type: 'vehicle', value: 28000, updatedAt: '2024-12-01'}, + {id: 'a6', name: '2021 Tesla Model 3', type: 'vehicle', value: 28000, updatedAt: '2024-12-01'} ]; const mockLiabilities: Liability[] = [ {id: 'l1', name: 'Mortgage', type: 'mortgage', balance: 320000, updatedAt: '2024-12-01'}, {id: 'l2', name: 'Auto Loan', type: 'loan', balance: 15000, updatedAt: '2024-12-01'}, - {id: 'l3', name: 'Student Loans', type: 'loan', balance: 28000, updatedAt: '2024-12-01'}, + {id: 'l3', name: 'Student Loans', type: 'loan', balance: 28000, updatedAt: '2024-12-01'} ]; const mockSnapshots: NetWorthSnapshot[] = [ @@ -54,7 +54,7 @@ const mockSnapshots: NetWorthSnapshot[] = [ {id: 's3', date: '2024-09-01', totalAssets: 680000, totalLiabilities: 370000, netWorth: 310000}, {id: 's4', date: '2024-10-01', totalAssets: 685000, totalLiabilities: 368000, netWorth: 317000}, {id: 's5', date: '2024-11-01', totalAssets: 692000, totalLiabilities: 365000, netWorth: 327000}, - {id: 's6', date: '2024-12-01', totalAssets: 697500, totalLiabilities: 363000, netWorth: 334500}, + {id: 's6', date: '2024-12-01', totalAssets: 697500, totalLiabilities: 363000, netWorth: 334500} ]; const initialState: NetWorthState = { @@ -62,7 +62,7 @@ const initialState: NetWorthState = { liabilities: mockLiabilities, snapshots: mockSnapshots, isLoading: false, - error: null, + error: null }; const netWorthSlice = createSlice({ @@ -100,22 +100,11 @@ const netWorthSlice = createSlice({ }, setSnapshots: (state, action: PayloadAction) => { state.snapshots = action.payload; - }, - }, + } + } }); -export const { - setLoading, - setError, - addAsset, - updateAsset, - removeAsset, - addLiability, - updateLiability, - removeLiability, - addSnapshot, - setSnapshots, -} = netWorthSlice.actions; +export const {setLoading, setError, addAsset, updateAsset, removeAsset, addLiability, updateLiability, removeLiability, addSnapshot, setSnapshots} = + netWorthSlice.actions; export default netWorthSlice.reducer; - diff --git a/frontend-web/src/store/store.ts b/frontend-web/src/store/store.ts index df1c448..1d4cb93 100644 --- a/frontend-web/src/store/store.ts +++ b/frontend-web/src/store/store.ts @@ -11,8 +11,8 @@ export const store = configureStore({ netWorth: netWorthReducer, debts: debtsReducer, invoices: invoicesReducer, - cashflow: cashflowReducer, - }, + cashflow: cashflowReducer + } }); export type RootState = ReturnType;