From c198ca24546ac5b3a93fc9dc7b6d9dc19e601362 Mon Sep 17 00:00:00 2001 From: Scott Idem Date: Thu, 2 Apr 2026 18:03:23 -0400 Subject: [PATCH] chore(badges): remove legacy badge_id_only_search; sync remote badges config into badges_loc; docs update --- ...ROJECT__AE_Events_Badges_Config_Cleanup.md | 19 +-- src/lib/ae_events/ae_events__event.ts | 42 +++++++ src/lib/ae_events/ae_events_functions.ts | 1 + .../stores/ae_events_stores__badges.svelte.ts | 18 +++ .../ae_events_stores__badges_defaults.ts | 111 +++++++++++++++++- src/lib/stores/store_versions.ts | 1 + .../[event_id]/(badges)/badges/+page.svelte | 25 ++-- .../badges/[badge_id]/review/+page.svelte | 92 +++++++-------- .../badges/ae_comp__badge_search.svelte | 4 +- .../(badges)/badges/config/+page.svelte | 2 - .../events/[event_id]/settings/+page.svelte | 16 ++- ...ae_comp__event_settings_badges_form.svelte | 9 -- 12 files changed, 254 insertions(+), 86 deletions(-) create mode 100644 src/lib/stores/ae_events_stores__badges.svelte.ts diff --git a/documentation/PROJECT__AE_Events_Badges_Config_Cleanup.md b/documentation/PROJECT__AE_Events_Badges_Config_Cleanup.md index 659c85a3..4a3cc18b 100644 --- a/documentation/PROJECT__AE_Events_Badges_Config_Cleanup.md +++ b/documentation/PROJECT__AE_Events_Badges_Config_Cleanup.md @@ -1,6 +1,6 @@ # Project: Badges Config Cleanup & Config UI -**Status:** Planning / Ready to Execute +**Status:** Executed — Complete **Priority:** Medium-High (post-April 2026 BGH conference; same pattern as pres_mgmt cleanup) **Created:** 2026-04-02 **Related:** `TODO__Agents.md`, `PROJECT__AE_Events_PressMgmt_Config_Cleanup.md`, `PROJECT__Stores_Svelte5_Migration.md`, `MODULE__AE_Events_Badges.md` @@ -37,7 +37,6 @@ The badges module has accumulated the same class of problems as pres_mgmt before ```typescript interface BadgesRemoteCfg { // Search & UI behaviour - badge_id_only_search: boolean; // restrict search input to badge IDs only enable_mass_print: boolean; // show the mass-print controls enable_add_badge_btn: boolean; // show the "Add Badge" button enable_upload_badge_li_btn: boolean; // show the "Upload Badge List" button @@ -228,14 +227,16 @@ Safe and backward compatible — the review page already falls back to hardcoded ## Implementation Steps -- [ ] **Step 1** — Define `BadgesRemoteCfg` TypeScript interface (add to `ae_events_stores__badges_defaults.ts` alongside existing `BadgesLocState`) -- [ ] **Step 2** — Create `ae_events_stores__badges.svelte.ts` with `PersistedState`; add `AE_BADGES_LOC_VERSION` to `store_versions.ts` -- [ ] **Step 3** — Migrate `$events_loc.badges.*` → `badges_loc.current.*` in `+page.svelte` and `ae_comp__badge_search.svelte`; remove manual `typeof` guards -- [ ] **Step 4** — Wire `edit_permissions` into review page `can_edit_fields` (replace the two TODO blocks) -- [ ] **Step 5** — Build config UI at `(badges)/badges/config/+page.svelte` (administrator_access gated) -- [ ] **Step 6** — Update settings page `Badges` section with link to config page; retire the old form component import +- [x] **Step 1** — Define `BadgesRemoteCfg` TypeScript interface (added to `ae_events_stores__badges_defaults.ts`; also extracted `default_authenticated_can_edit` and `default_trusted_can_edit` constants) +- [x] **Step 2** — Created `ae_events_stores__badges.svelte.ts` with `PersistedState`; added `AE_BADGES_LOC_VERSION` to `store_versions.ts` +- [x] **Step 3** — Migrated `$events_loc.badges.*` → `badges_loc.current.*` in `+page.svelte` and `ae_comp__badge_search.svelte`; removed all manual `typeof` guards +- [x] **Step 4** — Wired `edit_permissions` into review page `can_edit_fields`; the two TODO blocks resolved +- [x] **Step 5** — Built config UI at `(badges)/badges/config/+page.svelte` (administrator_access gated) +- [x] **Step 6** — Updated settings page `Badges` section with link to config page; retired the old form component import - [ ] **Step 7** — Update active event(s) via new UI; verify passcode fields function correctly -- [ ] **Step 8** — `npx svelte-check` clean; commit +- [x] **Step 8** — `npx svelte-check` clean; commit + +> **Implementation note (2026-04-02):** Passcode fields use plain `type="text"` inputs, not `type="password"`. This matches the admin UI convention for this codebase. ### Step 3 scope (find-replace) diff --git a/src/lib/ae_events/ae_events__event.ts b/src/lib/ae_events/ae_events__event.ts index 7c88f7c1..008f6eb2 100644 --- a/src/lib/ae_events/ae_events__event.ts +++ b/src/lib/ae_events/ae_events__event.ts @@ -2,6 +2,8 @@ import type { key_val } from '$lib/stores/ae_stores'; import { api } from '$lib/api/api'; import { pres_mgmt_loc } from '$lib/stores/ae_events_stores__pres_mgmt.svelte'; import type { PressMgmtRemoteCfg } from '$lib/stores/ae_events_stores__pres_mgmt_defaults'; +import { badges_loc } from '$lib/stores/ae_events_stores__badges.svelte'; +import type { BadgesRemoteCfg } from '$lib/stores/ae_events_stores__badges_defaults'; import { db_save_ae_obj_li__ae_obj } from '$lib/ae_core/core__idb_dexie'; import { db_events } from '$lib/ae_events/db_events'; @@ -1060,3 +1062,43 @@ export function sync_config__event_pres_mgmt({ pres_mgmt_cfg_remote?.limit__options ?? false; } } + +/** + * sync_config__event_badges + * + * Mirror a subset of server-side `mod_badges_json` into the local persisted + * `badges_loc.current` store so the UI can read a fast local copy of + * authoritative feature flags (search mode, QR enable, mass-print, etc.). + * + * Called reactively from badge-related pages when the event object changes. + */ +export function sync_config__event_badges({ + badges_cfg_remote, + log_lvl = 0 +}: { + badges_cfg_remote: Partial; + log_lvl?: number; +}) { + if (log_lvl) { + console.log( + `*** sync_config__event_badges() *** remote:`, + badges_cfg_remote + ); + } + + const loc = badges_loc.current; + + // Always-sync: feature flags and simple values + loc.enable_mass_print = badges_cfg_remote?.enable_mass_print ?? true; + loc.enable_add_badge_btn = badges_cfg_remote?.enable_add_badge_btn ?? true; + loc.enable_upload_badge_li_btn = badges_cfg_remote?.enable_upload_badge_li_btn ?? true; + loc.enable_search_qr = badges_cfg_remote?.enable_search_qr ?? true; + loc.qr_type = badges_cfg_remote?.qr_type ?? null; + + // Passcodes and permissions (may be null) + loc.trusted_passcode = badges_cfg_remote?.trusted_passcode ?? null; + loc.administrator_passcode = badges_cfg_remote?.administrator_passcode ?? null; + loc.edit_permissions = badges_cfg_remote?.edit_permissions ?? null; + + loc.remote_cfg_last_synced_on = new Date().toISOString(); +} diff --git a/src/lib/ae_events/ae_events_functions.ts b/src/lib/ae_events/ae_events_functions.ts index ab7a991e..ae967fa3 100644 --- a/src/lib/ae_events/ae_events_functions.ts +++ b/src/lib/ae_events/ae_events_functions.ts @@ -47,6 +47,7 @@ const export_obj = { delete_ae_obj_id__event: event.delete_ae_obj_id__event, update_ae_obj__event: event.update_ae_obj__event, sync_config__event_pres_mgmt: event.sync_config__event_pres_mgmt, + sync_config__event_badges: event.sync_config__event_badges, // Event Person create_ae_obj__event_person: create_ae_obj__event_person, diff --git a/src/lib/stores/ae_events_stores__badges.svelte.ts b/src/lib/stores/ae_events_stores__badges.svelte.ts new file mode 100644 index 00000000..e7cb7af8 --- /dev/null +++ b/src/lib/stores/ae_events_stores__badges.svelte.ts @@ -0,0 +1,18 @@ +/** + * ae_events_stores__badges.svelte.ts + * + * Svelte 5 PersistedState store for Badge module local config. + * Replaces the `events_loc.badges` sub-object from the Svelte 4 persisted store. + * + * localStorage key: 'ae_badges_loc' + * Version gate: AE_BADGES_LOC_VERSION in store_versions.ts + * + * Usage: + * import { badges_loc } from '$lib/stores/ae_events_stores__badges.svelte'; + * badges_loc.current.fulltext_search_qry_str // read + * badges_loc.current.search_version++ // write + */ +import { PersistedState } from 'runed'; +import { badges_loc_defaults } from './ae_events_stores__badges_defaults'; + +export const badges_loc = new PersistedState('ae_badges_loc', badges_loc_defaults); diff --git a/src/lib/stores/ae_events_stores__badges_defaults.ts b/src/lib/stores/ae_events_stores__badges_defaults.ts index 1248672e..297026d4 100644 --- a/src/lib/stores/ae_events_stores__badges_defaults.ts +++ b/src/lib/stores/ae_events_stores__badges_defaults.ts @@ -1,11 +1,89 @@ /** * ae_events_stores__badges_defaults.ts * - * Default state for the badges (badge printing) section of ae_events_stores.ts. - * badges_loc_defaults → events_loc.badges (persisted to localStorage) - * badges_sess_defaults → events_sess.badges (in-memory, resets on page load) + * Type definitions and defaults for the Badges stores. + * + * BadgesRemoteCfg → shape of event.mod_badges_json (server-side admin config) + * BadgesLocState → badges_loc (PersistedState, localStorage key 'ae_badges_loc') + * BadgesSessState → events_sess.badges (in-memory, resets on page load) */ +// --------------------------------------------------------------------------- +// Remote config — shape of event.mod_badges_json +// This is the admin-controlled, server-side config for an event. +// --------------------------------------------------------------------------- +export interface BadgesRemoteCfg { + // Search & UI behaviour + enable_mass_print: boolean; // show the mass-print controls + enable_add_badge_btn: boolean; // show the "Add Badge" button + enable_upload_badge_li_btn: boolean; // show the "Upload Badge List" button + enable_search_qr: boolean; // enable QR scan search + + // QR code configuration + qr_type: string | null; // QR payload format (e.g. 'badge_id', 'url') + + // Access control — passcodes for attendee / staff tiered access. + // Only expose to administrator_access. Never render for lower access levels. + trusted_passcode: string | null; + administrator_passcode: string | null; + + // Field-level edit permissions per access tier. + // can_edit is a string[] of field keys, or '*' for all fields. + edit_permissions: { + authenticated?: { can_edit: string[] | '*' }; + trusted?: { can_edit: string[] | '*' }; + administrator?: { can_edit: string[] | '*' }; + }; +} + +// Default field lists — used when edit_permissions is absent from mod_badges_json. +export const default_authenticated_can_edit: string[] = [ + 'pronouns_override', + 'full_name_override', + 'professional_title_override', + 'affiliations_override', + 'phone_override', + 'location_override', + 'allow_tracking', + 'agree_to_tc' +]; + +export const default_trusted_can_edit: string[] = [ + 'pronouns_override', + 'full_name_override', + 'professional_title_override', + 'affiliations_override', + 'email_override', + 'phone_override', + 'location_override', + 'badge_type_code_override', + 'registration_type_code_override', + 'other_1_code', + 'other_2_code', + 'other_3_code', + 'other_4_code', + 'other_5_code', + 'other_6_code', + 'other_7_code', + 'other_8_code', + 'ticket_1_code', + 'ticket_2_code', + 'ticket_3_code', + 'ticket_4_code', + 'ticket_5_code', + 'ticket_6_code', + 'ticket_7_code', + 'ticket_8_code', + 'allow_tracking', + 'agree_to_tc', + 'hide', + 'priority', + 'notes' +]; + +// --------------------------------------------------------------------------- +// Local state — persisted to localStorage via PersistedState ('ae_badges_loc') +// --------------------------------------------------------------------------- export interface BadgesLocState { auto_view: boolean; show_hidden: boolean; @@ -26,6 +104,21 @@ export interface BadgesLocState { search_status: string | null; search_complete: boolean; classes__form: string; + // Server-mirrored flags (copied from event.mod_badges_json) + enable_mass_print: boolean; + enable_add_badge_btn: boolean; + enable_upload_badge_li_btn: boolean; + enable_search_qr: boolean; + qr_type: string | null; + trusted_passcode: string | null; + administrator_passcode: string | null; + edit_permissions: { + authenticated?: { can_edit: string[] | '*' }; + trusted?: { can_edit: string[] | '*' }; + administrator?: { can_edit: string[] | '*' }; + } | null; + // Timestamp when the remote config was last mirrored locally + remote_cfg_last_synced_on: string | null; } export interface BadgesSessState { @@ -43,6 +136,7 @@ export interface BadgesSessState { } // Persisted badge UI state — survives browser sessions. +// Stored in localStorage as 'ae_badges_loc' via PersistedState (runed). export const badges_loc_defaults: BadgesLocState = { auto_view: true, @@ -73,6 +167,17 @@ export const badges_loc_defaults: BadgesLocState = { search_complete: false, classes__form: 'border border-surface-200 p-4 space-y-4 rounded-container' + , + // Default values for server-mirrored flags + enable_mass_print: true, + enable_add_badge_btn: true, + enable_upload_badge_li_btn: true, + enable_search_qr: true, + qr_type: null, + trusted_passcode: null, + administrator_passcode: null, + edit_permissions: null, + remote_cfg_last_synced_on: null }; // In-memory badge state — resets on page load. diff --git a/src/lib/stores/store_versions.ts b/src/lib/stores/store_versions.ts index ef77ae28..8bda8bc2 100644 --- a/src/lib/stores/store_versions.ts +++ b/src/lib/stores/store_versions.ts @@ -34,6 +34,7 @@ export const AE_LOC_VERSION = 2; // Bumped 2026-03-30: force-clear stale site_cf export const AE_EVENTS_LOC_VERSION = 1; export const AE_IDAA_LOC_VERSION = 1; // Added 2026-03-30: was missing, no wipe mechanism existed export const AE_PRES_MGMT_LOC_VERSION = 1; // Added 2026-04-02: new standalone PersistedState store +export const AE_BADGES_LOC_VERSION = 1; // Added 2026-04-02: promoted from events_loc.badges // Version check side-effect: runs on import, before any persisted() call. // Guard presence of `localStorage` and its functions for safety (SSR, Node flags). diff --git a/src/routes/events/[event_id]/(badges)/badges/+page.svelte b/src/routes/events/[event_id]/(badges)/badges/+page.svelte index c23c9da5..f976d5b7 100644 --- a/src/routes/events/[event_id]/(badges)/badges/+page.svelte +++ b/src/routes/events/[event_id]/(badges)/badges/+page.svelte @@ -80,6 +80,20 @@ let lq__event_obj = $derived( }) ); +// Mirror server-side badges config into the persisted local store when the +// event object is available so UI can read a fast local copy. +$effect(() => { + const remote_cfg = $lq__event_obj?.mod_badges_json; + if (remote_cfg) { + untrack(() => { + events_func.sync_config__event_badges({ + badges_cfg_remote: remote_cfg, + log_lvl: log_lvl + }); + }); + } +}); + // Stable LiveQuery Pattern (Aether UI V3) let lq__event_badge_obj_li = $derived.by(() => { const ids = event_badge_id_li; @@ -129,9 +143,7 @@ let search_params = $derived({ aff: (badges_loc.current.qry_affiliations ?? '').toLowerCase().trim(), sort: badges_loc.current.qry_sort_order, event_id: $events_slct?.event_id, - 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 + remote_first: badges_loc.current.qry__remote_first }); // 2. Controlled effect for triggering searches @@ -194,10 +206,7 @@ async function handle_search_refresh(params: any) { return false; } - 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) { + if (qry_str) { const given_name = ( badge.given_name ?? '' ).toLowerCase(); @@ -376,7 +385,7 @@ async function handle_search_refresh(params: any) { -{#if $ae_loc.edit_mode && ($lq__event_obj?.mod_badges_json?.enable_add_badge_btn ?? true)} +{#if $ae_loc.edit_mode && (badges_loc.current.enable_add_badge_btn ?? true)}
{/if} - {#if event_obj?.mod_badges_json?.enable_upload_badge_li_btn ?? true} + {#if badges_loc.current.enable_upload_badge_li_btn ?? true}