fix: sửa lại các nút hiển thị trên màn hình gom lại thành 1 nút

This commit is contained in:
2026-06-17 20:08:28 +07:00
parent b6551da147
commit f181009fa5
2 changed files with 59 additions and 51 deletions
+6 -6
View File
@@ -223,7 +223,7 @@ export const ExploreMap = ({ onBack, onLogout, user, onViewTour, onOpenMyPhotos
<div className="flex items-center gap-3 pointer-events-auto"> <div className="flex items-center gap-3 pointer-events-auto">
<button <button
onClick={onBack} onClick={onBack}
className="bg-white p-3 rounded-full shadow-xl hover:bg-gray-50 transition-all border border-gray-100" className="w-11 h-11 flex items-center justify-center bg-white rounded-full shadow-xl hover:bg-gray-50 transition-all border border-gray-100 shrink-0"
title="Quay lại" title="Quay lại"
> >
<X className="w-6 h-6 text-gray-800" /> <X className="w-6 h-6 text-gray-800" />
@@ -233,7 +233,7 @@ export const ExploreMap = ({ onBack, onLogout, user, onViewTour, onOpenMyPhotos
<div className="relative"> <div className="relative">
<button <button
onClick={() => setIsFilterDropdownOpen(prev => !prev)} onClick={() => setIsFilterDropdownOpen(prev => !prev)}
className="bg-white p-3 rounded-full shadow-xl hover:bg-gray-50 transition-all border border-gray-100 flex items-center justify-center" className="w-11 h-11 bg-white rounded-full shadow-xl hover:bg-gray-50 transition-all border border-gray-100 flex items-center justify-center shrink-0"
title="Lọc theo loại" title="Lọc theo loại"
> >
<Filter className="w-6 h-6 text-gray-800" /> <Filter className="w-6 h-6 text-gray-800" />
@@ -318,7 +318,7 @@ export const ExploreMap = ({ onBack, onLogout, user, onViewTour, onOpenMyPhotos
console.log("Đang mở Ảnh của tôi..."); console.log("Đang mở Ảnh của tôi...");
onOpenMyPhotos(); onOpenMyPhotos();
}} }}
className="bg-white p-3 md:px-4 md:py-3 rounded-2xl shadow-xl hover:bg-blue-50 text-blue-600 transition-all flex items-center gap-2 font-bold border border-blue-100" className="w-11 h-11 md:w-auto bg-white p-0 md:px-4 md:py-3 rounded-2xl shadow-xl hover:bg-blue-50 text-blue-600 transition-all flex items-center justify-center md:justify-start gap-2 font-bold border border-blue-100 shrink-0"
title="Ảnh của tôi" title="Ảnh của tôi"
> >
<ImageIcon className="w-5 h-5" /> <ImageIcon className="w-5 h-5" />
@@ -330,7 +330,7 @@ export const ExploreMap = ({ onBack, onLogout, user, onViewTour, onOpenMyPhotos
{user && ( {user && (
<button <button
onClick={() => setIsCreateModalOpen(true)} onClick={() => setIsCreateModalOpen(true)}
className="bg-green-600 p-3 md:px-4 md:py-3 rounded-2xl shadow-xl hover:bg-green-700 text-white transition-all flex items-center gap-2 font-bold" className="w-11 h-11 md:w-auto bg-green-600 p-0 md:px-4 md:py-3 rounded-2xl shadow-xl hover:bg-green-700 text-white transition-all flex items-center justify-center md:justify-start gap-2 font-bold shrink-0"
title="Tạo Tour mới" title="Tạo Tour mới"
> >
<Navigation className="w-5 h-5" /> <Navigation className="w-5 h-5" />
@@ -342,7 +342,7 @@ export const ExploreMap = ({ onBack, onLogout, user, onViewTour, onOpenMyPhotos
{user?.isAdmin && ( {user?.isAdmin && (
<button <button
onClick={() => setIsAdminModalOpen(true)} onClick={() => setIsAdminModalOpen(true)}
className="bg-blue-600 p-3 md:px-4 md:py-3 rounded-2xl shadow-xl hover:bg-blue-700 text-white transition-all flex items-center gap-2 font-bold" className="w-11 h-11 md:w-auto bg-blue-600 p-0 md:px-4 md:py-3 rounded-2xl shadow-xl hover:bg-blue-700 text-white transition-all flex items-center justify-center md:justify-start gap-2 font-bold shrink-0"
title="Quản lý hệ thống" title="Quản lý hệ thống"
> >
<Settings className="w-5 h-5" /> <Settings className="w-5 h-5" />
@@ -354,7 +354,7 @@ export const ExploreMap = ({ onBack, onLogout, user, onViewTour, onOpenMyPhotos
{onLogout && ( {onLogout && (
<button <button
onClick={onLogout} onClick={onLogout}
className="bg-white p-3 md:px-4 md:py-3 rounded-2xl shadow-xl hover:bg-red-50 hover:text-red-600 transition-all flex items-center gap-2 font-bold text-gray-700 border border-gray-100" className="w-11 h-11 md:w-auto bg-white p-0 md:px-4 md:py-3 rounded-2xl shadow-xl hover:bg-red-50 hover:text-red-600 transition-all flex items-center justify-center md:justify-start gap-2 font-bold text-gray-700 border border-gray-100 shrink-0"
title="Đăng xuất" title="Đăng xuất"
> >
<LogOut className="w-5 h-5" /> <LogOut className="w-5 h-5" />
+53 -45
View File
@@ -524,12 +524,16 @@ export const TourDetailPage = ({
const allLocations = useMemo(() => legs.flatMap(l => l.locations), [legs]); const allLocations = useMemo(() => legs.flatMap(l => l.locations), [legs]);
// Tạo key định danh cho lộ trình để buộc bản đồ vẽ lại khi dữ liệu thay đổi // Tạo key định danh cho lộ trình để buộc bản đồ vẽ lại khi dữ liệu thay đổi
const routeKey = useMemo(() => allLocations.map(l => `${l.id}-${l.latitude}-${l.longitude}`).join('|'), [allLocations]); const routeKey = useMemo(() => {
const locsKey = allLocations.map(l => `${l.id}-${l.latitude}-${l.longitude}`).join('|');
const userKey = userLocation ? `${userLocation[0].toFixed(5)}-${userLocation[1].toFixed(5)}` : 'no-user';
return `${locsKey}-${userKey}`;
}, [allLocations, userLocation]);
// Tự động tìm các quãng đường di chuyển thực tế theo phương tiện và vẽ lên bản đồ // Tự động tìm các quãng đường di chuyển thực tế theo phương tiện và vẽ lên bản đồ
useEffect(() => { useEffect(() => {
const fetchRoutes = async () => { const fetchRoutes = async () => {
if (allLocations.length < 2) { if (allLocations.length === 0 || (allLocations.length < 2 && !userLocation)) {
setRoutes([]); setRoutes([]);
setSelectedRouteIndex(0); setSelectedRouteIndex(0);
setDrivingRoute([]); setDrivingRoute([]);
@@ -537,14 +541,19 @@ export const TourDetailPage = ({
return; return;
} }
const coordsString = allLocations // Chèn vị trí người dùng vào đầu danh sách tọa độ nếu có
.map(loc => `${loc.longitude},${loc.latitude}`) const coordsArray = allLocations.map(loc => `${loc.longitude},${loc.latitude}`);
.join(';');
if (userLocation) {
coordsArray.unshift(`${userLocation[1]},${userLocation[0]}`);
}
const coordsString = coordsArray.join(';');
setIsRoutingLoading(true); setIsRoutingLoading(true);
try { try {
// Thêm alternatives=true để yêu cầu các phương án lộ trình khác từ OSRM (Lưu ý: OSRM thường chỉ trả về lộ trình thay thế cho 2 điểm tọa độ) // Yêu cầu tối đa 3 phương án lộ trình. Lưu ý: OSRM chỉ hỗ trợ alternatives cho hành trình 2 điểm.
const response = await fetch(`https://router.project-osrm.org/route/v1/${travelMode}/${coordsString}?overview=full&geometries=geojson&alternatives=true`); const response = await fetch(`https://router.project-osrm.org/route/v1/${travelMode}/${coordsString}?overview=full&geometries=geojson&alternatives=3`);
const data = await response.json(); const data = await response.json();
if (data.code === 'Ok' && data.routes.length > 0) { if (data.code === 'Ok' && data.routes.length > 0) {
setRoutes(data.routes); setRoutes(data.routes);
@@ -559,7 +568,7 @@ export const TourDetailPage = ({
}; };
fetchRoutes(); fetchRoutes();
}, [allLocations, travelMode]); }, [allLocations, travelMode, userLocation]);
// Cập nhật dữ liệu lộ trình hiển thị khi người dùng chọn phương án khác // Cập nhật dữ liệu lộ trình hiển thị khi người dùng chọn phương án khác
useEffect(() => { useEffect(() => {
@@ -1208,18 +1217,18 @@ export const TourDetailPage = ({
<div className="h-[60vh] w-full rounded-3xl overflow-hidden shadow-xl border-4 border-white relative"> <div className="h-[60vh] w-full rounded-3xl overflow-hidden shadow-xl border-4 border-white relative">
{/* Search Bar Overlay - Thanh tìm kiếm địa điểm trên bản đồ lộ trình */} {/* Search Bar Overlay - Thanh tìm kiếm địa điểm trên bản đồ lộ trình */}
{!isPublicView && ( {!isPublicView && (
<div className="absolute top-4 right-4 z-[1001] w-64 md:w-80"> <div className="absolute top-3 right-3 z-[1001] w-48 md:w-64">
<div className="relative group"> <div className="relative group">
<input <input
type="text" type="text"
placeholder="Tìm địa điểm để ghim..." placeholder="Tìm địa điểm..."
className="w-full pl-10 pr-10 py-3 bg-white/95 backdrop-blur-md border border-white/20 rounded-2xl shadow-xl outline-none focus:ring-2 focus:ring-blue-500 transition-all text-sm font-bold" className="w-full pl-8 pr-8 py-2 bg-white/95 backdrop-blur-md border border-white/20 rounded-xl shadow-xl outline-none focus:ring-2 focus:ring-blue-500 transition-all text-xs font-bold"
value={searchQuery} value={searchQuery}
onChange={e => handleSearchLocation(e.target.value)} onChange={e => handleSearchLocation(e.target.value)}
/> />
<Search className="absolute left-3.5 top-1/2 -translate-y-1/2 w-4 h-4 text-blue-500" /> <Search className="absolute left-2.5 top-1/2 -translate-y-1/2 w-3.5 h-3.5 text-blue-500" />
{isSearching ? ( {isSearching ? (
<Loader2 className="absolute right-3.5 top-1/2 -translate-y-1/2 w-4 h-4 animate-spin text-blue-500" /> <Loader2 className="absolute right-2.5 top-1/2 -translate-y-1/2 w-3.5 h-3.5 animate-spin text-blue-500" />
) : searchQuery && ( ) : searchQuery && (
<button onClick={() => { setSearchQuery(''); setSearchResults([]); }} className="absolute right-3 top-1/2 -translate-y-1/2 p-1 text-gray-400 hover:text-red-500 transition-colors"> <button onClick={() => { setSearchQuery(''); setSearchResults([]); }} className="absolute right-3 top-1/2 -translate-y-1/2 p-1 text-gray-400 hover:text-red-500 transition-colors">
<X className="w-4 h-4" /> <X className="w-4 h-4" />
@@ -1296,11 +1305,11 @@ export const TourDetailPage = ({
}) })
.map(({ data, index }) => ( .map(({ data, index }) => (
<Polyline <Polyline
key={`route-${index}-${index === selectedRouteIndex ? 'active' : 'alt'}-${routeKey}-${routes.length}`} key={`polyline-${index}-${index === selectedRouteIndex ? 'active' : 'alt'}-${routeKey}`}
positions={data.geometry.coordinates.map((c: any) => [c[1], c[0]])} positions={data.geometry.coordinates.map((c: any) => [c[1], c[0]])}
color={index === selectedRouteIndex ? "#2563eb" : "#94a3b8"} color={index === selectedRouteIndex ? "#2563eb" : "#94a3b8"}
weight={index === selectedRouteIndex ? 6 : 14} weight={index === selectedRouteIndex ? 6 : 12}
opacity={index === selectedRouteIndex ? 1 : 0.4} opacity={index === selectedRouteIndex ? 1 : 0.35}
dashArray={index === selectedRouteIndex ? undefined : "15, 15"} dashArray={index === selectedRouteIndex ? undefined : "15, 15"}
smoothFactor={1} smoothFactor={1}
eventHandlers={{ eventHandlers={{
@@ -1323,12 +1332,12 @@ export const TourDetailPage = ({
}, },
mouseover: (e) => { mouseover: (e) => {
if (index !== selectedRouteIndex) { if (index !== selectedRouteIndex) {
(e.target as L.Polyline).setStyle({ opacity: 0.8, weight: 16, color: '#64748b' }); (e.target as L.Polyline).setStyle({ opacity: 0.7, weight: 14, color: '#64748b' });
} }
}, },
mouseout: (e) => { mouseout: (e) => {
if (index !== selectedRouteIndex) { if (index !== selectedRouteIndex) {
(e.target as L.Polyline).setStyle({ opacity: 0.4, weight: 14, color: '#94a3b8' }); (e.target as L.Polyline).setStyle({ opacity: 0.35, weight: 12, color: '#94a3b8' });
} }
} }
}} }}
@@ -1437,42 +1446,42 @@ export const TourDetailPage = ({
)} )}
{/* Overlay điều khiển trên bản đồ */} {/* Overlay điều khiển trên bản đồ */}
<div className="absolute top-4 left-4 z-[1001] flex flex-col gap-2"> <div className="absolute top-3 left-3 z-[1001] flex flex-col gap-1.5">
<button <button
onClick={() => setIsMapControlsOpen(!isMapControlsOpen)} onClick={() => setIsMapControlsOpen(!isMapControlsOpen)}
className="bg-white/90 backdrop-blur-md p-3 rounded-2xl shadow-xl border border-white text-blue-600 hover:bg-blue-50 transition-all active:scale-95 flex items-center justify-center" className="w-9 h-9 bg-white/90 backdrop-blur-md rounded-xl shadow-xl border border-white text-blue-600 hover:bg-blue-50 transition-all active:scale-95 flex items-center justify-center shrink-0"
> >
{isMapControlsOpen ? ( {isMapControlsOpen ? (
<X className="w-5 h-5 text-gray-400" /> <X className="w-4 h-4 text-gray-400" />
) : ( ) : (
travelMode === 'driving' ? <Car className="w-5 h-5" /> : travelMode === 'driving' ? <Car className="w-4 h-4" /> :
travelMode === 'bike' ? <Bike className="w-5 h-5" /> : travelMode === 'bike' ? <Bike className="w-4 h-4" /> :
<Footprints className="w-5 h-5" /> <Footprints className="w-4 h-4" />
)} )}
</button> </button>
{isMapControlsOpen && ( {isMapControlsOpen && (
<div className="bg-white/90 backdrop-blur-md p-1.5 rounded-2xl shadow-xl border border-white flex flex-col gap-1.5 animate-in slide-in-from-top-2 duration-300"> <div className="bg-white/90 backdrop-blur-md p-1 rounded-xl shadow-xl border border-white flex flex-col gap-1 animate-in slide-in-from-top-2 duration-300 items-center">
<button <button
onClick={() => setTravelMode('driving')} onClick={() => { setTravelMode('driving'); setIsMapControlsOpen(false); }}
className={`p-2.5 rounded-xl transition-all ${travelMode === 'driving' ? 'bg-blue-600 text-white shadow-md' : 'text-gray-500 hover:bg-gray-100'}`} className={`w-8 h-8 flex items-center justify-center rounded-lg transition-all ${travelMode === 'driving' ? 'bg-blue-600 text-white shadow-md' : 'text-gray-500 hover:bg-gray-100'}`}
title="Ô tô" title="Ô tô"
> >
<Car className="w-4 h-4" /> <Car className="w-3.5 h-3.5" />
</button> </button>
<button <button
onClick={() => setTravelMode('bike')} onClick={() => { setTravelMode('bike'); setIsMapControlsOpen(false); }}
className={`p-2.5 rounded-xl transition-all ${travelMode === 'bike' ? 'bg-blue-600 text-white shadow-md' : 'text-gray-500 hover:bg-gray-100'}`} className={`w-8 h-8 flex items-center justify-center rounded-lg transition-all ${travelMode === 'bike' ? 'bg-blue-600 text-white shadow-md' : 'text-gray-500 hover:bg-gray-100'}`}
title="Xe máy / Xe đạp" title="Xe máy / Xe đạp"
> >
<Bike className="w-4 h-4" /> <Bike className="w-3.5 h-3.5" />
</button> </button>
<button <button
onClick={() => setTravelMode('foot')} onClick={() => { setTravelMode('foot'); setIsMapControlsOpen(false); }}
className={`p-2.5 rounded-xl transition-all ${travelMode === 'foot' ? 'bg-blue-600 text-white shadow-md' : 'text-gray-500 hover:bg-gray-100'}`} className={`w-8 h-8 flex items-center justify-center rounded-lg transition-all ${travelMode === 'foot' ? 'bg-blue-600 text-white shadow-md' : 'text-gray-500 hover:bg-gray-100'}`}
title="Đi bộ" title="Đi bộ"
> >
<Footprints className="w-4 h-4" /> <Footprints className="w-3.5 h-3.5" />
</button> </button>
{/* Nút La bàn / Xoay bản đồ */} {/* Nút La bàn / Xoay bản đồ */}
@@ -1485,38 +1494,37 @@ export const TourDetailPage = ({
setIsHeadingMode(!isHeadingMode); setIsHeadingMode(!isHeadingMode);
} }
}} }}
className={`p-2.5 rounded-xl transition-all border-t border-gray-100 mt-1 ${isHeadingMode ? 'bg-blue-600 text-white shadow-md' : 'text-gray-500 hover:bg-gray-100'}`} className={`w-8 h-8 flex items-center justify-center rounded-lg transition-all border-t border-gray-100 mt-0.5 ${isHeadingMode ? 'bg-blue-600 text-white shadow-md' : 'text-gray-500 hover:bg-gray-100'}`}
title={isHeadingMode ? "Dừng xoay (Khóa hướng Bắc)" : "Tự động xoay theo hướng di chuyển"} title={isHeadingMode ? "Dừng xoay (Khóa hướng Bắc)" : "Tự động xoay theo hướng di chuyển"}
> >
<Compass className="w-4 h-4 transition-transform duration-500" style={{ transform: `rotate(${mapRotation}deg)` }} /> <Compass className="w-3.5 h-3.5 transition-transform duration-500" style={{ transform: `rotate(${mapRotation}deg)` }} />
</button> </button>
{/* Nút Tìm tôi */} {/* Nút Tìm tôi */}
<button <button
onClick={() => setLocateTrigger(prev => prev + 1)} onClick={() => { setLocateTrigger(prev => prev + 1); setIsMapControlsOpen(false); }}
disabled={!userLocation} disabled={!userLocation}
className={`p-2.5 rounded-xl transition-all border-t border-gray-100 mt-1 ${!userLocation ? 'opacity-30 cursor-not-allowed' : 'text-blue-600 hover:bg-blue-50'}`} className={`w-8 h-8 flex items-center justify-center rounded-lg transition-all border-t border-gray-100 mt-0.5 ${!userLocation ? 'opacity-30 cursor-not-allowed' : 'text-blue-600 hover:bg-blue-50'}`}
title="Vị trí của tôi" title="Vị trí của tôi"
> >
<LocateFixed className="w-4 h-4" /> <LocateFixed className="w-3.5 h-3.5" />
</button> </button>
{/* Danh sách lộ trình rút gọn */} {/* Danh sách lộ trình rút gọn */}
{routes.length > 0 && ( {routes.length > 0 && (
<div className="pt-2 border-t border-gray-100 flex flex-col gap-1 min-w-[120px]"> <div className="pt-1 border-t border-gray-100 flex flex-col gap-1">
<div className="text-[9px] font-black text-gray-400 uppercase tracking-tighter px-1 mb-1">Lộ trình</div>
<div className="flex flex-col gap-1 max-h-[160px] overflow-y-auto pr-1 custom-scrollbar"> <div className="flex flex-col gap-1 max-h-[160px] overflow-y-auto pr-1 custom-scrollbar">
{routes.map((route, idx) => ( {routes.map((route, idx) => (
<button <button
key={idx} key={idx}
onClick={() => setSelectedRouteIndex(idx)} onClick={() => { setSelectedRouteIndex(idx); setIsMapControlsOpen(false); }}
className={`p-2 rounded-xl text-left transition-all border ${ className={`w-8 h-8 flex items-center justify-center rounded-lg transition-all border ${
selectedRouteIndex === idx selectedRouteIndex === idx
? 'bg-blue-600 text-white border-blue-600 shadow-sm' ? 'bg-blue-600 text-white border-blue-600 shadow-sm'
: 'bg-gray-50 text-gray-600 border-gray-100' : 'bg-gray-50 text-gray-600 border-gray-100'
}`} }`}
> >
<div className="text-[10px] font-bold">#{idx + 1} - {(route.distance / 1000).toFixed(1)} km</div> <span className="text-[10px] font-bold">{idx + 1}</span>
</button> </button>
))} ))}
</div> </div>
@@ -1881,7 +1889,7 @@ export const TourDetailPage = ({
if (activeTab === 'plan') setIsAddLocationOpen(true); if (activeTab === 'plan') setIsAddLocationOpen(true);
if (activeTab === 'photo' && canUploadPhoto) setIsAddPhotoOpen(true); if (activeTab === 'photo' && canUploadPhoto) setIsAddPhotoOpen(true);
}} }}
className="bg-blue-600 text-white px-6 py-3 rounded-full shadow-lg hover:bg-blue-700 transition-all flex items-center font-bold"> className="bg-blue-600 text-white px-4 py-2.5 rounded-full shadow-lg hover:bg-blue-700 transition-all flex items-center font-bold text-sm">
{activeTab === 'plan' ? 'Thêm địa điểm' : activeTab === 'expense' ? 'Thêm chi phí' : 'Đăng ảnh'} {activeTab === 'plan' ? 'Thêm địa điểm' : activeTab === 'expense' ? 'Thêm chi phí' : 'Đăng ảnh'}
</button> </button>
</div> </div>