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:
@@ -223,7 +223,7 @@ export const ExploreMap = ({ onBack, onLogout, user, onViewTour, onOpenMyPhotos
|
||||
<div className="flex items-center gap-3 pointer-events-auto">
|
||||
<button
|
||||
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"
|
||||
>
|
||||
<X className="w-6 h-6 text-gray-800" />
|
||||
@@ -233,7 +233,7 @@ export const ExploreMap = ({ onBack, onLogout, user, onViewTour, onOpenMyPhotos
|
||||
<div className="relative">
|
||||
<button
|
||||
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"
|
||||
>
|
||||
<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...");
|
||||
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"
|
||||
>
|
||||
<ImageIcon className="w-5 h-5" />
|
||||
@@ -330,7 +330,7 @@ export const ExploreMap = ({ onBack, onLogout, user, onViewTour, onOpenMyPhotos
|
||||
{user && (
|
||||
<button
|
||||
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"
|
||||
>
|
||||
<Navigation className="w-5 h-5" />
|
||||
@@ -342,7 +342,7 @@ export const ExploreMap = ({ onBack, onLogout, user, onViewTour, onOpenMyPhotos
|
||||
{user?.isAdmin && (
|
||||
<button
|
||||
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"
|
||||
>
|
||||
<Settings className="w-5 h-5" />
|
||||
@@ -354,7 +354,7 @@ export const ExploreMap = ({ onBack, onLogout, user, onViewTour, onOpenMyPhotos
|
||||
{onLogout && (
|
||||
<button
|
||||
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"
|
||||
>
|
||||
<LogOut className="w-5 h-5" />
|
||||
|
||||
@@ -524,12 +524,16 @@ export const TourDetailPage = ({
|
||||
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
|
||||
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 đồ
|
||||
useEffect(() => {
|
||||
const fetchRoutes = async () => {
|
||||
if (allLocations.length < 2) {
|
||||
if (allLocations.length === 0 || (allLocations.length < 2 && !userLocation)) {
|
||||
setRoutes([]);
|
||||
setSelectedRouteIndex(0);
|
||||
setDrivingRoute([]);
|
||||
@@ -537,14 +541,19 @@ export const TourDetailPage = ({
|
||||
return;
|
||||
}
|
||||
|
||||
const coordsString = allLocations
|
||||
.map(loc => `${loc.longitude},${loc.latitude}`)
|
||||
.join(';');
|
||||
// Chèn vị trí người dùng vào đầu danh sách tọa độ nếu có
|
||||
const coordsArray = allLocations.map(loc => `${loc.longitude},${loc.latitude}`);
|
||||
|
||||
if (userLocation) {
|
||||
coordsArray.unshift(`${userLocation[1]},${userLocation[0]}`);
|
||||
}
|
||||
|
||||
const coordsString = coordsArray.join(';');
|
||||
|
||||
setIsRoutingLoading(true);
|
||||
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 độ)
|
||||
const response = await fetch(`https://router.project-osrm.org/route/v1/${travelMode}/${coordsString}?overview=full&geometries=geojson&alternatives=true`);
|
||||
// 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=3`);
|
||||
const data = await response.json();
|
||||
if (data.code === 'Ok' && data.routes.length > 0) {
|
||||
setRoutes(data.routes);
|
||||
@@ -559,7 +568,7 @@ export const TourDetailPage = ({
|
||||
};
|
||||
|
||||
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
|
||||
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">
|
||||
{/* Search Bar Overlay - Thanh tìm kiếm địa điểm trên bản đồ lộ trình */}
|
||||
{!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">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Tìm địa điểm để ghim..."
|
||||
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"
|
||||
placeholder="Tìm địa điểm..."
|
||||
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}
|
||||
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 ? (
|
||||
<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 && (
|
||||
<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" />
|
||||
@@ -1296,11 +1305,11 @@ export const TourDetailPage = ({
|
||||
})
|
||||
.map(({ data, index }) => (
|
||||
<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]])}
|
||||
color={index === selectedRouteIndex ? "#2563eb" : "#94a3b8"}
|
||||
weight={index === selectedRouteIndex ? 6 : 14}
|
||||
opacity={index === selectedRouteIndex ? 1 : 0.4}
|
||||
weight={index === selectedRouteIndex ? 6 : 12}
|
||||
opacity={index === selectedRouteIndex ? 1 : 0.35}
|
||||
dashArray={index === selectedRouteIndex ? undefined : "15, 15"}
|
||||
smoothFactor={1}
|
||||
eventHandlers={{
|
||||
@@ -1323,12 +1332,12 @@ export const TourDetailPage = ({
|
||||
},
|
||||
mouseover: (e) => {
|
||||
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) => {
|
||||
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 đồ */}
|
||||
<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
|
||||
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 ? (
|
||||
<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 === 'bike' ? <Bike className="w-5 h-5" /> :
|
||||
<Footprints className="w-5 h-5" />
|
||||
travelMode === 'driving' ? <Car className="w-4 h-4" /> :
|
||||
travelMode === 'bike' ? <Bike className="w-4 h-4" /> :
|
||||
<Footprints className="w-4 h-4" />
|
||||
)}
|
||||
</button>
|
||||
|
||||
{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
|
||||
onClick={() => setTravelMode('driving')}
|
||||
className={`p-2.5 rounded-xl transition-all ${travelMode === 'driving' ? 'bg-blue-600 text-white shadow-md' : 'text-gray-500 hover:bg-gray-100'}`}
|
||||
onClick={() => { setTravelMode('driving'); setIsMapControlsOpen(false); }}
|
||||
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ô"
|
||||
>
|
||||
<Car className="w-4 h-4" />
|
||||
<Car className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setTravelMode('bike')}
|
||||
className={`p-2.5 rounded-xl transition-all ${travelMode === 'bike' ? 'bg-blue-600 text-white shadow-md' : 'text-gray-500 hover:bg-gray-100'}`}
|
||||
onClick={() => { setTravelMode('bike'); setIsMapControlsOpen(false); }}
|
||||
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"
|
||||
>
|
||||
<Bike className="w-4 h-4" />
|
||||
<Bike className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setTravelMode('foot')}
|
||||
className={`p-2.5 rounded-xl transition-all ${travelMode === 'foot' ? 'bg-blue-600 text-white shadow-md' : 'text-gray-500 hover:bg-gray-100'}`}
|
||||
onClick={() => { setTravelMode('foot'); setIsMapControlsOpen(false); }}
|
||||
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ộ"
|
||||
>
|
||||
<Footprints className="w-4 h-4" />
|
||||
<Footprints className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
|
||||
{/* Nút La bàn / Xoay bản đồ */}
|
||||
@@ -1485,38 +1494,37 @@ export const TourDetailPage = ({
|
||||
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"}
|
||||
>
|
||||
<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>
|
||||
|
||||
{/* Nút Tìm tôi */}
|
||||
<button
|
||||
onClick={() => setLocateTrigger(prev => prev + 1)}
|
||||
onClick={() => { setLocateTrigger(prev => prev + 1); setIsMapControlsOpen(false); }}
|
||||
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"
|
||||
>
|
||||
<LocateFixed className="w-4 h-4" />
|
||||
<LocateFixed className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
|
||||
{/* Danh sách lộ trình rút gọn */}
|
||||
{routes.length > 0 && (
|
||||
<div className="pt-2 border-t border-gray-100 flex flex-col gap-1 min-w-[120px]">
|
||||
<div className="text-[9px] font-black text-gray-400 uppercase tracking-tighter px-1 mb-1">Lộ trình</div>
|
||||
<div className="pt-1 border-t border-gray-100 flex flex-col gap-1">
|
||||
<div className="flex flex-col gap-1 max-h-[160px] overflow-y-auto pr-1 custom-scrollbar">
|
||||
{routes.map((route, idx) => (
|
||||
<button
|
||||
key={idx}
|
||||
onClick={() => setSelectedRouteIndex(idx)}
|
||||
className={`p-2 rounded-xl text-left transition-all border ${
|
||||
onClick={() => { setSelectedRouteIndex(idx); setIsMapControlsOpen(false); }}
|
||||
className={`w-8 h-8 flex items-center justify-center rounded-lg transition-all border ${
|
||||
selectedRouteIndex === idx
|
||||
? 'bg-blue-600 text-white border-blue-600 shadow-sm'
|
||||
: '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>
|
||||
))}
|
||||
</div>
|
||||
@@ -1881,7 +1889,7 @@ export const TourDetailPage = ({
|
||||
if (activeTab === 'plan') setIsAddLocationOpen(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'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user