import React, { useState, useEffect, useRef } from 'react'; import { Link } from 'react-router-dom'; import { useAuth } from '../../context/AuthContext'; import api from '../../utils/api'; import { Card } from '../../components/ui/card'; import { Button } from '../../components/ui/button'; import { Input } from '../../components/ui/input'; import { Label } from '../../components/ui/label'; import { Textarea } from '../../components/ui/textarea'; import { Badge } from '../../components/ui/badge'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '../../components/ui/select'; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from '../../components/ui/dialog'; import { Upload, Trash2, Edit, X, ImageIcon, Calendar, MapPin, AlertCircle } from 'lucide-react'; import { toast } from 'sonner'; import moment from 'moment'; const AdminGallery = () => { const { hasPermission } = useAuth(); const [events, setEvents] = useState([]); const [selectedEvent, setSelectedEvent] = useState(null); const [galleryImages, setGalleryImages] = useState([]); const [loading, setLoading] = useState(true); const [uploading, setUploading] = useState(false); const [editingCaption, setEditingCaption] = useState(null); const [newCaption, setNewCaption] = useState(''); const [maxFileSize, setMaxFileSize] = useState(5242880); // Default 5MB const fileInputRef = useRef(null); useEffect(() => { fetchEvents(); fetchConfigLimits(); }, []); useEffect(() => { if (selectedEvent) { fetchGallery(selectedEvent); } }, [selectedEvent]); const fetchEvents = async () => { try { const response = await api.get('/admin/events'); setEvents(response.data); } catch (error) { console.error('Failed to fetch events:', error); toast.error('Failed to load events'); } finally { setLoading(false); } }; const fetchGallery = async (eventId) => { try { const response = await api.get(`/events/${eventId}/gallery`); setGalleryImages(response.data); } catch (error) { console.error('Failed to fetch gallery:', error); toast.error('Failed to load gallery'); } }; const fetchConfigLimits = async () => { try { const response = await api.get('/config/limits'); setMaxFileSize(response.data.max_file_size_bytes); } catch (error) { console.error('Failed to fetch config limits:', error); // Keep default value (5MB) if fetch fails } }; const formatFileSize = (bytes) => { if (bytes < 1024) return bytes + ' B'; else if (bytes < 1048576) return (bytes / 1024).toFixed(1) + ' KB'; else return (bytes / 1048576).toFixed(1) + ' MB'; }; const handleFileSelect = async (e) => { const files = Array.from(e.target.files); if (files.length === 0) return; setUploading(true); try { for (const file of files) { const formData = new FormData(); formData.append('file', file); await api.post(`/admin/events/${selectedEvent}/gallery`, formData, { headers: { 'Content-Type': 'multipart/form-data' } }); } toast.success(`${files.length} ${files.length === 1 ? 'image' : 'images'} uploaded successfully`); await fetchGallery(selectedEvent); } catch (error) { console.error('Failed to upload images:', error); toast.error(error.response?.data?.detail || 'Failed to upload images'); } finally { setUploading(false); if (fileInputRef.current) { fileInputRef.current.value = ''; } } }; const handleDeleteImage = async (imageId) => { if (!window.confirm('Are you sure you want to delete this image?')) { return; } try { await api.delete(`/admin/event-gallery/${imageId}`); toast.success('Image deleted successfully'); await fetchGallery(selectedEvent); } catch (error) { console.error('Failed to delete image:', error); toast.error('Failed to delete image'); } }; const handleUpdateCaption = async () => { if (!editingCaption) return; try { await api.put(`/admin/event-gallery/${editingCaption.id}`, null, { params: { caption: newCaption } }); toast.success('Caption updated successfully'); setEditingCaption(null); setNewCaption(''); await fetchGallery(selectedEvent); } catch (error) { console.error('Failed to update caption:', error); toast.error('Failed to update caption'); } }; const openEditCaption = (image) => { setEditingCaption(image); setNewCaption(image.caption || ''); }; return (
Upload and manage photos for event galleries
You need to create an event before uploading gallery images. Events help organize photos by occasion.
You can select multiple images. Max {formatFileSize(maxFileSize)} per image.
{image.caption}
{formatFileSize(image.file_size_bytes)}
Upload images to create a gallery for this event.