fix(imports): point to element_data_store_v3 and restore Data Store v3; commit workspace updates

This commit is contained in:
Scott Idem
2026-03-17 18:57:27 -04:00
parent 3038be0686
commit 9fc3ee0198
22 changed files with 841 additions and 91 deletions

View File

@@ -8,7 +8,7 @@
// import { PUBLIC_TESTING } from '$env/static/public';
// console.log(`AE Config - +page.svelte PUBLIC_TESTING:`, PUBLIC_TESTING);
import Element_data_store from '$lib/elements/element_data_store.svelte';
import Element_data_store from '$lib/elements/element_data_store_v3.svelte';
// import { api } from '$lib/api';
import { ae_loc, ae_sess, ae_api, slct, slct_trigger } from '$lib/stores/ae_stores';

View File

@@ -8,7 +8,7 @@
import type { key_val } from '$lib/stores/ae_stores';
import { ae_util } from '$lib/ae_utils/ae_utils';
// import { api } from '$lib/api';
import Element_data_store from '$lib/elements/element_data_store.svelte';
import Element_data_store from '$lib/elements/element_data_store_v3.svelte';
import { liveQuery } from 'dexie';
// import { core_func } from '$lib/ae_core_functions';

View File

@@ -22,7 +22,7 @@
// *** Import Aether specific variables and functions
import type { key_val } from '$lib/stores/ae_stores';
import { ae_loc, ae_sess, ae_api, slct } from '$lib/stores/ae_stores';
import Element_data_store from '$lib/elements/element_data_store.svelte';
import Element_data_store from '$lib/elements/element_data_store_v3.svelte';
import {
events_loc,
events_sess,

View File

@@ -20,7 +20,7 @@
import { events_func } from '$lib/ae_events_functions';
import { api } from '$lib/api/api';
import Element_data_store from '$lib/elements/element_data_store.svelte';
import Element_data_store from '$lib/elements/element_data_store_v3.svelte';
import Comp__events_menu_nav from '../../ae_comp__events_menu_nav.svelte';
import Comp__pres_mgmt_menu_opts from '../../ae_comp__events_menu_opts.svelte';
import AE_Record_Controls from '$lib/ae_elements/AE_Record_Controls.svelte';

View File

@@ -25,7 +25,7 @@
import { events_func } from '$lib/ae_events_functions';
import { api } from '$lib/api/api';
import Element_data_store from '$lib/elements/element_data_store.svelte';
import Element_data_store from '$lib/elements/element_data_store_v3.svelte';
import Comp__events_menu_nav from '../../../../ae_comp__events_menu_nav.svelte';
import AE_Record_Controls from '$lib/ae_elements/AE_Record_Controls.svelte';

View File

@@ -22,7 +22,7 @@
import type { key_val } from '$lib/stores/ae_stores';
// import { ae_util } from '$lib/ae_utils/ae_utils';
import Element_ae_obj_field_editor_v3 from '$lib/elements/element_ae_obj_field_editor_v3.svelte';
import Element_data_store from '$lib/elements/element_data_store.svelte';
import Element_data_store from '$lib/elements/element_data_store_v3.svelte';
let ae_promises: key_val = $state({});
// let ae_tmp: key_val = {};

View File

@@ -14,7 +14,7 @@
import type { key_val } from '$lib/stores/ae_stores';
import { ae_util } from '$lib/ae_utils/ae_utils';
import Element_data_store from '$lib/elements/element_data_store.svelte';
import Element_data_store from '$lib/elements/element_data_store_v3.svelte';
let ae_promises: key_val = {};
let ae_tmp: key_val = {};

View File

@@ -14,7 +14,7 @@
events_slct
} from '$lib/stores/ae_events_stores';
import Element_data_store from '$lib/elements/element_data_store.svelte';
import Element_data_store from '$lib/elements/element_data_store_v3.svelte';
import Comp__events_menu_nav from '../../../ae_comp__events_menu_nav.svelte';
let show_modal = $state(false);

