Phase 12: Add TanStack React Query
- Install @tanstack/react-query - Create queryClient with MutationCache for global toast on success/error - QueryToastBridge component bridges module-level handlers to ToastProvider - Wrap App in QueryClientProvider - Create domain hook files: budgets, incomes, outgos, shares, summary - Update all 5 pages to use hooks; remove inline useEffect/useState fetch logic - DnD reorder pages use local displayItems state synced from query data
This commit is contained in:
@@ -1,34 +1,25 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import type { SummaryDto } from '../types';
|
||||
import { api } from '../api/client';
|
||||
import { MoneyDisplay } from '../components/MoneyDisplay';
|
||||
import { BudgetNav } from '../components/BudgetNav';
|
||||
import { useSummary, useUpdateTaxRate } from '../api/summary';
|
||||
|
||||
const fmt = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' });
|
||||
|
||||
export function SummaryPage() {
|
||||
const { id: budgetId } = useParams<{ id: string }>();
|
||||
const [summary, setSummary] = useState<SummaryDto | null>(null);
|
||||
const { data: summary } = useSummary(budgetId!);
|
||||
const [taxRateInput, setTaxRateInput] = useState('');
|
||||
const [savingTax, setSavingTax] = useState(false);
|
||||
const updateTaxRate = useUpdateTaxRate(budgetId!);
|
||||
|
||||
useEffect(() => {
|
||||
if (!budgetId) return;
|
||||
api.get<SummaryDto>(`/api/budgets/${budgetId}/summary`).then(s => {
|
||||
setSummary(s);
|
||||
setTaxRateInput(String(Math.round(s.preTaxIncome.effectiveTaxRate * 100)));
|
||||
});
|
||||
}, [budgetId]);
|
||||
if (summary) {
|
||||
setTaxRateInput(String(Math.round(summary.preTaxIncome.effectiveTaxRate * 100)));
|
||||
}
|
||||
}, [summary]);
|
||||
|
||||
const saveTaxRate = async () => {
|
||||
if (!budgetId || !summary) return;
|
||||
setSavingTax(true);
|
||||
const rate = parseFloat(taxRateInput) / 100;
|
||||
await api.put(`/api/budgets/${budgetId}/summary/tax-rate`, { effectiveTaxRate: rate });
|
||||
const updated = await api.get<SummaryDto>(`/api/budgets/${budgetId}/summary`);
|
||||
setSummary(updated);
|
||||
setSavingTax(false);
|
||||
const saveTaxRate = () => {
|
||||
updateTaxRate.mutate(parseFloat(taxRateInput) / 100);
|
||||
};
|
||||
|
||||
if (!summary) return <div>Loading...</div>;
|
||||
@@ -96,8 +87,8 @@ export function SummaryPage() {
|
||||
onChange={e => setTaxRateInput(e.target.value)}
|
||||
style={{ width: '60px' }}
|
||||
/>
|
||||
<button onClick={saveTaxRate} disabled={savingTax} style={{ marginLeft: '8px' }}>
|
||||
{savingTax ? 'Saving…' : 'Save'}
|
||||
<button onClick={saveTaxRate} disabled={updateTaxRate.isPending} style={{ marginLeft: '8px' }}>
|
||||
{updateTaxRate.isPending ? 'Saving…' : 'Save'}
|
||||
</button>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user