Saving initial changes to the Leads v3 project files and directories.

This commit is contained in:
Scott Idem
2026-02-07 14:54:31 -05:00
parent ea661f5893
commit f8f65139a7
11 changed files with 217 additions and 33 deletions

View File

@@ -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.

View File

@@ -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" />

View File

@@ -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>

View File

@@ -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(() => {

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>