UI: Add multi-select filter dropdown to Type column in Pricing view

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Christian Vidal Wolf
2026-04-23 13:31:39 +02:00
co-authored by Claude Sonnet 4.6
parent f545776e15
commit e7a3a4619d
+47 -3
View File
@@ -15,6 +15,7 @@ import {
Check,
Search,
MessageSquare,
Maximize2,
} from 'lucide-react';
import { cn } from '../lib/utils';
import { ColumnFilterPopover } from './ColumnFilterPopover';
@@ -78,6 +79,7 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
const inputRef = useRef<HTMLInputElement>(null);
const [lineMultiFilter, setLineMultiFilter] = useState<string[]>([]);
const [classificationFilter, setClassificationFilter] = useState<string[]>([]);
const [productTypeFilter, setProductTypeFilter] = useState<string[]>([]);
const [nameColFilter, setNameColFilter] = useState('');
const [openFilter, setOpenFilter] = useState<string | null>(null);
const [unitsOuterFilter, setUnitsOuterFilter] = useState<string[]>([]);
@@ -92,6 +94,7 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
const typeInputRef = useRef<HTMLInputElement>(null);
const logisticInputRef = useRef<HTMLInputElement>(null);
const [sortConfig, setSortConfig] = useState<{ key: string | number; direction: 'asc' | 'desc' | null }>({ key: null, direction: null });
const [isFullscreen, setIsFullscreen] = useState(false);
const [isSearchOpen, setIsSearchOpen] = useState(false);
const [selectedSearchItems, setSelectedSearchItems] = useState<Set<number>>(new Set());
@@ -276,6 +279,10 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
Array.from(new Set(data.map(r => String(r[COLUMNS.CLASSIFICATION] || '')))).sort(),
[data]);
const uniqueProductTypes = useMemo(() =>
Array.from(new Set(data.map(r => String(r[COLUMNS.PRODUCT_TYPE] || '')).filter(Boolean))).sort(),
[data]);
const uniqueUnitsOuter = useMemo(() =>
Array.from(new Set(data.map(r => String(r[unitsOuterIdx] || '')))).filter(v => v).sort(),
[data, unitsOuterIdx]);
@@ -331,6 +338,9 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
if (classificationFilter.length > 0) {
result = result.filter(r => classificationFilter.includes(String(r.row[COLUMNS.CLASSIFICATION] || '')));
}
if (productTypeFilter.length > 0) {
result = result.filter(r => productTypeFilter.includes(String(r.row[COLUMNS.PRODUCT_TYPE] || '')));
}
if (unitsOuterFilter.length > 0) {
result = result.filter(r => unitsOuterFilter.includes(String(r.row[unitsOuterIdx] || '')));
}
@@ -361,7 +371,7 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
}
return result;
}, [analyzedRows, filterMode, search, nameColFilter, lineMultiFilter, classificationFilter, unitsOuterFilter, outerWFilter, outerLFilter, outerHFilter, dynamicColFilters, weightIssueFilter]);
}, [analyzedRows, filterMode, search, nameColFilter, lineMultiFilter, classificationFilter, productTypeFilter, unitsOuterFilter, outerWFilter, outerLFilter, outerHFilter, dynamicColFilters, weightIssueFilter]);
// ── Sorted rows ──────────────────────────────────────────────────────────
const sortedRows = useMemo(() => {
@@ -646,7 +656,16 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
];
return (
<div className="flex flex-col h-full gap-4 relative">
<div className={isFullscreen ? "fixed inset-0 z-40 bg-[#041021] p-4 overflow-auto" : "space-y-6"}>
{isFullscreen && (
<button
onClick={() => setIsFullscreen(false)}
className="fixed top-4 right-4 z-50 w-10 h-10 flex items-center justify-center bg-slate-800/90 backdrop-blur border border-slate-600 text-white rounded hover:bg-slate-700 hover:scale-105 transition-all"
title="Exit fullscreen"
>
<X className="w-5 h-5" />
</button>
)}
{/* ── Anna Note Editor Modal ── */}
{annaEditor && (
<div className="fixed inset-0 bg-slate-950/50 backdrop-blur-sm z-[100] flex items-center justify-center p-4">
@@ -857,6 +876,13 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
<StatCard label="Pricing Issues" value={stats.withPricing} icon={<DollarSign className="w-4 h-4" />} color="amber" />
<StatCard label="Units Issues" value={stats.withUnits} icon={<AlertCircle className="w-4 h-4" />} color="red" />
<StatCard label="All OK" value={stats.allOk} icon={<CheckCircle2 className="w-4 h-4" />} color="emerald" />
<button
onClick={() => setIsFullscreen(true)}
className="flex items-center justify-center w-full h-full min-h-[80px] bg-slate-800/60 hover:bg-slate-700 border border-slate-700/50 rounded-xl transition-colors"
title="Maximize view"
>
<Maximize2 className="w-5 h-5 text-slate-400" />
</button>
</div>
{/* ── Table ── */}
@@ -951,7 +977,25 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
</th>
<th style={{ width: columnWidths.productType }} className="text-left px-3 py-3 text-xs font-semibold text-purple-400 uppercase tracking-wider whitespace-nowrap group cursor-pointer hover:bg-slate-800/50 transition-colors relative" onClick={() => handleSort('productType')}>
<span className="flex items-center gap-1">Type <SortIcon current={sortConfig.key === 'productType' ? sortConfig.direction : null} /></span>
<span className="flex items-center gap-1">
Type <SortIcon current={sortConfig.key === 'productType' ? sortConfig.direction : null} />
<button
onClick={(e: React.MouseEvent) => { e.stopPropagation(); setOpenFilter(openFilter === 'productType' ? null : 'productType'); }}
className={cn('ml-1 p-0.5 rounded transition-colors', productTypeFilter.length > 0 ? 'text-purple-400 bg-purple-400/10' : 'text-slate-500 opacity-0 group-hover:opacity-100')}
>
<Filter className="w-3 h-3" />
</button>
</span>
{openFilter === 'productType' && (
<ColumnFilterPopover
uniqueValues={uniqueProductTypes}
selectedValues={productTypeFilter}
onToggle={(val: string) => setProductTypeFilter((prev: string[]) => prev.includes(val) ? prev.filter((v: string) => v !== val) : [...prev, val])}
onSelectAll={vals => setProductTypeFilter(vals)}
onClear={() => { setProductTypeFilter([]); setOpenFilter(null); }}
onClose={() => setOpenFilter(null)}
/>
)}
<ResizeHandle onMouseDown={e => { e.stopPropagation(); handleResizeStart(e, 'productType', columnWidths.productType); }} />
</th>