fix(events): server-side search/pagination to remove first-100 cap (#346)
Counters and search on Admin → Events were bounded to the first 100 rows
returned from /admin/events?page=1&limit=100, so on instances with more
events the totals were wrong and search couldn't find anything outside
that window. The dashboard's expiring list had the same first-100 issue.
Backend
- adminEvents.js: extend search to include customer_email so the column
shown in the table is actually queryable.
- adminDashboard.js: add totalEvents to /dashboard/stats so the events
page can render an accurate "All (N)" / Total Events counter without
walking the full table on the client.
Frontend
- events.service.ts: getEvents() now accepts search + the full status
enum (active|inactive|archived|draft|expiring); response type matches
the actual {events, pagination} shape.
- admin.service.ts: DashboardStats gains totalEvents.
- EventsListPage.tsx: rewired around server-side pagination, status
filter, and 300ms-debounced search; Prev/Next + range/page indicator
below the table; placeholderData keeps the previous page visible
during fetches; stat cards and "All (N)" pull from /dashboard/stats so
totals stay accurate regardless of the visible page; archive/delete
invalidates dashboard-stats so cards refresh.
- AdminDashboard.tsx: expiring list now fetches getEvents(1, 5,
'expiring') directly instead of slicing the first 100 client-side. As
a side effect the dashboard's "expiring" definition now matches the
backend (was excluding events expiring within the next 24h).
This commit is contained in:
@@ -62,6 +62,13 @@ router.get('/stats', adminAuth, requirePermission('analytics.view'), async (req,
|
||||
.count('id as count')
|
||||
.first();
|
||||
|
||||
// Get total events count (all events regardless of status) — used by the
|
||||
// events list page to render accurate "All (N)" / Total Events counters
|
||||
// when the table is server-paginated (#346).
|
||||
const totalEvents = await db('events')
|
||||
.count('id as count')
|
||||
.first();
|
||||
|
||||
// Calculate trends (compare with previous 30 days)
|
||||
const sixtyDaysAgo = new Date();
|
||||
sixtyDaysAgo.setDate(sixtyDaysAgo.getDate() - 60);
|
||||
@@ -98,7 +105,8 @@ router.get('/stats', adminAuth, requirePermission('analytics.view'), async (req,
|
||||
totalDownloads: totalDownloads.count || 0,
|
||||
viewsTrend: Math.round(viewsTrend * 10) / 10,
|
||||
downloadsTrend: Math.round(downloadsTrend * 10) / 10,
|
||||
archivedEvents: archivedEvents.count || 0
|
||||
archivedEvents: archivedEvents.count || 0,
|
||||
totalEvents: totalEvents.count || 0
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Dashboard stats error:', error);
|
||||
|
||||
@@ -739,6 +739,7 @@ router.get('/', adminAuth, requirePermission('events.view'), async (req, res) =>
|
||||
query = query.where((builder) => {
|
||||
builder.where('event_name', 'like', `%${escapedSearch}%`)
|
||||
.orWhere('admin_email', 'like', `%${escapedSearch}%`)
|
||||
.orWhere('customer_email', 'like', `%${escapedSearch}%`)
|
||||
.orWhere('slug', 'like', `%${escapedSearch}%`);
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user