mirror of
https://github.com/escalante29/WealthySmart.git
synced 2026-07-17 17:08:47 +02:00
Migrate Analytics, Salarios, Pensions, ServiciosMunicipales, Proyecciones to queries
Every page now goes through TanStack Query with cancellation, placeholder data on filter changes, and a visible ErrorState with retry instead of console.error/silent-empty rendering (FE-02, UX-02). Analytics keeps the trend query on its own key so cycle changes don't refetch it. Pensions/Servicios upload-triggered refreshes are bounded by the api-level 30s timeout (FE-05); pension upload results cap at 10 rows with an overflow note (FE-19). Note: the Pensions ROI fallback already guarded short chart data — FE-04 was already handled. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import {
|
||||
PieChart,
|
||||
Pie,
|
||||
@@ -13,6 +14,7 @@ import {
|
||||
import { BarChart3 } from 'lucide-react';
|
||||
|
||||
import api from '@/lib/api';
|
||||
import ErrorState from '@/components/ErrorState';
|
||||
import BillingCycleSelector from '@/components/BillingCycleSelector';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import {
|
||||
@@ -70,29 +72,43 @@ const dailyChartConfig = {
|
||||
|
||||
export default function Analytics() {
|
||||
const [cycle, setCycle] = useState<{ year: number; month: number } | null>(null);
|
||||
const [byCategory, setByCategory] = useState<CategorySpending[]>([]);
|
||||
const [trend, setTrend] = useState<MonthlyTrend[]>([]);
|
||||
const [daily, setDaily] = useState<DailySpending[]>([]);
|
||||
const cycleParams: Record<string, string> = cycle
|
||||
? { cycle_year: String(cycle.year), cycle_month: String(cycle.month) }
|
||||
: {};
|
||||
|
||||
useEffect(() => {
|
||||
const params: Record<string, string> = {};
|
||||
if (cycle) {
|
||||
params.cycle_year = String(cycle.year);
|
||||
params.cycle_month = String(cycle.month);
|
||||
}
|
||||
const byCategoryQ = useQuery({
|
||||
queryKey: ['analytics', 'by-category', cycle],
|
||||
queryFn: ({ signal }) =>
|
||||
api
|
||||
.get<CategorySpending[]>('/analytics/by-category', { params: cycleParams, signal })
|
||||
.then((r) => r.data),
|
||||
placeholderData: (prev) => prev,
|
||||
});
|
||||
// The trend endpoint ignores the cycle filter — keep it on its own key so
|
||||
// changing cycles doesn't refetch it.
|
||||
const trendQ = useQuery({
|
||||
queryKey: ['analytics', 'trend'],
|
||||
queryFn: ({ signal }) =>
|
||||
api.get<MonthlyTrend[]>('/analytics/monthly-trend', { signal }).then((r) => r.data),
|
||||
});
|
||||
const dailyQ = useQuery({
|
||||
queryKey: ['analytics', 'daily', cycle],
|
||||
queryFn: ({ signal }) =>
|
||||
api
|
||||
.get<DailySpending[]>('/analytics/daily-spending', { params: cycleParams, signal })
|
||||
.then((r) => r.data),
|
||||
placeholderData: (prev) => prev,
|
||||
});
|
||||
|
||||
Promise.all([
|
||||
api.get<CategorySpending[]>('/analytics/by-category', { params }),
|
||||
api.get<MonthlyTrend[]>('/analytics/monthly-trend'),
|
||||
api.get<DailySpending[]>('/analytics/daily-spending', { params }),
|
||||
])
|
||||
.then(([catRes, trendRes, dailyRes]) => {
|
||||
setByCategory(catRes.data);
|
||||
setTrend(trendRes.data);
|
||||
setDaily(dailyRes.data);
|
||||
})
|
||||
.catch(console.error);
|
||||
}, [cycle]);
|
||||
const byCategory = byCategoryQ.data ?? [];
|
||||
const trend = trendQ.data ?? [];
|
||||
const daily = dailyQ.data ?? [];
|
||||
const anyError = byCategoryQ.isError || trendQ.isError || dailyQ.isError;
|
||||
const retryAll = () => {
|
||||
byCategoryQ.refetch();
|
||||
trendQ.refetch();
|
||||
dailyQ.refetch();
|
||||
};
|
||||
|
||||
// Build dynamic chart config for pie chart
|
||||
const pieChartConfig = byCategory.reduce<ChartConfig>((acc, cat, i) => {
|
||||
@@ -116,6 +132,13 @@ export default function Analytics() {
|
||||
<BillingCycleSelector value={cycle} onChange={setCycle} />
|
||||
</div>
|
||||
|
||||
{anyError && (
|
||||
<ErrorState
|
||||
message="No se pudieron cargar los datos de analytics"
|
||||
onRetry={retryAll}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
{/* Spending by Category - Donut */}
|
||||
<Card>
|
||||
|
||||
Reference in New Issue
Block a user