import React, { useState, useRef, useEffect } from 'react'; export interface NumericFilterConfig { week: string; metric: 'units' | 'spend' | 'revenue' | 'gv'; operator: '=' | '>' | '>=' | '<' | '<=' | 'between'; value: number; value2?: number; } interface NumericColumnFilterProps { week: string; metric: 'units' | 'spend' | 'revenue' | 'gv'; currentFilter: NumericFilterConfig | null; onFilterChange: (filter: NumericFilterConfig | null) => void; accentColor?: 'indigo' | 'amber' | 'teal'; } const OPERATORS = [ { value: '=', label: '= Igual a', symbol: '=' }, { value: '>', label: '> Mayor que', symbol: '>' }, { value: '>=', label: '≥ Mayor o igual', symbol: '≥' }, { value: '<', label: '< Menor que', symbol: '<' }, { value: '<=', label: '≤ Menor o igual', symbol: '≤' }, { value: 'between', label: '↔ Entre', symbol: '↔' }, ] as const; export const NumericColumnFilter: React.FC = ({ week, metric, currentFilter, onFilterChange, accentColor = 'indigo' }) => { const [isOpen, setIsOpen] = useState(false); const [operator, setOperator] = useState(currentFilter?.operator || '>'); const [value, setValue] = useState(currentFilter?.value?.toString() || ''); const [value2, setValue2] = useState(currentFilter?.value2?.toString() || ''); const containerRef = useRef(null); const colorClasses = { indigo: { active: 'text-indigo-400 bg-indigo-500/10', button: 'bg-indigo-500 border-indigo-400', hover: 'hover:bg-indigo-500/10', }, amber: { active: 'text-amber-400 bg-amber-500/10', button: 'bg-amber-500 border-amber-400', hover: 'hover:bg-amber-500/10', }, teal: { active: 'text-teal-400 bg-teal-500/10', button: 'bg-teal-500 border-teal-400', hover: 'hover:bg-teal-500/10', } }; const colors = colorClasses[accentColor]; // Sync local state with prop useEffect(() => { if (currentFilter) { setOperator(currentFilter.operator); setValue(currentFilter.value.toString()); setValue2(currentFilter.value2?.toString() || ''); } else { setOperator('>'); setValue(''); setValue2(''); } }, [currentFilter]); // Close when clicking outside useEffect(() => { const handleClickOutside = (event: MouseEvent) => { if (containerRef.current && !containerRef.current.contains(event.target as Node)) { setIsOpen(false); } }; document.addEventListener('mousedown', handleClickOutside); return () => document.removeEventListener('mousedown', handleClickOutside); }, []); const handleApply = () => { const numValue = parseFloat(value); if (isNaN(numValue)) { onFilterChange(null); setIsOpen(false); return; } const filter: NumericFilterConfig = { week, metric, operator, value: numValue, }; if (operator === 'between') { const numValue2 = parseFloat(value2); if (!isNaN(numValue2)) { filter.value2 = numValue2; } } onFilterChange(filter); setIsOpen(false); }; const handleClear = () => { onFilterChange(null); setValue(''); setValue2(''); setOperator('>'); setIsOpen(false); }; const isActive = currentFilter !== null; const operatorSymbol = OPERATORS.find(o => o.value === (currentFilter?.operator || operator))?.symbol || '▼'; return (
{isOpen && (
e.stopPropagation()} > {/* Header */}
Filter {metric.toUpperCase()} {isActive && ( )}
{/* Operator Select */}
{/* Value Input(s) */}
setValue(e.target.value)} placeholder="0" className="w-full bg-slate-950 border border-white/10 rounded-lg px-2.5 py-2 text-xs text-white focus:outline-none focus:ring-2 focus:ring-indigo-500" onKeyDown={(e) => { if (e.key === 'Enter') handleApply(); }} />
{operator === 'between' && (
setValue2(e.target.value)} placeholder="100" className="w-full bg-slate-950 border border-white/10 rounded-lg px-2.5 py-2 text-xs text-white focus:outline-none focus:ring-2 focus:ring-indigo-500" onKeyDown={(e) => { if (e.key === 'Enter') handleApply(); }} />
)}
{/* Apply Button */}
)}
); }; // Helper function to check if a value passes a filter export const passesNumericFilter = ( value: number, filter: NumericFilterConfig ): boolean => { switch (filter.operator) { case '=': return value === filter.value; case '>': return value > filter.value; case '>=': return value >= filter.value; case '<': return value < filter.value; case '<=': return value <= filter.value; case 'between': return value >= filter.value && value <= (filter.value2 ?? filter.value); default: return true; } };