fix: tạo tags cho ảnh public của guest

This commit is contained in:
2026-06-23 16:01:46 +07:00
parent 9f41400bd8
commit 28ea9abccd
28 changed files with 601 additions and 60 deletions
+99 -20
View File
@@ -24,6 +24,21 @@ const DefaultIcon = L.icon({
});
L.Marker.prototype.options.icon = DefaultIcon;
// Tag ID to Label Mapping for Public Photos
const PHOTO_TAG_LABELS: { [key: string]: string } = {
'phong-canh': '🏞️ Phong cảnh',
'con-nguoi': '👥 Con người',
'doi-thuong': '🎒 Đời thường',
'bien': '🌊 Biển',
'nui': '⛰️ Núi',
'do-thi': '🏙️ Đô thị',
'thuc-an': '🍜 Thức ăn',
'cho': '🛍️ Chợ',
'hien-dai': '🏗️ Hiện đại',
'dong-vat': '🦁 Động vật',
'thu-cung': '🐕 Thú cưng'
};
// 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();
@@ -192,10 +207,22 @@ export const ExploreMap = ({ onBack, onLogout, user, onViewTour, onOpenMyPhotos,
const [publicPhotos, setPublicPhotos] = useState<any[]>([]);
const [selectedPhoto, setSelectedPhoto] = useState<any | null>(null);
const [selectedPhotoGroup, setSelectedPhotoGroup] = useState<any[]>([]);
const [selectedPhotoFilterTags, setSelectedPhotoFilterTags] = useState<string[]>([]);
const groupedPhotos = React.useMemo(() => {
// Filter photos by selected tags if any are selected
let filteredPhotos = publicPhotos;
if (selectedPhotoFilterTags.length > 0) {
filteredPhotos = publicPhotos.filter((photo) => {
const photoTags = photo.metadata?.tags as string[] | undefined;
if (!Array.isArray(photoTags)) return false;
// Check if photo has at least one of the selected tags
return selectedPhotoFilterTags.some(tag => photoTags.includes(tag));
});
}
const groups: { [key: string]: any[] } = {};
publicPhotos.forEach((photo) => {
filteredPhotos.forEach((photo) => {
const lat = photo.metadata?.lat;
const lng = photo.metadata?.lng;
if (typeof lat === 'number' && typeof lng === 'number') {
@@ -215,7 +242,7 @@ export const ExploreMap = ({ onBack, onLogout, user, onViewTour, onOpenMyPhotos,
});
});
return Object.values(groups);
}, [publicPhotos]);
}, [publicPhotos, selectedPhotoFilterTags]);
const fetchPublicPhotos = async () => {
try {
@@ -464,6 +491,18 @@ export const ExploreMap = ({ onBack, onLogout, user, onViewTour, onOpenMyPhotos,
return Array.from(tagsSet);
}, [publicTours]);
// Tổng hợp nhãn từ danh sách ảnh công khai để lọc ảnh
const availablePhotoTags = React.useMemo(() => {
const tagsSet = new Set<string>();
publicPhotos.forEach(photo => {
const tags = photo.metadata?.tags as string[] | undefined;
if (Array.isArray(tags)) {
tags.forEach(tag => tagsSet.add(tag));
}
});
return Array.from(tagsSet).sort();
}, [publicPhotos]);
// State cho menu chuột phải chia sẻ
const [shareMenu, setShareMenu] = useState<{ x: number, y: number, id: string, title: string, canShare: boolean, isParticipant: boolean, hasPendingRequest: boolean } | null>(null);
@@ -634,28 +673,68 @@ export const ExploreMap = ({ onBack, onLogout, user, onViewTour, onOpenMyPhotos,
{/* Filter Dropdown Content */}
{isFilterDropdownOpen && (
<div className="absolute top-full left-0 mt-3 bg-white/90 backdrop-blur-md p-2 rounded-2xl shadow-xl border border-white/20 flex flex-col gap-2 max-w-[200px] z-[1003] animate-in slide-in-from-left-2 duration-200">
<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-col gap-1.5"> {/* Hiển thị tag theo chiều dọc */}
<button
onClick={() => { setSelectedFilterTag(null); setIsFilterDropdownOpen(false); }}
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 => (
<div className="absolute top-full left-0 mt-3 bg-white/90 backdrop-blur-md p-3 rounded-2xl shadow-xl border border-white/20 flex flex-col gap-3 max-w-[220px] z-[1003] animate-in slide-in-from-left-2 duration-200 max-h-[400px] overflow-y-auto">
{/* Tour Filter Section */}
<div>
<div className="flex items-center gap-2 px-2 py-1 border-b border-gray-100 mb-1.5">
<Filter className="w-3.5 h-3.5 text-blue-600" />
<span className="text-[10px] font-black uppercase text-gray-500 tracking-wider">🧳 Chuyến đi</span>
</div>
<div className="flex flex-col gap-1.5">
<button
key={tag}
onClick={() => { setSelectedFilterTag(tag === selectedFilterTag ? null : tag); setIsFilterDropdownOpen(false); }}
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'}`}
onClick={() => { setSelectedFilterTag(null); setIsFilterDropdownOpen(false); }}
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'}`}
>
{tag}
Tất cả
</button>
))}
{allFilterTags.map(tag => (
<button
key={tag}
onClick={() => { setSelectedFilterTag(tag === selectedFilterTag ? null : tag); setIsFilterDropdownOpen(false); }}
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>
{/* Photo Filter Section */}
{availablePhotoTags.length > 0 && (
<div className="border-t border-gray-200 pt-3">
<div className="flex items-center gap-2 px-2 py-1 border-b border-gray-100 mb-1.5">
<ImageIcon className="w-3.5 h-3.5 text-emerald-600" />
<span className="text-[10px] font-black uppercase text-gray-500 tracking-wider">📸 nh công khai</span>
</div>
<div className="flex flex-wrap gap-1.5">
<button
onClick={() => { setSelectedPhotoFilterTags([]); setIsFilterDropdownOpen(false); }}
className={`px-2 py-1 rounded-lg text-[9px] font-bold transition-all ${selectedPhotoFilterTags.length === 0 ? 'bg-emerald-600 text-white' : 'bg-gray-100 text-gray-500 hover:bg-gray-200'}`}
>
Tất cả
</button>
{availablePhotoTags.map(tagId => {
const tagLabel = PHOTO_TAG_LABELS[tagId] || tagId;
return (
<button
key={tagId}
onClick={() => {
setSelectedPhotoFilterTags(prev =>
prev.includes(tagId)
? prev.filter(t => t !== tagId)
: [...prev, tagId]
);
}}
className={`px-2 py-1 rounded-lg text-[9px] font-bold transition-all ${selectedPhotoFilterTags.includes(tagId) ? 'bg-emerald-600 text-white' : 'bg-gray-100 text-gray-500 hover:bg-gray-200'}`}
title={tagLabel}
>
{tagLabel.length > 13 ? tagLabel.substring(0, 13) + '...' : tagLabel}
</button>
);
})}
</div>
</div>
)}
</div>
)}
</div>