diff --git a/documentation/Aether_Events_Exhibitor_Leads_v3_detail.md b/documentation/Aether_Events_Exhibitor_Leads_v3_detail.md new file mode 100644 index 00000000..5781c7a9 --- /dev/null +++ b/documentation/Aether_Events_Exhibitor_Leads_v3_detail.md @@ -0,0 +1,104 @@ +Aether Events Exhibitor Leads v3 - Details +======= + +## [root page] Leads for Exhibitors Main Landing +* Has a search for Exhibits section that allows for searching Exhibits by name or code. +* Results can be sortable. They can be sorted by name, booth number, or last created/updated. +* This is for exhibitors to find their Exhibit and sign in with the shared passcode or their assigned licensed user credentials. +* If not signed in with Administrator Access or above, then only show results for Exhibitors that have been marked as "priority" (paid). Otherwise show all Exhibitors (to admins). +* Has a list of Exhibits with basic info and link to Exhibit Leads management page for each Exhibit. Do not show results by default or if less than 3 characters in search and they are not signed in with Trusted Access or above. + + +## [exhibit_id page] Exhibit Leads Module +* This is minimalistic. Only the tabs and content needed. + +### Header/Footer +* Overall header/footer should hide by default once signed in. Use Aether iframe mode? +* Leads module header should show the Exhibit name and booth number. It should also have a button to toggle the Add Lead tab and Lead List tab. There should also be a button to show the Manage/Config tab for the Exhibit. +Header: [Name and Booth # text] [Add Lead / Lead List toggle] [Manage/Config button] + + +### Tabs: +I am probably using the term "tab" loosely here. It may just be sections that show/hide based on buttons or whatever. The main thing is to keep the UI simple and not overwhelm the user with too many options at once. The main flow should be to easily add leads and then view/manage those leads. The Manage/Config tab is more for the person managing the Exhibit and should be separate from the lead capture and management interface. + +1. Start - Sign In / Licenses / Payment +2. Add - Search/QR +3. Leads - List of Attendee Leads for Exhibitor +4. Manage - Leads (app and exhibit) Manage + + +### [tab 1] Start / Sign In / Payment +* Only shows when not signed in as Exhibit Licensed Leads User +* Show notification and or message that this is a PWA and can be installed on their device for easier access. + +* Sign in with Exhibit passcode + * This will then allow them to Manage the Exhibit License +* Sign in with Exhibit Licensed user + * This will then allow them to Manage the Exhibit License +* Payment - Leads Payment +* Not Paid: Waiting for payment +* Paid: Marked as paid +* Need to switch to Lucide icons +* Sections: + * Sign in with Exhibit "shared" passcode + * This will then allow them to Manage the Exhibit License + * Change to Sign Out once signed in + * Once signed in, show message to the Exhibit person. Allow them to select number of Licenses, make a payment, and manage the Licenses if paid. + * Sign in with Exhibit Licensed user + * This will then allow them to Manage the Exhibit License + * Change to Sign Out once signed in + * Once signed in, show message to the Leads user. A big button should allow them to then start adding leads. + * Payment (Stripe) - Leads Payment + * Only show if signed in with Exhibit "shared" passcode and not marked as paid. + * Licenses (table): + * A client staff (Trusted Access or above) or someone signed in with an Exhibit passcode can add/edit/remove licenses. + * License: + * full_name + * email + * passcode +* Buttons and Inputs: + * Sign in with Exhibit passcode + * Sign in with Exhibit Licensed user + * Payment - Leads Payment + * Add/Edit/Remove Licenses (if signed in with Exhibit passcode or Trusted Access or above) + +### [tab 2] Add - Search/QR +* Show only when signed in as Exhibit Licensed Leads User or Trusted Access or above. +* Allow for text search of Attendee Badge ID, QR code, name, email, or affiliations. +* Allow for QR code scan to add Attendee Badge as Lead. +* Once found, show basic Attendee Badge info and button to "Add as Lead". +* If already added as Lead, show message and button to "View Lead". +* Sections: + * Text search + * QR scan + * Results with "Add as Lead" or "View Lead" button +* Buttons and Inputs: + * Text input for search + * Button to trigger search + * Button to trigger QR scan (opens camera and scans QR code on badge) + * Button to "Add as Lead" if Attendee Badge found and not already a Lead + * Button to "View Lead" if Attendee Badge found and already a Lead + +### [tab 3] Leads - List of Attendee Leads for Exhibitor +* Allow for toggle between showing all per Exhibit and per licensed user based on their email address. Not perfect, but works well enough. +* Allow for easy edit or remove +* Buttons and Inputs: + * Button to Export Data - CSV or XLSX + * Toggle for show/hide Hidden records + * Select options for sorting: Newest added first, Oldest added first, Alpha ascending, Alpha descending, Last updated first + +### [tab 4] Manage - Leads (app and exhibit) Manage +* Show list of Leads added for this Exhibit. +* Allow for easy edit or remove +* Allow for sorting: Newest added first, Oldest added first, Alpha ascending, Alpha descending, Last updated first +* Allow for toggle for show/hide Hidden records +* Allow for filtering by Licensed user email address +* Buttons and Inputs: + * Button to Export Data - CSV or XLSX + * Toggle for show/hide Hidden records + * Select options for sorting: Newest added first, Oldest added first, Alpha ascending, Alpha descending, Last updated first + * Filter by Licensed user email address (dropdown of emails that have added leads for this Exhibit) + + + + diff --git a/src/lib/ae_events/ae_events__exhibit.editable_fields.ts b/src/lib/ae_events/ae_events__exhibit.editable_fields.ts index c18a7172..fca87fd5 100644 --- a/src/lib/ae_events/ae_events__exhibit.editable_fields.ts +++ b/src/lib/ae_events/ae_events__exhibit.editable_fields.ts @@ -17,16 +17,4 @@ export const editable_fields__event_exhibit = [ 'sort', 'group', 'notes' -]; - -export const editable_fields__event_exhibit_tracking = [ - 'exhibitor_notes', - 'responses_json', - 'data_json', - 'enable', - 'hide', - 'priority', - 'sort', - 'group', - 'notes' -]; +]; \ No newline at end of file diff --git a/src/lib/ae_events/ae_events__exhibit.ts b/src/lib/ae_events/ae_events__exhibit.ts index eed85c3c..79655e81 100644 --- a/src/lib/ae_events/ae_events__exhibit.ts +++ b/src/lib/ae_events/ae_events__exhibit.ts @@ -3,49 +3,10 @@ import { api } from '$lib/api/api'; import { db_save_ae_obj_li__ae_obj } from '$lib/ae_core/core__idb_dexie'; import { db_events } from '$lib/ae_events/db_events'; -import type { ae_EventExhibit, ae_EventExhibitTracking } from '$lib/types/ae_types'; +import type { ae_EventExhibit } from '$lib/types/ae_types'; const ae_promises: key_val = {}; -// --- PROPERTIES TO SAVE --- -export const properties_to_save_exhibit_tracking = [ - 'id', - 'event_exhibit_tracking_id', - 'event_exhibit_id', - 'event_badge_id', - 'event_person_id', - 'external_person_id', - 'exhibitor_notes', - 'responses_json', - 'data_json', - 'event_exhibit_name', - 'event_badge_title_names', - 'event_badge_given_name', - 'event_badge_family_name', - 'event_badge_designations', - 'event_badge_full_name', - 'event_badge_full_name_override', - 'event_badge_professional_title', - 'event_badge_professional_title_override', - 'event_badge_affiliations', - 'event_badge_affiliations_override', - 'event_badge_email', - 'event_badge_email_override', - 'event_badge_location', - 'event_badge_location_override', - 'event_badge_country', - 'enable', - 'hide', - 'priority', - 'sort', - 'group', - 'notes', - 'created_on', - 'updated_on', - 'tmp_sort_1', - 'tmp_sort_2' -]; - // --- PROPERTIES TO SAVE --- export const properties_to_save = [ 'id', @@ -126,20 +87,6 @@ async function _process_generic_props>({ return processed_obj_li; } -export async function process_ae_obj__exhibit_tracking_props({ - obj_li, - log_lvl = 0 -}: { - obj_li: any[]; - log_lvl?: number; -}) { - return _process_generic_props({ - obj_li, - obj_type: 'event_exhibit_tracking', - log_lvl - }); -} - export async function process_ae_obj__exhibit_props({ obj_li, log_lvl = 0 @@ -287,334 +234,6 @@ export async function load_ae_obj_li__exhibit({ return ae_promises.load__event_exhibit_obj_li || []; } -// Updated 2026-01-20 to V3 -export async function load_ae_obj_id__exhibit_tracking({ - api_cfg, - exhibit_tracking_id, - view = 'default', - try_cache = false, - log_lvl = 0 -}: { - api_cfg: any; - exhibit_tracking_id: string; - view?: string; - 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 - }); - - 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, - 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 ae_promises.load__event_exhibit_tracking_obj || null; -} - -// Updated 2026-01-20 to V3 -export async function load_ae_obj_li__exhibit_tracking({ - api_cfg, - exhibit_id, - enabled = 'enabled', - hidden = 'all', - view = 'default', - limit = 100, - offset = 0, - order_by_li = [ - { priority: 'DESC' }, - { sort: 'DESC' }, - { updated_on: 'DESC' } - ], - try_cache = true, - log_lvl = 0 -}: { - api_cfg: any; - exhibit_id: string; - enabled?: 'enabled' | 'all' | 'not_enabled'; - hidden?: 'hidden' | 'all' | 'not_hidden'; - view?: string; - limit?: number; - offset?: number; - order_by_li?: any; - try_cache?: boolean; - log_lvl?: number; -}): Promise { - try { - ae_promises.load__event_exhibit_tracking_obj_li = await api.get_ae_obj_li_v3({ - api_cfg, - obj_type: 'event_exhibit_tracking', - for_obj_type: 'event_exhibit', - for_obj_id: exhibit_id, - enabled, - hidden, - view, - limit, - offset, - order_by_li, - log_lvl - }); - - if (ae_promises.load__event_exhibit_tracking_obj_li) { - 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, - 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 ae_promises.load__event_exhibit_tracking_obj_li || []; -} - -// Updated 2026-01-20 to V3 -export async function create_ae_obj__exhibit_tracking({ - api_cfg, - exhibit_id, - event_badge_id, - external_person_id, - try_cache = true, - log_lvl = 0 -}: { - api_cfg: any; - exhibit_id: string; - event_badge_id: string; - external_person_id: string; - try_cache?: boolean; - log_lvl?: number; -}): Promise { - const result = await api.create_ae_obj_v3({ - api_cfg, - obj_type: 'event_exhibit_tracking', - fields: { - event_exhibit_id_random: exhibit_id, - event_badge_id_random: 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 - }); - } - - return result; -} - -// Updated 2026-01-20 to V3 -export async function update_ae_obj__exhibit_tracking({ - api_cfg, - exhibit_tracking_id, - data, - try_cache = true, - log_lvl = 0 -}: { - api_cfg: any; - exhibit_tracking_id: string; - data: any; - try_cache?: boolean; - log_lvl?: number; -}): Promise { - const result = await api.update_ae_obj_v3({ - api_cfg, - obj_type: 'event_exhibit_tracking', - obj_id: exhibit_tracking_id, - fields: data, - 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 - }); - } - - return result; -} - -export async function download_export__event_exhibit_tracking({ - api_cfg, - exhibit_id, - file_type = 'CSV', - filename = 'exhibit_tracking_export.csv', - log_lvl = 0 -}: { - api_cfg: any; - exhibit_id: string; - file_type?: string; - filename?: string; - log_lvl?: number; -}) { - const endpoint = `/event/exhibit/${exhibit_id}/tracking/export`; - const params = { - file_type, - return_file: true - }; - - return await api.get_object({ - api_cfg, - endpoint, - params, - return_blob: true, - filename, - 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', - view = 'default', - 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'; - view?: string; - order_by_li?: any; - limit?: number; - offset?: number; - log_lvl?: number; -}): Promise { - 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, - enabled, - hidden, - view, - 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, @@ -705,4 +324,4 @@ export async function search__exhibit({ } return []; -} \ No newline at end of file +} diff --git a/src/lib/ae_events/ae_events__exhibit_tracking.editable_fields.ts b/src/lib/ae_events/ae_events__exhibit_tracking.editable_fields.ts new file mode 100644 index 00000000..2b527912 --- /dev/null +++ b/src/lib/ae_events/ae_events__exhibit_tracking.editable_fields.ts @@ -0,0 +1,11 @@ +export const editable_fields__event_exhibit_tracking = [ + 'exhibitor_notes', + 'responses_json', + 'data_json', + 'enable', + 'hide', + 'priority', + 'sort', + 'group', + 'notes' +]; diff --git a/src/lib/ae_events/ae_events__exhibit_tracking.ts b/src/lib/ae_events/ae_events__exhibit_tracking.ts new file mode 100644 index 00000000..433643d7 --- /dev/null +++ b/src/lib/ae_events/ae_events__exhibit_tracking.ts @@ -0,0 +1,440 @@ +import type { key_val } from '$lib/stores/ae_stores'; +import { api } from '$lib/api/api'; + +import { db_save_ae_obj_li__ae_obj } from '$lib/ae_core/core__idb_dexie'; +import { db_events } from '$lib/ae_events/db_events'; +import type { ae_EventExhibitTracking } from '$lib/types/ae_types'; + +const ae_promises: key_val = {}; + +// --- PROPERTIES TO SAVE --- +export const properties_to_save_exhibit_tracking = [ + 'id', + 'event_exhibit_tracking_id', + 'event_exhibit_id', + 'event_badge_id', + 'event_person_id', + 'external_person_id', + 'exhibitor_notes', + 'responses_json', + 'data_json', + 'event_exhibit_name', + 'event_badge_title_names', + 'event_badge_given_name', + 'event_badge_family_name', + 'event_badge_designations', + 'event_badge_full_name', + 'event_badge_full_name_override', + 'event_badge_professional_title', + 'event_badge_professional_title_override', + 'event_badge_affiliations', + 'event_badge_affiliations_override', + 'event_badge_email', + 'event_badge_email_override', + 'event_badge_location', + 'event_badge_location_override', + 'event_badge_country', + 'enable', + 'hide', + 'priority', + 'sort', + 'group', + 'notes', + 'created_on', + 'updated_on', + 'tmp_sort_1', + 'tmp_sort_2' +]; + +/** + * NON-EXPORTED LOCAL HELPER + */ +async function _process_generic_props>({ + obj_li, + obj_type, + log_lvl = 0, + specific_processor +}: { + obj_li: T[]; + obj_type: string; + log_lvl?: number; + specific_processor?: (obj: T) => Promise | T; +}): Promise { + if (!obj_li || obj_li.length === 0) return []; + + const processed_obj_li: T[] = []; + + for (const original_obj of obj_li) { + let processed_obj = { ...original_obj }; + + for (const key in processed_obj) { + if (key.endsWith('_random')) { + const newKey = key.slice(0, -7); + (processed_obj as any)[newKey] = processed_obj[key]; + } + } + const randomIdKey = `${obj_type}_id_random`; + if (processed_obj[randomIdKey]) { + (processed_obj as any).id = String(processed_obj[randomIdKey]); + } + + 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 ?? ''; + + (processed_obj as any).tmp_sort_1 = `${group}_${priority}_${sort}_${updated}`; + (processed_obj as any).tmp_sort_2 = `${group}_${priority}_${sort}_${name}_${updated}`; + + if (specific_processor) { + processed_obj = await Promise.resolve(specific_processor(processed_obj)); + } + + processed_obj_li.push(processed_obj as T); + } + + return processed_obj_li; +} + +export async function process_ae_obj__exhibit_tracking_props({ + obj_li, + log_lvl = 0 +}: { + obj_li: any[]; + log_lvl?: number; +}) { + return _process_generic_props({ + obj_li, + obj_type: 'event_exhibit_tracking', + log_lvl + }); +} + +// Updated 2026-01-20 to V3 +export async function load_ae_obj_id__exhibit_tracking({ + api_cfg, + exhibit_tracking_id, + view = 'default', + try_cache = false, + log_lvl = 0 +}: { + api_cfg: any; + exhibit_tracking_id: string; + view?: string; + 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 + }); + + 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, + 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 ae_promises.load__event_exhibit_tracking_obj || null; +} + +// Updated 2026-01-20 to V3 +export async function load_ae_obj_li__exhibit_tracking({ + api_cfg, + exhibit_id, + enabled = 'enabled', + hidden = 'all', + view = 'default', + limit = 100, + offset = 0, + order_by_li = [ + { priority: 'DESC' }, + { sort: 'DESC' }, + { updated_on: 'DESC' } + ], + try_cache = true, + log_lvl = 0 +}: { + api_cfg: any; + exhibit_id: string; + enabled?: 'enabled' | 'all' | 'not_enabled'; + hidden?: 'hidden' | 'all' | 'not_hidden'; + view?: string; + limit?: number; + offset?: number; + order_by_li?: any; + try_cache?: boolean; + log_lvl?: number; +}): Promise { + try { + ae_promises.load__event_exhibit_tracking_obj_li = await api.get_ae_obj_li_v3({ + api_cfg, + obj_type: 'event_exhibit_tracking', + for_obj_type: 'event_exhibit', + for_obj_id: exhibit_id, + enabled, + hidden, + view, + limit, + offset, + order_by_li, + log_lvl + }); + + if (ae_promises.load__event_exhibit_tracking_obj_li) { + 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, + 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 ae_promises.load__event_exhibit_tracking_obj_li || []; +} + +// Updated 2026-01-20 to V3 +export async function create_ae_obj__exhibit_tracking({ + api_cfg, + exhibit_id, + event_badge_id, + external_person_id, + try_cache = true, + log_lvl = 0 +}: { + api_cfg: any; + exhibit_id: string; + event_badge_id: string; + external_person_id: string; + try_cache?: boolean; + log_lvl?: number; +}): Promise { + const result = await api.create_ae_obj_v3({ + api_cfg, + obj_type: 'event_exhibit_tracking', + fields: { + event_exhibit_id_random: exhibit_id, + event_badge_id_random: 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 + }); + } + + return result; +} + +// Updated 2026-01-20 to V3 +export async function update_ae_obj__exhibit_tracking({ + api_cfg, + exhibit_tracking_id, + data, + try_cache = true, + log_lvl = 0 +}: { + api_cfg: any; + exhibit_tracking_id: string; + data: any; + try_cache?: boolean; + log_lvl?: number; +}): Promise { + const result = await api.update_ae_obj_v3({ + api_cfg, + obj_type: 'event_exhibit_tracking', + obj_id: exhibit_tracking_id, + fields: data, + 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 + }); + } + + return result; +} + +export async function download_export__event_exhibit_tracking({ + api_cfg, + exhibit_id, + file_type = 'CSV', + filename = 'exhibit_tracking_export.csv', + log_lvl = 0 +}: { + api_cfg: any; + exhibit_id: string; + file_type?: string; + filename?: string; + log_lvl?: number; +}) { + const endpoint = `/event/exhibit/${exhibit_id}/tracking/export`; + const params = { + file_type, + return_file: true + }; + + return await api.get_object({ + api_cfg, + endpoint, + params, + return_blob: true, + filename, + 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', + view = 'default', + 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'; + view?: string; + order_by_li?: any; + limit?: number; + offset?: number; + log_lvl?: number; +}): Promise { + 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, + enabled, + hidden, + view, + 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 []; +} diff --git a/src/lib/ae_events_functions.ts b/src/lib/ae_events_functions.ts index 68b1b581..c9dbbde0 100644 --- a/src/lib/ae_events_functions.ts +++ b/src/lib/ae_events_functions.ts @@ -9,14 +9,17 @@ 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 +} from '$lib/ae_events/ae_events__exhibit'; + +import { search__exhibit_tracking, load_ae_obj_id__exhibit_tracking, load_ae_obj_li__exhibit_tracking, create_ae_obj__exhibit_tracking, update_ae_obj__exhibit_tracking, download_export__event_exhibit_tracking -} from '$lib/ae_events/ae_events__exhibit'; +} from '$lib/ae_events/ae_events__exhibit_tracking'; import * as event_location from '$lib/ae_events/ae_events__event_location'; @@ -68,15 +71,15 @@ const export_obj = { update_ae_obj__event_device: event_device.update_ae_obj__event_device, // Event Exhibits - handle_load_ae_obj_id__exhibit: load_ae_obj_id__exhibit, - handle_load_ae_obj_li__exhibit: load_ae_obj_li__exhibit, + load_ae_obj_id__exhibit: load_ae_obj_id__exhibit, + 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, + load_ae_obj_id__exhibit_tracking: load_ae_obj_id__exhibit_tracking, + 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, + create_ae_obj__exhibit_tracking: create_ae_obj__exhibit_tracking, + update_ae_obj__exhibit_tracking: update_ae_obj__exhibit_tracking, + download_export__event_exhibit_tracking: download_export__event_exhibit_tracking, // Event Files load_ae_obj_id__event_file: event_file.load_ae_obj_id__event_file, diff --git a/src/routes/events/[event_id]/(leads)/README.md b/src/routes/events/[event_id]/(leads)/README.md index 265ac5c1..969072dd 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.handle_load_ae_obj_li__exhibit`. + - `+page.ts`: Loads the data for available exhibits using `events_func.load_ae_obj_li__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.handle_load_ae_obj_id__exhibit` and `events_func.handle_load_ae_obj_li__exhibit_tracking`. + - `+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`. ### Core Components (within `src/routes/events/[event_id]/(leads)/exhibit/[slug]/`) diff --git a/src/routes/events/[event_id]/(leads)/leads/+layout.ts b/src/routes/events/[event_id]/(leads)/leads/+layout.ts new file mode 100644 index 00000000..37cc4414 --- /dev/null +++ b/src/routes/events/[event_id]/(leads)/leads/+layout.ts @@ -0,0 +1,25 @@ +/** + * src/routes/events/[event_id]/(leads)/leads/+layout.ts + * Leads Module Level Layout Loader. + */ +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 event_id = params.event_id; + + if (browser) { + events_func.load_ae_obj_id__event({ + api_cfg: ae_acct.api, + event_id: event_id, + log_lvl: 0 + }); + } + + return { + ...parent_data + }; +} \ No newline at end of file diff --git a/src/routes/events/[event_id]/(leads)/leads/+page.ts b/src/routes/events/[event_id]/(leads)/leads/+page.ts new file mode 100644 index 00000000..5311fb59 --- /dev/null +++ b/src/routes/events/[event_id]/(leads)/leads/+page.ts @@ -0,0 +1,25 @@ +/** + * src/routes/events/[event_id]/(leads)/leads/+page.ts + * Exhibit Finder Page Loader. + */ +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 event_id = params.event_id; + + if (browser && event_id) { + events_func.load_ae_obj_li__exhibit({ + api_cfg: ae_acct.api, + for_obj_type: 'event', + for_obj_id: event_id, + limit: 100, + log_lvl: 0 + }); + } + + return {}; +} \ No newline at end of file diff --git a/src/routes/events/[event_id]/(leads)/leads/exhibit/[exhibit_id]/+layout.svelte b/src/routes/events/[event_id]/(leads)/leads/exhibit/[exhibit_id]/+layout.svelte new file mode 100644 index 00000000..6c1c4cc2 --- /dev/null +++ b/src/routes/events/[event_id]/(leads)/leads/exhibit/[exhibit_id]/+layout.svelte @@ -0,0 +1,26 @@ + + +
+ {@render children?.()} +
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 new file mode 100644 index 00000000..49a41d1d --- /dev/null +++ b/src/routes/events/[event_id]/(leads)/leads/exhibit/[exhibit_id]/+layout.ts @@ -0,0 +1,38 @@ +/** + * src/routes/events/[event_id]/(leads)/leads/exhibit/[exhibit_id]/+layout.ts + * Exhibitor Dashboard Loader. + */ +import { browser } from '$app/environment'; +import { events_func } from '$lib/ae_events_functions'; +import { events_slct } from '$lib/stores/ae_events_stores'; + +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_id = params.exhibit_id; + + // Sync to store for components + events_slct.update(s => { + s.exhibit_id = exhibit_id; + return s; + }); + + if (browser && exhibit_id) { + events_func.load_ae_obj_id__exhibit({ + api_cfg: ae_acct.api, + exhibit_id: exhibit_id, + log_lvl: 0 + }); + + events_func.load_ae_obj_li__exhibit_tracking({ + api_cfg: ae_acct.api, + for_obj_type: 'event_exhibit', + for_obj_id: exhibit_id, + limit: 250, + log_lvl: 0 + }); + } + + return {}; +} \ No newline at end of file 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 6d89c8d0..7f35ae3e 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 @@ -228,7 +228,7 @@ const exhibit_id = page.params.exhibit_id; if (!exhibit_id) return; - await events_func.handle_download_export__event_exhibit_tracking({ + await events_func.download_export__event_exhibit_tracking({ api_cfg: $ae_api, exhibit_id: exhibit_id, log_lvl: 1 diff --git a/src/routes/events/[event_id]/(leads)/leads/exhibit/[exhibit_id]/ae_comp__exhibit_license_list.svelte b/src/routes/events/[event_id]/(leads)/leads/exhibit/[exhibit_id]/ae_comp__exhibit_license_list.svelte new file mode 100644 index 00000000..838dbd67 --- /dev/null +++ b/src/routes/events/[event_id]/(leads)/leads/exhibit/[exhibit_id]/ae_comp__exhibit_license_list.svelte @@ -0,0 +1,11 @@ + + +
+

