fix: tags không có trong csdl gây nên lỗi crash backend

This commit is contained in:
2026-06-16 11:15:39 +07:00
parent bc49e081c6
commit 8cca4a83ca
9 changed files with 189 additions and 10 deletions
+53 -2
View File
@@ -5,7 +5,7 @@ const MarkerClusterGroup = (_MarkerClusterGroup as any).default || _MarkerCluste
import L from 'leaflet';
import 'leaflet/dist/leaflet.css';
import { useTourStore } from '@/store/useTourStore';
import { X, Navigation, Image as ImageIcon, LogOut, Settings, Edit2, Trash2, Share2 } from 'lucide-react';
import { X, Navigation, Image as ImageIcon, LogOut, Settings, Edit2, Trash2, Share2, Filter, Tag as TagIcon } from 'lucide-react';
import { UserManagementModal } from '@/components/UserManagementModal';
import { NotificationModal, useNotificationModal } from '@/components/NotificationModal';
import { CreateTourModal } from '../components/CreateTourModal';
@@ -79,6 +79,18 @@ export const ExploreMap = ({ onBack, onLogout, user, onViewTour }: { onBack: ()
const [isAdminModalOpen, setIsAdminModalOpen] = useState(false);
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
const [selectedFilterTag, setSelectedFilterTag] = useState<string | null>(null);
const availableTags = ['Văn hóa', 'Mạo hiểm', 'Nghỉ dưỡng', 'Thư giãn', 'Ẩm thực', 'Khám phá', 'Gia đình'];
// Tổng hợp nhãn từ danh sách Tour đang có để hiển thị bộ lọc đầy đủ (bao gồm cả nhãn tùy chỉnh)
const allFilterTags = React.useMemo(() => {
const tagsSet = new Set(availableTags);
publicTours.forEach(tour => {
tour.tags?.forEach((tag: string) => tagsSet.add(tag));
});
return Array.from(tagsSet);
}, [publicTours]);
// State cho menu chuột phải chia sẻ
const [shareMenu, setShareMenu] = useState<{ x: number, y: number, id: string, title: string, canShare: boolean } | null>(null);
@@ -109,6 +121,11 @@ export const ExploreMap = ({ onBack, onLogout, user, onViewTour }: { onBack: ()
setShareMenu(null);
};
const filteredTours = React.useMemo(() => {
if (!selectedFilterTag) return publicTours;
return publicTours.filter(tour => tour.tags?.includes(selectedFilterTag));
}, [publicTours, selectedFilterTag]);
useEffect(() => {
// Chỉ fetch dữ liệu khi người dùng đã đăng nhập và có token
if (user || localStorage.getItem('token')) {
@@ -182,6 +199,33 @@ export const ExploreMap = ({ onBack, onLogout, user, onViewTour }: { onBack: ()
</div>
</div>
{/* Bộ lọc theo Tag */}
<div className="absolute top-24 left-6 z-[1000] flex flex-col gap-2 pointer-events-none">
<div className="bg-white/90 backdrop-blur-md p-2 rounded-2xl shadow-xl border border-white/20 pointer-events-auto flex flex-col gap-2 max-w-[200px]">
<div className="flex items-center gap-2 px-2 py-1 border-b border-gray-100 mb-1">
<Filter className="w-3.5 h-3.5 text-blue-600" />
<span className="text-[11px] font-black uppercase text-gray-500 tracking-wider">Lọc theo loại</span>
</div>
<div className="flex flex-wrap gap-1.5">
<button
onClick={() => setSelectedFilterTag(null)}
className={`px-2.5 py-1 rounded-lg text-[10px] font-bold transition-all ${!selectedFilterTag ? 'bg-blue-600 text-white' : 'bg-gray-100 text-gray-500 hover:bg-gray-200'}`}
>
Tất cả
</button>
{allFilterTags.map(tag => (
<button
key={tag}
onClick={() => setSelectedFilterTag(tag === selectedFilterTag ? null : tag)}
className={`px-2.5 py-1 rounded-lg text-[10px] font-bold transition-all ${selectedFilterTag === tag ? 'bg-blue-600 text-white' : 'bg-gray-100 text-gray-500 hover:bg-gray-200'}`}
>
{tag}
</button>
))}
</div>
</div>
</div>
<MapContainer
center={userPos}
zoom={mapZoom}
@@ -203,7 +247,7 @@ export const ExploreMap = ({ onBack, onLogout, user, onViewTour }: { onBack: ()
<RecenterMap position={userPos} />
<MarkerClusterGroup chunkedLoading>
{publicTours.map((tour) => {
{filteredTours.map((tour) => {
const startLoc = tour.legs?.[0]?.locations?.[0];
const tourImage = tour.photos?.[0]?.imageUrl || `https://picsum.photos/seed/${tour.id}/200/200`;
const markerPos = startLoc
@@ -250,6 +294,13 @@ export const ExploreMap = ({ onBack, onLogout, user, onViewTour }: { onBack: ()
<Tooltip direction="top" offset={[0, -20]} opacity={1}>
<div className="p-1 max-w-[180px]">
<div className="font-black text-blue-600 text-[11px] mb-0.5 uppercase tracking-tight truncate">{tour.title}</div>
{tour.tags && tour.tags.length > 0 && (
<div className="flex flex-wrap gap-1 mb-1">
{tour.tags.map((tag: string) => (
<span key={tag} className="px-1.5 py-0.5 bg-blue-50 text-blue-500 rounded text-[8px] font-bold border border-blue-100">{tag}</span>
))}
</div>
)}
{tour.description && (
<div className="text-[10px] text-gray-500 line-clamp-2 leading-tight italic">
{tour.description}