feat(leads): implement reactive search for exhibitors and lead tracking

- Implemented V3-style reactive search (Local Cache -> Remote Revalidation) for exhibitors.
- Standardized search fields to 'name' for Exhibits and 'event_badge_full_name' for Lead Tracking.
- Refactored Leads UI with standardized search components and grid layout.
- Updated event routing to exclusively use string-based IDs (Triple-ID pattern).
- Hardened 'ae_EventSession' type definitions to handle null values from V3 API/Dexie.
This commit is contained in:
Scott Idem
2026-01-28 12:05:42 -05:00
parent f145b4dcac
commit cc3a6b0f59
11 changed files with 937 additions and 42 deletions

View File

@@ -104,7 +104,7 @@ async function _process_generic_props<T extends Record<string, any>>({
}
const randomIdKey = `${obj_type}_id_random`;
if (processed_obj[randomIdKey]) {
(processed_obj as any).id = processed_obj[randomIdKey];
(processed_obj as any).id = String(processed_obj[randomIdKey]);
}
const group = processed_obj.group ?? '0';
@@ -514,4 +514,179 @@ export async function download_export__event_exhibit_tracking({
auto_download: true,
log_lvl
});
}
// Updated 2026-01-28 to V3
export async function search__exhibit_tracking({
api_cfg,
event_exhibit_id,
fulltext_search_qry_str = null,
enabled = 'enabled',
hidden = 'all',
order_by_li = { created_on: 'DESC' },
limit = 100,
offset = 0,
log_lvl = 0
}: {
api_cfg: any;
event_exhibit_id: string;
fulltext_search_qry_str?: string | null;
enabled?: 'enabled' | 'all' | 'not_enabled';
hidden?: 'hidden' | 'all' | 'not_hidden';
order_by_li?: any;
limit?: number;
offset?: number;
log_lvl?: number;
}): Promise<ae_EventExhibitTracking[]> {
if (log_lvl) {
console.log(`*** search__exhibit_tracking() *** exhibit_id=${event_exhibit_id} ft=${fulltext_search_qry_str}`);
}
const search_query: any = {
q: '',
and: []
};
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 });
if (hidden === 'hidden') search_query.and.push({ field: 'hide', op: 'eq', value: 1 });
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({
api_cfg,
obj_type: 'event_exhibit_tracking',
for_obj_type: 'event_exhibit',
for_obj_id: event_exhibit_id,
search_query,
params,
order_by_li,
limit,
offset,
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;
}
} catch (error: any) {
console.error('search__exhibit_tracking V3 Request failed.', error);
}
return [];
}
// Updated 2026-01-28 to V3
export async function search__exhibit({
api_cfg,
event_id,
fulltext_search_qry_str = null,
enabled = 'enabled',
hidden = 'not_hidden',
order_by_li = { name: 'ASC' },
limit = 100,
offset = 0,
log_lvl = 0
}: {
api_cfg: any;
event_id: string;
fulltext_search_qry_str?: string | null;
enabled?: 'enabled' | 'all' | 'not_enabled';
hidden?: 'hidden' | 'all' | 'not_hidden';
order_by_li?: any;
limit?: number;
offset?: number;
log_lvl?: number;
}): Promise<ae_EventExhibit[]> {
if (log_lvl) {
console.log(`*** search__exhibit() *** event_id=${event_id} ft=${fulltext_search_qry_str}`);
}
const search_query: any = {
q: '',
and: []
};
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 });
if (hidden === 'hidden') search_query.and.push({ field: 'hide', op: 'eq', value: 1 });
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({
api_cfg,
obj_type: 'event_exhibit',
for_obj_type: 'event',
for_obj_id: event_id,
search_query,
params,
order_by_li,
limit,
offset,
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;
}
} catch (error: any) {
console.error('search__exhibit V3 Request failed.', error);
}
return [];
}

View File

@@ -9,6 +9,8 @@ import * as event_file from '$lib/ae_events/ae_events__event_file';
import {
load_ae_obj_id__exhibit,
load_ae_obj_li__exhibit,
search__exhibit,
search__exhibit_tracking,
load_ae_obj_id__exhibit_tracking,
load_ae_obj_li__exhibit_tracking,
create_ae_obj__exhibit_tracking,
@@ -68,8 +70,10 @@ const export_obj = {
// Event Exhibits
handle_load_ae_obj_id__exhibit: load_ae_obj_id__exhibit,
handle_load_ae_obj_li__exhibit: load_ae_obj_li__exhibit,
search__exhibit: search__exhibit,
handle_load_ae_obj_id__exhibit_tracking: load_ae_obj_id__exhibit_tracking,
handle_load_ae_obj_li__exhibit_tracking: load_ae_obj_li__exhibit_tracking,
search__exhibit_tracking: search__exhibit_tracking,
handle_create_ae_obj__exhibit_tracking: create_ae_obj__exhibit_tracking,
handle_update_ae_obj__exhibit_tracking: update_ae_obj__exhibit_tracking,
handle_download_export__event_exhibit_tracking: download_export__event_exhibit_tracking,

View File

@@ -209,6 +209,18 @@ const events_local_data_struct: key_val = {
refresh_interval__tracking_li: 30000, // 30 seconds
// Standardized Search Pattern 2026-01-28
search_version: 0,
qry__remote_first: false,
qry__search_text: '',
qry__sort_order: 'name_asc',
// Standardized Search Pattern (Tracking) 2026-01-28
tracking__search_version: 0,
tracking__qry__remote_first: false,
tracking__qry__search_text: '',
tracking__qry__sort_order: 'created_desc',
// The entered_passcode is the exhibit booths shared passcode for staff. This is used to initially access the lead retrieval service.
entered_passcode: null,
@@ -696,4 +708,4 @@ const tmp__events_trig_kv: key_val = {};
// 'a-rand-id-6': Promise.resolve('This is a test promise.'),
// },
// };
export const events_trig_kv = writable(tmp__events_trig_kv);
export const events_trig_kv = writable(tmp__events_trig_kv);

View File

@@ -550,22 +550,22 @@ export interface ae_EventSession extends ae_BaseObj {
event_session_id_random: string;
event_id: string;
event_id_random: string;
event_location_id?: string;
event_location_id_random?: string;
event_track_id?: string;
event_track_id_random?: string;
event_location_id?: string | null;
event_location_id_random?: string | null;
event_track_id?: string | null;
event_track_id_random?: string | null;
type_code?: string;
start_datetime?: string | Date;
end_datetime?: string | Date;
type_code?: string | null;
start_datetime?: string | Date | null;
end_datetime?: string | Date | null;
internal_use?: boolean;
record_audio?: boolean;
record_video?: boolean;
internal_use?: boolean | null;
record_audio?: boolean | null;
record_video?: boolean | null;
status?: number;
approve?: boolean;
ready?: boolean;
status?: number | null;
approve?: boolean | null;
ready?: boolean | null;
data_json?: any;