fix(conciliacion): filtros de columna se atascaban al escribir
- Mover FilterHeader fuera de ConciliacionPage para evitar desmonte/remonte en cada render (causaba perdida de foco) - Agregar debounce de 300ms al input de filtro para reducir re-renders mientras el usuario escribe
This commit is contained in:
@@ -4,7 +4,7 @@ import { useState, useEffect } from 'react';
|
|||||||
import { useCfdisConConciliacion, useConciliar, useDesconciliar } from '@/lib/hooks/use-conciliacion';
|
import { useCfdisConConciliacion, useConciliar, useDesconciliar } from '@/lib/hooks/use-conciliacion';
|
||||||
import { useBancos } from '@/lib/hooks/use-bancos';
|
import { useBancos } from '@/lib/hooks/use-bancos';
|
||||||
import { useRegimenesDelPeriodo } from '@/lib/hooks/use-dashboard';
|
import { useRegimenesDelPeriodo } from '@/lib/hooks/use-dashboard';
|
||||||
import { PeriodSelector, RegimenSelector } from '@horux/shared-ui';
|
import { PeriodSelector, RegimenSelector, useDebounce } from '@horux/shared-ui';
|
||||||
import { CfdiViewerModal } from '@/components/cfdi/cfdi-viewer-modal';
|
import { CfdiViewerModal } from '@/components/cfdi/cfdi-viewer-modal';
|
||||||
import { Header } from '@/components/layouts/header';
|
import { Header } from '@/components/layouts/header';
|
||||||
import { Card, CardContent, Button, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Input, Label, Popover, PopoverTrigger, PopoverContent } from '@horux/shared-ui';
|
import { Card, CardContent, Button, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Input, Label, Popover, PopoverTrigger, PopoverContent } from '@horux/shared-ui';
|
||||||
@@ -29,6 +29,75 @@ function getMonthRange(year: number, month: number) {
|
|||||||
return { start, end };
|
return { start, end };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function FilterHeader({
|
||||||
|
label,
|
||||||
|
filterKey,
|
||||||
|
filters,
|
||||||
|
setFilters,
|
||||||
|
openFilter,
|
||||||
|
setOpenFilter,
|
||||||
|
}: {
|
||||||
|
label: string;
|
||||||
|
filterKey: string;
|
||||||
|
filters: { rfcEmisor: string; nombreEmisor: string; rfcReceptor: string; nombreReceptor: string };
|
||||||
|
setFilters: React.Dispatch<React.SetStateAction<{ rfcEmisor: string; nombreEmisor: string; rfcReceptor: string; nombreReceptor: string }>>;
|
||||||
|
openFilter: string | null;
|
||||||
|
setOpenFilter: (v: string | null) => void;
|
||||||
|
}) {
|
||||||
|
const rawValue = (filters as any)[filterKey] || '';
|
||||||
|
const [localValue, setLocalValue] = useState(rawValue);
|
||||||
|
const debouncedValue = useDebounce(localValue, 300);
|
||||||
|
|
||||||
|
// Sync local state when popover opens or external filter changes
|
||||||
|
useEffect(() => {
|
||||||
|
setLocalValue(rawValue);
|
||||||
|
}, [rawValue, openFilter === filterKey]);
|
||||||
|
|
||||||
|
// Update parent filter only when debounced value changes
|
||||||
|
useEffect(() => {
|
||||||
|
if (debouncedValue !== rawValue) {
|
||||||
|
setFilters((prev: any) => ({ ...prev, [filterKey]: debouncedValue }));
|
||||||
|
}
|
||||||
|
}, [debouncedValue]);
|
||||||
|
|
||||||
|
const hasFilter = !!rawValue;
|
||||||
|
return (
|
||||||
|
<div className="flex items-center justify-center gap-1">
|
||||||
|
{label}
|
||||||
|
<Popover open={openFilter === filterKey} onOpenChange={(open) => setOpenFilter(open ? filterKey : null)}>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<button className={`p-1 rounded hover:bg-muted ${hasFilter ? 'text-primary' : ''}`}>
|
||||||
|
<Filter className="h-3.5 w-3.5" />
|
||||||
|
</button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent className="w-64" align="start">
|
||||||
|
<div className="space-y-3">
|
||||||
|
<h4 className="font-medium text-sm">Filtrar por {label}</h4>
|
||||||
|
<div>
|
||||||
|
<Label className="text-xs">Contiene</Label>
|
||||||
|
<Input
|
||||||
|
placeholder={`Buscar ${label.toLowerCase()}...`}
|
||||||
|
className="h-8 text-sm"
|
||||||
|
value={localValue}
|
||||||
|
onChange={(e) => setLocalValue(e.target.value)}
|
||||||
|
autoFocus
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Button size="sm" onClick={() => setOpenFilter(null)}>Aplicar</Button>
|
||||||
|
{hasFilter && (
|
||||||
|
<Button size="sm" variant="outline" onClick={() => { setFilters((prev: any) => ({ ...prev, [filterKey]: '' })); setOpenFilter(null); }}>
|
||||||
|
Limpiar
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export default function ConciliacionPage() {
|
export default function ConciliacionPage() {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const defaultRange = getMonthRange(now.getFullYear(), now.getMonth() + 1);
|
const defaultRange = getMonthRange(now.getFullYear(), now.getMonth() + 1);
|
||||||
@@ -163,58 +232,6 @@ export default function ConciliacionPage() {
|
|||||||
return sorted;
|
return sorted;
|
||||||
}
|
}
|
||||||
|
|
||||||
function FilterHeader({
|
|
||||||
label,
|
|
||||||
filterKey,
|
|
||||||
filters,
|
|
||||||
setFilters,
|
|
||||||
openFilter,
|
|
||||||
setOpenFilter,
|
|
||||||
}: {
|
|
||||||
label: string;
|
|
||||||
filterKey: string;
|
|
||||||
filters: { rfcEmisor: string; nombreEmisor: string; rfcReceptor: string; nombreReceptor: string };
|
|
||||||
setFilters: React.Dispatch<React.SetStateAction<{ rfcEmisor: string; nombreEmisor: string; rfcReceptor: string; nombreReceptor: string }>>;
|
|
||||||
openFilter: string | null;
|
|
||||||
setOpenFilter: (v: string | null) => void;
|
|
||||||
}) {
|
|
||||||
const hasFilter = !!(filters as any)[filterKey];
|
|
||||||
return (
|
|
||||||
<div className="flex items-center justify-center gap-1">
|
|
||||||
{label}
|
|
||||||
<Popover open={openFilter === filterKey} onOpenChange={(open) => setOpenFilter(open ? filterKey : null)}>
|
|
||||||
<PopoverTrigger asChild>
|
|
||||||
<button className={`p-1 rounded hover:bg-muted ${hasFilter ? 'text-primary' : ''}`}>
|
|
||||||
<Filter className="h-3.5 w-3.5" />
|
|
||||||
</button>
|
|
||||||
</PopoverTrigger>
|
|
||||||
<PopoverContent className="w-64" align="start">
|
|
||||||
<div className="space-y-3">
|
|
||||||
<h4 className="font-medium text-sm">Filtrar por {label}</h4>
|
|
||||||
<div>
|
|
||||||
<Label className="text-xs">Contiene</Label>
|
|
||||||
<Input
|
|
||||||
placeholder={`Buscar ${label.toLowerCase()}...`}
|
|
||||||
className="h-8 text-sm"
|
|
||||||
value={(filters as any)[filterKey]}
|
|
||||||
onChange={(e) => setFilters((prev: any) => ({ ...prev, [filterKey]: e.target.value }))}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="flex gap-2">
|
|
||||||
<Button size="sm" onClick={() => setOpenFilter(null)}>Aplicar</Button>
|
|
||||||
{hasFilter && (
|
|
||||||
<Button size="sm" variant="outline" onClick={() => { setFilters((prev: any) => ({ ...prev, [filterKey]: '' })); setOpenFilter(null); }}>
|
|
||||||
Limpiar
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</PopoverContent>
|
|
||||||
</Popover>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const pendientesOrdenados = sortCfdis(
|
const pendientesOrdenados = sortCfdis(
|
||||||
pendientes.filter((c) => matchesColumnFilters(c, filtersPendientes)),
|
pendientes.filter((c) => matchesColumnFilters(c, filtersPendientes)),
|
||||||
sortPendientes
|
sortPendientes
|
||||||
|
|||||||
Reference in New Issue
Block a user