feat: enhance PricingView search bar with a searchable dropdown list

This commit is contained in:
Christian Vidal Wolf
2026-04-20 20:34:28 +02:00
parent ecf1c7831c
commit 9878efb2ca
+74 -19
View File
@@ -63,6 +63,29 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
const [outerHFilter, setOuterHFilter] = useState<string[]>([]); const [outerHFilter, setOuterHFilter] = useState<string[]>([]);
const [dynamicColFilters, setDynamicColFilters] = useState<Record<number, string[]>>({}); const [dynamicColFilters, setDynamicColFilters] = useState<Record<number, string[]>>({});
const [isSearchOpen, setIsSearchOpen] = useState(false);
const searchDropdownRef = useRef<HTMLDivElement>(null);
// Close search dropdown on click outside
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (searchDropdownRef.current && !searchDropdownRef.current.contains(event.target as Node)) {
setIsSearchOpen(false);
}
};
document.addEventListener('mousedown', handleClickOutside);
return () => document.removeEventListener('mousedown', handleClickOutside);
}, []);
const searchSuggestions = useMemo(() => {
if (!search) return data.slice(0, 50); // Show first 50 as default
const s = search.toLowerCase();
return data.filter(r =>
String(r[COLUMNS.ARTICLE_NO] || '').toLowerCase().includes(s) ||
String(r[COLUMNS.ARTICLE_NAME] || '').toLowerCase().includes(s)
).slice(0, 50);
}, [data, search]);
// ── Dynamic column detection ────────────────────────────────────────────── // ── Dynamic column detection ──────────────────────────────────────────────
const { uvpIdx, srpCols, containerCols } = useMemo(() => { const { uvpIdx, srpCols, containerCols } = useMemo(() => {
const uvpIdx = findCol(headers, 'uvp'); const uvpIdx = findCol(headers, 'uvp');
@@ -407,25 +430,57 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
)} )}
<div className="flex items-center justify-between gap-4"> <div className="flex items-center justify-between gap-4">
{/* ── Search bar ── */} {/* ── Search bar with Dropdown ── */}
<div className="flex-1 max-w-md relative"> <div className="flex-1 max-w-md relative" ref={searchDropdownRef}>
<input <div className="relative">
type="text" <input
placeholder="Search SKU or Name..." type="text"
value={search} placeholder="Search SKU or Name..."
onChange={e => setSearch(e.target.value)} value={search}
className="w-full pl-4 pr-10 py-2 bg-slate-800 border border-slate-700 rounded-lg text-sm text-white focus:outline-none focus:border-blue-500 focus:ring-1 focus:ring-blue-500 transition-all" onChange={e => {
/> setSearch(e.target.value);
{search ? ( setIsSearchOpen(true);
<button }}
onClick={() => setSearch('')} onFocus={() => setIsSearchOpen(true)}
className="absolute right-3 top-1/2 -translate-y-1/2 text-slate-400 hover:text-white" className="w-full pl-4 pr-10 py-2 bg-slate-800 border border-slate-700 rounded-lg text-sm text-white focus:outline-none focus:border-blue-500 focus:ring-1 focus:ring-blue-500 transition-all"
> />
<X className="w-4 h-4" /> {search ? (
</button> <button
) : ( onClick={() => { setSearch(''); setIsSearchOpen(false); }}
<div className="absolute right-3 top-1/2 -translate-y-1/2 text-slate-500"> className="absolute right-3 top-1/2 -translate-y-1/2 text-slate-400 hover:text-white"
<Package className="w-4 h-4" /> >
<X className="w-4 h-4" />
</button>
) : (
<div className="absolute right-3 top-1/2 -translate-y-1/2 text-slate-500">
<Package className="w-4 h-4" />
</div>
)}
</div>
{/* Search Dropdown Panel */}
{isSearchOpen && searchSuggestions.length > 0 && (
<div className="absolute top-full left-0 right-0 mt-1 bg-slate-800 border border-slate-700 rounded-lg shadow-2xl z-[150] overflow-hidden animate-in fade-in slide-in-from-top-2 duration-200">
<div className="max-h-64 overflow-y-auto">
{searchSuggestions.map((row, idx) => (
<button
key={`${row[COLUMNS.ARTICLE_NO]}-${idx}`}
onClick={() => {
setSearch(String(row[COLUMNS.ARTICLE_NO]));
setIsSearchOpen(false);
}}
className="w-full text-left px-4 py-2.5 hover:bg-slate-700/50 flex flex-col gap-0.5 border-b border-slate-700/30 last:border-0 transition-colors"
>
<span className="text-xs font-mono font-bold text-blue-400">{row[COLUMNS.ARTICLE_NO]}</span>
<span className="text-xs text-slate-300 truncate">{row[COLUMNS.ARTICLE_NAME]}</span>
</button>
))}
</div>
{searchSuggestions.length === 50 && (
<div className="px-4 py-1.5 bg-slate-900/50 text-[10px] text-slate-500 border-t border-slate-700 italic">
Showing first 50 results...
</div>
)}
</div> </div>
)} )}
</div> </div>