Vào thằng Tour Dashboard khi click vào Tour callout

This commit is contained in:
2026-06-14 10:01:26 +07:00
parent b3519c94cd
commit bf63510980
9 changed files with 190 additions and 243 deletions
+18 -80
View File
@@ -1,5 +1,7 @@
import React, { useEffect, useState } from 'react';
import { MapContainer, TileLayer, Marker, Popup, useMap, useMapEvents } from 'react-leaflet';
import _MarkerClusterGroup from 'react-leaflet-cluster';
const MarkerClusterGroup = (_MarkerClusterGroup as any).default || _MarkerClusterGroup;
import L from 'leaflet';
import 'leaflet/dist/leaflet.css';
import { useTourStore } from './useTourStore.js';
@@ -44,7 +46,7 @@ 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 { publicTours, fetchPublicTours, setMapCenter } = useTourStore();
// 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(() => {
@@ -79,27 +81,6 @@ export const ExploreMap = ({ onBack, onLogout, user, onViewTour }: { onBack: ()
}
}, []);
const handleEditTour = async (tour: any) => {
const newTitle = window.prompt("Nhập tên mới cho Tour:", tour.title);
if (newTitle && newTitle !== tour.title) {
try {
await updateTour(tour.id, { title: newTitle });
} catch (err: any) {
alert(err.message);
}
}
};
const handleDeleteTour = async (id: string) => {
if (window.confirm("Bạn có chắc chắn muốn xóa Tour này và toàn bộ dữ liệu liên quan?")) {
try {
await deleteTour(id);
} catch (err: any) {
alert(err.message);
}
}
};
return (
<div className="h-screen w-full relative">
{/* Nút quay lại */}
@@ -151,7 +132,12 @@ export const ExploreMap = ({ onBack, onLogout, user, onViewTour }: { onBack: ()
</div>
</div>
<MapContainer center={userPos} zoom={mapZoom} className="h-full w-full">
<MapContainer
center={userPos}
zoom={mapZoom}
className="h-full w-full"
preferCanvas={true}
>
<TileLayer
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
attribution='&copy; OpenStreetMap contributors'
@@ -163,49 +149,21 @@ export const ExploreMap = ({ onBack, onLogout, user, onViewTour }: { onBack: ()
{/* Tự động di chuyển bản đồ đến vị trí người dùng khi tìm thấy tọa độ */}
<RecenterMap position={userPos} />
{publicTours.map((tour) => {
<MarkerClusterGroup chunkedLoading>
{publicTours.map((tour) => {
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`;
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>
)}
</div>
</Popup>
);
return (
<React.Fragment key={tour.id}>
{/* Start Marker - Blue with 'S' Badge */}
{/* Tour Marker - Bong bóng chứa thumbnail. Click chuyển vào Dashboard */}
<Marker
position={[startLoc.latitude, startLoc.longitude]}
eventHandlers={{
click: () => onViewTour(tour.id)
}}
icon={L.divIcon({
className: 'custom-bubble',
html: `
@@ -221,31 +179,11 @@ export const ExploreMap = ({ onBack, onLogout, user, onViewTour }: { onBack: ()
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>
);
})}
})}
</MarkerClusterGroup>
</MapContainer>
{/* Admin Modal */}