import * as React from "react"; import { X, Check, ChevronsUpDown, Search, User, Users } from "lucide-react"; import { cn } from "@/lib/utils"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Separator } from "@/components/ui/separator"; export interface ComboboxOption { value: number; label: string; department?: string; role?: string; } interface ComboboxMultiProps { options: ComboboxOption[]; selectedValues: number[]; onValuesChange: (values: number[]) => void; placeholder?: string; emptyText?: string; label?: string; } export function ComboboxMulti({ options, selectedValues, onValuesChange, placeholder = "Select options", emptyText = "No options found.", label, }: ComboboxMultiProps) { const [isOpen, setIsOpen] = React.useState(false); const [searchTerm, setSearchTerm] = React.useState(""); const containerRef = React.useRef(null); // Safety checks const safeOptions = Array.isArray(options) ? options : []; const safeSelectedValues = Array.isArray(selectedValues) ? selectedValues : []; // Close dropdown when clicking outside React.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); }, []); // Filter options based on search term const filteredOptions = React.useMemo(() => { if (!searchTerm) return safeOptions; return safeOptions.filter(option => option.label.toLowerCase().includes(searchTerm.toLowerCase()) ); }, [safeOptions, searchTerm]); // Toggle option selection const toggleOption = React.useCallback((value: number) => { try { if (safeSelectedValues.includes(value)) { onValuesChange(safeSelectedValues.filter(v => v !== value)); } else { onValuesChange([...safeSelectedValues, value]); } } catch (error) { console.error("Error toggling option selection:", error); } }, [safeSelectedValues, onValuesChange]); // Select all options const selectAll = React.useCallback(() => { try { const allValues = safeOptions.map(option => option.value); onValuesChange(allValues); } catch (error) { console.error("Error selecting all options:", error); } }, [safeOptions, onValuesChange]); // Clear all selections (except any that should be preserved) const clearAll = React.useCallback(() => { try { onValuesChange([]); } catch (error) { console.error("Error clearing selections:", error); } }, [onValuesChange]); // Get selected option names for display const getSelectedOptionLabels = React.useCallback(() => { const selectedCount = safeSelectedValues.length; if (selectedCount === 0) { return placeholder; } else if (selectedCount === 1) { const option = safeOptions.find(o => o.value === safeSelectedValues[0]); return option ? option.label : '1 selected'; } else if (selectedCount <= 2) { return safeSelectedValues .map(value => safeOptions.find(option => option.value === value)?.label) .filter(Boolean) .join(", "); } else { return `${selectedCount} attendees selected`; } }, [safeOptions, safeSelectedValues, placeholder]); // Calculate unselected options count const unselectedCount = React.useMemo(() => { return safeOptions.length - safeSelectedValues.length; }, [safeOptions, safeSelectedValues]); return (
{label && }
setIsOpen(!isOpen)} >
{getSelectedOptionLabels()}
{isOpen && (
setSearchTerm(e.target.value)} className="h-9 pl-8" onClick={(e) => e.stopPropagation()} />
{safeOptions.length > 3 && (

{safeSelectedValues.length} of {safeOptions.length} selected

)}
{filteredOptions.length === 0 ? (
{emptyText}
) : (
{filteredOptions.map((option) => (
{ e.stopPropagation(); toggleOption(option.value); }} >
{option.label} {option.role && ( {option.role} )}
{safeSelectedValues.includes(option.value) && ( )}
))}
)}
)} {safeSelectedValues.length > 0 && (
{safeSelectedValues.map(value => { const option = safeOptions.find(o => o.value === value); if (!option) return null; return ( {option.label} ); })}
)}
); }