Update frontend-web dependencies and implement routing structure

- Added new dependencies: @fontsource-variable/geist, @radix-ui/react-separator, @radix-ui/react-tooltip, date-fns, react-router-dom, and recharts.
- Updated index.html to set the HTML class to "dark" for dark mode support.
- Refactored App component to implement routing with React Router, replacing the previous UI structure with a layout and multiple pages (NetWorth, Debts, Invoices, Clients).
- Enhanced CSS for dark mode and added depth utilities for improved UI aesthetics.
- Expanded Redux store to include net worth, debts, and invoices slices for comprehensive state management.
This commit is contained in:
2025-12-07 10:49:43 -05:00
parent f1f0032bca
commit bf00261e1d
19 changed files with 1283 additions and 55 deletions

View File

@@ -5,6 +5,51 @@ export type {RootState, AppDispatch} from './store';
// Hooks
export {useAppDispatch, useAppSelector} from './hooks';
// User slice exports
export {setLoading, setUser, clearUser, setError} from './slices/userSlice';
// User slice
export {setLoading as setUserLoading, setUser, clearUser, setError as setUserError} from './slices/userSlice';
export type {User, UserState} from './slices/userSlice';
// Net Worth slice
export {
setLoading as setNetWorthLoading,
setError as setNetWorthError,
addAsset,
updateAsset,
removeAsset,
addLiability,
updateLiability,
removeLiability,
addSnapshot,
setSnapshots,
} from './slices/netWorthSlice';
export type {Asset, Liability, NetWorthSnapshot, NetWorthState} from './slices/netWorthSlice';
// Debts slice
export {
setLoading as setDebtsLoading,
setError as setDebtsError,
addDebt,
updateDebt,
removeDebt,
addPayment,
removePayment,
setDebts,
setPayments,
} from './slices/debtsSlice';
export type {Debt, DebtPayment, DebtsState} from './slices/debtsSlice';
// Invoices slice
export {
setLoading as setInvoicesLoading,
setError as setInvoicesError,
addClient,
updateClient,
removeClient,
setClients,
addInvoice,
updateInvoice,
removeInvoice,
setInvoices,
updateInvoiceStatus,
} from './slices/invoicesSlice';
export type {Client, Invoice, InvoiceLineItem, InvoicesState} from './slices/invoicesSlice';

View File

@@ -0,0 +1,88 @@
import {createSlice, type PayloadAction} from '@reduxjs/toolkit';
export interface Debt {
id: string;
name: string;
type: 'credit_card' | 'personal_loan' | 'auto_loan' | 'student_loan' | 'mortgage' | 'other';
originalBalance: number;
currentBalance: number;
interestRate: number;
minimumPayment: number;
dueDay: number;
lender: string;
createdAt: string;
updatedAt: string;
}
export interface DebtPayment {
id: string;
debtId: string;
amount: number;
date: string;
note?: string;
}
export interface DebtsState {
debts: Debt[];
payments: DebtPayment[];
isLoading: boolean;
error: string | null;
}
const initialState: DebtsState = {
debts: [],
payments: [],
isLoading: false,
error: null,
};
const debtsSlice = createSlice({
name: 'debts',
initialState,
reducers: {
setLoading: (state, action: PayloadAction<boolean>) => {
state.isLoading = action.payload;
},
setError: (state, action: PayloadAction<string | null>) => {
state.error = action.payload;
},
addDebt: (state, action: PayloadAction<Debt>) => {
state.debts.push(action.payload);
},
updateDebt: (state, action: PayloadAction<Debt>) => {
const index = state.debts.findIndex(d => d.id === action.payload.id);
if (index !== -1) state.debts[index] = action.payload;
},
removeDebt: (state, action: PayloadAction<string>) => {
state.debts = state.debts.filter(d => d.id !== action.payload);
state.payments = state.payments.filter(p => p.debtId !== action.payload);
},
addPayment: (state, action: PayloadAction<DebtPayment>) => {
state.payments.push(action.payload);
},
removePayment: (state, action: PayloadAction<string>) => {
state.payments = state.payments.filter(p => p.id !== action.payload);
},
setDebts: (state, action: PayloadAction<Debt[]>) => {
state.debts = action.payload;
},
setPayments: (state, action: PayloadAction<DebtPayment[]>) => {
state.payments = action.payload;
},
},
});
export const {
setLoading,
setError,
addDebt,
updateDebt,
removeDebt,
addPayment,
removePayment,
setDebts,
setPayments,
} = debtsSlice.actions;
export default debtsSlice.reducer;

View File

