Files
WealthySmart/frontend/src/hooks/useBudget.ts
Carlos Escalante b68129a171
All checks were successful
Deploy to VPS / deploy (push) Successful in 21s
Add cumulative balance tracking with editable overrides
- New BalanceOverride table for manual balance adjustments per month
- Cumulative balance computation with cross-year carryover
- Three new columns: Acum. Anterior, Neto Mes, Balance Acum.
- Inline editing on Balance Acum. cell (pencil icon for overrides)
- Year navigation clamped to 2026–2030, fresh start at March 2026
- PUT/DELETE /budget/balance-override/{year}/{month} endpoints

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 12:03:43 -06:00

104 lines
3.0 KiB
TypeScript

import { useState, useEffect, useCallback } from 'react';
import {
type YearlyProjection,
type MonthlyDetail,
type RecurringItem,
type RecurringItemCreate,
type RecurringItemUpdate,
getYearlyProjection,
getMonthlyDetail,
getRecurringItems,
createRecurringItem,
updateRecurringItem as apiUpdateItem,
deleteRecurringItem as apiDeleteItem,
upsertBalanceOverride,
deleteBalanceOverride,
} from '@/api';
export function useBudget(initialYear: number) {
const [year, setYear] = useState(initialYear);
const [selectedMonth, setSelectedMonth] = useState<number>(new Date().getMonth() + 1);
const [projection, setProjection] = useState<YearlyProjection | null>(null);
const [monthDetail, setMonthDetail] = useState<MonthlyDetail | null>(null);
const [recurringItems, setRecurringItems] = useState<RecurringItem[]>([]);
const [loading, setLoading] = useState(true);
const [monthLoading, setMonthLoading] = useState(false);
const fetchProjection = useCallback(async () => {
setLoading(true);
try {
const { data } = await getYearlyProjection(year);
setProjection(data);
} finally {
setLoading(false);
}
}, [year]);
const fetchMonthDetail = useCallback(async () => {
setMonthLoading(true);
try {
const { data } = await getMonthlyDetail(year, selectedMonth);
setMonthDetail(data);
} finally {
setMonthLoading(false);
}
}, [year, selectedMonth]);
const fetchRecurringItems = useCallback(async () => {
const { data } = await getRecurringItems();
setRecurringItems(data);
}, []);
useEffect(() => {
fetchProjection();
fetchRecurringItems();
}, [fetchProjection, fetchRecurringItems]);
useEffect(() => {
fetchMonthDetail();
}, [fetchMonthDetail]);
const addItem = async (data: RecurringItemCreate) => {
await createRecurringItem(data);
await Promise.all([fetchProjection(), fetchMonthDetail(), fetchRecurringItems()]);
};
const updateItem = async (id: number, data: RecurringItemUpdate) => {
await apiUpdateItem(id, data);
await Promise.all([fetchProjection(), fetchMonthDetail(), fetchRecurringItems()]);
};
const deleteItem = async (id: number) => {
await apiDeleteItem(id);
await Promise.all([fetchProjection(), fetchMonthDetail(), fetchRecurringItems()]);
};
const saveBalanceOverride = async (overrideYear: number, month: number, value: number) => {
await upsertBalanceOverride(overrideYear, month, value);
await fetchProjection();
};
const clearBalanceOverride = async (overrideYear: number, month: number) => {
await deleteBalanceOverride(overrideYear, month);
await fetchProjection();
};
return {
year,
setYear,
selectedMonth,
setSelectedMonth,
projection,
monthDetail,
recurringItems,
loading,
monthLoading,
addItem,
updateItem,
deleteItem,
saveBalanceOverride,
clearBalanceOverride,
refresh: () => Promise.all([fetchProjection(), fetchMonthDetail(), fetchRecurringItems()]),
};
}