There have been a lot of changes. For some reason the commit is not working? Trying again.

This commit is contained in:
Scott Idem
2025-11-19 18:56:58 -05:00
parent b3c0446440
commit 10cc435146
20 changed files with 6362 additions and 292 deletions

View File

@@ -11,14 +11,14 @@
// import { goto } from '$app/navigation';
// *** Import other supporting libraries
// import { browser } from '$app/environment';
import { browser } from '$app/environment';
import { liveQuery } from 'dexie';
// *** Import Aether specific variables and functions
// import type { key_val } from '$lib/ae_stores';
import { ae_util } from '$lib/ae_utils/ae_utils';
// import { core_func } from '$lib/ae_core_functions';
// import { ae_snip, ae_loc, ae_sess, ae_api, ae_trig, slct, slct_trigger } from '$lib/ae_stores';
import { ae_loc } from '$lib/stores/ae_stores';
// import Element_ae_crud from '$lib/element_ae_crud
// import Element_data_store from '$lib/element_data_store_v2.svelte';
@@ -58,14 +58,31 @@
})
);
let is_review_mode: boolean = $state(false);
// *** Functions and Logic
// if (browser) {
// console.log('Browser environment detected.');
import { onMount } from 'svelte';
let lq__event_obj = $state(null);
// let url_test_val = data.url.searchParams.get('test_val');
// console.log(`URL test_val = ${url_test_val}`);
// }
onMount(() => {
const observable = liveQuery(() => db_events.event.get($events_slct?.event_id ?? ''));
const subscription = observable.subscribe((value) => {
lq__event_obj = value;
});
if (browser && window.location.hash === '#review') {
is_review_mode = true;
$ae_loc.edit_mode = true;
} else {
is_review_mode = false;
$ae_loc.edit_mode = false;
}
return () => {
subscription.unsubscribe();
};
});
</script>
<svelte:head>
@@ -116,6 +133,7 @@
event_id={$lq__event_badge_obj.event_id}
{event_badge_id}
{lq__event_badge_obj}
{is_review_mode}
/>
<!-- {/if} -->
{:else}

View File

