From 8a41f02f0dcc9097b4bcec7b6f469503f22729b6 Mon Sep 17 00:00:00 2001 From: Scott Idem Date: Mon, 8 Jun 2026 19:32:53 -0400 Subject: [PATCH] feat(badges): add Long Names and Print Throughput reports Two IDB-backed reports under /badges/reports: - Long Names: filter badges by given/family/full name length (threshold adjustable), colour-coded by severity, links to review page - Print Throughput: bucket print_last_datetime into 5/15/30/60-min windows with a horizontal bar chart and expandable badge name list Also adds a "Badge Reports" nav link on the badges main page. Co-Authored-By: Claude Sonnet 4.6 --- .../[event_id]/(badges)/badges/+page.svelte | 7 +- .../(badges)/badges/reports/+page.svelte | 113 ++++++++++ .../reports/reports_badge_long_names.svelte | 160 ++++++++++++++ .../reports_badge_print_throughput.svelte | 205 ++++++++++++++++++ 4 files changed, 484 insertions(+), 1 deletion(-) create mode 100644 src/routes/events/[event_id]/(badges)/badges/reports/+page.svelte create mode 100644 src/routes/events/[event_id]/(badges)/badges/reports/reports_badge_long_names.svelte create mode 100644 src/routes/events/[event_id]/(badges)/badges/reports/reports_badge_print_throughput.svelte diff --git a/src/routes/events/[event_id]/(badges)/badges/+page.svelte b/src/routes/events/[event_id]/(badges)/badges/+page.svelte index d7057e1c..d6f59e76 100644 --- a/src/routes/events/[event_id]/(badges)/badges/+page.svelte +++ b/src/routes/events/[event_id]/(badges)/badges/+page.svelte @@ -32,7 +32,7 @@ import Comp_badge_obj_li from './ae_comp__badge_obj_li.svelte'; import Comp_badge_create_form from './ae_comp__badge_create_form.svelte'; import Comp_badge_upload_form from './ae_comp__badge_upload_form.svelte'; -import { UserPlus, Printer, Upload, FileText, ChartColumnBig, LayoutTemplate } from '@lucide/svelte'; +import { UserPlus, Printer, Upload, FileText, ChartColumnBig, LayoutTemplate, TrendingUp } from '@lucide/svelte'; // Load templates for this event so the create form can show the selector and // derive badge_type_code_li from whichever template the user picks. @@ -545,6 +545,11 @@ async function handle_search_refresh(params: any) { class="btn btn-tertiary"> Badge Printing Stats + + Badge Reports + {/if} {/if} diff --git a/src/routes/events/[event_id]/(badges)/badges/reports/+page.svelte b/src/routes/events/[event_id]/(badges)/badges/reports/+page.svelte new file mode 100644 index 00000000..e6a53994 --- /dev/null +++ b/src/routes/events/[event_id]/(badges)/badges/reports/+page.svelte @@ -0,0 +1,113 @@ + + + + + Badge Reports — {$lq__event_obj?.name ?? '…'} — {$events_loc?.title ?? 'Æ'} + + + +{#if !is_trusted} +
+

Trusted access required for badge reports.

+
+{:else} +
+
+ + + + +
+

+ + Badge Reports +

+ {#if $lq__event_obj?.name} +

{$lq__event_obj.name}

+ {/if} +
+
+
+ {badge_count} badges · {printed_count} printed +
+
+ + +
+ + +
+ + {#if $lq__badge_li === undefined} +
+ + Loading badge data… +
+ {:else if active_report === 'long_names'} + + {:else if active_report === 'print_throughput'} + + {:else} +
Select a report above to get started.
+ {/if} +{/if} diff --git a/src/routes/events/[event_id]/(badges)/badges/reports/reports_badge_long_names.svelte b/src/routes/events/[event_id]/(badges)/badges/reports/reports_badge_long_names.svelte new file mode 100644 index 00000000..a52b71c1 --- /dev/null +++ b/src/routes/events/[event_id]/(badges)/badges/reports/reports_badge_long_names.svelte @@ -0,0 +1,160 @@ + + +
+ +
+ +
+ Field: + {#each (['full', 'given', 'family'] as NameField[]) as field} + + {/each} +
+ + +
+ Min length: + + {threshold} + +
+ + + + {results.length} badge{results.length !== 1 ? 's' : ''} with {get_field_label(name_field).toLowerCase()} ≥ {threshold} chars + +
+ + + {#if results.length === 0} +

+ No badges found with a {get_field_label(name_field).toLowerCase()} of {threshold} or more characters. +

+ {:else} +
+ + + + + + + + + + + + {#each results as row (row.event_badge_id)} + + + + + + + + {/each} + +
Name ({get_field_label(name_field)})LenBadge TypeAffiliations
+ {row.display_name} + {#if row.has_override} + override + {/if} + + = 30} + class:bg-red-100={row.name_len >= 30} + class:text-amber-700={row.name_len >= 25 && row.name_len < 30} + class:bg-amber-100={row.name_len >= 25 && row.name_len < 30} + class:text-gray-600={row.name_len < 25}> + {row.name_len} + + {row.badge_type ?? '—'} + {row.affiliations ?? '—'} + + + Edit + +
+
+ {/if} +
diff --git a/src/routes/events/[event_id]/(badges)/badges/reports/reports_badge_print_throughput.svelte b/src/routes/events/[event_id]/(badges)/badges/reports/reports_badge_print_throughput.svelte new file mode 100644 index 00000000..ad6c47f9 --- /dev/null +++ b/src/routes/events/[event_id]/(badges)/badges/reports/reports_badge_print_throughput.svelte @@ -0,0 +1,205 @@ + + +
+ +
+
+ Window: + {#each ([5, 15, 30, 60] as BucketMin[]) as sz} + + {/each} +
+ + {#if stats.total_printed > 0} + + {stats.total_printed} printed · {stats.buckets.length} window{stats.buckets.length !== 1 ? 's' : ''} with activity + {#if stats.span_label}· {stats.span_label}{/if} + + {/if} +
+ + + {#if stats.total_printed === 0} +

No printed badges found for this event.

+ {:else if stats.buckets.length === 0} +

No valid print timestamps found.

+ {:else} +
+ {#each stats.buckets as bucket (bucket.start_ms)} + + {#if bucket.date_label} +
+ {bucket.date_label} +
+ {/if} + + +
+ + + + {#if expanded_bucket === bucket.start_ms} +
+
+ {#each bucket.badges as b (b.event_badge_id)} + + {get_effective_name(b)} + + {/each} +
+

+ {bucket.count} badge{bucket.count !== 1 ? 's' : ''} printed in this {bucket_size}-min window · click badge to review +

+
+ {/if} +
+ {/each} +
+ + +
+ + Total printed: {stats.total_printed} + + + Peak window: {stats.max_count} in {bucket_size} min + + + Avg per window: {stats.buckets.length ? (stats.total_printed / stats.buckets.length).toFixed(1) : '—'} + +
+ {/if} +