Saving initial changes to the Leads v3 project files and directories.
This commit is contained in:
175
documentation/Aether_Events_Exhibitor_Leads_v3.md
Normal file
175
documentation/Aether_Events_Exhibitor_Leads_v3.md
Normal file
@@ -0,0 +1,175 @@
|
||||
Aether Events Exhibitor Leads v3
|
||||
=======
|
||||
|
||||
|
||||
## Overview:
|
||||
* Mobile first!
|
||||
* Offline caching for spotty network connections
|
||||
* Clean and simple
|
||||
* Exhibitors have between 0 and X license
|
||||
* Sign in and "claim" an assigned license linked to an Exhibitor
|
||||
* Collect and manage Leads per Exhibitor
|
||||
* Export Leads data to CSV or XLSX
|
||||
* Exhibitors will have the link to their Exhibit sent to them or they can use the Exhibit Search to find their Exhibit and sign in with the shared passcode or their assigned licensed user credentials.
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Primary Leads Pages/Tabs
|
||||
1. start - Sign In / Licenses
|
||||
* Exhibit passcode sign in
|
||||
* Exhibitor Leads user sign in
|
||||
* Payment - Leads Payment
|
||||
2. add - Add (Search/QR)
|
||||
* Text search
|
||||
* QR scan
|
||||
3. leads - Leads List
|
||||
* List of Attendees (Event Badge ID) linked to Exhibit
|
||||
* Click to view/edit Exhibit Tracking entry
|
||||
4. manage - Leads (app and exhibit) Manage
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Exhibitor Sign In and Licenses:
|
||||
* An Exhibit needs to have a "Shared exhibit passcode" for primary sign in.
|
||||
* A Licensed Exhibit staff person needs to have a License assigned to them. It is important that the email address not be changed per license. Or if they do, be aware of the affects on tracked attendees. They can then sign in with that email address and passcode assigned. And/Or we need to make the email sign in link work as well.
|
||||
* Once signed in with the Exhibit passcode, they can change it (passcode) and assign Exhibit Leads licenses once they have paid.
|
||||
* An exhibitor marked as "priority" means they have paid. Only OSIT admins can mark them as paid.
|
||||
* Should at least claim/assign one initial user license so an Exhibit related staff can sign in (without the shared Exhibit passcode). Then add, update, or remove licenses based on max allowed per Exhitibor.
|
||||
|
||||
Payment:
|
||||
* Not Paid: <span class="fas fa-question text-red-500 m-1"></span> <span class="fas fa-credit-card mx-1"></span> Waiting for payment
|
||||
* Paid: <span class="fas fa-check text-green-500 m-1"></span> <span class="fas fa-credit-card mx-1"></span> Marked as paid
|
||||
|
||||
### Licenses:
|
||||
A client staff (Trusted Access or above) or someone signed in with an Exhibit passcode can add/edit/remove icenses.
|
||||
|
||||
License:
|
||||
* full_name
|
||||
* email
|
||||
* passcode
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Structure Overview
|
||||
* There are 4 primary tabs for the Event Exhibit Leads module.
|
||||
* The overall header/footer should hide by default once signed in.
|
||||
* If not signed in, only the first tab, to start and sign in is shown.
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
## [tab 1] Start / Sign In / Payment
|
||||
* Only shows when not signed in as Exhibit Licensed Leads User
|
||||
* Sign in with Exhibit passcode
|
||||
* This will then allow them to Manage the Exhibit License
|
||||
* Sign in with Exhibit Licensed user
|
||||
|
||||
### [tab section] Payment
|
||||
* Shows if signed in with Exhibit Passcode and not marked as paid / priority
|
||||
* Field for license info - event_exhibit.license_li_json
|
||||
* Use Exhibit passcode first?
|
||||
* Select number of licenses
|
||||
* Select number of small/large devices
|
||||
* Make payment (Stripe)
|
||||
|
||||
### [tab section] Licensed Users
|
||||
* Shows if signed in with Exhibit Passcode and are marked as paid / priority
|
||||
* Fill in Exhibit staff users per max licenses
|
||||
|
||||
|
||||
## [tab 2] Add - Search / QR Scan
|
||||
* One button that toggles between showing and hiding the QR add mode? The text search is always shown above or below the QR scan camera image area.
|
||||
* Search - Essentially the same as the Badge search. Full name, email, affiliations, ID, etc
|
||||
* QR Scan - Allow for auto add toggle instead of confirming per scan. Allow for manual entire of Badge ID
|
||||
* The QR scan is basically just using the Event Badge ID encoded in the persons QR code on their badge. In fact it can probably just populate the text search field?
|
||||
* Must include the Leads licensed user's email address when adding to the Exhibitor Tracking list. Linked using event_exhibit_tracking with the Licensed user's email address.
|
||||
|
||||
|
||||
## [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
|
||||
* 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 / Config
|
||||
### Exhibit Specific
|
||||
* Priorty/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"?
|
||||
|
||||
|
||||
## [page] View / Edit Tracked Attendee (event_exhbit_id, event_badge_id
|
||||
* Able to edit the obvious things
|
||||
* Can add custom exhibitor_notes and custom responses_json
|
||||
* Can set Priority (Star/Flag)
|
||||
* Can set Sort (number)
|
||||
* Can set Hide (toggle)
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
## App Specific - Leads Module Config Options and Stored Data
|
||||
* Signed in using Shared Exhibit Passcode
|
||||
* Can make payment and manage user licenses
|
||||
* Need to be able to sign in with Shared Passcode and Leads License passcode.
|
||||
* Signed in using one Leads License slot they were assigned to
|
||||
* Things will be tied to their email address
|
||||
* Can add, edit, remove, etc the Leads specific to an Exhibit.
|
||||
* This list is shared among all Licensed staff for a specific Exhibit. It is recommended they stay toggled to only showing their additions by default.
|
||||
* Need to be able to sign in with Shared Passcode and Leads License passcode.
|
||||
* Show and Hide payment tab (override sort of)
|
||||
* Show and Hide header/footer (override sort of)
|
||||
* Use iframe mode??
|
||||
* Light and Dark mode??
|
||||
* List refresh interval
|
||||
* Tracking list sorting
|
||||
* Show and hide hidden records
|
||||
* Show and hide enabled records (Administrator Access or above)
|
||||
|
||||
There are essentially 3 ways to "Sign In":
|
||||
1. Aether Platform in general using a defined Core User or using a Client Site specific passcode.
|
||||
2. Exhibit shared passcode for general Exhibitor management. Should only be briefly needed to pay and assign license slots
|
||||
3. Licensed Leads User using the email address and passcode assigned by the Exhibitor manager or OSIT/client staff person.
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
## First Steps for Leads v3
|
||||
Create stub directories, pages, and other supporting files for each primary tab, and each primary section within each tab, and for viewing a specific Lead (Exhibit Tracking).
|
||||
|
||||
Once we have that looking good, then we can add the functionality. So we are kind of laying out the framework and pre-documenting (commenting) the files as we go.
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
## For Reference if Needed
|
||||
Known goodish reference version of Leads v2ish:
|
||||
d1021e28227db6d49b16cc48e324872b1a322da3 November 19, 2025 at 10:45 PM
|
||||
Hopefully that is not needed...
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
## Overview
|
||||
|
||||
This is a major work in progress. It still needs the overall directory and file structure planned, documented, and created.
|
||||
|
||||
This module provides a comprehensive solution for event exhibitors to capture and manage leads. It supports exhibitor login, badge scanning for lead capture, manual lead entry, lead qualification (ranking/priority), and data export functionalities. It integrates with the Aether API for data synchronization and utilizes local caching for performance and offline capabilities.
|
||||
|
||||
## Key Features
|
||||
@@ -22,7 +24,7 @@ The core data structures managed by this module are `Exhibit` and `Exhibit_track
|
||||
|
||||
Represents an exhibitor's presence at an event. It contains configuration for lead retrieval, such as:
|
||||
|
||||
- `event_exhibit_id_random`: Unique identifier for the exhibit (primary key).
|
||||
- `event_exhibit_id`: Unique identifier for the exhibit (primary key).
|
||||
- `code`, `name`, `description`: Basic information about the exhibit.
|
||||
- `staff_passcode`: For staff login.
|
||||
- `leads_api_access`: Boolean indicating if lead retrieval API access is enabled.
|
||||
@@ -33,9 +35,9 @@ Represents an exhibitor's presence at an event. It contains configuration for le
|
||||
|
||||
Represents a single lead captured by an exhibitor. It links an exhibitor to an attendee (badge) and stores details about the interaction:
|
||||
|
||||
- `event_exhibit_tracking_id_random`: Unique identifier for the captured lead (primary key).
|
||||
- `event_exhibit_id_random`: Links to the `Exhibit` that captured the lead.
|
||||
- `event_badge_id_random`: Links to the attendee's `Badge` information.
|
||||
- `event_exhibit_tracking_id`: Unique identifier for the captured lead (primary key).
|
||||
- `event_exhibit_id`: Links to the `Exhibit` that captured the lead.
|
||||
- `event_badge_id`: Links to the attendee's `Badge` information.
|
||||
- `exhibitor_notes`: Notes added by the exhibitor about the lead.
|
||||
- `responses_json`: Responses to custom questions.
|
||||
- Contains duplicated badge information for convenience (e.g., `event_badge_full_name`, `event_badge_email`, `event_badge_professional_title`, `event_badge_affiliations`) to denormalize data for faster display.
|
||||
@@ -48,7 +50,7 @@ Represents a single lead captured by an exhibitor. It links an exhibitor to an a
|
||||
- `+page.svelte`: Renders the list of exhibits.
|
||||
- `+page.ts`: Loads the data for available exhibits using `events_func.handle_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_random`.
|
||||
- `/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`.
|
||||
|
||||
@@ -63,7 +65,7 @@ Represents a single lead captured by an exhibitor. It links an exhibitor to an a
|
||||
## Technical Implementation
|
||||
|
||||
- **Svelte v5 with Runes:** The module adheres to Svelte v5's reactivity model, utilizing `$state` and `$derived` for reactive state management.
|
||||
- **ID Convention (`id_random`):** All frontend operations, including routing and data fetching, consistently use the string-based `event_exhibit_id_random` and `event_exhibit_tracking_id_random` as primary identifiers, rather than numeric `id` values.
|
||||
- **ID Convention (`id`):** All frontend operations, including routing and data fetching, consistently use the string-based `event_exhibit_id` and `event_exhibit_tracking_id` as primary identifiers, rather than numeric `id` values.
|
||||
- **API Interaction:** Data is fetched and synchronized with the backend FastAPI application through functions exposed in `src/lib/ae_events_functions.ts`.
|
||||
- **Local Database (Dexie.js):** Data for `Exhibit` and `Exhibit_tracking` is cached in the browser's IndexedDB using Dexie.js, defined in `src/lib/ae_events/db_events.ts`. This ensures data persistence and fast retrieval, especially for offline scenarios.
|
||||
- **Styling:** The UI is primarily styled using Tailwind CSS, having migrated from Skeleton UI to resolve previous rendering issues.
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
// SCENARIO 2: Fallback broad search
|
||||
if (event_id && !$events_loc.leads.qry__search_text) {
|
||||
return await db_events.exhibit
|
||||
.where('event_id_random')
|
||||
.where('event_id')
|
||||
.equals(event_id)
|
||||
.sortBy('name');
|
||||
}
|
||||
@@ -98,7 +98,7 @@
|
||||
if (!remote_first) {
|
||||
try {
|
||||
let local_results = await db_events.exhibit
|
||||
.where('event_id_random')
|
||||
.where('event_id')
|
||||
.equals(event_id)
|
||||
.filter((exhibit) => {
|
||||
if (qry_str) {
|
||||
@@ -135,7 +135,7 @@
|
||||
});
|
||||
|
||||
const local_ids = local_results
|
||||
.map((e) => String(e.id || e.event_exhibit_id_random))
|
||||
.map((e) => String(e.event_exhibit_id))
|
||||
.filter(Boolean);
|
||||
if (current_search_id === last_search_id) {
|
||||
untrack(() => {
|
||||
@@ -180,7 +180,7 @@
|
||||
|
||||
if (current_search_id === last_search_id) {
|
||||
const api_ids = results
|
||||
.map((e: any) => String(e.id || e.event_exhibit_id_random))
|
||||
.map((e: any) => String(e.event_exhibit_id))
|
||||
.filter(Boolean);
|
||||
untrack(() => {
|
||||
exhibit_id_li = api_ids;
|
||||
@@ -219,7 +219,7 @@
|
||||
{#each $lq__event_exhibit_obj_li as exhibit_obj}
|
||||
<a
|
||||
href="/events/{page.params
|
||||
.event_id}/leads/exhibit/{exhibit_obj.id_random}"
|
||||
.event_id}/leads/exhibit/{exhibit_obj.event_exhibit_id}"
|
||||
class="card card-hover p-4 flex flex-col items-center justify-center text-center space-y-2 preset-tonal"
|
||||
>
|
||||
<Store size="2em" />
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
<script lang="ts">
|
||||
// Page for viewing/editing a single lead
|
||||
</script>
|
||||
|
||||
<h1 class="h1">Lead Details</h1>
|
||||
|
||||
<p>This page will show the details for a single lead.</p>
|
||||
@@ -47,7 +47,7 @@
|
||||
|
||||
if (exhibit_id && !$events_loc.leads.tracking__qry__search_text) {
|
||||
return await db_events.exhibit_tracking
|
||||
.where('event_exhibit_id_random')
|
||||
.where('event_exhibit_id')
|
||||
.equals(exhibit_id)
|
||||
.reverse()
|
||||
.sortBy('created_on');
|
||||
@@ -109,7 +109,7 @@
|
||||
if (!remote_first) {
|
||||
try {
|
||||
let local_results = await db_events.exhibit_tracking
|
||||
.where('event_exhibit_id_random')
|
||||
.where('event_exhibit_id')
|
||||
.equals(exhibit_id)
|
||||
.filter((tracking) => {
|
||||
if (qry_str) {
|
||||
@@ -163,7 +163,7 @@
|
||||
|
||||
const local_ids = local_results
|
||||
.map((e) =>
|
||||
String(e.id || e.event_exhibit_tracking_id_random)
|
||||
String(e.event_exhibit_tracking_id)
|
||||
)
|
||||
.filter(Boolean);
|
||||
if (current_search_id === last_search_id) {
|
||||
@@ -207,7 +207,7 @@
|
||||
if (current_search_id === last_search_id) {
|
||||
const api_ids = results
|
||||
.map((e: any) =>
|
||||
String(e.id || e.event_exhibit_tracking_id_random)
|
||||
String(e.event_exhibit_tracking_id)
|
||||
)
|
||||
.filter(Boolean);
|
||||
untrack(() => {
|
||||
|
||||
@@ -45,17 +45,17 @@
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 gap-4">
|
||||
{#each $lq__event_exhibit_tracking_obj_li as tracking_obj}
|
||||
{#each $lq__event_exhibit_tracking_obj_li as event_tracking_obj}
|
||||
<a
|
||||
href={`/events/${page.params.event_id}/leads/exhibit/${tracking_obj.event_exhibit_id_random}/lead/${tracking_obj.id_random || tracking_obj.event_exhibit_tracking_id_random}`}
|
||||
href={`/events/${page.params.event_id}/leads/exhibit/${event_tracking_obj.event_exhibit_id}/lead/${event_tracking_obj.event_exhibit_tracking_id}`}
|
||||
class="card card-hover p-4 variant-filled-surface border-l-4 border-primary-500 flex flex-col md:flex-row gap-4 items-start md:items-center"
|
||||
>
|
||||
<div class="flex-grow space-y-1">
|
||||
<div class="flex items-center gap-2">
|
||||
<User size="1.25em" class="text-primary-500" />
|
||||
<span class="text-xl font-bold">
|
||||
{tracking_obj.event_badge_full_name ||
|
||||
tracking_obj.event_badge_full_name_override ||
|
||||
{event_tracking_obj.event_badge_full_name ||
|
||||
event_tracking_obj.event_badge_full_name_override ||
|
||||
'Unknown Attendee'}
|
||||
</span>
|
||||
</div>
|
||||
@@ -63,31 +63,31 @@
|
||||
<div
|
||||
class="flex flex-wrap gap-x-4 gap-y-1 text-sm opacity-70"
|
||||
>
|
||||
{#if tracking_obj.event_badge_email}
|
||||
{#if event_tracking_obj.event_badge_email}
|
||||
<div class="flex items-center gap-1">
|
||||
<Mail size="1em" />
|
||||
{tracking_obj.event_badge_email}
|
||||
{event_tracking_obj.event_badge_email}
|
||||
</div>
|
||||
{/if}
|
||||
{#if tracking_obj.event_badge_affiliations}
|
||||
{#if event_tracking_obj.event_badge_affiliations}
|
||||
<div class="flex items-center gap-1">
|
||||
<MapPin size="1em" />
|
||||
{tracking_obj.event_badge_affiliations}
|
||||
{event_tracking_obj.event_badge_affiliations}
|
||||
</div>
|
||||
{/if}
|
||||
<div class="flex items-center gap-1">
|
||||
<Clock size="1em" />
|
||||
{format_date(tracking_obj.created_on)}
|
||||
{format_date(event_tracking_obj.created_on)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if tracking_obj.exhibitor_notes}
|
||||
{#if event_tracking_obj.exhibitor_notes}
|
||||
<div
|
||||
class="mt-2 p-2 bg-surface-100-900 rounded text-sm italic border-l-2 border-surface-300-700"
|
||||
>
|
||||
<FileText size="1em" class="inline mr-1" />
|
||||
{ae_util.shorten_string({
|
||||
string: tracking_obj.exhibitor_notes,
|
||||
string: event_tracking_obj.exhibitor_notes,
|
||||
max_length: 100
|
||||
})}
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
<script lang="ts">
|
||||
// Page for viewing/editing a single lead
|
||||
</script>
|
||||
|
||||
<h1 class="h1">Lead Details</h1>
|
||||
|
||||
<p>This page will show the details for a single lead. A Lead is actually in the event_exhibit_tracking table in the MariaDB or exhibit_tracking table in the Indexed DB ae_events_db.</p>
|
||||
@@ -0,0 +1,7 @@
|
||||
<script lang="ts">
|
||||
// Page for viewing/editing a single lead
|
||||
</script>
|
||||
|
||||
<h1 class="h1">Lead Details</h1>
|
||||
|
||||
<p>This page will show the details for a single lead. A Lead is actually in the event_exhibit_tracking table in the MariaDB or exhibit_tracking table in the Indexed DB ae_events_db.</p>
|
||||
Reference in New Issue
Block a user