import React, { useState, useMemo, useEffect } from 'react'; import { useNavigate, useSearchParams } from 'react-router-dom'; import { Plus, Search, Archive, AlertTriangle, MoreVertical, ExternalLink, Edit, Download, Trash2, Calendar, Image, Activity } from 'lucide-react'; import { parseISO, differenceInDays } from 'date-fns'; import { toast } from 'react-toastify'; import { useLocalizedDate } from '../../hooks/useLocalizedDate'; import { Button, Input, Card, SkeletonTable, ErrorBoundary } from '../../components/common'; import { BulkArchiveModal } from '../../components/admin'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { eventsService } from '../../services/events.service'; import type { Event } from '../../types'; import { useTranslation } from 'react-i18next'; export const EventsListPage: React.FC = () => { const { t } = useTranslation(); const { format } = useLocalizedDate(); const navigate = useNavigate(); const queryClient = useQueryClient(); const [searchParams, setSearchParams] = useSearchParams(); const [searchTerm, setSearchTerm] = useState(''); const [selectedEvents, setSelectedEvents] = useState([]); // const [showFilters, setShowFilters] = useState(false); const [activeDropdown, setActiveDropdown] = useState(null); const [dropdownPosition, setDropdownPosition] = useState<{ top: number; left: number } | null>(null); const [showBulkArchiveModal, setShowBulkArchiveModal] = useState(false); // Get filter from URL const statusFilter = searchParams.get('filter') as 'active' | 'archived' | null; const isExpiringFilter = searchParams.get('filter') === 'expiring'; // Close dropdown when clicking outside useEffect(() => { const handleClickOutside = (event: MouseEvent) => { const target = event.target as HTMLElement; if (!target.closest('.dropdown-container')) { setActiveDropdown(null); setDropdownPosition(null); } }; if (activeDropdown !== null) { document.addEventListener('mousedown', handleClickOutside); return () => document.removeEventListener('mousedown', handleClickOutside); } }, [activeDropdown]); // Update dropdown position on scroll/resize useEffect(() => { const handleScrollOrResize = () => { if (activeDropdown !== null) { setActiveDropdown(null); setDropdownPosition(null); } }; window.addEventListener('scroll', handleScrollOrResize, true); window.addEventListener('resize', handleScrollOrResize); return () => { window.removeEventListener('scroll', handleScrollOrResize, true); window.removeEventListener('resize', handleScrollOrResize); }; }, [activeDropdown]); // Fetch events const { data, isLoading, error } = useQuery({ queryKey: ['admin-events', statusFilter], queryFn: () => eventsService.getEvents(1, 100, (statusFilter === 'archived' || statusFilter === 'active') ? statusFilter : undefined), }); // Archive mutation const archiveMutation = useMutation({ mutationFn: eventsService.archiveEvent, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['admin-events'] }); toast.success(t('toast.eventArchived')); }, onError: () => { toast.error(t('toast.saveError')); }, }); // Delete mutation const deleteMutation = useMutation({ mutationFn: eventsService.deleteEvent, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['admin-events'] }); toast.success(t('toast.deleteSuccess')); }, onError: () => { toast.error(t('toast.deleteError')); }, }); // Bulk archive mutation const bulkArchiveMutation = useMutation({ mutationFn: eventsService.bulkArchiveEvents, onSuccess: (data) => { queryClient.invalidateQueries({ queryKey: ['admin-events'] }); setSelectedEvents([]); setShowBulkArchiveModal(false); if (data.results.failed.length === 0) { toast.success(t('events.bulkArchiveSuccess', { count: data.results.successful.length })); } else { toast.warning(t('events.bulkArchivePartial', { success: data.results.successful.length, failed: data.results.failed.length })); } }, onError: () => { toast.error(t('toast.saveError')); }, }); // Filter and search events const filteredEvents = useMemo(() => { if (!data?.events) return []; let events = [...data.events]; // Apply status filter if (statusFilter === 'active') { events = events.filter(e => e.is_active && !e.is_archived); } else if (isExpiringFilter) { events = events.filter(e => { if (!e.is_active || e.is_archived) return false; const days = e.expires_at ? differenceInDays(parseISO(e.expires_at), new Date()) : 0; return days <= 7 && days > 0; }); } else if (statusFilter === 'archived') { events = events.filter(e => e.is_archived); } // Apply search if (searchTerm) { const term = searchTerm.toLowerCase(); events = events.filter(e => e.event_name.toLowerCase().includes(term) || e.event_type.toLowerCase().includes(term) || e.host_email.toLowerCase().includes(term) ); } // Sort by creation date (newest first) events.sort((a, b) => { const dateA = a.created_at ? new Date(a.created_at).getTime() : 0; const dateB = b.created_at ? new Date(b.created_at).getTime() : 0; return dateB - dateA; }); return events; }, [data?.events, statusFilter, searchTerm]); const handleSelectAll = () => { if (selectedEvents.length === filteredEvents.length) { setSelectedEvents([]); } else { setSelectedEvents(filteredEvents.map(e => e.id)); } }; const handleSelectEvent = (id: number) => { setSelectedEvents(prev => prev.includes(id) ? prev.filter(i => i !== id) : [...prev, id] ); }; const getEventStatus = (event: Event) => { if (event.is_archived) return { label: t('events.archived'), color: 'text-neutral-500 bg-neutral-100' }; if (!event.is_active) return { label: t('events.inactive'), color: 'text-red-600 bg-red-100' }; const days = event.expires_at ? differenceInDays(parseISO(event.expires_at), new Date()) : 0; if (days <= 0) return { label: t('events.expired'), color: 'text-red-600 bg-red-100' }; if (days <= 7) return { label: t('events.daysLeft', { count: days }), color: 'text-orange-600 bg-orange-100' }; return { label: t('events.active'), color: 'text-green-600 bg-green-100' }; }; if (isLoading) { return (

{t('events.title')}

{t('events.subtitle')}

); } if (error) { return (

{t('events.failedToLoadEvents')}

); } return (
{/* Page Header */}

{t('events.title')}

{t('events.subtitle')}

{/* Statistics Cards */}

{t('events.stats.totalEvents')}

{data?.events.length || 0}

{t('events.stats.activeEvents')}

{data?.events.filter(e => e.is_active && !e.is_archived).length || 0}

{t('events.stats.totalPhotos')}

{data?.events.reduce((sum, e) => sum + (e.photo_count || 0), 0) || 0}

{t('events.stats.expiringEvents')}

{data?.events.filter(e => { if (!e.is_active || e.is_archived) return false; const days = e.expires_at ? differenceInDays(parseISO(e.expires_at), new Date()) : 0; return days <= 7 && days > 0; }).length || 0}

{/* Filters and Search */}
{/* Search */}
} value={searchTerm} onChange={(e) => setSearchTerm(e.target.value)} />
{/* Filter Buttons */}
{/* Bulk Actions */} {selectedEvents.length > 0 && (
{t('events.eventsSelected', { count: selectedEvents.length })}
)}
{/* Events Table */}
{filteredEvents.length === 0 ? ( ) : ( filteredEvents.map((event) => { const status = getEventStatus(event); return ( ); }) )}
0} onChange={handleSelectAll} className="w-4 h-4 text-primary-600 border-neutral-300 rounded focus:ring-primary-500" /> {t('events.event')} {t('events.type')} {t('events.date')} {t('events.status')} {t('events.expires')} {t('events.actions')}
{t('events.noEventsFound')}
handleSelectEvent(event.id)} className="w-4 h-4 text-primary-600 border-neutral-300 rounded focus:ring-primary-500" />

{event.event_name}

{event.host_email}

{event.event_type} {event.event_date ? format(parseISO(event.event_date), 'MMM d, yyyy') : 'N/A'} {status.label} {event.expires_at ? format(parseISO(event.expires_at), 'MMM d, yyyy') : 'N/A'}
{activeDropdown === event.id && dropdownPosition && (
{event.share_link ? ( { setActiveDropdown(null); setDropdownPosition(null); }} > {t('events.viewGallery')} ) : null} {!event.is_archived ? ( ) : null} {event.is_archived ? ( ) : null}
)}
{/* Bulk Archive Modal */} setShowBulkArchiveModal(false)} onConfirm={() => bulkArchiveMutation.mutate(selectedEvents)} selectedEvents={filteredEvents.filter(e => selectedEvents.includes(e.id))} isLoading={bulkArchiveMutation.isPending} />
); }; EventsListPage.displayName = 'EventsListPage';