mirror of
https://github.com/christianvidalwolf-prog/Craze-Data-check.git
synced 2026-08-03 12:35:25 +02:00
feat: enhance PricingView search bar with a searchable dropdown list
This commit is contained in:
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user