import { useNavigate } from "@tanstack/react-router"; import { CalendarClock, ExternalLink } from "lucide-react"; import { useMemo } from "react"; import type { PendingUpcomingExpense } from "@/lib/api"; import { usePendingUpcomingExpenses } from "@/lib/queries"; type GroupedExpense = { periodicExpenseId: number; description: string; totalAmount: number; earliestDueDate: string; dueType: "overdue" | "upcoming"; count: number; }; function groupExpenses(expenses: PendingUpcomingExpense[]): GroupedExpense[] { const map = new Map(); for (const e of expenses) { if (e.periodicExpenseId === null) continue; const key = e.periodicExpenseId; const existing = map.get(key); if (existing) { existing.totalAmount += Number(e.amount); existing.count += 1; if (e.dueDate < existing.earliestDueDate) { existing.earliestDueDate = e.dueDate; } if (e.dueType === "overdue") { existing.dueType = "overdue"; } } else { map.set(key, { periodicExpenseId: key, description: e.periodicExpense?.description ?? e.description, totalAmount: Number(e.amount), earliestDueDate: e.dueDate, dueType: e.dueType, count: 1, }); } } return Array.from(map.values()).sort((a, b) => { if (a.dueType !== b.dueType) return a.dueType === "overdue" ? -1 : 1; return a.earliestDueDate.localeCompare(b.earliestDueDate); }); } function formatDate(value: string) { return new Date(value).toLocaleDateString("es-AR", { timeZone: "UTC", day: "2-digit", month: "2-digit", year: "numeric", }); } function formatAmount(value: number) { return `$${value.toLocaleString("es-AR", { minimumFractionDigits: 2 })}`; } function DueTypeBadge({ dueType }: { dueType: "overdue" | "upcoming" }) { return ( {dueType === "overdue" ? "Vencido" : "Próximo"} ); } export function DashboardExpensesTable() { const { data: expenses, isLoading } = usePendingUpcomingExpenses(); const navigate = useNavigate(); const groups = useMemo(() => { if (!expenses) return []; return groupExpenses(expenses); }, [expenses]); function goToExpenses(periodicExpenseId: number) { navigate({ to: "/expenses", search: { periodicExpenseId } }); } const overdueCount = groups.filter((g) => g.dueType === "overdue").length; const upcomingCount = groups.filter((g) => g.dueType === "upcoming").length; const grandTotal = groups.reduce((sum, g) => sum + g.totalAmount, 0); const hasData = groups.length > 0; return (

Gastos por vencer

{isLoading && ( Cargando... )} {!isLoading && hasData && ( {overdueCount} vencido{overdueCount !== 1 ? "s" : ""} {upcomingCount > 0 && ` · ${upcomingCount} próximo${upcomingCount !== 1 ? "s" : ""}`} )}
{!isLoading && !hasData && (
No hay gastos pendientes.
)} {isLoading && (
Cargando...
)}
{groups.map((group) => (

Vence {formatDate(group.earliestDueDate)} {group.count > 1 && ` (${group.count} gastos)`}

{formatAmount(group.totalAmount)}

))} {hasData && (
Total {formatAmount(grandTotal)}
)}
{groups.map((group) => ( ))} {hasData && ( )}
Descripción Monto Vencimiento Cantidad Estado
{formatAmount(group.totalAmount)} {formatDate(group.earliestDueDate)} {group.count > 1 ? `${group.count} gastos` : "1 gasto"}
Total {formatAmount(grandTotal)}
); }