diff --git a/backend/uploads/members/0d1d5413-0757-4c91-ad2d-da590808bbac/originals/1781619362591-585044778.jpg b/backend/uploads/members/0d1d5413-0757-4c91-ad2d-da590808bbac/originals/1781619362591-585044778.jpg new file mode 100644 index 0000000..08c133b Binary files /dev/null and b/backend/uploads/members/0d1d5413-0757-4c91-ad2d-da590808bbac/originals/1781619362591-585044778.jpg differ diff --git a/backend/uploads/tours/1781619362591-585044778.jpg b/backend/uploads/tours/1781619362591-585044778.jpg new file mode 100644 index 0000000..e3430d9 Binary files /dev/null and b/backend/uploads/tours/1781619362591-585044778.jpg differ diff --git a/frontend/src/pages/MyPhotosPage.tsx b/frontend/src/pages/MyPhotosPage.tsx index 59cb185..b5b43b9 100644 --- a/frontend/src/pages/MyPhotosPage.tsx +++ b/frontend/src/pages/MyPhotosPage.tsx @@ -6,9 +6,9 @@ import { useConfirm } from '@/hooks/useConfirm'; export const MyPhotosPage = ({ onBack }: { onBack: () => void }) => { const [photos, setPhotos] = useState([]); const [isLoading, setIsLoading] = useState(true); - const [filterTourId, setFilterTourId] = useState(''); + const [selectedTourIdForPhoto, setSelectedTourIdForPhoto] = useState('all'); // Changed from filterTourId const [filterDate, setFilterDate] = useState(''); - const [selectedPhoto, setSelectedPhoto] = useState(null); // State cho lightbox + 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(); @@ -34,7 +34,7 @@ export const MyPhotosPage = ({ onBack }: { onBack: () => void }) => { }, []); // Lấy danh sách các Tour duy nhất để hiển thị trong bộ lọc - const uniqueTours = useMemo(() => { + const toursWithPhotos = useMemo(() => { const tourMap = new Map(); photos.forEach(p => { if (p.tourId && p.tour) { @@ -46,14 +46,14 @@ export const MyPhotosPage = ({ onBack }: { onBack: () => void }) => { // Logic lọc ảnh tại Frontend const filteredPhotos = useMemo(() => { - let sortedPhotos = photos.filter(p => { - const matchTour = !filterTourId || p.tourId === filterTourId; + 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()); @@ -61,7 +61,21 @@ export const MyPhotosPage = ({ onBack }: { onBack: () => void }) => { sortedPhotos.sort((a, b) => new Date(a.capturedAt).getTime() - new Date(b.capturedAt).getTime()); } return sortedPhotos; - }, [photos, filterTourId, filterDate, sortOrder]); + }, [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({ @@ -84,18 +98,12 @@ export const MyPhotosPage = ({ onBack }: { onBack: () => void }) => { 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)); - setSelectedPhoto(null); // Đóng lightbox + 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' }); } }; - // Đóng lightbox khi nhấn ESC - useEffect(() => { - const handleEsc = (event: KeyboardEvent) => event.key === 'Escape' && setSelectedPhoto(null); - window.addEventListener('keydown', handleEsc); - return () => window.removeEventListener('keydown', handleEsc); - }, []); return (
@@ -116,21 +124,11 @@ export const MyPhotosPage = ({ onBack }: { onBack: () => void }) => { Bộ lọc:
- - {/* Lọc theo Tour */} -
- -
- + + {/* 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'}
@@ -161,9 +159,9 @@ export const MyPhotosPage = ({ onBack }: { onBack: () => void }) => { {/* Reset Filters - Nút xóa nhanh lọc */} - {(filterTourId || filterDate) && ( + {(selectedTourIdForPhoto !== 'all' || filterDate) && (
- ) : filteredPhotos.length > 0 ? ( -
- {filteredPhotos.map((photo) => ( -
setSelectedPhoto(photo)} - className="group relative bg-white rounded-3xl overflow-hidden shadow-sm border border-gray-100 transition-all hover:shadow-xl hover:-translate-y-1 cursor-pointer" - > - {/* Image Preview */} - - - {/* Info */} -
-
- - - {photo.tour?.title || 'Không rõ hành trình'} - -
-
- - - {new Date(photo.capturedAt).toLocaleDateString('vi-VN')} - -
+ ) : ( +
+
+ {/* Left Column: Tour List */} +
+

Tour của bạn

+
+ + {toursWithPhotos.map(tour => ( + + ))}
- ))} + + {/* Right Column: Large Photo Display */} +
+ {selectedPhotoForDisplay ? ( +
+ Selected Photo + + +
+
+
+ + {selectedPhotoForDisplay.tour?.title || 'Không rõ hành trình'} +
+
+ + {new Date(selectedPhotoForDisplay.capturedAt).toLocaleDateString('vi-VN', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' })} +
+
+ + {selectedPhotoForDisplay.originalUrl && ( + + Tải xuống ảnh gốc + + )} +
+
+ ) : ( +
+ +

Chọn một tấm ảnh để xem

+
+ )} +
+
+ + {/* Bottom Row: Thumbnails */} +
+
+

Kho ảnh ({filteredPhotos.length})

+
+
+ {filteredPhotos.map((photo: any) => ( +
setSelectedPhotoForDisplay(photo)} + className={`aspect-square bg-gray-100 rounded-xl overflow-hidden relative group border-2 transition-all cursor-pointer ${ + selectedPhotoForDisplay?.id === photo.id ? 'border-blue-500 scale-[0.98]' : 'border-transparent hover:border-blue-200' + }`} + > + thumbnail +
+ ))} +
+
- ) : ( + ) } +

Chưa có ảnh nào

Hãy tham gia các chuyến đi và lưu lại khoảnh khắc nhé!

- )} + {}
- - {/* Lightbox - Xem ảnh toàn màn hình */} - {selectedPhoto && ( -
setSelectedPhoto(null)} - > - - - {/* Nút xóa ảnh */} - - -
e.stopPropagation()}> - Fullscreen view - -
-

{selectedPhoto.tour?.title || 'Không rõ hành trình'}

-

{new Date(selectedPhoto.capturedAt).toLocaleDateString('vi-VN', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' })}

- - {selectedPhoto.originalUrl && ( - - - Tải xuống ảnh gốc - - )} -
-
-
- )}
); }; \ No newline at end of file