View File

@@ -11,7 +11,7 @@
// Imports (external and then internal)
// import type { key_val } from '$lib/stores/ae_stores';
import { ae_util } from '$lib/ae_utils/ae_utils';
import Element_data_store from '$lib/elements/element_data_store.svelte';
import Element_data_store from '$lib/elements/element_data_store_v3.svelte';
import { liveQuery } from 'dexie';
import { Modal } from 'flowbite-svelte';

View File

@@ -17,7 +17,7 @@
import type { key_val } from '$lib/stores/ae_stores';
import { ae_util } from '$lib/ae_utils/ae_utils';
import { api } from '$lib/api/api';
import Element_data_store from '$lib/elements/element_data_store.svelte';
import Element_data_store from '$lib/elements/element_data_store_v3.svelte';
import {
ae_loc,

View File

@@ -25,7 +25,7 @@
import { events_func } from '$lib/ae_events_functions';
import { api } from '$lib/api/api';
import Element_data_store from '$lib/elements/element_data_store.svelte';
import Element_data_store from '$lib/elements/element_data_store_v3.svelte';
import Comp__events_menu_nav from '../../../../ae_comp__events_menu_nav.svelte';
import AE_Record_Controls from '$lib/ae_elements/AE_Record_Controls.svelte';

View File

@@ -14,7 +14,7 @@
events_slct
} from '$lib/stores/ae_events_stores';
import Element_data_store from '$lib/elements/element_data_store.svelte';
import Element_data_store from '$lib/elements/element_data_store_v3.svelte';
import Comp__events_menu_nav from '../../../ae_comp__events_menu_nav.svelte';
let show_modal = $state(false);

View File

@@ -29,7 +29,7 @@
import { events_func } from '$lib/ae_events_functions';
// Import components and elements
import Element_data_store from '$lib/elements/element_data_store.svelte';
import Element_data_store from '$lib/elements/element_data_store_v3.svelte';
import { Check, CheckCircle, LoaderCircle, TriangleAlert, X } from '@lucide/svelte';
// Local Variables
let ae_promises: key_val = $state({});

View File

@@ -27,7 +27,7 @@
import { events_func } from '$lib/ae_events_functions';
import { api } from '$lib/api/api';
import Element_data_store from '$lib/elements/element_data_store.svelte';
import Element_data_store from '$lib/elements/element_data_store_v3.svelte';
import Sign_in_out from '../../../sign_in_out.svelte';
import Comp__events_menu_nav from '../../../../ae_comp__events_menu_nav.svelte';
import AE_Record_Controls from '$lib/ae_elements/AE_Record_Controls.svelte';

View File

@@ -23,7 +23,7 @@
import { ae_loc, ae_api } from '$lib/stores/ae_stores';
import { events_func } from '$lib/ae_events_functions';
import Element_data_store from '$lib/elements/element_data_store.svelte';
import Element_data_store from '$lib/elements/element_data_store_v3.svelte';
import Comp__event_obj_qry from './ae_idaa_comp__event_obj_qry.svelte';
import Comp__event_obj_li_wrapper from './ae_idaa_comp__event_obj_li_wrapper.svelte';

View File

