Mobile scanner redesign + backend live search
- Scanner page: fullscreen mobile-first layout, Scan/Search/Recent tabs - Scan tab: auto-start camera, switch camera, vibration/sound feedback - Valid/invalid fullscreen states, confirm check-in, auto-return to camera - Search tab: live backend search (300ms debounce), tap card for detail + check-in - Recent tab: last 20 check-ins, session counter - Backend: GET /api/tickets/search (live search), GET /api/tickets/stats/checkin - Admin layout: hide sidebar on scanner page; fix hooks order (no early return before useEffect) - Back button to dashboard/events (staff → events, others → admin) - API: searchLive, getCheckinStats, LiveSearchResult; PostgreSQL LOWER cast for UUID Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -92,6 +92,27 @@ export const ticketsApi = {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ code, eventId }),
|
||||
}),
|
||||
|
||||
// Search tickets by name/email (for scanner manual search)
|
||||
search: (query: string, eventId?: string) =>
|
||||
fetchApi<{ tickets: TicketSearchResult[] }>('/api/tickets/search', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ query, eventId }),
|
||||
}),
|
||||
|
||||
// Get event check-in stats (for scanner header counter)
|
||||
getCheckinStats: (eventId: string) =>
|
||||
fetchApi<{ eventId: string; capacity: number; checkedIn: number; totalActive: number }>(
|
||||
`/api/tickets/stats/checkin?eventId=${eventId}`
|
||||
),
|
||||
|
||||
// Live search tickets (GET - for scanner live search with debounce)
|
||||
searchLive: (q: string, eventId?: string) => {
|
||||
const params = new URLSearchParams();
|
||||
params.set('q', q);
|
||||
if (eventId) params.set('eventId', eventId);
|
||||
return fetchApi<{ tickets: LiveSearchResult[] }>(`/api/tickets/search?${params}`);
|
||||
},
|
||||
|
||||
checkin: (id: string) =>
|
||||
fetchApi<{ ticket: Ticket & { attendeeName?: string }; event?: { id: string; title: string }; message: string }>(`/api/tickets/${id}/checkin`, {
|
||||
@@ -508,6 +529,39 @@ export interface TicketValidationResult {
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export interface TicketSearchResult {
|
||||
id: string;
|
||||
qrCode: string;
|
||||
attendeeName: string;
|
||||
attendeeEmail?: string;
|
||||
attendeePhone?: string;
|
||||
status: string;
|
||||
checkinAt?: string;
|
||||
event?: {
|
||||
id: string;
|
||||
title: string;
|
||||
startDatetime: string;
|
||||
location: string;
|
||||
} | null;
|
||||
}
|
||||
|
||||
export interface LiveSearchResult {
|
||||
ticket_id: string;
|
||||
name: string;
|
||||
email?: string;
|
||||
status: string;
|
||||
checked_in: boolean;
|
||||
checkinAt?: string;
|
||||
event_id: string;
|
||||
qrCode: string;
|
||||
event?: {
|
||||
id: string;
|
||||
title: string;
|
||||
startDatetime: string;
|
||||
location: string;
|
||||
} | null;
|
||||
}
|
||||
|
||||
export interface Payment {
|
||||
id: string;
|
||||
ticketId: string;
|
||||
|
||||
Reference in New Issue
Block a user