mirror of
https://github.com/escalante29/WealthySmart.git
synced 2026-05-19 08:48:48 +02:00
All checks were successful
Deploy to VPS / deploy (push) Successful in 21s
- 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>
104 lines
3.0 KiB
TypeScript
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()]),
|
|
};
|
|
}
|