docs(events): reorganize badges and leads documentation
Standardized documentation structure for Badges and Leads modules into focused technical references and practical onsite guides. - Refined MODULE__AE_Events_Badges.md (Core data integrity & sync logic) - Renamed MODULE__AE_Events_Exhibitor_Leads.md to MODULE__AE_Events_Leads.md - Renamed MODULE__AE_Events_Badges_Onsite.md to GUIDE__AE_Events_Badges_Onsite.md - Expanded GUIDE__AE_Events_Onsite_Runbook.md with Badge and Leads sections. - Maintained all critical business logic, including the 'Override Fields' pattern. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,11 +1,50 @@
|
|||||||
# Guide — Aether Events: Onsite Runbook
|
# Guide — Aether Events: Onsite Runbook
|
||||||
|
|
||||||
This guide covers the human-centric logistics and "In the Heat of the Moment" support for onsite event operations using Presentation Management and the Launcher.
|
This guide covers the human-centric logistics and "In the Heat of the Moment" support for onsite event operations.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Badge Printing
|
||||||
|
|
||||||
|
Aether badge printing uses the browser's native `window.print()` — no special software or print
|
||||||
|
server needed.
|
||||||
|
|
||||||
|
### Kiosk Station Setup
|
||||||
|
- **Browser:** Use **Chrome (Chromium)** for all kiosk stations.
|
||||||
|
- **Settings:** Set Margins to **None**. Enable **Background Graphics**.
|
||||||
|
- **Mode:** Use normal browser sessions (not Incognito) to allow PWA caching.
|
||||||
|
|
||||||
|
### Printer Reference: Zebra ZC10L (PVC)
|
||||||
|
- **Stock:** 3.5" × 5.5" PVC cards.
|
||||||
|
- **Orientation:** Cards face-up, landscape in the hopper.
|
||||||
|
- **Single-Sided:** Only the front face prints; the back section is hidden via CSS.
|
||||||
|
|
||||||
|
### Printing Workflow
|
||||||
|
1. **Search:** Find the attendee by name or QR scan in the Badges module.
|
||||||
|
2. **Review:** Open the print page and confirm the layout looks correct.
|
||||||
|
3. **Print:** Click **Print Badge**. `print_count` increments automatically.
|
||||||
|
4. **Handoff:** Verify the card print quality before handing it to the attendee.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Exhibitor Leads (Lead Retrieval)
|
||||||
|
|
||||||
|
Exhibitors use a PWA (Progressive Web App) to scan badges and capture leads.
|
||||||
|
|
||||||
|
### Exhibitor Support Workflow
|
||||||
|
1. **Booth Lookup:** Help the exhibitor find their booth in the Leads landing page.
|
||||||
|
2. **Sign-In:** Assist with the **Shared Passcode** or individual **Licensed User** login.
|
||||||
|
3. **App Install:** Encourage them to "Add to Home Screen" (iOS) or click the Install button (Android/Chrome) for offline stability.
|
||||||
|
4. **Scanning Demo:** Show them the **Rapid Scan** mode. Remind them that attendees must have `allow_tracking = true` on their record to be scanned.
|
||||||
|
|
||||||
|
### Managing Licenses
|
||||||
|
- License counts are managed in the **Manage** tab (Admin or Shared Passcode only).
|
||||||
|
- If an exhibitor needs more staff slots, update the `license_max` in the Exhibit record.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Speaker Ready Room (SRR)
|
## Speaker Ready Room (SRR)
|
||||||
|
... (rest of the file) ...
|
||||||
The SRR is the central hub for content management and presenter support.
|
The SRR is the central hub for content management and presenter support.
|
||||||
|
|
||||||
### SRR Practice Stations
|
### SRR Practice Stations
|
||||||
|
|||||||
@@ -1,843 +1,106 @@
|
|||||||
# MODULE: Aether Events — Badges
|
# Aether Events — Badges
|
||||||
|
|
||||||
**Module Path:** `src/routes/events/[event_id]/(badges)/badges/`
|
The Badges module manages event attendee records and their physical badge configurations. It supports multi-source imports, field protection for onsite edits, and multi-tier access control for self-service review.
|
||||||
**API Module:** `src/lib/ae_events/ae_events__event_badge.ts`
|
|
||||||
**Database:** `db_events.badge` (Dexie IndexedDB table)
|
|
||||||
**Last Updated:** 2026-02-27 (rev 6)
|
|
||||||
**Related Docs:** `documentation/PROJECT__AE_Events_Badges_Review_Print.md` (implementation guide)
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Overview
|
## Data Model & Hierarchy
|
||||||
|
|
||||||
The Badges module manages event attendee badges with support for:
|
### Core Objects
|
||||||
- **External system imports as needed** (CSV/Excel, iMIS, Zoom, Novi, Impexium, Confex, Cvent, and others)
|
- **Event Badge** (`event_badge`): The attendee record containing name, title, affiliations, and tracking flags.
|
||||||
- **Field override protection** to prevent staff/attendee edits from being overwritten by automated syncs
|
- **Badge Template** (`event_badge_template`): The visual and structural configuration for printing (branding, layout, QR placement).
|
||||||
- **Multi-tier access control** for field editing
|
|
||||||
- **QR code generation** for badge scanning
|
### Relationships
|
||||||
- **Print tracking** (count, first/last print datetime)
|
- **Badge → Event:** Many-to-one.
|
||||||
- **Advanced search and filtering**
|
- **Badge → Template:** Many-to-one (via `event_badge_template_id`).
|
||||||
- **HTML rendering** in display fields for rich text formatting
|
- **Badge → Person:** Optional link to core Aether Person record for unified profiles.
|
||||||
- **Accessibility features** (text enlargement toggle)
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Critical Design Pattern: Override Fields
|
## Critical Design Pattern: Override Fields
|
||||||
|
|
||||||
### Purpose
|
### Purpose
|
||||||
The `*_override` fields pattern protects data from being overwritten during scheduled cron syncs from external systems. This is essential because:
|
The `*_override` fields pattern (established in 2018) protects data from being overwritten during scheduled cron syncs from external systems (iMIS, Novi, etc.). This ensures that staff corrections or attendee self-updates persist across multiple sync cycles.
|
||||||
1. Staff may need to correct imported data
|
|
||||||
2. Attendees may be allowed to self-update certain fields (e.g., preferred name, pronouns)
|
|
||||||
3. External systems often have outdated or incorrect data
|
|
||||||
4. Changes should persist across multiple sync cycles
|
|
||||||
|
|
||||||
### How It Works
|
### How It Works
|
||||||
|
1. **Import:** External systems populate **REGULAR** fields only.
|
||||||
|
2. **Display Logic:** The UI displays the `*_override` field if it has a value; otherwise, it falls back to the regular field.
|
||||||
|
3. **HTML Rendering:** Certain display fields (Name, Title, Affiliations, Location) support HTML markup for rich text formatting (bold, italics, line breaks) on the physical badge.
|
||||||
|
|
||||||
**Import Behavior:**
|
### Standard Override Pairs
|
||||||
```
|
|
||||||
External System → Aether API → Populates REGULAR fields only
|
|
||||||
(never touches *_override fields)
|
|
||||||
```
|
|
||||||
|
|
||||||
**Display Behavior:**
|
| Regular Field | Override Field | Editable By | HTML? |
|
||||||
```
|
|---|---|---|---|
|
||||||
UI Display Logic:
|
| `full_name` | `full_name_override` | Staff, Attendee | ✅ |
|
||||||
1. IF `*_override` field has value → USE IT (highest priority)
|
| `professional_title` | `professional_title_override` | Staff, Attendee | ✅ |
|
||||||
2. ELSE IF regular field has value → USE IT (fallback)
|
| `affiliations` | `affiliations_override` | Staff, Attendee | ✅ |
|
||||||
3. ELSE → Display placeholder/empty
|
| `location` | `location_override` | Staff, Attendee | ✅ |
|
||||||
```
|
| `email` | `email_override` | Staff Only | No |
|
||||||
|
| `badge_type` | `badge_type_override` | Staff Only | No |
|
||||||
**HTML Rendering (implemented 2026-02-27):**
|
|
||||||
|
|
||||||
Certain fields support HTML markup for rich text formatting. When viewing (not editing), these fields use Svelte's `{@html}` directive to render the markup:
|
|
||||||
- `full_name` / `full_name_override`
|
|
||||||
- `professional_title` / `professional_title_override`
|
|
||||||
- `affiliations` / `affiliations_override`
|
|
||||||
- `location` / `location_override`
|
|
||||||
|
|
||||||
This allows for formatting like:
|
|
||||||
- Bold/italic: `<b>Dr.</b> Jane Smith` or `<i>Chief Medical Officer</i>`
|
|
||||||
- Line breaks: `Hospital Name<br>Department Name`
|
|
||||||
- Special characters and entities
|
|
||||||
|
|
||||||
**Example — Full Name:**
|
|
||||||
```typescript
|
|
||||||
// API imports from iMIS
|
|
||||||
badge.given_name = "Robert"
|
|
||||||
badge.family_name = "Smith"
|
|
||||||
badge.full_name = "Robert Smith" // Auto-computed
|
|
||||||
|
|
||||||
// Staff edits to preferred name with HTML
|
|
||||||
badge.full_name_override = "<b>Bob</b> Smith"
|
|
||||||
|
|
||||||
// Display in UI (review form)
|
|
||||||
{@html badge.full_name_override || badge.full_name || "— no name —"}
|
|
||||||
// Result: **Bob** Smith (bold rendered)
|
|
||||||
|
|
||||||
// Edit mode shows raw HTML
|
|
||||||
<input bind:value={editable_full_name_override} />
|
|
||||||
// Shows: <b>Bob</b> Smith (editable as text)
|
|
||||||
|
|
||||||
// Next cron sync from iMIS
|
|
||||||
// ✅ badge.full_name updated to "Robert J. Smith" (middle initial added)
|
|
||||||
// ✅ badge.full_name_override remains "<b>Bob</b> Smith" (PROTECTED)
|
|
||||||
// ✅ Display still shows **Bob** Smith (bold rendered)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Override Fields
|
|
||||||
|
|
||||||
| Regular Field | Override Field | Purpose | Editable By | HTML Rendering |
|
|
||||||
|---|---|---|---|---|
|
|
||||||
| `pronouns` | `pronouns_override` | Preferred pronouns | Staff, Attendee | No |
|
|
||||||
| `professional_title` | `professional_title_override` | Job title display | Staff, Attendee | ✅ Yes |
|
|
||||||
| `full_name` | `full_name_override` | Preferred name display | Staff, Attendee | ✅ Yes |
|
|
||||||
| `affiliations` | `affiliations_override` | Organization display | Staff, Attendee | ✅ Yes |
|
|
||||||
| `phone` | `phone_override` | Phone number | Staff, Attendee | No |
|
|
||||||
| `email` | `email_override` | Contact email override | Staff only | No |
|
|
||||||
| `location` | `location_override` | City/State/Country display | Staff, Attendee | ✅ Yes |
|
|
||||||
| `badge_type` | `badge_type_override` | Badge category label text | Staff only | No |
|
|
||||||
| `badge_type_code` | `badge_type_code_override` | Badge access level code | Staff only | No |
|
|
||||||
| `registration_type` | `registration_type_override` | Registration category label text | Staff only | No |
|
|
||||||
| `registration_type_code` | `registration_type_code_override` | Registration category code | Staff only | No |
|
|
||||||
|
|
||||||
> **Note:** `phone`, `phone_override`, `pronouns_override`, `registration_type`, `registration_type_code`, `registration_type_override`, `registration_type_code_override` may need to be confirmed against the DB schema via `ae_describe event_badge` and added to `properties_to_save` in `ae_events__event_badge.ts` if not already present.
|
|
||||||
|
|
||||||
### Sync Safety Rules
|
|
||||||
|
|
||||||
**Automated Sync (Cron Jobs):**
|
|
||||||
- ✅ CAN update: All regular fields (`given_name`, `family_name`, `email`, `affiliations`, etc.)
|
|
||||||
- ❌ CANNOT update: Any `*_override` field
|
|
||||||
- ❌ CANNOT delete: Any `*_override` value
|
|
||||||
|
|
||||||
**Manual Staff Edit:**
|
|
||||||
- ✅ CAN update: Any field (including overrides)
|
|
||||||
- ✅ CAN clear: Override fields (reverts to regular field)
|
|
||||||
|
|
||||||
**Attendee Self-Service Edit:**
|
|
||||||
- ✅ CAN update: Only specific override fields (per event config)
|
|
||||||
- ✅ CAN clear: Their own override fields
|
|
||||||
- ❌ CANNOT edit: Regular fields, badge_type, email_override
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## External System Integration
|
## External System Integration
|
||||||
|
|
||||||
### Supported Import Sources
|
Aether acts as a **Pull-Only** consumer for registration data. It does not push changes back to external systems, maintaining them as the source of truth for base registration while Aether handles the "Onsite Truth."
|
||||||
- **iMIS** (Association Management)
|
|
||||||
- **Zoom** (Virtual event registration)
|
|
||||||
- **Novi AMS** (Association Management)
|
|
||||||
- **Impexium** (Association Management)
|
|
||||||
- **Confex** (Event abstract management)
|
|
||||||
- **Cvent** (Event registration)
|
|
||||||
- **Custom CSV/Excel** imports
|
|
||||||
|
|
||||||
### Data Flow Direction
|
### Supported Sources
|
||||||
```
|
- **iMIS**, **Novi AMS**, **Impexium** (Associations)
|
||||||
External Systems ─────────> Aether
|
- **Zoom**, **Cvent** (Registrations)
|
||||||
(READ ONLY) (WRITE + DISPLAY)
|
- **Confex** (Abstracts/Presenters)
|
||||||
```
|
- **Custom CSV/Excel**
|
||||||
|
|
||||||
**Important:** Aether is **pull-only** — does not push changes back to external systems. This prevents sync conflicts and maintains external systems as the source of truth for base data.
|
|
||||||
|
|
||||||
### Sync Behavior
|
|
||||||
- **Frequency:** Scheduled cron jobs (typically hourly, daily, or on-demand)
|
|
||||||
- **Method:** Full sync or incremental (depends on external system API)
|
|
||||||
- **Conflict Resolution:** Override fields always win
|
|
||||||
|
|
||||||
**Pseudocode:**
|
|
||||||
```python
|
|
||||||
def sync_badge_from_external(external_badge_data, existing_badge):
|
|
||||||
# Update regular fields from external source
|
|
||||||
existing_badge.given_name = external_badge_data.first_name
|
|
||||||
existing_badge.family_name = external_badge_data.last_name
|
|
||||||
existing_badge.email = external_badge_data.email
|
|
||||||
existing_badge.affiliations = external_badge_data.organization
|
|
||||||
existing_badge.badge_type_code = external_badge_data.registration_type
|
|
||||||
|
|
||||||
# NEVER TOUCH OVERRIDE FIELDS
|
|
||||||
# existing_badge.full_name_override ← PROTECTED
|
|
||||||
# existing_badge.affiliations_override ← PROTECTED
|
|
||||||
# existing_badge.email_override ← PROTECTED
|
|
||||||
|
|
||||||
return existing_badge
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Access Control & Edit Permissions
|
## Access Control & Permissions
|
||||||
|
|
||||||
### Access Levels (Ascending)
|
| Level | Access |
|
||||||
1. **Anonymous** — No access to badges
|
|---|---|
|
||||||
2. **Public** — View public event info only (no badge access)
|
| **Authenticated** | View own badge, limited self-edit (overrides only). |
|
||||||
3. **Authenticated** — View own badge, limited self-edit
|
| **Trusted** | Search all badges, view all, reprint existing badges. |
|
||||||
4. **Trusted** — Search all badges, view all, edit own
|
| **Administrator** | Full CRUD, bulk operations, override any field. |
|
||||||
5. **Administrator** — Full CRUD, bulk operations, override any field
|
| **Manager** | All Admin + Event/Template configuration. |
|
||||||
6. **Manager** — All administrator + event configuration
|
|
||||||
7. **Super** — All manager + cross-event operations
|
|
||||||
|
|
||||||
### Current Implementation (v3) — 2026-02-27
|
### Attendee Self-Service (`/review`)
|
||||||
|
Attendees can access their own record via a passcode-gated link (typically `?passcode=...`). This allows them to verify their info and provide preferred name/title overrides before printing.
|
||||||
#### Badge Search Results Visibility
|
|
||||||
|
|
||||||
| Access Level | Sees |
|
|
||||||
| --- | --- |
|
|
||||||
| Below Trusted (incl. anonymous) | Only badges where `print_count < 1` and not hidden |
|
|
||||||
| Trusted, not Edit Mode | Only badges where `print_count < 1` and not hidden |
|
|
||||||
| Trusted + Edit Mode | All badges where `hide === false` (including already-printed) |
|
|
||||||
|
|
||||||
#### Print Button Behavior (per result row)
|
|
||||||
|
|
||||||
| Access Level | Print Action |
|
|
||||||
| --- | --- |
|
|
||||||
| Below Trusted | No print action — name shown with User icon, non-interactive |
|
|
||||||
| Trusted, `print_count < 1` | Clickable link → `/print` page, Printer icon |
|
|
||||||
| Trusted, `print_count >= 1`, not Edit Mode | Disabled (already printed safety lock), shows `Nx` count |
|
|
||||||
| Trusted, `print_count >= 1`, Edit Mode | Clickable reprint — shows `Nx` count badge next to icon |
|
|
||||||
|
|
||||||
Print count displayed as `[Printer][2×] Name` when `print_count >= 1`.
|
|
||||||
|
|
||||||
#### Review Area Buttons (per result row, up to 3 buttons total)
|
|
||||||
|
|
||||||
| Button | Visible To | Behavior |
|
|
||||||
| --- | --- | --- |
|
|
||||||
| Email Review Link | All users | Placeholder `alert()` — will trigger email API |
|
|
||||||
| Review Link (clipboard) | Trusted + Edit Mode only | Copies `/review` URL to clipboard; shows `Copied!` feedback |
|
|
||||||
| *(direct Review link)* | *(future)* | *(not yet implemented as separate nav button)* |
|
|
||||||
|
|
||||||
#### Badge Edit Form (`ae_comp__badge_obj_view.svelte`)
|
|
||||||
|
|
||||||
**Currently editable fields (local `edit_mode_active`, not global `edit_mode`):**
|
|
||||||
```typescript
|
|
||||||
editable_full_name_override: string | null
|
|
||||||
editable_professional_title_override: string | null
|
|
||||||
editable_affiliations_override: string | null // textarea
|
|
||||||
editable_location_override: string | null
|
|
||||||
editable_allow_tracking: boolean | null
|
|
||||||
editable_email: string | null
|
|
||||||
editable_badge_type_code: string | null
|
|
||||||
```
|
|
||||||
|
|
||||||
- Save button → `handle_save_changes()` — only changed fields sent to API
|
|
||||||
- Cancel button → `handle_cancel_changes()` — reverts to IDB values
|
|
||||||
- **IMPORTANT:** This component must NEVER write to `$ae_loc.edit_mode` — it uses its own local `edit_mode_active` flag only. (Bug fixed 2026-02-27)
|
|
||||||
|
|
||||||
#### Badge Review Form (`ae_comp__badge_review_form.svelte`)
|
|
||||||
|
|
||||||
Form-based review (NOT a badge render). Used by the `/review` page.
|
|
||||||
|
|
||||||
**Props:**
|
|
||||||
- `can_edit_fields: string[]` prop controls which fields are editable per user level
|
|
||||||
- `['*']` = administrator (all fields)
|
|
||||||
- `is_staff: boolean` prop shows/hides the staff-only fields
|
|
||||||
- Fields show "(overridden)" label when an override value differs from the base field
|
|
||||||
|
|
||||||
**Features (implemented 2026-02-27):**
|
|
||||||
- **HTML Rendering**: `full_name_override`, `professional_title_override`, `affiliations_override`, and `location_override` fields render HTML markup using `{@html}` directive when viewing (not when editing)
|
|
||||||
- **Accessibility**: Text enlargement toggle button switches between text-2xl (normal) and text-4xl (enlarged) for improved readability
|
|
||||||
- **Help Modal**: Flowbite Modal component with 6 help sections (Reviewing Badge, Editing Info, Accessibility, QR Code, Lead Scanning, Assistance)
|
|
||||||
- **QR Code Display**: Generates QR code using `core_func.js_generate_qr_code()` with badge ID, supports hover zoom and click-to-expand
|
|
||||||
- **Print Status**: Shows print count, first print datetime, and last print datetime at top of form
|
|
||||||
- **Local Edit Mode**: Independent `local_edit_active` state (never writes to `$ae_loc.edit_mode`)
|
|
||||||
- **Save/Cancel**: Only changed fields sent to API; revert button for override fields
|
|
||||||
|
|
||||||
**Editable Fields:**
|
|
||||||
- Pronouns, Full Name, Professional Title, Affiliations, Phone, Location (all with override support)
|
|
||||||
- Allow Tracking checkbox (lead scanning permission)
|
|
||||||
- Staff-only: Email, Badge Type, Registration Type, Hide, Priority, Notes
|
|
||||||
- Staff-only: Options (`other_1_code` through `other_8_code`) and Tickets (`ticket_1_code` through `ticket_8_code`)
|
|
||||||
- Agree to Terms & Conditions checkbox (attendee-visible when in can_edit_fields)
|
|
||||||
|
|
||||||
#### Badge Review Page — Header Buttons (implemented 2026-02-27)
|
|
||||||
|
|
||||||
| Button | Visible To | Behavior |
|
|
||||||
| --- | --- | --- |
|
|
||||||
| Back → Search (ArrowLeft) | Staff (`has_staff_access`) only | `<a href="/events/{id}/badges">` |
|
|
||||||
| Print (Printer icon) | Trusted+, not printed OR Trusted+Edit if printed | `<a href="/print">`, shows `Nx` count if reprinting |
|
|
||||||
| Copy Link (clipboard) | Trusted + Edit Mode only | Copies review URL to clipboard; `Copied!` feedback for 2s |
|
|
||||||
| Email Link (Mail icon) | All if not printed; Trusted+Edit if printed | Placeholder `alert()` — email API pending |
|
|
||||||
|
|
||||||
#### Badge Print Page — Header Buttons (implemented 2026-02-27)
|
|
||||||
|
|
||||||
| Button | Visible To | Behavior |
|
|
||||||
| --- | --- | --- |
|
|
||||||
| Back → Search (ArrowLeft) | Always (when badge loaded) | `<a href="/events/{id}/badges">` |
|
|
||||||
| Print Now (Printer icon) | Trusted+, not printed OR Trusted+Edit if printed | Calls `window.print()` directly (convenience duplicate); print count tracked by component button |
|
|
||||||
| Review (Eye icon) | Trusted + Edit Mode only | `<a href="/review">` nav link |
|
|
||||||
| Email Link (Mail icon) | All if not printed; Trusted+Edit if printed | Placeholder `alert()` — email API pending |
|
|
||||||
|
|
||||||
#### Badge Review Page — Display Sections (implemented 2026-02-27)
|
|
||||||
|
|
||||||
The review form (`ae_comp__badge_review_form.svelte`) displays:
|
|
||||||
|
|
||||||
1. **Print status** ✅ — print count + first/last print timestamps (read-only, hidden if never printed)
|
|
||||||
2. **QR Code** ✅ — the attendee's badge QR code for scanning at the badge kiosk (for automatic badge search + print flow). Generated using `core_func.js_generate_qr_code()` with `obj_type: 'event_badge'` and badge ID. Supports hover zoom overlay and click-to-expand.
|
|
||||||
3. **Editable Fields** ✅ — all fields with access-level gating, override support, and HTML rendering for display fields
|
|
||||||
4. **Options** ✅ (`other_1_code` through `other_8_code`) — Staff: editable text inputs; Attendees: shown as `[✓] Option X` checkmark display only when value exists
|
|
||||||
5. **Tickets** ✅ (`ticket_1_code` through `ticket_8_code`) — Staff: editable text inputs; Attendees: shown as `[✓] Ticket X` checkmark display only when value exists
|
|
||||||
6. **Accessibility Toggle** ✅ — Font size enlargement button in sticky header (text-2xl ↔ text-4xl)
|
|
||||||
7. **Help Modal** ✅ — Attendee guidance modal with 6 sections explaining the review process, editing, QR codes, and lead scanning
|
|
||||||
|
|
||||||
#### Default Field Permissions (hardcoded for now — Axonius first show, mid-April 2026)
|
|
||||||
|
|
||||||
These are hardcoded in `review/+page.svelte` pending connection to `mod_badges_json.edit_permissions`.
|
|
||||||
|
|
||||||
**Attendee (passcode-authenticated / anonymous with link):**
|
|
||||||
```typescript
|
|
||||||
[
|
|
||||||
'pronouns_override',
|
|
||||||
'full_name_override',
|
|
||||||
'professional_title_override',
|
|
||||||
'affiliations_override',
|
|
||||||
'phone_override',
|
|
||||||
'location_override',
|
|
||||||
'allow_tracking', // Exhibitor Leads opt-in
|
|
||||||
'agree_to_tc', // Terms & Conditions placeholder
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
**Trusted Staff and above:**
|
|
||||||
```typescript
|
|
||||||
[
|
|
||||||
'pronouns_override',
|
|
||||||
'full_name_override',
|
|
||||||
'professional_title_override',
|
|
||||||
'affiliations_override',
|
|
||||||
'email_override',
|
|
||||||
'phone_override',
|
|
||||||
'location_override',
|
|
||||||
'badge_type_code_override', // + badge_type_override (text label)
|
|
||||||
'registration_type_code_override', // + registration_type_override (text label)
|
|
||||||
'option_1' ... 'option_8', // i.e. other_1_code ... other_8_code
|
|
||||||
'ticket_1_code' ... 'ticket_8_code',
|
|
||||||
'allow_tracking',
|
|
||||||
'agree_to_tc',
|
|
||||||
'hide',
|
|
||||||
'priority',
|
|
||||||
'notes',
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
**Administrator** — `can_edit_fields = ['*']` (all fields)
|
|
||||||
|
|
||||||
**Badge type options (hardcoded for now):** `member`, `non-member`, `guest`, `exhibitor`, `staff`, `test`
|
|
||||||
(In future: read from Event Badge Template's configured list)
|
|
||||||
|
|
||||||
**Registration type options:** Same list as badge type for now — identical select options.
|
|
||||||
|
|
||||||
#### Future: Per-Event Configuration
|
|
||||||
|
|
||||||
`event.mod_badges_json.edit_permissions` — placeholder settings UI exists in
|
|
||||||
`ae_comp__event_settings_badges_form.svelte`. Review page uses hardcoded defaults for now.
|
|
||||||
The settings form and review page are not yet connected.
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"authenticated": {
|
|
||||||
"can_edit": ["pronouns_override", "full_name_override", "professional_title_override", "affiliations_override", "phone_override", "location_override", "allow_tracking", "agree_to_tc"]
|
|
||||||
},
|
|
||||||
"trusted": {
|
|
||||||
"can_edit": ["*attendee_fields", "email_override", "badge_type_code_override", "registration_type_code_override", "option_x", "ticket_x_code", "allow_tracking", "agree_to_tc", "hide", "priority", "notes"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Search & Filter Capabilities
|
## Search & Filter Capabilities
|
||||||
|
|
||||||
### Search Component
|
- **Fulltext Search:** Matches against a consolidated `default_qry_str` (Name, email, IDs).
|
||||||
**File:** `ae_comp__badge_search.svelte`
|
- **Multi-Word Logic:** Queries like "Scott Idem" are split and treated as `LIKE %Scott% AND LIKE %Idem%`.
|
||||||
|
- **QR Scan Search:** Scanning an attendee's QR code (from a confirmation email or old badge) immediately jumps to their record.
|
||||||
### Multi-Word Search Fix (2026-02-26)
|
- **Advanced Filters:** Filter by Badge Type, Printed Status, or Affiliations (Staff only).
|
||||||
Fulltext search now correctly handles multi-word queries by splitting on whitespace and applying AND logic per word:
|
|
||||||
```typescript
|
|
||||||
// "scott idem" → LIKE '%scott%' AND LIKE '%idem%'
|
|
||||||
// Previously: LIKE '%scott idem%' (failed to match)
|
|
||||||
const words = qry.split(/\s+/).filter(w => w.length > 0);
|
|
||||||
for (const word of words) {
|
|
||||||
search_query.and.push({ field: 'default_qry_str', op: 'like', value: `%${word}%` });
|
|
||||||
}
|
|
||||||
```
|
|
||||||
**Committed:** dc0f3066
|
|
||||||
|
|
||||||
### Available Filters
|
|
||||||
|
|
||||||
**Fulltext Search** (All Users)
|
|
||||||
- Searches: `default_qry_str` database field
|
|
||||||
- Includes: Name, email, external IDs
|
|
||||||
- Type: `LIKE %query%` (case-insensitive)
|
|
||||||
- Trigger: Enter key or 3+ characters typed
|
|
||||||
|
|
||||||
**Advanced Filters** (Trusted Access & Above)
|
|
||||||
```typescript
|
|
||||||
// Badge Type Filter
|
|
||||||
badge_type_code: 'current_member' | 'inactive_member' | 'ex_all' | 'staff' | etc.
|
|
||||||
// Note: Badge types are defined per Event and Event Badge Template in database table records.
|
|
||||||
// Common types include: member, nonmember, guest, exhibitor, staff
|
|
||||||
// This is a work in progress - types vary by event configuration.
|
|
||||||
|
|
||||||
// Print Status Filter
|
|
||||||
qry_printed_status: 'all' | 'printed' | 'not_printed'
|
|
||||||
|
|
||||||
// Affiliations Search
|
|
||||||
qry_affiliations: string // Separate filter for organization search
|
|
||||||
|
|
||||||
// Sort Options
|
|
||||||
qry_sort_order:
|
|
||||||
- 'name_asc' / 'name_desc'
|
|
||||||
- 'updated_desc' / 'updated_asc'
|
|
||||||
- 'print_count_desc'
|
|
||||||
- 'print_first_desc' / 'print_last_desc'
|
|
||||||
- 'badge_type_asc'
|
|
||||||
- 'affiliations_asc'
|
|
||||||
```
|
|
||||||
|
|
||||||
### QR Scan Search
|
|
||||||
- Scans badge QR code
|
|
||||||
- Extracts badge ID
|
|
||||||
- Auto-fills search with ID
|
|
||||||
- Jumps to badge detail view
|
|
||||||
|
|
||||||
### Search Implementation Pattern
|
|
||||||
**File:** `badges/+page.svelte` (Lines 117-365)
|
|
||||||
|
|
||||||
**Strategy:** Standardized Reactive Search Pattern (Aether UI V3)
|
|
||||||
1. **Isolate dependencies** into stable `$derived` object
|
|
||||||
2. **Debounced effect** (300ms) triggers search
|
|
||||||
3. **Fast Path:** Search IDB first (if not `remote_first`)
|
|
||||||
4. **Revalidate:** API request updates IDB
|
|
||||||
5. **LiveQuery:** UI auto-updates from IDB changes
|
|
||||||
|
|
||||||
**Search API:** `events_func.search__event_badge()`
|
|
||||||
```typescript
|
|
||||||
await search__event_badge({
|
|
||||||
api_cfg: $ae_api,
|
|
||||||
event_id: event_id,
|
|
||||||
fulltext_search_qry_str: qry_str || null,
|
|
||||||
type_code: type_code || null,
|
|
||||||
printed_status: printed_status,
|
|
||||||
affiliations_qry_str: aff_str || null,
|
|
||||||
order_by_li: order_by_li,
|
|
||||||
limit: 150,
|
|
||||||
log_lvl: 0
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Badge Display Logic
|
|
||||||
|
|
||||||
### Name Display Priority
|
|
||||||
```typescript
|
|
||||||
// Component: ae_comp__badge_obj_li.svelte (Lines 113-121)
|
|
||||||
if (event_badge_obj?.full_name_override)
|
|
||||||
display: full_name_override
|
|
||||||
else if (event_badge_obj?.full_name)
|
|
||||||
display: full_name
|
|
||||||
else
|
|
||||||
display: given_name + ' ' + family_name
|
|
||||||
```
|
|
||||||
|
|
||||||
### Badge View Page
|
|
||||||
**Route:** `/events/[event_id]/badges/[badge_id]`
|
|
||||||
|
|
||||||
**Components:**
|
|
||||||
- `+page.svelte` — Container with LiveQuery for badge data
|
|
||||||
- `ae_comp__badge_obj_view.svelte` — Full badge display + edit UI
|
|
||||||
|
|
||||||
**LiveQueries:**
|
|
||||||
```typescript
|
|
||||||
lq__event_badge_obj = liveQuery(() => db_events.badge.get(event_badge_id))
|
|
||||||
lq__event_badge_template_obj = liveQuery(() =>
|
|
||||||
db_events.badge_template.get(badge.event_badge_template_id)
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
**Loading States:**
|
|
||||||
- `is_loading_idb` — Waiting for initial IDB lookup
|
|
||||||
- If badge not found → "Badge Not Found" error with reload button
|
|
||||||
- Loader spinner while fetching
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Badge Templates
|
|
||||||
|
|
||||||
### Purpose
|
|
||||||
Badge templates define the visual layout and content structure for printed badges:
|
|
||||||
- Header images/logos
|
|
||||||
- Field positions and font sizes
|
|
||||||
- QR code placement
|
|
||||||
- Ticket/option indicator display
|
|
||||||
- WiFi credentials display
|
|
||||||
|
|
||||||
### Template Selection
|
|
||||||
Each badge references an `event_badge_template_id`. The template controls:
|
|
||||||
- Layout (front/back)
|
|
||||||
- Branding elements
|
|
||||||
- Which fields to show
|
|
||||||
- Field formatting rules
|
|
||||||
|
|
||||||
### Template Loading
|
|
||||||
Templates are loaded alongside badges via `inc_template` parameter:
|
|
||||||
```typescript
|
|
||||||
load_ae_obj_id__event_badge({
|
|
||||||
event_badge_id: badge_id,
|
|
||||||
inc_template: true // Also loads template
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Print Tracking
|
## Print Tracking
|
||||||
|
|
||||||
### Print Fields
|
Aether tracks the lifecycle of every physical badge to prevent unauthorized reprints and monitor kiosk activity.
|
||||||
```typescript
|
|
||||||
print_count: number // Increments each print
|
|
||||||
print_first_datetime: string // ISO datetime of first print
|
|
||||||
print_last_datetime: string // ISO datetime of most recent print
|
|
||||||
```
|
|
||||||
|
|
||||||
### Print Button (Implemented 2026-02-26)
|
| Field | Purpose |
|
||||||
The `handle_print_badge()` function in `ae_comp__badge_obj_view.svelte` increments the count and records timestamps:
|
|---|---|
|
||||||
```typescript
|
| `print_count` | Increments on every "Print Badge" action. |
|
||||||
async function handle_print_badge() {
|
| `print_first_datetime` | Timestamp of the very first print. |
|
||||||
const now = new Date().toISOString();
|
| `print_last_datetime` | Timestamp of the most recent print. |
|
||||||
const current_print_count = $lq__event_badge_obj.print_count ?? 0;
|
|
||||||
const data_to_update = {
|
|
||||||
print_count: current_print_count + 1,
|
|
||||||
print_last_datetime: now
|
|
||||||
};
|
|
||||||
if (current_print_count === 0) {
|
|
||||||
data_to_update.print_first_datetime = now; // Only set on first print
|
|
||||||
}
|
|
||||||
await events_func.update_ae_obj__event_badge({ ... });
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Button has `data-testid="badge-print-btn"` and shows loading/done/error states with icon feedback.
|
> **Operational Note:** Reprints triggered via the Edit Mode shortcut do not increment the count; only the formal "Print Badge" workflow does.
|
||||||
|
|
||||||
### Print Workflow
|
|
||||||
1. **Pre-Print:** Badge print page (`/print`) shows "Already printed N times" warning in screen-only header if `print_count >= 1`
|
|
||||||
2. **Record:** `handle_print_badge()` updates `print_count`, `print_last_datetime`, and `print_first_datetime` (first print only) via API before printing
|
|
||||||
3. **Print:** `window.print()` — standard browser print dialog, wired and working (2026-02-27)
|
|
||||||
4. **Redirect:** After 1 second, `goto(/events/{id}/badges)` returns to search
|
|
||||||
5. **Audit:** `print_first_datetime` and `print_last_datetime` visible in Edit Mode debug row
|
|
||||||
|
|
||||||
**Browser vs Electron:** Badge printing does NOT require the Electron native app. The standard browser print dialog works well across Chrome, Chromium, and Firefox. The Electron native app is specialized for the **Events Pres Mgmt Launcher only** and should not be assumed available for badge stations.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Database Schema
|
## Route Map (Badges)
|
||||||
|
|
||||||
### IndexedDB Table: `badge`
|
| URL | Purpose |
|
||||||
**File:** `src/lib/ae_events/db_events.ts` (Lines 841-852)
|
|---|---|
|
||||||
|
| `/events/[id]/badges` | Main search and attendee list. |
|
||||||
**Indexed Fields:**
|
| `/events/[id]/badges/templates` | Badge template management. |
|
||||||
```typescript
|
| `/events/[id]/badges/[id]/print` | The actual print-ready render page. |
|
||||||
badge: `
|
| `/events/[id]/badges/[id]/review` | Attendee-facing self-service form. |
|
||||||
event_badge_id, id,
|
|
||||||
event_id,
|
|
||||||
full_name, full_name_override, email, email_override,
|
|
||||||
affiliations, affiliations_override,
|
|
||||||
badge_type, badge_type_code, badge_type_code_override, badge_type_override,
|
|
||||||
external_event_id, external_id, external_person_id,
|
|
||||||
default_qry_str,
|
|
||||||
alert,
|
|
||||||
tmp_sort_1, tmp_sort_2,
|
|
||||||
print_count, print_first_datetime, print_last_datetime,
|
|
||||||
enable, hide, priority, sort, group, notes, created_on, updated_on
|
|
||||||
`
|
|
||||||
```
|
|
||||||
|
|
||||||
### Saved Properties
|
|
||||||
**File:** `ae_events__event_badge.ts` (Lines 495-563)
|
|
||||||
|
|
||||||
**Complete field list** (67 fields total):
|
|
||||||
- Identity: `id`, `event_badge_id`, `event_id`, `event_badge_template_id`
|
|
||||||
- Name: `pronouns`, `informal_name`, `title_names`, `given_name`, `middle_name`, `family_name`, `designations`
|
|
||||||
- Professional: `professional_title`, `professional_title_override`
|
|
||||||
- Display: `full_name`, `full_name_override`
|
|
||||||
- Organization: `affiliations`, `affiliations_override`
|
|
||||||
- Contact: `email`, `email_override`
|
|
||||||
- Address: `address_line_1`, `address_line_2`, `address_line_3`, `city`, `country_subdivision_code`, `state_province`, `state_province_abb`, `postal_code`, `country_alpha_2_code`, `country`, `full_address`
|
|
||||||
- Location: `location`, `location_override`
|
|
||||||
- Classification: `badge_type`, `badge_type_code`, `badge_type_override`, `badge_type_code_override`
|
|
||||||
- External: `external_event_id`, `external_id`, `external_person_id`
|
|
||||||
- Search: `query_str`, `default_qry_str`
|
|
||||||
- System: `alert`, `enable`, `hide`, `priority`, `sort`, `group`, `notes`, `created_on`, `updated_on`
|
|
||||||
- Print: `print_count`, `print_first_datetime`, `print_last_datetime`
|
|
||||||
- Sorting: `tmp_sort_1`, `tmp_sort_2`
|
|
||||||
- Person Link: `person_external_id`, `person_external_sys_id`, `person_given_name`, `person_family_name`, `person_full_name`, `person_professional_title`, `person_affiliations`, `person_primary_email`, `person_passcode`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## API Functions
|
|
||||||
|
|
||||||
### CRUD Operations
|
|
||||||
**File:** `src/lib/ae_events/ae_events__event_badge.ts`
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// Load single badge
|
|
||||||
load_ae_obj_id__event_badge({ event_badge_id, event_id, inc_template })
|
|
||||||
|
|
||||||
// Load badge list
|
|
||||||
load_ae_obj_li__event_badge({ event_id, view, limit, order_by_li })
|
|
||||||
|
|
||||||
// Search badges (V3 API)
|
|
||||||
search__event_badge({
|
|
||||||
event_id,
|
|
||||||
fulltext_search_qry_str,
|
|
||||||
type_code,
|
|
||||||
printed_status,
|
|
||||||
affiliations_qry_str,
|
|
||||||
order_by_li
|
|
||||||
})
|
|
||||||
|
|
||||||
// Create badge
|
|
||||||
create_ae_obj__event_badge({ event_id, data_kv })
|
|
||||||
|
|
||||||
// Update badge
|
|
||||||
update_ae_obj__event_badge({ event_badge_id, event_id, data_kv })
|
|
||||||
|
|
||||||
// Delete badge
|
|
||||||
delete_ae_obj_id__event_badge({ event_badge_id, event_id, method })
|
|
||||||
```
|
|
||||||
|
|
||||||
### Field Processing
|
|
||||||
**Function:** `process_ae_obj__event_badge_props()`
|
|
||||||
|
|
||||||
**Processing Steps:**
|
|
||||||
1. Map `*_random` fields to clean names (`event_badge_id_random` → `event_badge_id`)
|
|
||||||
2. Set primary `id` field from `event_badge_id`
|
|
||||||
3. Ensure `event_id` is set (from function parameter if missing)
|
|
||||||
4. Calculate `tmp_sort_1` and `tmp_sort_2` for efficient sorting
|
|
||||||
5. Return processed objects
|
|
||||||
|
|
||||||
**Critical Fix (2026-02-26):** All CRUD functions now return **processed** data (matches IDB cache) instead of raw API responses. This ensures consistency between function return values and cached data.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Component Architecture
|
|
||||||
|
|
||||||
### Route Structure
|
|
||||||
```
|
|
||||||
/events/[event_id]/(badges)/badges/
|
|
||||||
├── +layout.svelte # Layout wrapper (minimal)
|
|
||||||
├── +page.svelte # Badge list + search
|
|
||||||
├── ae_comp__badge_search.svelte # Search form + filters
|
|
||||||
├── ae_comp__badge_obj_li.svelte # Badge list display (results)
|
|
||||||
├── ae_comp__badge_create_form.svelte # (Not actively used)
|
|
||||||
├── ae_comp__badge_upload_form.svelte # Bulk CSV upload
|
|
||||||
└── [badge_id]/
|
|
||||||
├── ae_comp__badge_obj_view.svelte # Badge rendering + staff edit + print button
|
|
||||||
├── ae_comp__badge_review_form.svelte # Form-based field review/edit (attendee + staff)
|
|
||||||
├── print/
|
|
||||||
│ ├── +page.ts # Non-blocking badge loader (inc_template: true)
|
|
||||||
│ └── +page.svelte # Print-focused page — screen header + badge render
|
|
||||||
└── review/
|
|
||||||
├── +page.ts # Non-blocking badge loader (inc_template: false)
|
|
||||||
└── +page.svelte # Passcode-gated review page
|
|
||||||
```
|
|
||||||
|
|
||||||
> **Note:** The old `[badge_id]/+page.svelte` placeholder was removed (2026-02-27). The name link in the search results list now goes directly to `/print`.
|
|
||||||
|
|
||||||
#### Badge Print Page (`/print`)
|
|
||||||
- Screen-only header (`print:hidden`): "Back to Search" link + "Already printed N times" warning
|
|
||||||
- Badge rendered via `ae_comp__badge_obj_view` with `is_review_mode={false}`
|
|
||||||
- Print button inside `ae_comp__badge_obj_view` handles count update → `window.print()` → redirect to search
|
|
||||||
- Page `<title>` includes badge name + event name
|
|
||||||
|
|
||||||
#### Badge Review Page (`/review`)
|
|
||||||
- Passcode-gated for attendees — URL `?passcode=...` matched against `badge.person_passcode`
|
|
||||||
- **Note:** `person_passcode` field is not yet in the DB (as of 2026-02-27). Review page accessible to staff via `trusted_access` without a passcode.
|
|
||||||
- Access hierarchy (checked in order):
|
|
||||||
1. Administrator → full access (`can_edit_fields = ['*']`)
|
|
||||||
2. Trusted Staff → staff field set
|
|
||||||
3. Attendee with valid passcode → attendee field set
|
|
||||||
4. No access → passcode entry form shown
|
|
||||||
- Uses `ae_comp__badge_review_form.svelte` (NOT badge render)
|
|
||||||
- "Back to Search" link shown for staff only
|
|
||||||
|
|
||||||
### Key Components
|
|
||||||
|
|
||||||
**Badge List Page** (`+page.svelte`)
|
|
||||||
- **LiveQuery:** Reactive badge list from IDB
|
|
||||||
- **Search Pattern:** Debounced search with fast path + revalidation
|
|
||||||
- **ID List:** `event_badge_id_li` drives LiveQuery
|
|
||||||
- **Loading State:** Shows spinner when `search_status === 'loading'`
|
|
||||||
|
|
||||||
**Badge Search** (`ae_comp__badge_search.svelte`)
|
|
||||||
- **Form Mode:** Toggle between search form and QR scanner
|
|
||||||
- **Filters:** Badge type, print status, affiliations, sort order (trusted+ only)
|
|
||||||
- **Fulltext:** Name/email search (all users)
|
|
||||||
- **QR Scan:** Integrated QR scanner for badge ID lookup
|
|
||||||
|
|
||||||
**Badge List Display** (`ae_comp__badge_obj_li.svelte`)
|
|
||||||
- **Visibility Filter:** Respects `hide` flag (trusted+ sees all)
|
|
||||||
- **Display Logic:** Override → regular → fallback pattern
|
|
||||||
- **Print Indicator:** Green checkmark badge shows `print_count`
|
|
||||||
- **Metadata:** ID, created/updated timestamps (edit mode only)
|
|
||||||
|
|
||||||
**Badge Detail View** (`ae_comp__badge_obj_view.svelte`)
|
|
||||||
- **Edit Mode:** Activated by edit button (or `#review` URL hash for future self-service)
|
|
||||||
- **Condition:** Renders only when BOTH `$lq__event_badge_obj` AND `$lq__event_badge_template_obj` are non-null
|
|
||||||
- **Form Binding:** Direct `bind:value` on editable fields
|
|
||||||
- **Dynamic Sizing:** Font size adjusts based on text length
|
|
||||||
- **Print Preview:** Full badge layout with template
|
|
||||||
- **Save Handler:** Only sends changed fields to API
|
|
||||||
- **`data-testid` attributes:** `badge-edit-btn`, `badge-save-btn`, `badge-cancel-btn`, `badge-print-btn`, `badge-professional-title-input` — use these in tests
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Testing Status
|
|
||||||
|
|
||||||
### Current Test Coverage
|
|
||||||
- ✅ Badge list loads (all 6 data integrity tests passing)
|
|
||||||
- ✅ Badge template list loads and displays
|
|
||||||
- ✅ Badge template form renders and populates correctly
|
|
||||||
- ✅ Badge template values persist in edit form
|
|
||||||
- ✅ Electron bridge compatibility (graceful degradation in browser)
|
|
||||||
- ✅ Badge field processor handles missing optional fields
|
|
||||||
- ✅ Badge type filter tests
|
|
||||||
- ✅ Badge template relationship tests
|
|
||||||
- ✅ **Attendee workflow test** — navigate → edit professional title → print → return (d1ded2d4)
|
|
||||||
|
|
||||||
### Key Test Lessons Learned
|
|
||||||
|
|
||||||
**Search API path is FLAT, not nested.** `search_ae_obj` builds `/v3/crud/{obj_type}/search` — always flat regardless of the parent relationship. Mocks must match this:
|
|
||||||
```typescript
|
|
||||||
// CORRECT — flat path
|
|
||||||
url.includes('/v3/crud/event_badge/search') && method === 'POST'
|
|
||||||
// WRONG — nested path, mock will never fire
|
|
||||||
url.includes(`/v3/crud/event/${event_id}/event_badge/search`) && method === 'POST'
|
|
||||||
```
|
|
||||||
|
|
||||||
**List API (GET) is also FLAT with query params.** `get_ae_obj_li` builds `/v3/crud/{obj_type}/?for_obj_id=...` — always flat. Mocks must check `url.includes('/v3/crud/event_badge_template/') && url.includes('for_obj_id')`.
|
|
||||||
|
|
||||||
**CSS `input[value*=...]` selectors don't work with Svelte bind:value.** The CSS selector checks the HTML *attribute*; Svelte's `bind:value` sets the DOM *property* only. In Playwright tests, use `page.getByLabel()` or `locator.inputValue()` instead.
|
|
||||||
|
|
||||||
**Dexie requires `_random` ID fields.** Badge objects saved to IDB must include:
|
|
||||||
```typescript
|
|
||||||
event_badge_id_random: string // Must be present or Dexie skips the object
|
|
||||||
id_random: string // Also checked
|
|
||||||
// Error: "Object is missing a valid ID for table 'badge'"
|
|
||||||
```
|
|
||||||
All API mock responses in tests need these fields.
|
|
||||||
|
|
||||||
**Badge view requires both badge AND template.** `ae_comp__badge_obj_view.svelte` wraps everything in `{#if $lq__event_badge_obj && $lq__event_badge_template_obj}` — if the template isn't loaded, edit/print buttons and the badge itself don't render. Tests must mock the badge template endpoint.
|
|
||||||
|
|
||||||
**Badge GET endpoint (single object):** `/v3/crud/event_badge/{id}` (NOT nested under event). Matches `api.get_ae_obj()` which uses the flat path.
|
|
||||||
|
|
||||||
**Badge PATCH endpoint (update):** `/v3/crud/event/${event_id}/event_badge/${badge_id}` (nested under event). Matches `api.patch_ae_obj()` which uses the nested path.
|
|
||||||
|
|
||||||
**Use `data-testid` for test selectors.** Key buttons have targets: `badge-edit-btn`, `badge-save-btn`, `badge-cancel-btn`, `badge-print-btn`, `badge-professional-title-input`.
|
|
||||||
|
|
||||||
### Remaining Test Issues
|
|
||||||
None — all current badge tests passing as of 2026-02-26 (f5e98b8c).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Known Issues & Future Enhancements
|
|
||||||
|
|
||||||
### Known Issues
|
|
||||||
1. **Session Cold-Start:** Potential race condition on first load (same as pres mgmt module)
|
|
||||||
2. **Type Definitions:** Some pre-existing TypeScript errors on external package types (not introduced by badge work)
|
|
||||||
3. **`person_passcode` not in DB:** Attendee-gated review URL (`?passcode=...`) cannot function until this field is added to the `event_badge` schema. The review page falls back to passcode entry form for non-staff.
|
|
||||||
4. **Print page CSS:** Badge print rendering and `@page` print styles not yet fine-tuned — expected to need work
|
|
||||||
5. **`mod_badges_json.edit_permissions` not connected:** Settings UI exists but review page uses hardcoded field defaults
|
|
||||||
|
|
||||||
### Implemented (2026-02-27)
|
|
||||||
- ✅ `window.print()` wired to print button (records count first, then prints, then redirects)
|
|
||||||
- ✅ Dedicated `/print` page — replaces old `[badge_id]/+page.svelte` placeholder
|
|
||||||
- ✅ Dedicated `/review` page — passcode-gated, access-tiered
|
|
||||||
- ✅ `ae_comp__badge_review_form.svelte` — stub created, full form fields pending
|
|
||||||
- ✅ Badge search results visibility rules (unprinted-only for non-edit, all for trusted+edit)
|
|
||||||
- ✅ Badge list: 4 action buttons per row (Print, Review nav, Copy Link, Email Link) — all Lucide icons
|
|
||||||
- ✅ Print page: 3 action buttons in header (Print Now, Review nav, Email Link) — all Lucide icons
|
|
||||||
- ✅ Review page: 3 action buttons in header (Print nav, Copy Link, Email Link) — all Lucide icons
|
|
||||||
- ✅ Print button: not shown when already printed (unless Edit Mode)
|
|
||||||
- ✅ Print count shown as `Nx` badge next to printer icon
|
|
||||||
- ✅ Email obscuring for non-trusted users
|
|
||||||
- ✅ Email Review Link button (placeholder alert — email API pending)
|
|
||||||
- ✅ Direct Review Link clipboard copy (trusted + Edit Mode only)
|
|
||||||
- ✅ Fixed: components no longer write to `$ae_loc.edit_mode`
|
|
||||||
- ✅ Settings UI for `edit_permissions` per event (`ae_comp__event_settings_badges_form.svelte`)
|
|
||||||
- ✅ All badge module icons converted to Lucide (Font Awesome removed from badge routes)
|
|
||||||
|
|
||||||
### Recently Completed (2026-02-27)
|
|
||||||
- ✅ **Badge Review Form** — `ae_comp__badge_review_form.svelte` fully implemented (fields, QR, save/cancel, options/tickets, accessibility, help modal)
|
|
||||||
- ✅ **Print font size controls (v1)** — Screen-only `[−]/[+]/[↺]` panel on print page; 4 px props added to `ae_comp__badge_obj_view.svelte`; auto-sizing unchanged when props absent
|
|
||||||
- ✅ **Bug fix** — `default_authenticated_fields` / `default_trusted_fields` in `review/+page.svelte` corrected (wrong field names caused silent save drops)
|
|
||||||
|
|
||||||
### Still Needed — HIGH PRIORITY (first show: April 2026)
|
|
||||||
|
|
||||||
### Still Needed — MEDIUM PRIORITY
|
|
||||||
1. **Email API for review links:** `send_review_email()` is a placeholder `alert()`. Needs actual email send endpoint.
|
|
||||||
2. **`person_passcode` DB field:** Add to `event_badge` schema to enable attendee-gated review URLs.
|
|
||||||
3. **Connect `edit_permissions` config:** Read `mod_badges_json.edit_permissions` in review page instead of hardcoded defaults.
|
|
||||||
4. **Print page CSS / `@page` styles:** Badge rendering, sizing, and print-specific stylesheet.
|
|
||||||
|
|
||||||
### Still Needed — FUTURE / LOW PRIORITY
|
|
||||||
1. **Batch Operations:** Bulk update, bulk print, bulk export
|
|
||||||
2. **Audit Log:** Track who edited which fields and when
|
|
||||||
3. **Photo Badges:** Support badge photo upload and display
|
|
||||||
4. **Real-Time Sync:** WebSocket updates for multi-device badge printing stations
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Development Guidelines
|
|
||||||
|
|
||||||
### Adding New Override Fields
|
|
||||||
1. Add `{field}_override` to database schema
|
|
||||||
2. Add to `properties_to_save` array in `ae_events__event_badge.ts`
|
|
||||||
3. Update display logic to check override first
|
|
||||||
4. Add to editable fields in `ae_comp__badge_obj_view.svelte`
|
|
||||||
5. Update access control config
|
|
||||||
6. Document in this file
|
|
||||||
|
|
||||||
### Testing Override Fields
|
|
||||||
```typescript
|
|
||||||
// Simulate external sync
|
|
||||||
badge.given_name = "External Value"
|
|
||||||
|
|
||||||
// User edits
|
|
||||||
badge.given_name_override = "User Value"
|
|
||||||
|
|
||||||
// Next sync (should NOT change override)
|
|
||||||
badge.given_name = "Updated External Value"
|
|
||||||
|
|
||||||
// Display should still show "User Value"
|
|
||||||
assert(display === badge.given_name_override)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Debugging Search Issues
|
|
||||||
```typescript
|
|
||||||
// Enable search logging
|
|
||||||
log_lvl: 2
|
|
||||||
|
|
||||||
// Check search params object
|
|
||||||
console.log('Search params:', search_params)
|
|
||||||
|
|
||||||
// Verify API request
|
|
||||||
console.log('API request:', { event_id, fulltext_search_qry_str, type_code })
|
|
||||||
|
|
||||||
// Check returned IDs
|
|
||||||
console.log('Badge IDs:', event_badge_id_li)
|
|
||||||
|
|
||||||
// Verify IDB contents
|
|
||||||
db_events.badge.toArray().then(console.log)
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Related Documentation
|
## Related Documentation
|
||||||
- [AE API V3 for Frontend](./GUIDE__AE_API_V3_for_Frontend.md)
|
👉 **[MODULE__AE_Events_Badge_Templates.md](./MODULE__AE_Events_Badge_Templates.md)** (Technical reference for layouts)
|
||||||
- [Development Guide](./GUIDE__Development.md)
|
👉 **[GUIDE__AE_Events_Badges_Onsite.md](./GUIDE__AE_Events_Badges_Onsite.md)** (Hardware & station setup)
|
||||||
- [Events Launcher Native Integration](./PROJECT__AE_Events_Launcher_Native_integration.md)
|
👉 **[GUIDE__AE_Events_Onsite_Runbook.md](./GUIDE__AE_Events_Onsite_Runbook.md)** (Onsite operational checklists)
|
||||||
- [Naming Conventions](./AE__Naming_Conventions.md)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Document Status:** 🔄 In Progress
|
|
||||||
**Last Verified:** 2026-02-27 (rev 5 — field permissions spec added, header buttons implemented, review form fields pending)
|
|
||||||
**Verified Against:** Code as of 2026-02-27 (branch ae_app_3x_llm)
|
|
||||||
|
|||||||
Reference in New Issue
Block a user