feat(activities): move scan counts below the camera

The Scanned / Sold / Remaining strip moves out of the page header
to below the Tabs block. The camera (or scanned list, depending on
the active tab) stays prominent at the top; the counts read as a
summary footer instead of competing with the title for attention.
The stats-error notice follows the counts strip so the warning
stays adjacent to the values it affects.
This commit is contained in:
Padreug 2026-06-04 23:29:53 +02:00
commit 1aeea23296

View file

@ -115,45 +115,6 @@ function fmtTime(iso: string) {
{{ activity.title }} {{ activity.title }}
</p> </p>
<!-- Counts strip backend-authoritative. Source: the
`events_list_event_tickets` RPC, refreshed after every scan.
Stays consistent across organizer devices unlike a
per-device localStorage count. -->
<div class="grid grid-cols-3 gap-2 mb-4">
<div class="rounded-lg border border-border bg-muted/30 p-3 text-center">
<p class="text-2xl font-bold text-foreground">{{ registeredCount }}</p>
<p class="text-[10px] uppercase tracking-wide text-muted-foreground mt-1">Scanned</p>
</div>
<div class="rounded-lg border border-border bg-muted/30 p-3 text-center">
<p class="text-2xl font-bold text-foreground">
{{ soldCount ?? '—' }}
</p>
<p class="text-[10px] uppercase tracking-wide text-muted-foreground mt-1">Sold</p>
</div>
<div class="rounded-lg border border-border bg-muted/30 p-3 text-center">
<p class="text-2xl font-bold text-foreground">
{{ remainingCount ?? '—' }}
</p>
<p class="text-[10px] uppercase tracking-wide text-muted-foreground mt-1">Remaining</p>
</div>
</div>
<!-- Surface stats fetch failures (e.g. backend missing the /stats
endpoint, or wallet ownership rejected). Without this the
counts strip silently freezes on the last good value while
scans keep landing on the backend. -->
<div
v-if="statsError"
class="mb-4 flex items-start gap-2 rounded-md border border-destructive/40 bg-destructive/10 p-3 text-xs text-destructive"
role="alert"
>
<AlertCircle class="w-4 h-4 mt-0.5 shrink-0" />
<div class="min-w-0">
<p class="font-medium">Counts may be out of date</p>
<p class="text-destructive/80 mt-0.5 break-words">{{ statsError }}</p>
</div>
</div>
<Tabs v-model="activeTab" class="w-full"> <Tabs v-model="activeTab" class="w-full">
<TabsList class="grid w-full grid-cols-2 mb-4"> <TabsList class="grid w-full grid-cols-2 mb-4">
<TabsTrigger value="scanner" class="gap-1.5"> <TabsTrigger value="scanner" class="gap-1.5">
@ -228,6 +189,47 @@ function fmtTime(iso: string) {
</TabsContent> </TabsContent>
</Tabs> </Tabs>
<!-- Counts strip backend-authoritative. Source: the
`events_list_event_tickets` RPC, refreshed after every scan.
Stays consistent across organizer devices unlike a
per-device localStorage count. Lives below the scanner so
the camera view stays prominent and the counts read as a
summary footer rather than a header. -->
<div class="grid grid-cols-3 gap-2 mt-4">
<div class="rounded-lg border border-border bg-muted/30 p-3 text-center">
<p class="text-2xl font-bold text-foreground">{{ registeredCount }}</p>
<p class="text-[10px] uppercase tracking-wide text-muted-foreground mt-1">Scanned</p>
</div>
<div class="rounded-lg border border-border bg-muted/30 p-3 text-center">
<p class="text-2xl font-bold text-foreground">
{{ soldCount ?? '—' }}
</p>
<p class="text-[10px] uppercase tracking-wide text-muted-foreground mt-1">Sold</p>
</div>
<div class="rounded-lg border border-border bg-muted/30 p-3 text-center">
<p class="text-2xl font-bold text-foreground">
{{ remainingCount ?? '—' }}
</p>
<p class="text-[10px] uppercase tracking-wide text-muted-foreground mt-1">Remaining</p>
</div>
</div>
<!-- Surface stats fetch failures (e.g. backend missing the /stats
endpoint, or wallet ownership rejected). Without this the
counts strip silently freezes on the last good value while
scans keep landing on the backend. -->
<div
v-if="statsError"
class="mt-3 flex items-start gap-2 rounded-md border border-destructive/40 bg-destructive/10 p-3 text-xs text-destructive"
role="alert"
>
<AlertCircle class="w-4 h-4 mt-0.5 shrink-0" />
<div class="min-w-0">
<p class="font-medium">Counts may be out of date</p>
<p class="text-destructive/80 mt-0.5 break-words">{{ statsError }}</p>
</div>
</div>
<!-- Full-screen result overlay. Tap anywhere to dismiss and <!-- Full-screen result overlay. Tap anywhere to dismiss and
resume the decode loop. Replaces the inline banner so the resume the decode loop. Replaces the inline banner so the
door operator can't miss the outcome a busy entry meant door operator can't miss the outcome a busy entry meant