diff --git a/frontend/src/components/AddLocationModal.tsx b/frontend/src/components/AddLocationModal.tsx index 13fe966..7e47a70 100644 --- a/frontend/src/components/AddLocationModal.tsx +++ b/frontend/src/components/AddLocationModal.tsx @@ -1,6 +1,6 @@ import React, { useState, useEffect, useRef } from 'react'; import { format, parseISO } from 'date-fns'; -import { X, MapPin, Loader2, Clock, Map as MapIcon } from 'lucide-react'; +import { X, MapPin, Loader2, Clock, Map as MapIcon, Navigation } from 'lucide-react'; import { useTourStore } from '@/store/useTourStore.js'; import { MapContainer, TileLayer, Marker, useMapEvents, useMap } from 'react-leaflet'; import L from 'leaflet'; @@ -80,7 +80,7 @@ export const AddLocationModal = ({ isOpen, onClose, tourId, initialLegId, editin 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(); + const { legs, addLocation, updateLocation, mapCenter, currentTour, userRole } = 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(() => { @@ -147,6 +147,58 @@ export const AddLocationModal = ({ isOpen, onClose, tourId, initialLegId, editin } catch (e) {} }; + const handleUseCurrentLocation = () => { + if (!navigator.geolocation) { + alert("Trình duyệt hoặc thiết bị của bạn không hỗ trợ định vị GPS."); + return; + } + + // Kiểm tra môi trường Secure Context (HTTPS) - Bắt buộc cho Geolocation trên Mobile + if (!window.isSecureContext) { + alert("Tính năng định vị GPS yêu cầu kết nối bảo mật (HTTPS). Nếu bạn đang truy cập qua địa chỉ IP, vui lòng sử dụng HTTPS hoặc Localhost."); + return; + } + + navigator.geolocation.getCurrentPosition( + async (pos) => { + const latlng = L.latLng(pos.coords.latitude, pos.coords.longitude); + const now = new Date(); + const formattedTime = format(now, "yyyy-MM-dd'T'HH:mm"); + + setFormData(prev => ({ + ...prev, + latitude: latlng.lat, + longitude: latlng.lng, + plannedStart: formattedTime + })); + + // Tự động thực hiện reverse geocoding để lấy tên địa điểm và địa chỉ + handlePickLocation(latlng); + }, + (err) => { + let errorMessage = "Không thể lấy vị trí: "; + switch(err.code) { + case err.PERMISSION_DENIED: + errorMessage += "Bạn đã từ chối quyền truy cập vị trí."; + break; + case err.POSITION_UNAVAILABLE: + errorMessage += "Thông tin vị trí không khả dụng."; + break; + case err.TIMEOUT: + errorMessage += "Hết thời gian chờ yêu cầu định vị."; + break; + default: errorMessage += err.message; + } + alert(errorMessage); + }, + { + enableHighAccuracy: true, // Ưu tiên dùng GPS thay vì Wifi/Cell tower + timeout: 10000, // Chờ tối đa 10 giây + maximumAge: 0 // Không dùng vị trí cũ trong cache + } + ); + }; + // 3. Early return phải nằm SAU tất cả các khai báo Hook if (!isOpen || isPublicView) return null; // Do not render if public view @@ -200,6 +252,17 @@ export const AddLocationModal = ({ isOpen, onClose, tourId, initialLegId, editin + {/* Nút lấy vị trí và thời gian hiện tại - Chỉ dành cho OWNER/MANAGER khi thêm mới */} + {!editingLocation && (userRole === 'OWNER' || userRole === 'MANAGER') && ( + + )} +