Admin event page: redesign UI, export endpoints, mobile fixes
- Backend: Add /events/:eventId/attendees/export and /events/:eventId/tickets/export with q/status; legacy redirect for old export path - API: exportAttendees q param, new exportTicketsCSV for tickets CSV - Admin event page: unified tabs+content container, portal dropdowns to fix clipping, separate mobile export/add-ticket sheets (fix double menu), responsive tab bar and card layout Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -373,16 +373,17 @@ export const adminApi = {
|
||||
return fetchApi<{ payments: ExportedPayment[]; summary: FinancialSummary }>(`/api/admin/export/financial?${query}`);
|
||||
},
|
||||
/** Download attendee export as a file (CSV). Returns a Blob. */
|
||||
exportAttendees: async (eventId: string, params?: { status?: string; format?: string }) => {
|
||||
exportAttendees: async (eventId: string, params?: { status?: string; format?: string; q?: string }) => {
|
||||
const query = new URLSearchParams();
|
||||
if (params?.status) query.set('status', params.status);
|
||||
if (params?.format) query.set('format', params.format);
|
||||
if (params?.q) query.set('q', params.q);
|
||||
const token = typeof window !== 'undefined'
|
||||
? localStorage.getItem('spanglish-token')
|
||||
: null;
|
||||
const headers: Record<string, string> = {};
|
||||
if (token) headers['Authorization'] = `Bearer ${token}`;
|
||||
const res = await fetch(`${API_BASE}/api/admin/events/${eventId}/export?${query}`, { headers });
|
||||
const res = await fetch(`${API_BASE}/api/admin/events/${eventId}/attendees/export?${query}`, { headers });
|
||||
if (!res.ok) {
|
||||
const errorData = await res.json().catch(() => ({ error: 'Export failed' }));
|
||||
throw new Error(errorData.error || 'Export failed');
|
||||
@@ -393,6 +394,27 @@ export const adminApi = {
|
||||
const blob = await res.blob();
|
||||
return { blob, filename };
|
||||
},
|
||||
/** Download tickets export as CSV. Returns a Blob. */
|
||||
exportTicketsCSV: async (eventId: string, params?: { status?: string; q?: string }) => {
|
||||
const query = new URLSearchParams();
|
||||
if (params?.status) query.set('status', params.status);
|
||||
if (params?.q) query.set('q', params.q);
|
||||
const token = typeof window !== 'undefined'
|
||||
? localStorage.getItem('spanglish-token')
|
||||
: null;
|
||||
const headers: Record<string, string> = {};
|
||||
if (token) headers['Authorization'] = `Bearer ${token}`;
|
||||
const res = await fetch(`${API_BASE}/api/admin/events/${eventId}/tickets/export?${query}`, { headers });
|
||||
if (!res.ok) {
|
||||
const errorData = await res.json().catch(() => ({ error: 'Export failed' }));
|
||||
throw new Error(errorData.error || 'Export failed');
|
||||
}
|
||||
const disposition = res.headers.get('Content-Disposition') || '';
|
||||
const filenameMatch = disposition.match(/filename="?([^"]+)"?/);
|
||||
const filename = filenameMatch ? filenameMatch[1] : `tickets-${new Date().toISOString().split('T')[0]}.csv`;
|
||||
const blob = await res.blob();
|
||||
return { blob, filename };
|
||||
},
|
||||
};
|
||||
|
||||
// Emails API
|
||||
|
||||
Reference in New Issue
Block a user