Staff Licenses

+

Placeholder for license assignment logic.

+
diff --git a/src/routes/events/[event_id]/(leads)/leads/exhibit/[exhibit_id]/ae_comp__exhibit_payment.svelte b/src/routes/events/[event_id]/(leads)/leads/exhibit/[exhibit_id]/ae_comp__exhibit_payment.svelte new file mode 100644 index 00000000..00c513f3 --- /dev/null +++ b/src/routes/events/[event_id]/(leads)/leads/exhibit/[exhibit_id]/ae_comp__exhibit_payment.svelte @@ -0,0 +1,11 @@ + + +
+

Payment & Licensing

+

Placeholder for Stripe integration.

+
diff --git a/src/routes/events/[event_id]/(leads)/leads/exhibit/[exhibit_id]/ae_comp__exhibit_signin.svelte b/src/routes/events/[event_id]/(leads)/leads/exhibit/[exhibit_id]/ae_comp__exhibit_signin.svelte new file mode 100644 index 00000000..56126e61 --- /dev/null +++ b/src/routes/events/[event_id]/(leads)/leads/exhibit/[exhibit_id]/ae_comp__exhibit_signin.svelte @@ -0,0 +1,11 @@ + + + diff --git a/src/routes/events/[event_id]/(leads)/leads/exhibit/[exhibit_id]/ae_comp__lead_manual_search.svelte b/src/routes/events/[event_id]/(leads)/leads/exhibit/[exhibit_id]/ae_comp__lead_manual_search.svelte new file mode 100644 index 00000000..5c7a68a6 --- /dev/null +++ b/src/routes/events/[event_id]/(leads)/leads/exhibit/[exhibit_id]/ae_comp__lead_manual_search.svelte @@ -0,0 +1,11 @@ + + + diff --git a/src/routes/events/[event_id]/(leads)/leads/exhibit/[exhibit_id]/ae_comp__lead_qr_scanner.svelte b/src/routes/events/[event_id]/(leads)/leads/exhibit/[exhibit_id]/ae_comp__lead_qr_scanner.svelte new file mode 100644 index 00000000..f6636815 --- /dev/null +++ b/src/routes/events/[event_id]/(leads)/leads/exhibit/[exhibit_id]/ae_comp__lead_qr_scanner.svelte @@ -0,0 +1,11 @@ + + +
+

