'use client'; import { useState, useEffect, useRef } from 'react'; import Link from 'next/link'; import { useRouter, useSearchParams } from 'next/navigation'; import { useLanguage } from '@/context/LanguageContext'; import { eventsApi, siteSettingsApi, Event } from '@/lib/api'; import Card from '@/components/ui/Card'; import Button from '@/components/ui/Button'; import { AdminPageSkeleton } from '@/components/ui/Skeleton'; import { MoreMenu, DropdownItem, AdminMobileStyles } from '@/components/admin/MobileComponents'; import { PlusIcon, PencilIcon, TrashIcon, EyeIcon, PhotoIcon, DocumentDuplicateIcon, ArchiveBoxIcon, StarIcon, LinkIcon, ChevronLeftIcon, ChevronRightIcon } from '@heroicons/react/24/outline'; import { StarIcon as StarIconSolid } from '@heroicons/react/24/solid'; import toast from 'react-hot-toast'; import clsx from 'clsx'; import { parseDate } from '@/lib/utils'; import EventFormModal from './_components/EventFormModal'; const PAGE_SIZE_OPTIONS = [10, 25, 50, 100]; function getPageNumbers(current: number, totalPages: number): (number | '...')[] { if (totalPages <= 7) return Array.from({ length: totalPages }, (_, i) => i + 1); const pages: (number | '...')[] = [1]; const start = Math.max(2, current - 1); const end = Math.min(totalPages - 1, current + 1); if (start > 2) pages.push('...'); for (let i = start; i <= end; i++) pages.push(i); if (end < totalPages - 1) pages.push('...'); pages.push(totalPages); return pages; } export default function AdminEventsPage() { const router = useRouter(); const { t, locale } = useLanguage(); const searchParams = useSearchParams(); const [events, setEvents] = useState([]); const [total, setTotal] = useState(0); const [page, setPage] = useState(1); const [pageSize, setPageSize] = useState(25); const [loading, setLoading] = useState(true); const [showForm, setShowForm] = useState(false); const [editingEvent, setEditingEvent] = useState(null); const [featuredEventId, setFeaturedEventId] = useState(null); const [settingFeatured, setSettingFeatured] = useState(null); useEffect(() => { loadFeaturedEvent(); }, []); useEffect(() => { loadEvents(); }, [page, pageSize]); // The ?edit= deep link may point at an event that is not on the current // page, so fall back to fetching it directly instead of only scanning the page. const handledEditId = useRef(null); useEffect(() => { const editId = searchParams.get('edit'); if (!editId || handledEditId.current === editId) return; const event = events.find(e => e.id === editId); if (event) { handledEditId.current = editId; handleEdit(event); return; } if (loading) return; handledEditId.current = editId; eventsApi.getById(editId) .then(({ event }) => handleEdit(event)) .catch(() => toast.error('Event not found')); }, [searchParams, events, loading]); const loadEvents = async () => { try { const { events, total } = await eventsApi.getAll({ page, pageSize }); setEvents(events); setTotal(total ?? events.length); // If the current page emptied out (e.g. after deleting its last event), // fall back to the new last page. if (events.length === 0 && (total ?? 0) > 0 && page > 1) { setPage(Math.max(1, Math.ceil((total ?? 0) / pageSize))); } } catch (error) { toast.error('Failed to load events'); } finally { setLoading(false); } }; const loadFeaturedEvent = async () => { try { const { settings } = await siteSettingsApi.get(); setFeaturedEventId(settings.featuredEventId || null); } catch (error) { // Ignore - settings may not exist yet } }; const handleSetFeatured = async (eventId: string | null) => { setSettingFeatured(eventId || 'clearing'); try { await siteSettingsApi.setFeaturedEvent(eventId); setFeaturedEventId(eventId); toast.success(eventId ? 'Event set as featured' : 'Featured event removed'); } catch (error: any) { toast.error(error.message || 'Failed to update featured event'); } finally { setSettingFeatured(null); } }; const handleEdit = (event: Event) => { setEditingEvent(event); setShowForm(true); }; const handleCloseForm = () => { setShowForm(false); setEditingEvent(null); }; const handleFormSaved = () => { handleCloseForm(); loadEvents(); }; const handleDelete = async (id: string) => { if (!confirm('Are you sure you want to delete this event?')) return; try { await eventsApi.delete(id); toast.success('Event deleted'); loadEvents(); } catch (error) { toast.error('Failed to delete event'); } }; const handleStatusChange = async (event: Event, status: Event['status']) => { try { await eventsApi.update(event.id, { status }); toast.success('Status updated'); loadEvents(); } catch (error) { toast.error('Failed to update status'); } }; const formatDate = (dateStr: string) => { return parseDate(dateStr).toLocaleDateString(locale === 'es' ? 'es-ES' : 'en-US', { month: 'short', day: 'numeric', year: 'numeric', timeZone: 'America/Asuncion', }); }; const isEventOver = (event: Event) => { const refDate = event.endDatetime || event.startDatetime; return new Date(refDate) < new Date(); }; const getStatusBadge = (status: string) => { const styles: Record = { draft: 'badge-gray', published: 'badge-success', unlisted: 'badge-warning', cancelled: 'badge-danger', completed: 'badge-info', archived: 'badge-gray', }; return {status}; }; const handleDuplicate = async (event: Event) => { try { await eventsApi.duplicate(event.id); toast.success('Event duplicated successfully'); loadEvents(); } catch (error) { toast.error('Failed to duplicate event'); } }; const handleArchive = async (event: Event) => { try { await eventsApi.update(event.id, { status: 'archived' }); toast.success('Event archived'); loadEvents(); } catch (error) { toast.error('Failed to archive event'); } }; if (loading) { return ; } return (

{t('admin.events.title')}

{/* Desktop: Table */}
{events.length === 0 ? ( ) : ( events.map((event) => ( router.push(`/admin/events/${event.id}`)} className={clsx("hover:bg-gray-50 cursor-pointer", featuredEventId === event.id && "bg-amber-50")} > )) )}
Event Date Capacity Status Actions
No events found. Create your first event!
{event.bannerUrl ? ( {event.title} ) : (
)}

