From 8e984e609bd6df41d27051606b23108bfafa4545 Mon Sep 17 00:00:00 2001 From: 3dtours Date: Thu, 25 Jun 2026 11:34:09 +0700 Subject: [PATCH] =?UTF-8?q?feat:=20Th=C3=AAm=20n=C3=BAt=20=C4=91=E1=BB=8Bn?= =?UTF-8?q?h=20v=E1=BB=8B=20ng=C6=B0=E1=BB=9Di=20d=C3=B9ng=20tr=C3=AAn=20b?= =?UTF-8?q?=E1=BA=A3n=20=C4=91=E1=BB=93=20=C4=91i=E1=BB=81u=20h=C6=B0?= =?UTF-8?q?=E1=BB=9Bng=20mobile?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Thêm nút 'Vị trí của tôi' ở góc dưới bên trái bản đồ - Khi bật: map tự động center theo vị trí GPS của người dùng (dùng watchPosition) - Cho phép thu phóng tự do ngay cả khi đang định vị - Khi người dùng kéo thả bản đồ thủ công: tự động tắt chế độ định vị - Nút hoạt động độc lập với nút la bàn (compass) - Sửa import leaflet.css bị lỗi path --- frontend/src/pages/TourNavigationPage.tsx | 91 +++++++++++++++++++++-- 1 file changed, 85 insertions(+), 6 deletions(-) diff --git a/frontend/src/pages/TourNavigationPage.tsx b/frontend/src/pages/TourNavigationPage.tsx index 2333296..696b53a 100644 --- a/frontend/src/pages/TourNavigationPage.tsx +++ b/frontend/src/pages/TourNavigationPage.tsx @@ -114,8 +114,6 @@ const MapSizeHandler = ({ mapRef }: { mapRef: React.MutableRefObject 2 || Math.abs(newHeight - prev.height) > 2)) || newWidth === 0 || newHeight === 0) { prevSizeRef.current = { width: newWidth, height: newHeight }; - const prevZoom = map.getZoom(); - const prevCenter = map.getCenter(); const userZoom = (map as any)._userZoomLevel as number | undefined; map.invalidateSize({ animate: false }); @@ -135,6 +133,7 @@ const MapSizeHandler = ({ mapRef }: { mapRef: React.MutableRefObject = ({ tourId, routeData, onBack }) => { const [isCompassActive, setIsCompassActive] = useState(false); + const [isLocatingUser, setIsLocatingUser] = useState(false); const [currentHeading, setCurrentHeading] = useState(0); const [routeGeometry, setRouteGeometry] = useState<[number, number][] | null>(null); const [error, setError] = useState(null); @@ -149,6 +148,49 @@ export const TourNavigationPage: React.FC = ({ tourId, const destLat = routeData?.destination?.lat; const destLng = routeData?.destination?.lng; + const stopLocating = useCallback(() => { + setIsLocatingUser(false); + }, []); + + useEffect(() => { + const map = mapRef.current; + if (!map) return; + + const onMoveStart = () => { + if (isLocatingUser) { + stopLocating(); + } + }; + map.on('movestart', onMoveStart); + return () => { + map.off('movestart', onMoveStart); + }; + }, [isLocatingUser, stopLocating]); + + useEffect(() => { + if (!isLocatingUser || !mapRef.current) return; + + if (navigator.geolocation) { + watchIdRef.current = navigator.geolocation.watchPosition( + (position) => { + if (mapRef.current) { + const currentZoom = mapRef.current.getZoom(); + mapRef.current.setView([position.coords.latitude, position.coords.longitude], currentZoom, { animate: true }); + } + }, + (err) => console.error("User location tracking error:", err), + { enableHighAccuracy: true } + ); + } + + return () => { + if (watchIdRef.current !== null) { + navigator.geolocation.clearWatch(watchIdRef.current); + watchIdRef.current = null; + } + }; + }, [isLocatingUser]); + useEffect(() => { if (!routeData || !originLat || !originLng || !destLat || !destLng) { setRouteGeometry(null); @@ -245,9 +287,9 @@ export const TourNavigationPage: React.FC = ({ tourId, const destIcon = useMemo(() => L.divIcon({ className: '!bg-transparent !border-none', - html: `
Đ
`, - iconSize: [32, 32], - iconAnchor: [16, 16] + html: `
Đ
`, + iconSize: [40, 40], + iconAnchor: [20, 20] }), []); if (!routeData) return null; @@ -256,6 +298,22 @@ export const TourNavigationPage: React.FC = ({ tourId, ? [(routeData.origin.lat + routeData.destination.lat) / 2, (routeData.origin.lng + routeData.destination.lng) / 2] : [0, 0]; + const centerOnUser = () => { + if (navigator.geolocation) { + navigator.geolocation.getCurrentPosition( + (position) => { + if (mapRef.current) { + const currentZoom = mapRef.current.getZoom(); + mapRef.current.setView([position.coords.latitude, position.coords.longitude], currentZoom, { animate: true }); + canFitRef.current = false; + } + }, + (err) => console.error("Cannot get user location:", err), + { enableHighAccuracy: true } + ); + } + }; + return (
@@ -289,7 +347,7 @@ export const TourNavigationPage: React.FC = ({ tourId, - + {/* */} {routeGeometry && ( )} @@ -298,6 +356,27 @@ export const TourNavigationPage: React.FC = ({ tourId, + {/* Locate User Button */} + +