/admin/registrations renders live event data sourced from Supabase. This page documents each section of the dashboard and the underlying API.
Summary statistics
The four animated stat cards at the top of the dashboard are computed from theget_registrations_stats RPC response:
Total registrations
The raw count of all rows in the
registrations table within the event date range, returned as total_registrations from the stats RPC.Peak daily attendance
The highest single-day value in
daily_expected_attendance — computed client-side with Math.max(...Object.values(stats.daily_expected_attendance)).Avg daily arrivals
The mean of all values in
counts_by_arrival_date, rounded to the nearest integer. Calculated as total / values.length across all arrival dates.Unique ghaams
The number of distinct Ghaam keys in
counts_by_ghaam, derived from Object.keys(stats.counts_by_ghaam).length.useMotionValue and animate.
Charts
Charts are loaded lazily viaReact.lazy and Suspense. A skeleton placeholder is shown while they load. All charts use Recharts.
Daily expected attendance
A full-width line chart plottingdaily_expected_attendance over time. Core event dates (July 27 – August 2, 2026) are highlighted with a shaded ReferenceArea. Dates are formatted as M/d on the X axis.
Arrivals by date
A bar chart of registrant arrival dates fromcounts_by_arrival_date. Bars are sorted chronologically, and core event dates are shaded. X-axis labels are rotated –35° to accommodate dense date ranges.
Departures by date
A bar chart mirroring the arrivals chart, usingcounts_by_departure_date. Shares the same visual style and core-date shading logic.
Highest attendees by Ghaam
A ranked table showing the top 8 Ghaams by registrant count fromcounts_by_ghaam. “Unknown” values are excluded. If there are more than 8 Ghaams, a “View all” button opens a scrollable dialog listing all Ghaams sorted by count.
Highest attendees by Mandal
The same ranked-table pattern forcounts_by_mandal. Mandal names are converted from their stored format to display labels via mandalStoredToDisplay() before rendering.
The charts section renders client-side only. If JavaScript is disabled or the bundle fails to load, the Suspense fallback skeleton remains visible.
Registrations table
The table does not load automatically — it requires clicking the Load Registrations button. This avoids running an expensive query before the admin needs it. Table columns:| Column | Source field |
|---|---|
| # | Row number (1-based, tracks across pages) |
| Name | first_name + middle_name + last_name |
email | |
| Ghaam | ghaam |
| Mandal | mandal (display label via mandalStoredToDisplay) |
| Arrival | arrival_date (formatted as MMM d, yyyy) |
| Departure | departure_date (formatted as MMM d, yyyy) |
| Age | age |
Search
The search box queriesname, email, and mobile_number fields simultaneously. Search terms shorter than 2 characters are ignored. Input is debounced by 350 ms before triggering a new fetch.
Filters
Ghaam
Dropdown populated from the
GET /api/admin/registrations/distinct endpoint. Shows all distinct Ghaam values present in the database.Mandal
Dropdown populated from the static
getAllMandalOptionsStored() helper. Labels are converted for display via mandalStoredToDisplay().Country
Dropdown populated from
GET /api/admin/registrations/distinct. Shows all distinct country values.Age filters
Three numeric inputs: exact age, age minimum, and age maximum. Exposed behind the “Show age & date filters” toggle.
age_min must be ≤ age_max.Arrival date range
Arrival from and Arrival to date pickers, constrained to the event date range from REGISTRATION_DATE_RANGE.Departure date range
Departure from and Departure to date pickers, using the same event date range constraints.Pagination
The table uses keyset (cursor-based) pagination ordered byid DESC. Page size options are 25, 50, or 100 rows. The footer shows the current row range and the total row count.
- Navigating forward uses
nextCursorfrom the previous response. - Navigating backward uses
prevCursor. - Changing page size resets to the first page.
- Changing any filter resets to the first page and fires a new count request.
The total row count is fetched from
GET /api/admin/registrations/count when filters change. When no filters are active, the count from the stats RPC (total_registrations) is used instead to avoid a redundant query.CSV export
Two export actions are available:| Button | Endpoint | Behavior |
|---|---|---|
| Export entire registration list | GET /api/registrations/export | Exports all rows regardless of current filters |
| Export current view | GET /api/admin/registrations/export?{filters} | Exports only rows matching the current filter state |
get_registrations_filtered RPC in a while (hasMore) loop. The file is delivered as text/csv; charset=utf-8 with a UTF-8 BOM (\uFEFF) for Excel compatibility.
Exported columns (in order):
registrations-{ISO-timestamp}.csv or registrations-filtered-{ISO-timestamp}.csv.
API reference
GET /api/admin/stats
Returns aggregated statistics for the dashboard stat cards and charts. Authentication: Requires an active Supabase session with an@nj.sgadi.us email.
Query parameters:
| Parameter | Type | Description |
|---|---|---|
start_date | ISO date | Start of date range (default: REGISTRATION_DATE_RANGE.start = 2026-07-23) |
end_date | ISO date | End of date range (default: REGISTRATION_DATE_RANGE.end = 2026-08-08) |
401 (unauthenticated), 403 (wrong domain), 500 (Supabase not configured or RPC error).
GET /api/registrations/export
Streams all registrations as a CSV file regardless of any filters. This is the “Export entire registration list” button in the dashboard. Authentication: Requires@nj.sgadi.us domain session.
Response: text/csv; charset=utf-8 stream with UTF-8 BOM. Uses the registrations table directly with keyset pagination (id ASC, chunk size 500). Filename: registrations-{ISO-timestamp}.csv.
GET /api/admin/registrations
Returns a single page of registrations with keyset pagination. Authentication: Requires an active Supabase session with an@nj.sgadi.us email. Returns 401 if unauthenticated, 403 if the email domain is not allowed.
Query parameters:
| Parameter | Type | Description |
|---|---|---|
page_size | 25 | 50 | 100 | Number of rows per page (default: 25) |
cursor | integer | ID cursor for keyset pagination |
direction | next | prev | Pagination direction (default: next) |
search | string | Full-text search across name, email, phone (min 2 chars) |
ghaam | string | Exact Ghaam filter |
mandal | string | Exact Mandal filter (stored value) |
country | string | Exact country filter |
age | integer | Exact age filter |
age_min | integer | Minimum age (inclusive) |
age_max | integer | Maximum age (inclusive) |
arrival_from | ISO date | Earliest arrival date (inclusive) |
arrival_to | ISO date | Latest arrival date (inclusive) |
departure_from | ISO date | Earliest departure date (inclusive) |
departure_to | ISO date | Latest departure date (inclusive) |
| Status | Meaning |
|---|---|
400 | age_min > age_max |
401 | No active session |
403 | Email not in @nj.sgadi.us domain |
500 | Supabase not configured or RPC error |
GET /api/admin/registrations/count
Returns the total number of rows matching the given filters. Accepts the same filter parameters as the main endpoint (no pagination params). Times out after 12 seconds and caps at 100,000 to prevent runaway queries.GET /api/admin/registrations/export
Streams all matching rows as a CSV file. Accepts the same filter parameters as the main endpoint. No pagination parameters — all rows are exported in chunks of 500.GET /api/admin/registrations/distinct
Returns distinct values forghaam and country columns, used to populate the filter dropdowns.
