diff --git a/documentation/Aether_Events_Exhibitor_Leads_v3.md b/documentation/Aether_Events_Exhibitor_Leads_v3.md index 645aa97f..071ff5ff 100644 --- a/documentation/Aether_Events_Exhibitor_Leads_v3.md +++ b/documentation/Aether_Events_Exhibitor_Leads_v3.md @@ -102,7 +102,7 @@ License: ## [tab 4] Manage / Config ### Exhibit Specific -* Priorty/payment toggle - Administrator Access or above +* Priority/payment toggle - Administrator Access or above * Max licenses (number) - readonly or edit for Administrator Access or above * Small devices (number) - readonly or edit for Administrator Access or above * Large devices (number) - readonly or edit for Administrator Access or above diff --git a/documentation/Aether_Events_Exhibitor_Leads_v3_detail.md b/documentation/Aether_Events_Exhibitor_Leads_v3_detail.md index 5781c7a9..7bc91469 100644 --- a/documentation/Aether_Events_Exhibitor_Leads_v3_detail.md +++ b/documentation/Aether_Events_Exhibitor_Leads_v3_detail.md @@ -78,27 +78,76 @@ I am probably using the term "tab" loosely here. It may just be sections that sh * 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 +Functions needed: + * Search function to find Attendee Badge by Badge ID, QR code, name, email, or affiliations. + * QR code scan function to read QR code and find Attendee Badge. + * Add Lead function to create Exhibit_tracking entry linking Exhibit and Attendee Badge. ### [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 +* Sections: + * List of Leads with basic info and buttons to Edit or Remove + * Options: + * Filter by Licensed user email address (dropdown of emails that have added leads for this Exhibit) + * Toggle for show/hide Hidden records + * Select options for sorting: Newest added first, Oldest added first, Alpha ascending, Alpha descending, Last updated first * 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 + * Should it have a text search? +* NOTE: It is probably easiest for them to us the search tab to find a lead that has already been added. It will show "View Lead" button if already added. +Functions needed: + * Load Leads function to get Exhibit_tracking entries for the Exhibit. + * Filter function to filter by Licensed user email address. + * Sort function to sort by selected option. + * Export function to export displayed Leads to CSV or XLSX. -### [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 +### [tab 4] Manage - Leads (app and exhibit) Manage / Config +#### Exhibit Specific +* Priority/payment toggle - Administrator Access or above +* Max licenses (number) - readonly or edit for Administrator Access or above +* Small devices (number) - readonly or edit for Administrator Access or above +* Large devices (number) - readonly or edit for Administrator Access or above +* Exhibit (shared) Passcode +* Same Exhibit Leads License list component as the Start tab's Licensed Users section + +#### App Specific + +* Show/Hide Payment Tab +* Additional Settings: + * List refresh interval in seconds - default 25 seconds; 1 second to 2 minutes (120000) + * Basic reload/refresh + * Clear Indexed DB + * Clear localStorage + * Auto hide header/footer on sign in - default true + * (?) Turn on iframe mode + * (?) Show or hide additional details - Use "$events_loc.show_details"? + +* Sections: + * Exhibit Specific Manage/Config + * App Specific Manage/Config * 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) - - - + * Exhibit Specific: + * Priority/payment toggle - Administrator Access or above + * Max licenses (number) - readonly or edit for Administrator Access or above + * Small devices (number) - readonly or edit for Administrator Access or above + * Large devices (number) - readonly or edit for Administrator Access or above + * Exhibit (shared) Passcode + * Same Exhibit Leads License list component as the Start tab's Licensed Users section + * App Specific: + * Show/Hide Payment Tab + * Show last refresh time and counter for next refresh based on the List refresh interval setting. + * Additional Settings: + * List refresh interval in seconds - default 25 seconds; 1 second to 2 minutes (120000) + * Basic reload/refresh (F5) + * Clear Indexed DB + * Clear localStorage + * Auto hide header/footer on sign in - default true + * (?) Turn on iframe mode + * (?) Show or hide additional details - Use "$events_loc.show_details"? +* Functions: + * Update Exhibit configuration function to update the Exhibit with the new settings. + * Update App configuration function to update the app-wide settings for the Leads module. diff --git a/src/routes/events/[event_id]/(leads)/leads/+layout.svelte b/src/routes/events/[event_id]/(leads)/leads/+layout.svelte index fadaa765..a7062e7b 100644 --- a/src/routes/events/[event_id]/(leads)/leads/+layout.svelte +++ b/src/routes/events/[event_id]/(leads)/leads/+layout.svelte @@ -7,6 +7,6 @@ // Basic layout for the leads module -
+ {@render children?.()} -
+ diff --git a/src/routes/events/[event_id]/(leads)/leads/+page.ts b/src/routes/events/[event_id]/(leads)/leads/+page.ts index 5311fb59..1e120cb5 100644 --- a/src/routes/events/[event_id]/(leads)/leads/+page.ts +++ b/src/routes/events/[event_id]/(leads)/leads/+page.ts @@ -14,8 +14,7 @@ export async function load({ params, parent }) { 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, + event_id: event_id, limit: 100, log_lvl: 0 }); 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 index 6c1c4cc2..d569a765 100644 --- 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 @@ -21,6 +21,6 @@ ); -
+ {@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 index 49a41d1d..990988ae 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 @@ -27,8 +27,7 @@ export async function load({ params, parent }) { events_func.load_ae_obj_li__exhibit_tracking({ api_cfg: ae_acct.api, - for_obj_type: 'event_exhibit', - for_obj_id: exhibit_id, + exhibit_id: exhibit_id, limit: 250, log_lvl: 0 }); 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 7f35ae3e..8c309f7d 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,9 +10,22 @@ import { ae_api, ae_loc } from '$lib/stores/ae_stores'; import { page } from '$app/state'; import { events_func } from '$lib/ae_events_functions'; - import { LoaderCircle, UserPlus, Download } from 'lucide-svelte'; + import { + LoaderCircle, + UserPlus, + Download, + Settings, + Plus, + List as ListIcon, + LogIn, + LayoutGrid, + Search + } from 'lucide-svelte'; import Comp_exhibit_tracking_search from './ae_comp__exhibit_tracking_search.svelte'; import Comp_exhibit_tracking_obj_li from './ae_comp__exhibit_tracking_obj_li.svelte'; + import Tab_add from './ae_tab__add.svelte'; + import Tab_start from './ae_tab__start.svelte'; + import Tab_manage from './ae_tab__manage.svelte'; // *** Initialization & Store Guard *** if ($events_loc.leads) { @@ -28,6 +41,13 @@ $events_loc.leads.tracking__qry__sort_order = 'created_desc'; } + // --- Tab State --- + let active_tab = $state('list'); // 'start', 'add', 'list', 'manage' + let previous_main_tab = $state('list'); // To remember if we were on 'add' or 'list' before going to 'manage' + + // Mock sign-in state for now + let is_signed_in = $state(true); + let tracking_id_li: Array = $state([]); let search_debounce_timer: any = null; let last_search_id = 0; @@ -234,47 +254,110 @@ log_lvl: 1 }); } + + function toggle_main_tab() { + if (active_tab === 'add') { + active_tab = 'list'; + previous_main_tab = 'list'; + } else { + active_tab = 'add'; + previous_main_tab = 'add'; + } + } + + function toggle_manage_tab() { + if (active_tab === 'manage') { + active_tab = previous_main_tab; + } else { + active_tab = 'manage'; + } + }
-
-
-

- Leads for {$lq__exhibit_obj?.name ?? 'Exhibitor'} + +
+
+

+ {$lq__exhibit_obj?.name ?? 'Exhibitor'}

-

Booth #{$lq__exhibit_obj?.code ?? '...'}

+

Booth #{$lq__exhibit_obj?.code ?? '...'}

-
+ +
+ - + +
+
+ + +
+
+ {#if !is_signed_in} +
+ +
+ {:else if active_tab === 'add'} + + {:else if active_tab === 'list'} +
+
+

Lead List

+ +
+ + + + {#if $events_sess.leads.submit_status__search === 'searching' && tracking_id_li.length === 0} +
+ +

Searching leads...

+
+ {:else} + + {/if} +
+ {:else if active_tab === 'manage'} +
+ +
+ {/if}
- - - - {#if $events_sess.leads.submit_status__search === 'searching' && tracking_id_li.length === 0} -
- -

Searching leads...

-
- {:else} - - {/if}

+ + 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 bb9229cc..dae311d7 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 @@ -24,7 +24,7 @@ } -
+
{#if !$lq__event_exhibit_tracking_obj_li}
diff --git a/src/routes/events/[event_id]/(leads)/leads/exhibit/[exhibit_id]/ae_comp__exhibit_tracking_search.svelte b/src/routes/events/[event_id]/(leads)/leads/exhibit/[exhibit_id]/ae_comp__exhibit_tracking_search.svelte index 83e20433..4b43c075 100644 --- a/src/routes/events/[event_id]/(leads)/leads/exhibit/[exhibit_id]/ae_comp__exhibit_tracking_search.svelte +++ b/src/routes/events/[event_id]/(leads)/leads/exhibit/[exhibit_id]/ae_comp__exhibit_tracking_search.svelte @@ -40,7 +40,7 @@ handle_search_trigger(); })} autocomplete="off" - class="search_form flex flex-row flex-wrap gap-1 items-center justify-center w-full max-w-7xl px-2 md:px-12 py-2 preset-tonal-primary rounded-lg shadow-sm" + class="search_form flex flex-row flex-wrap gap-1 items-center justify-center w-full px-2 py-2 preset-tonal-primary rounded-lg shadow-sm" >
/** * src/routes/events/[event_id]/(leads)/leads/exhibit/[exhibit_id]/ae_comp__lead_manual_search.svelte - * Manual Attendee Search Stub. + * Manual Attendee Search for adding leads. */ + import { page } from '$app/state'; + import { ae_api } from '$lib/stores/ae_stores'; + import { events_func } from '$lib/ae_events_functions'; + import { Search, UserPlus, CheckCircle, LoaderCircle } from 'lucide-svelte'; + import type { ae_EventBadge } from '$lib/types/ae_types'; + import { ae_util } from '$lib/ae_utils/ae_utils'; + + interface Props { + exhibit_id: string; + on_lead_added?: (badge: ae_EventBadge) => void; + } + + let { exhibit_id, on_lead_added }: Props = $props(); + + let search_query = $state(''); + let results: ae_EventBadge[] = $state([]); + let searching = $state(false); + let adding_id = $state(''); + + async function handle_search() { + if (!search_query.trim()) return; + searching = true; + try { + const search_results = await events_func.search__event_badge({ + api_cfg: $ae_api, + event_id: page.params.event_id || '', + fulltext_search_qry_str: search_query, + limit: 20 + }); + results = Array.isArray(search_results) ? search_results : []; + } catch (e) { + console.error('Badge search failed', e); + } finally { + searching = false; + } + } + + async function add_as_lead(badge: ae_EventBadge) { + if (!badge.event_badge_id_random) return; + adding_id = badge.event_badge_id_random; + + // TODO: Get the actual signed-in licensed user's email + const user_email = 'placeholder@exhibitor.com'; + + try { + const result = await events_func.create_ae_obj__exhibit_tracking({ + api_cfg: $ae_api, + exhibit_id: exhibit_id, + event_badge_id: badge.event_badge_id_random, + external_person_id: user_email + }); + + if (result && on_lead_added) { + on_lead_added(badge); + } + } catch (e) { + console.error('Failed to add lead', e); + } finally { + adding_id = ''; + } + } - + \ No newline at end of file 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 index f6636815..8c83128a 100644 --- 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 @@ -1,11 +1,147 @@ -
-

Scan Badge

-

Placeholder for QR scanner component.

-
+
+ {#if scanning_status === 'idle' || scanning_status === 'scanning'} +
+ +
+
+

Point camera at the badge QR code

+ + {:else if scanning_status === 'found' || scanning_status === 'adding'} +
+
+

{found_badge?.full_name || 'Badge Found'}

+

{found_badge?.affiliations || ''}

+
+ + + + +
+ + {:else if scanning_status === 'success'} +
+ +
+

Lead Added!

+

{found_badge?.full_name}

+
+

Resetting scanner...

+
+ + {:else if scanning_status === 'error'} +
+ +

{error_msg}

+ +
+ {/if} +
\ No newline at end of file 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 index 9c654369..2f783259 100644 --- 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 @@ -1,18 +1,72 @@ -
-
- - +
+ +
+ +
- {#if show_qr} {:else} {/if} -
+ + +
+ {#if mode === 'qr'} + + {:else} + + {/if} +
+ + +
+ +
+
\ No newline at end of file diff --git a/src/routes/journals/[journal_id]/+layout.svelte b/src/routes/journals/[journal_id]/+layout.svelte index de91546d..c8c9a5c0 100644 --- a/src/routes/journals/[journal_id]/+layout.svelte +++ b/src/routes/journals/[journal_id]/+layout.svelte @@ -281,7 +281,7 @@ Middle-click to open in new tab`} ); } $journals_slct.journal_entry_id = - results?.journal_entry_id_random; + results?.journal_entry_id; // $journals_loc.entry.edit = true; $journals_loc.entry.edit_kv[ $journals_slct.journal_entry_id diff --git a/src/routes/journals/[journal_id]/+page.ts b/src/routes/journals/[journal_id]/+page.ts index 4c8935e0..d880be43 100644 --- a/src/routes/journals/[journal_id]/+page.ts +++ b/src/routes/journals/[journal_id]/+page.ts @@ -1,74 +1,8 @@ /** @type {import('./$types').PageLoad} */ -import { error } from '@sveltejs/kit'; -console.log(`ae_p_journals [journal_id] +page.ts start`); +// console.log(`ae_p_journals [journal_id] +page.ts start`); -import { browser } from '$app/environment'; -import { journals_func } from '$lib/ae_journals/ae_journals_functions'; +// import { browser } from '$app/environment'; +// import { journals_func } from '$lib/ae_journals/ae_journals_functions'; -export async function load({ params, parent }) { - // route - // let log_lvl: number = 1; - // let data = await parent(); - // data.log_lvl = log_lvl; - // let account_id = data.account_id; - // let ae_acct = data[account_id]; - // let journal_id = params.journal_id; - // if (!journal_id) { - // console.log(`ae_journals journals [journal_id] +page.ts: The journal_id was not found in the params!!!`); - // error(404, { - // message: 'Journals - Journal ID not found' - // }); - // } - // ae_acct.slct.journal_id = journal_id; - // console.log(`ae_journals journals [journal_id] +page.ts: journal_id = `, ae_acct.slct.journal_id); - // if (browser) { - // if (log_lvl) { - // console.log(`ae_journals journals [journal_id] +page.ts: journal_id = `, journal_id); - // } - // // Load event journal object - // let load_journal_obj = journals_func.load_ae_obj_id__journal({ - // api_cfg: ae_acct.api, - // journal_id: journal_id, - // inc_entry_li: true, - // try_cache: true, - // log_lvl: log_lvl - // }); - // ae_acct.slct.journal_obj = load_journal_obj; - // Load journal entries for the journal - // let load_journal_entry_obj_li = journals_func.load_ae_obj_li__journal_entry({ - // api_cfg: ae_acct.api, - // for_obj_type: 'journal', - // for_obj_id: journal_id, - // params: {qry__enabled: 'all', qry__limit: 99}, - // try_cache: true - // }) - // .then((journal_entry_obj_li) => { - // if (log_lvl) { - // console.log(`journal_entry_obj_li = `, journal_entry_obj_li); - // } - // for (let index = 0; index < journal_entry_obj_li.length; index++) { - // let journal_entry_obj = journal_entry_obj_li[index]; - // let journal_entry_id = journal_entry_obj.journal_entry_id_random; - // let load_journal_entry_obj_li = journals_func.load_ae_obj_li__journal_entry({ - // api_cfg: ae_acct.api, - // for_obj_type: 'journal_entry', - // for_obj_id: journal_entry_id, - // params: {qry__enabled: 'all', qry__limit: 15}, - // try_cache: true - // }); - // if (log_lvl) { - // console.log(`load_journal_entry_obj_li = `, load_journal_entry_obj_li); - // } - // journal_entry_obj_li[index].journal_entry_li = load_journal_entry_obj_li; - // } - // return journal_entry_obj_li; - // }); - // if (log_lvl) { - // console.log(`load_journal_entry_obj_li = `, load_journal_entry_obj_li); - // } - // ae_acct.slct.journal_entry_obj_li = load_journal_entry_obj_li; - // } - // WARNING: Precaution against shared data between sites. - // data[account_id] = ae_acct; - // return data; -} +// export async function load({ params, parent }) { +// }