From dbc47d95cb3972f9317d5d8e4646f26aea0ddc5a Mon Sep 17 00:00:00 2001 From: Christian Vidal Wolf Date: Fri, 24 Apr 2026 12:27:30 +0200 Subject: [PATCH] feat: implement advanced Excel-style Custom Filter for SKU and Name columns and fix search clear bug --- src/components/PricingView.tsx | 162 +++++++++++++++++++++++++-------- 1 file changed, 124 insertions(+), 38 deletions(-) diff --git a/src/components/PricingView.tsx b/src/components/PricingView.tsx index 0da3223..3d4db04 100644 --- a/src/components/PricingView.tsx +++ b/src/components/PricingView.tsx @@ -80,7 +80,8 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit, const [lineMultiFilter, setLineMultiFilter] = useState([]); const [classificationFilter, setClassificationFilter] = useState([]); const [productTypeFilter, setProductTypeFilter] = useState([]); - const [nameColFilter, setNameColFilter] = useState(''); + const [nameColFilter, setNameColFilter] = useState<{ t1: string; t2: string; op: 'and' | 'or' }>({ t1: '', t2: '', op: 'and' }); + const [articleNoColFilter, setArticleNoColFilter] = useState<{ t1: string; t2: string; op: 'and' | 'or' }>({ t1: '', t2: '', op: 'and' }); const [openFilter, setOpenFilter] = useState(null); const [unitsOuterFilter, setUnitsOuterFilter] = useState([]); const [outerWFilter, setOuterWFilter] = useState([]); @@ -335,15 +336,24 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit, } // Column filters - // Column-specific name filter - Multi-word AND support - if (nameColFilter) { - const terms = nameColFilter.toLowerCase().split(/\s+/).filter(Boolean); - if (terms.length > 0) { - result = result.filter(r => { - const name = String(r.row[COLUMNS.ARTICLE_NAME] || '').toLowerCase(); - return terms.every(term => name.includes(term)); - }); - } + // Column-specific name filter - Excel-style Custom Filter + if (nameColFilter.t1 || nameColFilter.t2) { + result = result.filter(r => { + const name = String(r.row[COLUMNS.ARTICLE_NAME] || '').toLowerCase(); + const m1 = nameColFilter.t1 ? name.includes(nameColFilter.t1.toLowerCase()) : (nameColFilter.op === 'and'); + const m2 = nameColFilter.t2 ? name.includes(nameColFilter.t2.toLowerCase()) : (nameColFilter.op === 'and'); + return nameColFilter.op === 'and' ? (m1 && m2) : (m1 || m2); + }); + } + + // Column-specific SKU filter - Excel-style Custom Filter + if (articleNoColFilter.t1 || articleNoColFilter.t2) { + result = result.filter(r => { + const sku = String(r.row[COLUMNS.ARTICLE_NO] || '').toLowerCase(); + const m1 = articleNoColFilter.t1 ? sku.includes(articleNoColFilter.t1.toLowerCase()) : (articleNoColFilter.op === 'and'); + const m2 = articleNoColFilter.t2 ? sku.includes(articleNoColFilter.t2.toLowerCase()) : (articleNoColFilter.op === 'and'); + return articleNoColFilter.op === 'and' ? (m1 && m2) : (m1 || m2); + }); } if (lineMultiFilter.length > 0) { result = result.filter(r => lineMultiFilter.includes(String(r.row[COLUMNS.LINE] || ''))); @@ -761,12 +771,17 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit, /> {selectedSearchItems.size > 0 ? ( ) : search ? ( + {openFilter === 'sku' && ( + setOpenFilter(null)} + /> + )} { e.stopPropagation(); handleResizeStart(e, 'articleNo', columnWidths.articleNo); }} /> handleSort('articleName')}> @@ -922,7 +957,7 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit, onClick={(e) => { e.stopPropagation(); setOpenFilter(openFilter === 'name' ? null : 'name'); }} className={cn( 'p-0.5 rounded hover:bg-slate-700 transition-colors shrink-0', - nameColFilter ? 'text-blue-400 bg-blue-400/10' : 'text-slate-500 opacity-0 group-hover:opacity-100' + (nameColFilter.t1 || nameColFilter.t2) ? 'text-blue-400 bg-blue-400/10' : 'text-slate-500 opacity-0 group-hover:opacity-100' )} > @@ -1607,29 +1642,80 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit, // ── Text filter popover ─────────────────────────────────────────────────────── function TextFilterPopover({ value, onChange, onClose }: { - value: string; - onChange: (v: string) => void; + value: { t1: string; t2: string; op: 'and' | 'or' }; + onChange: (v: { t1: string; t2: string; op: 'and' | 'or' }) => void; onClose: () => void; }) { return ( -
-
- - onChange(e.target.value)} - autoFocus - className="w-full bg-slate-900 border border-slate-700 rounded p-1.5 pl-8 text-xs text-white focus:outline-none focus:border-blue-500" - /> -
- Use spaces to search multiple terms (AND logic) +
+
+
+ +
+ + onChange({ ...value, t1: e.target.value })} + autoFocus + className="w-full bg-slate-900 border border-slate-700 rounded-md p-2 pl-8 text-xs text-white focus:outline-none focus:border-blue-500 transition-all" + /> +
+
+ +
+ + +
+ +
+
+ + onChange({ ...value, t2: e.target.value })} + className="w-full bg-slate-900 border border-slate-700 rounded-md p-2 pl-8 text-xs text-white focus:outline-none focus:border-blue-500 transition-all" + /> +
-
- - + +
+ +
+ +
);