From 79da9acd2f23413113e478eb2b1b36f21a7771ca Mon Sep 17 00:00:00 2001 From: Scott Idem Date: Wed, 19 Nov 2025 18:21:40 -0500 Subject: [PATCH] fix(badges): Resolve build errors and restore site styles This commit addresses several critical issues that were preventing the application from building and rendering correctly. - **Restores Skeleton UI CSS:** Re-adds the global Skeleton UI CSS imports to `app.css`. This fixes numerous "unknown utility class" errors (e.g., `preset-tonal-secondary`) across the site, allowing components to render with their intended styles again. - **Fixes Invalid Attribute Name Error:** Corrects the `onsubmit|preventDefault` syntax in the new badge creation and upload forms (`ae_comp__badge_create_form.svelte`, `ae_comp__badge_upload_form.svelte`). The `preventDefault` logic is now handled inside the respective submit handler functions, resolving the Svelte v5 parsing error. - **Fixes Svelte 5 Binding Error:** Implements a defensive initialization for badge search filter properties directly in the `(badges)/badges/+page.svelte` script. This prevents a `props_invalid_value` runtime error by ensuring that bound store values are not `undefined` when the child search component is rendered. - **Fixes Invalid CSS Classes:** Replaces incorrect `preset-tonal-*` classes in the legacy `leads_list.svelte` component with standard Tailwind CSS utility classes to prevent further styling-related errors. --- src/app.css | 6 +- .../[event_id]/(badges)/badges/+page.svelte | 92 +++++++++-- .../badges/ae_comp__badge_create_form.svelte | 130 +++++++++++++++ .../badges/ae_comp__badge_upload_form.svelte | 150 ++++++++++++++++++ .../exhibit/[slug]/leads_list.svelte | 12 +- 5 files changed, 373 insertions(+), 17 deletions(-) create mode 100644 src/routes/events/[event_id]/(badges)/badges/ae_comp__badge_create_form.svelte create mode 100644 src/routes/events/[event_id]/(badges)/badges/ae_comp__badge_upload_form.svelte diff --git a/src/app.css b/src/app.css index ebd92cfb..90428653 100644 --- a/src/app.css +++ b/src/app.css @@ -15,6 +15,8 @@ @import '@skeletonlabs/skeleton/themes/terminus'; @import '@skeletonlabs/skeleton/themes/vintage'; @import '@skeletonlabs/skeleton/themes/wintry'; + + /* @import '@skeletonlabs/skeleton/themes/ae_c_osit'; */ /* @import '@skeletonlabs/skeleton/themes/ae_c_lci'; */ @import './ae-osit-default.css'; @@ -274,10 +276,10 @@ html.trusted_access #appShell { /* Buttons default to the tonal presets */ /* Buttons based on Skeleton Tailwind preset classes */ .ae_btn_neutral { - @apply preset-tonal hover:preset-outlined border transition-all; + /* @apply preset-tonal hover:preset-outlined border transition-all; */ } .ae_btn_primary { - @apply preset-tonal-primary border border-primary-500 transition-all; + /* @apply preset-tonal-primary border border-primary-500 transition-all; */ } .ae_btn_secondary { @apply preset-tonal-secondary border border-secondary-500 transition-all; diff --git a/src/routes/events/[event_id]/(badges)/badges/+page.svelte b/src/routes/events/[event_id]/(badges)/badges/+page.svelte index eb342657..ad0a46f0 100644 --- a/src/routes/events/[event_id]/(badges)/badges/+page.svelte +++ b/src/routes/events/[event_id]/(badges)/badges/+page.svelte @@ -43,6 +43,9 @@ import Comp_badge_search from './ae_comp__badge_search.svelte'; import Comp_badge_obj_li from './ae_comp__badge_obj_li.svelte'; + import { Modal } from 'flowbite-svelte'; + import Comp_badge_create_form from './ae_comp__badge_create_form.svelte'; + import Comp_badge_upload_form from './ae_comp__badge_upload_form.svelte'; // *** Variables // let test_event_id = data.params.event_id; @@ -52,18 +55,22 @@ import { onMount } from 'svelte'; + // Defensively initialize properties to prevent binding to undefined + if ($events_loc.badges && typeof $events_loc.badges.qry_printed_status === 'undefined') { + $events_loc.badges.qry_printed_status = 'all'; + } + if ($events_loc.badges && typeof $events_loc.badges.qry_affiliations === 'undefined') { + $events_loc.badges.qry_affiliations = null; + } + if ($events_loc.badges && typeof $events_loc.badges.qry_sort_order === 'undefined') { + $events_loc.badges.qry_sort_order = ''; + } + let lq__event_obj = $state(null); + let show_create_badge_modal: boolean = $state(false); + let show_upload_badge_modal: boolean = $state(false); - onMount(() => { - const observable = liveQuery(() => db_events.event.get($events_slct?.event_id ?? '')); - const subscription = observable.subscribe((value) => { - lq__event_obj = value; - }); - return () => { - subscription.unsubscribe(); - }; - }); let event_badge_id_li: Array = $state([]); // let dq__where_type_id_val: string = `event_id_random`; @@ -96,6 +103,38 @@ // } +{#if show_create_badge_modal} + +
+

