Files
travelplanning/ExploreMap.tsx
T

157 lines
6.6 KiB
TypeScript

import React, { useEffect, useState } from 'react';
import { MapContainer, TileLayer, Marker, Popup, useMap } from 'react-leaflet';
import L from 'leaflet';
import 'leaflet/dist/leaflet.css';
import { useTourStore } from './useTourStore.js';
import { X, Navigation, Image as ImageIcon, LogOut, Settings } from 'lucide-react';
import { UserManagementModal } from './UserManagementModal.js';
import { CreateTourModal } from './CreateTourModal.js';
// Fix lỗi icon mặc định của Leaflet
const DefaultIcon = L.icon({
iconUrl: 'https://unpkg.com/leaflet@1.9.4/dist/images/marker-icon.png',
shadowUrl: 'https://unpkg.com/leaflet@1.9.4/dist/images/marker-shadow.png',
iconSize: [25, 41],
iconAnchor: [12, 41],
});
L.Marker.prototype.options.icon = DefaultIcon;
// Component Helper để cập nhật tâm bản đồ khi vị trí người dùng thay đổi
function RecenterMap({ position }: { position: [number, number] }) {
const map = useMap();
useEffect(() => {
map.setView(position, map.getZoom());
}, [position, map]);
return null;
}
export const ExploreMap = ({ onBack, onLogout, user, onViewTour }: { onBack: () => void, onLogout?: () => void, user?: any, onViewTour: (id: string) => void }) => {
const { publicTours, fetchPublicTours, fetchTour } = useTourStore();
const [userPos, setUserPos] = useState<[number, number]>([10.7769, 106.7009]); // Mặc định TP.HCM
const [isAdminModalOpen, setIsAdminModalOpen] = useState(false);
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
useEffect(() => {
fetchPublicTours();
navigator.geolocation.getCurrentPosition(
(pos) => setUserPos([pos.coords.latitude, pos.coords.longitude]),
() => console.log("Không thể lấy vị trí người dùng")
);
}, []);
return (
<div className="h-screen w-full relative">
{/* Nút quay lại */}
<button
onClick={onBack}
className="absolute top-6 left-6 z-[1000] bg-white p-3 rounded-full shadow-xl hover:bg-gray-50 transition-all"
>
<X className="w-6 h-6 text-gray-800" />
</button>
{/* Nút đăng xuất - Chỉ hiển thị khi có user login */}
{onLogout && (
<button
onClick={onLogout}
className="absolute top-6 right-6 z-[1000] bg-white px-4 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"
>
<LogOut className="w-5 h-5" />
<span className="hidden sm:inline">Đăng xuất</span>
</button>
)}
{/* Nút quản lý người dùng cho Admin */}
{user?.isAdmin && (
<button
onClick={() => setIsAdminModalOpen(true)}
className="absolute top-6 right-40 z-[1000] bg-blue-600 px-4 py-3 rounded-2xl shadow-xl hover:bg-blue-700 text-white transition-all flex items-center gap-2 font-bold"
>
<Settings className="w-5 h-5" />
<span className="hidden sm:inline">Quản hệ thống</span>
</button>
)}
{/* Nút tạo Tour mới */}
{user && (
<button
onClick={() => setIsCreateModalOpen(true)}
className="absolute top-6 right-80 z-[1000] bg-green-600 px-4 py-3 rounded-2xl shadow-xl hover:bg-green-700 text-white transition-all flex items-center gap-2 font-bold"
>
<Navigation className="w-5 h-5" />
<span className="hidden sm:inline">Tạo Tour mới</span>
</button>
)}
{/* Header Overlay */}
<div className="absolute top-6 left-20 z-[1000] bg-white/90 backdrop-blur-md px-6 py-3 rounded-2xl shadow-xl border border-white/20 hidden sm:block">
<div className="flex items-center gap-2">
<Navigation className="w-4 h-4 text-blue-600" />
<span className="font-bold text-gray-800">Đang khám phá khu vực của bạn</span>
</div>
</div>
<MapContainer center={userPos} zoom={13} className="h-full w-full">
<TileLayer
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
attribution='&copy; OpenStreetMap contributors'
/>
{/* 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) => {
const location = tour.legs?.[0]?.locations?.[0];
if (!location) return null;
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>
</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>
</div>
</Popup>
</Marker>
);
})}
</MapContainer>
{/* Admin Modal */}
<UserManagementModal isOpen={isAdminModalOpen} onClose={() => setIsAdminModalOpen(false)} />
{/* Create Tour Modal */}
<CreateTourModal
isOpen={isCreateModalOpen}
onClose={() => setIsCreateModalOpen(false)}
onSuccess={(tour) => {
fetchTour(tour.id);
onViewTour(tour.id);
}}
/>
</div>
);
};