import { useEffect, useState, useMemo } from 'react'; import { ChevronLeft, Image as ImageIcon, Download, Calendar, MapPin, Loader2, Filter, X, Trash2, Edit, Heart } from 'lucide-react'; import { useNotification } from '@/hooks/useNotification'; import { useConfirm } from '@/hooks/useConfirm'; import { CoordinateSelectModal } from '../components/CoordinateSelectModal'; export const MyPhotosPage = ({ onBack }: { onBack: () => void }) => { const [photos, setPhotos] = useState([]); const [isLoading, setIsLoading] = useState(true); const [selectedTourIdForPhoto, setSelectedTourIdForPhoto] = useState('all'); // Changed from filterTourId const [filterDate, setFilterDate] = useState(''); const [selectedPhotoForDisplay, setSelectedPhotoForDisplay] = useState(null); // New state for large photo display const [sortOrder, setSortOrder] = useState<'newest' | 'oldest'>('newest'); // 'newest' by default const notify = useNotification(); const confirm = useConfirm(); const [isEditing, setIsEditing] = useState(false); const [editTitle, setEditTitle] = useState(''); const [editDescription, setEditDescription] = useState(''); const [editLat, setEditLat] = useState(''); const [editLng, setEditLng] = useState(''); const [isSavingEdit, setIsSavingEdit] = useState(false); const [isMapOpen, setIsMapOpen] = useState(false); const [resolvedAddress, setResolvedAddress] = useState(''); const [isFullscreen, setIsFullscreen] = useState(false); useEffect(() => { const lat = selectedPhotoForDisplay?.metadata?.lat; const lng = selectedPhotoForDisplay?.metadata?.lng; if (typeof lat === 'number' && typeof lng === 'number') { setResolvedAddress('Đang xác định địa điểm...'); fetch(`https://nominatim.openstreetmap.org/reverse?format=json&lat=${lat}&lon=${lng}&accept-language=vi`) .then(res => { if (!res.ok) throw new Error(); return res.json(); }) .then(data => { if (data && data.display_name) { const shortAddress = data.display_name.split(',').slice(0, 3).join(',').trim(); setResolvedAddress(shortAddress || data.display_name); } else { setResolvedAddress(`${lat.toFixed(4)}, ${lng.toFixed(4)}`); } }) .catch(() => { setResolvedAddress(`${lat.toFixed(4)}, ${lng.toFixed(4)}`); }); } else { setResolvedAddress('Chưa xác định tọa độ'); } }, [selectedPhotoForDisplay?.id, selectedPhotoForDisplay?.metadata?.lat, selectedPhotoForDisplay?.metadata?.lng]); useEffect(() => { if (selectedPhotoForDisplay) { setEditTitle(selectedPhotoForDisplay.metadata?.title || ''); setEditDescription(selectedPhotoForDisplay.metadata?.description || ''); setEditLat(selectedPhotoForDisplay.metadata?.lat ?? ''); setEditLng(selectedPhotoForDisplay.metadata?.lng ?? ''); setIsEditing(false); } }, [selectedPhotoForDisplay]); const handleSaveEdit = async () => { if (editLat !== '' && (isNaN(editLat) || editLat < -90 || editLat > 90)) { notify({ title: 'Lỗi', message: 'Vĩ độ không hợp lệ (-90 đến 90).', type: 'error' }); return; } if (editLng !== '' && (isNaN(editLng) || editLng < -180 || editLng > 180)) { notify({ title: 'Lỗi', message: 'Kinh độ không hợp lệ (-180 đến 180).', type: 'error' }); return; } setIsSavingEdit(true); try { const response = await fetch(`/api/v1/photos/${selectedPhotoForDisplay.id}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${localStorage.getItem('token') || localStorage.getItem('guest_token')}` }, body: JSON.stringify({ title: editTitle, description: editDescription, latitude: editLat === '' ? undefined : editLat, longitude: editLng === '' ? undefined : editLng }) }); if (!response.ok) throw new Error('Failed to update photo info'); const updatedPhoto = await response.json(); notify({ title: 'Thành công', message: 'Thông tin ảnh đã được cập nhật.', type: 'success' }); // Update photos list setPhotos(prev => prev.map(p => p.id === updatedPhoto.id ? { ...p, metadata: updatedPhoto.metadata } : p)); // Update selectedPhotoForDisplay setSelectedPhotoForDisplay((prev: any) => prev ? ({ ...prev, metadata: updatedPhoto.metadata }) : null); setIsEditing(false); } catch (error) { notify({ title: 'Lỗi', message: 'Không thể cập nhật thông tin ảnh. Vui lòng thử lại.', type: 'error' }); } finally { setIsSavingEdit(false); } }; const currentUser = useMemo(() => { const userStr = localStorage.getItem('user') || localStorage.getItem('guest_user'); if (!userStr) return null; try { return JSON.parse(userStr); } catch (e) { return null; } }, []); const likedUserIds = selectedPhotoForDisplay && selectedPhotoForDisplay.metadata && Array.isArray(selectedPhotoForDisplay.metadata.likedUserIds) ? selectedPhotoForDisplay.metadata.likedUserIds : []; const isLiked = currentUser && likedUserIds.includes(currentUser.id); const likeCount = likedUserIds.length; const handleToggleLike = async () => { if (!selectedPhotoForDisplay) return; const token = localStorage.getItem('token') || localStorage.getItem('guest_token'); if (!token) return; try { const response = await fetch(`/api/v1/photos/${selectedPhotoForDisplay.id}/toggle-like`, { method: 'POST', headers: { 'Authorization': `Bearer ${token}` } }); if (response.ok) { const updatedPhoto = await response.json(); setPhotos(prev => prev.map(p => p.id === updatedPhoto.id ? { ...p, metadata: updatedPhoto.metadata } : p)); setSelectedPhotoForDisplay((prev: any) => prev ? ({ ...prev, metadata: updatedPhoto.metadata }) : null); } } catch (error) { console.error('Lỗi khi thích ảnh:', error); } }; useEffect(() => { const fetchPhotos = async () => { try { const response = await fetch('/api/v1/users/me/photos', { headers: { 'Authorization': `Bearer ${localStorage.getItem('token') || localStorage.getItem('guest_token')}` } }); if (!response.ok) throw new Error('Failed to fetch photos'); const data = await response.json(); setPhotos(data); } catch (error) { notify({ title: 'Lỗi', message: 'Không thể tải danh sách ảnh.', type: 'error' }); } finally { setIsLoading(false); } }; fetchPhotos(); }, []); // Lấy danh sách các Tour duy nhất để hiển thị trong bộ lọc const toursWithPhotos = useMemo(() => { const tourMap = new Map(); photos.forEach(p => { if (p.tourId && p.tour) { tourMap.set(p.tourId, { id: p.tourId, title: p.tour.title }); } }); return Array.from(tourMap.values()); }, [photos]); // Logic lọc ảnh tại Frontend const filteredPhotos = useMemo(() => { let photosToFilter = photos.filter(p => { const matchTour = selectedTourIdForPhoto === 'all' || p.tourId === selectedTourIdForPhoto; // So sánh ngày định dạng YYYY-MM-DD const photoDate = p.capturedAt ? p.capturedAt.split('T')[0] : ''; const matchDate = !filterDate || photoDate === filterDate; return matchTour && matchDate; }); let sortedPhotos = photosToFilter; // Sắp xếp ảnh if (sortOrder === 'newest') { sortedPhotos.sort((a, b) => new Date(b.capturedAt).getTime() - new Date(a.capturedAt).getTime()); } else { // 'oldest' sortedPhotos.sort((a, b) => new Date(a.capturedAt).getTime() - new Date(b.capturedAt).getTime()); } return sortedPhotos; }, [photos, selectedTourIdForPhoto, filterDate, sortOrder]); // Effect để thiết lập ảnh được chọn hiển thị hoặc reset nếu ảnh hiện tại không còn trong danh sách lọc useEffect(() => { if (filteredPhotos.length > 0 && (!selectedPhotoForDisplay || !filteredPhotos.some(p => p.id === selectedPhotoForDisplay.id))) { setSelectedPhotoForDisplay(filteredPhotos[0]); } else if (filteredPhotos.length === 0) { setSelectedPhotoForDisplay(null); } else if (selectedPhotoForDisplay) { // Nếu ảnh đang chọn vẫn còn trong danh sách lọc, không làm gì cả } else { // Nếu không có ảnh nào để hiển thị setSelectedPhotoForDisplay(null); // No photos to display } }, [filteredPhotos, selectedPhotoForDisplay]); const handleDeletePhoto = async (photoId: string) => { const isConfirmed = await confirm({ title: 'Xóa ảnh này?', message: 'Bạn có chắc chắn muốn xóa ảnh này không? Hành động này không thể hoàn tác.' }); if (!isConfirmed) return; try { const response = await fetch(`/api/v1/photos/${photoId}`, { method: 'DELETE', headers: { 'Authorization': `Bearer ${localStorage.getItem('token') || localStorage.getItem('guest_token')}` } }); if (!response.ok) throw new Error('Failed to delete photo'); notify({ title: 'Thành công', message: 'Ảnh đã được xóa.', type: 'success' }); // Cập nhật lại danh sách ảnh sau khi xóa setPhotos(prev => prev.filter(p => p.id !== photoId)); setSelectedPhotoForDisplay(null); // Reset ảnh đang hiển thị } catch (error) { notify({ title: 'Lỗi', message: 'Không thể xóa ảnh. Vui lòng thử lại.', type: 'error' }); } }; return (
{/* Header */}

Ảnh của tôi

Kho lưu trữ ảnh gốc cá nhân

{/* Filter Bar - Thanh công cụ lọc */}
Bộ lọc:
{/* Chỉ báo Tour hiện tại */}
{selectedTourIdForPhoto === 'all' ? 'Tất cả hành trình' : toursWithPhotos.find(t => t.id === selectedTourIdForPhoto)?.title || 'Tour đã chọn'}
{/* Lọc theo Thời gian */}
setFilterDate(e.target.value)} className="pl-3 pr-3 py-2 bg-gray-50 border border-gray-100 rounded-xl text-xs font-bold outline-none focus:ring-2 focus:ring-blue-500 transition-all text-gray-700 cursor-pointer" />
{/* Lọc theo Sắp xếp */}
{/* Reset Filters - Nút xóa nhanh lọc */} {(selectedTourIdForPhoto !== 'all' || filterDate) && ( )}