@@ -5,6 +5,7 @@
lq__event_badge_obj?: any;
update_status?: string;
update_complete?: boolean;
is_review_mode?: boolean;
log_lvl?: number;
}
@@ -14,6 +15,7 @@
lq__event_badge_obj,
update_status = $bindable('idle'),
update_complete = $bindable(true),
is_review_mode = false,
log_lvl = 0
}: Props = $props();
@@ -23,27 +25,7 @@
// *** Import other supporting libraries
import { liveQuery } from 'dexie';
// import {
// ArrowDown01, ArrowDown10, ArrowDownUp,
// BetweenVerticalEnd, BetweenVerticalStart,
// BookHeart, BookImage, Bookmark, BookOpenText, BriefcaseBusiness,
// Check, Copy,
// Expand, Eye, EyeOff,
// Flag, FlagOff, FilePlus, Fingerprint,
// Globe,
// Library,
// MessageSquareWarning, Minus,
// Notebook,
// Pencil, Plus,
// RemoveFormatting,
// SquareLibrary,
// Shapes, Share2, ShieldCheck, ShieldMinus, Siren, Skull,
// Tags, Target, ToggleLeft, ToggleRight, Trash2, TypeOutline,
// X
// } from '@lucide/svelte';
import type { key_val } from '$lib/stores/ae_stores';
// import { ae_util } from '$lib/ae_utils/ae_utils';
import { core_func } from '$lib/ae_core/ae_core_functions';
import {
ae_snip,
@@ -100,90 +82,57 @@
let show_event_badge_tools_modal: boolean = $state(false);
let show_restricted_fields: boolean = $state(false);
let allow_tracking: null | boolean = $state(null);
let show_allow_tracking: boolean = $state(false);
// Editable fields
let editable_full_name_override: string | null = $state(null);
let editable_professional_title_override: string | null = $state(null);
let editable_affiliations_override: string | null = $state(null);
let editable_location_override: string | null = $state(null);
let editable_allow_tracking: boolean | null = $state(null);
let editable_email: string | null = $state(null);
let editable_badge_type_code: string | null = $state(null);
let edit_full_name_override = $state(false);
let edit_professional_title_override = $state(false);
let edit_affiliations_override = $state(false);
let edit_location_override = $state(false);
// Manage edit state locally
let edit_mode_active: boolean = $state(false);
// Initialize editable fields when lq__event_badge_obj changes
$effect(() => {
if (lq__event_badge_obj) {
editable_full_name_override = lq__event_badge_obj.full_name_override ?? lq__event_badge_obj.full_name;
editable_professional_title_override = lq__event_badge_obj.professional_title_override ?? lq__event_badge_obj.professional_title;
editable_affiliations_override = lq__event_badge_obj.affiliations_override ?? lq__event_badge_obj.affiliations;
editable_location_override = lq__event_badge_obj.location_override ?? lq__event_badge_obj.location;
editable_allow_tracking = lq__event_badge_obj.allow_tracking ?? null;
editable_email = lq__event_badge_obj.email ?? null;
editable_badge_type_code = lq__event_badge_obj.badge_type_code ?? null;
if (is_review_mode) {
edit_mode_active = true;
$ae_loc.edit_mode = true;
} else {
edit_mode_active = false; // Ensure it starts off if not in review mode
$ae_loc.edit_mode = false;
}
}
});
let show_print_msg: null | boolean = $state(null);
let hide_qr: null | boolean = $state(null);
let use_badge_type = $state(null);
let use_badge_type_code = $state('');
// let use_badge_type_code_list = [];
// use_badge_type_code_list.push({code: 'current_member', name: 'Member'});
// use_badge_type_code_list.push({code: 'inactive_member', name: 'Non-Member'});
// use_badge_type_code_list.push({code: 'MBR', name: 'Member'});
// use_badge_type_code_list.push({code: 'AHMB', name: 'Member'});
// use_badge_type_code_list.push({code: 'SNMB', name: 'Student/Trainee Non-Member'});
// use_badge_type_code_list.push({code: 'SMBR', name: 'Student/Trainee Member'});
// use_badge_type_code_list.push({code: 'NMBR', name: 'Non-Member'});
// use_badge_type_code_list.push({code: 'ANHM', name: 'Non-Member'});
// use_badge_type_code_list.push({code: 'INMB', name: 'Non-Member'});
// use_badge_type_code_list.push({code: 'EXO', name: 'Exhibitor Booth Staff'});
// use_badge_type_code_list.push({code: 'EXALL', name: 'Exhibitor All Access'});
// use_badge_type_code_list.push({code: 'GUEST', name: 'Guest'});
// use_badge_type_code_list.push({code: 'HEART', name: 'HFTX Core'});
// use_badge_type_code_list.push({code: 'LUNG', name: 'LTX Core'});
// use_badge_type_code_list.push({code: 'STAFF', name: 'Staff'});
// use_badge_type_code_list.push({code: 'VIP', name: 'VIP'});
// use_badge_type_code_list.push({code: 'VOL', name: 'Volunteer'});
let full_name_override = $state(null); // Usually set by the person or similar
let longest_full_name_override_part = 0;
let full_name = $state(null); // Usually auto generated
let professional_title_override = $state(null);
let longest_professional_title_override_part = 0;
let affiliations_override = $state(null);
let longest_affiliations_override_part = 0;
let location_override = $state(null);
let longest_location_override_part = 0;
let option_other_1_display_opt: any = $state(null);
let option_other_1_override: any = $state(null);
let option_other_2_display_opt: any = $state(null);
let option_other_2_override: any = $state(null);
let option_ticket_1_display_opt: any = $state(null);
let option_ticket_1_override: any = $state(null);
let option_ticket_2_display_opt: any = $state(null);
let option_ticket_2_override: any = $state(null);
let option_ticket_3_display_opt: any = $state(null);
let option_ticket_3_override: any = $state(null);
// let option_ticket_4_display_opt = $state(null);
// let option_ticket_5_display_opt = $state(null);
// let option_ticket_6_display_opt = $state(null);
// let option_ticket_7_display_opt = $state(null);
// let option_ticket_8_display_opt = $state(null);
// These variables seem unused or redundant now with editable_ fields
// let use_badge_type = $state(null);
// let use_badge_type_code = $state('');
let slct_badge_type = '';
// let qr_type = 'mecard';
// let qr_img_src = $state(null);
// let img_obj_url = $state(null);
/* *** BEGIN *** This should be moved out */
// display options: 'front_bool', 'front_html', 'back_bool', 'back_html'
option_ticket_1_display_opt = 'front_bool';
option_ticket_2_display_opt = 'front_bool';
option_ticket_3_display_opt = 'front_bool';
let option_ticket_1_display_opt = 'front_bool';
let option_ticket_2_display_opt = 'front_bool';
let option_ticket_3_display_opt = 'front_bool';
option_other_1_display_opt = 'back_html';
option_other_2_display_opt = 'back_html';
let option_other_1_display_opt = 'back_html';
let option_other_2_display_opt = 'back_html';
let code_to_html: any = { option_1: {}, option_2: {} };
code_to_html.option_1['1'] = '<span class="fas fa-biohazard"></span>';
@@ -203,12 +152,6 @@
code_to_html.option_2['True'] = '<span class="fas fa-star-of-life"></span>';
code_to_html.option_2['First Time '] = '<span class="fas fa-hand-paper"></span>';
// code_to_html.option_2['1-5'] = '<span class="fas fa-grimace"></span>';
// code_to_html.option_2['6-10'] = '<span class="fas fa-grimace"></span>';
// code_to_html.option_2['11-15'] = '<span class="fas fa-grimace"></span>';
// code_to_html.option_2['16-29'] = '<span class="fas fa-grimace"></span>';
// code_to_html.option_2['30+'] = '<span class="fas fa-grimace"></span>';
// code_to_html.option_2['Unknown'] = '<span class="fas fa-radiation"></span>';
/* *** END *** This should be moved out */
let full_name_class_size: string = $state('text-[.60in]');
@@ -227,10 +170,14 @@
$lq__event_badge_obj?.event_badge_template_id
);
full_name_override =
$lq__event_badge_obj?.full_name_override ?? $lq__event_badge_obj?.full_name;
longest_full_name_override_part = longest_str_part(full_name_override ?? '');
// Re-calculate font sizes based on potentially edited values
const current_full_name = editable_full_name_override ?? lq__event_badge_obj?.full_name ?? '';
const current_professional_title = editable_professional_title_override ?? lq__event_badge_obj?.professional_title ?? '';
const current_affiliations = editable_affiliations_override ?? lq__event_badge_obj?.affiliations ?? '';
const current_location = editable_location_override ?? lq__event_badge_obj?.location ?? '';
longest_full_name_override_part = longest_str_part(current_full_name);
if (longest_full_name_override_part >= 9) {
full_name_class_size = 'text-[.45in]';
} else if (longest_full_name_override_part >= 7) {
@@ -239,12 +186,7 @@
full_name_class_size = 'text-[.75in]';
}
professional_title_override =
$lq__event_badge_obj?.professional_title_override ??
$lq__event_badge_obj?.professional_title;
longest_professional_title_override_part = longest_str_part(
professional_title_override ?? ''
);
longest_professional_title_override_part = longest_str_part(current_professional_title);
if (longest_professional_title_override_part >= 13) {
professional_title_class_size = 'text-[.35in]';
} else if (longest_professional_title_override_part >= 10) {
@@ -255,9 +197,7 @@
professional_title_class_size = 'text-[.35in]';
}
affiliations_override =
$lq__event_badge_obj?.affiliations_override ?? $lq__event_badge_obj?.affiliations;
longest_affiliations_override_part = longest_str_part(affiliations_override ?? '');
longest_affiliations_override_part = longest_str_part(current_affiliations);
if (longest_affiliations_override_part >= 55) {
affiliations_class_size = 'text-[.30in]';
} else if (longest_affiliations_override_part >= 45) {
@@ -277,9 +217,7 @@
affiliations_class_size = full_name_class_size;
}
location_override =
$lq__event_badge_obj?.location_override ?? $lq__event_badge_obj?.location;
longest_location_override_part = longest_str_part(location_override ?? '');
longest_location_override_part = longest_str_part(current_location);
if (longest_location_override_part >= 55) {
location_class_size = 'text-[.30in]';
} else if (longest_location_override_part >= 45) {
@@ -301,27 +239,12 @@
professional_title_class_size = location_class_size;
}
option_other_1_override =
$lq__event_badge_obj?.option_other_1_override ??
$lq__event_badge_obj?.option_other_1;
option_other_2_override =
$lq__event_badge_obj?.option_other_2_override ??
$lq__event_badge_obj?.option_other_2;
option_ticket_1_override =
$lq__event_badge_obj?.option_ticket_1_override ??
$lq__event_badge_obj?.option_ticket_1;
option_ticket_2_override =
$lq__event_badge_obj?.option_ticket_2_override ??
$lq__event_badge_obj?.option_ticket_2;
option_ticket_3_override =
$lq__event_badge_obj?.option_ticket_3_override ??
$lq__event_badge_obj?.option_ticket_3;
// option_ticket_4_override = $lq__event_badge_obj?.option_ticket_4_override ?? $lq__event_badge_obj?.option_ticket_4;
// option_ticket_5_override = $lq__event_badge_obj?.option_ticket_5_override ?? $lq__event_badge_obj?.option_ticket_5;
// option_ticket_6_override = $lq__event_badge_obj?.option_ticket_6_override ?? $lq__event_badge_obj?.option_ticket_6;
// option_ticket_7_override = $lq__event_badge_obj?.option_ticket_7_override ?? $lq__event_badge_obj?.option_ticket_7;
// option_ticket_8_override = $lq__event_badge_obj?.option_ticket_8_override ?? $lq__event_badge_obj?.option_ticket_8;
// These are no longer needed as we use the editable fields directly
// option_other_1_override = $lq__event_badge_obj?.option_other_1_override ?? $lq__event_badge_obj?.option_other_1;
// option_other_2_override = $lq__event_badge_obj?.option_other_2_override ?? $lq__event_badge_obj?.option_other_2;
// option_ticket_1_override = $lq__event_badge_obj?.option_ticket_1_override ?? $lq__event_badge_obj?.option_ticket_1;
// option_ticket_2_override = $lq__event_badge_obj?.option_ticket_2_override ?? $lq__event_badge_obj?.option_ticket_2;
// option_ticket_3_override = $lq__event_badge_obj?.option_ticket_3_override ?? $lq__event_badge_obj?.option_ticket_3;
return results;
})
@@ -411,6 +334,90 @@
console.error(error);
}
}
async function handle_save_changes() {
if (!lq__event_badge_obj?.event_badge_id_random) {
console.error('Cannot save changes: event_badge_id_random is missing.');
return;
}
update_status = 'loading';
update_complete = false;
const data_to_update: key_val = {};
// Only include fields that have actually changed
if (editable_full_name_override !== (lq__event_badge_obj.full_name_override ?? lq__event_badge_obj.full_name)) {
data_to_update.full_name_override = editable_full_name_override;
}
if (editable_professional_title_override !== (lq__event_badge_obj.professional_title_override ?? lq__event_badge_obj.professional_title)) {
data_to_update.professional_title_override = editable_professional_title_override;
}
if (editable_affiliations_override !== (lq__event_badge_obj.affiliations_override ?? lq__event_badge_obj.affiliations)) {
data_to_update.affiliations_override = editable_affiliations_override;
}
if (editable_location_override !== (lq__event_badge_obj.location_override ?? lq__event_badge_obj.location)) {
data_to_update.location_override = editable_location_override;
}
if (editable_allow_tracking !== lq__event_badge_obj.allow_tracking) {
data_to_update.allow_tracking = editable_allow_tracking;
}
if (editable_email !== lq__event_badge_obj.email) {
data_to_update.email = editable_email;
}
if (editable_badge_type_code !== lq__event_badge_obj.badge_type_code) {
data_to_update.badge_type_code = editable_badge_type_code;
}
if (Object.keys(data_to_update).length === 0) {
console.log('No changes to save.');
update_status = 'done';
update_complete = true;
if (!is_review_mode) {
edit_mode_active = false;
$ae_loc.edit_mode = false;
}
return;
}
try {
await events_func.update_ae_obj__event_badge({
api_cfg: $ae_api,
event_badge_id: lq__event_badge_obj.event_badge_id_random,
data_kv: data_to_update,
log_lvl: log_lvl
});
update_status = 'done';
update_complete = true;
if (!is_review_mode) {
edit_mode_active = false;
$ae_loc.edit_mode = false;
}
// Optionally, refresh the lq__event_badge_obj if needed, though Dexie might handle it
} catch (error) {
console.error('Error saving changes:', error);
update_status = 'error';
update_complete = true;
}
}
function handle_cancel_changes() {
if (lq__event_badge_obj) {
editable_full_name_override = lq__event_badge_obj.full_name_override ?? lq__event_badge_obj.full_name;
editable_professional_title_override = lq__event_badge_obj.professional_title_override ?? lq__event_badge_obj.professional_title;
editable_affiliations_override = lq__event_badge_obj.affiliations_override ?? lq__event_badge_obj.affiliations;
editable_location_override = lq__event_badge_obj.location_override ?? lq__event_badge_obj.location;
editable_allow_tracking = lq__event_badge_obj.allow_tracking ?? null;
editable_email = lq__event_badge_obj.email ?? null;
editable_badge_type_code = lq__event_badge_obj.badge_type_code ?? null;
}
if (!is_review_mode) {
edit_mode_active = false;
$ae_loc.edit_mode = false;
}
update_status = 'idle';
update_complete = true;
}
</script>
<!--
@@ -482,42 +489,77 @@ onkeypress={() => {
transition-all group
flex flex-col gap-1 items-center justify-center
"
class:preset-outlined-warning-200-800={$ae_loc.edit_mode}
class:preset-tonal-warning={$ae_loc.edit_mode}
class:preset-outlined-warning-200-800={edit_mode_active}
class:preset-tonal-warning={edit_mode_active}
>
<button
class="
btn btn-sm text-xs
preset-tonal-warning preset-outlined-warning-100-900 hover:preset-filled-secondary-500
transition-all group
"
onclick={() => {
show_event_badge_tools_modal = true;
$ae_loc.edit_mode = !$ae_loc.edit_mode;
}}
title="Edit Badge Information"
>
{#if $ae_loc.edit_mode}
<span class="fas fa-times m-1"></span>
{:else}
<span class="fas fa-edit m-1"></span>
{/if}
<span
{#if edit_mode_active}
<button
class="
hidden
group-hover:inline-block
text-xs
btn btn-sm text-xs
preset-tonal-success preset-outlined-success-100-900 hover:preset-filled-success-500
transition-all group
"
onclick={handle_save_changes}
title="Save Changes"
>
{#if $ae_loc.edit_mode}
Close Edit
{:else}
<span class="fas fa-save m-1"></span>
<span
class="
hidden
group-hover:inline-block
text-xs
"
>
Save Changes
</span>
</button>
<button
class="
btn btn-sm text-xs
preset-tonal-tertiary preset-outlined-tertiary-100-900 hover:preset-filled-tertiary-500
transition-all group
"
onclick={handle_cancel_changes}
title="Cancel Editing"
>
<span class="fas fa-times m-1"></span>
<span
class="
hidden
group-hover:inline-block
text-xs
"
>
Cancel
</span>
</button>
{:else}
<button
class="
btn btn-sm text-xs
preset-tonal-warning preset-outlined-warning-100-900 hover:preset-filled-secondary-500
transition-all group
"
onclick={() => {
edit_mode_active = true;
$ae_loc.edit_mode = true;
}}
title="Edit Badge Information"
>
<span class="fas fa-edit m-1"></span>
<span
class="
hidden
group-hover:inline-block
text-xs
"
>
Edit Badge Information
{/if}
</span>
</button>
</span>
</button>
{/if}
<div class="w-md max-w-lg m-1 p-1" class:hidden={!$ae_loc.edit_mode}>
<div class="w-md max-w-lg m-1 p-1" class:hidden={!edit_mode_active}>
<p class="text-xs italic text-gray-500">
Show list of fields that they can edit here. This may need to broken down in
to sections that can be collapsed.
@@ -599,78 +641,97 @@ onkeypress={() => {
<!-- <span class="float-right text-sm italic m-2">
{longest_full_name_override_part}&times; char
</span> -->
<!-- {#if $lq__event_badge_obj.title_names}<span class="title_names">{$lq__event_badge_obj.title_names}</span>{/if} -->
<span class="full_name_override">
{#if full_name_override}
{@html full_name_override.trim()}
{:else}
-- no name --
{/if}
</span>
<!-- {#if $lq__event_badge_obj.designations}<span class="designations">{$lq__event_badge_obj.designations}</span>{/if} -->
{#if edit_mode_active}
<input type="text" bind:value={editable_full_name_override} class="input w-full text-center" />
{:else}
<span class="full_name_override">
{#if editable_full_name_override}
{@html editable_full_name_override.trim()}
{:else}
-- no name --
{/if}
</span>
{/if}
</div>
{#if professional_title_override}
{#if editable_professional_title_override || edit_mode_active}
<div
class="professional_title
{professional_title_class_size} leading-none
italic
"
>
{@html professional_title_override}
{#if edit_mode_active}
<input type="text" bind:value={editable_professional_title_override} class="input w-full text-center" />
{:else}
{@html editable_professional_title_override}
{/if}
</div>
{/if}
</div>
<!-- {#if $lq__event_badge_obj.pronouns}
<div class="pronouns">
<span class="pronouns">{@html $lq__event_badge_obj.pronouns}</span>
</div>
{/if} -->
<!-- {#if $lq__event_badge_obj.professional_title}
<div class="professional_title">
<span class="professional_title" class:str_25={$lq__event_badge_obj.professional_title.length>25}>{@html $lq__event_badge_obj.professional_title}</span>
</div>
{/if} -->
{#if affiliations_override || location_override}
{#if editable_affiliations_override || editable_location_override || edit_mode_active || editable_email || edit_mode_active || editable_allow_tracking !== null || edit_mode_active}
<div
class="affiliations_location
max-h-[3.0in] m-0 p-0
hover:outline-2 hover:outline-dashed hover:outline-gray-500/75
"
>
{#if affiliations_override}
{#if editable_affiliations_override || edit_mode_active}
<div
class="affiliations
{affiliations_class_size} leading-none
"
>
{@html affiliations_override}
{#if edit_mode_active}
<textarea bind:value={editable_affiliations_override} class="textarea w-full text-center" rows="2"></textarea>
{:else}
{@html editable_affiliations_override}
{/if}
</div>
{/if}
{#if location_override}
{#if editable_location_override || edit_mode_active}
<div
class="location
{location_class_size} leading-none
"
>
<span class="city state_province country"
>{@html location_override}</span
>
{#if edit_mode_active}
<input type="text" bind:value={editable_location_override} class="input w-full text-center" />
{:else}
<span class="city state_province country"
>{@html editable_location_override}</span
>
{/if}
</div>
{/if}
{#if editable_email || edit_mode_active}
<div class="email-field text-sm">
{#if edit_mode_active}
<label>Email: <input type="email" bind:value={editable_email} class="input w-full" /></label>
{:else}
Email: {editable_email}
{/if}
</div>
{/if}
{#if editable_allow_tracking !== null || edit_mode_active}
<div class="allow-tracking-field text-sm flex items-center justify-center gap-2">
{#if edit_mode_active}
<label>Allow Tracking: <input type="checkbox" bind:checked={editable_allow_tracking} class="checkbox" /></label>
{:else}
Allow Tracking: {editable_allow_tracking ? 'Yes' : 'No'}
{/if}
</div>
{/if}
</div>
{/if}
{#if ['front_bool', 'front_back_bool'].includes(option_ticket_1_display_opt) || ['front_bool', 'front_back_bool'].includes(option_ticket_2_display_opt) || ['front_bool', 'front_back_bool'].includes(option_ticket_3_display_opt) || $lq__event_badge_template_obj.show_qr_front}
{#if ['front_bool', 'front_back_bool'].includes(option_ticket_1_display_opt) || ['front_bool', 'front_back_bool'].includes(option_ticket_2_display_opt) || ['front_bool', 'front_back_bool'].includes(option_ticket_3_display_opt) || $lq__event_badge_template_obj.show_qr_front || edit_mode_active}
<div class="special">
<span class="badge_body_special_left">
<!-- {#if option_ticket_1_override}<span class="ticket_1_code fg_red fas fa-star"></span>{/if} -->
{#if option_ticket_1_override}<span
class="ticket_1_code fg_green fas fa-star"
></span>{/if}
@@ -678,7 +739,6 @@ onkeypress={() => {
<span class="badge_body_special_right">
{#if option_ticket_2_override}
<span class=" ticket_2_code fg_red fas fa-star"></span>
<!-- <span class=" ticket_2_code fg_gold fas fa-star"></span> -->
{/if}
{#if option_ticket_3_override}
<span class="ticket_3_code fg_blue fas fa-star"></span>
@@ -704,7 +764,7 @@ onkeypress={() => {
<div
class="badge_footer
{use_badge_type_code.toLowerCase()}
{editable_badge_type_code?.toLowerCase()}
justify-self-end
min-h-[.25in]
max-h-[.50in]
@@ -714,34 +774,41 @@ onkeypress={() => {
flex flex-row gap-1 items-center justify-center
hover:outline-2 hover:outline-dashed hover:outline-gray-500/75
"
title={use_badge_type_code}
title={editable_badge_type_code}
>
THE FOOTER
{#if option_other_1_override && ['front_bool', 'front_back_bool'].includes(option_other_1_display_opt)}
<span class="badge_footer_special_left"
><span class="fas fa-biohazard"></span></span
{#if edit_mode_active && badge_type_code_li}
<label>Badge Type:
<select bind:value={editable_badge_type_code} class="select text-xs px-1 max-w-fit">
{#each badge_type_code_li as badge_type_code_item}
<option value={badge_type_code_item.code}>{badge_type_code_item.name}</option>
{/each}
</select>
</label>
{:else}
THE FOOTER
{#if option_other_1_override && ['front_bool', 'front_back_bool'].includes(option_other_1_display_opt)}
<span class="badge_footer_special_left"
><span class="fas fa-biohazard"></span></span
>
{:else if option_other_1_override && ['front_html', 'front_back_html'].includes(option_other_1_display_opt)}
<span class="badge_footer_special_left">{@html option_other_1_override}</span>
{/if}
<span class="badge_footer_center {editable_badge_type_code?.toLowerCase()}"
>{editable_badge_type_code}</span
>
{:else if option_other_1_override && ['front_html', 'front_back_html'].includes(option_other_1_display_opt)}
<span class="badge_footer_special_left">{@html option_other_1_override}</span>
{/if}
<span class="badge_footer_center {use_badge_type_code.toLowerCase()}"
>{@html use_badge_type}</span
>
<!-- {#if $lq__event_badge_obj.other_2}
<span class="badge_footer_special_right"><span class="fas fa-star-of-life"></span></span>
{/if} -->
{#if option_other_2_override && ['front_bool', 'front_back_bool'].includes(option_other_2_display_opt)}
<span class="badge_footer_special_right"
><span class="fas fa-star-of-life"></span></span
>
{:else if option_other_2_override && ['front_html', 'front_back_html'].includes(option_other_2_display_opt)}
<span class="badge_footer_special_right">{@html option_other_2_override}</span>
{#if option_other_2_override && ['front_bool', 'front_back_bool'].includes(option_other_2_display_opt)}
<span class="badge_footer_special_right"
><span class="fas fa-star-of-life"></span></span
>
{:else if option_other_2_override && ['front_html', 'front_back_html'].includes(option_other_2_display_opt)}
<span class="badge_footer_special_right">{@html option_other_2_override}</span>
{/if}
{/if}
</div>
<!-- {#if $lq__event_badge_template_obj.show_qr_front}
<div class="container qr_code">
{#await initial_loading_promise}

View File

@@ -53,72 +53,114 @@
<ul class="list-disc list-inside">
{#each $lq__event_badge_obj_li as event_badge_obj (event_badge_obj.event_badge_id_random)}
<li
class="
border-b border-gray-300 dark:border-gray-600 py-0.5
flex flex-row gap-1 items-center justify-between w-full
"
>
<a
href={`/events/${event_badge_obj.event_id}/badges/${event_badge_obj.event_badge_id}`}
class="flex flex-row gap-1 items-center justify-start min-w-fit"
title={`Badge: ${event_badge_obj.full_name ?? event_badge_obj.given_name ?? '-- no name --'}\nID: ${event_badge_obj.event_badge_id}`}
{#if !event_badge_obj.hide || trusted_access}
<li
class="
border-b border-gray-300 dark:border-gray-600 py-0.5
flex flex-row gap-1 items-center justify-between w-full
"
>
<span>
{#if event_badge_obj.hide}
<span class="fas fa-eye-slash mx-1"></span>
{:else}
<span class="fas fa-id-badge mx-1"></span>
{/if}
{#if event_badge_obj.print_count >= 1}
<!-- Show a green checkmark -->
<span class="fas fa-check text-green-500"></span>
{#if event_badge_obj.print_count < 1 || trusted_access}
<a
href={`/events/${event_badge_obj.event_id}/badges/${event_badge_obj.event_badge_id}`}
class="flex flex-row gap-1 items-center justify-start min-w-fit"
title={`Badge: ${event_badge_obj.full_name ?? event_badge_obj.given_name ?? '-- no name --'}\nID: ${event_badge_obj.event_badge_id}`}
>
<span>
{#if event_badge_obj.hide}
<span class="fas fa-eye-slash mx-1"></span>
{:else}
<span class="fas fa-id-badge mx-1"></span>
{/if}
{#if event_badge_obj.print_count >= 1}
<!-- Show a green checkmark -->
<span class="fas fa-check text-green-500"></span>
<span class="print_count px-1"
>{event_badge_obj.print_count}&times;</span
> {/if}
</span>
<span class="font-bold">
{#if event_badge_obj.full_name_override}
{event_badge_obj.full_name_override}
{:else if event_badge_obj.full_name}
{event_badge_obj.full_name}
{:else if event_badge_obj.given_name}
{event_badge_obj.given_name} {event_badge_obj.family_name}
{:else}
-- no name --
{/if}
</span>
</a>
{:else}
<span
class="flex flex-row gap-1 items-center justify-start min-w-fit"
title={`Badge: ${event_badge_obj.full_name ?? event_badge_obj.given_name ?? '-- no name --'}\nID: ${event_badge_obj.event_badge_id}`}
>
<span>
{#if event_badge_obj.hide}
<span class="fas fa-eye-slash mx-1"></span>
{:else}
<span class="fas fa-id-badge mx-1"></span>
{/if}
{#if event_badge_obj.print_count >= 1}
<!-- Show a green checkmark -->
<span class="fas fa-check text-green-500"></span>
<span class="print_count px-1"
>{event_badge_obj.print_count}&times;</span
> {/if}
</span>
<span class="font-bold">
{#if event_badge_obj.full_name_override}
{event_badge_obj.full_name_override}
{:else if event_badge_obj.full_name}
{event_badge_obj.full_name}
{:else if event_badge_obj.given_name}
{event_badge_obj.given_name} {event_badge_obj.family_name}
{:else}
-- no name --
{/if}
</span>
</span>
{/if}
{#if show_sensitive_fields}
-
<span class="min-w-fit">
<span class="fas fa-envelope"></span>
{#if trusted_access}
<span class="print_count preset-tonal-warning px-1"
>{event_badge_obj.print_count}&times;</span
>
{event_badge_obj.email}
{:else}
{event_badge_obj.email
? event_badge_obj.email.replace(/^(.{3}).*@/, '$1...@')
: ''}
{/if}
{/if}
</span>
</span>
{/if}
{#if !hide_affiliations}
-
{event_badge_obj?.affiliations ?? '-- no affiliations --'}
{/if}
{#if !hide_location}
-
{event_badge_obj?.location ?? '-- no location --'}
{/if}
{#if !hide_badge_type}
-
<span class="italic">{event_badge_obj?.badge_type}</span>
{/if}
<span class="font-bold">
{#if event_badge_obj.full_name_override}
{event_badge_obj.full_name_override}
{:else if event_badge_obj.full_name}
{event_badge_obj.full_name}
{:else if event_badge_obj.given_name}
{event_badge_obj.given_name} {event_badge_obj.family_name}
{:else}
-- no name --
{/if}
</span>
</a>
{#if show_sensitive_fields}
-
<span class="min-w-fit">
<!-- The email should only be the first 3 chars and then @domain name. -->
<!-- Example: original: scott.idem@oneskyit.com obscured: sco...@oneskyit.com -->
<span class="fas fa-envelope"></span>
{event_badge_obj.email
? event_badge_obj.email.replace(/^(.{3}).*@/, '$1...@')
: ''}
</span>
{/if}
{#if !hide_affiliations}
-
{event_badge_obj?.affiliations ?? '-- no affiliations --'}
{/if}
{#if !hide_location}
-
{event_badge_obj?.location ?? '-- no location --'}
{/if}
{#if !hide_badge_type}
-
<span class="italic">{event_badge_obj?.badge_type}</span>
{/if}
</li>
{#if trusted_access}
<a
href={`/events/${event_badge_obj.event_id}/badges/${event_badge_obj.event_badge_id}#review`}
class="btn btn-sm variant-soft-primary">Review</a
>
{/if}
</li>
{/if}
{/each}
</ul>

View File

@@ -0,0 +1,102 @@
<script lang="ts">
import { liveQuery } from 'dexie';
import { events_func } from '$lib/ae_events_functions';
import { ae_api } from '$lib/stores/ae_stores';
import { events_slct } from '$lib/stores/ae_events_stores';
import Comp_badge_obj_view from '../[badge_id]/ae_comp__badge_obj_view.svelte';
interface Props {
data: any; // PageData from SvelteKit
}
let { data }: Props = $props();
let event_id: string = data.params.event_id;
let printed_status_filter: string | null = data.url.searchParams.get('printed_status');
let badge_type_code_filter: string | null = data.url.searchParams.get('badge_type_code');
let lq__filtered_badges = $derived(
liveQuery(async () => {
const filters: { printed_status?: string; type_code?: string } = {};
if (printed_status_filter) filters.printed_status = printed_status_filter;
if (badge_type_code_filter) filters.type_code = badge_type_code_filter;
// Fetch badges using the search function, with a high limit for bulk printing
const result = await events_func.search__event_badge({
api_cfg: $ae_api,
event_id: event_id,
limit: 5000, // A high limit to fetch all relevant badges for printing
...filters,
log_lvl: 0
});
return result || [];
})
);
// Function to trigger browser print
function trigger_print() {
window.print();
}
</script>
<svelte:head>
<title>Bulk Print Badges for Event {event_id}</title>
<style>
/* Print-specific styles */
@media print {
body {
margin: 0;
padding: 0;
}
.print-button {
display: none;
}
/* Customize how badges are laid out for printing */
.badge-print-container {
page-break-after: always; /* Each badge on a new page or arrange multiple per page */
/* Adjust width/height for optimal badge size on print media */
width: 4in; /* Example width */
height: 6in; /* Example height */
margin: 0.25in; /* Example margin */
border: 1px solid #ccc; /* Optional: for visual separation on print */
}
}
</style>
</svelte:head>
<section class="p-4">
<h1 class="h1">Bulk Print Badges</h1>
<div class="print-button my-4">
<button class="btn btn-primary" onclick={trigger_print}>
<span class="fas fa-print mr-2"></span> Print Badges
</button>
<a href={`/events/${event_id}/badges`} class="btn btn-tertiary ml-2">
<span class="fas fa-arrow-left mr-2"></span> Back to Search
</a>
</div>
{#await lq__filtered_badges}
<p>Loading badges for printing...</p>
{:then badges_to_print}
{#if badges_to_print.length > 0}
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{#each badges_to_print as badge_obj (badge_obj.event_badge_id_random)}
<div class="badge-print-container">
<Comp_badge_obj_view
event_id={event_id}
event_badge_id={badge_obj.event_badge_id_random}
lq__event_badge_obj={badge_obj}
is_review_mode={false}
log_lvl={0}
/>
</div>
{/each}
</div>
{:else}
<p>No badges found matching your criteria for printing.</p>
{/if}
{:catch error}
<p class="text-error-500">Error loading badges: {error.message}</p>
{/await}
</section>

View File

@@ -0,0 +1,136 @@
<script lang="ts">
import { liveQuery } from 'dexie';
import { events_func } from '$lib/ae_events_functions';
import { ae_api } from '$lib/stores/ae_stores';
import { events_slct } from '$lib/stores/ae_events_stores';
import { Modal } from '@skeletonlabs/skeleton';
import Comp_badge_template_form from './ae_comp__badge_template_form.svelte';
interface Props {
data: any; // PageData from SvelteKit
}
let { data }: Props = $props();
let event_id: string = data.params.event_id;
let show_create_template_modal: boolean = $state(false);
let selected_template_id: string | null = $state(null);
let show_edit_template_modal: boolean = $state(false);
let lq__badge_templates = $derived(
liveQuery(async () => {
const result = await events_func.load_ae_obj_li__event_badge_template({
api_cfg: $ae_api,
event_id: event_id,
log_lvl: 0
});
return result || [];
})
);
function handle_create_success() {
show_create_template_modal = false;
// Trigger a refresh of the list (by updating a store or re-fetching)
// For now, relying on liveQuery to react to DB changes
}
function handle_edit_success() {
show_edit_template_modal = false;
selected_template_id = null;
// Trigger a refresh of the list
}
function handle_cancel() {
show_create_template_modal = false;
show_edit_template_modal = false;
selected_template_id = null;
}
function edit_template(template_id: string) {
selected_template_id = template_id;
show_edit_template_modal = true;
}
async function delete_template(template_id: string) {
if (confirm('Are you sure you want to delete this badge template? This action cannot be undone.')) {
try {
await events_func.delete_ae_obj_id__event_badge_template({
api_cfg: $ae_api,
event_badge_template_id: template_id
});
// Rely on liveQuery to refresh list
} catch (error) {
console.error('Error deleting template:', error);
alert('Failed to delete template.');
}
}
}
</script>
<svelte:head>
<title>Badge Templates - Event {event_id}</title>
</svelte:head>
<section class="p-4">
<h1 class="h1">Badge Templates</h1>
<div class="my-4 flex justify-end">
<button class="btn btn-primary" onclick={() => show_create_template_modal = true}>
<span class="fas fa-plus mr-2"></span> Add New Template
</button>
</div>
{#await lq__badge_templates}
<p>Loading badge templates...</p>
{:then templates}
{#if templates.length > 0}
<div class="card p-4">
<ul class="list-group">
{#each templates as template (template.event_badge_template_id_random)}
<li class="list-group-item flex justify-between items-center">
<span>{template.name}</span>
<div>
<button class="btn btn-sm variant-filled-primary" onclick={() => edit_template(template.event_badge_template_id_random)}>
<span class="fas fa-edit"></span> Edit
</button>
<button class="btn btn-sm variant-filled-error ml-2" onclick={() => delete_template(template.event_badge_template_id_random)}>
<span class="fas fa-trash"></span> Delete
</button>
</div>
</li>
{/each}
</ul>
</div>
{:else}
<p>No badge templates found for this event. Click "Add New Template" to create one.</p>
{/if}
{:catch error}
<p class="text-error-500">Error loading templates: {error.message}</p>
{/await}
</section>
{#if show_create_template_modal}
<Modal bind:show={show_create_template_modal}>
<div class="card p-4">
<Comp_badge_template_form
event_id={event_id}
on:success={handle_create_success}
on:cancel={handle_cancel}
/>
</div>
</Modal>
{/if}
{#if show_edit_template_modal}
<Modal bind:show={show_edit_template_modal}>
<div class="card p-4">
<Comp_badge_template_form
event_id={event_id}
template_id={selected_template_id}
on:success={handle_edit_success}
on:cancel={handle_cancel}
/>
</div>
</Modal>
{/if}

View File

@@ -0,0 +1,204 @@
<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;
template_id?: string | null; // Null for creation, string for editing
}
let { event_id, template_id = null }: Props = $props();
const dispatch = createEventDispatcher();
// Form fields
let name: string = '';
let header_path: string = '';
let logo_path: string = '';
let header_row_1: string = '';
let header_row_2: string = '';
let secondary_header_path: string = '';
let footer_text: string = '';
let show_qr_front: boolean = true;
let show_qr_back: boolean = true;
let wireless_ssid: string = '';
let wireless_password: string = '';
let ticket_1_text: string = '';
let ticket_2_text: string = '';
let ticket_3_text: string = '';
let submit_status: string = 'idle'; // idle, loading, success, error
// Load template data if in edit mode
$effect(async () => {
if (template_id) {
submit_status = 'loading';
try {
const template_obj = await events_func.load_ae_obj_id__event_badge_template({
api_cfg: $ae_api,
event_badge_template_id: template_id
});
if (template_obj) {
name = template_obj.name || '';
header_path = template_obj.header_path || '';
logo_path = template_obj.logo_path || '';
header_row_1 = template_obj.header_row_1 || '';
header_row_2 = template_obj.header_row_2 || '';
secondary_header_path = template_obj.secondary_header_path || '';
footer_text = template_obj.footer_text || '';
show_qr_front = template_obj.show_qr_front ?? true;
show_qr_back = template_obj.show_qr_back ?? true;
wireless_ssid = template_obj.wireless_ssid || '';
wireless_password = template_obj.wireless_password || '';
ticket_1_text = template_obj.ticket_1_text || '';
ticket_2_text = template_obj.ticket_2_text || '';
ticket_3_text = template_obj.ticket_3_text || '';
submit_status = 'idle';
} else {
submit_status = 'error';
console.error('Template not found for editing.');
}
} catch (error) {
submit_status = 'error';
console.error('Error loading template for editing:', error);
}
}
});
async function handle_submit() {
submit_status = 'loading';
const data_to_save: key_val = {
name,
header_path,
logo_path,
header_row_1,
header_row_2,
secondary_header_path,
footer_text,
show_qr_front,
show_qr_back,
wireless_ssid,
wireless_password,
ticket_1_text,
ticket_2_text,
ticket_3_text
};
try {
let result;
if (template_id) {
result = await events_func.update_ae_obj__event_badge_template({
api_cfg: $ae_api,
event_badge_template_id: template_id,
data_kv: data_to_save
});
} else {
result = await events_func.create_ae_obj__event_badge_template({
api_cfg: $ae_api,
event_id: event_id,
data_kv: data_to_save
});
}
if (result) {
submit_status = 'success';
dispatch('success', result);
} else {
submit_status = 'error';
dispatch('error', 'Failed to save template');
}
} catch (error) {
submit_status = 'error';
console.error('Error saving template:', error);
dispatch('error', error);
}
}
function handle_cancel() {
dispatch('cancel');
}
</script>
<form onsubmit|preventDefault={handle_submit} class="p-4 space-y-4">
<h3 class="h3">{template_id ? 'Edit' : 'Create New'} Badge Template</h3>
<label class="label">
<span>Template Name</span>
<input type="text" bind:value={name} class="input" required />
</label>
<label class="label">
<span>Header Path (URL)</span>
<input type="text" bind:value={header_path} class="input" />
</label>
<label class="label">
<span>Logo Path (URL, if no Header Path)</span>
<input type="text" bind:value={logo_path} class="input" />
</label>
<label class="label">
<span>Header Row 1 Text (HTML allowed)</span>
<textarea bind:value={header_row_1} class="textarea" rows="2"></textarea>
</label>
<label class="label">
<span>Header Row 2 Text (HTML allowed)</span>
<textarea bind:value={header_row_2} class="textarea" rows="2"></textarea>
</label>
<label class="label">
<span>Secondary Header Path (URL, back of badge)</span>
<input type="text" bind:value={secondary_header_path} class="input" />
</label>
<label class="label">
<span>Footer Text (HTML allowed)</span>
<textarea bind:value={footer_text} class="textarea" rows="2"></textarea>
</label>
<label class="label flex items-center gap-2">
<input type="checkbox" bind:checked={show_qr_front} class="checkbox" />
<span>Show QR Code on Front</span>
</label>
<label class="label flex items-center gap-2">
<input type="checkbox" bind:checked={show_qr_back} class="checkbox" />
<span>Show QR Code on Back</span>
</label>
<label class="label">
<span>Wireless SSID</span>
<input type="text" bind:value={wireless_ssid} class="input" />
</label>
<label class="label">
<span>Wireless Password</span>
<input type="text" bind:value={wireless_password} class="input" />
</label>
<label class="label">
<span>Ticket 1 Text (HTML allowed)</span>
<textarea bind:value={ticket_1_text} class="textarea" rows="2"></textarea>
</label>
<label class="label">
<span>Ticket 2 Text (HTML allowed)</span>
<textarea bind:value={ticket_2_text} class="textarea" rows="2"></textarea>
</label>
<label class="label">
<span>Ticket 3 Text (HTML allowed)</span>
<textarea bind:value={ticket_3_text} class="textarea" rows="2"></textarea>
</label>
<div class="flex justify-end gap-2">
<button type="button" class="btn variant-filled-tertiary" onclick={handle_cancel} disabled={submit_status === 'loading'}>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}
{template_id ? 'Save Changes' : 'Create Template'}
</button>
</div>
</form>
{#if submit_status === 'success'}
<p class="text-green-500">Template saved successfully!</p>
{:else if submit_status === 'error'}
<p class="text-red-500">Error saving template. Please try again.</p>
{/if}

View File

@@ -1331,7 +1331,7 @@
bodyClass="p-0 space-y-0 overflow-y-auto flex flex-col gap-1 items-center justify-center"
headerClass={`fixed top-0 right-0 left-0 p-1 md:p-2 flex flex-row items-center ${$events_loc.launcher.controller == 'remote' ? 'hidden' : ''} bg-white dark:bg-gray-800 opacity-50 ${$events_loc.launcher.hide__modal_header_title ? 'justify-center' : 'justify-between'}`}
footerClass="text-center hidden"
onclose={async () => {
on:close={async () => {
$events_sess.launcher.modal__open_event_file_id = null;
if (
$events_loc.launcher.controller == 'local_push' &&