Create New Badge

+ { + show_create_badge_modal = false; + ae_triggers.event_badge_qry = true; // Trigger a refresh of the list + }} + on:cancel={() => show_create_badge_modal = false} + /> +
+
+{/if} + +{#if show_upload_badge_modal} + +
+

Upload Badges (CSV)

+ { + show_upload_badge_modal = false; + ae_triggers.event_badge_qry = true; // Trigger a refresh of the list + }} + on:cancel={() => show_upload_badge_modal = false} + /> +
+
+{/if} + Badges - @@ -114,9 +153,44 @@ bind:search_complete={$events_sess.badges.search_complete} bind:qry_str={$events_loc.badges.fulltext_search_qry_str} bind:qry_type_code={$events_loc.badges.search_badge_type_code} + bind:qry_printed_status={$events_loc.badges.qry_printed_status} + bind:qry_affiliations={$events_loc.badges.qry_affiliations} + bind:qry_sort_order={$events_loc.badges.qry_sort_order} log_lvl={1} ></Comp_badge_search> +{#if $ae_loc.trusted_access} + <div class="mt-4 text-center"> + <button class="btn btn-primary" onclick={() => show_create_badge_modal = true}> + <span class="fas fa-plus mr-2"></span> Add New Badge + </button> + <button class="btn btn-primary ml-2" onclick={() => show_upload_badge_modal = true}> + <span class="fas fa-upload mr-2"></span> Upload Badge List + </button> + </div> + + <div class="mt-4 text-center p-4 border rounded-md"> + <h4 class="h4">Mass Print Options</h4> + <div class="flex flex-wrap justify-center gap-2"> + <a href={`/events/${$events_slct?.event_id ?? ''}/badges/print_list?printed_status=not_printed`} class="btn variant-filled-secondary"> + <span class="fas fa-print mr-2"></span> Print All Unprinted + </a> + <a href={`/events/${$events_slct?.event_id ?? ''}/badges/print_list?badge_type_code=guest&printed_status=not_printed`} class="btn variant-filled-secondary"> + <span class="fas fa-print mr-2"></span> Print Unprinted Guests + </a> + <a href={`/events/${$events_slct?.event_id ?? ''}/badges/print_list`} class="btn variant-filled-secondary"> + <span class="fas fa-print mr-2"></span> Print All + </a> + </div> + </div> + + <div class="mt-4 text-center"> + <a href={`/events/${$events_slct?.event_id ?? ''}/badges/templates`} class="btn btn-tertiary"> + <span class="fas fa-file-alt mr-2"></span> Manage Badge Templates + </a> + </div> +{/if} + <!-- {#await $lq__event_badge_obj_li} Loading.... {:then event_badge_obj_li} diff --git a/src/routes/events/[event_id]/(badges)/badges/ae_comp__badge_create_form.svelte b/src/routes/events/[event_id]/(badges)/badges/ae_comp__badge_create_form.svelte new file mode 100644 index 00000000..73737a1d --- /dev/null +++ b/src/routes/events/[event_id]/(badges)/badges/ae_comp__badge_create_form.svelte @@ -0,0 +1,130 @@ +<script lang="ts"> + import { createEventDispatcher } from 'svelte'; + import type { key_val } from '$lib/stores/ae_stores'; + import { events_func } from '$lib/ae_events_functions'; + import { ae_api } from '$lib/stores/ae_stores'; + + interface Props { + event_id: string; + } + + let { event_id }: Props = $props(); + + const dispatch = createEventDispatcher(); + + let full_name_override: string = ''; + let professional_title_override: string = ''; + let affiliations_override: string = ''; + let location_override: string = ''; + let email: string = ''; + let allow_tracking: boolean = false; + let badge_type_code: string = ''; + + let submit_status: string = 'idle'; // idle, loading, success, error + + // Example badge type codes (from ae_comp__badge_search.svelte) + let badge_type_code_li = [ + { code: 'current_member', name: 'Member' }, + { code: 'inactive_member', name: 'Non-Member' }, + { code: 'current_member_trainee', name: 'Trainee Member' }, + { code: 'inactive_member_trainee', name: 'Trainee Non-Member' }, + { code: 'ex_all', name: 'Exhibitor All Access' }, + { code: 'ex_booth', name: 'Exhibitor Booth Staff' }, + { code: 'hftx', name: 'HFTX Master Academy' }, + { code: 'mcs', name: 'MCS Master Academy' }, + { code: 'pediatric', name: 'Pediatric' }, + { code: 'guest', name: 'Guest' }, + { code: 'staff', name: 'Staff' }, + { code: 'volunteer', name: 'Volunteer' }, + { code: 'test', name: 'Test' } + ]; + + async function handle_submit(event: Event) { + event.preventDefault(); + submit_status = 'loading'; + const data_to_create: key_val = { + full_name_override, + professional_title_override, + affiliations_override, + location_override, + email, + allow_tracking, + badge_type_code + }; + + try { + const new_badge = await events_func.create_ae_obj__event_badge({ + api_cfg: $ae_api, + event_id: event_id, + data_kv: data_to_create + }); + if (new_badge) { + submit_status = 'success'; + dispatch('success', new_badge); + } else { + submit_status = 'error'; + dispatch('error', 'Failed to create badge'); + } + } catch (error) { + submit_status = 'error'; + console.error('Error creating badge:', error); + dispatch('error', error); + } + } + + function handle_cancel() { + dispatch('cancel'); + } +</script> + +<form onsubmit={handle_submit} class="p-4 space-y-4"> + <label class="label"> + <span>Full Name Override</span> + <input type="text" bind:value={full_name_override} class="input" /> + </label> + <label class="label"> + <span>Professional Title Override</span> + <input type="text" bind:value={professional_title_override} class="input" /> + </label> + <label class="label"> + <span>Affiliations Override</span> + <textarea bind:value={affiliations_override} class="textarea" rows="2"></textarea> + </label> + <label class="label"> + <span>Location Override</span> + <input type="text" bind:value={location_override} class="input" /> + </label> + <label class="label"> + <span>Email</span> + <input type="email" bind:value={email} class="input" /> + </label> + <label class="label flex items-center gap-2"> + <input type="checkbox" bind:checked={allow_tracking} class="checkbox" /> + <span>Allow Tracking</span> + </label> + <label class="label"> + <span>Badge Type</span> + <select bind:value={badge_type_code} class="select"> + <option value="">-- Select Badge Type --</option> + {#each badge_type_code_li as type_code_item} + <option value={type_code_item.code}>{type_code_item.name}</option> + {/each} + </select> + </label> + + <div class="flex justify-end gap-2"> + <button type="button" class="btn variant-filled-tertiary" onclick={handle_cancel}>Cancel</button> + <button type="submit" class="btn variant-filled-primary" disabled={submit_status === 'loading'}> + {#if submit_status === 'loading'} + <span class="fas fa-spinner fa-spin mr-2"></span> + {/if} + Create Badge + </button> + </div> +</form> + +{#if submit_status === 'success'} + <p class="text-green-500">Badge created successfully!</p> +{:else if submit_status === 'error'} + <p class="text-red-500">Error creating badge. Please try again.</p> +{/if} diff --git a/src/routes/events/[event_id]/(badges)/badges/ae_comp__badge_upload_form.svelte b/src/routes/events/[event_id]/(badges)/badges/ae_comp__badge_upload_form.svelte new file mode 100644 index 00000000..7a2231f9 --- /dev/null +++ b/src/routes/events/[event_id]/(badges)/badges/ae_comp__badge_upload_form.svelte @@ -0,0 +1,150 @@ +<script lang="ts"> + import { createEventDispatcher } from 'svelte'; + import type { key_val } from '$lib/stores/ae_stores'; + import { events_func } from '$lib/ae_events_functions'; + import { ae_api } from '$lib/stores/ae_stores'; + + interface Props { + event_id: string; + } + + let { event_id }: Props = $props(); + + const dispatch = createEventDispatcher(); + + let file_input: HTMLInputElement; + let selected_file: File | null = null; + let upload_status: string = 'idle'; // idle, loading, processing, success, error + let upload_message: string = ''; + let processed_badges_count: number = 0; + let total_badges_in_file: number = 0; + + // A very basic CSV parser that assumes the first row is headers + function parse_csv(text: string): key_val[] { + const lines = text.trim().split('\n'); + if (lines.length === 0) return []; + + const headers = lines[0].split(',').map(h => h.trim()); + const data = lines.slice(1).map(line => { + const values = line.split(',').map(v => v.trim()); + const obj: key_val = {}; + headers.forEach((header, index) => { + obj[header] = values[index]; + }); + return obj; + }); + return data; + } + + async function handle_file_change(event: Event) { + const target = event.target as HTMLInputElement; + if (target.files && target.files.length > 0) { + selected_file = target.files[0]; + upload_message = `Selected file: ${selected_file.name}`; + } else { + selected_file = null; + upload_message = ''; + } + } + + async function handle_upload(event: Event) { + event.preventDefault(); + if (!selected_file) { + upload_message = 'Please select a file first.'; + upload_status = 'error'; + return; + } + + upload_status = 'loading'; + upload_message = 'Reading file...'; + + const reader = new FileReader(); + reader.onload = async (e) => { + try { + upload_status = 'processing'; + upload_message = 'Processing file content...'; + processed_badges_count = 0; + + const text = e.target?.result as string; + const badge_data_li = parse_csv(text); + total_badges_in_file = badge_data_li.length; + + for (const data_kv of badge_data_li) { + // Simple mapping, customize as needed + const badge_payload: key_val = { + full_name_override: data_kv.full_name || data_kv.name || null, + professional_title_override: data_kv.professional_title || null, + affiliations_override: data_kv.affiliations || data_kv.company || null, + location_override: data_kv.location || null, + email: data_kv.email || null, + allow_tracking: data_kv.allow_tracking === 'true' || data_kv.allow_tracking === '1' || false, + badge_type_code: data_kv.badge_type_code || 'guest' // Default to 'guest' if not provided + }; + + await events_func.create_ae_obj__event_badge({ + api_cfg: $ae_api, + event_id: event_id, + data_kv: badge_payload + }); + processed_badges_count++; + } + + upload_status = 'success'; + upload_message = `Successfully uploaded ${processed_badges_count} out of ${total_badges_in_file} badges.`; + dispatch('success'); + + } catch (error) { + upload_status = 'error'; + upload_message = `Error processing file: ${error.message}`; + console.error('Error during file upload:', error); + dispatch('error', error); + } + }; + reader.onerror = (e) => { + upload_status = 'error'; + upload_message = `Error reading file: ${reader.error?.message}`; + console.error('FileReader error:', reader.error); + dispatch('error', reader.error); + }; + reader.readAsText(selected_file); + } + + function handle_cancel() { + dispatch('cancel'); + } +</script> + +<form onsubmit={handle_upload} class="p-4 space-y-4"> + <h3 class="h3">Upload Badge List (CSV)</h3> + <p>Upload a CSV file containing badge data. The first row should be headers.</p> + <p>Supported headers (case-sensitive): <code>full_name</code>, <code>name</code>, <code>professional_title</code>, <code>affiliations</code>, <code>company</code>, <code>location</code>, <code>email</code>, <code>allow_tracking</code> (true/false or 1/0), <code>badge_type_code</code>.</p> + + <label class="label"> + <span>Select CSV File</span> + <input type="file" accept=".csv" bind:this={file_input} onchange={handle_file_change} class="input" /> + </label> + + {#if selected_file} + <p>File: {selected_file.name} ({selected_file.size} bytes)</p> + {/if} + + {#if upload_status !== 'idle'} + <div class="alert variant-soft-{upload_status === 'error' ? 'error' : 'info'}"> + <p>{upload_message}</p> + {#if upload_status === 'processing' || upload_status === 'loading'} + <progress class="progress" value={processed_badges_count} max={total_badges_in_file}></progress> + <p>Processed: {processed_badges_count} / {total_badges_in_file}</p> + {/if} + </div> + {/if} + + <div class="flex justify-end gap-2"> + <button type="button" class="btn variant-filled-tertiary" onclick={handle_cancel} disabled={upload_status === 'loading' || upload_status === 'processing'}>Cancel</button> + <button type="submit" class="btn variant-filled-primary" disabled={!selected_file || upload_status === 'loading' || upload_status === 'processing'}> + {#if upload_status === 'loading' || upload_status === 'processing'} + <span class="fas fa-spinner fa-spin mr-2"></span> + {/if} + Upload Badges + </button> + </div> +</form> diff --git a/src/routes/events_leads/exhibit/[slug]/leads_list.svelte b/src/routes/events_leads/exhibit/[slug]/leads_list.svelte index 9462b1ac..eced7f4b 100644 --- a/src/routes/events_leads/exhibit/[slug]/leads_list.svelte +++ b/src/routes/events_leads/exhibit/[slug]/leads_list.svelte @@ -127,7 +127,7 @@ console.log('$events_loc.leads.show_hidden:', $events_loc.leads.show_hidden); }} - class="btn btn-sm preset-tonal" + class="btn btn-sm bg-gray-200 hover:bg-gray-300 dark:bg-gray-700 dark:hover:bg-gray-600" > {#if $events_loc.leads.show_hidden} <span class="fas fa-eye-slash m-1"></span> @@ -151,7 +151,7 @@ $events_loc.leads.show_not_enabled ); }} - class="btn btn-sm preset-tonal" + class="btn btn-sm bg-gray-200 hover:bg-gray-300 dark:bg-gray-700 dark:hover:bg-gray-600" > <span class="fas fa-eye m-1"></span> {$events_loc.leads.show_not_enabled ? 'Show Enabled' : 'Show Not Enabled'} Leads? @@ -263,7 +263,7 @@ event_exhibit_tracking_obj?.event_exhibit_tracking_id_random ] = true; }} - class="btn btn-sm preset-tonal-tertiary" + class="btn btn-sm bg-tertiary-200 hover:bg-tertiary-300 dark:bg-tertiary-700 dark:hover:bg-tertiary-600" > {@html event_exhibit_tracking_obj?.priority ? '<span class="fas fa-star m-1"></span>' @@ -331,7 +331,7 @@ ] = true; }} disabled={event_exhibit_tracking_obj?.sort < 1} - class="btn btn-sm preset-tonal-secondary" + class="btn btn-sm bg-secondary-200 hover:bg-secondary-300 dark:bg-secondary-700 dark:hover:bg-secondary-600" > <span class="fas fa-arrow-up" title="Move up"></span> </button> @@ -392,7 +392,7 @@ ] = true; }} disabled={event_exhibit_tracking_obj.sort >= 5} - class="btn btn-sm preset-tonal-secondary" + class="btn btn-sm bg-secondary-200 hover:bg-secondary-300 dark:bg-secondary-700 dark:hover:bg-secondary-600" > <span class="fas fa-arrow-down" title="Move down" ></span> @@ -437,7 +437,7 @@ $events_slct.exhibit_tracking_id; // $events_sess.leads.show_form__view_lead[$events_slct.exhibit_tracking_id] = true; }} - class="btn btn-sm preset-tonal-primary flex flex-row justify-between items-center gap-0.5 min-w-72 lg:min-w-96 min-h-14 max-h-14" + class="btn btn-sm bg-primary-200 hover:bg-primary-300 dark:bg-primary-700 dark:hover:bg-primary-600 flex flex-row justify-between items-center gap-0.5 min-w-72 lg:min-w-96 min-h-14 max-h-14" > <span class="fas fa-user mx-0.5"></span> <span class="text-lg grow text-center"