From b7483e7ff03ee27f60b89fd9f8385bb574a466e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roman=20Kr=C4=8Dek?= Date: Sat, 12 Jul 2025 13:49:31 +0200 Subject: [PATCH 1/6] Get rid of old APIs --- src/routes/api/auth/refresh/+server.ts | 30 ------------------- src/routes/api/events/+server.ts | 0 .../api/sheets/[sheetId]/data/+server.ts | 22 -------------- src/routes/api/sheets/recent/+server.ts | 20 ------------- 4 files changed, 72 deletions(-) delete mode 100644 src/routes/api/auth/refresh/+server.ts delete mode 100644 src/routes/api/events/+server.ts delete mode 100644 src/routes/api/sheets/[sheetId]/data/+server.ts delete mode 100644 src/routes/api/sheets/recent/+server.ts diff --git a/src/routes/api/auth/refresh/+server.ts b/src/routes/api/auth/refresh/+server.ts deleted file mode 100644 index cfbd733..0000000 --- a/src/routes/api/auth/refresh/+server.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { json } from '@sveltejs/kit'; -import type { RequestHandler } from './$types'; -import { getOAuthClient } from '$lib/google/auth/server.js'; - -export const POST: RequestHandler = async ({ request }) => { - try { - const { refreshToken } = await request.json(); - - if (!refreshToken) { - return json({ error: 'Refresh token is required' }, { status: 400 }); - } - - const oauth = getOAuthClient(); - oauth.setCredentials({ refresh_token: refreshToken }); - - const { credentials } = await oauth.refreshAccessToken(); - - if (!credentials.access_token) { - return json({ error: 'Failed to refresh token' }, { status: 500 }); - } - - return json({ - accessToken: credentials.access_token, - expiresIn: credentials.expiry_date - }); - } catch (error) { - console.error('Error refreshing access token:', error); - return json({ error: 'Failed to refresh access token' }, { status: 500 }); - } -}; diff --git a/src/routes/api/events/+server.ts b/src/routes/api/events/+server.ts deleted file mode 100644 index e69de29..0000000 diff --git a/src/routes/api/sheets/[sheetId]/data/+server.ts b/src/routes/api/sheets/[sheetId]/data/+server.ts deleted file mode 100644 index 9e9b4b0..0000000 --- a/src/routes/api/sheets/[sheetId]/data/+server.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { json } from '@sveltejs/kit'; -import type { RequestHandler } from './$types'; -import { googleSheetsServer } from '$lib/google/sheets/server'; - -export const GET: RequestHandler = async ({ params, request }) => { - try { - const { sheetId } = params; - const authHeader = request.headers.get('authorization'); - - if (!authHeader?.startsWith('Bearer ')) { - return json({ error: 'Missing or invalid authorization header' }, { status: 401 }); - } - - const refreshToken = authHeader.slice(7); - const sheetData = await googleSheetsServer.getSpreadsheetData(refreshToken, sheetId, 'A1:Z10'); - - return json(sheetData); - } catch (error) { - console.error('Error fetching spreadsheet data:', error); - return json({ error: 'Failed to fetch spreadsheet data' }, { status: 500 }); - } -}; diff --git a/src/routes/api/sheets/recent/+server.ts b/src/routes/api/sheets/recent/+server.ts deleted file mode 100644 index d3eba61..0000000 --- a/src/routes/api/sheets/recent/+server.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { json } from '@sveltejs/kit'; -import type { RequestHandler } from './$types'; -import { googleSheetsServer } from '$lib/google/sheets/server'; - -export const GET: RequestHandler = async ({ request }) => { - try { - const authHeader = request.headers.get('authorization'); - if (!authHeader?.startsWith('Bearer ')) { - return json({ error: 'Missing or invalid authorization header' }, { status: 401 }); - } - - const refreshToken = authHeader.slice(7); - const spreadsheets = await googleSheetsServer.getRecentSpreadsheets(refreshToken, 20); - - return json(spreadsheets); - } catch (error) { - console.error('Error fetching recent spreadsheets:', error); - return json({ error: 'Failed to fetch spreadsheets' }, { status: 500 }); - } -}; -- 2.49.1 From 8247cd33a63b1fa3c0695aceb522c5529f1d2957 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roman=20Kr=C4=8Dek?= Date: Sat, 12 Jul 2025 14:10:33 +0200 Subject: [PATCH 2/6] Got rid of more old APIs --- .github/copilot-instructions.md | 3 + .../private/api/{ => google}/gmail/+server.ts | 0 .../private/events/event/view/+page.svelte | 2 +- src/routes/private/scanner/+page.svelte | 119 ++++++++---------- 4 files changed, 53 insertions(+), 71 deletions(-) rename src/routes/private/api/{ => google}/gmail/+server.ts (100%) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 084af10..a009208 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -1,4 +1,7 @@ GitHub Copilot Instructions for This Repository + +If you have any questions, always ask me first. + Use Svelte 5 runes exclusively Declare reactive state with $state(); derive values with $derived(); run side-effect logic with $effect() etc. diff --git a/src/routes/private/api/gmail/+server.ts b/src/routes/private/api/google/gmail/+server.ts similarity index 100% rename from src/routes/private/api/gmail/+server.ts rename to src/routes/private/api/google/gmail/+server.ts diff --git a/src/routes/private/events/event/view/+page.svelte b/src/routes/private/events/event/view/+page.svelte index 51fd7f2..b671f38 100644 --- a/src/routes/private/events/event/view/+page.svelte +++ b/src/routes/private/events/event/view/+page.svelte @@ -197,7 +197,7 @@ try { // Send all emails in batch - const response = await fetch('/private/api/gmail', { + const response = await fetch('/private/api/google/gmail', { method: 'POST', headers: { 'Content-Type': 'application/json' diff --git a/src/routes/private/scanner/+page.svelte b/src/routes/private/scanner/+page.svelte index 8220dab..36fad5f 100644 --- a/src/routes/private/scanner/+page.svelte +++ b/src/routes/private/scanner/+page.svelte @@ -7,21 +7,21 @@ import { ScanState, defaultTicketData } from '$lib/types/types'; let { data } = $props(); - let scanned_id = $state(""); + let scanned_id = $state(''); let ticket_data = $state(defaultTicketData); let scan_state = $state(ScanState.scanning); - + // Events related state interface Event { id: string; name: string; date: string; } - + let events = $state([]); - let selectedEventId = $state(""); + let selectedEventId = $state(''); let isLoadingEvents = $state(true); - let eventsError = $state(""); + let eventsError = $state(''); onMount(async () => { await loadEvents(); @@ -30,7 +30,7 @@ async function loadEvents() { isLoadingEvents = true; eventsError = ''; - + try { const { data: eventsData, error } = await data.supabase .from('events') @@ -39,7 +39,7 @@ if (error) throw error; events = eventsData || []; - + // If there are events, select the first one by default if (events.length > 0) { selectedEventId = events[0].id; @@ -54,20 +54,20 @@ // Process a scanned QR code $effect(() => { - if (scanned_id === "") return; + if (scanned_id === '') return; scan_state = ScanState.scanning; - console.log("Scanned ID:", scanned_id); + console.log('Scanned ID:', scanned_id); data.supabase .from('participants') .select(`*, event ( id, name ), scanned_by ( id, display_name )`) .eq('id', scanned_id) - .then(response => { + .then((response) => { if (response.data && response.data.length > 0) { const participant = response.data[0]; ticket_data = participant; - + // Check if the participant belongs to the selected event if (selectedEventId && participant.event.id !== selectedEventId) { scan_state = ScanState.wrong_event; @@ -81,73 +81,52 @@ ticket_data = defaultTicketData; scan_state = ScanState.scan_failed; } - + // Reset the scanned_id after 3 seconds to allow for a new scan setTimeout(() => { - scanned_id = ""; + scanned_id = ''; }, 3000); }); }); -
-

Code Scanner

+

Code Scanner

- -
-

Select Event

- {#if isLoadingEvents} -
-
-
- {:else if eventsError} -
- {eventsError} - -
- {:else if events.length === 0} -

No events found

- {:else} - - {/if} -
- - -
- -
- - -

Ticket Information

- - - - {#if scan_state !== ScanState.scanning} -
- + +
+

Select Event

+ {#if isLoadingEvents} +
+
+ {:else if eventsError} +
+ {eventsError} + +
+ {:else if events.length === 0} +

No events found

+ {:else} + {/if}
+ + +
+ +
+ + +

Ticket Information

+ -- 2.49.1 From 0e5d39b1492c6043b214b7243f2207f6df7b8778 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roman=20Kr=C4=8Dek?= Date: Sat, 12 Jul 2025 14:10:50 +0200 Subject: [PATCH 3/6] Fixed problems with changing height --- .../private/scanner/TicketDisplay.svelte | 120 ++++++++++-------- 1 file changed, 64 insertions(+), 56 deletions(-) diff --git a/src/routes/private/scanner/TicketDisplay.svelte b/src/routes/private/scanner/TicketDisplay.svelte index 053b66b..9f0446a 100644 --- a/src/routes/private/scanner/TicketDisplay.svelte +++ b/src/routes/private/scanner/TicketDisplay.svelte @@ -15,65 +15,73 @@ } -
- {#if scan_state === ScanState.scanning} -
-
-

Waiting for QR code...

-
- {:else if scan_state === ScanState.scan_failed} -
-
- - - -

Invalid Code

+
+
+ {#if scan_state === ScanState.scanning} +
+
+

Waiting for QR code...

-

This QR code is not a valid ticket or doesn't exist in our system.

-
- {:else if scan_state === ScanState.wrong_event} -
-
- - - -

Wrong Event

+ {:else if scan_state === ScanState.scan_failed} +
+
+ + + +

Invalid Code

+
+

This QR code is not a valid ticket or doesn't exist in our system.

+
+ + + +
-

This ticket belongs to a different event:

-
-

{ticket_data.event.name}

-

{ticket_data.name} {ticket_data.surname}

+ {:else if scan_state === ScanState.wrong_event} +
+
+ + + +

Wrong Event

+
+

This ticket belongs to a different event:

+
+

{ticket_data.event?.name || ''}

+

{ticket_data.name || ''} {ticket_data.surname || ''}

+
-
- {:else if scan_state === ScanState.already_scanned} -
-
- - - -

Already Scanned

+ {:else if scan_state === ScanState.already_scanned} +
+
+ + + +

Already Scanned

+
+

+ This ticket was already scanned by:
{ticket_data.scanned_by?.display_name || 'someone'} + {ticket_data.scanned_at ? `on ${formatScannedAt(ticket_data.scanned_at)}` : ''} +

+
+

{ticket_data.event?.name || ''}

+

{ticket_data.name || ''} {ticket_data.surname || ''}

+
-

- This ticket was already scanned by {ticket_data.scanned_by?.display_name || 'someone'} - {ticket_data.scanned_at ? `on ${formatScannedAt(ticket_data.scanned_at)}` : ''} -

-
-

{ticket_data.event.name}

-

{ticket_data.name} {ticket_data.surname}

+ {:else if scan_state === ScanState.scan_successful} +
+
+ + + +

Valid Ticket

+
+

Ticket successfully validated.

+
+

{ticket_data.event?.name || ''}

+

{ticket_data.name || ''} {ticket_data.surname || ''}

+
-
- {:else if scan_state === ScanState.scan_successful} -
-
- - - -

Valid Ticket

-
-
-

{ticket_data.event.name}

-

{ticket_data.name} {ticket_data.surname}

-
-
- {/if} + {/if} +
-- 2.49.1 From d6eee9c4984524751ef364512edf0d4a661efe51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roman=20Kr=C4=8Dek?= Date: Sat, 12 Jul 2025 14:21:18 +0200 Subject: [PATCH 4/6] Better email template display --- .github/copilot-instructions.md | 13 +- .../private/events/event/view/+page.svelte | 192 +++++++++++------- 2 files changed, 125 insertions(+), 80 deletions(-) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index a009208..be05df8 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -1,12 +1,11 @@ GitHub Copilot Instructions for This Repository -If you have any questions, always ask me first. - -Use Svelte 5 runes exclusively - -Declare reactive state with $state(); derive values with $derived(); run side-effect logic with $effect() etc. -svelte.dev -svelte.dev +Basics: +- If you have any questions, always ask me first! +- Use Svelte 5 runes exclusively +- Declare reactive state with $state(); derive values with $derived(); run side-effect logic with $effect() etc. +- When doing client-side loading, always implement placeholders and loaders, so the UI remains responsive and layout shifts are minimized. + - Don't use placeholders and loaders for static data like heading etc. Do not fall back to the legacy $: label syntax or Svelte 3/4 stores! This is important! diff --git a/src/routes/private/events/event/view/+page.svelte b/src/routes/private/events/event/view/+page.svelte index b671f38..7cf5c17 100644 --- a/src/routes/private/events/event/view/+page.svelte +++ b/src/routes/private/events/event/view/+page.svelte @@ -252,64 +252,60 @@
{#if loading} - -
-
-
-
-
-
- {:else if event} -
-
-

{event.name}

-
-
- Date: - {formatDate(event.date)} -
-
- Created: - {formatDate(event.created_at)} -
- + +
+

Event Information

+
+
+ Date: +
+
+
+ Created: +
+
+
+ Sheet ID: +
-
-

Email Details

-
-
- Subject: -

{event.email_subject}

-
-
- Body: -

{event.email_body}

-
+
+ {:else if event} +
+

{event.name}

+
+
+ Date: + {formatDate(event.date)} +
+
+ Created: + {formatDate(event.created_at)} +
+
@@ -320,23 +316,65 @@ {/if}
+ +
+
+

Email Template

+
+ + {#if loading} + +
+
+ Subject: +
+
+ +
+ Body: +
+
+
+ {:else if event} + +
+
+ Subject: +
+

{event.email_subject}

+
+
+
+ Body: +
+

{event.email_body}

+
+
+
+ {/if} +
+

Google Account

Required for syncing participants and sending emails

- { - // Refresh the page or update UI state as needed - error = ''; - }} - onError={(errorMsg) => { - error = errorMsg; - }} - /> + {#if loading} +
+ {:else} + { + // Refresh the page or update UI state as needed + error = ''; + }} + onError={(errorMsg) => { + error = errorMsg; + }} + /> + {/if}
@@ -345,7 +383,7 @@

Participants

- {#if participantsLoading} + {#if participantsLoading || loading}
{#each Array(5) as _} @@ -475,12 +513,20 @@

Send Emails

-
- {participants.filter(p => !p.email_sent).length} uncontacted participants -
+ {#if !loading} +
+ {participants.filter(p => !p.email_sent).length} uncontacted participants +
+ {:else} +
+ Loading participants... +
+ {/if}
- {#if sendingEmails} + {#if loading} +
+ {:else if sendingEmails}
-- 2.49.1 From d945209465b6d39f462571ff7f2d8db4ef5f75e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roman=20Kr=C4=8Dek?= Date: Sat, 12 Jul 2025 14:40:46 +0200 Subject: [PATCH 5/6] Split event view into components --- .../private/events/event/view/+page.svelte | 452 ++---------------- .../event/view/components/EmailResults.svelte | 100 ++++ .../event/view/components/EmailSending.svelte | 94 ++++ .../view/components/EmailTemplate.svelte | 47 ++ .../event/view/components/ErrorMessage.svelte | 11 + .../view/components/EventInformation.svelte | 88 ++++ .../components/GoogleAuthentication.svelte | 26 + .../view/components/ParticipantsTable.svelte | 162 +++++++ 8 files changed, 575 insertions(+), 405 deletions(-) create mode 100644 src/routes/private/events/event/view/components/EmailResults.svelte create mode 100644 src/routes/private/events/event/view/components/EmailSending.svelte create mode 100644 src/routes/private/events/event/view/components/EmailTemplate.svelte create mode 100644 src/routes/private/events/event/view/components/ErrorMessage.svelte create mode 100644 src/routes/private/events/event/view/components/EventInformation.svelte create mode 100644 src/routes/private/events/event/view/components/GoogleAuthentication.svelte create mode 100644 src/routes/private/events/event/view/components/ParticipantsTable.svelte diff --git a/src/routes/private/events/event/view/+page.svelte b/src/routes/private/events/event/view/+page.svelte index 7cf5c17..d9f4413 100644 --- a/src/routes/private/events/event/view/+page.svelte +++ b/src/routes/private/events/event/view/+page.svelte @@ -1,7 +1,15 @@ @@ -249,406 +259,38 @@

Event Overview

- -
- {#if loading} - -
-

Event Information

-
-
- Date: -
-
-
- Created: -
-
-
- Sheet ID: -
-
-
-
- {:else if event} -
-

{event.name}

-
-
- Date: - {formatDate(event.date)} -
-
- Created: - {formatDate(event.created_at)} -
- -
-
- {:else if error} -
-

{error}

-
- {/if} -
+ + - -
-
-

Email Template

-
- - {#if loading} - -
-
- Subject: -
-
- -
- Body: -
-
-
- {:else if event} + + -
-
- Subject: -
-

{event.email_subject}

-
-
-
- Body: -
-

{event.email_body}

-
-
-
- {/if} -
+ - -
-
-

Google Account

-

Required for syncing participants and sending emails

-
- {#if loading} -
- {:else} - { - // Refresh the page or update UI state as needed - error = ''; - }} - onError={(errorMsg) => { - error = errorMsg; - }} - /> - {/if} -
+ - -
-
-

Participants

- -
- - {#if participantsLoading || loading} - -
- {#each Array(5) as _} -
-
-
-
-
-
-
- {/each} -
- {:else if participants.length > 0} -
- - - - - - - - - - - - {#each participants as participant} - - - - - - - - {/each} - -
NameSurnameEmailScannedEmail Sent
{participant.name}{participant.surname}{participant.email} - {#if participant.scanned} -
- - - -
- {:else} -
- - - -
- {/if} -
- {#if participant.email_sent} -
- - - -
- {:else} -
- - - -
- {/if} -
-
- {:else} -
-

- No participants found. Click "Sync Participants" to load from Google Sheets. -

-
- {/if} -
- - -
-
-

Send Emails

- {#if !loading} -
- {participants.filter(p => !p.email_sent).length} uncontacted participants -
- {:else} -
- Loading participants... -
- {/if} -
- - {#if loading} -
- {:else if sendingEmails} -
-
- - - - - Sending {emailProgress.total} emails... Please wait. -
-
- {:else} -
- {#if participants.filter(p => !p.email_sent).length > 0} -
-
- - - - - Warning: Do not close this window while emails are being sent. The process may take several minutes. - -
-
- -
- -
- {:else} -
-
- - - -
-

All participants have been contacted!

-

No pending emails to send.

-
- {/if} -
- {/if} -
- - {#if emailResults} -
-
-

Email Results

-
- {emailResults.summary.success} successful, {emailResults.summary.errors} failed -
-
- -
-
-
-
- Sent: {emailResults.summary.success} -
-
-
- Failed: {emailResults.summary.errors} -
-
-
- Total: {emailResults.summary.total} -
-
-
- - {#if emailResults.results.length > 0} -
- - - - - - - - - - {#each emailResults.results as result} - - - - - - {/each} - -
NameEmailStatus
- {result.participant.name} {result.participant.surname} - {result.participant.email} - {#if result.success} -
- - - - Sent -
- {:else} -
- - - - Failed - {#if result.error} - ({result.error}) - {/if} -
- {/if} -
-
- {/if} -
+ {/if} {#if error} -
-

{error}

-
+ {/if} diff --git a/src/routes/private/events/event/view/components/EmailResults.svelte b/src/routes/private/events/event/view/components/EmailResults.svelte new file mode 100644 index 0000000..1e2c2c7 --- /dev/null +++ b/src/routes/private/events/event/view/components/EmailResults.svelte @@ -0,0 +1,100 @@ + + +{#if emailResults} +
+
+

Email Results

+
+ {emailResults.summary.success} successful, {emailResults.summary.errors} failed +
+
+ +
+
+
+
+ Sent: {emailResults.summary.success} +
+
+
+ Failed: {emailResults.summary.errors} +
+
+
+ Total: {emailResults.summary.total} +
+
+
+ + {#if emailResults.results.length > 0} +
+ + + + + + + + + + {#each emailResults.results as result} + + + + + + {/each} + +
NameEmailStatus
+ {result.participant.name} {result.participant.surname} + {result.participant.email} + {#if result.success} +
+ + + + Sent +
+ {:else} +
+ + + + Failed + {#if result.error} + ({result.error}) + {/if} +
+ {/if} +
+
+ {/if} +
+{/if} diff --git a/src/routes/private/events/event/view/components/EmailSending.svelte b/src/routes/private/events/event/view/components/EmailSending.svelte new file mode 100644 index 0000000..0e27113 --- /dev/null +++ b/src/routes/private/events/event/view/components/EmailSending.svelte @@ -0,0 +1,94 @@ + + +
+
+

Send Emails

+ {#if !loading} +
+ {uncontactedParticipantsCount} uncontacted participants +
+ {:else} +
+ Loading participants... +
+ {/if} +
+ + {#if loading} +
+ {:else if sendingEmails} +
+
+ + + + + Sending {emailProgress.total} emails... Please wait. +
+
+ {:else} +
+ {#if uncontactedParticipantsCount > 0} +
+
+ + + + + Warning: Do not close this window while emails are being sent. The process may take several minutes. + +
+
+ +
+ +
+ {:else} +
+
+ + + +
+

All participants have been contacted!

+

No pending emails to send.

+
+ {/if} +
+ {/if} +
diff --git a/src/routes/private/events/event/view/components/EmailTemplate.svelte b/src/routes/private/events/event/view/components/EmailTemplate.svelte new file mode 100644 index 0000000..2e9718e --- /dev/null +++ b/src/routes/private/events/event/view/components/EmailTemplate.svelte @@ -0,0 +1,47 @@ + + +
+
+

Email Template

+
+ + {#if loading} + +
+
+ Subject: +
+
+ +
+ Body: +
+
+
+ {:else if event} +
+
+ Subject: +
+

{event.email_subject}

+
+
+
+ Body: +
+

{event.email_body}

+
+
+
+ {/if} +
diff --git a/src/routes/private/events/event/view/components/ErrorMessage.svelte b/src/routes/private/events/event/view/components/ErrorMessage.svelte new file mode 100644 index 0000000..e222576 --- /dev/null +++ b/src/routes/private/events/event/view/components/ErrorMessage.svelte @@ -0,0 +1,11 @@ + + +{#if error} +
+

{error}

+
+{/if} diff --git a/src/routes/private/events/event/view/components/EventInformation.svelte b/src/routes/private/events/event/view/components/EventInformation.svelte new file mode 100644 index 0000000..7d99aac --- /dev/null +++ b/src/routes/private/events/event/view/components/EventInformation.svelte @@ -0,0 +1,88 @@ + + +
+ {#if loading} + +
+

Event Information

+
+
+ Date: +
+
+
+ Created: +
+
+
+ Sheet ID: +
+
+
+
+ {:else if event} +
+

{event.name}

+
+
+ Date: + {formatDate(event.date)} +
+
+ Created: + {formatDate(event.created_at)} +
+ +
+
+ {:else if error} +
+

{error}

+
+ {/if} +
diff --git a/src/routes/private/events/event/view/components/GoogleAuthentication.svelte b/src/routes/private/events/event/view/components/GoogleAuthentication.svelte new file mode 100644 index 0000000..4c51af1 --- /dev/null +++ b/src/routes/private/events/event/view/components/GoogleAuthentication.svelte @@ -0,0 +1,26 @@ + + +
+
+

Google Account

+

Required for syncing participants and sending emails

+
+ {#if loading} +
+ {:else} + + {/if} +
diff --git a/src/routes/private/events/event/view/components/ParticipantsTable.svelte b/src/routes/private/events/event/view/components/ParticipantsTable.svelte new file mode 100644 index 0000000..5595401 --- /dev/null +++ b/src/routes/private/events/event/view/components/ParticipantsTable.svelte @@ -0,0 +1,162 @@ + + +
+
+

Participants

+ +
+ + {#if participantsLoading || loading} + +
+ {#each Array(5) as _} +
+
+
+
+
+
+
+ {/each} +
+ {:else if participants.length > 0} +
+ + + + + + + + + + + + {#each participants as participant} + + + + + + + + {/each} + +
NameSurnameEmailScannedEmail Sent
{participant.name}{participant.surname}{participant.email} + {#if participant.scanned} +
+ + + +
+ {:else} +
+ + + +
+ {/if} +
+ {#if participant.email_sent} +
+ + + +
+ {:else} +
+ + + +
+ {/if} +
+
+ {:else} +
+

+ No participants found. Click "Sync Participants" to load from Google Sheets. +

+
+ {/if} +
-- 2.49.1 From 48e2944eba984bd79c344811c313ae007fe02e37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roman=20Kr=C4=8Dek?= Date: Sat, 12 Jul 2025 15:03:23 +0200 Subject: [PATCH 6/6] Event view impovements --- .../events/event/archived/+page.svelte | 62 ++------- .../components/EventInformation.svelte | 28 ++++ .../archived/components/Statistics.svelte | 73 ++++++++++ .../private/events/event/view/+page.svelte | 27 +++- .../view/components/EmailSendingFixed.svelte | 94 +++++++++++++ .../view/components/ParticipantsTable.svelte | 2 +- .../event/view/components/Statistics.svelte | 127 ++++++++++++++++++ 7 files changed, 357 insertions(+), 56 deletions(-) create mode 100644 src/routes/private/events/event/archived/components/EventInformation.svelte create mode 100644 src/routes/private/events/event/archived/components/Statistics.svelte create mode 100644 src/routes/private/events/event/view/components/EmailSendingFixed.svelte create mode 100644 src/routes/private/events/event/view/components/Statistics.svelte diff --git a/src/routes/private/events/event/archived/+page.svelte b/src/routes/private/events/event/archived/+page.svelte index 37cdb56..050d66d 100644 --- a/src/routes/private/events/event/archived/+page.svelte +++ b/src/routes/private/events/event/archived/+page.svelte @@ -1,6 +1,8 @@ + +
+
+ {#if loading} +
+
+ {:else} +

{event?.name}

+
+
+ Date: + {event?.date} +
+
+ {/if} +
+
diff --git a/src/routes/private/events/event/archived/components/Statistics.svelte b/src/routes/private/events/event/archived/components/Statistics.svelte new file mode 100644 index 0000000..526483b --- /dev/null +++ b/src/routes/private/events/event/archived/components/Statistics.svelte @@ -0,0 +1,73 @@ + + +
+ + + + + + + + + + + + + + + + + + + + +
CategoryCount
+
+ + + + Total participants +
+
+ {#if loading} +
+ {:else} + {totalParticipants} + {/if} +
+
+ + + + Scanned participants +
+
+ {#if loading} +
+ {:else} + {scannedParticipants} + {/if} +
+
diff --git a/src/routes/private/events/event/view/+page.svelte b/src/routes/private/events/event/view/+page.svelte index d9f4413..5a36662 100644 --- a/src/routes/private/events/event/view/+page.svelte +++ b/src/routes/private/events/event/view/+page.svelte @@ -10,6 +10,7 @@ import EmailSending from './components/EmailSending.svelte'; import EmailResults from './components/EmailResults.svelte'; import ErrorMessage from './components/ErrorMessage.svelte'; + import Statistics from './components/Statistics.svelte'; let { data } = $props(); @@ -262,7 +263,6 @@ - +
+

Statistics

+ p.scanned).length} + emailSentCount={participants.filter(p => p.email_sent).length} + pendingCount={participants.filter(p => !p.email_sent).length} + /> +
+ + + + interface Participant { + id: string; + email_sent: boolean; + } + + interface EmailProgress { + sent: number; + total: number; + } + + let { + loading, + participants, + sendingEmails, + emailProgress, + event, + onSendEmails + } = $props<{ + loading: boolean; + participants: Participant[]; + sendingEmails: boolean; + emailProgress: EmailProgress; + event: any | null; + onSendEmails: () => void; + }>(); + + // Using the $derived rune to calculate uncontacted participants + let uncontactedParticipantsCount = $derived(participants.filter(p => !p.email_sent).length); + + +
+
+

Send Emails

+ {#if !loading} +
+ {uncontactedParticipantsCount} uncontacted participants +
+ {:else} +
+ Loading participants... +
+ {/if} +
+ + {#if loading} +
+ {:else if sendingEmails} +
+
+ + + + + Sending {emailProgress.total} emails... Please wait. +
+
+ {:else} +
+ {#if uncontactedParticipantsCount > 0} +
+
+ + + + + Warning: Do not close this window while emails are being sent. The process may take several minutes. + +
+
+ +
+ +
+ {:else} +
+
+ + + +
+

All participants have been contacted!

+

No pending emails to send.

+
+ {/if} +
+ {/if} +
diff --git a/src/routes/private/events/event/view/components/ParticipantsTable.svelte b/src/routes/private/events/event/view/components/ParticipantsTable.svelte index 5595401..1660920 100644 --- a/src/routes/private/events/event/view/components/ParticipantsTable.svelte +++ b/src/routes/private/events/event/view/components/ParticipantsTable.svelte @@ -30,7 +30,7 @@ }>(); -
+

Participants