diff --git a/apps/web/app/(dashboard)/cfdi/page.tsx b/apps/web/app/(dashboard)/cfdi/page.tsx index 692bd89..2e92278 100644 --- a/apps/web/app/(dashboard)/cfdi/page.tsx +++ b/apps/web/app/(dashboard)/cfdi/page.tsx @@ -11,7 +11,8 @@ import { useCfdis, useCreateCfdi, useDeleteCfdi } from '@/lib/hooks/use-cfdi'; import { createManyCfdis } from '@/lib/api/cfdi'; import type { CfdiFilters, TipoCfdi, Cfdi } from '@horux/shared'; import type { CreateCfdiData } from '@/lib/api/cfdi'; -import { FileText, Search, ChevronLeft, ChevronRight, Plus, Upload, Trash2, X, FileUp, CheckCircle, AlertCircle, Loader2, Eye, Filter, XCircle } from 'lucide-react'; +import { FileText, Search, ChevronLeft, ChevronRight, Plus, Upload, Trash2, X, FileUp, CheckCircle, AlertCircle, Loader2, Eye, Filter, XCircle, Calendar, User, Building2 } from 'lucide-react'; +import { Popover, PopoverTrigger, PopoverContent } from '@/components/ui/popover'; import { CfdiViewerModal } from '@/components/cfdi/cfdi-viewer-modal'; import { getCfdiById } from '@/lib/api/cfdi'; import { useAuthStore } from '@/stores/auth-store'; @@ -230,13 +231,13 @@ export default function CfdiPage() { limit: 20, }); const [searchTerm, setSearchTerm] = useState(''); - const [showColumnFilters, setShowColumnFilters] = useState(false); const [columnFilters, setColumnFilters] = useState({ fechaInicio: '', fechaFin: '', emisor: '', receptor: '', }); + const [openFilter, setOpenFilter] = useState<'fecha' | 'emisor' | 'receptor' | null>(null); const [showForm, setShowForm] = useState(false); const [showBulkForm, setShowBulkForm] = useState(false); const [formData, setFormData] = useState(initialFormData); @@ -287,35 +288,56 @@ export default function CfdiPage() { setFilters({ ...filters, search: searchTerm, page: 1 }); }; - const applyColumnFilters = () => { + const applyDateFilter = () => { setFilters({ ...filters, fechaInicio: columnFilters.fechaInicio || undefined, fechaFin: columnFilters.fechaFin || undefined, + page: 1, + }); + setOpenFilter(null); + }; + + const applyEmisorFilter = () => { + setFilters({ + ...filters, emisor: columnFilters.emisor || undefined, + page: 1, + }); + setOpenFilter(null); + }; + + const applyReceptorFilter = () => { + setFilters({ + ...filters, receptor: columnFilters.receptor || undefined, page: 1, }); + setOpenFilter(null); }; - const clearColumnFilters = () => { - setColumnFilters({ - fechaInicio: '', - fechaFin: '', - emisor: '', - receptor: '', - }); - setFilters({ - ...filters, - fechaInicio: undefined, - fechaFin: undefined, - emisor: undefined, - receptor: undefined, - page: 1, - }); + const clearDateFilter = () => { + setColumnFilters({ ...columnFilters, fechaInicio: '', fechaFin: '' }); + setFilters({ ...filters, fechaInicio: undefined, fechaFin: undefined, page: 1 }); + setOpenFilter(null); }; - const hasActiveColumnFilters = columnFilters.fechaInicio || columnFilters.fechaFin || columnFilters.emisor || columnFilters.receptor; + const clearEmisorFilter = () => { + setColumnFilters({ ...columnFilters, emisor: '' }); + setFilters({ ...filters, emisor: undefined, page: 1 }); + setOpenFilter(null); + }; + + const clearReceptorFilter = () => { + setColumnFilters({ ...columnFilters, receptor: '' }); + setFilters({ ...filters, receptor: undefined, page: 1 }); + setOpenFilter(null); + }; + + const hasDateFilter = filters.fechaInicio || filters.fechaFin; + const hasEmisorFilter = filters.emisor; + const hasReceptorFilter = filters.receptor; + const hasActiveColumnFilters = hasDateFilter || hasEmisorFilter || hasReceptorFilter; const handleFilterType = (tipo?: TipoCfdi) => { setFilters({ ...filters, tipo, page: 1 }); @@ -1102,22 +1124,35 @@ export default function CfdiPage() { CFDIs ({data?.total || 0}) -
- {hasActiveColumnFilters && ( - - )} - -
+ {hasActiveColumnFilters && ( +
+ Filtros activos: + {hasDateFilter && ( + + Fecha + + + )} + {hasEmisorFilter && ( + + Emisor: {filters.emisor} + + + )} + {hasReceptorFilter && ( + + Receptor: {filters.receptor} + + + )} +
+ )} @@ -1134,68 +1169,134 @@ export default function CfdiPage() { - - - - - - - - - {canEdit && } + + + + + + + + + {canEdit && } - {showColumnFilters && ( - - - - - - - - - - {canEdit && } - - )} {data?.data.map((cfdi) => ( diff --git a/apps/web/components/ui/popover.tsx b/apps/web/components/ui/popover.tsx new file mode 100644 index 0000000..b1372f6 --- /dev/null +++ b/apps/web/components/ui/popover.tsx @@ -0,0 +1,30 @@ +'use client'; + +import * as React from 'react'; +import * as PopoverPrimitive from '@radix-ui/react-popover'; +import { cn } from '@/lib/utils'; + +const Popover = PopoverPrimitive.Root; + +const PopoverTrigger = PopoverPrimitive.Trigger; + +const PopoverContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, align = 'center', sideOffset = 4, ...props }, ref) => ( + + + +)); +PopoverContent.displayName = PopoverPrimitive.Content.displayName; + +export { Popover, PopoverTrigger, PopoverContent }; diff --git a/apps/web/package.json b/apps/web/package.json index 8c2e6dc..46ee1a2 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -14,6 +14,7 @@ "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-dropdown-menu": "^2.1.0", "@radix-ui/react-label": "^2.1.0", + "@radix-ui/react-popover": "^1.1.15", "@radix-ui/react-select": "^2.1.0", "@radix-ui/react-separator": "^1.1.0", "@radix-ui/react-slot": "^1.1.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8e71b8e..36905c5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -123,6 +123,9 @@ importers: '@radix-ui/react-label': specifier: ^2.1.0 version: 2.1.8(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-popover': + specifier: ^1.1.15 + version: 1.1.15(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-select': specifier: ^2.1.0 version: 2.2.6(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -723,6 +726,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-popover@1.1.15': + resolution: {integrity: sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-popper@1.2.8': resolution: {integrity: sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==} peerDependencies: @@ -2926,6 +2942,29 @@ snapshots: '@types/react': 18.3.27 '@types/react-dom': 18.3.7(@types/react@18.3.27) + '@radix-ui/react-popover@1.1.15(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-id': 1.1.1(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-slot': 1.2.3(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.27)(react@18.3.1) + aria-hidden: 1.2.6 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-remove-scroll: 2.7.2(@types/react@18.3.27)(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.27 + '@types/react-dom': 18.3.7(@types/react@18.3.27) + '@radix-ui/react-popper@1.2.8(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@floating-ui/react-dom': 2.1.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
FechaTipoFolioEmisorReceptorTotalEstado +
+ Fecha + setOpenFilter(open ? 'fecha' : null)}> + + + + +
+

Filtrar por fecha

+
+
+ + setColumnFilters({ ...columnFilters, fechaInicio: e.target.value })} + /> +
+
+ + setColumnFilters({ ...columnFilters, fechaFin: e.target.value })} + /> +
+
+
+ + {hasDateFilter && ( + + )} +
+
+
+
+
+
TipoFolio +
+ Emisor + setOpenFilter(open ? 'emisor' : null)}> + + + + +
+

Filtrar por emisor

+
+ + setColumnFilters({ ...columnFilters, emisor: e.target.value })} + onKeyDown={(e) => e.key === 'Enter' && applyEmisorFilter()} + /> +
+
+ + {hasEmisorFilter && ( + + )} +
+
+
+
+
+
+
+ Receptor + setOpenFilter(open ? 'receptor' : null)}> + + + + +
+

Filtrar por receptor

+
+ + setColumnFilters({ ...columnFilters, receptor: e.target.value })} + onKeyDown={(e) => e.key === 'Enter' && applyReceptorFilter()} + /> +
+
+ + {hasReceptorFilter && ( + + )} +
+
+
+
+
+
TotalEstado
-
- setColumnFilters({ ...columnFilters, fechaInicio: e.target.value })} - onKeyDown={(e) => e.key === 'Enter' && applyColumnFilters()} - /> - setColumnFilters({ ...columnFilters, fechaFin: e.target.value })} - onKeyDown={(e) => e.key === 'Enter' && applyColumnFilters()} - /> -
-
- setColumnFilters({ ...columnFilters, emisor: e.target.value })} - onKeyDown={(e) => e.key === 'Enter' && applyColumnFilters()} - /> - - setColumnFilters({ ...columnFilters, receptor: e.target.value })} - onKeyDown={(e) => e.key === 'Enter' && applyColumnFilters()} - /> - - -