Sửa lỗi hiển thị điểm đầu và điểm cuối của lộ trình trên bản đồ
This commit is contained in:
+109
-57
@@ -32,7 +32,12 @@ function MapTracker() {
|
||||
moveend: (e) => {
|
||||
const map = e.target;
|
||||
const center = map.getCenter();
|
||||
setMapCenter([center.lat, center.lng]);
|
||||
const zoom = map.getZoom();
|
||||
const coords: [number, number] = [center.lat, center.lng];
|
||||
|
||||
setMapCenter(coords);
|
||||
// Lưu vị trí và mức zoom vào localStorage để sử dụng cho lần sau
|
||||
localStorage.setItem('map_view_state', JSON.stringify({ center: coords, zoom }));
|
||||
},
|
||||
});
|
||||
return null;
|
||||
@@ -40,20 +45,38 @@ function MapTracker() {
|
||||
|
||||
export const ExploreMap = ({ onBack, onLogout, user, onViewTour }: { onBack: () => void, onLogout?: () => void, user?: any, onViewTour: (id: string) => void }) => {
|
||||
const { publicTours, fetchPublicTours, fetchTour, updateTour, deleteTour, mapCenter, setMapCenter } = useTourStore();
|
||||
const [userPos, setUserPos] = useState<[number, number]>([10.7769, 106.7009]); // Mặc định TP.HCM
|
||||
|
||||
// Khởi tạo vị trí từ localStorage nếu có, nếu không dùng mặc định (TP.HCM)
|
||||
const [initialViewState] = useState(() => {
|
||||
const saved = localStorage.getItem('map_view_state');
|
||||
if (saved) {
|
||||
try { return JSON.parse(saved); } catch (e) { return null; }
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
const [userPos, setUserPos] = useState<[number, number]>(initialViewState?.center || [10.7769, 106.7009]);
|
||||
const [mapZoom] = useState(initialViewState?.zoom || 13);
|
||||
const [isAdminModalOpen, setIsAdminModalOpen] = useState(false);
|
||||
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
fetchPublicTours();
|
||||
navigator.geolocation.getCurrentPosition(
|
||||
(pos) => {
|
||||
const posArray: [number, number] = [pos.coords.latitude, pos.coords.longitude];
|
||||
setUserPos(posArray);
|
||||
setMapCenter(posArray);
|
||||
},
|
||||
() => console.log("Không thể lấy vị trí người dùng")
|
||||
);
|
||||
|
||||
// Nếu không có vị trí lưu từ trước, mới yêu cầu lấy vị trí hiện tại của thiết bị
|
||||
if (!initialViewState) {
|
||||
navigator.geolocation.getCurrentPosition(
|
||||
(pos) => {
|
||||
const posArray: [number, number] = [pos.coords.latitude, pos.coords.longitude];
|
||||
setUserPos(posArray);
|
||||
setMapCenter(posArray);
|
||||
},
|
||||
() => console.log("Không thể lấy vị trí người dùng")
|
||||
);
|
||||
} else {
|
||||
// Cập nhật store để đồng bộ với vị trí khởi tạo từ cache
|
||||
setMapCenter(initialViewState.center);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleEditTour = async (tour: any) => {
|
||||
@@ -128,7 +151,7 @@ export const ExploreMap = ({ onBack, onLogout, user, onViewTour }: { onBack: ()
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<MapContainer center={mapCenter} zoom={13} className="h-full w-full">
|
||||
<MapContainer center={userPos} zoom={mapZoom} className="h-full w-full">
|
||||
<TileLayer
|
||||
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
|
||||
attribution='© OpenStreetMap contributors'
|
||||
@@ -141,57 +164,86 @@ export const ExploreMap = ({ onBack, onLogout, user, onViewTour }: { onBack: ()
|
||||
<RecenterMap position={userPos} />
|
||||
|
||||
{publicTours.map((tour) => {
|
||||
const location = tour.legs?.[0]?.locations?.[0];
|
||||
if (!location) return null;
|
||||
const startLoc = tour.legs?.[0]?.locations?.[0];
|
||||
if (!startLoc) return null;
|
||||
|
||||
const lastLeg = tour.legs?.[tour.legs.length - 1];
|
||||
const endLoc = lastLeg?.locations?.[lastLeg.locations.length - 1];
|
||||
|
||||
const tourImage = tour.photos?.[0]?.imageUrl || `https://picsum.photos/seed/${tour.id}/200/200`;
|
||||
|
||||
return (
|
||||
<Marker
|
||||
key={tour.id}
|
||||
position={[location.latitude, location.longitude]}
|
||||
icon={L.divIcon({
|
||||
className: 'custom-bubble',
|
||||
html: `
|
||||
<div class="relative group">
|
||||
<div class="w-12 h-12 rounded-full border-4 border-white shadow-lg overflow-hidden transition-transform group-hover:scale-110">
|
||||
<img src="${tourImage}" class="w-full h-full object-cover" />
|
||||
</div>
|
||||
<div class="absolute -bottom-1 -right-1 bg-blue-600 rounded-full p-1 border-2 border-white">
|
||||
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="3"><path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/><circle cx="12" cy="13" r="4"/></svg>
|
||||
</div>
|
||||
const commonPopup = (
|
||||
<Popup className="custom-popup">
|
||||
<div className="p-1 max-w-[200px]">
|
||||
<img src={tourImage} className="w-full h-32 object-cover rounded-lg mb-2" />
|
||||
<h4 className="font-bold text-gray-900 leading-tight mb-1">{tour.title}</h4>
|
||||
<button
|
||||
onClick={() => onViewTour(tour.id)}
|
||||
className="text-xs font-bold text-blue-600 flex items-center gap-1">
|
||||
Xem chi tiết hình ảnh <ImageIcon className="w-3 h-3" />
|
||||
</button>
|
||||
|
||||
{user && (
|
||||
<div className="flex gap-2 mt-3 pt-3 border-t border-gray-100">
|
||||
<button
|
||||
onClick={() => handleEditTour(tour)}
|
||||
className="flex-1 flex items-center justify-center gap-1 py-1.5 bg-blue-50 text-blue-600 rounded-lg text-[10px] font-bold hover:bg-blue-100 transition-colors">
|
||||
<Edit2 className="w-3 h-3" /> Sửa
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleDeleteTour(tour.id)}
|
||||
className="flex-1 flex items-center justify-center gap-1 py-1.5 bg-red-50 text-red-600 rounded-lg text-[10px] font-bold hover:bg-red-100 transition-colors">
|
||||
<Trash2 className="w-3 h-3" /> Xóa
|
||||
</button>
|
||||
</div>
|
||||
`,
|
||||
iconSize: [48, 48],
|
||||
})}
|
||||
>
|
||||
<Popup className="custom-popup">
|
||||
<div className="p-1 max-w-[200px]">
|
||||
<img src={tourImage} className="w-full h-32 object-cover rounded-lg mb-2" />
|
||||
<h4 className="font-bold text-gray-900 leading-tight mb-1">{tour.title}</h4>
|
||||
<button
|
||||
onClick={() => onViewTour(tour.id)}
|
||||
className="text-xs font-bold text-blue-600 flex items-center gap-1">
|
||||
Xem chi tiết hình ảnh <ImageIcon className="w-3 h-3" />
|
||||
</button>
|
||||
|
||||
{user && (
|
||||
<div className="flex gap-2 mt-3 pt-3 border-t border-gray-100">
|
||||
<button
|
||||
onClick={() => handleEditTour(tour)}
|
||||
className="flex-1 flex items-center justify-center gap-1 py-1.5 bg-blue-50 text-blue-600 rounded-lg text-[10px] font-bold hover:bg-blue-100 transition-colors">
|
||||
<Edit2 className="w-3 h-3" /> Sửa
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleDeleteTour(tour.id)}
|
||||
className="flex-1 flex items-center justify-center gap-1 py-1.5 bg-red-50 text-red-600 rounded-lg text-[10px] font-bold hover:bg-red-100 transition-colors">
|
||||
<Trash2 className="w-3 h-3" /> Xóa
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</Popup>
|
||||
);
|
||||
|
||||
return (
|
||||
<React.Fragment key={tour.id}>
|
||||
{/* Start Marker - Blue with 'S' Badge */}
|
||||
<Marker
|
||||
position={[startLoc.latitude, startLoc.longitude]}
|
||||
icon={L.divIcon({
|
||||
className: 'custom-bubble',
|
||||
html: `
|
||||
<div class="relative group">
|
||||
<div class="w-12 h-12 rounded-full border-4 border-white shadow-lg overflow-hidden transition-transform group-hover:scale-110">
|
||||
<img src="${tourImage}" class="w-full h-full object-cover" />
|
||||
</div>
|
||||
<div class="absolute -bottom-1 -right-1 bg-blue-600 rounded-full w-5 h-5 border-2 border-white flex items-center justify-center text-[10px] font-black text-white">
|
||||
S
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Popup>
|
||||
</Marker>
|
||||
`,
|
||||
iconSize: [48, 48],
|
||||
iconAnchor: [24, 24]
|
||||
})}
|
||||
>
|
||||
{commonPopup}
|
||||
</Marker>
|
||||
|
||||
{/* End Marker - Green with 'E' (Only if different from start) */}
|
||||
{endLoc && (endLoc.latitude !== startLoc.latitude || endLoc.longitude !== startLoc.longitude) && (
|
||||
<Marker
|
||||
position={[endLoc.latitude, endLoc.longitude]}
|
||||
icon={L.divIcon({
|
||||
className: 'custom-end-marker',
|
||||
html: `
|
||||
<div class="w-8 h-8 bg-green-600 rounded-full border-2 border-white shadow-lg flex items-center justify-center text-xs font-black text-white hover:scale-110 transition-transform">
|
||||
E
|
||||
</div>
|
||||
`,
|
||||
iconSize: [32, 32],
|
||||
iconAnchor: [16, 16]
|
||||
})}
|
||||
>
|
||||
{commonPopup}
|
||||
</Marker>
|
||||
)}
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
</MapContainer>
|
||||
|
||||
Reference in New Issue
Block a user