Kết quả: {filteredPhotos.length} / {photos.length} ảnh

{isLoading ? (

Đang tải kho ảnh...

) : (
{/* Left Column: Tour List */}

Tour của bạn

{toursWithPhotos.map(tour => ( ))}
{/* Right Column: Large Photo Display */}
{selectedPhotoForDisplay ? (
Selected Photo setIsFullscreen(true)} /> {/* Overlays (Only show when not editing) */} {!isEditing && ( <> {/* Like (Heart) button overlay */} {/* Delete button overlay */} {/* Bottom metadata details gradient panel overlay */}
{selectedPhotoForDisplay.metadata?.title ? (

{selectedPhotoForDisplay.metadata.title}

) : ( Chưa có tiêu đề )} {selectedPhotoForDisplay.metadata?.description ? (

{selectedPhotoForDisplay.metadata.description}

) : ( Chưa có mô tả )}
{/* Edit Button Overlay */}
{/* Additional metadata info inside bottom overlay */}
Ngày chụp: {new Date(selectedPhotoForDisplay.capturedAt).toLocaleDateString('vi-VN', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' })}
Địa điểm: {resolvedAddress}
{selectedPhotoForDisplay.tour?.title && (
Hành trình: {selectedPhotoForDisplay.tour.title}
)}
{selectedPhotoForDisplay.originalUrl && ( Tải ảnh gốc )}
)}
{isEditing && (

Chỉnh sửa thông tin ảnh

setEditTitle(e.target.value)} placeholder="Nhập tiêu đề..." className="w-full bg-white border border-gray-200 text-gray-800 rounded-xl px-3 py-2 text-xs focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-transparent" />