import React, { useState, useEffect } from 'react'; import { useParams, useNavigate, Link } from 'react-router-dom'; import api from '../../utils/api'; import { Card } from '../../components/ui/card'; import { Button } from '../../components/ui/button'; import { Input } from '../../components/ui/input'; import { Badge } from '../../components/ui/badge'; import { Checkbox } from '../../components/ui/checkbox'; import { ArrowLeft, Calendar, MapPin, Download, Check, X, Search, Users, UserCheck, UserX, HelpCircle } from 'lucide-react'; import { toast } from 'sonner'; import moment from 'moment'; const AdminEventAttendance = () => { const { eventId } = useParams(); const navigate = useNavigate(); const [event, setEvent] = useState(null); const [rsvps, setRsvps] = useState([]); const [filteredRsvps, setFilteredRsvps] = useState([]); const [loading, setLoading] = useState(true); const [saving, setSaving] = useState(false); // Filters and search const [activeTab, setActiveTab] = useState('all'); const [searchQuery, setSearchQuery] = useState(''); // Bulk selection const [selectedRsvps, setSelectedRsvps] = useState(new Set()); const [selectAll, setSelectAll] = useState(false); useEffect(() => { fetchEventAndRsvps(); }, [eventId]); useEffect(() => { filterRsvps(); }, [rsvps, activeTab, searchQuery]); const fetchEventAndRsvps = async () => { try { setLoading(true); const [eventRes, rsvpsRes] = await Promise.all([ api.get(`/admin/events/${eventId}`), api.get(`/admin/events/${eventId}/rsvps`) ]); setEvent(eventRes.data); setRsvps(rsvpsRes.data); } catch (error) { console.error('Failed to fetch event data:', error); toast.error('Failed to load event data'); } finally { setLoading(false); } }; const filterRsvps = () => { let filtered = [...rsvps]; // Filter by RSVP status tab if (activeTab !== 'all') { filtered = filtered.filter(rsvp => rsvp.rsvp_status === activeTab); } // Filter by search query if (searchQuery) { const query = searchQuery.toLowerCase(); filtered = filtered.filter(rsvp => rsvp.user_name?.toLowerCase().includes(query) || rsvp.user_email?.toLowerCase().includes(query) ); } setFilteredRsvps(filtered); }; const handleSelectAll = () => { if (selectAll) { setSelectedRsvps(new Set()); } else { setSelectedRsvps(new Set(filteredRsvps.map(rsvp => rsvp.user_id))); } setSelectAll(!selectAll); }; const handleSelectRsvp = (userId) => { const newSelected = new Set(selectedRsvps); if (newSelected.has(userId)) { newSelected.delete(userId); } else { newSelected.add(userId); } setSelectedRsvps(newSelected); setSelectAll(newSelected.size === filteredRsvps.length); }; const handleBulkAttendance = async (attended) => { if (selectedRsvps.size === 0) { toast.error('Please select at least one RSVP'); return; } try { setSaving(true); const updates = Array.from(selectedRsvps).map(userId => ({ user_id: userId, attended })); await api.put(`/admin/events/${eventId}/attendance`, { updates }); toast.success(`Marked ${selectedRsvps.size} ${selectedRsvps.size === 1 ? 'person' : 'people'} as ${attended ? 'attended' : 'not attended'}`); // Refresh data await fetchEventAndRsvps(); // Clear selection setSelectedRsvps(new Set()); setSelectAll(false); } catch (error) { console.error('Failed to update attendance:', error); toast.error('Failed to update attendance'); } finally { setSaving(false); } }; const handleIndividualAttendance = async (userId, attended) => { try { setSaving(true); const updates = [{ user_id: userId, attended }]; await api.put(`/admin/events/${eventId}/attendance`, { updates }); toast.success(`Attendance ${attended ? 'confirmed' : 'removed'}`); // Refresh data await fetchEventAndRsvps(); } catch (error) { console.error('Failed to update attendance:', error); toast.error('Failed to update attendance'); } finally { setSaving(false); } }; const exportToCSV = () => { if (filteredRsvps.length === 0) { toast.error('No RSVPs to export'); return; } // CSV header const headers = ['Name', 'Email', 'RSVP Status', 'Attended', 'Attended At']; // CSV rows const rows = filteredRsvps.map(rsvp => [ `"${rsvp.user_name}"`, `"${rsvp.user_email}"`, `"${rsvp.rsvp_status.toUpperCase()}"`, rsvp.attended ? 'Yes' : 'No', rsvp.attended_at ? `"${moment(rsvp.attended_at).format('YYYY-MM-DD HH:mm A')}"` : '' ]); // Combine headers and rows const csvContent = [ headers.join(','), ...rows.map(row => row.join(',')) ].join('\n'); // Create blob and download const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' }); const link = document.createElement('a'); const url = URL.createObjectURL(blob); link.setAttribute('href', url); link.setAttribute('download', `${event?.title.replace(/\s+/g, '_')}_RSVPs_${moment().format('YYYY-MM-DD')}.csv`); link.style.visibility = 'hidden'; document.body.appendChild(link); link.click(); document.body.removeChild(link); toast.success('CSV exported successfully'); }; const getStats = () => { const total = rsvps.length; const yesCount = rsvps.filter(r => r.rsvp_status === 'yes').length; const noCount = rsvps.filter(r => r.rsvp_status === 'no').length; const maybeCount = rsvps.filter(r => r.rsvp_status === 'maybe').length; const attendedCount = rsvps.filter(r => r.attended).length; return { total, yesCount, noCount, maybeCount, attendedCount }; }; const stats = getStats(); if (loading) { return (
Event not found
Manage RSVPs and track attendance for this event
Total RSVPs
{stats.total}
Yes
{stats.yesCount}
No
{stats.noCount}
Maybe
{stats.maybeCount}
Attended
{stats.attendedCount}
|
|
Name | RSVP Status | Attendance | Attended At | |
|---|---|---|---|---|---|
|
|
{rsvp.user_name} | {rsvp.user_email} |
|
{rsvp.attended ? ( ) : ( )} | {rsvp.attended_at ? moment(rsvp.attended_at).format('MMM D, YYYY h:mm A') : '-'} |
|
{searchQuery ? 'No RSVPs match your search' : 'No RSVPs for this filter'} |
|||||