From 25e35f6f96a16c5ff6d28d46cead9aaea7eee5b6 Mon Sep 17 00:00:00 2001 From: Scott Idem Date: Wed, 6 May 2026 14:29:27 -0400 Subject: [PATCH] Add Jitsi participant copy actions Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../CLIENT__IDAA_and_customized_mods.md | 1 + .../idaa/(idaa)/jitsi_reports/+page.svelte | 84 +++++++++++++++++-- 2 files changed, 78 insertions(+), 7 deletions(-) diff --git a/documentation/CLIENT__IDAA_and_customized_mods.md b/documentation/CLIENT__IDAA_and_customized_mods.md index 9bf5729e..6001e11b 100644 --- a/documentation/CLIENT__IDAA_and_customized_mods.md +++ b/documentation/CLIENT__IDAA_and_customized_mods.md @@ -533,6 +533,7 @@ Shown above the meeting list when data is loaded. Stats reflect the **filtered + - **Total Duration** — sum of all session durations (HH:MM:SS) In grouped view, each room header also shows its own subtotals (meeting count, unique participants by Novi UUID when available). +Each meeting instance now includes a **Copy names** button so staff can grab the full participant list for pasting into follow-up reports. ### Caching / Load Behavior diff --git a/src/routes/idaa/(idaa)/jitsi_reports/+page.svelte b/src/routes/idaa/(idaa)/jitsi_reports/+page.svelte index 59ebcccc..c235f616 100644 --- a/src/routes/idaa/(idaa)/jitsi_reports/+page.svelte +++ b/src/routes/idaa/(idaa)/jitsi_reports/+page.svelte @@ -358,6 +358,41 @@ function is_room_open(room_name: string): boolean { // --- URL Builder --- let show_url_builder = $state(false); +let copied_participants_meeting_id = $state(null); +let copied_participants_timeout: ReturnType | null = null; + +function build_participant_copy_text(participants: MeetingParticipant[]): string { + return participants + .map((participant) => + participant.role === 'moderator' + ? `Mod: ${participant.displayName}` + : participant.displayName + ) + .join('\n'); +} + +async function copy_participants( + meeting_id: string, + participants: MeetingParticipant[] +) { + const text = build_participant_copy_text(participants); + if (!text) return; + + try { + await navigator.clipboard.writeText(text); + copied_participants_meeting_id = meeting_id; + if (copied_participants_timeout) { + clearTimeout(copied_participants_timeout); + } + copied_participants_timeout = setTimeout(() => { + if (copied_participants_meeting_id === meeting_id) { + copied_participants_meeting_id = null; + } + }, 2000); + } catch (err) { + console.warn('Failed to copy participants to clipboard.', err); + } +} // --- Export --- function download_file(content: string, filename: string, mime: string) { @@ -776,6 +811,7 @@ function export_json() { {@const mods = m.real_participants.filter((p) => p.role === 'moderator')} {@const others = m.real_participants.filter((p) => p.role !== 'moderator')} {@const all_names = m.real_participants.map((p) => `${p.displayName} (${p.role})`).join('\n')} + {@const participant_copy_text = build_participant_copy_text(m.real_participants)} — {:else} -
+
{#if mods.length > 0} -
+
Mod: {mods.map((p) => p.displayName).join(', ')}
{/if} {#if others.length > 0} -
- {others.slice(0, 5).map((p) => p.displayName).join(', ')}{others.length > 5 ? ` +${others.length - 5} more` : ''} +
+ {others.map((p) => p.displayName).join(', ')}
{/if} +
{/if} @@ -993,9 +1045,27 @@ function export_json() {
-
- Final Participants ({meeting.real_participant_count}) +
+
+ Final Participants ({meeting.real_participant_count}) +
+
{#if meeting.real_participants && meeting.real_participants.length > 0}