'use client' import { useState, useRef, useEffect, useCallback } from 'react' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, } from '@/components/ui/dialog' import { useToast } from '@/components/ui/use-toast' import { MapPin, Loader2, X } from 'lucide-react' import type { Project } from '@/types' interface NominatimResult { place_id: number display_name: string lat: string lon: string type: string address?: { road?: string house_number?: string postcode?: string city?: string town?: string village?: string municipality?: string state?: string } } interface ProjectDialogProps { open: boolean onOpenChange: (open: boolean) => void onProjectCreated: (project: Project) => void } export function ProjectDialog({ open, onOpenChange, onProjectCreated, }: ProjectDialogProps) { const [title, setTitle] = useState('') const [location, setLocation] = useState('') const [description, setDescription] = useState('') const [einsatzleiter, setEinsatzleiter] = useState('') const [journalfuehrer, setJournalfuehrer] = useState('') const [isCreating, setIsCreating] = useState(false) const { toast } = useToast() // Address autocomplete state const [suggestions, setSuggestions] = useState([]) const [isSearching, setIsSearching] = useState(false) const [showSuggestions, setShowSuggestions] = useState(false) const [selectedCoords, setSelectedCoords] = useState<{ lat: number; lng: number } | null>(null) const debounceRef = useRef(null) const suggestionsRef = useRef(null) // Debounced Nominatim search const searchAddress = useCallback((query: string) => { if (debounceRef.current) clearTimeout(debounceRef.current) if (query.length < 3) { setSuggestions([]) setShowSuggestions(false) return } debounceRef.current = setTimeout(async () => { setIsSearching(true) try { const res = await fetch( `https://nominatim.openstreetmap.org/search?` + `q=${encodeURIComponent(query)}&format=json&addressdetails=1&limit=5&countrycodes=ch,de,at,li,fr,it` ) if (res.ok) { const data: NominatimResult[] = await res.json() setSuggestions(data) setShowSuggestions(data.length > 0) } } catch (e) { console.warn('Nominatim search failed:', e) } finally { setIsSearching(false) } }, 350) }, []) const handleLocationChange = (value: string) => { setLocation(value) setSelectedCoords(null) // Clear coords when typing searchAddress(value) } const handleSelectSuggestion = (result: NominatimResult) => { // Build a clean display name const addr = result.address let displayName = result.display_name if (addr) { const parts: string[] = [] if (addr.road) { parts.push(addr.road + (addr.house_number ? ' ' + addr.house_number : '')) } const city = addr.city || addr.town || addr.village || addr.municipality if (addr.postcode && city) { parts.push(`${addr.postcode} ${city}`) } else if (city) { parts.push(city) } if (parts.length > 0) displayName = parts.join(', ') } setLocation(displayName) setSelectedCoords({ lat: parseFloat(result.lat), lng: parseFloat(result.lon) }) setSuggestions([]) setShowSuggestions(false) } // Close suggestions on click outside useEffect(() => { const handleClickOutside = (e: MouseEvent) => { if (suggestionsRef.current && !suggestionsRef.current.contains(e.target as Node)) { setShowSuggestions(false) } } document.addEventListener('mousedown', handleClickOutside) return () => document.removeEventListener('mousedown', handleClickOutside) }, []) const handleCreate = async () => { if (!title.trim()) { toast({ title: 'Fehler', description: 'Bitte geben Sie einen Titel ein.', variant: 'destructive', }) return } setIsCreating(true) try { const body: any = { title: title.trim(), location: location.trim() || undefined, description: description.trim() || undefined, einsatzleiter: einsatzleiter.trim() || undefined, journalfuehrer: journalfuehrer.trim() || undefined, } // If an address was selected with coordinates, set mapCenter if (selectedCoords) { body.mapCenter = { lng: selectedCoords.lng, lat: selectedCoords.lat } body.mapZoom = 17 } const res = await fetch('/api/projects', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body), }) if (!res.ok) { const data = await res.json() throw new Error(data.error || 'Einsatz konnte nicht erstellt werden') } const data = await res.json() onProjectCreated(data.project) // Reset form setTitle('') setLocation('') setDescription('') setEinsatzleiter('') setJournalfuehrer('') setSelectedCoords(null) setSuggestions([]) } catch (error) { toast({ title: 'Fehler', description: error instanceof Error ? error.message : 'Unbekannter Fehler', variant: 'destructive', }) } finally { setIsCreating(false) } } const handleClose = () => { if (!isCreating) { setTitle('') setLocation('') setDescription('') setEinsatzleiter('') setJournalfuehrer('') setSelectedCoords(null) setSuggestions([]) onOpenChange(false) } } return ( Neuer Einsatz
setTitle(e.target.value)} placeholder="z.B. Wohnungsbrand Musterstrasse" disabled={isCreating} />
handleLocationChange(e.target.value)} placeholder="Adresse eingeben — z.B. Bahnhofstrasse 1, Zürich" disabled={isCreating} autoComplete="off" className={selectedCoords ? 'pr-8 border-green-300 focus:ring-green-500' : ''} /> {isSearching && ( )} {selectedCoords && !isSearching && ( )}
{/* Autocomplete dropdown */} {showSuggestions && suggestions.length > 0 && (
{suggestions.map((s) => { const addr = s.address const city = addr?.city || addr?.town || addr?.village || addr?.municipality || '' return ( ) })}
)}

Adresse suchen — die Karte springt automatisch zum Einsatzort

setDescription(e.target.value)} placeholder="Optionale Notizen zum Einsatz" disabled={isCreating} />
setEinsatzleiter(e.target.value)} placeholder="Name" disabled={isCreating} />
setJournalfuehrer(e.target.value)} placeholder="Name" disabled={isCreating} />
) }