'use client'; import type { DeclarationDto } from '@impuestos/contracts'; import { formatGs, formatMonthName, type Locale } from '@impuestos/i18n'; import { addMonths, formatPeriod } from '@impuestos/rules'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { FileCheck } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Card } from '@/components/ui/card'; import { EmptyState } from '@/components/ui/empty-state'; import { Skeleton } from '@/components/ui/skeleton'; import { Link, useRouter } from '@/i18n/navigation'; import { useT } from '@/i18n/t'; import { api } from '@/lib/api'; import { cn } from '@/lib/utils'; /** Flow D1: grouped by year, newest first, with a way to prepare the open period. */ export function DeclarationsScreen({ locale }: { locale: string }) { const t = useT(); const router = useRouter(); const queryClient = useQueryClient(); const declarations = useQuery({ queryKey: ['declarations'], queryFn: ({ signal }) => api.listDeclarations(undefined, signal), }); // The month just closed is the one there is a complete picture for. const now = new Date(); const openPeriod = addMonths(formatPeriod(now.getUTCFullYear(), now.getUTCMonth() + 1), -1); const alreadyPrepared = declarations.data?.items.some( (item) => item.formCode === '120' && item.period === openPeriod, ); const generate = useMutation({ mutationFn: () => api.generateDeclaration({ formCode: '120', period: openPeriod }), onSuccess: async (declaration) => { await queryClient.invalidateQueries({ queryKey: ['declarations'] }); router.push(`/declaraciones/${declaration.id}`); }, }); const byYear = groupByYear(declarations.data?.items ?? []); return (

{t('decl.title')}

{!alreadyPrepared ? (

{t('decl.generate', { form: '120', period: formatMonthName(locale as Locale, openPeriod) })}

{generate.isError ? (

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

) : null}
) : null} {declarations.isPending ? ( ) : null} {declarations.isError ? (

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

) : null} {declarations.data && declarations.data.items.length === 0 ? ( } title={t('decl.empty')} body={t('decl.emptyBody')} /> ) : null} {byYear.map(([year, items]) => (

{year}

    {items.map((declaration) => (
  • {declaration.formCode} {declaration.period} {headline(declaration)}
  • ))}
))}
); } function StatusChip({ declaration }: { declaration: DeclarationDto }) { const t = useT(); return ( {t(`decl.status.${declaration.status}` as const)} ); } /** The one number that matters on a row: what it costs, or what it saves. */ function headline(declaration: DeclarationDto): string { const summary = declaration.summary; const amount = declaration.formCode === '120' ? Number(summary['aPagar'] ?? 0) || Number(summary['saldoAFavor'] ?? 0) : Number(summary['tax'] ?? 0); return formatGs(amount); } function groupByYear(items: DeclarationDto[]): [string, DeclarationDto[]][] { const groups = new Map(); for (const item of items) { const year = item.period.slice(0, 4); groups.set(year, [...(groups.get(year) ?? []), item]); } return [...groups.entries()].sort((a, b) => b[0].localeCompare(a[0])); }