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:
2026-06-14 09:06:51 +07:00
parent c7db2c3ed8
commit ed437cdb0f
16 changed files with 783 additions and 163 deletions
+109 -57
View File
@@ -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='&copy; 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>