feat: advanced features — dashboard, budgets, history, export

Backend:
- GET /api/v1/dashboard?month=YYYY-MM: KPIs, by_category, 6-month trend, budget alerts
- GET/POST/PUT/DELETE /api/v1/budgets: budget envelopes with spent_cents/remaining_cents
- POST /api/v1/budgets/rollover: copy budgets from M-1 to target month
- GET /api/v1/history?year=YYYY: monthly summary for the year
- GET /api/v1/export/csv|pdf?month=YYYY-MM: StreamingResponse exports (WeasyPrint PDF)
- New schemas: dashboard, budget, history
- Services: dashboard_service, budget_service
- Routers mounted in main.py

Frontend:
- DashboardPage: 4 KPI cards, PieChart (expenses by category), BarChart (6-month trend),
  month navigation, budget alert badges, CSV/PDF export buttons
- BudgetsPage: progress bars (green/orange/red), create/edit form, delete, rollover M-1
- HistoryPage: annual table with month click → dashboard, LineChart revenues/expenses
- CategoriesPage: list by type with create/edit/delete (was missing from Phase 2)
- TransactionsPage: added CSV/PDF export buttons
- App.tsx: full routing with ProtectedRoute + Layout for all authenticated pages
- New hooks: useDashboard, useBudgets (with mutations), useHistory
- API types + client updated for all new endpoints

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Nox (OpenClaw)
2026-03-17 16:48:26 +00:00
parent 9f7378cb69
commit e3fac99045
21 changed files with 2026 additions and 12 deletions
+49
View File
@@ -0,0 +1,49 @@
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import {
createBudget,
deleteBudget,
getBudgets,
rolloverBudgets,
updateBudget,
} from "../api/client";
import type { CreateBudgetPayload, UpdateBudgetPayload } from "../api/types";
export function useBudgets(month?: string) {
return useQuery({
queryKey: ["budgets", month],
queryFn: () => getBudgets(month),
});
}
export function useCreateBudget() {
const qc = useQueryClient();
return useMutation({
mutationFn: (payload: CreateBudgetPayload) => createBudget(payload),
onSuccess: () => qc.invalidateQueries({ queryKey: ["budgets"] }),
});
}
export function useUpdateBudget() {
const qc = useQueryClient();
return useMutation({
mutationFn: ({ id, payload }: { id: string; payload: UpdateBudgetPayload }) =>
updateBudget(id, payload),
onSuccess: () => qc.invalidateQueries({ queryKey: ["budgets"] }),
});
}
export function useDeleteBudget() {
const qc = useQueryClient();
return useMutation({
mutationFn: (id: string) => deleteBudget(id),
onSuccess: () => qc.invalidateQueries({ queryKey: ["budgets"] }),
});
}
export function useRolloverBudgets() {
const qc = useQueryClient();
return useMutation({
mutationFn: (month: string) => rolloverBudgets(month),
onSuccess: () => qc.invalidateQueries({ queryKey: ["budgets"] }),
});
}
+9
View File
@@ -0,0 +1,9 @@
import { useQuery } from "@tanstack/react-query";
import { getDashboard } from "../api/client";
export function useDashboard(month?: string) {
return useQuery({
queryKey: ["dashboard", month],
queryFn: () => getDashboard(month),
});
}
+9
View File
@@ -0,0 +1,9 @@
import { useQuery } from "@tanstack/react-query";
import { getHistory } from "../api/client";
export function useHistory(year?: number) {
return useQuery({
queryKey: ["history", year],
queryFn: () => getHistory(year),
});
}