From a5beff4aa83872d588c32dd7986859997be07901 Mon Sep 17 00:00:00 2001 From: Scott Idem Date: Thu, 11 Jun 2026 04:23:07 -0400 Subject: [PATCH] feat(reports): add created_on timestamps and detail links to file downloads report Show upload timestamp for every file; bold the newest upload per session/presenter group so staff can quickly identify the most recent version. Add Session/Presenter navigation links in each card header for direct access without searching. Co-Authored-By: Claude Sonnet 4.6 --- .../reports/reports_file_downloads.svelte | 65 +++++++++++++++++-- 1 file changed, 58 insertions(+), 7 deletions(-) diff --git a/src/routes/events/[event_id]/(pres_mgmt)/reports/reports_file_downloads.svelte b/src/routes/events/[event_id]/(pres_mgmt)/reports/reports_file_downloads.svelte index 41b16cf8..ce0c315c 100644 --- a/src/routes/events/[event_id]/(pres_mgmt)/reports/reports_file_downloads.svelte +++ b/src/routes/events/[event_id]/(pres_mgmt)/reports/reports_file_downloads.svelte @@ -27,6 +27,7 @@ import MyClipboard from '$lib/app_components/e_app_clipboard.svelte'; import { ArrowUpDown, Download, + ExternalLink, FileText, LoaderCircle, RefreshCw, @@ -148,6 +149,11 @@ function build_filename(file: any, fmt: FormatKey): string { return `${pre}${stem}${suf}.${ext}`; } +// Returns the largest created_on string in a file list — identifies the newest upload per group. +function newest_in_group(files: { created_on?: string | null }[]): string { + return files.reduce((max, f) => ((f.created_on ?? '') > max ? (f.created_on ?? '') : max), ''); +} + // Strip :443 from https URLs — redundant and clutters shareable links. let base_url = $derived($ae_api.base_url.replace(/^(https:\/\/[^/:]+):443(\/|$)/, '$1$2')); @@ -416,6 +422,7 @@ async function handle_qry() { {:else} {#each session_groups as sg (sg.session_id)} {@const has_any = sg.session_files.length > 0 || sg.presenter_groups.length > 0} + {@const newest_session_ts = newest_in_group(sg.session_files)} {#if has_any}
@@ -435,6 +442,15 @@ async function handle_qry() { {sg.session_files.length} session file{sg.session_files.length !== 1 ? 's' : ''},  {sg.presenter_groups.reduce((n, pg) => n + pg.files.length, 0)} presenter file{sg.presenter_groups.reduce((n, pg) => n + pg.files.length, 0) !== 1 ? 's' : ''} + {#if sg.session_id !== '__no_session__'} + + + Session + + {/if}
@@ -452,6 +468,7 @@ async function handle_qry() { {@const computed_name = build_filename(file, selected_format)} {@const dl_url = build_download_url(file, selected_format)} {@const is_long = computed_name.length > FILENAME_WARN_LEN} + {@const is_newest = !!file.created_on && file.created_on === newest_session_ts}
@@ -466,6 +483,16 @@ async function handle_qry() { title="{computed_name}{is_long ? ` (${computed_name.length} chars — may be long for some systems)` : ''}"> {computed_name} + {#if file.created_on} + + {ae_util.iso_datetime_formatter(file.created_on, 'date_iso')} +  {ae_util.iso_datetime_formatter(file.created_on, 'time_12_short_no_leading')} + + {/if}
@@ -496,21 +523,35 @@ async function handle_qry() { {#each sg.presenter_groups as pg (pg.presenter_id)} + {@const newest_presenter_ts = newest_in_group(pg.files)}
-

- - {pg.presenter_full_name || '— presenter name not set —'} - {#if pg.presentation_name} - — {pg.presentation_name} + +
+

+ + {pg.presenter_full_name || '— presenter name not set —'} + {#if pg.presentation_name} + — {pg.presentation_name} + {/if} + ({pg.files.length} file{pg.files.length !== 1 ? 's' : ''}) +

+ {#if pg.presenter_id !== '__unknown__'} + + + Presenter + {/if} - ({pg.files.length} file{pg.files.length !== 1 ? 's' : ''}) -

+
{#each pg.files as file (file.event_file_id)} {@const ExtIcon = ae_util.file_extension_icon_lucide(file.extension)} {@const computed_name = build_filename(file, selected_format)} {@const dl_url = build_download_url(file, selected_format)} {@const is_long = computed_name.length > FILENAME_WARN_LEN} + {@const is_newest = !!file.created_on && file.created_on === newest_presenter_ts}
@@ -525,6 +566,16 @@ async function handle_qry() { title="{computed_name}{is_long ? ` (${computed_name.length} chars — may be long for some systems)` : ''}"> {computed_name} + {#if file.created_on} + + {ae_util.iso_datetime_formatter(file.created_on, 'date_iso')} +  {ae_util.iso_datetime_formatter(file.created_on, 'time_12_short_no_leading')} + + {/if}