'use client' import { useState, useRef, useEffect, useCallback } from 'react' import { DIMENSION_LABELS, OPERATORS, OPERATOR_LABELS, type DimensionFilter } from '@/lib/filters' export interface FilterSuggestion { value: string label: string count?: number } export interface FilterSuggestions { [dimension: string]: FilterSuggestion[] } interface AddFilterDropdownProps { onAdd: (filter: DimensionFilter) => void suggestions?: FilterSuggestions onFetchSuggestions?: (dimension: string) => Promise } const ALL_DIMS = ['page', 'referrer', 'country', 'region', 'city', 'browser', 'os', 'device', 'utm_source', 'utm_medium', 'utm_campaign'] export default function AddFilterDropdown({ onAdd, suggestions = {}, onFetchSuggestions }: AddFilterDropdownProps) { const [isOpen, setIsOpen] = useState(false) const [selectedDim, setSelectedDim] = useState(null) const [operator, setOperator] = useState('is') const [search, setSearch] = useState('') const [fetchedSuggestions, setFetchedSuggestions] = useState([]) const [isFetching, setIsFetching] = useState(false) const ref = useRef(null) const inputRef = useRef(null) // Close on outside click or Escape useEffect(() => { if (!isOpen) return function handleClick(e: MouseEvent) { if (ref.current && !ref.current.contains(e.target as Node)) { handleClose() } } function handleEsc(e: KeyboardEvent) { if (e.key === 'Escape') handleClose() } document.addEventListener('mousedown', handleClick) document.addEventListener('keydown', handleEsc) return () => { document.removeEventListener('mousedown', handleClick) document.removeEventListener('keydown', handleEsc) } }, [isOpen]) // Focus search input when a dimension is selected useEffect(() => { if (selectedDim) inputRef.current?.focus() }, [selectedDim]) // Fetch full suggestions when a dimension is selected useEffect(() => { if (!selectedDim || !onFetchSuggestions) { setFetchedSuggestions([]) return } let cancelled = false setIsFetching(true) onFetchSuggestions(selectedDim).then(data => { if (!cancelled) { setFetchedSuggestions(data) setIsFetching(false) } }).catch(() => { if (!cancelled) setIsFetching(false) }) return () => { cancelled = true } }, [selectedDim, onFetchSuggestions]) const handleClose = useCallback(() => { setIsOpen(false) setSelectedDim(null) setOperator('is') setSearch('') setFetchedSuggestions([]) }, []) function handleSelectValue(value: string) { onAdd({ dimension: selectedDim!, operator, values: [value] }) handleClose() } function handleSubmitCustom() { if (!search.trim() || !selectedDim) return onAdd({ dimension: selectedDim, operator, values: [search.trim()] }) handleClose() } // Use fetched data if available, fall back to prop suggestions const dimSuggestions = selectedDim ? (fetchedSuggestions.length > 0 ? fetchedSuggestions : (suggestions[selectedDim] || [])) : [] const filtered = dimSuggestions.filter(s => s.label.toLowerCase().includes(search.toLowerCase()) || s.value.toLowerCase().includes(search.toLowerCase()) ) return (
{isOpen && (
{!selectedDim ? ( /* Step 1: Dimension list */
{ALL_DIMS.map(dim => ( ))}
) : ( /* Step 2: Operator + search + values */ <> {/* Header with back button */}
{DIMENSION_LABELS[selectedDim]}
{/* Operator pills */}
{OPERATORS.map(op => ( ))}
{/* Search input */}
setSearch(e.target.value)} onKeyDown={e => { if (e.key === 'Enter') { if (filtered.length === 1) { handleSelectValue(filtered[0].value) } else { handleSubmitCustom() } } }} placeholder={`Search ${DIMENSION_LABELS[selectedDim]?.toLowerCase()}...`} className="w-full px-3 py-2 text-sm bg-neutral-50 dark:bg-neutral-800 border border-neutral-200 dark:border-neutral-700 rounded-lg text-neutral-900 dark:text-white placeholder-neutral-400 focus:outline-none focus:ring-2 focus:ring-brand-orange/40 focus:border-brand-orange transition-colors" />
{/* Values list */} {isFetching ? (
) : filtered.length > 0 ? (
{filtered.map(s => ( ))}
) : search.trim() ? (
) : null} )}
)}
) }