import React, { useState, useEffect } from 'react'; import { Download, Maximize2, Check, Package } from 'lucide-react'; import { useInView } from 'react-intersection-observer'; import { toast as toastify } from 'react-toastify'; import { useTranslation } from 'react-i18next'; import type { Photo } from '../../types'; import { useDownloadPhoto } from '../../hooks/useGallery'; import { PhotoLightbox } from './PhotoLightbox'; import { Button, AuthenticatedImage } from '../common'; import { galleryService } from '../../services/gallery.service'; import { analyticsService } from '../../services/analytics.service'; interface PhotoGridProps { photos: Photo[]; slug: string; categoryId?: number | null; } export const PhotoGrid: React.FC = ({ photos, slug, categoryId }) => { const { t } = useTranslation(); const [selectedPhotoIndex, setSelectedPhotoIndex] = useState(null); const [selectedPhotos, setSelectedPhotos] = useState>(new Set()); const [isSelectionMode, setIsSelectionMode] = useState(false); const downloadPhotoMutation = useDownloadPhoto(); // Clear selection when category changes useEffect(() => { setSelectedPhotos(new Set()); }, [categoryId]); const handlePhotoClick = (index: number, e?: React.MouseEvent) => { // Check for ctrl/cmd+click for quick selection if (e && (e.ctrlKey || e.metaKey)) { if (!isSelectionMode) { setIsSelectionMode(true); } const newSelected = new Set(selectedPhotos); if (newSelected.has(photos[index].id)) { newSelected.delete(photos[index].id); } else { newSelected.add(photos[index].id); } setSelectedPhotos(newSelected); } else if (isSelectionMode) { const newSelected = new Set(selectedPhotos); if (newSelected.has(photos[index].id)) { newSelected.delete(photos[index].id); } else { newSelected.add(photos[index].id); } setSelectedPhotos(newSelected); } else { setSelectedPhotoIndex(index); } }; const handleDownload = (photo: Photo, e: React.MouseEvent) => { e.stopPropagation(); // Track individual photo download analyticsService.trackDownload(photo.id, slug, false); downloadPhotoMutation.mutate({ slug, photoId: photo.id, filename: photo.filename, }); }; const toggleSelectionMode = () => { setIsSelectionMode(!isSelectionMode); setSelectedPhotos(new Set()); }; const selectAll = () => { setSelectedPhotos(new Set(photos.map(p => p.id))); }; const deselectAll = () => { setSelectedPhotos(new Set()); }; const handleDownloadSelected = async () => { if (selectedPhotos.size === 0) return; const selectedPhotosList = photos.filter(p => selectedPhotos.has(p.id)); toastify.info(t('gallery.downloading', { count: selectedPhotos.size })); // Download each selected photo const downloadPromises = selectedPhotosList.map(photo => galleryService.downloadPhoto(slug, photo.id, photo.filename) .catch(err => { // Download failed - error handled by UI return null; }) ); try { await Promise.all(downloadPromises); toastify.success(t('gallery.downloadedPhotos', { count: selectedPhotos.size })); // Track bulk download analyticsService.trackGalleryEvent('bulk_download', { gallery: slug, photo_count: selectedPhotos.size }); // Clear selection after download setSelectedPhotos(new Set()); setIsSelectionMode(false); } catch (error) { toastify.error(t('gallery.downloadError')); } }; if (photos.length === 0) { return (

{t('gallery.noPhotosFound')}

); } return ( <> {/* Selection Mode Controls */} {photos.length > 1 && (
{!isSelectionMode && ( )}
{isSelectionMode && (
{t('gallery.photosSelected', { count: selectedPhotos.size })}
{selectedPhotos.size > 0 && ( )}
)}
)} {/* Photo Grid */}
{photos.map((photo, index) => ( handlePhotoClick(index, e)} onDownload={(e) => handleDownload(photo, e)} /> ))}
{/* Lightbox */} {selectedPhotoIndex !== null && ( setSelectedPhotoIndex(null)} slug={slug} /> )} ); }; interface PhotoThumbnailProps { photo: Photo; isSelected: boolean; isSelectionMode: boolean; onClick: (e: React.MouseEvent) => void; onDownload: (e: React.MouseEvent) => void; } const PhotoThumbnail: React.FC = ({ photo, isSelected, isSelectionMode, onClick, onDownload, }) => { const { ref, inView } = useInView({ triggerOnce: true, threshold: 0.1, }); return (
onClick(e)} > {inView ? ( <> {/* Overlay on hover/tap - Always visible on mobile for better UX */}
{!isSelectionMode && ( <> )}
{/* Selection checkbox - Larger on mobile for easier tapping */} {isSelectionMode && (
{isSelected && }
)} {/* Photo type badge */} {photo.type === 'collage' && (
Collage
)} ) : (
)}
); };