Files
OSIT-AE-App-Svelte/src/routes/events/ae_comp__event_session_obj_li.svelte
Scott Idem 811afb2fd7 refactor(sessions): standardize reactive search and fix list layout
- Migrated Session search to the debounced  pattern with Search Guards and shared observables.
- Implemented 'Remote First' toggle support (Edit Mode only) for background-only revalidation.
- Resolved 'each_key_duplicate' crash and fixed icon scaling issues in the session list.
- Restored missing session alert visibility for managers.
- Standardized store initialization to prevent reactivity loops.
2026-01-27 18:41:19 -05:00

409 lines
14 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;
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,
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 {
LoaderCircle,
Presentation,
Check,
Eye,
EyeOff,
Mail,
MapPin,
User,
ChevronDown,
ChevronUp,
SendHorizontal,
Rocket,
Bell,
BellOff,
Edit,
FileSearch,
Search,
CalendarDays,
Clock
} from 'lucide-svelte';
import Element_ae_crud_v2 from '$lib/elements/element_ae_crud_v2.svelte';
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 { core_func } from '$lib/ae_core/ae_core_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)
let visible_session_obj_li = $derived((() => {
const list = $lq__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 px-0.5 py-2 space-y-2 min-w-full w-full container overflow-x-auto {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="animate-spin mb-2" />
<p>Loading sessions...</p>
</div>
{:else if visible_session_obj_li.length > 0}
<header class="w-full flex flex-row gap-2 items-center justify-start mb-2 px-2">
<h2 class="text-sm text-gray-500 font-normal"> Sessions: </h2>
<span class="badge preset-tonal-success font-bold text-lg px-3 py-1">
{visible_session_obj_li.length}<span class="text-gray-400 dark:text-gray-600">&times;</span>
</span>
</header>
<table class="table table-auto table-striped w-full">
<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" class:opacity-50={session_obj?.hide} class:variant-soft-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="flex flex-row gap-2 items-center font-bold text-lg hover:text-primary-500 text-left"
>
{#if session_obj?.hide}
<EyeOff size="1em" class="text-gray-400 flex-none" />
{:else}
<Presentation size="1em" class="text-primary-500 flex-none" />
{/if}
<span>{session_obj?.name}</span>
{#if session_obj?.file_count_all}
<span class="badge preset-tonal-success flex items-center gap-1 text-xs py-0 px-1">
<Check size="1em" />
{session_obj.file_count_all}
</span>
{/if}
</a>
{#if (show__session_presentations || show__session_files) && $ae_loc.manager_access}
<button
class="btn btn-icon btn-sm variant-soft-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="md:hidden text-xs text-surface-500 flex flex-wrap gap-x-3 gap-y-1">
<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="p-2 bg-surface-500/5 rounded-lg border border-surface-500/10 mt-1">
{#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 flex-col gap-1 min-w-32">
<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 flex-col text-xs min-w-32">
{#if session_obj?.poc_person_full_name}
<span class="font-bold flex items-center gap-1"><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 hover:underline flex items-center gap-1"><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
class="btn btn-icon btn-xs {session_obj?.hide ? 'preset-tonal-error' : 'preset-tonal-secondary'}"
onclick={() => core_func.update_ae_obj_id_crud_v2({ api_cfg: $ae_api, object_type: 'event_session', object_id: session_obj.event_session_id, field_name: 'hide', new_field_value: !session_obj.hide, log_lvl: 1 })}
>
{#if session_obj?.hide}<EyeOff size="1.2em" />{:else}<Eye size="1.2em" />{/if}
</button>
<button
class="btn btn-icon btn-xs {session_obj?.alert ? 'preset-tonal-warning' : 'preset-tonal-surface'}"
onclick={() => core_func.update_ae_obj_id_crud_v2({ api_cfg: $ae_api, object_type: 'event_session', object_id: session_obj.event_session_id, field_name: 'alert', new_field_value: !session_obj.alert, log_lvl: 1 })}
>
{#if session_obj?.alert}<Bell size="1.2em" />{:else}<BellOff size="1.2em" />{/if}
</button>
</div>
</td>
</tr>
{/each}
</tbody>
</table>
{:else}
<div class="flex flex-col items-center justify-center p-20 opacity-50 text-center bg-surface-50 dark:bg-surface-900/50 rounded-lg border border-dashed border-surface-300">
<FileSearch size="3em" class="mb-2 opacity-20 mx-auto" />
<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>