@@ -160,7 +160,9 @@
meta_json: {
duration: meeting_duration,
participants: participants_array,
participant_count: participants_array.length
participant_count: participants_array.length,
// Verified Novi UUID of the moderator who started this log
moderator_novi_uuid: $idaa_loc.novi_uuid ?? null
}
};
try {
@@ -194,13 +196,15 @@
api.on('participantLeft', (participant: { id: string }) => {
console.log('Jitsi Event: participantLeft', participant);
if (meeting_participants.has(participant.id)) {
// Capture name before removing from map — it won't be available after delete
const p_info = meeting_participants.get(participant.id);
if (p_info) {
meeting_participants.delete(participant.id);
update_primary_activity_log();
// NOTE: We also want to log this as a discrete event
create_discrete_activity_log('jitsi_meeting_participant_left', 'participantLeft', {
attendee_id: participant.id,
full_name: p_info.displayName,
});
}
});
@@ -232,7 +236,16 @@
api.on('videoConferenceLeft', () => {
console.log('Jitsi Event: videoConferenceLeft');
if (duration_timer_id) clearInterval(duration_timer_id);
// meeting_duration = '00:00:00';
// Do a final update to the primary log so it captures the true end state,
// then log the meeting end as a discrete event for the timeline.
if (is_moderator && primary_activity_log_id) {
update_primary_activity_log();
create_discrete_activity_log('jitsi_meeting_end', 'videoConferenceLeft', {
final_duration: meeting_duration,
final_participant_count: meeting_participants.size,
});
}
});
api.on('readyToClose', () => {

View File

@@ -21,7 +21,7 @@
} from '$lib/stores/ae_stores';
import { core_func } from '$lib/ae_core/ae_core_functions';
import { idaa_loc, idaa_sess, idaa_slct } from '$lib/stores/ae_idaa_stores';
import Element_data_store from '$lib/elements/element_data_store.svelte';
import Element_data_store from '$lib/elements/element_data_store_v3.svelte';
interface Props {
/** @type {import('./$types').LayoutData} */

View File

@@ -1,120 +1,428 @@
<script lang="ts">
import { ae_util } from '$lib/ae_utils/ae_utils';
interface MeetingEvent {
timestamp: string;
action: string;
details: { full_name?: string };
}
interface MeetingParticipant {
displayName: string;
role: string;
}
interface MeetingReport {
meeting_id: string;
room_name: string;
start_time: string;
final_duration: string;
final_participant_count: number;
final_participants: MeetingParticipant[];
events: MeetingEvent[];
}
interface Props {
data: any;
data: { streamed: { meetings: Promise<MeetingReport[]> } };
}
let { data }: Props = $props();
// --- Data state ---
// Resolve the streamed promise into reactive state so we can filter and export it.
let meetings_all = $state<MeetingReport[]>([]);
let meetings_loading = $state(true);
let meetings_error = $state<string | null>(null);
$effect(() => {
meetings_loading = true;
meetings_error = null;
data.streamed.meetings
.then((m: MeetingReport[]) => {
meetings_all = m ?? [];
meetings_loading = false;
})
.catch((err: Error) => {
meetings_error = err.message;
meetings_loading = false;
});
});
// --- Filter state ---
let filter_min_participants = $state(1);
let filter_room_name = $state('');
let filter_date_from = $state('');
let filter_date_to = $state('');
let filters_are_modified = $derived(
filter_min_participants !== 1 ||
filter_room_name !== '' ||
filter_date_from !== '' ||
filter_date_to !== ''
);
function reset_filters() {
filter_min_participants = 1;
filter_room_name = '';
filter_date_from = '';
filter_date_to = '';
}
// --- Derived: filtered meetings ---
let meetings_filtered = $derived.by(() => {
return meetings_all.filter((m) => {
if ((m.final_participant_count ?? 0) < filter_min_participants) return false;
if (filter_room_name && !m.room_name?.toLowerCase().includes(filter_room_name.toLowerCase())) return false;
if (filter_date_from) {
if (Date.parse(m.start_time) < Date.parse(filter_date_from)) return false;
}
if (filter_date_to) {
// Include full end-of-day by appending T23:59:59 to the date string
if (Date.parse(m.start_time) > Date.parse(filter_date_to + 'T23:59:59.999')) return false;
}
return true;
});
});
// --- Summary stats ---
function parse_duration_seconds(d: string): number {
if (!d) return 0;
const parts = d.split(':').map(Number);
return (parts[0] ?? 0) * 3600 + (parts[1] ?? 0) * 60 + (parts[2] ?? 0);
}
function format_seconds(total: number): string {
const h = Math.floor(total / 3600).toString().padStart(2, '0');
const m = Math.floor((total % 3600) / 60).toString().padStart(2, '0');
const s = Math.floor(total % 60).toString().padStart(2, '0');
return `${h}:${m}:${s}`;
}
let summary = $derived.by(() => {
const count = meetings_filtered.length;
const total_participants = meetings_filtered.reduce((sum, m) => sum + (m.final_participant_count ?? 0), 0);
const total_secs = meetings_filtered.reduce((sum, m) => sum + parse_duration_seconds(m.final_duration), 0);
const avg_secs = count > 0 ? Math.round(total_secs / count) : 0;
return {
count,
total_participants,
avg_duration: format_seconds(avg_secs),
total_duration: format_seconds(total_secs)
};
});
// --- Accordion state ---
let open_accordions = $state<{ [key: string]: boolean }>({});
function toggle_accordion(meeting_id: string) {
open_accordions[meeting_id] = !open_accordions[meeting_id];
}
// --- Export ---
function download_file(content: string, filename: string, mime: string) {
const blob = new Blob([content], { type: mime });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
function export_csv() {
const rows: string[][] = [
['Meeting ID', 'Room Name', 'Start Time', 'Duration', 'Participant Count']
];
for (const m of meetings_filtered) {
rows.push([
m.meeting_id ?? '',
m.room_name ?? '',
m.start_time ? new Date(m.start_time).toISOString() : '',
m.final_duration ?? '',
String(m.final_participant_count ?? 0)
]);
}
const csv = rows
.map((r) => r.map((cell) => `"${String(cell).replace(/"/g, '""')}"`).join(','))
.join('\n');
download_file(csv, 'jitsi_meeting_report.csv', 'text/csv;charset=utf-8;');
}
function export_json() {
download_file(
JSON.stringify(meetings_filtered, null, 2),
'jitsi_meeting_report.json',
'application/json'
);
}
</script>
<svelte:head>
<title>&AElig;: Jitsi Meeting Reports</title>
</svelte:head>
<div class="p-4 space-y-4">
<h1 class="h1">Jitsi Meeting Reports</h1>
<div class="p-4 space-y-4 w-full max-w-5xl">
{#await data.streamed.meetings}
<div class="space-y-4 animate-pulse">
<div class="h-12 w-full bg-gray-200 dark:bg-gray-700 rounded"></div>
<div class="h-12 w-full bg-gray-200 dark:bg-gray-700 rounded"></div>
<div class="h-12 w-full bg-gray-200 dark:bg-gray-700 rounded"></div>
<div class="h-12 w-full bg-gray-200 dark:bg-gray-700 rounded"></div>
<!-- Page header + export buttons -->
<div class="flex flex-row flex-wrap items-center justify-between gap-2">
<h1 class="text-xl font-bold">Jitsi Meeting Reports</h1>
<div class="flex gap-2">
<button
type="button"
onclick={export_csv}
disabled={meetings_filtered.length === 0}
title="Export filtered meetings as CSV"
class="btn btn-sm preset-tonal-primary disabled:opacity-40"
>
<span class="fas fa-file-csv" aria-hidden="true"></span>
Export CSV
</button>
<button
type="button"
onclick={export_json}
disabled={meetings_filtered.length === 0}
title="Export filtered meetings as JSON"
class="btn btn-sm preset-tonal-surface border border-surface-200-800 disabled:opacity-40"
>
<span class="fas fa-file-code" aria-hidden="true"></span>
Export JSON
</button>
</div>
{:then meetings}
{#if meetings && meetings.length > 0}
</div>
<!-- Filter bar -->
<div class="bg-surface-100-900 border border-surface-200-800 rounded-xl p-3 flex flex-row flex-wrap gap-3 items-end">
<div>
<label for="filter_min_p" class="block text-xs uppercase tracking-wide opacity-40 mb-1">
Min. Participants
</label>
<input
type="number"
id="filter_min_p"
min="0"
bind:value={filter_min_participants}
class="border border-surface-200-800 rounded px-2 py-1 w-20 bg-surface-50-950"
/>
</div>
<div>
<label for="filter_room" class="block text-xs uppercase tracking-wide opacity-40 mb-1">
Room Name
</label>
<input
type="text"
id="filter_room"
placeholder="Search rooms..."
bind:value={filter_room_name}
class="border border-surface-200-800 rounded px-2 py-1 bg-surface-50-950"
/>
</div>
<div>
<label for="filter_date_from" class="block text-xs uppercase tracking-wide opacity-40 mb-1">
From
</label>
<input
type="date"
id="filter_date_from"
bind:value={filter_date_from}
class="border border-surface-200-800 rounded px-2 py-1 bg-surface-50-950"
/>
</div>
<div>
<label for="filter_date_to" class="block text-xs uppercase tracking-wide opacity-40 mb-1">
To
</label>
<input
type="date"
id="filter_date_to"
bind:value={filter_date_to}
class="border border-surface-200-800 rounded px-2 py-1 bg-surface-50-950"
/>
</div>
{#if filters_are_modified}
<button
type="button"
onclick={reset_filters}
class="btn btn-sm preset-tonal-surface border border-surface-200-800 self-end"
title="Reset all filters to defaults"
>
<span class="fas fa-times" aria-hidden="true"></span>
Reset
</button>
{/if}
</div>
{#if meetings_loading}
<!-- Loading skeleton -->
<div class="space-y-2 animate-pulse" role="status" aria-live="polite" aria-label="Loading meeting reports">
{#each [1, 2, 3, 4] as _, i (i)}
<div class="h-14 w-full bg-surface-200-800 rounded-xl"></div>
{/each}
</div>
{:else if meetings_error}
<!-- Error state -->
<div class="bg-error-100 border border-error-300 rounded-xl p-4">
<div class="font-bold">Error Loading Reports</div>
<p class="mt-1">An error occurred while fetching the meeting reports:</p>
<pre class="mt-2 text-xs overflow-auto whitespace-pre-wrap">{meetings_error}</pre>
</div>
{:else}
<!-- Summary stats -->
{#if meetings_all.length > 0}
<div class="grid grid-cols-2 sm:grid-cols-4 gap-2">
<div class="bg-surface-100-900 border border-surface-200-800 rounded-xl p-3 text-center">
<div class="text-2xl font-bold">{summary.count}</div>
<div class="text-xs uppercase tracking-wide opacity-40">Meetings Shown</div>
</div>
<div class="bg-surface-100-900 border border-surface-200-800 rounded-xl p-3 text-center">
<div class="text-2xl font-bold">{summary.total_participants}</div>
<div class="text-xs uppercase tracking-wide opacity-40">Total Participants</div>
</div>
<div class="bg-surface-100-900 border border-surface-200-800 rounded-xl p-3 text-center">
<div class="text-2xl font-bold font-mono text-lg">{summary.avg_duration}</div>
<div class="text-xs uppercase tracking-wide opacity-40">Avg Duration</div>
</div>
<div class="bg-surface-100-900 border border-surface-200-800 rounded-xl p-3 text-center">
<div class="text-2xl font-bold font-mono text-lg">{summary.total_duration}</div>
<div class="text-xs uppercase tracking-wide opacity-40">Total Duration</div>
</div>
</div>
{/if}
<!-- Meeting list -->
{#if meetings_filtered.length > 0}
<div class="space-y-2">
{#each meetings as meeting (meeting.meeting_id)}
<div class="card card-hover">
{#each meetings_filtered as meeting (meeting.meeting_id)}
<div class="bg-surface-50-900 border border-surface-200-800 rounded-xl overflow-hidden">
<!-- Accordion header -->
<!-- svelte-ignore a11y_click_events_have_key_events -->
<!-- svelte-ignore a11y_no_static_element_interactions -->
<header class="card-header p-2 cursor-pointer" onclick={() => toggle_accordion(meeting.meeting_id)}>
<div class="flex justify-between items-center w-full">
<div class="flex-1">
<!-- NOTE: Normally I would the "h3" class, but Novi classes make things look odd. -->
<h3 class="text-base">{meeting.room_name}</h3>
<p class="text-sm text-gray-500">{new Date(meeting.start_time).toLocaleString()}</p>
<div
class="p-3 cursor-pointer hover:bg-surface-100-900 transition-colors duration-200"
onclick={() => toggle_accordion(meeting.meeting_id)}
>
<div class="flex items-center gap-2">
<div class="flex-1 min-w-0">
<div class="font-semibold truncate">{meeting.room_name}</div>
<div class="text-sm opacity-60">{new Date(meeting.start_time).toLocaleString()}</div>
</div>
<div class="flex-none flex items-center space-x-4 text-sm mr-4">
<span>Duration: {meeting.final_duration}</span>
<span>Participants: {meeting.final_participant_count}</span>
</div>
<div class="flex-none">
<span class="transition-transform duration-200" class:rotate-180={open_accordions[meeting.meeting_id]}>
<svg xmlns="https://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6"/></svg>
<div class="hidden sm:flex items-center gap-4 text-sm opacity-60 flex-none">
<span title="Duration">
<span class="fas fa-clock mr-1" aria-hidden="true"></span>
{meeting.final_duration}
</span>
<span title="Participant count">
<span class="fas fa-users mr-1" aria-hidden="true"></span>
{meeting.final_participant_count}
</span>
</div>
<div class="flex-none pl-2">
<span
class="fas transition-transform duration-200 inline-block"
class:fa-chevron-down={!open_accordions[meeting.meeting_id]}
class:fa-chevron-up={open_accordions[meeting.meeting_id]}
aria-hidden="true"
></span>
</div>
</div>
</header>
<!-- Mobile stats row -->
<div class="flex gap-4 text-sm opacity-60 mt-1 sm:hidden">
<span><span class="fas fa-clock mr-1" aria-hidden="true"></span>{meeting.final_duration}</span>
<span>
<span class="fas fa-users mr-1" aria-hidden="true"></span>
{meeting.final_participant_count}
{meeting.final_participant_count === 1 ? 'participant' : 'participants'}
</span>
</div>
</div>
<!-- Accordion body -->
{#if open_accordions[meeting.meeting_id]}
<div class="p-4 border-t border-gray-200 dark:border-gray-700 grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="border-t border-surface-200-800 p-4 grid grid-cols-1 md:grid-cols-2 gap-4">
<!-- Event Timeline -->
<div>
<!-- NOTE: Normally I would the "h4" class, but Novi classes make things look odd. -->
<h4 class="text-base">Event Timeline</h4>
<div class="text-xs uppercase tracking-wide opacity-40 mb-2">Event Timeline</div>
{#if meeting.events && meeting.events.length > 0}
<ul class="list-disc list-inside space-y-2 mt-2">
<ul class="space-y-1">
{#each meeting.events as event, i (i)}
<li>
<span class="font-mono text-xs">[{new Date(event.timestamp).toLocaleTimeString()}]</span>
<span class="font-semibold">{ae_util.to_title_case(event.action.replace('jitsi_meeting_', ''))}</span>
{#if event.details.full_name}
- by {event.details.full_name}
{/if}
<li class="flex gap-2 items-start text-sm">
<span class="font-mono text-xs opacity-60 whitespace-nowrap mt-0.5">
[{new Date(event.timestamp).toLocaleTimeString()}]
</span>
<span>
<span class="font-semibold">
{ae_util.to_title_case(event.action.replace('jitsi_meeting_', ''))}
</span>
{#if event.details?.full_name}
<span class="opacity-60">{event.details.full_name}</span>
{/if}
</span>
</li>
{/each}
</ul>
{:else}
<p class="text-gray-500 italic mt-2">No discrete events recorded.</p>
<p class="text-sm opacity-60 italic">No discrete events recorded.</p>
{/if}
</div>
<!-- Final Participants -->
<div>
<!-- NOTE: Normally I would the "h4" class, but Novi classes make things look odd. -->
<h4 class="text-base">Final Participants</h4>
<div class="text-xs uppercase tracking-wide opacity-40 mb-2">
Final Participants ({meeting.final_participant_count})
</div>
{#if meeting.final_participants && meeting.final_participants.length > 0}
<div class="table-container mt-2">
<table class="table table-hover">
<thead>
<tr>
<th>Name</th>
<th>Role</th>
<table class="w-full text-sm">
<thead>
<tr class="border-b border-surface-200-800">
<th class="text-left py-1 font-medium opacity-60">Name</th>
<th class="text-left py-1 font-medium opacity-60">Role</th>
</tr>
</thead>
<tbody>
{#each meeting.final_participants as participant (participant.displayName)}
<tr class="border-b border-surface-200-800 transition-colors duration-200 hover:bg-surface-100-900">
<td class="py-1">{participant.displayName}</td>
<td class="py-1">{ae_util.to_title_case(participant.role)}</td>
</tr>
</thead>
<tbody>
{#each meeting.final_participants as participant (participant.displayName)}
<tr>
<td>{participant.displayName}</td>
<td>{ae_util.to_title_case(participant.role)}</td>
</tr>
{/each}
</tbody>
</table>
</div>
{/each}
</tbody>
</table>
{:else}
<p class="text-gray-500 italic mt-2">No participant data available.</p>
<p class="text-sm opacity-60 italic">No participant data available.</p>
{/if}
</div>
</div>
{/if}
</div>
{/each}
</div>
{:else}
<div class="card p-4 text-center">
<h3 class="h3">No Meeting Reports Found</h3>
<p>There are no Jitsi activity logs to display.</p>
<!-- Empty state -->
<div class="bg-surface-100-900 border border-surface-200-800 rounded-xl p-6 text-center">
{#if meetings_all.length > 0}
<div class="font-semibold">No meetings match the current filters</div>
<p class="text-sm opacity-60 mt-1">Try lowering the minimum participants or clearing the date range.</p>
<button
type="button"
onclick={reset_filters}
class="btn btn-sm preset-tonal-surface border border-surface-200-800 mt-3"
>
<span class="fas fa-times mr-1" aria-hidden="true"></span>
Reset Filters
</button>
{:else}
<div class="font-semibold">No Meeting Reports Found</div>
<p class="text-sm opacity-60 mt-1">There are no Jitsi activity logs to display.</p>
{/if}
</div>
{/if}
{:catch error}
<div class="card p-4 bg-red-100 text-red-900 border-l-4 border-red-500">
<h3 class="h3 font-bold">Error Loading Reports</h3>
<p>An error occurred while fetching the meeting reports:</p>
<pre class="mt-2 text-xs overflow-auto">{error.message}</pre>
</div>
{/await}
{/if}
</div>

View File

@@ -17,7 +17,7 @@
journals_slct,
journals_trig
} from '$lib/ae_journals/ae_journals_stores';
import Element_data_store from '$lib/elements/element_data_store.svelte';
import Element_data_store from '$lib/elements/element_data_store_v3.svelte';
import Help_tech from '$lib/app_components/e_app_help_tech.svelte';
// *** Setup Svelte properties

View File

@@ -1,5 +1,5 @@
<script lang="ts">
import AE_Element_Data_Store from '$lib/elements/element_data_store.svelte';
import AE_Element_Data_Store from '$lib/elements/element_data_store_v3.svelte';
import { ae_loc } from '$lib/stores/ae_stores';
import { db_core } from '$lib/ae_core/db_core';
import { RefreshCw, Trash2 } from '@lucide/svelte';