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:
@@ -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';
|
||||
|
||||
88
frontend-web/src/store/slices/debtsSlice.ts
Normal file
88
frontend-web/src/store/slices/debtsSlice.ts
Normal 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;
|
||||
|
||||
118
frontend-web/src/store/slices/invoicesSlice.ts
Normal file
118
frontend-web/src/store/slices/invoicesSlice.ts
Normal 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;
|
||||
|
||||
96
frontend-web/src/store/slices/netWorthSlice.ts
Normal file
96
frontend-web/src/store/slices/netWorthSlice.ts
Normal 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;
|
||||
|
||||
@@ -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>;
|
||||
|
||||
Reference in New Issue
Block a user