Scan Badge

+

Placeholder for QR scanner component.

+
diff --git a/src/routes/events/[event_id]/(leads)/leads/exhibit/[exhibit_id]/ae_tab__add.svelte b/src/routes/events/[event_id]/(leads)/leads/exhibit/[exhibit_id]/ae_tab__add.svelte new file mode 100644 index 00000000..9c654369 --- /dev/null +++ b/src/routes/events/[event_id]/(leads)/leads/exhibit/[exhibit_id]/ae_tab__add.svelte @@ -0,0 +1,18 @@ + + +
+
+ + +
+ {#if show_qr} {:else} {/if} +
diff --git a/src/routes/events/[event_id]/(leads)/leads/exhibit/[exhibit_id]/ae_tab__list.svelte b/src/routes/events/[event_id]/(leads)/leads/exhibit/[exhibit_id]/ae_tab__list.svelte new file mode 100644 index 00000000..e31a40fb --- /dev/null +++ b/src/routes/events/[event_id]/(leads)/leads/exhibit/[exhibit_id]/ae_tab__list.svelte @@ -0,0 +1,17 @@ + + +
+

Captured Leads

+ +
diff --git a/src/routes/events/[event_id]/(leads)/leads/lead/[exhibit_tracking_id]/+page.ts b/src/routes/events/[event_id]/(leads)/leads/lead/[exhibit_tracking_id]/+page.ts new file mode 100644 index 00000000..d6f96d13 --- /dev/null +++ b/src/routes/events/[event_id]/(leads)/leads/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/lead/[exhibit_tracking_id]/ae_comp__lead_detail_form.svelte b/src/routes/events/[event_id]/(leads)/leads/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/lead/[exhibit_tracking_id]/ae_comp__lead_detail_form.svelte @@ -0,0 +1,11 @@ + + +
+

Lead Details

+

Placeholder for qualifiers and notes.

+