import { useEffect, useState, useRef, useCallback } from 'react' import { X, Navigation, Plus, Bookmark, ChevronDown, Copy } from 'lucide-react' import toast from 'react-hot-toast' import { useStore } from '../store' import { fetchElevation } from '../api' /** Meters to feet */ const M_TO_FT = 3.28084 /** Build display address from raw result data */ function buildAddress(place) { if (place.address) return place.address const raw = place.raw || {} const parts = [raw.street, raw.city, raw.state, raw.postcode].filter(Boolean) return parts.join(', ') || null } /** Copy popover — small dropdown beneath the Copy button */ function CopyPopover({ address, selectedPlace, onClose }) { const ref = useRef(null) // Close on click-outside useEffect(() => { function handleClick(e) { if (ref.current && !ref.current.contains(e.target)) onClose() } function handleKey(e) { if (e.key === 'Escape') onClose() } document.addEventListener('mousedown', handleClick) document.addEventListener('keydown', handleKey) return () => { document.removeEventListener('mousedown', handleClick) document.removeEventListener('keydown', handleKey) } }, [onClose]) const copyAddress = () => { const text = [selectedPlace.name, address].filter(Boolean).join('\n') navigator.clipboard.writeText(text).then( () => toast('Address copied'), () => toast.error('Failed to copy') ) onClose() } const copyCoords = () => { const text = `${selectedPlace.lat.toFixed(6)}, ${selectedPlace.lon.toFixed(6)}` navigator.clipboard.writeText(text).then( () => toast('Coordinates copied'), () => toast.error('Failed to copy') ) onClose() } return (
) } export default function PlaceDetail() { const selectedPlace = useStore((s) => s.selectedPlace) const clearSelectedPlace = useStore((s) => s.clearSelectedPlace) const startDirections = useStore((s) => s.startDirections) const addStop = useStore((s) => s.addStop) const stops = useStore((s) => s.stops) const geoPermission = useStore((s) => s.geoPermission) const [elevResult, setElevResult] = useState({ lat: null, lon: null, value: null }) const [isMobile, setIsMobile] = useState(false) const [copyForPlace, setCopyForPlace] = useState(null) const closeCopy = useCallback(() => setCopyForPlace(null), []) useEffect(() => { const check = () => setIsMobile(window.innerWidth < 768) check() window.addEventListener('resize', check) return () => window.removeEventListener('resize', check) }, []) // Escape key closes panel useEffect(() => { if (!selectedPlace) return function handleKey(e) { if (e.key === 'Escape') clearSelectedPlace() } document.addEventListener('keydown', handleKey) return () => document.removeEventListener('keydown', handleKey) }, [selectedPlace, clearSelectedPlace]) // Fetch elevation when place changes const placeLat = selectedPlace?.lat const placeLon = selectedPlace?.lon useEffect(() => { if (placeLat == null || placeLon == null) return let cancelled = false fetchElevation(placeLat, placeLon).then((h) => { if (!cancelled) setElevResult({ lat: placeLat, lon: placeLon, value: h }) }) return () => { cancelled = true } }, [placeLat, placeLon]) // Derive elevation/loading from comparing result to current place const elevLoading = placeLat != null && (elevResult.lat !== placeLat || elevResult.lon !== placeLon) const elevation = !elevLoading ? elevResult.value : null const placeKey = selectedPlace ? `${selectedPlace.lat},${selectedPlace.lon}` : null if (!selectedPlace) return null const address = buildAddress(selectedPlace) const elevFeet = elevation != null ? Math.round(elevation * M_TO_FT) : null const raw = selectedPlace.raw || {} // Check if place is already in stops const existingStopIndex = stops.findIndex( (s) => Math.abs(s.lat - selectedPlace.lat) < 0.00001 && Math.abs(s.lon - selectedPlace.lon) < 0.00001 ) const handleDirections = () => { startDirections(selectedPlace) if (geoPermission !== 'granted' && stops.length === 0) { toast('Set a starting point to get directions', { icon: '\u{1F4CD}' }) } } const handleAddStop = () => { addStop({ lat: selectedPlace.lat, lon: selectedPlace.lon, name: selectedPlace.name, source: selectedPlace.source, matchCode: selectedPlace.matchCode, }) clearSelectedPlace() } const handleSave = () => { toast('Saved places coming soon') } const panelContent = ( <> {/* Close button */} {/* Place name */}

{selectedPlace.name}

{selectedPlace.type && ( {selectedPlace.type} )}
{/* Address */} {address && (

{address}

)} {/* Coordinates + elevation */}
{selectedPlace.lat.toFixed(6)}, {selectedPlace.lon.toFixed(6)} · {elevLoading ? '...' : elevFeet != null ? `${elevFeet.toLocaleString()} ft` : '\u2014'}
{/* Optional extras */} {(raw.opening_hours || raw.website || raw.phone) && (
{raw.opening_hours && {raw.opening_hours}} {raw.website && ( {raw.website.replace(/^https?:\/\//, '').replace(/\/$/, '')} )} {raw.phone && {raw.phone}}
)} {/* Action buttons */}
{existingStopIndex >= 0 ? ( Added as stop {String.fromCharCode(65 + existingStopIndex)} ) : ( )} {/* Copy dropdown */}
{copyForPlace === placeKey && ( )}
) // Mobile: bottom overlay if (isMobile) { return (
{panelContent}
) } // Desktop: side panel return (
{panelContent}
) }