{event.title}

{featuredEventId === event.id && ( Featured )}

{event.location}

{formatDate(event.startDatetime)} {(event.bookedCount || 0) + (event.claimedCount || 0)} / {event.capacity} {(event.claimedCount || 0) > 0 && ( {event.claimedCount} pending approval )}
{getStatusBadge(event.status)} {isEventOver(event) && event.status !== 'completed' && event.status !== 'cancelled' && event.status !== 'archived' && ( Over )}
e.stopPropagation()}>
{event.status === 'draft' && ( )} {event.status === 'published' && ( )} {(event.status === 'draft' || event.status === 'published') && ( handleStatusChange(event, 'unlisted')}> Make Unlisted )} {event.status === 'unlisted' && ( handleStatusChange(event, 'published')}> Make Public )} {(event.status === 'published' || event.status === 'unlisted') && ( handleStatusChange(event, 'draft')}> Unpublish )} handleDuplicate(event)}> Duplicate {event.status !== 'archived' && ( handleArchive(event)}> Archive )} handleDelete(event.id)} className="text-red-600"> Delete
{/* Mobile: Card List */}
{events.length === 0 ? (
No events found. Create your first event!
) : ( events.map((event) => ( router.push(`/admin/events/${event.id}`)} >
{event.bannerUrl ? ( {event.title} ) : (
)}

{event.title}

{formatDate(event.startDatetime)}

{event.location}

{getStatusBadge(event.status)} {isEventOver(event) && event.status !== 'completed' && event.status !== 'cancelled' && event.status !== 'archived' && ( Over )} {featuredEventId === event.id && ( )}

{(event.bookedCount || 0) + (event.claimedCount || 0)} / {event.capacity} spots {(event.claimedCount || 0) > 0 && ( ยท {event.claimedCount} pending )}

e.stopPropagation()}> handleEdit(event)}> Edit {event.status === 'draft' && ( handleStatusChange(event, 'published')}> Publish )} {(event.status === 'draft' || event.status === 'published') && ( handleStatusChange(event, 'unlisted')}> Make Unlisted )} {event.status === 'unlisted' && ( handleStatusChange(event, 'published')}> Make Public )} {(event.status === 'published' || event.status === 'unlisted') && ( handleStatusChange(event, 'draft')}> Unpublish )} {event.status === 'published' && ( handleSetFeatured(featuredEventId === event.id ? null : event.id)}> {featuredEventId === event.id ? 'Unfeature' : 'Set Featured'} )} handleDuplicate(event)}> Duplicate {event.status !== 'archived' && ( handleArchive(event)}> Archive )} handleDelete(event.id)} className="text-red-600"> Delete
)) )}
{/* Pagination */} {total > 0 && (
{(page - 1) * pageSize + 1}–{Math.min(page * pageSize, total)} of {total}
{getPageNumbers(page, Math.max(1, Math.ceil(total / pageSize))).map((p, i) => p === '...' ? ( ) : ( ) )}
)} {/* Mobile FAB */}
); }