import React, { useState, useEffect, useRef } from 'react'; import { X, MapPin, Loader2, Clock, Map as MapIcon } from 'lucide-react'; import { useTourStore } from './useTourStore.js'; import { MapContainer, TileLayer, Marker, useMapEvents, useMap } from 'react-leaflet'; import L from 'leaflet'; // Cấu hình Icon mặc định để tránh crash Marker trong Modal delete (L.Icon.Default.prototype as any)._getIconUrl; L.Icon.Default.mergeOptions({ iconRetinaUrl: 'https://unpkg.com/leaflet@1.9.4/dist/images/marker-icon-2x.png', 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', }); // Component Helper xử lý việc chọn vị trí trên mini map bằng chuột phải const MapPicker = ({ onPick, center }: { onPick: (latlng: L.LatLng) => void, center: [number, number] }) => { const [menuPos, setMenuPos] = useState<{ x: number, y: number, latlng: L.LatLng } | null>(null); const menuRef = useRef(null); const map = useMap(); useMapEvents({ contextmenu: (e) => { L.DomEvent.preventDefault(e.originalEvent); L.DomEvent.stopPropagation(e.originalEvent); setMenuPos({ x: e.containerPoint.x, y: e.containerPoint.y, latlng: e.latlng }); }, click: () => setMenuPos(null), dragstart: () => setMenuPos(null), }); useEffect(() => { if (menuPos && menuRef.current) { L.DomEvent.disableClickPropagation(menuRef.current); } }, [menuPos]); useEffect(() => { map.setView(center, map.getZoom()); }, [center]); return ( <> {menuPos && (
)} ); }; export const AddLocationModal = ({ isOpen, onClose, tourId, initialLegId, editingLocation }: { isOpen: boolean, onClose: () => void, tourId: string, initialLegId?: string, editingLocation?: any }) => { const [formData, setFormData] = useState({ name: '', address: '', latitude: 10.7769, longitude: 106.7009, type: 'VISIT', legId: '', note: '', expenseAmount: '', expenseCategory: 'OTHER', expenseDescription: '', expenseNote: '', paidById: '', plannedStart: '', plannedEnd: '' }); const [isLoading, setIsLoading] = useState(false); // Gom các selector lại để giảm số lượng Hook gọi nội bộ và tăng hiệu năng const { legs, addLocation, updateLocation, mapCenter, currentTour } = useTourStore(); // 1. Luôn khai báo Hook ở cấp cao nhất, không đặt code logic/return giữa các Hook useEffect(() => { if (isOpen) { if (editingLocation) { const leg = legs.find(l => l.id === editingLocation.legId); const expense = leg?.expenses?.find((e: any) => e.locationId === editingLocation.id); setFormData({ name: editingLocation.name || '', address: editingLocation.address || '', latitude: editingLocation.latitude, longitude: editingLocation.longitude, type: editingLocation.type || 'VISIT', legId: editingLocation.legId || '', note: editingLocation.note || '', expenseAmount: expense?.amount?.toString() || '', expenseCategory: expense?.category || 'OTHER', expenseDescription: expense?.description || '', expenseNote: expense?.note || '', paidById: expense?.paidById || '', plannedStart: editingLocation.plannedStart ? editingLocation.plannedStart.slice(0, 16) : '', plannedEnd: editingLocation.plannedEnd ? editingLocation.plannedEnd.slice(0, 16) : '' }); } else { setFormData(prev => ({ ...prev, name: '', address: '', legId: initialLegId || (legs.length > 0 ? legs[0].id : ''), note: '', expenseAmount: '', expenseCategory: 'OTHER', expenseDescription: '', expenseNote: '', paidById: '', plannedStart: '', plannedEnd: '' })); } } }, [initialLegId, editingLocation, isOpen]); useEffect(() => { if (isOpen && !formData.name) { setFormData(prev => ({ ...prev, latitude: mapCenter[0], longitude: mapCenter[1] })); } }, [isOpen, mapCenter]); // 2. Thực hiện các tính toán và hàm xử lý const currentLegId = formData.legId || (legs.length > 0 ? legs[0].id : ''); const targetLeg = legs.find(l => l.id === currentLegId); const titleText = editingLocation ? `Sửa địa điểm: ${editingLocation.name}` : (initialLegId ? `Thêm địa điểm cho ${targetLeg?.note || `Chặng ${targetLeg?.sequence}`}` : 'Thêm địa điểm mới'); const buttonText = editingLocation ? 'Cập nhật thay đổi' : (initialLegId ? 'Xác nhận thêm vào chặng' : 'Thêm địa điểm'); const handlePickLocation = async (latlng: L.LatLng) => { setFormData(prev => ({ ...prev, latitude: latlng.lat, longitude: latlng.lng })); // Tự động lấy tên địa điểm từ tọa độ vừa chọn try { const res = await fetch(`https://nominatim.openstreetmap.org/reverse?format=json&lat=${latlng.lat}&lon=${latlng.lng}`); const data = await res.json(); const addr = data.address; const detectedName = addr.amenity || addr.building || addr.historic || addr.tourist || addr.shop || addr.office || addr.leisure || addr.attraction || addr.road || addr.neighbourhood || addr.suburb || data.display_name?.split(',')[0] || ""; setFormData(prev => ({ ...prev, name: detectedName || prev.name, address: data.display_name || prev.address })); } catch (e) {} }; // 3. Early return phải nằm SAU tất cả các khai báo Hook if (!isOpen) return null; const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); setIsLoading(true); try { const payload: any = { ...formData, legId: currentLegId, latitude: parseFloat(formData.latitude as any), longitude: parseFloat(formData.longitude as any), }; if (editingLocation) { await updateLocation(editingLocation.id, payload); } else { await addLocation(tourId, payload); } onClose(); } catch (error) { alert('Lỗi khi lưu địa điểm'); } finally { setIsLoading(false); } }; return (

{titleText}

{/* Mini Map Picker */}
CHUỘT PHẢI ĐỂ CHỌN VỊ TRÍ
setFormData({...formData, name: e.target.value})} />
setFormData({...formData, address: e.target.value})} />