From 36418673db827dc573ceb2d57f357e8ae6be409e Mon Sep 17 00:00:00 2001 From: 3dtours Date: Wed, 17 Jun 2026 17:44:51 +0700 Subject: [PATCH] =?UTF-8?q?feat:=20th=C3=AAm=20t=C3=ADnh=20n=C4=83ng=20v?= =?UTF-8?q?=E1=BA=BD=20=C4=91=C6=B0=E1=BB=9Dng=20di=20chuy=E1=BB=83n=20tr?= =?UTF-8?q?=C3=AAn=20b=E1=BA=A3n=20=C4=91=E1=BB=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/pages/TourDetailPage.tsx | 52 ++++++++++++++++++++++++--- 1 file changed, 47 insertions(+), 5 deletions(-) diff --git a/frontend/src/pages/TourDetailPage.tsx b/frontend/src/pages/TourDetailPage.tsx index 1856980..8aa88ea 100644 --- a/frontend/src/pages/TourDetailPage.tsx +++ b/frontend/src/pages/TourDetailPage.tsx @@ -270,6 +270,8 @@ export const TourDetailPage = ({ const [searchQuery, setSearchQuery] = useState(''); const [searchResults, setSearchResults] = useState([]); const [isSearching, setIsSearching] = useState(false); + const [drivingRoute, setDrivingRoute] = useState<[number, number][]>([]); + const [segmentDistances, setSegmentDistances] = useState([]); const handleSearchLocation = async (query: string) => { setSearchQuery(query); @@ -421,6 +423,37 @@ export const TourDetailPage = ({ // Memoize danh sách tọa độ để MapTourBounds không bị trigger thừa const allLocations = useMemo(() => legs.flatMap(l => l.locations), [legs]); + // Tự động tìm quãng đường di chuyển bằng ô tô thực tế và vẽ lên bản đồ + useEffect(() => { + const fetchDrivingRoute = async () => { + if (allLocations.length < 2) { + setDrivingRoute([]); + setSegmentDistances([]); + return; + } + + const coordsString = allLocations + .map(loc => `${loc.longitude},${loc.latitude}`) + .join(';'); + + try { + const response = await fetch(`https://router.project-osrm.org/route/v1/driving/${coordsString}?overview=full&geometries=geojson`); + const data = await response.json(); + if (data.code === 'Ok' && data.routes.length > 0) { + // OSRM trả về [lng, lat], cần đổi sang [lat, lng] cho Leaflet + const mappedCoords: [number, number][] = data.routes[0].geometry.coordinates.map((c: any) => [c[1], c[0]]); + setDrivingRoute(mappedCoords); + // Lưu quãng đường từng chặng (đơn vị km) + setSegmentDistances(data.routes[0].legs.map((leg: any) => leg.distance / 1000)); + } + } catch (error) { + console.error("Lỗi lấy lộ trình di chuyển ô tô:", error); + } + }; + + fetchDrivingRoute(); + }, [allLocations]); + useEffect(() => { if (initialViewState) { setMapCenter(initialViewState.center); @@ -1100,8 +1133,16 @@ export const TourDetailPage = ({ {/* Tự động đóng khung Start và End khi dữ liệu thay đổi */} - {/* Vẽ đường Polyline nối các điểm - Liên tục toàn bộ lộ trình xuyên suốt các chặng */} - {allLocations.length > 1 && ( + {/* Vẽ đường đi ô tô thực tế (Driving route) nếu lấy được dữ liệu, nếu không dùng đường thẳng nét đứt */} + {drivingRoute.length > 0 ? ( + + ) : allLocations.length > 1 && ( [l.latitude, l.longitude]) as any} color="#3b82f6" @@ -1124,6 +1165,7 @@ export const TourDetailPage = ({ const distanceToPrev = prevLoc ? calculateDistance(prevLoc.latitude, prevLoc.longitude, loc.latitude, loc.longitude).toFixed(1) : null; + const drivingDist = index > 0 ? segmentDistances[index - 1] : null; return ( @@ -1153,14 +1195,14 @@ export const TourDetailPage = ({ - {distanceToPrev && distanceToPrev !== "0.0" && ( + {(drivingDist || (distanceToPrev && distanceToPrev !== "0.0")) && (
- Khoảng cách từ chặng trước - {distanceToPrev} km + {drivingDist ? 'Đường bộ từ điểm trước' : 'Khoảng cách chim bay'} + {drivingDist ? drivingDist.toFixed(1) : distanceToPrev} km
)}