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 index eae31aea..51996e77 100644 --- 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 @@ -33,6 +33,14 @@ function fmt_date(ms: number): string { return new Date(ms).toLocaleDateString([], { month: 'short', day: 'numeric' }); } +// Naive UTC strings from the backend have no timezone indicator — append Z so +// the browser parses them as UTC and converts to local time, matching the badge list display. +function parse_utc_ms(dt: string | null | undefined): number { + if (!dt) return NaN; + const normalized = dt.match(/Z$|[+-]\d{2}:?\d{2}$/) ? dt : dt + 'Z'; + return new Date(normalized).getTime(); +} + let stats: PrintStats = $derived.by(() => { const printed = badge_li.filter((b) => (b.print_count ?? 0) >= 1 && b.print_last_datetime); if (!printed.length) { @@ -41,7 +49,7 @@ let stats: PrintStats = $derived.by(() => { const bucket_ms = bucket_size * 60 * 1000; const times = printed - .map((b) => new Date(b.print_last_datetime).getTime()) + .map((b) => parse_utc_ms(b.print_last_datetime)) .filter((t) => !isNaN(t)); if (!times.length) { return { buckets: [], total_printed: printed.length, max_count: 1, span_label: '' }; @@ -52,29 +60,33 @@ let stats: PrintStats = $derived.by(() => { const start = Math.floor(min_raw / bucket_ms) * bucket_ms; const end = Math.ceil((max_raw + 1) / bucket_ms) * bucket_ms; - const buckets: Bucket[] = []; - let prev_day = ''; + const raw_buckets: Omit[] = []; for (let t = start; t < end; t += bucket_ms) { const in_bucket = printed.filter((b) => { - const bt = new Date(b.print_last_datetime).getTime(); + const bt = parse_utc_ms(b.print_last_datetime); return bt >= t && bt < t + bucket_ms; }); if (in_bucket.length === 0) continue; - const day = fmt_date(t); - const date_label = day !== prev_day ? day : null; - prev_day = day; - - buckets.push({ + raw_buckets.push({ start_ms: t, label: fmt_time(t), - date_label, count: in_bucket.length, badges: in_bucket }); } + // Newest-first display; re-assign date separators after reversing. + raw_buckets.reverse(); + let prev_day = ''; + const buckets: Bucket[] = raw_buckets.map((b) => { + const day = fmt_date(b.start_ms); + const date_label = day !== prev_day ? day : null; + prev_day = day; + return { ...b, date_label }; + }); + const max_count = Math.max(...buckets.map((b) => b.count), 1); const span_label = buckets.length > 0