B2: TypeScript + React 19 migration (T1–B18) #87

Closed
null wants to merge 69 commits from branch/b2-typescript into branch/b1-react-query
3 changed files with 39 additions and 6 deletions
Showing only changes of commit 4af738f947 - Show all commits

View File

@ -1,4 +1,4 @@
import type { Bill, Payment, TrackerResponse } from '@/types';
import type { Bill, Category, Payment, TrackerResponse } from '@/types';
// Fetch CSRF token from the server once and cache in memory.
// The cookie is httpOnly so document.cookie cannot access it directly.
@ -332,10 +332,10 @@ export const api = {
abandonSnowballPlan: (id: Id) => post(`/snowball/plans/${id}/abandon`, {}),
// Categories
categories: () => get('/categories'),
createCategory: (data: Body) => post('/categories', data),
categories: () => get<Category[]>('/categories'),
createCategory: (data: Body) => post<Category>('/categories', data),
reorderCategories: (order: Body) => put('/categories/reorder', order),
updateCategory: (id: Id, data: Body) => put(`/categories/${id}`, data),
updateCategory: (id: Id, data: Body) => put<Category>(`/categories/${id}`, data),
toggleCategorySpending: (id: Id, val: unknown) => patch(`/categories/${id}/spending`, { spending_enabled: val }),
deleteCategory: (id: Id) => del(`/categories/${id}`),
restoreCategory: (id: Id) => post(`/categories/${id}/restore`),

View File

@ -184,8 +184,8 @@ export function useSpendingCategories() {
return useQuery({
queryKey: ['spending-categories'],
queryFn: async () => {
const d = await api.categories() as any;
return (d.categories || d || []).filter((c: any) => !c.deleted_at && c.spending_enabled);
const cats = await api.categories();
return cats.filter(c => !c.deleted_at && c.spending_enabled);
},
staleTime: 1000 * 60 * 5,
});

View File

@ -30,6 +30,39 @@ export interface Payment {
[key: string]: unknown;
}
// A category's per-bill rollup (categories list endpoint). `total_paid` here is a
// raw SQL sum (not run through fromCents), so it is deliberately NOT branded.
export interface CategoryBillSummary {
id: number;
name: string;
active: boolean;
payment_count: number;
total_paid: number;
last_paid_date: string | null;
[key: string]: unknown;
}
// A spending/bill category. Has no money field of its own (budgets live on the
// spending endpoints). The list endpoint adds bill rollups; create/update return
// the bare row — so the rollup fields are optional.
export interface Category {
id: number;
user_id?: number;
name: string;
sort_order?: number;
spending_enabled: boolean;
group_id: number | null;
created_at?: string;
updated_at?: string;
bill_count?: number;
active_bill_count?: number;
inactive_bill_count?: number;
payment_count?: number;
bill_names?: string[];
bills?: CategoryBillSummary[];
[key: string]: unknown;
}
// A bill as serialized by the server (billsService.serializeBill spreads the DB
// row and converts the three money columns to dollars). SQLite booleans come back
// as 0/1 integers. Extra joined columns (sparkline, has_merchant_rule, …) pass