'use client'; import { useEffect, useRef, useState } from 'react'; import type { ObjektOption, SelectedObjekt } from '@/types/logbuch'; interface Props { selected: SelectedObjekt[]; onChange: (objekte: SelectedObjekt[]) => void; kategorie?: 'stern' | 'sonne'; fixedItems?: SelectedObjekt[]; } export default function ObjektSelector({ selected, onChange, kategorie = 'stern', fixedItems = [] }: Props) { const [all, setAll] = useState([]); const [search, setSearch] = useState(''); const [dropdownOpen, setDropdownOpen] = useState(false); const wrapperRef = useRef(null); const inputRef = useRef(null); useEffect(() => { fetch('/api/objekte?kategorie=' + kategorie) .then((r) => { if (!r.ok) throw new Error('Fehler'); return r.json(); }) .then(setAll) .catch(() => {}); }, [kategorie]); useEffect(() => { function handleOutside(e: MouseEvent) { if (wrapperRef.current && !wrapperRef.current.contains(e.target as Node)) { setDropdownOpen(false); } } if (dropdownOpen) document.addEventListener('mousedown', handleOutside); return () => document.removeEventListener('mousedown', handleOutside); }, [dropdownOpen]); const fixedNamesLower = new Set(fixedItems.map((o) => o.Name.toLowerCase())); const selectedNames = new Set(selected.map((o) => o.Name.toLowerCase())); const available = all.filter( (o) => !selectedNames.has(o.Name.toLowerCase()) && !fixedNamesLower.has(o.Name.toLowerCase()) ); const filtered = search ? available.filter((o) => o.Name.toLowerCase().startsWith(search.toLowerCase())) : available; const searchTrimmed = search.trim(); const alreadySelected = searchTrimmed !== '' && (selectedNames.has(searchTrimmed.toLowerCase()) || fixedNamesLower.has(searchTrimmed.toLowerCase())); const exactAvailableMatch = available.find((o) => o.Name.toLowerCase() === searchTrimmed.toLowerCase()); const showAddNew = searchTrimmed !== '' && !alreadySelected && !exactAvailableMatch; function add(obj: ObjektOption) { onChange([...selected, { ID: obj.ID, Name: obj.Name }]); setSearch(''); inputRef.current?.focus(); } function addNew(name: string) { const trimmed = name.trim(); if (!trimmed || selectedNames.has(trimmed.toLowerCase()) || fixedNamesLower.has(trimmed.toLowerCase())) return; const existing = all.find((o) => o.Name.toLowerCase() === trimmed.toLowerCase()); if (existing) { onChange([...selected, { ID: existing.ID, Name: existing.Name }]); } else { onChange([...selected, { ID: null, Name: trimmed }]); } setSearch(''); inputRef.current?.focus(); } function remove(name: string) { onChange(selected.filter((o) => o.Name !== name)); } function handleKeyDown(e: React.KeyboardEvent) { if (e.key !== 'Enter') return; e.preventDefault(); if (filtered.length === 1 && !showAddNew) { add(filtered[0]); } else if (filtered.length === 0 && searchTrimmed) { addNew(searchTrimmed); } } return (
{fixedItems.map((o) => ( {o.Name} ))} {selected.map((o) => ( {o.Name} ))}
{ setSearch(e.target.value); setDropdownOpen(true); }} onFocus={() => setDropdownOpen(true)} onKeyDown={handleKeyDown} placeholder="Objekt suchen oder neu eingeben..." className="w-full px-3 py-2 border-2 border-gray-400 rounded-lg bg-white text-sm text-gray-900 focus:border-blue-500 focus:outline-none" /> {dropdownOpen && (filtered.length > 0 || showAddNew) && (
{filtered.map((o) => ( ))} {showAddNew && ( )}
)}
); }