diff --git a/src/components/ContactModal.jsx b/src/components/ContactModal.jsx index dc6e07e..d6e73ed 100644 --- a/src/components/ContactModal.jsx +++ b/src/components/ContactModal.jsx @@ -1,22 +1,31 @@ -import { useState, useEffect, useCallback } from 'react' -import { X, Trash2, MapPin } from 'lucide-react' +import { useState, useEffect, useCallback, useRef } from 'react' +import { X, Trash2, MapPin, Crosshair } from 'lucide-react' import toast from 'react-hot-toast' import { useStore } from '../store' -import { createContact, updateContact, deleteContact, fetchContacts, fetchReverse } from '../api' +import { createContact, updateContact, deleteContact, fetchContacts, fetchReverse, searchGeocode } from '../api' const CATEGORIES = ['family', 'friend', 'business', 'emergency', 'ham', 'bug-out', 'favorite'] export default function ContactModal() { const editingContact = useStore((s) => s.editingContact) const clearEditingContact = useStore((s) => s.clearEditingContact) + const setPickingLocationFor = useStore((s) => s.setPickingLocationFor) const setContacts = useStore((s) => s.setContacts) const [form, setForm] = useState({}) const [saving, setSaving] = useState(false) + // Geocode search state + const [searchResults, setSearchResults] = useState([]) + const [searchLoading, setSearchLoading] = useState(false) + const [showDropdown, setShowDropdown] = useState(false) + const debounceRef = useRef(null) + const inputRef = useRef(null) + useEffect(() => { if (editingContact) { setForm({ + id: editingContact.id, label: editingContact.label || '', name: editingContact.name || '', call_sign: editingContact.call_sign || '', @@ -31,6 +40,8 @@ export default function ContactModal() { osm_id: editingContact.osm_id || null, address: editingContact.address || '', }) + setSearchResults([]) + setShowDropdown(false) } }, [editingContact]) @@ -53,14 +64,29 @@ export default function ContactModal() { } }, [editingContact, form.lat, form.lon]) + // Cleanup debounce on unmount + useEffect(() => { + return () => { + if (debounceRef.current) clearTimeout(debounceRef.current) + } + }, []) + const close = useCallback(() => clearEditingContact(), [clearEditingContact]) useEffect(() => { if (!editingContact) return - const onKey = (e) => { if (e.key === 'Escape') close() } + const onKey = (e) => { + if (e.key === 'Escape') { + if (showDropdown) { + setShowDropdown(false) + } else { + close() + } + } + } document.addEventListener('keydown', onKey) return () => document.removeEventListener('keydown', onKey) - }, [editingContact, close]) + }, [editingContact, close, showDropdown]) if (!editingContact) return null @@ -69,6 +95,57 @@ export default function ContactModal() { const setField = (key, val) => setForm((f) => ({ ...f, [key]: val })) + // Handle address input change with debounced geocode search + const handleAddressChange = (e) => { + const query = e.target.value + setField('address', query) + + // Clear previous debounce + if (debounceRef.current) clearTimeout(debounceRef.current) + + if (!query || query.length < 3) { + setSearchResults([]) + setShowDropdown(false) + return + } + + debounceRef.current = setTimeout(async () => { + setSearchLoading(true) + try { + const results = await searchGeocode(query, 5) + setSearchResults(results || []) + setShowDropdown(true) + } catch (err) { + console.error('Geocode search error:', err) + setSearchResults([]) + } finally { + setSearchLoading(false) + } + }, 300) + } + + // Handle selecting a geocode result + const handleSelectResult = (result) => { + setForm((f) => ({ + ...f, + address: result.display_name || result.name || '', + lat: result.lat, + lon: result.lon, + osm_type: result.osm_type || null, + osm_id: result.osm_id || null, + })) + setShowDropdown(false) + setSearchResults([]) + } + + // Handle "Set on map" button + const handleSetOnMap = () => { + // Save current form state to store for map pick mode + setPickingLocationFor({ ...form }) + clearEditingContact() + toast('Click the map to set location', { icon: '📍', duration: 3000 }) + } + const refreshContacts = async () => { const data = await fetchContacts() if (!data?.auth && Array.isArray(data)) setContacts(data) @@ -83,6 +160,7 @@ export default function ContactModal() { setSaving(true) try { const payload = { ...form, label: form.label.trim() } + delete payload.id // Don't send id in payload if (payload.show_proximity === false) payload.show_proximity = false const result = isEdit ? await updateContact(editingContact.id, payload) @@ -122,6 +200,15 @@ export default function ContactModal() { } } + // Format result for display + const formatResult = (r) => { + const parts = [] + if (r.name) parts.push(r.name) + if (r.address?.city) parts.push(r.address.city) + if (r.address?.state) parts.push(r.address.state) + return parts.length > 0 ? parts.join(', ') : r.display_name || 'Unknown location' + } + return (