badges(runtime): honor mod_badges_json flags (badge_id_only search, QR toggle, add/upload/mass-print gating)

This commit is contained in:
Scott Idem
2026-04-02 17:23:35 -04:00
parent 4a5b4bf7cd
commit 0ab8b936ce
3 changed files with 72 additions and 66 deletions

View File

@@ -19,11 +19,11 @@ import { ae_loc, ae_api } from '$lib/stores/ae_stores';
import { db_events } from '$lib/ae_events/db_events'; import { db_events } from '$lib/ae_events/db_events';
import { import {
events_loc,
events_sess, events_sess,
events_slct, events_slct,
events_trigger events_trigger
} from '$lib/stores/ae_events_stores'; } from '$lib/stores/ae_events_stores';
import { badges_loc } from '$lib/stores/ae_events_stores__badges.svelte';
import { events_func } from '$lib/ae_events/ae_events_functions'; import { events_func } from '$lib/ae_events/ae_events_functions';
import Comp_badge_search from './ae_comp__badge_search.svelte'; import Comp_badge_search from './ae_comp__badge_search.svelte';
@@ -54,24 +54,8 @@ let lq__badge_template_li = $derived(
.sortBy('name'); .sortBy('name');
}) })
); );
// *** Initialization & Store Guard *** // badges_loc (PersistedState) is always initialized from badges_loc_defaults —
// Ensure all search fields are initialized to prevent circular undefined triggers // no manual typeof guards needed. All fields are guaranteed to exist.
if ($events_loc.badges) {
if (typeof $events_loc.badges.search_version === 'undefined')
$events_loc.badges.search_version = 0;
if (typeof $events_loc.badges.qry__remote_first === 'undefined')
$events_loc.badges.qry__remote_first = false;
if (typeof $events_loc.badges.fulltext_search_qry_str === 'undefined')
$events_loc.badges.fulltext_search_qry_str = '';
if (typeof $events_loc.badges.search_badge_type_code === 'undefined')
$events_loc.badges.search_badge_type_code = '';
if (typeof $events_loc.badges.qry_printed_status === 'undefined')
$events_loc.badges.qry_printed_status = 'all';
if (typeof $events_loc.badges.qry_affiliations === 'undefined')
$events_loc.badges.qry_affiliations = '';
if (typeof $events_loc.badges.qry_sort_order === 'undefined')
$events_loc.badges.qry_sort_order = '';
}
// Variables // Variables
let show_create_badge_modal: boolean = $state(false); let show_create_badge_modal: boolean = $state(false);
@@ -113,10 +97,10 @@ let lq__event_badge_obj_li = $derived.by(() => {
// SCENARIO 2: Fallback broad search (Only if no active filters) // SCENARIO 2: Fallback broad search (Only if no active filters)
if ( if (
event_id && event_id &&
!$events_loc.badges.fulltext_search_qry_str && !badges_loc.current.fulltext_search_qry_str &&
$events_loc.badges.qry_printed_status === 'all' && badges_loc.current.qry_printed_status === 'all' &&
!$events_loc.badges.qry_affiliations && !badges_loc.current.qry_affiliations &&
!$events_loc.badges.search_badge_type_code !badges_loc.current.search_badge_type_code
) { ) {
if (log_lvl) if (log_lvl)
console.log( console.log(
@@ -136,16 +120,18 @@ let lq__event_badge_obj_li = $derived.by(() => {
// Standardized Reactive Search Pattern (Aether UI V3) // Standardized Reactive Search Pattern (Aether UI V3)
// 1. Isolate dependencies into a stable derived object // 1. Isolate dependencies into a stable derived object
let search_params = $derived({ let search_params = $derived({
v: $events_loc.badges.search_version, v: badges_loc.current.search_version,
str: ($events_loc.badges.fulltext_search_qry_str ?? '') str: (badges_loc.current.fulltext_search_qry_str ?? '')
.toLowerCase() .toLowerCase()
.trim(), .trim(),
type: $events_loc.badges.search_badge_type_code, type: badges_loc.current.search_badge_type_code,
printed: $events_loc.badges.qry_printed_status, printed: badges_loc.current.qry_printed_status,
aff: ($events_loc.badges.qry_affiliations ?? '').toLowerCase().trim(), aff: (badges_loc.current.qry_affiliations ?? '').toLowerCase().trim(),
sort: $events_loc.badges.qry_sort_order, sort: badges_loc.current.qry_sort_order,
event_id: $events_slct?.event_id, event_id: $events_slct?.event_id,
remote_first: $events_loc.badges.qry__remote_first remote_first: badges_loc.current.qry__remote_first,
// Event-level override: when true, restrict searches to badge IDs only
badge_id_only: $lq__event_obj?.mod_badges_json?.badge_id_only_search ?? false
}); });
// 2. Controlled effect for triggering searches // 2. Controlled effect for triggering searches
@@ -208,7 +194,10 @@ async function handle_search_refresh(params: any) {
return false; return false;
} }
if (qry_str) { if (params.badge_id_only && qry_str) {
const id = (badge.event_badge_id ?? '').toLowerCase();
if (!id.includes(qry_str)) return false;
} else if (qry_str) {
const given_name = ( const given_name = (
badge.given_name ?? '' badge.given_name ?? ''
).toLowerCase(); ).toLowerCase();
@@ -387,7 +376,7 @@ async function handle_search_refresh(params: any) {
<Comp_badge_search event_id={$events_slct?.event_id ?? ''} log_lvl={1} <Comp_badge_search event_id={$events_slct?.event_id ?? ''} log_lvl={1}
></Comp_badge_search> ></Comp_badge_search>
{#if $ae_loc.edit_mode} {#if $ae_loc.edit_mode && ($lq__event_obj?.mod_badges_json?.enable_add_badge_btn ?? true)}
<div class="flex justify-end px-4"> <div class="flex justify-end px-4">
<button <button
type="button" type="button"
@@ -420,8 +409,8 @@ async function handle_search_refresh(params: any) {
create_badge_dialog?.close(); create_badge_dialog?.close();
show_create_badge_modal = false; show_create_badge_modal = false;
// Trigger a remote-first refresh so the new badge appears in results // Trigger a remote-first refresh so the new badge appears in results
$events_loc.badges.search_version = ($events_loc.badges.search_version ?? 0) + 1; badges_loc.current.search_version = (badges_loc.current.search_version ?? 0) + 1;
$events_loc.badges.qry__remote_first = true; badges_loc.current.qry__remote_first = true;
}} }}
oncancel={() => { oncancel={() => {
create_badge_dialog?.close(); create_badge_dialog?.close();

View File

@@ -15,9 +15,19 @@ import {
Search Search
} from '@lucide/svelte'; } from '@lucide/svelte';
import { ae_loc, ae_api } from '$lib/stores/ae_stores'; import { ae_loc, ae_api } from '$lib/stores/ae_stores';
import { events_loc, events_sess } from '$lib/stores/ae_events_stores'; import { events_sess } from '$lib/stores/ae_events_stores';
import { badges_loc } from '$lib/stores/ae_events_stores__badges.svelte';
import Element_qr_scanner from '$lib/elements/element_qr_scanner.svelte'; import Element_qr_scanner from '$lib/elements/element_qr_scanner.svelte';
import { ae_util } from '$lib/ae_utils/ae_utils'; import { ae_util } from '$lib/ae_utils/ae_utils';
import { liveQuery } from 'dexie';
import { db_events } from '$lib/ae_events/db_events';
let lq__event_obj = $derived(
liveQuery(async () => {
if (!event_id) return null;
return await db_events.event.get(event_id);
})
);
// ISHLT 2024 badge type codes // ISHLT 2024 badge type codes
let badge_type_code_li = [ let badge_type_code_li = [
@@ -37,10 +47,7 @@ let badge_type_code_li = [
]; ];
function handle_search_trigger() { function handle_search_trigger() {
if ($events_loc.badges.search_version === undefined) { badges_loc.current.search_version++;
$events_loc.badges.search_version = 0;
}
$events_loc.badges.search_version++;
} }
function prevent_default<T extends Event>(fn: (event: T) => void) { function prevent_default<T extends Event>(fn: (event: T) => void) {
@@ -57,7 +64,7 @@ function handle_qr_scan_result(event: {
let obj = ae_util.process_data_string(qr_scan_result); let obj = ae_util.process_data_string(qr_scan_result);
if (obj && obj.type && obj.id && obj.type === 'event_badge') { if (obj && obj.type && obj.id && obj.type === 'event_badge') {
$events_loc.badges.fulltext_search_qry_str = obj.id; badges_loc.current.fulltext_search_qry_str = obj.id;
handle_search_trigger(); handle_search_trigger();
$events_sess.badges.show_form__search = true; $events_sess.badges.show_form__search = true;
@@ -80,7 +87,7 @@ function handle_qr_scan_result(event: {
class="flex grow flex-col items-center justify-center gap-1 md:flex-row"> class="flex grow flex-col items-center justify-center gap-1 md:flex-row">
{#if $ae_loc.trusted_access} {#if $ae_loc.trusted_access}
<select <select
bind:value={$events_loc.badges.search_badge_type_code} bind:value={badges_loc.current.search_badge_type_code}
onchange={handle_search_trigger} onchange={handle_search_trigger}
class="select select-sm max-w-fit px-1 text-xs"> class="select select-sm max-w-fit px-1 text-xs">
<option value="">-- All Badge Types --</option> <option value="">-- All Badge Types --</option>
@@ -91,7 +98,7 @@ function handle_qr_scan_result(event: {
</select> </select>
<select <select
bind:value={$events_loc.badges.qry_printed_status} bind:value={badges_loc.current.qry_printed_status}
onchange={handle_search_trigger} onchange={handle_search_trigger}
class="select select-sm max-w-fit px-1 text-xs"> class="select select-sm max-w-fit px-1 text-xs">
<option value="all">-- All Print Status --</option> <option value="all">-- All Print Status --</option>
@@ -100,7 +107,7 @@ function handle_qr_scan_result(event: {
</select> </select>
<select <select
bind:value={$events_loc.badges.qry_sort_order} bind:value={badges_loc.current.qry_sort_order}
onchange={handle_search_trigger} onchange={handle_search_trigger}
class="select select-sm max-w-fit px-1 text-xs"> class="select select-sm max-w-fit px-1 text-xs">
<option value="">-- Default Sort --</option> <option value="">-- Default Sort --</option>
@@ -122,11 +129,11 @@ function handle_qr_scan_result(event: {
<input <input
type="search" type="search"
placeholder="affiliations" placeholder="affiliations"
bind:value={$events_loc.badges.qry_affiliations} bind:value={badges_loc.current.qry_affiliations}
onkeyup={(e) => { onkeyup={(e) => {
if ( if (
e.key === 'Enter' || e.key === 'Enter' ||
($events_loc.badges.qry_affiliations?.length ?? (badges_loc.current.qry_affiliations?.length ??
0) >= 3 0) >= 3
) { ) {
handle_search_trigger(); handle_search_trigger();
@@ -137,9 +144,9 @@ function handle_qr_scan_result(event: {
<input <input
type="search" type="search"
placeholder="name, email" placeholder={$lq__event_obj?.mod_badges_json?.badge_id_only_search ? 'Badge ID' : 'name, email'}
id="badge_fulltext_search_qry_str" id="badge_fulltext_search_qry_str"
bind:value={$events_loc.badges.fulltext_search_qry_str} bind:value={badges_loc.current.fulltext_search_qry_str}
autocomplete="off" autocomplete="off"
data-lpignore="true" data-lpignore="true"
class="input grow font-mono text-lg transition-all" class="input grow font-mono text-lg transition-all"
@@ -165,14 +172,14 @@ function handle_qr_scan_result(event: {
<button <button
type="button" type="button"
class:hidden={!$events_loc.badges.fulltext_search_qry_str && class:hidden={!badges_loc.current.fulltext_search_qry_str &&
!$events_loc.badges.search_badge_type_code && !badges_loc.current.search_badge_type_code &&
$events_loc.badges.qry_printed_status === 'all'} badges_loc.current.qry_printed_status === 'all'}
onclick={() => { onclick={() => {
$events_loc.badges.fulltext_search_qry_str = ''; badges_loc.current.fulltext_search_qry_str = '';
$events_loc.badges.search_badge_type_code = ''; badges_loc.current.search_badge_type_code = '';
$events_loc.badges.qry_printed_status = 'all'; badges_loc.current.qry_printed_status = 'all';
$events_loc.badges.qry_affiliations = ''; badges_loc.current.qry_affiliations = '';
handle_search_trigger(); handle_search_trigger();
}} }}
class="btn btn-sm preset-outlined-tertiary-100-900 hover:preset-filled-tertiary-100-900 text-xs transition-all" class="btn btn-sm preset-outlined-tertiary-100-900 hover:preset-filled-tertiary-100-900 text-xs transition-all"
@@ -194,17 +201,19 @@ function handle_qr_scan_result(event: {
<div <div
class="flex flex-row flex-wrap items-center justify-center gap-2 opacity-70 transition-all hover:opacity-100"> class="flex flex-row flex-wrap items-center justify-center gap-2 opacity-70 transition-all hover:opacity-100">
{#if $events_sess.badges.show_form__search} {#if $events_sess.badges.show_form__search}
<button {#if $lq__event_obj?.mod_badges_json?.enable_search_qr ?? true}
type="button" <button
onclick={() => { type="button"
$events_sess.badges.show_form__search = false; onclick={() => {
$events_sess.badges.show_form__scan = true; $events_sess.badges.show_form__search = false;
$events_sess.badges.qr_scan_start = true; $events_sess.badges.show_form__scan = true;
}} $events_sess.badges.qr_scan_start = true;
class="btn btn-sm preset-tonal-primary border-primary-500 border"> }}
<QrCode size="1em" class="mr-1" /> class="btn btn-sm preset-tonal-primary border-primary-500 border">
QR Scan <QrCode size="1em" class="mr-1" />
</button> QR Scan
</button>
{/if}
{:else} {:else}
<button <button
type="button" type="button"
@@ -225,7 +234,7 @@ function handle_qr_scan_result(event: {
<span> Remote First </span> <span> Remote First </span>
<input <input
type="checkbox" type="checkbox"
bind:checked={$events_loc.badges.qry__remote_first} bind:checked={badges_loc.current.qry__remote_first}
onchange={handle_search_trigger} onchange={handle_search_trigger}
class="checkbox checkbox-sm" /> class="checkbox checkbox-sm" />
</label> </label>

View File

@@ -122,9 +122,11 @@ async function handle_save(field_name: string, data: any) {
<summary class="summary text-error-500 font-bold" <summary class="summary text-error-500 font-bold"
>Admin Tools</summary> >Admin Tools</summary>
<div class="space-y-4 p-4"> <div class="space-y-4 p-4">
{#if (event_obj?.mod_badges_json?.enable_add_badge_btn ?? true) || (event_obj?.mod_badges_json?.enable_upload_badge_li_btn ?? true)}
<div class="card rounded-md border p-4 text-center"> <div class="card rounded-md border p-4 text-center">
<h4 class="h4">Badge Operations</h4> <h4 class="h4">Badge Operations</h4>
<div class="mt-2 flex flex-wrap justify-center gap-2"> <div class="mt-2 flex flex-wrap justify-center gap-2">
{#if event_obj?.mod_badges_json?.enable_add_badge_btn ?? true}
<button <button
type="button" type="button"
class="btn btn-primary" class="btn btn-primary"
@@ -132,6 +134,8 @@ async function handle_save(field_name: string, data: any) {
(show_create_badge_modal = true)}> (show_create_badge_modal = true)}>
<Plus size="1em" aria-hidden="true" /> Add New Badge <Plus size="1em" aria-hidden="true" /> Add New Badge
</button> </button>
{/if}
{#if event_obj?.mod_badges_json?.enable_upload_badge_li_btn ?? true}
<button <button
type="button" type="button"
class="btn btn-primary ml-2" class="btn btn-primary ml-2"
@@ -140,9 +144,12 @@ async function handle_save(field_name: string, data: any) {
<Upload size="1em" aria-hidden="true" /> Upload Badge <Upload size="1em" aria-hidden="true" /> Upload Badge
List List
</button> </button>
{/if}
</div> </div>
</div> </div>
{/if}
{#if event_obj?.mod_badges_json?.enable_mass_print ?? true}
<div class="card rounded-md border p-4 text-center"> <div class="card rounded-md border p-4 text-center">
<h4 class="h4">Mass Print Options</h4> <h4 class="h4">Mass Print Options</h4>
<div class="mt-2 flex flex-wrap justify-center gap-2"> <div class="mt-2 flex flex-wrap justify-center gap-2">
@@ -165,6 +172,7 @@ async function handle_save(field_name: string, data: any) {
</a> </a>
</div> </div>
</div> </div>
{/if}
<div class="mt-4 flex flex-wrap justify-center gap-4"> <div class="mt-4 flex flex-wrap justify-center gap-4">
<a <a