refactor(bills): migrate BillsPage to React Query (R5.2)
Reuses the shared useBills/useCategories caches (+ new useBillTemplates/ useDeletedBills), so bill mutations here now also refresh the Tracker/badge live via the shared ['bills'] key. Optimistic list edits (delete, reorder) write through queryClient.setQueryData; post-mutation load() calls became a refresh() that invalidates the 4 page queries. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
bb024ce161
commit
9cb254ea13
|
|
@ -81,3 +81,19 @@ export function useAnalyticsSummary(params) {
|
|||
placeholderData: keepPreviousData,
|
||||
});
|
||||
}
|
||||
|
||||
export function useBillTemplates() {
|
||||
return useQuery({
|
||||
queryKey: ['bill-templates'],
|
||||
queryFn: () => api.billTemplates(),
|
||||
staleTime: 1000 * 60 * 5,
|
||||
});
|
||||
}
|
||||
|
||||
export function useDeletedBills() {
|
||||
return useQuery({
|
||||
queryKey: ['deleted-bills'],
|
||||
queryFn: () => api.deletedBills().catch(() => []), // non-critical: never block the page
|
||||
staleTime: 1000 * 60 * 5,
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
import React, { useEffect, useState, useCallback, useMemo, useRef } from 'react';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { useBills, useCategories, useBillTemplates, useDeletedBills } from '@/hooks/useQueries';
|
||||
import { Plus, ChevronRight, SlidersHorizontal, Search, Trash2 } from 'lucide-react';
|
||||
import { toast } from 'sonner';
|
||||
import { Button } from '@/components/ui/button';
|
||||
|
|
@ -599,12 +601,28 @@ function HistoryVisibilityDialog({ bill, onClose, onSaved }) {
|
|||
// ── Bills Page ─────────────────────────────────────────────────────────────
|
||||
export default function BillsPage() {
|
||||
const [searchParams] = useSearchParams();
|
||||
const [bills, setBills] = useState([]);
|
||||
const [categories, setCategories] = useState([]);
|
||||
const [savedTemplates, setSavedTemplates] = useState([]);
|
||||
const [deletedBills, setDeletedBills] = useState([]);
|
||||
const queryClient = useQueryClient();
|
||||
// React Query is the source of truth (reuses the shared ['bills']/['categories']
|
||||
// caches, so bill mutations here also refresh the Tracker/badge). Optimistic
|
||||
// list edits below write to the cache via setQueryData.
|
||||
const { data: bills = [], isPending: billsPending } = useBills();
|
||||
const { data: categories = [] } = useCategories();
|
||||
const { data: savedTemplates = [] } = useBillTemplates();
|
||||
const { data: deletedBills = [] } = useDeletedBills();
|
||||
const loading = billsPending;
|
||||
const setBills = useCallback(
|
||||
(updater) => queryClient.setQueryData(['bills'], prev => (
|
||||
typeof updater === 'function' ? updater(prev || []) : updater
|
||||
)),
|
||||
[queryClient],
|
||||
);
|
||||
const refresh = useCallback(() => Promise.all([
|
||||
queryClient.invalidateQueries({ queryKey: ['bills'] }),
|
||||
queryClient.invalidateQueries({ queryKey: ['categories'] }),
|
||||
queryClient.invalidateQueries({ queryKey: ['bill-templates'] }),
|
||||
queryClient.invalidateQueries({ queryKey: ['deleted-bills'] }),
|
||||
]), [queryClient]);
|
||||
const [showDeleted, setShowDeleted] = useState(false);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [showInactive, setShowInactive] = useState(false);
|
||||
const [search, setSearch] = useState('');
|
||||
const [draggingId, setDraggingId] = useState(null);
|
||||
|
|
@ -641,26 +659,6 @@ export default function BillsPage() {
|
|||
localStorage.setItem(BILLS_SORT_KEY, billsSort);
|
||||
}, [billsSort]);
|
||||
|
||||
const load = useCallback(async () => {
|
||||
try {
|
||||
const [billsRes, catRes, templateRes, deletedRes] = await Promise.all([
|
||||
api.allBills(),
|
||||
api.categories(),
|
||||
api.billTemplates(),
|
||||
api.deletedBills().catch(() => []), // non-critical: never block the page
|
||||
]);
|
||||
setBills(billsRes || []);
|
||||
setCategories(catRes || []);
|
||||
setSavedTemplates(templateRes || []);
|
||||
setDeletedBills(deletedRes || []);
|
||||
} catch (err) {
|
||||
toast.error(err.message);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => { load(); }, [load]);
|
||||
|
||||
useEffect(() => {
|
||||
const querySearch = searchParams.get('search') || '';
|
||||
|
|
@ -745,7 +743,7 @@ export default function BillsPage() {
|
|||
if (bill.active && reason) payload.inactive_reason = reason;
|
||||
await api.updateBill(bill.id, payload);
|
||||
toast.success(bill.active ? 'Bill deactivated' : 'Bill activated');
|
||||
load();
|
||||
refresh();
|
||||
} catch (err) {
|
||||
toast.error(err.message);
|
||||
}
|
||||
|
|
@ -771,7 +769,7 @@ export default function BillsPage() {
|
|||
try {
|
||||
await api.restoreBill(bill.id);
|
||||
toast.success(`"${bill.name}" restored`);
|
||||
load();
|
||||
refresh();
|
||||
} catch (err) {
|
||||
toast.error(err.message || 'Failed to restore bill');
|
||||
}
|
||||
|
|
@ -780,7 +778,7 @@ export default function BillsPage() {
|
|||
});
|
||||
setDeleteTarget(null);
|
||||
setDeleteConfirmed(false);
|
||||
load();
|
||||
refresh();
|
||||
} catch (err) {
|
||||
toast.error(err.message || 'Failed to delete bill');
|
||||
} finally {
|
||||
|
|
@ -792,7 +790,7 @@ export default function BillsPage() {
|
|||
try {
|
||||
await api.restoreBill(bill.id);
|
||||
toast.success(`"${bill.name}" restored`);
|
||||
await load();
|
||||
await refresh();
|
||||
} catch (err) {
|
||||
toast.error(err.message || 'Failed to restore bill');
|
||||
}
|
||||
|
|
@ -850,10 +848,10 @@ export default function BillsPage() {
|
|||
try {
|
||||
await api.reorderBills(reorderPayload(nextBills));
|
||||
toast.success('Bill order saved');
|
||||
load();
|
||||
refresh();
|
||||
} catch (err) {
|
||||
toast.error(err.message || 'Failed to save bill order');
|
||||
load();
|
||||
refresh();
|
||||
} finally {
|
||||
setMovingBillId(null);
|
||||
}
|
||||
|
|
@ -1155,7 +1153,7 @@ export default function BillsPage() {
|
|||
initialBill={modal.initialBill}
|
||||
categories={categories}
|
||||
onClose={() => setModal(null)}
|
||||
onSave={load}
|
||||
onSave={refresh}
|
||||
onDuplicate={handleDuplicateBill}
|
||||
/>
|
||||
)}
|
||||
|
|
@ -1164,7 +1162,7 @@ export default function BillsPage() {
|
|||
<HistoryVisibilityDialog
|
||||
bill={historyTarget}
|
||||
onClose={() => setHistoryTarget(null)}
|
||||
onSaved={load}
|
||||
onSaved={refresh}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue