B1: React Query deeper adoption (P1–P3) #86
|
|
@ -98,6 +98,18 @@ export function useDeletedBills() {
|
|||
});
|
||||
}
|
||||
|
||||
export function useSummary(year, month) {
|
||||
return useQuery({
|
||||
queryKey: ['summary', year, month],
|
||||
queryFn: () => api.summary(year, month),
|
||||
staleTime: 1000 * 60 * 2,
|
||||
placeholderData: keepPreviousData,
|
||||
// Editable form fields (starting amounts, income) are seeded from this
|
||||
// result via an effect; don't let a focus refetch reset a mid-edit.
|
||||
refetchOnWindowFocus: false,
|
||||
});
|
||||
}
|
||||
|
||||
export function useSubscriptions() {
|
||||
return useQuery({
|
||||
queryKey: ['subscriptions'],
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { useSummary } from '@/hooks/useQueries';
|
||||
import { toast } from 'sonner';
|
||||
import {
|
||||
ArrowDown,
|
||||
|
|
@ -235,10 +237,12 @@ function ExpenseRow({ expense, moveControls, dragProps }) {
|
|||
|
||||
export default function SummaryPage() {
|
||||
const [selected, setSelected] = useState(selectedFromToday);
|
||||
const [data, setData] = useState(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const queryClient = useQueryClient();
|
||||
const { data = null, isPending: loading, error: queryError } = useSummary(selected.year, selected.month);
|
||||
const setData = useCallback((u) => queryClient.setQueryData(['summary', selected.year, selected.month],
|
||||
prev => (typeof u === 'function' ? u(prev) : u)), [queryClient, selected.year, selected.month]);
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [error, setError] = useState('');
|
||||
const error = queryError ? (queryError.message || 'Summary could not be loaded.') : '';
|
||||
const [startingFirst, setStartingFirst] = useState('0');
|
||||
const [startingFifteenth, setStartingFifteenth] = useState('0');
|
||||
const [startingOther, setStartingOther] = useState('0');
|
||||
|
|
@ -250,37 +254,27 @@ export default function SummaryPage() {
|
|||
const [dropTargetId, setDropTargetId] = useState(null);
|
||||
const [movingBillId, setMovingBillId] = useState(null);
|
||||
|
||||
const requestSeq = useRef(0);
|
||||
const loadSummary = useCallback(async () => {
|
||||
// Ignore out-of-order responses: only the newest month load updates state.
|
||||
const seq = ++requestSeq.current;
|
||||
setLoading(true);
|
||||
setError('');
|
||||
try {
|
||||
const result = await api.summary(selected.year, selected.month);
|
||||
if (seq !== requestSeq.current) return; // superseded by a newer request
|
||||
setData(result);
|
||||
setStartingFirst(String(result.starting_amounts?.first_amount ?? 0));
|
||||
setStartingFifteenth(String(result.starting_amounts?.fifteenth_amount ?? 0));
|
||||
setStartingOther(String(result.starting_amounts?.other_amount ?? 0));
|
||||
setEditingStarting(false);
|
||||
setIncomeAmount(String(result.income?.amount ?? 0));
|
||||
setIncomeLabel(result.income?.label || 'Salary');
|
||||
setEditingIncome(false);
|
||||
setDraggingId(null);
|
||||
setDropTargetId(null);
|
||||
setMovingBillId(null);
|
||||
} catch (err) {
|
||||
if (seq === requestSeq.current) setError(err.message || 'Summary could not be loaded.');
|
||||
toast.error(err.message || 'Summary could not be loaded.');
|
||||
} finally {
|
||||
if (seq === requestSeq.current) setLoading(false);
|
||||
}
|
||||
}, [selected.month, selected.year]);
|
||||
const loadSummary = useCallback(
|
||||
() => queryClient.invalidateQueries({ queryKey: ['summary', selected.year, selected.month] }),
|
||||
[queryClient, selected.month, selected.year],
|
||||
);
|
||||
|
||||
// Seed the editable form fields (starting amounts, income) from the loaded
|
||||
// summary and reset transient edit/drag state whenever the data changes —
|
||||
// this is what loadSummary used to do inline.
|
||||
useEffect(() => {
|
||||
loadSummary();
|
||||
}, [loadSummary]);
|
||||
if (!data) return;
|
||||
setStartingFirst(String(data.starting_amounts?.first_amount ?? 0));
|
||||
setStartingFifteenth(String(data.starting_amounts?.fifteenth_amount ?? 0));
|
||||
setStartingOther(String(data.starting_amounts?.other_amount ?? 0));
|
||||
setEditingStarting(false);
|
||||
setIncomeAmount(String(data.income?.amount ?? 0));
|
||||
setIncomeLabel(data.income?.label || 'Salary');
|
||||
setEditingIncome(false);
|
||||
setDraggingId(null);
|
||||
setDropTargetId(null);
|
||||
setMovingBillId(null);
|
||||
}, [data]);
|
||||
|
||||
const summary = data?.summary || {};
|
||||
const expenses = data?.expenses || [];
|
||||
|
|
|
|||
Loading…
Reference in New Issue