- 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>
384 lines
19 KiB
Svelte
384 lines
19 KiB
Svelte
<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">×</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>
|