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 }}
</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">
<TabsList class="grid w-full grid-cols-2 mb-4">
<TabsTrigger value="scanner" class="gap-1.5">
@ -228,6 +189,47 @@ function fmtTime(iso: string) {
</TabsContent>
</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
resume the decode loop. Replaces the inline banner so the
door operator can't miss the outcome a busy entry meant