From acf69c3deb93b2ebb579ec90b9ddbe702ef08b3e Mon Sep 17 00:00:00 2001 From: Scott Idem Date: Sat, 7 Feb 2026 18:58:20 -0500 Subject: [PATCH] feat(leads): standardize exhibit and lead tracking on V3 API and base ID keys --- src/lib/ae_events/ae_events__exhibit.ts | 288 +++++++++++------ .../ae_events/ae_events__exhibit_tracking.ts | 298 +++++++++++------- .../[event_id]/(leads)/leads/+page.svelte | 16 +- .../leads/exhibit/[exhibit_id]/+page.svelte | 24 +- .../lead/[exhibit_tracking_id]/+page.svelte | 209 ++++++++++++ .../lead/[exhibit_tracking_id]/+page.ts | 25 ++ .../ae_comp__lead_detail_form.svelte | 11 + 7 files changed, 654 insertions(+), 217 deletions(-) create mode 100644 src/routes/events/[event_id]/(leads)/leads/exhibit/[exhibit_id]/lead/[exhibit_tracking_id]/+page.svelte create mode 100644 src/routes/events/[event_id]/(leads)/leads/exhibit/[exhibit_id]/lead/[exhibit_tracking_id]/+page.ts create mode 100644 src/routes/events/[event_id]/(leads)/leads/exhibit/[exhibit_id]/lead/[exhibit_tracking_id]/ae_comp__lead_detail_form.svelte diff --git a/src/lib/ae_events/ae_events__exhibit.ts b/src/lib/ae_events/ae_events__exhibit.ts index 79655e81..7ec0f34f 100644 --- a/src/lib/ae_events/ae_events__exhibit.ts +++ b/src/lib/ae_events/ae_events__exhibit.ts @@ -8,10 +8,12 @@ import type { ae_EventExhibit } from '$lib/types/ae_types'; const ae_promises: key_val = {}; // --- PROPERTIES TO SAVE --- -export const properties_to_save = [ +export const properties_to_save_exhibit = [ 'id', 'event_exhibit_id', + // 'event_exhibit_id_random', 'event_id', + // 'event_id_random', 'code', 'name', 'description', @@ -37,7 +39,8 @@ export const properties_to_save = [ ]; /** - * NON-EXPORTED LOCAL HELPER + * Robust Property Processor (Aether UI V3) + * Handles ID aliasing and ID clobbering guards. */ async function _process_generic_props>({ obj_li, @@ -57,17 +60,29 @@ async function _process_generic_props>({ for (const original_obj of obj_li) { let processed_obj = { ...original_obj }; + // 1. Random ID Aliasing (Triple-ID Pattern) for (const key in processed_obj) { - if (key.endsWith('_random')) { + if (key.endsWith('_random') && processed_obj[key]) { const newKey = key.slice(0, -7); - (processed_obj as any)[newKey] = processed_obj[key]; + // Guard: Only overwrite if the target is null/undefined + if (!processed_obj[newKey]) { + (processed_obj as any)[newKey] = processed_obj[key]; + } } } + + // 2. Primary Key Mapping (Dexie compatible) const randomIdKey = `${obj_type}_id_random`; - if (processed_obj[randomIdKey]) { + const baseIdKey = `${obj_type}_id`; + + // Prioritize the base ID field as the primary source of truth + if (processed_obj[baseIdKey]) { + (processed_obj as any).id = String(processed_obj[baseIdKey]); + } else if (processed_obj[randomIdKey]) { (processed_obj as any).id = String(processed_obj[randomIdKey]); } + // 3. Metadata & Sorting Computation const group = processed_obj.group ?? '0'; const priority = processed_obj.priority ? 1 : 0; const sort = processed_obj.sort ?? '0'; @@ -101,12 +116,14 @@ export async function process_ae_obj__exhibit_props({ }); } -// Updated 2026-01-20 to V3 +/** + * Load Single Exhibit (SWR Pattern) + */ export async function load_ae_obj_id__exhibit({ api_cfg, exhibit_id, view = 'default', - try_cache = false, + try_cache = true, log_lvl = 0 }: { api_cfg: any; @@ -115,47 +132,66 @@ export async function load_ae_obj_id__exhibit({ try_cache?: boolean; log_lvl?: number; }): Promise { + const start_time = performance.now(); if (log_lvl) { - console.log(`*** load_ae_obj_id__exhibit() *** [V3] id=${exhibit_id}`); + console.log(`🔎 [Trace] load_ae_obj_id__exhibit: START (id=${exhibit_id}, try_cache=${try_cache})`); } - try { - ae_promises.load__exhibit_obj = await api.get_ae_obj_v3({ - api_cfg, - obj_type: 'event_exhibit', - obj_id: exhibit_id, - view, - log_lvl - }); + // 1. FAST PATH: Return cached data immediately + if (try_cache) { + try { + const cached = await db_events.exhibit.get(exhibit_id); + if (cached) { + const elapsed = (performance.now() - start_time).toFixed(2); + if (log_lvl) console.log(`✅ [Trace] load_ae_obj_id: CACHE HIT at ${elapsed}ms. Returning stale exhibit shell.`); + + // Background refresh (non-blocking) + _refresh_exhibit_id_background({ api_cfg, exhibit_id, view, try_cache, log_lvl: log_lvl > 1 ? log_lvl : 0 }); + return cached; + } + } catch (e) { + if (log_lvl) console.error(`❌ [Trace] load_ae_obj_id: Cache access error:`, e); + } + } + + // 2. SLOW PATH: Wait for API + return await _refresh_exhibit_id_background({ api_cfg, exhibit_id, view, try_cache, log_lvl }); +} + +async function _refresh_exhibit_id_background({ api_cfg, exhibit_id, view, try_cache, log_lvl }: any) { + const start_time = performance.now(); + if (typeof navigator !== 'undefined' && !navigator.onLine) return null; + try { + if (log_lvl) console.log(`📡 [Trace] _refresh_exhibit_id: API Fetching id=${exhibit_id}`); + const result = await api.get_ae_obj_v3({ api_cfg, obj_type: 'event_exhibit', obj_id: exhibit_id, view, log_lvl }); + + if (result) { + const processed = await process_ae_obj__exhibit_props({ obj_li: [result], log_lvl }); + const processed_obj = processed[0]; + const elapsed = (performance.now() - start_time).toFixed(2); + + if (log_lvl) console.log(`📦 [Trace] _refresh_exhibit_id: Received from API at ${elapsed}ms`); - if (ae_promises.load__exhibit_obj) { if (try_cache) { - const processed_obj_li = await process_ae_obj__exhibit_props({ - obj_li: [ae_promises.load__exhibit_obj], - log_lvl - }); await db_save_ae_obj_li__ae_obj({ db_instance: db_events, table_name: 'exhibit', - obj_li: processed_obj_li, - properties_to_save, + obj_li: [processed_obj], + properties_to_save: properties_to_save_exhibit, log_lvl }); } - } else if (try_cache) { - ae_promises.load__exhibit_obj = await db_events.exhibit.get(exhibit_id); - } - } catch (error: any) { - console.log('V3 Request failed.', error); - if (try_cache) { - ae_promises.load__exhibit_obj = await db_events.exhibit.get(exhibit_id); + return processed_obj; } + } catch (e) { + if (log_lvl) console.error(`❌ [Trace] _refresh_exhibit_id: API error for id=${exhibit_id}:`, e); } - - return ae_promises.load__exhibit_obj || null; + return null; } -// Updated 2026-01-20 to V3 +/** + * Load Collection of Exhibits (SWR Pattern) + */ export async function load_ae_obj_li__exhibit({ api_cfg, event_id, @@ -184,12 +220,39 @@ export async function load_ae_obj_li__exhibit({ try_cache?: boolean; log_lvl?: number; }): Promise { + const start_time = performance.now(); if (log_lvl) { - console.log(`*** load_ae_obj_li__exhibit() *** [V3] for=event:${event_id}`); + console.log(`🔎 [Trace] load_ae_obj_li__exhibit: START (event=${event_id}, try_cache=${try_cache})`); } + if (try_cache) { + try { + const cached_li = await db_events.exhibit + .where('event_id').equals(event_id) + .toArray(); + + if (cached_li && cached_li.length > 0) { + const elapsed = (performance.now() - start_time).toFixed(2); + if (log_lvl) console.log(`✅ [Trace] load_ae_obj_li: CACHE HIT at ${elapsed}ms (${cached_li.length} items).`); + + // Background refresh (non-blocking) + _refresh_exhibit_li_background({ api_cfg, event_id, enabled, hidden, view, limit, offset, order_by_li, try_cache, log_lvl: log_lvl > 1 ? log_lvl : 0 }); + return cached_li; + } + } catch (e) { + if (log_lvl) console.error(`❌ [Trace] load_ae_obj_li: Cache access error:`, e); + } + } + + return await _refresh_exhibit_li_background({ api_cfg, event_id, enabled, hidden, view, limit, offset, order_by_li, try_cache, log_lvl }); +} + +async function _refresh_exhibit_li_background({ api_cfg, event_id, enabled, hidden, view, limit, offset, order_by_li, try_cache, log_lvl }: any) { + const start_time = performance.now(); + if (typeof navigator !== 'undefined' && !navigator.onLine) return []; try { - ae_promises.load__event_exhibit_obj_li = await api.get_ae_obj_li_v3({ + if (log_lvl) console.log(`📡 [Trace] _refresh_exhibit_li: API Fetching exhibits for event=${event_id}`); + const result_li = await api.get_ae_obj_li_v3({ api_cfg, obj_type: 'event_exhibit', for_obj_type: 'event', @@ -203,38 +266,96 @@ export async function load_ae_obj_li__exhibit({ log_lvl }); - if (ae_promises.load__event_exhibit_obj_li) { + if (result_li) { + const processed = await process_ae_obj__exhibit_props({ obj_li: result_li, log_lvl }); + const elapsed = (performance.now() - start_time).toFixed(2); + if (log_lvl) console.log(`📦 [Trace] _refresh_exhibit_li: Received ${processed.length} items from API at ${elapsed}ms.`); + if (try_cache) { - const processed_obj_li = await process_ae_obj__exhibit_props({ - obj_li: ae_promises.load__event_exhibit_obj_li, - log_lvl - }); await db_save_ae_obj_li__ae_obj({ db_instance: db_events, table_name: 'exhibit', - obj_li: processed_obj_li, - properties_to_save, + obj_li: processed, + properties_to_save: properties_to_save_exhibit, log_lvl }); } - } else if (try_cache) { - ae_promises.load__event_exhibit_obj_li = await db_events.exhibit - .where('event_id').equals(event_id) - .toArray(); - } - } catch (error: any) { - console.log('V3 List Request failed.', error); - if (try_cache) { - ae_promises.load__event_exhibit_obj_li = await db_events.exhibit - .where('event_id').equals(event_id) - .toArray(); + return processed; } + } catch (e) { + if (log_lvl) console.error(`❌ [Trace] _refresh_exhibit_li: API error:`, e); } - - return ae_promises.load__event_exhibit_obj_li || []; + return []; } -// Updated 2026-01-28 to V3 +/** + * Exhibit Create (V3) + */ +export async function create_ae_obj__exhibit({ + api_cfg, event_id, data_kv, try_cache = true, log_lvl = 0 +}: { + api_cfg: any; event_id: string; data_kv: key_val; try_cache?: boolean; log_lvl?: number; +}): Promise { + const result = await api.create_ae_obj_v3({ + api_cfg, + obj_type: 'event_exhibit', + fields: { event_id: event_id, ...data_kv }, + log_lvl + }); + + if (result) { + const processed = await process_ae_obj__exhibit_props({ obj_li: [result], log_lvl }); + const processed_obj = processed[0]; + if (try_cache) { + await db_save_ae_obj_li__ae_obj({ + db_instance: db_events, + table_name: 'exhibit', + obj_li: [processed_obj], + properties_to_save: properties_to_save_exhibit, + log_lvl + }); + } + return processed_obj; + } + return null; +} + +/** + * Exhibit Update (V3) + */ +export async function update_ae_obj__exhibit({ + api_cfg, exhibit_id, data_kv, try_cache = true, log_lvl = 0 +}: { + api_cfg: any; exhibit_id: string; data_kv: key_val; try_cache?: boolean; log_lvl?: number; +}): Promise { + const result = await api.update_ae_obj_v3({ + api_cfg, + obj_type: 'event_exhibit', + obj_id: exhibit_id, + fields: data_kv, + log_lvl + }); + + if (result) { + const processed = await process_ae_obj__exhibit_props({ obj_li: [result], log_lvl }); + const processed_obj = processed[0]; + if (try_cache) { + await db_save_ae_obj_li__ae_obj({ + db_instance: db_events, + table_name: 'exhibit', + obj_li: [processed_obj], + properties_to_save: properties_to_save_exhibit, + log_lvl + }); + } + return processed_obj; + } + return null; +} + +/** + * Exhibit Search (V3 Standardized) + */ export async function search__exhibit({ api_cfg, event_id, @@ -245,6 +366,7 @@ export async function search__exhibit({ order_by_li = { name: 'ASC' }, limit = 100, offset = 0, + try_cache = true, log_lvl = 0 }: { api_cfg: any; @@ -256,6 +378,7 @@ export async function search__exhibit({ order_by_li?: any; limit?: number; offset?: number; + try_cache?: boolean; log_lvl?: number; }): Promise { if (log_lvl) { @@ -263,18 +386,12 @@ export async function search__exhibit({ } const search_query: any = { - q: '', - and: [] + q: fulltext_search_qry_str || '', + and: [ + { field: 'event_id', op: 'eq', value: event_id } + ] }; - const params: key_val = {}; - - if (fulltext_search_qry_str && fulltext_search_qry_str.trim().length > 0) { - const qry = fulltext_search_qry_str.trim(); - search_query.and.push({ field: 'name', op: 'like', value: `%${qry}%` }); - params['lk_qry'] = { name: qry }; - } - if (enabled === 'enabled') search_query.and.push({ field: 'enable', op: 'eq', value: 1 }); else if (enabled === 'not_enabled') search_query.and.push({ field: 'enable', op: 'eq', value: 0 }); @@ -282,13 +399,10 @@ export async function search__exhibit({ else if (hidden === 'not_hidden') search_query.and.push({ field: 'hide', op: 'eq', value: 0 }); try { - const result_get = await api.search_ae_obj_v3({ + const result_li = await api.search_ae_obj_v3({ api_cfg, obj_type: 'event_exhibit', - for_obj_type: 'event', - for_obj_id: event_id, search_query, - params, enabled, hidden, view, @@ -298,30 +412,22 @@ export async function search__exhibit({ log_lvl }); - let result_li: ae_EventExhibit[] = []; - if (Array.isArray(result_get)) { - result_li = result_get; - } else if (result_get?.data && Array.isArray(result_get.data)) { - result_li = result_get.data; - } - - if (result_li.length > 0) { - const processed_obj_li = await process_ae_obj__exhibit_props({ - obj_li: result_li, - log_lvl - }); - await db_save_ae_obj_li__ae_obj({ - db_instance: db_events, - table_name: 'exhibit', - obj_li: processed_obj_li, - properties_to_save, - log_lvl - }); - return processed_obj_li; + if (result_li && Array.isArray(result_li)) { + const processed = await process_ae_obj__exhibit_props({ obj_li: result_li, log_lvl }); + if (try_cache) { + await db_save_ae_obj_li__ae_obj({ + db_instance: db_events, + table_name: 'exhibit', + obj_li: processed, + properties_to_save: properties_to_save_exhibit, + log_lvl + }); + } + return processed; } } catch (error: any) { console.error('search__exhibit V3 Request failed.', error); } return []; -} +} \ No newline at end of file diff --git a/src/lib/ae_events/ae_events__exhibit_tracking.ts b/src/lib/ae_events/ae_events__exhibit_tracking.ts index 433643d7..09380087 100644 --- a/src/lib/ae_events/ae_events__exhibit_tracking.ts +++ b/src/lib/ae_events/ae_events__exhibit_tracking.ts @@ -11,9 +11,13 @@ const ae_promises: key_val = {}; export const properties_to_save_exhibit_tracking = [ 'id', 'event_exhibit_tracking_id', + // 'event_exhibit_tracking_id_random', 'event_exhibit_id', + // 'event_exhibit_id_random', 'event_badge_id', + // 'event_badge_id_random', 'event_person_id', + // 'event_person_id_random', 'external_person_id', 'exhibitor_notes', 'responses_json', @@ -47,7 +51,8 @@ export const properties_to_save_exhibit_tracking = [ ]; /** - * NON-EXPORTED LOCAL HELPER + * Robust Property Processor (Aether UI V3) + * Handles ID aliasing and ID clobbering guards. */ async function _process_generic_props>({ obj_li, @@ -67,22 +72,34 @@ async function _process_generic_props>({ for (const original_obj of obj_li) { let processed_obj = { ...original_obj }; + // 1. Random ID Aliasing (Triple-ID Pattern) for (const key in processed_obj) { - if (key.endsWith('_random')) { + if (key.endsWith('_random') && processed_obj[key]) { const newKey = key.slice(0, -7); - (processed_obj as any)[newKey] = processed_obj[key]; + // Guard: Only overwrite if the target is null/undefined to prevent clobbering clean IDs + if (!processed_obj[newKey]) { + (processed_obj as any)[newKey] = processed_obj[key]; + } } } + + // 2. Primary Key Mapping (Dexie compatible) const randomIdKey = `${obj_type}_id_random`; - if (processed_obj[randomIdKey]) { + const baseIdKey = `${obj_type}_id`; + + // Prioritize the base ID field as the primary source of truth + if (processed_obj[baseIdKey]) { + (processed_obj as any).id = String(processed_obj[baseIdKey]); + } else if (processed_obj[randomIdKey]) { (processed_obj as any).id = String(processed_obj[randomIdKey]); } + // 3. Metadata & Sorting Computation const group = processed_obj.group ?? '0'; const priority = processed_obj.priority ? 1 : 0; const sort = processed_obj.sort ?? '0'; const updated = processed_obj.updated_on ?? processed_obj.created_on; - const name = processed_obj.name ?? ''; + const name = processed_obj.event_badge_full_name ?? ''; (processed_obj as any).tmp_sort_1 = `${group}_${priority}_${sort}_${updated}`; (processed_obj as any).tmp_sort_2 = `${group}_${priority}_${sort}_${name}_${updated}`; @@ -111,12 +128,14 @@ export async function process_ae_obj__exhibit_tracking_props({ }); } -// Updated 2026-01-20 to V3 +/** + * Load Single Lead (SWR Pattern) + */ export async function load_ae_obj_id__exhibit_tracking({ api_cfg, exhibit_tracking_id, view = 'default', - try_cache = false, + try_cache = true, log_lvl = 0 }: { api_cfg: any; @@ -125,42 +144,66 @@ export async function load_ae_obj_id__exhibit_tracking({ try_cache?: boolean; log_lvl?: number; }): Promise { - try { - ae_promises.load__event_exhibit_tracking_obj = await api.get_ae_obj_v3({ - api_cfg, - obj_type: 'event_exhibit_tracking', - obj_id: exhibit_tracking_id, - view, - log_lvl - }); + 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})`); + } + + // 1. FAST PATH: Return cached data immediately + if (try_cache) { + try { + const cached = await db_events.exhibit_tracking.get(exhibit_tracking_id); + if (cached) { + const elapsed = (performance.now() - start_time).toFixed(2); + if (log_lvl) console.log(`✅ [Trace] load_ae_obj_id: CACHE HIT at ${elapsed}ms. Returning stale lead shell.`); + + // Background refresh (non-blocking) + _refresh_tracking_id_background({ api_cfg, exhibit_tracking_id, view, try_cache, log_lvl: log_lvl > 1 ? log_lvl : 0 }); + return cached; + } + } catch (e) { + if (log_lvl) console.error(`❌ [Trace] load_ae_obj_id: Cache access error:`, e); + } + } + + // 2. SLOW PATH: Wait for API + return await _refresh_tracking_id_background({ api_cfg, exhibit_tracking_id, view, try_cache, log_lvl }); +} + +async function _refresh_tracking_id_background({ api_cfg, exhibit_tracking_id, view, try_cache, log_lvl }: any) { + const start_time = performance.now(); + if (typeof navigator !== 'undefined' && !navigator.onLine) return null; + try { + if (log_lvl) console.log(`📡 [Trace] _refresh_tracking_id: API Fetching id=${exhibit_tracking_id}`); + const result = await api.get_ae_obj_v3({ api_cfg, obj_type: 'event_exhibit_tracking', obj_id: exhibit_tracking_id, view, log_lvl }); + + if (result) { + const processed = await process_ae_obj__exhibit_tracking_props({ obj_li: [result], log_lvl }); + const processed_obj = processed[0]; + const elapsed = (performance.now() - start_time).toFixed(2); + + if (log_lvl) console.log(`📦 [Trace] _refresh_tracking_id: Received from API at ${elapsed}ms`); - if (ae_promises.load__event_exhibit_tracking_obj) { if (try_cache) { - const processed_obj_li = await process_ae_obj__exhibit_tracking_props({ - obj_li: [ae_promises.load__event_exhibit_tracking_obj], - log_lvl - }); await db_save_ae_obj_li__ae_obj({ db_instance: db_events, table_name: 'exhibit_tracking', - obj_li: processed_obj_li, + obj_li: [processed_obj], properties_to_save: properties_to_save_exhibit_tracking, log_lvl }); } - } else if (try_cache) { - ae_promises.load__event_exhibit_tracking_obj = await db_events.exhibit_tracking.get(exhibit_tracking_id); - } - } catch (error: any) { - if (try_cache) { - ae_promises.load__event_exhibit_tracking_obj = await db_events.exhibit_tracking.get(exhibit_tracking_id); + return processed_obj; } + } catch (e) { + if (log_lvl) console.error(`❌ [Trace] _refresh_tracking_id: API error for id=${exhibit_tracking_id}:`, e); } - - return ae_promises.load__event_exhibit_tracking_obj || null; + return null; } -// Updated 2026-01-20 to V3 +/** + * Load Collection of Leads (SWR Pattern) + */ export async function load_ae_obj_li__exhibit_tracking({ api_cfg, exhibit_id, @@ -172,7 +215,7 @@ export async function load_ae_obj_li__exhibit_tracking({ order_by_li = [ { priority: 'DESC' }, { sort: 'DESC' }, - { updated_on: 'DESC' } + { created_on: 'DESC' } ], try_cache = true, log_lvl = 0 @@ -188,8 +231,39 @@ export async function load_ae_obj_li__exhibit_tracking({ try_cache?: boolean; log_lvl?: number; }): 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})`); + } + + if (try_cache) { + try { + const cached_li = await db_events.exhibit_tracking + .where('event_exhibit_id').equals(exhibit_id) + .toArray(); + + if (cached_li && cached_li.length > 0) { + const elapsed = (performance.now() - start_time).toFixed(2); + if (log_lvl) console.log(`✅ [Trace] load_ae_obj_li: CACHE HIT at ${elapsed}ms (${cached_li.length} items).`); + + // Background refresh (non-blocking) + _refresh_tracking_li_background({ api_cfg, exhibit_id, enabled, hidden, view, limit, offset, order_by_li, try_cache, log_lvl: log_lvl > 1 ? log_lvl : 0 }); + return cached_li; + } + } catch (e) { + if (log_lvl) console.error(`❌ [Trace] load_ae_obj_li: Cache access error:`, e); + } + } + + return await _refresh_tracking_li_background({ api_cfg, exhibit_id, enabled, hidden, view, limit, offset, order_by_li, try_cache, log_lvl }); +} + +async function _refresh_tracking_li_background({ api_cfg, exhibit_id, enabled, hidden, view, limit, offset, order_by_li, try_cache, log_lvl }: any) { + const start_time = performance.now(); + if (typeof navigator !== 'undefined' && !navigator.onLine) return []; try { - ae_promises.load__event_exhibit_tracking_obj_li = await api.get_ae_obj_li_v3({ + if (log_lvl) console.log(`📡 [Trace] _refresh_tracking_li: API Fetching leads for exhibit=${exhibit_id}`); + const result_li = await api.get_ae_obj_li_v3({ api_cfg, obj_type: 'event_exhibit_tracking', for_obj_type: 'event_exhibit', @@ -203,37 +277,31 @@ export async function load_ae_obj_li__exhibit_tracking({ log_lvl }); - if (ae_promises.load__event_exhibit_tracking_obj_li) { + if (result_li) { + const processed = await process_ae_obj__exhibit_tracking_props({ obj_li: result_li, log_lvl }); + const elapsed = (performance.now() - start_time).toFixed(2); + if (log_lvl) console.log(`📦 [Trace] _refresh_tracking_li: Received ${processed.length} items from API at ${elapsed}ms.`); + if (try_cache) { - const processed_obj_li = await process_ae_obj__exhibit_tracking_props({ - obj_li: ae_promises.load__event_exhibit_tracking_obj_li, - log_lvl - }); await db_save_ae_obj_li__ae_obj({ db_instance: db_events, table_name: 'exhibit_tracking', - obj_li: processed_obj_li, + obj_li: processed, properties_to_save: properties_to_save_exhibit_tracking, log_lvl }); } - } else if (try_cache) { - ae_promises.load__event_exhibit_tracking_obj_li = await db_events.exhibit_tracking - .where('event_exhibit_id').equals(exhibit_id) - .toArray(); - } - } catch (error: any) { - if (try_cache) { - ae_promises.load__event_exhibit_tracking_obj_li = await db_events.exhibit_tracking - .where('event_exhibit_id').equals(exhibit_id) - .toArray(); + return processed; } + } catch (e) { + if (log_lvl) console.error(`❌ [Trace] _refresh_tracking_li: API error:`, e); } - - return ae_promises.load__event_exhibit_tracking_obj_li || []; + return []; } -// Updated 2026-01-20 to V3 +/** + * Lead Capture (V3) + */ export async function create_ae_obj__exhibit_tracking({ api_cfg, exhibit_id, @@ -253,31 +321,33 @@ export async function create_ae_obj__exhibit_tracking({ api_cfg, obj_type: 'event_exhibit_tracking', fields: { - event_exhibit_id_random: exhibit_id, - event_badge_id_random: event_badge_id, + event_exhibit_id: exhibit_id, + event_badge_id: event_badge_id, external_person_id }, log_lvl }); - if (result && try_cache) { - const processed_obj_li = await process_ae_obj__exhibit_tracking_props({ - obj_li: [result], - log_lvl - }); - await db_save_ae_obj_li__ae_obj({ - db_instance: db_events, - table_name: 'exhibit_tracking', - obj_li: processed_obj_li, - properties_to_save: properties_to_save_exhibit_tracking, - log_lvl - }); + if (result) { + const processed = await process_ae_obj__exhibit_tracking_props({ obj_li: [result], log_lvl }); + const processed_obj = processed[0]; + if (try_cache) { + await db_save_ae_obj_li__ae_obj({ + db_instance: db_events, + table_name: 'exhibit_tracking', + obj_li: [processed_obj], + properties_to_save: properties_to_save_exhibit_tracking, + log_lvl + }); + } + return processed_obj; } - - return result; + return null; } -// Updated 2026-01-20 to V3 +/** + * Lead Update (V3) + */ export async function update_ae_obj__exhibit_tracking({ api_cfg, exhibit_tracking_id, @@ -299,23 +369,26 @@ export async function update_ae_obj__exhibit_tracking({ log_lvl }); - if (result && try_cache) { - const processed_obj_li = await process_ae_obj__exhibit_tracking_props({ - obj_li: [result], - log_lvl - }); - await db_save_ae_obj_li__ae_obj({ - db_instance: db_events, - table_name: 'exhibit_tracking', - obj_li: processed_obj_li, - properties_to_save: properties_to_save_exhibit_tracking, - log_lvl - }); + if (result) { + const processed = await process_ae_obj__exhibit_tracking_props({ obj_li: [result], log_lvl }); + const processed_obj = processed[0]; + if (try_cache) { + await db_save_ae_obj_li__ae_obj({ + db_instance: db_events, + table_name: 'exhibit_tracking', + obj_li: [processed_obj], + properties_to_save: properties_to_save_exhibit_tracking, + log_lvl + }); + } + return processed_obj; } - - return result; + return null; } +/** + * Legacy Export (Special Case - V3 Not Ready) + */ export async function download_export__event_exhibit_tracking({ api_cfg, exhibit_id, @@ -346,9 +419,12 @@ export async function download_export__event_exhibit_tracking({ }); } -// Updated 2026-01-28 to V3 +/** + * Lead Search (V3 Standardized) + */ export async function search__exhibit_tracking({ api_cfg, + event_id, event_exhibit_id, fulltext_search_qry_str = null, enabled = 'enabled', @@ -357,9 +433,11 @@ export async function search__exhibit_tracking({ order_by_li = { created_on: 'DESC' }, limit = 100, offset = 0, + try_cache = true, log_lvl = 0 }: { api_cfg: any; + event_id: string; event_exhibit_id: string; fulltext_search_qry_str?: string | null; enabled?: 'enabled' | 'all' | 'not_enabled'; @@ -368,6 +446,7 @@ export async function search__exhibit_tracking({ order_by_li?: any; limit?: number; offset?: number; + try_cache?: boolean; log_lvl?: number; }): Promise { if (log_lvl) { @@ -375,19 +454,13 @@ export async function search__exhibit_tracking({ } const search_query: any = { - q: '', - and: [] + q: fulltext_search_qry_str || '', + and: [ + { field: 'event_id', op: 'eq', value: event_id }, + { field: 'event_exhibit_id', op: 'eq', value: event_exhibit_id } + ] }; - const params: key_val = {}; - - if (fulltext_search_qry_str && fulltext_search_qry_str.trim().length > 0) { - const qry = fulltext_search_qry_str.trim(); - // Search across badge name and notes - search_query.and.push({ field: 'event_badge_full_name', op: 'like', value: `%${qry}%` }); - params['lk_qry'] = { event_badge_full_name: qry }; - } - if (enabled === 'enabled') search_query.and.push({ field: 'enable', op: 'eq', value: 1 }); else if (enabled === 'not_enabled') search_query.and.push({ field: 'enable', op: 'eq', value: 0 }); @@ -395,13 +468,12 @@ export async function search__exhibit_tracking({ else if (hidden === 'not_hidden') search_query.and.push({ field: 'hide', op: 'eq', value: 0 }); try { - const result_get = await api.search_ae_obj_v3({ + const result_li = await api.search_ae_obj_v3({ api_cfg, obj_type: 'event_exhibit_tracking', for_obj_type: 'event_exhibit', for_obj_id: event_exhibit_id, search_query, - params, enabled, hidden, view, @@ -411,30 +483,22 @@ export async function search__exhibit_tracking({ log_lvl }); - let result_li: ae_EventExhibitTracking[] = []; - if (Array.isArray(result_get)) { - result_li = result_get; - } else if (result_get?.data && Array.isArray(result_get.data)) { - result_li = result_get.data; - } - - if (result_li.length > 0) { - const processed_obj_li = await process_ae_obj__exhibit_tracking_props({ - obj_li: result_li, - log_lvl - }); - await db_save_ae_obj_li__ae_obj({ - db_instance: db_events, - table_name: 'exhibit_tracking', - obj_li: processed_obj_li, - properties_to_save: properties_to_save_exhibit_tracking, - log_lvl - }); - return processed_obj_li; + if (result_li && Array.isArray(result_li)) { + const processed = await process_ae_obj__exhibit_tracking_props({ obj_li: result_li, log_lvl }); + if (try_cache) { + await db_save_ae_obj_li__ae_obj({ + db_instance: db_events, + table_name: 'exhibit_tracking', + obj_li: processed, + properties_to_save: properties_to_save_exhibit_tracking, + log_lvl + }); + } + return processed; } } catch (error: any) { console.error('search__exhibit_tracking V3 Request failed.', error); } return []; -} +} \ No newline at end of file diff --git a/src/routes/events/[event_id]/(leads)/leads/+page.svelte b/src/routes/events/[event_id]/(leads)/leads/+page.svelte index 7365bc2c..0287927a 100644 --- a/src/routes/events/[event_id]/(leads)/leads/+page.svelte +++ b/src/routes/events/[event_id]/(leads)/leads/+page.svelte @@ -88,13 +88,15 @@ if (!event_id) return; + if (log_lvl) console.log(`🔎 [Trace] Exhibit Search #${current_search_id}: START (remote=${remote_first}, event=${event_id}, str=${params.str})`); + untrack(() => { $events_sess.leads.submit_status__search = 'searching'; }); const qry_str = params.str; - // 1. Local Search + // 1. FAST PATH: Local IDB Search if (!remote_first) { try { let local_results = await db_events.exhibit @@ -135,9 +137,11 @@ }); const local_ids = local_results - .map((e) => String(e.event_exhibit_id)) + .map((e) => String(e.id || e.event_exhibit_id)) .filter(Boolean); + if (current_search_id === last_search_id) { + if (log_lvl) console.log(`✅ [Trace] Exhibit Search #${current_search_id}: Local path found ${local_ids.length} items.`); untrack(() => { exhibit_id_li = local_ids; }); @@ -147,7 +151,7 @@ } } - // 2. Remote Revalidation + // 2. REVALIDATE: API Request try { let order_by_li: any = {}; switch (params.sort) { @@ -180,8 +184,11 @@ if (current_search_id === last_search_id) { const api_ids = results - .map((e: any) => String(e.event_exhibit_id)) + .map((e: any) => String(e.id || e.event_exhibit_id)) .filter(Boolean); + + if (log_lvl) console.log(`📦 [Trace] Exhibit Search #${current_search_id}: API revalidation found ${api_ids.length} items.`); + untrack(() => { exhibit_id_li = api_ids; $events_sess.leads.submit_status__search = 'done'; @@ -189,6 +196,7 @@ } } catch (error) { if (current_search_id === last_search_id) { + console.error('Exhibit revalidation failed:', error); untrack(() => { $events_sess.leads.submit_status__search = 'error'; }); 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 bb834fb8..4c797343 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 @@ -125,13 +125,15 @@ if (!exhibit_id) return; + if (log_lvl) console.log(`🔎 [Trace] Lead Search #${current_search_id}: START (remote=${remote_first}, exhibit=${exhibit_id}, str=${params.str})`); + untrack(() => { $events_sess.leads.submit_status__search = 'searching'; }); const qry_str = params.str; - // 1. Local Search + // 1. FAST PATH: Local IDB Search if (!remote_first) { try { let local_results = await db_events.exhibit_tracking @@ -148,10 +150,15 @@ const notes = ( tracking.exhibitor_notes ?? '' ).toLowerCase(); + const qry_string = ( + tracking.default_qry_str ?? '' + ).toLowerCase(); + if ( !name.includes(qry_str) && !email.includes(qry_str) && - !notes.includes(qry_str) + !notes.includes(qry_str) && + !qry_string.includes(qry_str) ) return false; } @@ -189,10 +196,12 @@ const local_ids = local_results .map((e) => - String(e.event_exhibit_tracking_id) + String(e.id || e.event_exhibit_tracking_id) ) .filter(Boolean); + if (current_search_id === last_search_id) { + if (log_lvl) console.log(`✅ [Trace] Lead Search #${current_search_id}: Local path found ${local_ids.length} items.`); untrack(() => { tracking_id_li = local_ids; }); @@ -202,7 +211,7 @@ } } - // 2. Remote Revalidation + // 2. REVALIDATE: API Request try { let order_by_li: any = {}; switch (params.sort) { @@ -224,6 +233,7 @@ const results = await events_func.search__exhibit_tracking({ api_cfg: $ae_api, + event_id: page.params.event_id || '', event_exhibit_id: exhibit_id, fulltext_search_qry_str: qry_str || null, order_by_li, @@ -233,9 +243,12 @@ if (current_search_id === last_search_id) { const api_ids = results .map((e: any) => - String(e.event_exhibit_tracking_id) + String(e.id || e.event_exhibit_tracking_id) ) .filter(Boolean); + + if (log_lvl) console.log(`📦 [Trace] Lead Search #${current_search_id}: API revalidation found ${api_ids.length} items.`); + untrack(() => { tracking_id_li = api_ids; $events_sess.leads.submit_status__search = 'done'; @@ -243,6 +256,7 @@ } } catch (error) { if (current_search_id === last_search_id) { + console.error('Lead revalidation failed:', error); untrack(() => { $events_sess.leads.submit_status__search = 'error'; }); diff --git a/src/routes/events/[event_id]/(leads)/leads/exhibit/[exhibit_id]/lead/[exhibit_tracking_id]/+page.svelte b/src/routes/events/[event_id]/(leads)/leads/exhibit/[exhibit_id]/lead/[exhibit_tracking_id]/+page.svelte new file mode 100644 index 00000000..711dde29 --- /dev/null +++ b/src/routes/events/[event_id]/(leads)/leads/exhibit/[exhibit_id]/lead/[exhibit_tracking_id]/+page.svelte @@ -0,0 +1,209 @@ + + + + Lead: {$lq__lead_obj?.event_badge_full_name ?? 'Loading...'} + + +
+ +
+
+ + + + +

