import { Grid, Paper, Skeleton, Stack, Typography } from "@mui/material"; import { useQuery } from "@tanstack/react-query"; import { useMemo } from "react"; import { useTranslation } from "react-i18next"; import { ResponsiveContainer, BarChart, Bar, Tooltip, XAxis, YAxis } from "recharts"; import { fetchContracts, fetchUpcomingDeadlines } from "../api/contracts"; import DeadlineList from "../components/DeadlineList"; import PageHeader from "../components/PageHeader"; import StatCard from "../components/StatCard"; import { Contract, UpcomingDeadline } from "../types"; import { formatCurrency } from "../utils/date"; function buildDeadlineSeries(deadlines: UpcomingDeadline[]) { const grouped = new Map(); deadlines.forEach((deadline) => { if (!deadline.terminationDeadline) return; const month = deadline.terminationDeadline.slice(0, 7); grouped.set(month, (grouped.get(month) ?? 0) + 1); }); return Array.from(grouped.entries()) .sort(([a], [b]) => a.localeCompare(b)) .map(([month, count]) => ({ month, count })); } function countActiveContracts(contracts: Contract[]): number { const now = new Date(); return contracts.filter((contract) => { if (!contract.contractEndDate) { return true; } const end = new Date(`${contract.contractEndDate}T00:00:00Z`); return end >= now; }).length; } function calcMonthlySpend(contracts: Contract[]): number { return contracts.reduce((total, contract) => { if (!contract.price) return total; return total + contract.price; }, 0); } export default function Dashboard() { const { data: contracts, isLoading: loadingContracts, isError: errorContracts } = useQuery({ queryKey: ["contracts", "dashboard"], queryFn: () => fetchContracts({ limit: 200 }) }); const { data: deadlines, isLoading: loadingDeadlines, isError: errorDeadlines } = useQuery({ queryKey: ["deadlines", 60], queryFn: () => fetchUpcomingDeadlines(60) }); const normalizedContracts = useMemo(() => { if (!contracts) return [] as Contract[]; if (Array.isArray(contracts)) return contracts as Contract[]; if (typeof (contracts as any).results === "object" && Array.isArray((contracts as any).results)) { return (contracts as any).results as Contract[]; } return [] as Contract[]; }, [contracts]); const normalizedDeadlines = useMemo(() => { if (!deadlines) return [] as UpcomingDeadline[]; if (Array.isArray(deadlines)) return deadlines as UpcomingDeadline[]; if (typeof (deadlines as any).results === "object" && Array.isArray((deadlines as any).results)) { return (deadlines as any).results as UpcomingDeadline[]; } return [] as UpcomingDeadline[]; }, [deadlines]); const activeContracts = useMemo(() => countActiveContracts(normalizedContracts), [normalizedContracts]); const monthlySpend = useMemo(() => calcMonthlySpend(normalizedContracts), [normalizedContracts]); const deadlineSeries = useMemo(() => buildDeadlineSeries(normalizedDeadlines), [normalizedDeadlines]); const { t } = useTranslation(); return ( <> {loadingContracts ? ( ) : errorContracts ? ( {t("dashboard.contractsError")} ) : ( 0 ? "up" : undefined} trendLabel={contracts && contracts.length > 0 ? t("dashboard.totalContractsTrend") : undefined} /> )} {loadingContracts ? ( ) : errorContracts ? ( {t("dashboard.contractsError")} ) : ( )} {loadingContracts ? ( ) : errorContracts ? ( {t("dashboard.contractsError")} ) : ( 0 ? "up" : undefined} trendLabel={monthlySpend > 0 ? t("dashboard.monthlySpendTrend") : undefined} /> )} {t("dashboard.deadlineChartTitle")} {loadingDeadlines ? ( ) : errorDeadlines ? ( {t("dashboard.deadlinesError")} ) : deadlines && deadlines.length > 0 ? ( ) : ( {t("dashboard.noDeadlines")} )} {loadingDeadlines ? ( ) : errorDeadlines ? ( {t("dashboard.deadlinesError")} ) : ( )} ); }