'use client' import { useState, useEffect, useRef, type CSSProperties, type ReactNode } from 'react' import { getGeoData, type GeoData } from '@/lib/geo-cache' interface BarrioAutocompleteFieldProps { name: string label: ReactNode value: string required: boolean departamento?: string | null ciudad?: string | null onChange: (value: string) => void error?: string } export function BarrioAutocompleteField({ name, label, value, required, departamento, ciudad, onChange, error, }: BarrioAutocompleteFieldProps) { const [geoData, setGeoData] = useState(null) const [geoStatus, setGeoStatus] = useState<'loading' | 'ready' | 'error'>('loading') const [open, setOpen] = useState(false) const [activeIdx, setActiveIdx] = useState(-1) const [dropdownStyle, setDropdownStyle] = useState({}) const inputRef = useRef(null) useEffect(() => { getGeoData() .then((data) => { setGeoData(data); setGeoStatus('ready') }) .catch(() => setGeoStatus('error')) }, []) // Close dropdown on scroll so the fixed-position list doesn't drift from the input useEffect(() => { if (!open) return const close = () => setOpen(false) window.addEventListener('scroll', close, { passive: true, capture: true }) return () => window.removeEventListener('scroll', close, { capture: true }) }, [open]) function computeDropdownStyle() { if (!inputRef.current) return const rect = inputRef.current.getBoundingClientRect() setDropdownStyle({ top: rect.bottom + 4, left: rect.left, width: rect.width, }) } const barrios: string[] = (() => { if (!geoData) return [] if (departamento && ciudad) { return geoData[departamento]?.[ciudad] ?? [] } if (departamento) { return Object.values(geoData[departamento] ?? {}).flat() } return Object.values(geoData).flatMap((d) => Object.values(d).flat()) })() const query = value.trim().toLowerCase() const suggestions = query.length >= 2 ? barrios.filter((b) => b.toLowerCase().includes(query)).slice(0, 10) : [] function handleInput(v: string) { onChange(v) computeDropdownStyle() setOpen(true) setActiveIdx(-1) } function select(barrio: string) { onChange(barrio) setOpen(false) setActiveIdx(-1) } function handleKeyDown(e: React.KeyboardEvent) { if (!open || suggestions.length === 0) return if (e.key === 'ArrowDown') { e.preventDefault() setActiveIdx((i) => Math.min(i + 1, suggestions.length - 1)) } else if (e.key === 'ArrowUp') { e.preventDefault() setActiveIdx((i) => Math.max(i - 1, -1)) } else if (e.key === 'Enter' && activeIdx >= 0) { e.preventDefault() select(suggestions[activeIdx]) } else if (e.key === 'Escape') { setOpen(false) } } const listId = `${name}-suggestions` return (
0 ? listId : undefined} aria-activedescendant={activeIdx >= 0 ? `${name}-opt-${activeIdx}` : undefined} autoComplete="off" className="text-short-input" ref={inputRef} disabled={geoStatus === 'loading'} placeholder={ geoStatus === 'loading' ? 'Cargando opciones...' : geoStatus === 'error' ? 'No se pudieron cargar las opciones. Podés escribir el nombre manualmente.' : undefined } onChange={(e) => handleInput(e.target.value)} onKeyDown={handleKeyDown} onFocus={() => { if (suggestions.length > 0) { computeDropdownStyle(); setOpen(true) } }} onBlur={() => setTimeout(() => setOpen(false), 150)} /> {open && suggestions.length > 0 && (
    {suggestions.map((barrio, i) => (
  • select(barrio)} onTouchEnd={(e) => { e.preventDefault(); select(barrio) }} > {barrio}
  • ))}
)}
{error && ( )}
) }