From fe4380f819527c78b7f4eb76cdf2ae0faf7e10c9 Mon Sep 17 00:00:00 2001 From: Scott Idem Date: Mon, 9 Feb 2026 15:30:00 -0500 Subject: [PATCH] Standardize Exhibit Leads module: Rich text support and naming alignment - Integrated TipTap rich text editor for Booth Descriptions and Exhibitor Notes. - Implemented strip_html utility for clean search/preview of rich text fields. - Renamed exhibit loading functions to follow load_ae_obj_id__event_exhibit* pattern. - Hardened property processors with 'undefined' string guards and automatic reload triggers. - Resolved type mismatches and naming inconsistencies across the Leads module. - Verified zero-error state via svelte-check. --- TODO.md | 15 +++++++++++---- src/lib/ae_core/ae_core_functions.ts | 2 ++ src/lib/ae_events/ae_events__exhibit.ts | 13 +++++++++---- .../ae_events/ae_events__exhibit_tracking.ts | 13 +++++++++---- src/lib/ae_events_functions.ts | 16 ++++++++-------- src/lib/ae_utils/ae_utils.ts | 9 +++++++++ src/lib/elements/element_ae_crud_v2.svelte | 9 +++++++++ src/routes/events/[event_id]/(leads)/README.md | 4 ++-- .../events/[event_id]/(leads)/leads/+page.ts | 2 +- .../leads/exhibit/[exhibit_id]/+layout.ts | 4 ++-- .../leads/exhibit/[exhibit_id]/+page.svelte | 9 ++++++--- .../ae_comp__exhibit_tracking_obj_li.svelte | 2 +- .../exhibit/[exhibit_id]/ae_tab__manage.svelte | 18 ++++++++++++++++-- .../lead/[exhibit_tracking_id]/+page.svelte | 8 +++++--- .../lead/[exhibit_tracking_id]/+page.ts | 2 +- 15 files changed, 91 insertions(+), 35 deletions(-) diff --git a/TODO.md b/TODO.md index 65dee3ca..a0c404e6 100644 --- a/TODO.md +++ b/TODO.md @@ -85,6 +85,7 @@ This is a list of tasks to be completed before the next event/show/conference. ## Current Priorities (Feb 3, 2026) 1. **Codebase Hygiene:** + - [ ] **CRUD v2 Review:** Perform a comprehensive review/enhancement of 'Element_ae_crud_v2.svelte' (Mobile accessibility, 'Select' mode, workflow optimization). - [ ] **Type Mismatch Resolution:** Resolve remaining simple type mismatches flagged by `svelte-check` (currently ~160). - [ ] **Bite-Sized Refactoring:** Split large components (>800 lines) into modular sub-components (e.g., `ae_idaa_comp__event_obj_id_edit.svelte`). 2. **Reactivity & Performance:** @@ -119,10 +120,16 @@ This is a list of tasks to be completed before the next event/show/conference. ## Recent Accomplishments (Feb 8, 2026) -- [x] **Zero-Error Compiler State:** Successfully cleared all 68 'svelte-check' errors across the entire application. -- [x] **Event Settings Hardening:** Refactored complex JSON configuration editors to use a temporary string-buffer pattern, preventing data corruption and resolving multiple TypeScript assignment errors. -- [x] **Prop Reactivity Pass:** Standardized prop synchronization logic in core components (Sign-In, File Upload, Layout) to adhere to Svelte 5 Runes best practices. -- [x] **Triple-ID TypeScript Alignment:** Updated 'ae_types.ts' and refined property processing to fully support semantic string IDs across core modules. +- **Exhibitor Leads Module (V3 Completion):** + - [x] **Authentication:** Implemented dual-mode Sign-In (Shared Passcode / Licensee) with persistent state and Admin pre-fill. + - [x] **Portal Management:** Built comprehensive Manage tab with Admin Tools, Licensee management (license_li_json), and dynamic Question editor. + - [x] **Lead List Filtering:** Implemented reactive licensee filtering (My Leads vs All Leads) with 'Hard Guard' post-filtering for robust data narrowing. + - [x] **Sync Telemetry:** Added real-time refresh countdown and 'Last Sync' timestamp indicators. + - [x] **Lead Detail Editor:** Built dynamic form for editing custom responses/qualifiers based on booth-specific question definitions. + - [x] **Duplicate Prevention:** Implemented 'Already Captured' detection in both Manual Search and QR Scanner with direct 'View' links. +- **Zero-Error State:** Successfully reached a zero-error baseline in `npm run check` after resolving all typing and ReferenceErrors in core modules. +- **Performance:** Optimized IDAA Bulletin Board by disabling redundant comment fetches in list views. +- **Terminology:** Standardized on 'Licensed User' (licensee) terminology across all Leads components. ## Recent Accomplishments (Feb 7, 2026) diff --git a/src/lib/ae_core/ae_core_functions.ts b/src/lib/ae_core/ae_core_functions.ts index 33bdbdae..cc3552d0 100644 --- a/src/lib/ae_core/ae_core_functions.ts +++ b/src/lib/ae_core/ae_core_functions.ts @@ -341,6 +341,7 @@ import { load_ae_obj_id__archive_content } from '$lib/ae_archives/ae_archives__a import { load_ae_obj_id__event } from '$lib/ae_events/ae_events__event'; // import { load_ae_obj_id__event_badge } from "$lib/ae_events/ae_events__event_badge"; +import { load_ae_obj_id__event_exhibit } from '$lib/ae_events/ae_events__exhibit'; import { load_ae_obj_id__event_device } from '$lib/ae_events/ae_events__event_device'; // import { load_ae_obj_id__event_exhibit } from "$lib/ae_events/ae_events__event_exhibit"; import { load_ae_obj_id__event_file } from '$lib/ae_events/ae_events__event_file'; @@ -410,6 +411,7 @@ async function update_ae_obj_id_crud_v2({ if (object_type == 'journal') load_ae_obj_id__journal({ api_cfg, journal_id: object_id, log_lvl }); if (object_type == 'journal_entry') load_ae_obj_id__journal_entry({ api_cfg, journal_entry_id: object_id, log_lvl }); if (object_type == 'event') load_ae_obj_id__event({ api_cfg, event_id: object_id, log_lvl }); + if (object_type == 'event_exhibit') load_ae_obj_id__event_exhibit({ api_cfg, exhibit_id: object_id, log_lvl }); if (object_type == 'event_device') load_ae_obj_id__event_device({ api_cfg, event_device_id: object_id, log_lvl }); if (object_type == 'event_file') load_ae_obj_id__event_file({ api_cfg, event_file_id: object_id, log_lvl }); if (object_type == 'event_location') load_ae_obj_id__event_location({ api_cfg, event_location_id: object_id, log_lvl }); diff --git a/src/lib/ae_events/ae_events__exhibit.ts b/src/lib/ae_events/ae_events__exhibit.ts index 42d2de46..0a8c0134 100644 --- a/src/lib/ae_events/ae_events__exhibit.ts +++ b/src/lib/ae_events/ae_events__exhibit.ts @@ -89,6 +89,11 @@ async function _process_generic_props>({ const updated = processed_obj.updated_on ?? processed_obj.created_on; const name = processed_obj.name ?? ''; + // Guard: Prevent literal "undefined" string from showing in description + if ((processed_obj as any).description === 'undefined') { + (processed_obj as any).description = ''; + } + (processed_obj as any).tmp_sort_1 = `${group}_${priority}_${sort}_${updated}`; (processed_obj as any).tmp_sort_2 = `${group}_${priority}_${sort}_${name}_${updated}`; @@ -119,7 +124,7 @@ export async function process_ae_obj__exhibit_props({ /** * Load Single Exhibit (SWR Pattern) */ -export async function load_ae_obj_id__exhibit({ +export async function load_ae_obj_id__event_exhibit({ api_cfg, exhibit_id, view = 'default', @@ -134,7 +139,7 @@ export async function load_ae_obj_id__exhibit({ }): Promise { const start_time = performance.now(); if (log_lvl) { - console.log(`🔎 [Trace] load_ae_obj_id__exhibit: START (id=${exhibit_id}, try_cache=${try_cache})`); + console.log(`🔎 [Trace] load_ae_obj_id__event_exhibit: START (id=${exhibit_id}, try_cache=${try_cache})`); } // 1. FAST PATH: Return cached data immediately @@ -192,7 +197,7 @@ async function _refresh_exhibit_id_background({ api_cfg, exhibit_id, view, try_c /** * Load Collection of Exhibits (SWR Pattern) */ -export async function load_ae_obj_li__exhibit({ +export async function load_ae_obj_li__event_exhibit({ api_cfg, event_id, enabled = 'enabled', @@ -222,7 +227,7 @@ export async function load_ae_obj_li__exhibit({ }): Promise { const start_time = performance.now(); if (log_lvl) { - console.log(`🔎 [Trace] load_ae_obj_li__exhibit: START (event=${event_id}, try_cache=${try_cache})`); + console.log(`🔎 [Trace] load_ae_obj_li__event_exhibit: START (event=${event_id}, try_cache=${try_cache})`); } if (try_cache) { diff --git a/src/lib/ae_events/ae_events__exhibit_tracking.ts b/src/lib/ae_events/ae_events__exhibit_tracking.ts index 07b71cab..8288430d 100644 --- a/src/lib/ae_events/ae_events__exhibit_tracking.ts +++ b/src/lib/ae_events/ae_events__exhibit_tracking.ts @@ -101,6 +101,11 @@ async function _process_generic_props>({ const updated = processed_obj.updated_on ?? processed_obj.created_on; const name = processed_obj.event_badge_full_name ?? ''; + // Guard: Prevent literal "undefined" string from showing in notes + if ((processed_obj as any).exhibitor_notes === 'undefined') { + (processed_obj as any).exhibitor_notes = ''; + } + (processed_obj as any).tmp_sort_1 = `${group}_${priority}_${sort}_${updated}`; (processed_obj as any).tmp_sort_2 = `${group}_${priority}_${sort}_${name}_${updated}`; @@ -131,7 +136,7 @@ export async function process_ae_obj__exhibit_tracking_props({ /** * Load Single Lead (SWR Pattern) */ -export async function load_ae_obj_id__exhibit_tracking({ +export async function load_ae_obj_id__event_exhibit_tracking({ api_cfg, exhibit_tracking_id, view = 'default', @@ -146,7 +151,7 @@ export async function load_ae_obj_id__exhibit_tracking({ }): Promise { const start_time = performance.now(); if (log_lvl) { - console.log(`🔎 [Trace] load_ae_obj_id__exhibit_tracking: START (id=${exhibit_tracking_id}, try_cache=${try_cache})`); + console.log(`🔎 [Trace] load_ae_obj_id__event_exhibit_tracking: START (id=${exhibit_tracking_id}, try_cache=${try_cache})`); } // 1. FAST PATH: Return cached data immediately @@ -204,7 +209,7 @@ async function _refresh_tracking_id_background({ api_cfg, exhibit_tracking_id, v /** * Load Collection of Leads (SWR Pattern) */ -export async function load_ae_obj_li__exhibit_tracking({ +export async function load_ae_obj_li__event_exhibit_tracking({ api_cfg, exhibit_id, enabled = 'enabled', @@ -233,7 +238,7 @@ export async function load_ae_obj_li__exhibit_tracking({ }): Promise { const start_time = performance.now(); if (log_lvl) { - console.log(`🔎 [Trace] load_ae_obj_li__exhibit_tracking: START (exhibit=${exhibit_id}, try_cache=${try_cache})`); + console.log(`🔎 [Trace] load_ae_obj_li__event_exhibit_tracking: START (exhibit=${exhibit_id}, try_cache=${try_cache})`); } if (try_cache) { diff --git a/src/lib/ae_events_functions.ts b/src/lib/ae_events_functions.ts index e601ba2d..3f0388c1 100644 --- a/src/lib/ae_events_functions.ts +++ b/src/lib/ae_events_functions.ts @@ -7,8 +7,8 @@ import * as event_device from '$lib/ae_events/ae_events__event_device'; import * as event_file from '$lib/ae_events/ae_events__event_file'; import { - load_ae_obj_id__exhibit, - load_ae_obj_li__exhibit, + load_ae_obj_id__event_exhibit, + load_ae_obj_li__event_exhibit, search__exhibit, create_ae_obj__exhibit, update_ae_obj__exhibit @@ -16,8 +16,8 @@ import { import { search__exhibit_tracking, - load_ae_obj_id__exhibit_tracking, - load_ae_obj_li__exhibit_tracking, + load_ae_obj_id__event_exhibit_tracking, + load_ae_obj_li__event_exhibit_tracking, create_ae_obj__exhibit_tracking, update_ae_obj__exhibit_tracking, download_export__event_exhibit_tracking @@ -73,13 +73,13 @@ const export_obj = { update_ae_obj__event_device: event_device.update_ae_obj__event_device, // Event Exhibits - load_ae_obj_id__exhibit: load_ae_obj_id__exhibit, - load_ae_obj_li__exhibit: load_ae_obj_li__exhibit, + load_ae_obj_id__event_exhibit: load_ae_obj_id__event_exhibit, + load_ae_obj_li__event_exhibit: load_ae_obj_li__event_exhibit, search__exhibit: search__exhibit, create_ae_obj__exhibit: create_ae_obj__exhibit, update_ae_obj__exhibit: update_ae_obj__exhibit, - load_ae_obj_id__exhibit_tracking: load_ae_obj_id__exhibit_tracking, - load_ae_obj_li__exhibit_tracking: load_ae_obj_li__exhibit_tracking, + load_ae_obj_id__event_exhibit_tracking: load_ae_obj_id__event_exhibit_tracking, + load_ae_obj_li__event_exhibit_tracking: load_ae_obj_li__event_exhibit_tracking, search__exhibit_tracking: search__exhibit_tracking, create_ae_obj__exhibit_tracking: create_ae_obj__exhibit_tracking, update_ae_obj__exhibit_tracking: update_ae_obj__exhibit_tracking, diff --git a/src/lib/ae_utils/ae_utils.ts b/src/lib/ae_utils/ae_utils.ts index 77565a6b..7ab75d4b 100644 --- a/src/lib/ae_utils/ae_utils.ts +++ b/src/lib/ae_utils/ae_utils.ts @@ -265,6 +265,14 @@ export const shorten_string = function shorten_string({ return new_string; }; +/** + * Strips HTML tags from a string. + */ +export function strip_html(html: string): string { + if (!html) return ''; + return html.replace(/<[^>]*>?/gm, ''); +} + // Svelte action to set focus on an element function set_focus(node: HTMLElement, focus: boolean) { if (focus) { @@ -341,6 +349,7 @@ export const ae_util = { to_title_case: to_title_case, shorten_string: shorten_string, shorten_filename: shorten_filename, + strip_html: strip_html, file_extension_icon: file_extension_icon, file_extension_icon_lucide: file_extension_icon_lucide, format_html: format_html, diff --git a/src/lib/elements/element_ae_crud_v2.svelte b/src/lib/elements/element_ae_crud_v2.svelte index df2163a3..4cd56062 100644 --- a/src/lib/elements/element_ae_crud_v2.svelte +++ b/src/lib/elements/element_ae_crud_v2.svelte @@ -101,6 +101,8 @@ // import { ae_loc, ae_sess, ae_api, ae_trig, slct, slct_trigger } from '$lib/ae_stores'; // *** Import Aether core components + import AE_Comp_Editor_TipTap from '$lib/elements/AE_Comp_Editor_TipTap.svelte'; + // *** Import Aether module variables and functions // *** Import Aether module components @@ -364,6 +366,13 @@ rows={textarea_rows} class="textarea" > + {:else if field_type == 'tiptap'} +
+ +
{:else} {/if} diff --git a/src/routes/events/[event_id]/(leads)/README.md b/src/routes/events/[event_id]/(leads)/README.md index 969072dd..78a50dbd 100644 --- a/src/routes/events/[event_id]/(leads)/README.md +++ b/src/routes/events/[event_id]/(leads)/README.md @@ -48,11 +48,11 @@ Represents a single lead captured by an exhibitor. It links an exhibitor to an a - `/events/[event_id]/(leads)`: The main entry point for the Leads module within a specific event, typically displays a list of available exhibits. - `+page.svelte`: Renders the list of exhibits. - - `+page.ts`: Loads the data for available exhibits using `events_func.load_ae_obj_li__exhibit`. + - `+page.ts`: Loads the data for available exhibits using `events_func.load_ae_obj_li__event_exhibit`. - `+layout.svelte`/`+layout.ts`: Provides a common layout and data for the module, including a submenu. - `/events/[event_id]/(leads)/exhibit/[slug]`: Dynamic route for managing leads for a specific exhibitor within an event. The `[slug]` corresponds to `event_exhibit_id`. - `+page.svelte`: The primary interface for an exhibitor, orchestrating lead capture and management components. - - `+page.ts`: Loads specific `Exhibit` data and associated `Exhibit_tracking` (leads) using `events_func.load_ae_obj_id__exhibit` and `events_func.load_ae_obj_li__exhibit_tracking`. + - `+page.ts`: Loads specific `Exhibit` data and associated `Exhibit_tracking` (leads) using `events_func.load_ae_obj_id__event_exhibit` and `events_func.load_ae_obj_li__event_exhibit_tracking`. ### Core Components (within `src/routes/events/[event_id]/(leads)/exhibit/[slug]/`) diff --git a/src/routes/events/[event_id]/(leads)/leads/+page.ts b/src/routes/events/[event_id]/(leads)/leads/+page.ts index 1e120cb5..a17b760b 100644 --- a/src/routes/events/[event_id]/(leads)/leads/+page.ts +++ b/src/routes/events/[event_id]/(leads)/leads/+page.ts @@ -12,7 +12,7 @@ export async function load({ params, parent }) { const event_id = params.event_id; if (browser && event_id) { - events_func.load_ae_obj_li__exhibit({ + events_func.load_ae_obj_li__event_exhibit({ api_cfg: ae_acct.api, event_id: event_id, limit: 100, diff --git a/src/routes/events/[event_id]/(leads)/leads/exhibit/[exhibit_id]/+layout.ts b/src/routes/events/[event_id]/(leads)/leads/exhibit/[exhibit_id]/+layout.ts index 990988ae..d16b2112 100644 --- a/src/routes/events/[event_id]/(leads)/leads/exhibit/[exhibit_id]/+layout.ts +++ b/src/routes/events/[event_id]/(leads)/leads/exhibit/[exhibit_id]/+layout.ts @@ -19,13 +19,13 @@ export async function load({ params, parent }) { }); if (browser && exhibit_id) { - events_func.load_ae_obj_id__exhibit({ + events_func.load_ae_obj_id__event_exhibit({ api_cfg: ae_acct.api, exhibit_id: exhibit_id, log_lvl: 0 }); - events_func.load_ae_obj_li__exhibit_tracking({ + events_func.load_ae_obj_li__event_exhibit_tracking({ api_cfg: ae_acct.api, exhibit_id: exhibit_id, limit: 250, diff --git a/src/routes/events/[event_id]/(leads)/leads/exhibit/[exhibit_id]/+page.svelte b/src/routes/events/[event_id]/(leads)/leads/exhibit/[exhibit_id]/+page.svelte index c344c778..832fc26d 100644 --- a/src/routes/events/[event_id]/(leads)/leads/exhibit/[exhibit_id]/+page.svelte +++ b/src/routes/events/[event_id]/(leads)/leads/exhibit/[exhibit_id]/+page.svelte @@ -10,6 +10,7 @@ import { ae_api, ae_loc } from '$lib/stores/ae_stores'; import { page } from '$app/state'; import { events_func } from '$lib/ae_events_functions'; + import { ae_util } from '$lib/ae_utils/ae_utils'; import { LoaderCircle, UserPlus, @@ -228,9 +229,11 @@ const email = ( tracking.event_badge_email ?? '' ).toLowerCase(); - const notes = ( - tracking.exhibitor_notes ?? '' - ).toLowerCase(); + const notes = ae_util.strip_html(tracking.exhibitor_notes ?? '').toLowerCase(); + // Guard: Prevent "undefined" from being searched + if (tracking.exhibitor_notes === 'undefined') { + tracking.exhibitor_notes = ''; + } const qry_string = ( tracking.default_qry_str ?? '' ).toLowerCase(); diff --git a/src/routes/events/[event_id]/(leads)/leads/exhibit/[exhibit_id]/ae_comp__exhibit_tracking_obj_li.svelte b/src/routes/events/[event_id]/(leads)/leads/exhibit/[exhibit_id]/ae_comp__exhibit_tracking_obj_li.svelte index 35e6a90b..94e28719 100644 --- a/src/routes/events/[event_id]/(leads)/leads/exhibit/[exhibit_id]/ae_comp__exhibit_tracking_obj_li.svelte +++ b/src/routes/events/[event_id]/(leads)/leads/exhibit/[exhibit_id]/ae_comp__exhibit_tracking_obj_li.svelte @@ -87,7 +87,7 @@ > {ae_util.shorten_string({ - string: event_tracking_obj.exhibitor_notes, + string: ae_util.strip_html(event_tracking_obj.exhibitor_notes), max_length: 100 })} diff --git a/src/routes/events/[event_id]/(leads)/leads/exhibit/[exhibit_id]/ae_tab__manage.svelte b/src/routes/events/[event_id]/(leads)/leads/exhibit/[exhibit_id]/ae_tab__manage.svelte index 1c62ca0d..6d260ef7 100644 --- a/src/routes/events/[event_id]/(leads)/leads/exhibit/[exhibit_id]/ae_tab__manage.svelte +++ b/src/routes/events/[event_id]/(leads)/leads/exhibit/[exhibit_id]/ae_tab__manage.svelte @@ -66,6 +66,7 @@ field_name="priority" field_type="boolean" current_field_value={$lq__exhibit_obj?.priority} + object_reload={true} /> @@ -81,6 +82,7 @@ field_name="license_max" field_type="number" current_field_value={$lq__exhibit_obj?.license_max} + object_reload={true} class_li="w-16 font-mono text-right" /> @@ -97,6 +99,7 @@ field_name="leads_device_sm_qty" field_type="number" current_field_value={$lq__exhibit_obj?.leads_device_sm_qty} + object_reload={true} class_li="w-16 font-mono text-right" /> @@ -113,6 +116,7 @@ field_name="leads_device_lg_qty" field_type="number" current_field_value={$lq__exhibit_obj?.leads_device_lg_qty} + object_reload={true} class_li="w-16 font-mono text-right" /> @@ -140,6 +144,7 @@ field_name="name" field_type="text" current_field_value={$lq__exhibit_obj?.name} + object_reload={true} hide_element={false} display_block={true} class_li="font-bold text-xl" @@ -157,9 +162,9 @@ object_type="event_exhibit" object_id={exhibit_id} field_name="description" - field_type="textarea" + field_type="tiptap" current_field_value={$lq__exhibit_obj?.description} - textarea_rows={4} + object_reload={true} hide_element={false} display_block={true} class_li="text-sm" @@ -181,6 +186,14 @@
Staff Passcode
+ + + {#if $ae_loc.administrator_access} +
+ {$lq__exhibit_obj?.staff_passcode || '----'} +
+ {/if} + {:else if $lq__lead_obj.exhibitor_notes} -

{$lq__lead_obj.exhibitor_notes}

+
+ {@html $lq__lead_obj.exhibitor_notes} +
{:else}
No notes have been added for this lead yet. diff --git a/src/routes/events/[event_id]/(leads)/leads/exhibit/[exhibit_id]/lead/[exhibit_tracking_id]/+page.ts b/src/routes/events/[event_id]/(leads)/leads/exhibit/[exhibit_id]/lead/[exhibit_tracking_id]/+page.ts index d6f96d13..a20838d7 100644 --- a/src/routes/events/[event_id]/(leads)/leads/exhibit/[exhibit_id]/lead/[exhibit_tracking_id]/+page.ts +++ b/src/routes/events/[event_id]/(leads)/leads/exhibit/[exhibit_id]/lead/[exhibit_tracking_id]/+page.ts @@ -14,7 +14,7 @@ export async function load({ params, parent }) { if (browser && exhibit_tracking_id) { // Refresh the specific Lead (Tracking) object - events_func.load_ae_obj_id__exhibit_tracking({ + events_func.load_ae_obj_id__event_exhibit_tracking({ api_cfg: ae_acct.api, exhibit_tracking_id: exhibit_tracking_id, log_lvl: 0