Lead Profile

+
+ + {#if $lq__lead_obj?.priority} + + + Priority + + {/if} +
+ +
+ {#if !$lq__lead_obj} +
+ +

Loading lead details...

+
+ {:else} + +
+ + +
+ +
+
+
+ +
+
+

+ {$lq__lead_obj.event_badge_full_name || $lq__lead_obj.event_badge_full_name_override || 'Unknown Attendee'} +

+

+ + {$lq__lead_obj.event_badge_professional_title || $lq__lead_obj.event_badge_professional_title_override || 'Professional Title Not Set'} +

+

+ + {$lq__lead_obj.event_badge_affiliations || $lq__lead_obj.event_badge_affiliations_override || 'Organization Not Set'} +

+
+
+ +
+
+
+
+
Email Address
+
{$lq__lead_obj.event_badge_email || 'N/A'}
+
+
+
+
+
+
Captured On
+
{format_date($lq__lead_obj.created_on)}
+
+
+
+
+ + + {#if $lq__lead_obj.responses_json} + {@const responses = typeof $lq__lead_obj.responses_json === 'string' ? JSON.parse($lq__lead_obj.responses_json) : $lq__lead_obj.responses_json} + {#if Object.keys(responses).length > 0} +
+
+ +

Custom Responses

+
+
+ {#each Object.entries(responses) as [question, answer]} +
+
{question}
+
{answer}
+
+ {/each} +
+
+ {/if} + {/if} + + +
+
+ +

Exhibitor Notes

+
+
+ {#if $lq__lead_obj.exhibitor_notes} +

{$lq__lead_obj.exhibitor_notes}

+ {:else} +
+ No notes have been added for this lead yet. +
+ {/if} +
+
+
+ + +
+ +
+
+ +

Exhibit Context

+
+
+
+ Exhibit Name + {$lq__lead_obj.event_exhibit_name || '...'} +
+
+ Captured By + {$lq__lead_obj.external_person_id || 'Unknown'} +
+
+
+ + +
+
System Audit
+
+
LEAD ID: {$lq__lead_obj.event_exhibit_tracking_id}
+
BADGE ID: {$lq__lead_obj.event_badge_id}
+
PERSON ID: {$lq__lead_obj.event_person_id}
+
MODIFIED: {format_date($lq__lead_obj.updated_on)}
+
+
+ + +
+ +
+
{$lq__lead_obj.enable ? 'Record Enabled' : 'Record Disabled'}
+
Visibility Status
+
+
+
+ +
+ {/if} +
+
+ + \ No newline at end of file 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 new file mode 100644 index 00000000..d6f96d13 --- /dev/null +++ b/src/routes/events/[event_id]/(leads)/leads/exhibit/[exhibit_id]/lead/[exhibit_tracking_id]/+page.ts @@ -0,0 +1,25 @@ +/** + * src/routes/events/[event_id]/(leads)/leads/lead/[exhibit_tracking_id]/+page.ts + * Lead Detail Page Loader. + * Responsible for loading a single exhibit tracking record and its associated badge data. + */ +import { browser } from '$app/environment'; +import { events_func } from '$lib/ae_events_functions'; + +export async function load({ params, parent }) { + const parent_data = await parent(); + const account_id = parent_data.account_id; + const ae_acct = parent_data[account_id]; + const exhibit_tracking_id = params.exhibit_tracking_id; + + if (browser && exhibit_tracking_id) { + // Refresh the specific Lead (Tracking) object + events_func.load_ae_obj_id__exhibit_tracking({ + api_cfg: ae_acct.api, + exhibit_tracking_id: exhibit_tracking_id, + log_lvl: 0 + }); + } + + return {}; +} diff --git a/src/routes/events/[event_id]/(leads)/leads/exhibit/[exhibit_id]/lead/[exhibit_tracking_id]/ae_comp__lead_detail_form.svelte b/src/routes/events/[event_id]/(leads)/leads/exhibit/[exhibit_id]/lead/[exhibit_tracking_id]/ae_comp__lead_detail_form.svelte new file mode 100644 index 00000000..1010139b --- /dev/null +++ b/src/routes/events/[event_id]/(leads)/leads/exhibit/[exhibit_id]/lead/[exhibit_tracking_id]/ae_comp__lead_detail_form.svelte @@ -0,0 +1,11 @@ + + +
+

Lead Details

+

Placeholder for qualifiers and notes.

+