mirror of
https://github.com/christianvidalwolf-prog/CrazeAnalytix.git
synced 2026-08-03 19:15:22 +02:00
139 lines
6.0 KiB
TypeScript
139 lines
6.0 KiB
TypeScript
import React, { useState, useRef, useEffect } from 'react';
|
|
|
|
interface InColumnStockFilterProps {
|
|
currentFilters: string[];
|
|
onFilterChange: (newFilters: string[]) => void;
|
|
title?: string;
|
|
options?: string[];
|
|
icon?: React.ReactNode;
|
|
}
|
|
|
|
const DEFAULT_STOCK_OPTIONS = [
|
|
'Out of Stock (0)',
|
|
'In Stock (>0)',
|
|
'Low Stock (<10)',
|
|
];
|
|
|
|
export const InColumnStockFilter: React.FC<InColumnStockFilterProps> = ({
|
|
currentFilters,
|
|
onFilterChange,
|
|
title = "Stock Filter",
|
|
options = DEFAULT_STOCK_OPTIONS,
|
|
icon
|
|
}) => {
|
|
const [isOpen, setIsOpen] = useState(false);
|
|
const [customInput, setCustomInput] = useState('');
|
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
const inputRef = useRef<HTMLInputElement>(null);
|
|
|
|
// Close when clicking outside
|
|
useEffect(() => {
|
|
const handleClickOutside = (event: MouseEvent) => {
|
|
if (containerRef.current && !containerRef.current.contains(event.target as Node)) {
|
|
setIsOpen(false);
|
|
}
|
|
};
|
|
document.addEventListener('mousedown', handleClickOutside);
|
|
return () => document.removeEventListener('mousedown', handleClickOutside);
|
|
}, []);
|
|
|
|
const toggleFilter = (option: string) => {
|
|
let newFilters = [...currentFilters];
|
|
if (newFilters.includes(option)) {
|
|
newFilters = newFilters.filter(f => f !== option);
|
|
} else {
|
|
newFilters.push(option);
|
|
}
|
|
onFilterChange(newFilters);
|
|
};
|
|
|
|
const clearFilters = () => {
|
|
onFilterChange([]);
|
|
setIsOpen(false);
|
|
};
|
|
|
|
const handleCustomAdd = () => {
|
|
if (customInput.trim()) {
|
|
if (!currentFilters.includes(customInput.trim())) {
|
|
onFilterChange([...currentFilters, customInput.trim()]);
|
|
}
|
|
setCustomInput('');
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="relative inline-block ml-1" ref={containerRef}>
|
|
<button
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
setIsOpen(!isOpen);
|
|
}}
|
|
className={`flex flex-col items-center gap-0.5 p-1 rounded hover:bg-white/10 transition-all ${currentFilters.length > 0 ? 'text-indigo-400 bg-indigo-500/10' : 'text-slate-500'}`}
|
|
title={`Filter by ${title}`}
|
|
>
|
|
{icon ? (
|
|
<div className="transition-transform group-hover:scale-110">
|
|
{icon}
|
|
</div>
|
|
) : (
|
|
<svg className="w-3 h-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={3} d="M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707l-6.414 6.414a1 1 0 00-.293.707V17l-4 4v-6.586a1 1 0 00-.293-.707L3.293 7.293A1 1 0 013 6.586V4z" />
|
|
</svg>
|
|
)}
|
|
</button>
|
|
|
|
{isOpen && (
|
|
<div className="absolute left-0 mt-2 w-56 bg-slate-900 border border-slate-700 rounded-xl shadow-2xl z-[999] p-2 animate-in fade-in zoom-in duration-150">
|
|
<div className="px-2 py-1.5 text-[10px] font-black text-slate-500 uppercase tracking-widest border-b border-slate-800 mb-2 flex justify-between items-center">
|
|
<span>{title}</span>
|
|
{currentFilters.length > 0 && (
|
|
<button onClick={clearFilters} className="text-indigo-400 hover:text-indigo-300">Clear</button>
|
|
)}
|
|
</div>
|
|
|
|
{/* Custom Input */}
|
|
<div className="px-2 mb-2 flex gap-1">
|
|
<input
|
|
ref={inputRef}
|
|
type="text"
|
|
className="bg-slate-950 border border-slate-700 text-xs text-white rounded px-2 py-1 w-full focus:border-indigo-500 focus:outline-none placeholder:text-slate-600"
|
|
placeholder="e.g. >10, 50-100"
|
|
value={customInput}
|
|
onChange={(e) => setCustomInput(e.target.value)}
|
|
onKeyDown={(e) => {
|
|
if (e.key === 'Enter') handleCustomAdd();
|
|
e.stopPropagation(); // Prevent grid row clicks if any
|
|
}}
|
|
autoFocus
|
|
/>
|
|
<button
|
|
onClick={handleCustomAdd}
|
|
className="bg-indigo-600 hover:bg-indigo-500 text-white px-2 rounded text-xs font-bold"
|
|
>
|
|
+
|
|
</button>
|
|
</div>
|
|
{options.map(option => {
|
|
const isSelected = currentFilters.includes(option);
|
|
return (
|
|
<div
|
|
key={option}
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
toggleFilter(option);
|
|
}}
|
|
className={`flex items-center gap-2 px-2 py-1.5 rounded-lg cursor-pointer transition-colors ${isSelected ? 'bg-indigo-500/20 text-indigo-300' : 'text-slate-300 hover:bg-white/5'}`}
|
|
>
|
|
<div className={`w-3.5 h-3.5 rounded border flex items-center justify-center transition-colors ${isSelected ? 'bg-indigo-500 border-indigo-400' : 'border-slate-600'}`}>
|
|
{isSelected && <svg className="w-2.5 h-2.5 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={4}><path d="M5 13l4 4L19 7" /></svg>}
|
|
</div>
|
|
<span className="text-[11px] font-bold">{option}</span>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
};
|