'use client'; import type { DashboardDto } from '@impuestos/contracts'; import { formatDateLong, formatGs, formatGsAmount, formatMonthName, type Locale } from '@impuestos/i18n'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { ScanLine, X } from 'lucide-react'; import { TraceableNumber } from '@/components/traceable-number'; import { Button, buttonClasses } from '@/components/ui/button'; import { Card } from '@/components/ui/card'; import { EmptyState } from '@/components/ui/empty-state'; import { Reveal } from '@/components/ui/reveal'; import { Skeleton } from '@/components/ui/skeleton'; import { Link } from '@/i18n/navigation'; import { useT } from '@/i18n/t'; import { api } from '@/lib/api'; /** Flow C: position header, then exactly one next action, then the insight feed. */ export function Dashboard({ locale, fullName }: { locale: Locale; fullName: string }) { const t = useT(); const dashboard = useQuery({ queryKey: ['dashboard'], queryFn: ({ signal }) => api.getDashboard(signal), }); if (dashboard.isPending) { return (
); } if (dashboard.isError || !dashboard.data) { return (

{t('common.error.generic')}

); } const data = dashboard.data; // Flow A6: until the first comprobante arrives there is nothing to show but the way in. const nothingYet = !data.hasDocuments; return (

{t('home.title')}

{fullName}

{nothingYet ? ( } title={t('home.firstRun.title')} body={t('home.noPosition')} action={ {t('home.firstRun.scan')} } /> ) : null} {!nothingYet ? ( <> {/* Zone 1: the position, one card per obligation, swipeable on a phone. */}
{data.iva ? : null} {data.irp ? : null}
{/* Zone 2: exactly one next action. */} {/* Zone 3: the insight feed. */} ) : null}
); } function IvaCard({ iva, locale }: { iva: NonNullable; locale: Locale }) { const t = useT(); const owes = iva.aPagar > 0; return (

{t('home.iva.title', { month: formatMonthName(locale, iva.period) })}

{owes ? t('home.iva.aPagar') : t('home.iva.aFavor')}

{/* Owing tax on time is neutral, never red (FLOWS.md section 1). */}

{t('home.iva.breakdown', { debito: formatGsAmount(iva.debito), credito: formatGsAmount(iva.credito), })}

); } function IrpCard({ irp, locale }: { irp: NonNullable; locale: Locale }) { const t = useT(); return (

{t('home.irp.title', { year: irp.year })}

{irp.monthDelta > 0 ? (

{t('home.irp.delta', { amount: formatGsAmount(irp.monthDelta) })}

) : null} {irp.belowThreshold ? (

{t('home.irp.belowThreshold')}

) : null}
); } function NextAction({ action, locale, }: { action: DashboardDto['nextAction']; locale: Locale; }) { const t = useT(); switch (action.kind) { case 'overdue': return (

{t('home.next.overdue', { form: action.form ?? '120', date: action.dueDate ? formatDateLong(locale, action.dueDate) : '', })}

{t('home.next.overdueCta')}
); case 'declaration_ready': return (

{t('decl.status.ready')}

{t('home.next.reviewCta')}
); case 'deadline_soon': return (

{t('home.next.upcoming', { date: action.dueDate ? formatDateLong(locale, action.dueDate) : '', })}

{t('home.next.deadlineCta')}
); case 'bandeja': return (

{t('home.next.bandeja', { count: action.count ?? 0 })}

{t('home.next.bandejaCta')}
); case 'all_clear': return (

{t('home.next.allClear')}

{action.dueDate ? (

{t('home.next.upcoming', { date: formatDateLong(locale, action.dueDate) })}

) : null}
); } } function InsightFeed({ insights, locale, }: { insights: DashboardDto['insights']; locale: Locale; }) { const t = useT(); const queryClient = useQueryClient(); const dismiss = useMutation({ mutationFn: (id: string) => api.dismissInsight(id), onSuccess: () => queryClient.invalidateQueries({ queryKey: ['dashboard'] }), }); if (insights.length === 0) return null; return ( ); } type Translate = ReturnType; function insightTitle(insight: DashboardDto['insights'][number], t: Translate): string { if (insight.kind === 'deduction_gap') return t('home.insight.gapTitle'); if (insight.kind === 'month_close') return t('decl.status.ready'); return t('vto.title'); } function insightBody( insight: DashboardDto['insights'][number], t: Translate, locale: Locale, ): string { if (insight.kind === 'deduction_gap') { const category = String(insight.params['category'] ?? 'none'); return t('home.insight.gapBody', { category: t(`categories.${category}` as 'categories.none'), }); } if (insight.kind === 'month_close') { return t('home.insight.monthClose', { month: formatMonthName(locale, String(insight.params['month'] ?? '')), savings: formatGsAmount(Number(insight.params['savings'] ?? 0)), }); } return t('home.insight.deadline', { form: String(insight.params['form'] ?? '120'), date: formatDateLong(locale, String(insight.params['date'] ?? '')), }); } export { formatGs };