+
-
-
- Assets
- What you own
-
-
+
+ Assets
+
-
+
{assets.length === 0 ? (
- No assets added yet
+ No assets added yet
) : (
-
+
{assets.map(asset => (
-
-
- {asset.name}
- {formatCurrency(asset.value)}
-
+
+
+ {asset.name}
+ {asset.type}
+
+
{fmt(asset.value)}
+
))}
-
+
)}
-
-
- Liabilities
- What you owe
-
-
+
+ Liabilities
+
-
+
{liabilities.length === 0 ? (
- No liabilities added yet
+ No liabilities added yet
) : (
-
@@ -177,4 +138,3 @@ export default function NetWorthPage() {
);
}
-
diff --git a/frontend-web/src/store/index.ts b/frontend-web/src/store/index.ts
index ea1d4fe..164d74b 100644
--- a/frontend-web/src/store/index.ts
+++ b/frontend-web/src/store/index.ts
@@ -28,15 +28,19 @@ export type {Asset, Liability, NetWorthSnapshot, NetWorthState} from './slices/n
export {
setLoading as setDebtsLoading,
setError as setDebtsError,
- addDebt,
- updateDebt,
- removeDebt,
+ addCategory,
+ updateCategory,
+ removeCategory,
+ setCategories,
+ addAccount,
+ updateAccount,
+ removeAccount,
+ setAccounts,
addPayment,
removePayment,
- setDebts,
setPayments,
} from './slices/debtsSlice';
-export type {Debt, DebtPayment, DebtsState} from './slices/debtsSlice';
+export type {DebtCategory, DebtAccount, DebtPayment, DebtsState} from './slices/debtsSlice';
// Invoices slice
export {
diff --git a/frontend-web/src/store/slices/debtsSlice.ts b/frontend-web/src/store/slices/debtsSlice.ts
index 478b976..e81d3b2 100644
--- a/frontend-web/src/store/slices/debtsSlice.ts
+++ b/frontend-web/src/store/slices/debtsSlice.ts
@@ -1,37 +1,153 @@
import {createSlice, type PayloadAction} from '@reduxjs/toolkit';
-export interface Debt {
+export interface DebtCategory {
id: string;
name: string;
- type: 'credit_card' | 'personal_loan' | 'auto_loan' | 'student_loan' | 'mortgage' | 'other';
+ color: string;
+ createdAt: string;
+}
+
+export interface DebtAccount {
+ id: string;
+ name: string;
+ categoryId: string;
+ institution: string;
+ accountNumber?: string; // last 4 digits
originalBalance: number;
currentBalance: number;
interestRate: number;
minimumPayment: number;
dueDay: number;
- lender: string;
+ notes?: string;
createdAt: string;
updatedAt: string;
}
export interface DebtPayment {
id: string;
- debtId: string;
+ accountId: string;
amount: number;
date: string;
note?: string;
}
export interface DebtsState {
- debts: Debt[];
+ categories: DebtCategory[];
+ accounts: DebtAccount[];
payments: DebtPayment[];
isLoading: boolean;
error: string | null;
}
+// Default categories
+const defaultCategories: DebtCategory[] = [
+ {id: 'credit-cards', name: 'Credit Cards', color: '#6b7280', createdAt: new Date().toISOString()},
+ {id: 'personal-loans', name: 'Personal Loans', color: '#6b7280', createdAt: new Date().toISOString()},
+ {id: 'auto-loans', name: 'Auto Loans', color: '#6b7280', createdAt: new Date().toISOString()},
+ {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()},
+];
+
+// Mock data for development
+const mockAccounts: DebtAccount[] = [
+ {
+ id: 'cc1',
+ name: 'Chase Sapphire Preferred',
+ categoryId: 'credit-cards',
+ institution: 'Chase',
+ accountNumber: '4521',
+ originalBalance: 8500,
+ currentBalance: 3200,
+ interestRate: 21.99,
+ minimumPayment: 95,
+ dueDay: 15,
+ createdAt: '2024-01-15',
+ updatedAt: '2024-12-01',
+ },
+ {
+ id: 'cc2',
+ name: 'Amex Blue Cash',
+ categoryId: 'credit-cards',
+ institution: 'American Express',
+ accountNumber: '1008',
+ originalBalance: 4200,
+ currentBalance: 1850,
+ interestRate: 19.24,
+ minimumPayment: 55,
+ dueDay: 22,
+ createdAt: '2024-02-10',
+ updatedAt: '2024-12-01',
+ },
+ {
+ id: 'cc3',
+ name: 'Citi Double Cash',
+ categoryId: 'credit-cards',
+ institution: 'Citibank',
+ accountNumber: '7732',
+ originalBalance: 2800,
+ currentBalance: 950,
+ interestRate: 18.49,
+ minimumPayment: 35,
+ dueDay: 8,
+ createdAt: '2024-03-05',
+ updatedAt: '2024-12-01',
+ },
+ {
+ id: 'al1',
+ name: 'Tesla Model 3 Loan',
+ categoryId: 'auto-loans',
+ institution: 'Tesla Finance',
+ accountNumber: '9901',
+ originalBalance: 42000,
+ currentBalance: 15000,
+ interestRate: 4.99,
+ minimumPayment: 650,
+ dueDay: 1,
+ createdAt: '2021-06-15',
+ updatedAt: '2024-12-01',
+ },
+ {
+ id: 'sl1',
+ name: 'Federal Student Loan',
+ categoryId: 'student-loans',
+ institution: 'Dept of Education',
+ originalBalance: 45000,
+ currentBalance: 28000,
+ interestRate: 5.5,
+ minimumPayment: 320,
+ dueDay: 25,
+ createdAt: '2018-09-01',
+ updatedAt: '2024-12-01',
+ },
+ {
+ id: 'pl1',
+ name: 'Home Improvement Loan',
+ categoryId: 'personal-loans',
+ institution: 'SoFi',
+ accountNumber: '3344',
+ originalBalance: 15000,
+ currentBalance: 8500,
+ interestRate: 8.99,
+ minimumPayment: 285,
+ dueDay: 12,
+ createdAt: '2023-08-20',
+ 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'},
+];
+
const initialState: DebtsState = {
- debts: [],
- payments: [],
+ categories: defaultCategories,
+ accounts: mockAccounts,
+ payments: mockPayments,
isLoading: false,
error: null,
};
@@ -46,26 +162,61 @@ const debtsSlice = createSlice({
setError: (state, action: PayloadAction
) => {
state.error = action.payload;
},
- addDebt: (state, action: PayloadAction) => {
- state.debts.push(action.payload);
+ // Category actions
+ addCategory: (state, action: PayloadAction) => {
+ state.categories.push(action.payload);
},
- updateDebt: (state, action: PayloadAction) => {
- const index = state.debts.findIndex(d => d.id === action.payload.id);
- if (index !== -1) state.debts[index] = action.payload;
+ updateCategory: (state, action: PayloadAction) => {
+ const index = state.categories.findIndex(c => c.id === action.payload.id);
+ if (index !== -1) state.categories[index] = action.payload;
},
- removeDebt: (state, action: PayloadAction) => {
- state.debts = state.debts.filter(d => d.id !== action.payload);
- state.payments = state.payments.filter(p => p.debtId !== action.payload);
+ removeCategory: (state, action: PayloadAction) => {
+ state.categories = state.categories.filter(c => c.id !== action.payload);
+ // Move accounts in this category to 'other'
+ state.accounts.forEach(a => {
+ if (a.categoryId === action.payload) a.categoryId = 'other';
+ });
},
+ setCategories: (state, action: PayloadAction) => {
+ state.categories = action.payload;
+ },
+ // Account actions
+ addAccount: (state, action: PayloadAction) => {
+ state.accounts.push(action.payload);
+ },
+ updateAccount: (state, action: PayloadAction) => {
+ const index = state.accounts.findIndex(a => a.id === action.payload.id);
+ if (index !== -1) state.accounts[index] = action.payload;
+ },
+ removeAccount: (state, action: PayloadAction) => {
+ state.accounts = state.accounts.filter(a => a.id !== action.payload);
+ state.payments = state.payments.filter(p => p.accountId !== action.payload);
+ },
+ setAccounts: (state, action: PayloadAction) => {
+ state.accounts = action.payload;
+ },
+ // Payment actions
addPayment: (state, action: PayloadAction) => {
state.payments.push(action.payload);
+ // Update account balance
+ const account = state.accounts.find(a => a.id === action.payload.accountId);
+ if (account) {
+ account.currentBalance = Math.max(0, account.currentBalance - action.payload.amount);
+ account.updatedAt = new Date().toISOString();
+ }
},
removePayment: (state, action: PayloadAction) => {
+ const payment = state.payments.find(p => p.id === action.payload);
+ if (payment) {
+ // Restore account balance
+ const account = state.accounts.find(a => a.id === payment.accountId);
+ if (account) {
+ account.currentBalance += payment.amount;
+ account.updatedAt = new Date().toISOString();
+ }
+ }
state.payments = state.payments.filter(p => p.id !== action.payload);
},
- setDebts: (state, action: PayloadAction) => {
- state.debts = action.payload;
- },
setPayments: (state, action: PayloadAction) => {
state.payments = action.payload;
},
@@ -75,14 +226,17 @@ const debtsSlice = createSlice({
export const {
setLoading,
setError,
- addDebt,
- updateDebt,
- removeDebt,
+ addCategory,
+ updateCategory,
+ removeCategory,
+ setCategories,
+ addAccount,
+ updateAccount,
+ removeAccount,
+ setAccounts,
addPayment,
removePayment,
- setDebts,
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 755192d..036d8c1 100644
--- a/frontend-web/src/store/slices/invoicesSlice.ts
+++ b/frontend-web/src/store/slices/invoicesSlice.ts
@@ -42,9 +42,133 @@ export interface InvoicesState {
error: string | null;
}
+// Mock data for development
+const mockClients: Client[] = [
+ {
+ id: 'c1',
+ name: 'Acme Corp',
+ email: 'billing@acme.com',
+ phone: '555-0100',
+ company: 'Acme Corporation',
+ address: '123 Business Ave, Suite 400, San Francisco, CA 94102',
+ createdAt: '2024-01-10',
+ },
+ {
+ id: 'c2',
+ name: 'TechStart Inc',
+ email: 'accounts@techstart.io',
+ phone: '555-0200',
+ company: 'TechStart Inc',
+ address: '456 Innovation Blvd, Austin, TX 78701',
+ createdAt: '2024-02-15',
+ },
+ {
+ id: 'c3',
+ name: 'Sarah Mitchell',
+ email: 'sarah@mitchell.design',
+ company: 'Mitchell Design Studio',
+ createdAt: '2024-03-22',
+ },
+ {
+ id: 'c4',
+ name: 'Global Media LLC',
+ email: 'finance@globalmedia.com',
+ phone: '555-0400',
+ company: 'Global Media LLC',
+ address: '789 Media Row, New York, NY 10001',
+ createdAt: '2024-04-08',
+ },
+];
+
+const mockInvoices: Invoice[] = [
+ {
+ id: 'inv1',
+ invoiceNumber: 'INV-2024-001',
+ clientId: 'c1',
+ status: 'paid',
+ issueDate: '2024-10-01',
+ 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},
+ ],
+ subtotal: 12500,
+ tax: 0,
+ total: 12500,
+ createdAt: '2024-10-01',
+ updatedAt: '2024-10-15',
+ },
+ {
+ id: 'inv2',
+ invoiceNumber: 'INV-2024-002',
+ clientId: 'c2',
+ status: 'paid',
+ issueDate: '2024-10-15',
+ dueDate: '2024-11-14',
+ 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',
+ },
+ {
+ id: 'inv3',
+ invoiceNumber: 'INV-2024-003',
+ clientId: 'c1',
+ status: 'sent',
+ issueDate: '2024-11-01',
+ 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},
+ ],
+ subtotal: 12500,
+ tax: 0,
+ total: 12500,
+ createdAt: '2024-11-01',
+ updatedAt: '2024-11-01',
+ },
+ {
+ id: 'inv4',
+ invoiceNumber: 'INV-2024-004',
+ clientId: 'c3',
+ status: 'overdue',
+ issueDate: '2024-10-20',
+ dueDate: '2024-11-20',
+ 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',
+ },
+ {
+ id: 'inv5',
+ invoiceNumber: 'INV-2024-005',
+ clientId: 'c4',
+ status: 'draft',
+ issueDate: '2024-12-01',
+ 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},
+ ],
+ subtotal: 15000,
+ tax: 0,
+ total: 15000,
+ createdAt: '2024-12-01',
+ updatedAt: '2024-12-01',
+ },
+];
+
const initialState: InvoicesState = {
- clients: [],
- invoices: [],
+ clients: mockClients,
+ invoices: mockInvoices,
isLoading: false,
error: null,
};
diff --git a/frontend-web/src/store/slices/netWorthSlice.ts b/frontend-web/src/store/slices/netWorthSlice.ts
index 4445647..fd39c7e 100644
--- a/frontend-web/src/store/slices/netWorthSlice.ts
+++ b/frontend-web/src/store/slices/netWorthSlice.ts
@@ -32,10 +32,35 @@ export interface NetWorthState {
error: string | null;
}
+// Mock data for development
+const mockAssets: Asset[] = [
+ {id: 'a1', name: 'Chase Checking', type: 'cash', value: 12500, updatedAt: '2024-12-01'},
+ {id: 'a2', name: 'Ally Savings', type: 'cash', value: 35000, updatedAt: '2024-12-01'},
+ {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'},
+];
+
+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'},
+];
+
+const mockSnapshots: NetWorthSnapshot[] = [
+ {id: 's1', date: '2024-07-01', totalAssets: 650000, totalLiabilities: 380000, netWorth: 270000},
+ {id: 's2', date: '2024-08-01', totalAssets: 665000, totalLiabilities: 375000, netWorth: 290000},
+ {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},
+];
+
const initialState: NetWorthState = {
- assets: [],
- liabilities: [],
- snapshots: [],
+ assets: mockAssets,
+ liabilities: mockLiabilities,
+ snapshots: mockSnapshots,
isLoading: false,
error: null,
};