Add column filters for Units/Outer and Outer W/L/H in Pricing tab

Add dropdown filters to filter by specific values in Units/Outer, Outer W, Outer L, Outer H columns
This commit is contained in:
Christian Vidal Wolf
2026-04-20 16:57:02 +02:00
parent 67a0466627
commit 5f96272182
+130 -10
View File
@@ -55,7 +55,15 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
const [lineMultiFilter, setLineMultiFilter] = useState<string[]>([]);
const [classificationFilter, setClassificationFilter] = useState<string[]>([]);
const [nameColFilter, setNameColFilter] = useState('');
const [openFilter, setOpenFilter] = useState<'name' | 'line' | 'classification' | null>(null);
const [openFilter, setOpenFilter] = useState<'name' | 'line' | 'classification' | 'unitsOuter' | 'outerW' | 'outerL' | 'outerH' | 'srp' | 'container' | null>(null);
const [unitsOuterFilter, setUnitsOuterFilter] = useState<string[]>([]);
const [outerWFilter, setOuterWFilter] = useState<string[]>([]);
const [outerLFilter, setOuterLFilter] = useState<string[]>([]);
const [outerHFilter, setOuterHFilter] = useState<string[]>([]);
const [srpFilter, setSrpFilter] = useState<string[]>([]);
const [containerFilter, setContainerFilter] = useState<string[]>([]);
const [srpColFilter, setSrpColFilter] = useState<string[]>([]);
const [containerColFilter, setContainerColFilter] = useState<string[]>([]);
// ── Dynamic column detection ──────────────────────────────────────────────
const { uvpIdx, srpCols, containerCols } = useMemo(() => {
@@ -146,6 +154,22 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
Array.from(new Set(data.map(r => String(r[COLUMNS.CLASSIFICATION] || '')))).sort(),
[data]);
const uniqueUnitsOuter = useMemo(() =>
Array.from(new Set(data.map(r => String(r[COLUMNS.UNITS_OUTER] || '')))).filter(v => v).sort(),
[data]);
const uniqueOuterW = useMemo(() =>
Array.from(new Set(data.map(r => String(r[COLUMNS.OUTER_W] || '')))).filter(v => v).sort((a, b) => Number(a) - Number(b)),
[data]);
const uniqueOuterL = useMemo(() =>
Array.from(new Set(data.map(r => String(r[COLUMNS.OUTER_L] || '')))).filter(v => v).sort((a, b) => Number(a) - Number(b)),
[data]);
const uniqueOuterH = useMemo(() =>
Array.from(new Set(data.map(r => String(r[COLUMNS.OUTER_H] || '')))).filter(v => v).sort((a, b) => Number(a) - Number(b)),
[data]);
// ── Filtered rows ─────────────────────────────────────────────────────────
const filteredRows = useMemo(() => {
let result = analyzedRows;
@@ -177,9 +201,21 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
if (classificationFilter.length > 0) {
result = result.filter(r => classificationFilter.includes(String(r.row[COLUMNS.CLASSIFICATION] || '')));
}
if (unitsOuterFilter.length > 0) {
result = result.filter(r => unitsOuterFilter.includes(String(r.row[COLUMNS.UNITS_OUTER] || '')));
}
if (outerWFilter.length > 0) {
result = result.filter(r => outerWFilter.includes(String(r.row[COLUMNS.OUTER_W] || '')));
}
if (outerLFilter.length > 0) {
result = result.filter(r => outerLFilter.includes(String(r.row[COLUMNS.OUTER_L] || '')));
}
if (outerHFilter.length > 0) {
result = result.filter(r => outerHFilter.includes(String(r.row[COLUMNS.OUTER_H] || '')));
}
return result;
}, [analyzedRows, filterMode, search, nameColFilter, lineMultiFilter, classificationFilter]);
}, [analyzedRows, filterMode, search, nameColFilter, lineMultiFilter, classificationFilter, unitsOuterFilter, outerWFilter, outerLFilter, outerHFilter]);
// ── Inline edit helpers ───────────────────────────────────────────────────
const startEdit = (rowIndex: number, colIndex: number, currentValue: string) => {
@@ -441,23 +477,107 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
))}
{/* Units/Outer */}
<th className="text-left px-3 py-3 text-xs font-semibold text-slate-400 uppercase tracking-wider border-b border-slate-700 whitespace-nowrap">
Units/Outer
<th className="text-left px-3 py-3 text-xs font-semibold text-slate-400 uppercase tracking-wider border-b border-slate-700 whitespace-nowrap group relative">
<div className="flex items-center gap-1">
<span>Units/Outer</span>
<button
onClick={() => setOpenFilter(openFilter === 'unitsOuter' ? null : 'unitsOuter')}
className={cn(
'p-0.5 rounded hover:bg-slate-700 transition-colors',
unitsOuterFilter.length > 0 ? 'text-blue-400 bg-blue-400/10' : 'text-slate-500 opacity-0 group-hover:opacity-100'
)}
>
<Filter className="w-3 h-3" />
</button>
</div>
{openFilter === 'unitsOuter' && (
<ColumnFilterPopover
uniqueValues={uniqueUnitsOuter}
selectedValues={unitsOuterFilter}
onToggle={val => setUnitsOuterFilter(prev => prev.includes(val) ? prev.filter(v => v !== val) : [...prev, val])}
onSelectAll={vals => setUnitsOuterFilter(vals)}
onClear={() => { setUnitsOuterFilter([]); setOpenFilter(null); }}
onClose={() => setOpenFilter(null)}
/>
)}
</th>
{/* Outer W */}
<th className="text-left px-3 py-3 text-xs font-semibold text-slate-400 uppercase tracking-wider border-b border-slate-700 whitespace-nowrap">
Outer W
<th className="text-left px-3 py-3 text-xs font-semibold text-slate-400 uppercase tracking-wider border-b border-slate-700 whitespace-nowrap group relative">
<div className="flex items-center gap-1">
<span>Outer W</span>
<button
onClick={() => setOpenFilter(openFilter === 'outerW' ? null : 'outerW')}
className={cn(
'p-0.5 rounded hover:bg-slate-700 transition-colors',
outerWFilter.length > 0 ? 'text-blue-400 bg-blue-400/10' : 'text-slate-500 opacity-0 group-hover:opacity-100'
)}
>
<Filter className="w-3 h-3" />
</button>
</div>
{openFilter === 'outerW' && (
<ColumnFilterPopover
uniqueValues={uniqueOuterW}
selectedValues={outerWFilter}
onToggle={val => setOuterWFilter(prev => prev.includes(val) ? prev.filter(v => v !== val) : [...prev, val])}
onSelectAll={vals => setOuterWFilter(vals)}
onClear={() => { setOuterWFilter([]); setOpenFilter(null); }}
onClose={() => setOpenFilter(null)}
/>
)}
</th>
{/* Outer L */}
<th className="text-left px-3 py-3 text-xs font-semibold text-slate-400 uppercase tracking-wider border-b border-slate-700 whitespace-nowrap">
Outer L
<th className="text-left px-3 py-3 text-xs font-semibold text-slate-400 uppercase tracking-wider border-b border-slate-700 whitespace-nowrap group relative">
<div className="flex items-center gap-1">
<span>Outer L</span>
<button
onClick={() => setOpenFilter(openFilter === 'outerL' ? null : 'outerL')}
className={cn(
'p-0.5 rounded hover:bg-slate-700 transition-colors',
outerLFilter.length > 0 ? 'text-blue-400 bg-blue-400/10' : 'text-slate-500 opacity-0 group-hover:opacity-100'
)}
>
<Filter className="w-3 h-3" />
</button>
</div>
{openFilter === 'outerL' && (
<ColumnFilterPopover
uniqueValues={uniqueOuterL}
selectedValues={outerLFilter}
onToggle={val => setOuterLFilter(prev => prev.includes(val) ? prev.filter(v => v !== val) : [...prev, val])}
onSelectAll={vals => setOuterLFilter(vals)}
onClear={() => { setOuterLFilter([]); setOpenFilter(null); }}
onClose={() => setOpenFilter(null)}
/>
)}
</th>
{/* Outer H */}
<th className="text-left px-3 py-3 text-xs font-semibold text-slate-400 uppercase tracking-wider border-b border-slate-700 whitespace-nowrap">
Outer H
<th className="text-left px-3 py-3 text-xs font-semibold text-slate-400 uppercase tracking-wider border-b border-slate-700 whitespace-nowrap group relative">
<div className="flex items-center gap-1">
<span>Outer H</span>
<button
onClick={() => setOpenFilter(openFilter === 'outerH' ? null : 'outerH')}
className={cn(
'p-0.5 rounded hover:bg-slate-700 transition-colors',
outerHFilter.length > 0 ? 'text-blue-400 bg-blue-400/10' : 'text-slate-500 opacity-0 group-hover:opacity-100'
)}
>
<Filter className="w-3 h-3" />
</button>
</div>
{openFilter === 'outerH' && (
<ColumnFilterPopover
uniqueValues={uniqueOuterH}
selectedValues={outerHFilter}
onToggle={val => setOuterHFilter(prev => prev.includes(val) ? prev.filter(v => v !== val) : [...prev, val])}
onSelectAll={vals => setOuterHFilter(vals)}
onClear={() => { setOuterHFilter([]); setOpenFilter(null); }}
onClose={() => setOpenFilter(null)}
/>
)}
</th>
{/* Container columns */}