Files
OSIT-AE-App-Svelte/src/routes/events/ae_comp__event_session_obj_li.svelte
Scott Idem d12a4bf71f feat(events): restore inc_file_counts opt-in, session list layout + button polish
- Add `inc_file_counts` flag to `load_ae_obj_id__event_session` — maps to
  backend alt view (v_event_session_w_file_count) when true; default stays
  lightweight. Callers never pass raw view names.
- Preserve-on-write fallback in `_refresh_session_id_background` keeps
  cached file_count/file_count_all if API response omits them.
- Session detail +page.ts uses `inc_file_counts: true` so SvelteKit prefetch
  no longer clobbers counts via bulkPut on hover.
- Remove explicit `view: 'alt'` from launcher +page.ts (now invalid param).
- Session list link: flex-1 + min-w-0 for full-row width; name flex-1 pushes
  badge group right; code + file_count stacked in flex-col items-end.
- Hover styling: button-like appearance with slow fade-out (duration-500) /
  fast snap-in (hover:duration-150).
- Session +page.svelte: use url_session_id (string) for link_to_id props and
  auth__kv.session[] index — fixes TS type error from number|undefined.
- IDAA layout: dormant tech notice banner (guarded by 1==3, remove when ready).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 16:38:13 -04:00

384 lines
19 KiB
Svelte
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script lang="ts">
interface Props {
log_lvl?: number;
container_class_li?: string | Array<string>;
lq__event_session_obj_li?: any;
event_session_obj_li?: any[] | null;
hide__session_location?: boolean;
hide__session_poc?: boolean;
hide__admin?: boolean;
hide__launcher_link_legacy?: boolean;
hide__launcher_link?: boolean;
hide__location_link?: boolean;
show__session_files?: boolean;
show__session_presentations?: boolean;
}
let {
log_lvl = 0,
container_class_li = [],
lq__event_session_obj_li = null,
event_session_obj_li = null,
hide__session_location = $bindable(false),
hide__session_poc = $bindable(false),
hide__admin = $bindable(false),
hide__launcher_link_legacy = $bindable(false),
hide__launcher_link = $bindable(false),
hide__location_link = $bindable(false),
show__session_files = $bindable(false),
show__session_presentations = $bindable(false)
}: Props = $props();
import { ae_util } from '$lib/ae_utils/ae_utils';
import {
Bell,
BellOff,
CalendarDays,
Check,
ChevronDown,
ChevronUp,
Clock,
Edit,
Eye,
EyeOff,
FileSearch,
LoaderCircle,
Mail,
MapPin,
Presentation,
Rocket,
Search,
SendHorizontal,
User
} from '@lucide/svelte';
import { api } from '$lib/api/api';
import Comp_event_presenter_obj_li from './[event_id]/(pres_mgmt)/presenter/ae_comp__event_presenter_obj_li_wrapper.svelte';
import Element_manage_event_file_li from '$lib/elements/element_manage_event_file_li_direct.svelte';
import Comp_event_session_alert from './[event_id]/(pres_mgmt)/session/ae_comp__event_session_alert.svelte';
import { events_func } from '$lib/ae_events/ae_events_functions';
import { ae_loc, ae_api, ae_snip } from '$lib/stores/ae_stores';
import {
events_loc,
events_sess,
events_slct
} from '$lib/stores/ae_events_stores';
let show_details_kv: Record<string, boolean> = $state({});
// Derived list of visible items (Standardized Pattern 2026-01-27)
// Supports both a liveQuery observable (lq__event_session_obj_li) and a
// plain pre-fetched array (event_session_obj_li) as a fallback.
let visible_session_obj_li = $derived(
(() => {
const list = $lq__event_session_obj_li ?? event_session_obj_li;
if (list === undefined || list === null) return null;
if (!Array.isArray(list)) return [];
const filtered = list.filter((item: any) => {
if (!item) return false;
if ($ae_loc.trusted_access) return true;
return !item.hide;
});
if (log_lvl)
console.log(
`visible_session_obj_li: Input=${list.length}, Output=${filtered.length}`
);
return filtered;
})()
);
function toggle_details(id: string) {
show_details_kv[id] = !show_details_kv[id];
}
</script>
<section
class="ae_comp event_session_obj_li container w-full min-w-full space-y-2 overflow-x-auto px-0.5 py-2 {container_class_li}">
{#if visible_session_obj_li === null}
<div class="flex flex-col items-center justify-center p-10 opacity-50">
<LoaderCircle size="3em" class="mb-2 animate-spin" />
<p>Loading sessions...</p>
</div>
{:else if visible_session_obj_li.length > 0}
<header
class="mb-2 flex w-full flex-row items-center justify-start gap-2 px-2">
<h2 class="text-sm font-normal text-gray-500">Sessions:</h2>
<span
class="badge preset-tonal-success px-3 py-1 text-lg font-bold">
{visible_session_obj_li.length}<span
class="text-gray-400 dark:text-gray-600">&times;</span>
</span>
</header>
<table class="table-striped table w-full table-auto">
<thead>
<tr class="bg-surface-100-900">
<th>Session</th>
<th class="hidden md:table-cell">Schedule</th>
<th class:hidden={hide__session_location}>Location</th>
<th class:hidden={hide__session_poc}>POC</th>
<th
class:hidden={!$ae_loc.edit_mode ||
!$ae_loc.adv_mode ||
hide__admin}>Admin</th>
</tr>
</thead>
<tbody>
{#each visible_session_obj_li as session_obj, index (session_obj.id || session_obj.event_session_id || session_obj.event_session_id_random || index)}
<tr
class="relative transition-colors duration-200"
class:opacity-50={session_obj?.hide}
class:preset-tonal-warning={!session_obj?.enable}>
<td>
{#if session_obj?.alert && $ae_loc.trusted_access}
<Comp_event_session_alert
event_session_obj={session_obj}
{log_lvl} />
{/if}
<div class="flex flex-col gap-1">
<div class="flex items-center gap-2">
<a
href="/events/{session_obj?.event_id}/session/{session_obj?.event_session_id}"
class="hover:text-primary-800-200 hover:bg-surface-400-600 active:bg-surface-200-700 flex flex-1 flex-row items-center gap-2 rounded-lg px-2 py-2 text-left text-lg font-bold transition-colors duration-1000 hover:duration-300 min-w-0">
{#if session_obj?.hide}
<EyeOff
size="1em"
class="flex-none text-gray-400" />
{:else}
<Presentation
size="1em"
class="text-primary-500 flex-none" />
{/if}
<span class="flex-1">{session_obj?.name}</span>
{#if (!$events_loc.pres_mgmt.hide__session_code && session_obj?.code) || session_obj?.file_count_all}
<div class="flex flex-col items-end gap-0.5">
{#if !$events_loc.pres_mgmt.hide__session_code && session_obj?.code}
<span class="border-surface-300-700 text-surface-700-300 bg-surface-200-800 rounded border px-1.5 py-0.5 font-mono text-xs select-all hover:bg-surface-100-900">
{session_obj.code}
</span>
{/if}
{#if session_obj?.file_count_all}
<span
class="badge preset-tonal-success flex items-center gap-1 px-1 py-0 text-xs">
<Check size="1em" />
{session_obj.file_count_all}
</span>
{/if}
</div>
{/if}
</a>
{#if (show__session_presentations || show__session_files) && $ae_loc.manager_access}
<button
type="button"
class="btn btn-icon btn-sm preset-tonal-surface"
onclick={() =>
toggle_details(
session_obj.event_session_id
)}>
{#if show_details_kv[session_obj.event_session_id]}
<ChevronUp size="1.2em" />
{:else}
<ChevronDown size="1.2em" />
{/if}
</button>
{/if}
</div>
<!-- Mobile Schedule Summary -->
<div
class="text-surface-500 flex flex-wrap gap-x-3 gap-y-1 text-xs md:hidden">
<span class="flex items-center gap-1"
><CalendarDays size="1em" />
{ae_util.iso_datetime_formatter(
session_obj?.start_datetime,
'date_short_month_day'
)}</span>
<span class="flex items-center gap-1"
><Clock size="1em" />
{ae_util.iso_datetime_formatter(
session_obj?.start_datetime,
'time_12_short'
)}</span>
</div>
{#if show_details_kv[session_obj.event_session_id]}
<div
class="bg-surface-500/5 border-surface-500/10 mt-1 rounded-lg border p-2">
{#if show__session_presentations && $ae_loc.manager_access}
<Comp_event_presenter_obj_li
link_to_type={'event_session'}
link_to_id={session_obj?.event_session_id}
display_mode={'minimal'}
{log_lvl} />
{/if}
{#if show__session_files && $ae_loc.manager_access}
<Element_manage_event_file_li
link_to_type={'event_session'}
link_to_id={session_obj?.event_session_id}
allow_basic={true}
allow_moderator={true}
display_mode={'minimal'} />
{/if}
</div>
{/if}
</div>
</td>
<td class="hidden md:table-cell">
<div class="flex flex-col text-xs font-medium">
<span class="text-surface-900-100"
>{ae_util.iso_datetime_formatter(
session_obj?.start_datetime,
'dddd, MMM D'
)}</span>
<span class="text-surface-500 whitespace-nowrap"
>{ae_util.iso_datetime_formatter(
session_obj?.start_datetime,
'time_12_short'
)} {ae_util.iso_datetime_formatter(
session_obj?.end_datetime,
'time_12_short'
)}</span>
</div>
</td>
<td class:hidden={hide__session_location}>
<div class="flex min-w-32 flex-col gap-1">
<span class="text-xs font-semibold"
>{session_obj?.event_location_name ??
'--'}</span>
<div class="flex gap-1">
{#if !hide__launcher_link}
<a
href="/events/{session_obj?.event_id}/launcher/{session_obj?.event_location_id}?session_id={session_obj?.event_session_id}"
class="btn btn-icon btn-xs preset-tonal-tertiary"
title="Svelte Launcher"
><Rocket size="1em" /></a>
{/if}
{#if !hide__location_link}
<a
href="/events/{session_obj?.event_id}/location/{session_obj?.event_location_id}"
class="btn btn-icon btn-xs preset-tonal-surface"
title="Location Details"
><MapPin size="1em" /></a>
{/if}
</div>
</div>
</td>
<td class:hidden={hide__session_poc}>
<div class="flex min-w-32 flex-col text-xs">
{#if session_obj?.poc_person_full_name}
<span
class="flex items-center gap-1 font-bold"
><User size="1em" />
{session_obj.poc_person_full_name}</span>
{#if $ae_loc.trusted_access && session_obj?.poc_person_primary_email}
<a
href="mailto:{session_obj.poc_person_primary_email}"
class="text-primary-500 flex items-center gap-1 hover:underline"
><Mail size="1em" /> Email</a>
{/if}
{:else}
<span class="opacity-30">--</span>
{/if}
</div>
</td>
<td
class:hidden={!$ae_loc.edit_mode ||
!$ae_loc.adv_mode ||
hide__admin}>
<div class="flex gap-1">
<button
type="button"
class="btn btn-icon btn-xs {session_obj?.hide
? 'preset-tonal-error'
: 'preset-tonal-secondary'}"
onclick={async () => {
await api.update_ae_obj({
api_cfg: $ae_api,
obj_type: 'event_session',
obj_id: session_obj.event_session_id,
fields: { hide: !session_obj.hide },
log_lvl: 1
});
events_func.load_ae_obj_id__event_session(
{
api_cfg: $ae_api,
event_session_id:
session_obj.event_session_id
}
);
}}>
{#if session_obj?.hide}<EyeOff
size="1.2em" />{:else}<Eye
size="1.2em" />{/if}
</button>
<button
type="button"
class="btn btn-icon btn-xs {session_obj?.alert
? 'preset-tonal-warning'
: 'preset-tonal-surface'}"
onclick={async () => {
await api.update_ae_obj({
api_cfg: $ae_api,
obj_type: 'event_session',
obj_id: session_obj.event_session_id,
fields: {
alert: !session_obj.alert
},
log_lvl: 1
});
events_func.load_ae_obj_id__event_session(
{
api_cfg: $ae_api,
event_session_id:
session_obj.event_session_id
}
);
}}>
{#if session_obj?.alert}<Bell
size="1.2em" />{:else}<BellOff
size="1.2em" />{/if}
</button>
</div>
</td>
</tr>
{/each}
</tbody>
</table>
{:else}
<div
class="bg-surface-50 dark:bg-surface-900/50 border-surface-300 flex flex-col items-center justify-center rounded-lg border border-dashed p-20 text-center opacity-50">
<FileSearch size="3em" class="mx-auto mb-2 opacity-20" />
<p class="text-xl">No sessions found matching your criteria.</p>
<p class="text-sm">Try adjusting your filters or search terms.</p>
</div>
{/if}
</section>