import React, { useEffect, useState } from 'react'; import { X, Image as ImageIcon, Loader2, Download, Eye, MapPin, Tag, Trash2, Edit2, Check } from 'lucide-react'; import { useNotification } from '@/hooks/useNotification'; import { useConfirm } from '@/hooks/useConfirm'; interface PhotoGalleryModalProps { isOpen: boolean; onClose: () => void; user: any; } const PHOTO_TAGS = [ { value: 'phong-canh', label: '🏞️ Phong cảnh' }, { value: 'con-nguoi', label: '👥 Con người' }, { value: 'doi-thuong', label: '🎒 Đời thường' }, { value: 'bien', label: '🌊 Biển' }, { value: 'nui', label: '⛰️ Núi' }, { value: 'do-thi', label: '🏙️ Đô thị' }, { value: 'thuc-an', label: '🍜 Thức ăn' }, { value: 'cho', label: '🛍️ Chợ' }, { value: 'hien-dai', label: '🏗️ Hiện đại' }, { value: 'dong-vat', label: '🦁 Động vật' }, { value: 'thu-cung', label: '🐕 Thú cưng' } ]; export const PhotoGalleryModal: React.FC = ({ isOpen, onClose, user, }) => { const notify = useNotification(); const confirm = useConfirm(); const [photos, setPhotos] = useState([]); const [isLoading, setIsLoading] = useState(false); // Filtering states const [filterTourId, setFilterTourId] = useState('all'); const [filterTag, setFilterTag] = useState('all'); // Preview / Editor States const [selectedPhoto, setSelectedPhoto] = useState(null); const [editingPhotoId, setEditingPhotoId] = useState(null); const [editTitle, setEditTitle] = useState(''); const [editDescription, setEditDescription] = useState(''); const [editTags, setEditTags] = useState([]); const [isSaving, setIsSaving] = useState(false); const [isDeletingId, setIsDeletingId] = useState(null); const getHeaders = () => ({ 'Authorization': `Bearer ${localStorage.getItem('token')}`, }); const loadPhotos = async () => { setIsLoading(true); try { const res = await fetch('/api/v1/users/me/photos', { headers: getHeaders() }); if (res.ok) { const data = await res.json(); setPhotos(data || []); } else { throw new Error('Không thể tải thư viện ảnh.'); } } catch (e: any) { console.error(e); notify({ title: 'Lỗi', message: e.message || 'Không thể tải ảnh.', type: 'error', }); } finally { setIsLoading(false); } }; useEffect(() => { if (isOpen && user) { loadPhotos(); } }, [isOpen, user]); if (!isOpen || !user) return null; // Extract unique tours from photos list for dropdown filtering const uniqueToursMap = new Map(); photos.forEach(p => { if (p.tour?.id && p.tour?.title) { uniqueToursMap.set(p.tour.id, p.tour.title); } }); const uniqueTours = Array.from(uniqueToursMap.entries()).map(([id, title]) => ({ id, title })); // Handle Photo Deletion const handleDelete = async (photoId: string) => { const ok = await confirm({ title: 'Xóa ảnh?', message: 'Bạn có chắc chắn muốn xóa bức ảnh này không? Ảnh sẽ được chuyển vào thùng rác.', }); if (!ok) return; setIsDeletingId(photoId); try { const res = await fetch(`/api/v1/photos/${photoId}`, { method: 'DELETE', headers: getHeaders(), }); if (res.ok) { notify({ title: 'Thành công', message: 'Đã xóa ảnh thành công.', type: 'success', }); setPhotos(prev => prev.filter(p => p.id !== photoId)); if (selectedPhoto?.id === photoId) setSelectedPhoto(null); } else { throw new Error('Xóa ảnh thất bại.'); } } catch (err: any) { notify({ title: 'Lỗi', message: err.message || 'Không thể xóa ảnh.', type: 'error', }); } finally { setIsDeletingId(null); } }; // Start Editing Tag details const startEdit = (photo: any) => { setEditingPhotoId(photo.id); const meta = photo.metadata || {}; setEditTitle(meta.title || ''); setEditDescription(meta.description || ''); setEditTags(meta.tags || []); }; const handleTagToggle = (tagValue: string) => { setEditTags(prev => prev.includes(tagValue) ? prev.filter(t => t !== tagValue) : [...prev, tagValue] ); }; // Save Tagging Details const saveEdit = async (photoId: string) => { setIsSaving(true); try { const res = await fetch(`/api/v1/photos/${photoId}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${localStorage.getItem('token')}`, }, body: JSON.stringify({ title: editTitle, description: editDescription, tags: editTags, }), }); if (res.ok) { notify({ title: 'Thành công', message: 'Cập nhật thông tin ảnh thành công.', type: 'success', }); // Reload photos list to synchronize await loadPhotos(); setEditingPhotoId(null); } else { throw new Error('Lỗi cập nhật ảnh.'); } } catch (err: any) { notify({ title: 'Lỗi', message: err.message || 'Cập nhật thất bại.', type: 'error', }); } finally { setIsSaving(false); } }; const handleDownload = async (url: string, filename: string) => { try { const response = await fetch(url); const blob = await response.blob(); const blobUrl = window.URL.createObjectURL(blob); const link = document.createElement('a'); link.href = blobUrl; link.download = filename; document.body.appendChild(link); link.click(); document.body.removeChild(link); window.URL.revokeObjectURL(blobUrl); } catch (e) { console.error(e); notify({ title: 'Lỗi tải về', message: 'Không thể tải trực tiếp ảnh xuống thiết bị.', type: 'error', }); } }; // Filter photos const filteredPhotos = photos.filter((photo) => { const matchTour = filterTourId === 'all' || photo.tour?.id === filterTourId; const matchTag = filterTag === 'all' || photo.metadata?.tags?.includes(filterTag); return matchTour && matchTag; }); return (
{/* Header */}
Thư viện ảnh
{/* Dual Filter Header Select Pinned Matrix */}
{/* Content body grid workspace */}
{isLoading ? (
Đang tải thư viện ảnh...
) : filteredPhotos.length === 0 ? (
Không tìm thấy bức ảnh nào

Không có ảnh nào khớp với bộ lọc hiện tại.

) : (
{filteredPhotos.map((photo) => { const photoTagsList = photo.metadata?.tags || []; return (
Gallery Item {/* Standard tags badge dot count */} {photoTagsList.length > 0 && (
{photoTagsList.length} tags
)} {/* Hover controls overlay */}
{photo.metadata?.title || 'Chưa đặt tiêu đề'}
{photo.tour?.title && (
{photo.tour.title}
)}
); })}
)}
{/* Editor & Tag modification Dialog */} {editingPhotoId && (
Chỉnh sửa thông tin ảnh
setEditTitle(e.target.value)} placeholder="Ví dụ: Hoàng hôn biển Ba Động..." className="bg-slate-950 border border-slate-805 rounded-xl p-2.5 text-white focus:outline-none focus:border-indigo-650" />