Add dynamic column filters to all pricing columns

Add filter dropdowns to SRP, UVP, and container columns in Pricing tab
This commit is contained in:
Christian Vidal Wolf
2026-04-20 17:01:47 +02:00
parent 5f96272182
commit af2ed8a037
+86 -13
View File
@@ -55,15 +55,12 @@ 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' | 'unitsOuter' | 'outerW' | 'outerL' | 'outerH' | 'srp' | 'container' | null>(null);
const [openFilter, setOpenFilter] = useState<string | 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[]>([]);
const [dynamicColFilters, setDynamicColFilters] = useState<Record<number, string[]>>({});
// ── Dynamic column detection ──────────────────────────────────────────────
const { uvpIdx, srpCols, containerCols } = useMemo(() => {
@@ -214,8 +211,17 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
result = result.filter(r => outerHFilter.includes(String(r.row[COLUMNS.OUTER_H] || '')));
}
// Dynamic column filters (SRP, container)
(Object.keys(dynamicColFilters) as string[]).forEach(colIdx => {
const filterVals = dynamicColFilters[Number(colIdx)];
if (filterVals && filterVals.length > 0) {
const col = Number(colIdx);
result = result.filter(r => filterVals.includes(String(r.row[col] || '')));
}
});
return result;
}, [analyzedRows, filterMode, search, nameColFilter, lineMultiFilter, classificationFilter, unitsOuterFilter, outerWFilter, outerLFilter, outerHFilter]);
}, [analyzedRows, filterMode, search, nameColFilter, lineMultiFilter, classificationFilter, unitsOuterFilter, outerWFilter, outerLFilter, outerHFilter, dynamicColFilters]);
// ── Inline edit helpers ───────────────────────────────────────────────────
const startEdit = (rowIndex: number, colIndex: number, currentValue: string) => {
@@ -249,6 +255,18 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
...srpCols,
];
// ═ Unique values for dynamic pricing columns ═════════════════════════════
const dynamicColUniqueValues = useMemo(() => {
const result: Record<number, string[]> = {};
pricingEditableCols.forEach(col => {
result[col.index] = Array.from(new Set(data.map(r => String(r[col.index] || '')))).filter(v => v).sort();
});
containerCols.forEach(col => {
result[col.index] = Array.from(new Set(data.map(r => String(r[col.index] || '')))).filter(v => v).sort();
});
return result;
}, [data, uvpIdx, srpCols, containerCols, headers]);
const formatPrice = (val: any) => {
if (val === undefined || val === null || val === '') return '';
const n = parseFloat(String(val).replace(',', '.'));
@@ -468,11 +486,37 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
{/* Editable pricing columns */}
{pricingEditableCols.map(col => (
<th key={col.index} className="text-left px-3 py-3 text-xs font-semibold text-blue-400 uppercase tracking-wider border-b border-slate-700 whitespace-nowrap">
<span className="flex items-center gap-1">
<Edit2 className="w-3 h-3" />
{col.name}
</span>
<th key={col.index} className="text-left px-3 py-3 text-xs font-semibold text-blue-400 uppercase tracking-wider border-b border-slate-700 whitespace-nowrap group relative">
<div className="flex items-center gap-1">
<span>{col.name}</span>
<button
onClick={(e) => {
e.stopPropagation();
setOpenFilter(openFilter === `prc_${col.index}` ? null : `prc_${col.index}`);
}}
className={cn(
'p-0.5 rounded hover:bg-slate-700 transition-colors',
dynamicColFilters[col.index]?.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 === `prc_${col.index}` && (
<ColumnFilterPopover
uniqueValues={dynamicColUniqueValues[col.index] || []}
selectedValues={dynamicColFilters[col.index] || []}
onToggle={val => setDynamicColFilters(prev => {
const current = prev[col.index] || [];
return current.includes(val)
? { ...prev, [col.index]: current.filter(v => v !== val) }
: { ...prev, [col.index]: [...current, val] };
})}
onSelectAll={vals => setDynamicColFilters(prev => ({ ...prev, [col.index]: vals }))}
onClear={() => { setDynamicColFilters(prev => { const next = { ...prev }; delete next[col.index]; return next; }); setOpenFilter(null); }}
onClose={() => setOpenFilter(null)}
/>
)}
</th>
))}
@@ -582,8 +626,37 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
{/* Container columns */}
{containerCols.map(col => (
<th key={col.index} className="text-left px-3 py-3 text-xs font-semibold text-orange-400 uppercase tracking-wider border-b border-slate-700 whitespace-nowrap">
{col.name}
<th key={col.index} className="text-left px-3 py-3 text-xs font-semibold text-orange-400 uppercase tracking-wider border-b border-slate-700 whitespace-nowrap group relative">
<div className="flex items-center gap-1">
<span>{col.name}</span>
<button
onClick={(e) => {
e.stopPropagation();
setOpenFilter(openFilter === `con_${col.index}` ? null : `con_${col.index}`);
}}
className={cn(
'p-0.5 rounded hover:bg-slate-700 transition-colors',
dynamicColFilters[col.index]?.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 === `con_${col.index}` && (
<ColumnFilterPopover
uniqueValues={dynamicColUniqueValues[col.index] || []}
selectedValues={dynamicColFilters[col.index] || []}
onToggle={val => setDynamicColFilters(prev => {
const current = prev[col.index] || [];
return current.includes(val)
? { ...prev, [col.index]: current.filter(v => v !== val) }
: { ...prev, [col.index]: [...current, val] };
})}
onSelectAll={vals => setDynamicColFilters(prev => ({ ...prev, [col.index]: vals }))}
onClear={() => { setDynamicColFilters(prev => { const next = { ...prev }; delete next[col.index]; return next; }); setOpenFilter(null); }}
onClose={() => setOpenFilter(null)}
/>
)}
</th>
))}