import React, { useState, useEffect } from 'react'; import { X, MapPin, Search, Loader2 } from 'lucide-react'; import { MapContainer, TileLayer, Marker, useMap, useMapEvents } from 'react-leaflet'; import L from 'leaflet'; import 'leaflet/dist/leaflet.css'; import { useTranslation } from '../hooks/useTranslation'; // Fix Leaflet default marker icon bug const DefaultIcon = L.icon({ iconUrl: 'https://unpkg.com/leaflet@1.9.4/dist/images/marker-icon.png', shadowUrl: 'https://unpkg.com/leaflet@1.9.4/dist/images/marker-shadow.png', iconSize: [25, 41], iconAnchor: [12, 41], }); interface CoordinateSelectModalProps { isOpen: boolean; onClose: () => void; initialLat?: number; initialLng?: number; onSelect: (lat: number, lng: number) => void; } function MapClickEvents({ onClick }: { onClick: (lat: number, lng: number) => void }) { useMapEvents({ click: (e) => { onClick(e.latlng.lat, e.latlng.lng); } }); return null; } function MapInvalidator() { const map = useMap(); useEffect(() => { const timer = setTimeout(() => { map.invalidateSize(); }, 250); return () => clearTimeout(timer); }, [map]); return null; } function RecenterMap({ position }: { position: [number, number] }) { const map = useMap(); useEffect(() => { map.setView(position, map.getZoom()); }, [position, map]); return null; } export const CoordinateSelectModal: React.FC = ({ isOpen, onClose, initialLat, initialLng, onSelect }) => { const { t } = useTranslation(); const defaultCenter: [number, number] = [10.7769, 106.7009]; // TP.HCM default const [position, setPosition] = useState<[number, number]>(defaultCenter); const [hasSelected, setHasSelected] = useState(false); // Search States const [searchQuery, setSearchQuery] = useState(''); const [searchResults, setSearchResults] = useState([]); const [isSearching, setIsSearching] = useState(false); useEffect(() => { if (isOpen) { if (typeof initialLat === 'number' && typeof initialLng === 'number' && !isNaN(initialLat) && !isNaN(initialLng)) { setPosition([initialLat, initialLng]); setHasSelected(true); } else { setPosition(defaultCenter); setHasSelected(false); } setSearchQuery(''); setSearchResults([]); } }, [isOpen, initialLat, initialLng]); if (!isOpen) return null; const handleMapClick = (lat: number, lng: number) => { setPosition([lat, lng]); setHasSelected(true); }; const handleConfirm = () => { onSelect(position[0], position[1]); onClose(); }; const handleSearch = async () => { if (!searchQuery.trim()) return; setIsSearching(true); try { const res = await fetch(`https://nominatim.openstreetmap.org/search?format=json&q=${encodeURIComponent(searchQuery)}&accept-language=vi&limit=5`); if (res.ok) { const data = await res.json(); setSearchResults(data); } } catch (e) { console.error('Error during Nominatim search:', e); } finally { setIsSearching(false); } }; const handleSelectResult = (place: any) => { const lat = parseFloat(place.lat); const lng = parseFloat(place.lon); setPosition([lat, lng]); setHasSelected(true); setSearchResults([]); setSearchQuery(place.display_name); }; return (
{/* Backdrop */}
{/* Content */}
{/* Header */}

{t('chooseLocationMap')}

{t('clickMapSelectCoords')}

{/* Map Body */}
{/* Floating Search Panel */}
setSearchQuery(e.target.value)} onKeyDown={(e) => e.key === 'Enter' && handleSearch()} placeholder={t('searchPlaceholder')} className="w-full bg-slate-50 dark:bg-slate-800 border-0 outline-none rounded-xl pl-8 pr-3 py-2 text-xs text-slate-800 dark:text-slate-100" />
{searchResults.length > 0 && (
{searchResults.map((r, i) => ( ))}
)}
{hasSelected && ( )}
{/* Footer */}
{hasSelected ? ( {t('coordsLabel')}: {position[0].toFixed(6)}, {position[1].toFixed(6)} ) : ( {t('noCoordsSelected')} )}
); };