@@ -0,0 +1,118 @@
import {createSlice, type PayloadAction} from '@reduxjs/toolkit';
export interface Client {
id: string;
name: string;
email: string;
phone?: string;
company?: string;
address?: string;
notes?: string;
createdAt: string;
}
export interface InvoiceLineItem {
id: string;
description: string;
quantity: number;
unitPrice: number;
total: number;
}
export interface Invoice {
id: string;
invoiceNumber: string;
clientId: string;
status: 'draft' | 'sent' | 'paid' | 'overdue' | 'cancelled';
issueDate: string;
dueDate: string;
lineItems: InvoiceLineItem[];
subtotal: number;
tax: number;
total: number;
notes?: string;
createdAt: string;
updatedAt: string;
}
export interface InvoicesState {
clients: Client[];
invoices: Invoice[];
isLoading: boolean;
error: string | null;
}
const initialState: InvoicesState = {
clients: [],
invoices: [],
isLoading: false,
error: null,
};
const invoicesSlice = createSlice({
name: 'invoices',
initialState,
reducers: {
setLoading: (state, action: PayloadAction<boolean>) => {
state.isLoading = action.payload;
},
setError: (state, action: PayloadAction<string | null>) => {
state.error = action.payload;
},
// Client actions
addClient: (state, action: PayloadAction<Client>) => {
state.clients.push(action.payload);
},
updateClient: (state, action: PayloadAction<Client>) => {
const index = state.clients.findIndex(c => c.id === action.payload.id);
if (index !== -1) state.clients[index] = action.payload;
},
removeClient: (state, action: PayloadAction<string>) => {
state.clients = state.clients.filter(c => c.id !== action.payload);
},
setClients: (state, action: PayloadAction<Client[]>) => {
state.clients = action.payload;
},
// Invoice actions
addInvoice: (state, action: PayloadAction<Invoice>) => {
state.invoices.push(action.payload);
},
updateInvoice: (state, action: PayloadAction<Invoice>) => {
const index = state.invoices.findIndex(i => i.id === action.payload.id);
if (index !== -1) state.invoices[index] = action.payload;
},
removeInvoice: (state, action: PayloadAction<string>) => {
state.invoices = state.invoices.filter(i => i.id !== action.payload);
},
setInvoices: (state, action: PayloadAction<Invoice[]>) => {
state.invoices = action.payload;
},
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 {
setLoading,
setError,
addClient,
updateClient,
removeClient,
setClients,
addInvoice,
updateInvoice,
removeInvoice,
setInvoices,
updateInvoiceStatus,
} = invoicesSlice.actions;
export default invoicesSlice.reducer;

View File

@@ -0,0 +1,96 @@
import {createSlice, type PayloadAction} from '@reduxjs/toolkit';
export interface Asset {
id: string;
name: string;
type: 'cash' | 'investment' | 'property' | 'vehicle' | 'other';
value: number;
updatedAt: string;
}
export interface Liability {
id: string;
name: string;
type: 'credit_card' | 'loan' | 'mortgage' | 'other';
balance: number;
updatedAt: string;
}
export interface NetWorthSnapshot {
id: string;
date: string;
totalAssets: number;
totalLiabilities: number;
netWorth: number;
}
export interface NetWorthState {
assets: Asset[];
liabilities: Liability[];
snapshots: NetWorthSnapshot[];
isLoading: boolean;
error: string | null;
}
const initialState: NetWorthState = {
assets: [],
liabilities: [],
snapshots: [],
isLoading: false,
error: null,
};
const netWorthSlice = createSlice({
name: 'netWorth',
initialState,
reducers: {
setLoading: (state, action: PayloadAction<boolean>) => {
state.isLoading = action.payload;
},
setError: (state, action: PayloadAction<string | null>) => {
state.error = action.payload;
},
addAsset: (state, action: PayloadAction<Asset>) => {
state.assets.push(action.payload);
},
updateAsset: (state, action: PayloadAction<Asset>) => {
const index = state.assets.findIndex(a => a.id === action.payload.id);
if (index !== -1) state.assets[index] = action.payload;
},
removeAsset: (state, action: PayloadAction<string>) => {
state.assets = state.assets.filter(a => a.id !== action.payload);
},
addLiability: (state, action: PayloadAction<Liability>) => {
state.liabilities.push(action.payload);
},
updateLiability: (state, action: PayloadAction<Liability>) => {
const index = state.liabilities.findIndex(l => l.id === action.payload.id);
if (index !== -1) state.liabilities[index] = action.payload;
},
removeLiability: (state, action: PayloadAction<string>) => {
state.liabilities = state.liabilities.filter(l => l.id !== action.payload);
},
addSnapshot: (state, action: PayloadAction<NetWorthSnapshot>) => {
state.snapshots.push(action.payload);
},
setSnapshots: (state, action: PayloadAction<NetWorthSnapshot[]>) => {
state.snapshots = action.payload;
},
},
});
export const {
setLoading,
setError,
addAsset,
updateAsset,
removeAsset,
addLiability,
updateLiability,
removeLiability,
addSnapshot,
setSnapshots,
} = netWorthSlice.actions;
export default netWorthSlice.reducer;

View File

@@ -1,10 +1,16 @@
import {configureStore} from '@reduxjs/toolkit';
import userReducer from './slices/userSlice';
import netWorthReducer from './slices/netWorthSlice';
import debtsReducer from './slices/debtsSlice';
import invoicesReducer from './slices/invoicesSlice';
export const store = configureStore({
reducer: {
user: userReducer
}
user: userReducer,
netWorth: netWorthReducer,
debts: debtsReducer,
invoices: invoicesReducer,
},
});
export type RootState = ReturnType<typeof store.getState>;