fix: tính năng tìm đường ngắn nhất và chỉ đường cho người dùng

This commit is contained in:
2026-06-24 22:05:41 +07:00
parent cfdcb573de
commit a135196cb5
12 changed files with 210 additions and 204 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -21,8 +21,8 @@
<meta name="twitter:title" content="Travel Planner - Lên kế hoạch chuyến đi của bạn" />
<meta name="twitter:description" content="Khám phá những hành trình tuyệt vời và chia sẻ khoảnh khắc đẹp với cộng đồng du lịch." />
<meta name="twitter:image" content="https://yotrip.labz.io.vn/background.avif" />
<script type="module" crossorigin src="/assets/index-ULMuD9f4.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-BU0L_D9H.css">
<script type="module" crossorigin src="/assets/index-D6jMbgMg.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-nLng8wU9.css">
</head>
<body>
<div id="root"></div>
@@ -1,4 +1,4 @@
import React, { useState, useEffect, useMemo } from 'react';
import React, { useState, useEffect, useRef, useMemo } from 'react';
import { MapContainer, TileLayer, Marker, Polyline, Popup, useMap } from 'react-leaflet';
import L from 'leaflet';
import 'leaflet/dist/leaflet.css';
@@ -34,26 +34,33 @@ const FitBounds = ({ coords }: { coords: [number, number][] }) => {
export const LocationNavigationModal: React.FC<NavModalProps> = ({ isOpen, onClose, routeData }) => {
const [routeGeometry, setRouteGeometry] = useState<[number, number][] | null>(null);
const [loading, setLoading] = useState(false);
const [isSearchingRoute, setIsSearchingRoute] = useState(false);
const [error, setError] = useState<string | null>(null);
const [routeInfo, setRouteInfo] = useState<{ distance: string; duration: string } | null>(null);
const fetchLockRef = useRef(false);
const originLat = routeData?.origin?.lat;
const originLng = routeData?.origin?.lng;
const destLat = routeData?.destination?.lat;
const destLng = routeData?.destination?.lng;
useEffect(() => {
if (!isOpen || !routeData.origin || !routeData.destination) {
if (!isOpen || !originLat || !originLng || !destLat || !destLng) {
setRouteGeometry(null);
setError(null);
setRouteInfo(null);
return;
}
const origin = routeData.origin;
const destination = routeData.destination;
if (fetchLockRef.current) return;
const fetchRoute = async () => {
setLoading(true);
setError(null);
const calculateOptimalRoute = async () => {
try {
const url = `https://router.project-osrm.org/route/v1/driving/${origin.lng},${origin.lat};${destination.lng},${destination.lat}?overview=full&geometries=geojson`;
setIsSearchingRoute(true);
fetchLockRef.current = true;
const url = `https://router.project-osrm.org/route/v1/driving/${originLng},${originLat};${destLng},${destLat}?overview=full&geometries=geojson`;
const res = await fetch(url);
if (!res.ok) throw new Error(`OSRM error: ${res.status}`);
const data: { code: string; routes: OSRMRoute[] } = await res.json();
@@ -73,11 +80,18 @@ export const LocationNavigationModal: React.FC<NavModalProps> = ({ isOpen, onClo
} catch (err: any) {
setError(err.message);
} finally {
setLoading(false);
setIsSearchingRoute(false);
fetchLockRef.current = false;
}
};
fetchRoute();
}, [isOpen, routeData.origin, routeData.destination]);
calculateOptimalRoute();
return () => {
fetchLockRef.current = false;
setIsSearchingRoute(false);
};
}, [isOpen, originLat, originLng, destLat, destLng]);
const userIcon = useMemo(() => L.divIcon({
className: '!bg-transparent !border-none',
@@ -126,7 +140,7 @@ export const LocationNavigationModal: React.FC<NavModalProps> = ({ isOpen, onClo
</div>
{/* Map Container */}
<div className="flex-1 relative bg-slate-950">
<div className="relative flex-1 bg-slate-950">
<MapContainer
center={center}
zoom={14}
@@ -155,11 +169,8 @@ export const LocationNavigationModal: React.FC<NavModalProps> = ({ isOpen, onClo
)}
</MapContainer>
{loading && (
<div className="absolute top-4 left-1/2 -translate-x-1/2 bg-slate-800 text-white text-xs px-3 py-1.5 rounded-lg shadow-lg z-[1000]">
Đang tải lộ trình...
</div>
)}
{error && (
<div className="absolute top-4 left-1/2 -translate-x-1/2 bg-red-900 text-white text-xs px-3 py-1.5 rounded-lg shadow-lg z-[1000]">
{error}
@@ -170,4 +181,3 @@ export const LocationNavigationModal: React.FC<NavModalProps> = ({ isOpen, onClo
</div>
);
};
+1 -7
View File
@@ -2585,13 +2585,7 @@ export const TourDetailPage = ({
</div>
)}
{/* Chỉ báo đang tìm đường */}
{isRoutingLoading && (
<div className="bg-white/90 backdrop-blur-md px-3 py-2 rounded-xl shadow-lg border border-white flex items-center gap-2 animate-pulse animate-in slide-in-from-left-2">
<Loader2 className="w-3.5 h-3.5 animate-spin text-blue-600" />
<span className="text-[10px] font-black text-gray-500 uppercase tracking-tighter">Đang tìm đưng tối ưu...</span>
</div>
)}
</div>
</div>