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.
This commit is contained in:
Scott Idem
2025-11-19 18:21:40 -05:00
parent 8029034e37
commit 79da9acd2f
5 changed files with 373 additions and 17 deletions

View File

@@ -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;

View File

@@ -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<string> = $state([]);
// let dq__where_type_id_val: string = `event_id_random`;
@@ -96,6 +103,38 @@
// }
</script>
{#if show_create_badge_modal}
<Modal bind:show={show_create_badge_modal}>
<div class="card p-4">
<h3 class="h3">Create New Badge</h3>
<Comp_badge_create_form
event_id={$events_slct?.event_id ?? ''}
on:success={() => {
show_create_badge_modal = false;
ae_triggers.event_badge_qry = true; // Trigger a refresh of the list
}}
on:cancel={() => show_create_badge_modal = false}
/>
</div>
</Modal>
{/if}
{#if show_upload_badge_modal}
<Modal bind:show={show_upload_badge_modal}>
<div class="card p-4">
<h3 class="h3">Upload Badges (CSV)</h3>
<Comp_badge_upload_form
event_id={$events_slct?.event_id ?? ''}
on:success={() => {
show_upload_badge_modal = false;
ae_triggers.event_badge_qry = true; // Trigger a refresh of the list
}}
on:cancel={() => show_upload_badge_modal = false}
/>
</div>
</Modal>
{/if}
<svelte:head>
<title>
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}

View File

@@ -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}

View File

@@ -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>

View File

@@ -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"