diff --git a/apps/web/app/(dashboard)/cfdi/page.tsx b/apps/web/app/(dashboard)/cfdi/page.tsx new file mode 100644 index 0000000..83803a1 --- /dev/null +++ b/apps/web/app/(dashboard)/cfdi/page.tsx @@ -0,0 +1,206 @@ +'use client'; + +import { useState } from 'react'; +import { Header } from '@/components/layouts/header'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { useCfdis } from '@/lib/hooks/use-cfdi'; +import type { CfdiFilters, TipoCfdi } from '@horux/shared'; +import { FileText, Search, ChevronLeft, ChevronRight } from 'lucide-react'; + +export default function CfdiPage() { + const [filters, setFilters] = useState({ + page: 1, + limit: 20, + }); + const [searchTerm, setSearchTerm] = useState(''); + + const { data, isLoading } = useCfdis(filters); + + const handleSearch = () => { + setFilters({ ...filters, search: searchTerm, page: 1 }); + }; + + const handleFilterType = (tipo?: TipoCfdi) => { + setFilters({ ...filters, tipo, page: 1 }); + }; + + const formatCurrency = (value: number) => + new Intl.NumberFormat('es-MX', { + style: 'currency', + currency: 'MXN', + }).format(value); + + const formatDate = (dateString: string) => + new Date(dateString).toLocaleDateString('es-MX', { + day: '2-digit', + month: 'short', + year: 'numeric', + }); + + return ( + <> +
+
+ {/* Filters */} + + +
+
+ setSearchTerm(e.target.value)} + onKeyDown={(e) => e.key === 'Enter' && handleSearch()} + /> + +
+
+ + + +
+
+
+
+ + {/* Table */} + + + + + CFDIs ({data?.total || 0}) + + + + {isLoading ? ( +
+ Cargando... +
+ ) : data?.data.length === 0 ? ( +
+ No se encontraron CFDIs +
+ ) : ( +
+ + + + + + + + + + + + + {data?.data.map((cfdi) => ( + + + + + + + + + ))} + +
FechaTipoSerie/FolioEmisor/ReceptorTotalEstado
{formatDate(cfdi.fechaEmision)} + + {cfdi.tipo === 'ingreso' ? 'Ingreso' : 'Egreso'} + + + {cfdi.serie || '-'}-{cfdi.folio || '-'} + +
+

+ {cfdi.tipo === 'ingreso' + ? cfdi.nombreReceptor + : cfdi.nombreEmisor} +

+

+ {cfdi.tipo === 'ingreso' + ? cfdi.rfcReceptor + : cfdi.rfcEmisor} +

+
+
+ {formatCurrency(cfdi.total)} + + + {cfdi.estado === 'vigente' ? 'Vigente' : 'Cancelado'} + +
+
+ )} + + {/* Pagination */} + {data && data.totalPages > 1 && ( +
+

+ Pagina {data.page} de {data.totalPages} +

+
+ + +
+
+ )} +
+
+
+ + ); +} diff --git a/apps/web/lib/api/cfdi.ts b/apps/web/lib/api/cfdi.ts new file mode 100644 index 0000000..c520a57 --- /dev/null +++ b/apps/web/lib/api/cfdi.ts @@ -0,0 +1,32 @@ +import { apiClient } from './client'; +import type { CfdiListResponse, CfdiFilters, Cfdi } from '@horux/shared'; + +export async function getCfdis(filters: CfdiFilters): Promise { + const params = new URLSearchParams(); + + if (filters.tipo) params.set('tipo', filters.tipo); + if (filters.estado) params.set('estado', filters.estado); + if (filters.fechaInicio) params.set('fechaInicio', filters.fechaInicio); + if (filters.fechaFin) params.set('fechaFin', filters.fechaFin); + if (filters.rfc) params.set('rfc', filters.rfc); + if (filters.search) params.set('search', filters.search); + if (filters.page) params.set('page', filters.page.toString()); + if (filters.limit) params.set('limit', filters.limit.toString()); + + const response = await apiClient.get(`/cfdi?${params}`); + return response.data; +} + +export async function getCfdiById(id: string): Promise { + const response = await apiClient.get(`/cfdi/${id}`); + return response.data; +} + +export async function getResumenCfdi(año?: number, mes?: number) { + const params = new URLSearchParams(); + if (año) params.set('año', año.toString()); + if (mes) params.set('mes', mes.toString()); + + const response = await apiClient.get(`/cfdi/resumen?${params}`); + return response.data; +} diff --git a/apps/web/lib/hooks/use-cfdi.ts b/apps/web/lib/hooks/use-cfdi.ts new file mode 100644 index 0000000..61f9ed8 --- /dev/null +++ b/apps/web/lib/hooks/use-cfdi.ts @@ -0,0 +1,25 @@ +import { useQuery } from '@tanstack/react-query'; +import * as cfdiApi from '@/lib/api/cfdi'; +import type { CfdiFilters } from '@horux/shared'; + +export function useCfdis(filters: CfdiFilters) { + return useQuery({ + queryKey: ['cfdis', filters], + queryFn: () => cfdiApi.getCfdis(filters), + }); +} + +export function useCfdi(id: string) { + return useQuery({ + queryKey: ['cfdi', id], + queryFn: () => cfdiApi.getCfdiById(id), + enabled: !!id, + }); +} + +export function useResumenCfdi(año?: number, mes?: number) { + return useQuery({ + queryKey: ['cfdi-resumen', año, mes], + queryFn: () => cfdiApi.getResumenCfdi(año, mes), + }); +}