diff --git a/src/APIFunctions/SCEvents.js b/src/APIFunctions/SCEvents.js index 6a387fb3c..c14d6bc46 100644 --- a/src/APIFunctions/SCEvents.js +++ b/src/APIFunctions/SCEvents.js @@ -33,6 +33,26 @@ export async function getEventByID(id) { return status; } +export async function getEventAttendanceSummary(id, token) { + const status = new ApiResponse(); + try { + const res = await fetch(`${SCEVENTS_API_URL}/events/${id}/attendance`, { + headers: { + Authorization: `Bearer ${token}`, + }, + }); + const result = await res.json(); + status.responseData = result; + if (!res.ok) { + status.error = true; + } + } catch (err) { + status.error = true; + status.responseData = err; + } + return status; +} + export async function createSCEvent(token, eventBody) { const status = new ApiResponse(); try { diff --git a/src/Pages/Events/CalendarView.js b/src/Pages/Events/CalendarView.js index 26f2d2a68..a3e04b4b4 100644 --- a/src/Pages/Events/CalendarView.js +++ b/src/Pages/Events/CalendarView.js @@ -1,5 +1,6 @@ import React, { useState, useMemo, useEffect, useRef } from 'react'; import { Link } from 'react-router-dom'; +import { getEventAttendanceSummary } from '../../APIFunctions/SCEvents'; // ─── tiny helpers ──────────────────────────────────────────────────────────── @@ -218,11 +219,22 @@ function EventPopup({ event, onClose, isAdminView, user }) { const popupRef = useRef(null); const colors = pillColors(event, isAdminView); const badgeText = getBadgeText(event, isAdminView); + const eventId = event.id || event._id; + const authToken = user?.token || window.localStorage.getItem('jwtToken'); const userId = user?._id != null ? String(user._id) : ''; + const [attendeeCount, setAttendeeCount] = useState(null); + const [attendanceLoading, setAttendanceLoading] = useState(false); + const [attendanceLoaded, setAttendanceLoaded] = useState(false); const canEditEvent = Array.isArray(event.admins) && userId && event.admins.includes(userId); + const maxAttendees = Number(event.max_attendees); + const hasCapacityLimit = Number.isFinite(maxAttendees) && maxAttendees > 0; + const remainingSpots = + hasCapacityLimit && typeof attendeeCount === 'number' + ? Math.max(maxAttendees - attendeeCount, 0) + : null; useEffect(() => { function onKey(e) { @@ -245,11 +257,39 @@ function EventPopup({ event, onClose, isAdminView, user }) { }; }, [onClose]); + useEffect(() => { + let isCurrent = true; + setAttendeeCount(null); + setAttendanceLoaded(false); + setAttendanceLoading(false); + + async function fetchAttendanceSummary() { + if (!eventId || !authToken) { + return; + } + setAttendanceLoading(true); + const response = await getEventAttendanceSummary(eventId, authToken); + if (isCurrent && !response.error && typeof response.responseData?.attendee_count === 'number') { + setAttendeeCount(response.responseData.attendee_count); + } + if (isCurrent) { + setAttendanceLoaded(true); + setAttendanceLoading(false); + } + } + + fetchAttendanceSummary(); + + return () => { + isCurrent = false; + }; + }, [eventId, authToken]); + return ( -
- {event.max_attendees} spot{event.max_attendees !== 1 ? 's' : ''} available + {attendeeCount} {attendeeCount === 1 ? 'person' : 'people'} registered {event.waitlist_enabled && ( · waitlist enabled )}
)} + {hasCapacityLimit && !canEditEvent && typeof remainingSpots === 'number' && ( ++ {remainingSpots} spot{remainingSpots !== 1 ? 's' : ''} left + {event.waitlist_enabled && ( + · waitlist enabled + )} +
+ )} + + {hasCapacityLimit && !canEditEvent && attendanceLoading && ( ++ Loading live spots... +
+ )} + + {hasCapacityLimit && !canEditEvent && attendanceLoaded && typeof remainingSpots !== 'number' && ( ++ Unable to load live spots +
+ )} + {canEditEvent && ( ++ {event.description} +
+ )} + +