fix(badges): wire badge_type_list from template instead of hardcoded ISHLT list
Replace static `badge_type_code_li` array (hardcoded ISHLT 2024 types) with `$derived.by()` that parses `$lq__event_badge_template_obj.badge_type_list` JSON. Each event's template defines its own badge type set — the component now reflects that correctly. Falls back to `[]` on missing/invalid JSON (hides the type select). Also add MODULE__AE_Events_Badge_Templates.md documenting template field reference, external CSS approach, layout codes, duplex field plan, and standard template setup. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
276
documentation/MODULE__AE_Events_Badge_Templates.md
Normal file
276
documentation/MODULE__AE_Events_Badge_Templates.md
Normal file
@@ -0,0 +1,276 @@
|
||||
# MODULE: Aether Events — Badge Templates
|
||||
|
||||
**Module Path:** `src/routes/events/[event_id]/(badges)/templates/`
|
||||
**API Module:** `src/lib/ae_events/ae_events__event_badge_template.ts`
|
||||
**Database Table:** `event_badge_template`
|
||||
**Last Updated:** 2026-03-02
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
Badge templates define the visual and structural configuration for printing event badges.
|
||||
Each template applies to one category of badge (e.g., general attendees, workshops,
|
||||
exhibitors). An event typically has 1–3 templates.
|
||||
|
||||
**Key principle:** One template per badge stock type/audience. Do not use flags on a
|
||||
single template to drive multiple layouts — create a separate template instead.
|
||||
|
||||
**Common template sets:**
|
||||
- **General Attendees** — main conference badge (most attendees)
|
||||
- **Workshops / Pre-conference** — alternate header, possibly different badge type list
|
||||
- **Exhibitors** — distinct footer stripe colors, exhibitor-specific badge types
|
||||
|
||||
Each template uses the same physical badge stock and printer configuration.
|
||||
|
||||
---
|
||||
|
||||
## DB Field Reference
|
||||
|
||||
### Core Identity
|
||||
| Field | Type | Notes |
|
||||
|---|---|---|
|
||||
| `id` | int | Internal PK |
|
||||
| `id_random` | str | External-facing ID (AE Triple ID pattern) |
|
||||
| `event_id` | str | Parent event |
|
||||
| `name` | str | Template display name |
|
||||
| `description` | str | Optional description |
|
||||
|
||||
### Image Assets
|
||||
| Field | Type | Notes |
|
||||
|---|---|---|
|
||||
| `logo_path` | str (URL) | Org logo — fallback when no header image |
|
||||
| `logo_filename` | str | **Deprecated** — redundant with logo_path; do not use |
|
||||
| `header_path` | str (URL) | Front-of-badge header image — primary branding |
|
||||
| `secondary_header_path` | str (URL) | Back-of-badge header image (falls back to header_path) |
|
||||
| `footer_path` | str (URL) | Optional footer image — rarely used |
|
||||
| `header_row_1` | str/HTML | Text fallback line 1 when no header image |
|
||||
| `header_row_2` | str/HTML | Text fallback line 2 |
|
||||
| `footer_title`, `footer_left`, `footer_right` | str | Legacy Flask-era fields — not used |
|
||||
| `header_background`, `footer_background` | str | Legacy — not used; do not add to new templates |
|
||||
|
||||
### Network / WiFi
|
||||
| Field | Type | Notes |
|
||||
|---|---|---|
|
||||
| `wireless_ssid` | str | WiFi network name — displayed on badge back |
|
||||
| `wireless_password` | str | WiFi password — displayed on badge back |
|
||||
|
||||
### QR Code Behavior
|
||||
| Field | Type | Notes |
|
||||
|---|---|---|
|
||||
| `show_qr_front` | bool (0/1) | Show attendee QR code on front of badge |
|
||||
| `show_qr_back` | bool (0/1) | Show attendee QR code (+ ID text) on back of badge |
|
||||
|
||||
### Badge Type List
|
||||
| Field | Type | Notes |
|
||||
|---|---|---|
|
||||
| `badge_type_list` | JSON string | List of `{code, name}` objects for this template |
|
||||
|
||||
**Format:**
|
||||
```json
|
||||
[
|
||||
{"code": "current_member", "name": "Member"},
|
||||
{"code": "guest", "name": "Guest"},
|
||||
{"code": "staff", "name": "Staff"},
|
||||
{"code": "test", "name": "Test"}
|
||||
]
|
||||
```
|
||||
|
||||
The badge type footer stripe color is driven by CSS rules targeting the `code` value
|
||||
as a class on the footer element. Each event/template defines its own list — there is
|
||||
no global default. The component derives this list from the template at render time.
|
||||
|
||||
### Ticket Definitions
|
||||
| Field | Type | Notes |
|
||||
|---|---|---|
|
||||
| `ticket_list` | JSON string | List of `{num, code, name}` for this template's tickets |
|
||||
| `ticket_1_text` – `ticket_8_text` | HTML | Ticket block HTML printed on badge back |
|
||||
|
||||
**ticket_list format:**
|
||||
```json
|
||||
[
|
||||
{"num": 1, "code": "foundation_reception", "name": "Foundation Reception"},
|
||||
{"num": 2, "code": "volunteer_reception", "name": "Volunteer Reception"}
|
||||
]
|
||||
```
|
||||
|
||||
The `ticket_N_code` field on the badge object references a ticket by its `code`. The
|
||||
corresponding `ticket_N_text` on the template provides the HTML rendered on the badge.
|
||||
|
||||
### Print Layout / Styling
|
||||
| Field | Type | Notes |
|
||||
|---|---|---|
|
||||
| `layout` | str | Layout code — see Layout Codes below |
|
||||
| `style_filename` | str | CSS filename for locally-served stylesheets |
|
||||
| `style_href` | str (URL) | **Preferred** — external URL for custom CSS |
|
||||
| `script_src` | str (URL) | **Do not use** — Flask-era arbitrary script injection |
|
||||
|
||||
### Access Control
|
||||
| Field | Type | Notes |
|
||||
|---|---|---|
|
||||
| `passcode` | str | Shared passcode for template management access |
|
||||
| `enable` | bool | Standard AE enable flag |
|
||||
| `hide` | bool | Standard AE hide flag |
|
||||
| `priority`, `sort`, `group` | int/str | Standard AE sort fields |
|
||||
| `notes` | str | Internal notes |
|
||||
|
||||
### New Field (pending backend addition)
|
||||
| Field | Type | Notes |
|
||||
|---|---|---|
|
||||
| `duplex` | bool | **Planned** — when `false`, back section is hidden from print (`@media print`) |
|
||||
|
||||
The `duplex` field controls whether the back-of-badge section renders during printing.
|
||||
When `false` (single-sided), `badge_back` gets `print:hidden` applied so only the front
|
||||
prints. The back section still displays on screen for configuration reference.
|
||||
|
||||
The first event using this system (Axonius, NYC, mid-April 2026) uses single-sided PVC
|
||||
cards on a Zebra ZC10L — `duplex` will be `false` for that event's templates.
|
||||
|
||||
---
|
||||
|
||||
## External CSS Approach
|
||||
|
||||
### Why External
|
||||
|
||||
Badge templates may need visual adjustments mid-event (e.g., a color correction, a
|
||||
footer fix) without deploying a new SvelteKit build. Hosting the CSS at an external URL
|
||||
allows changes to take effect on next page load without any deployment.
|
||||
|
||||
### How It Works
|
||||
|
||||
The `style_href` field contains a full URL to a CSS file hosted on the static server
|
||||
(e.g., `https://static.oneskyit.com/c/ISHLT/css/badges_custom_ishlt.css`).
|
||||
|
||||
The print page (`print/+page.svelte`) or the badge view should conditionally add a
|
||||
`<link>` element via `<svelte:head>` when `style_href` is populated:
|
||||
|
||||
```svelte
|
||||
<svelte:head>
|
||||
{#if $lq__event_badge_template_obj?.style_href}
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href={$lq__event_badge_template_obj.style_href}
|
||||
/>
|
||||
{/if}
|
||||
</svelte:head>
|
||||
```
|
||||
|
||||
This is not yet implemented — tracked as a pending Phase 1 item.
|
||||
|
||||
### CSS Scope
|
||||
|
||||
External badge CSS should scope all rules under `.badge_front`, `.badge_back`, etc.
|
||||
to avoid bleeding into the rest of the app. The classes used in
|
||||
`ae_comp__badge_obj_view.svelte` are the canonical hook points:
|
||||
|
||||
- `.badge_front` — entire front card
|
||||
- `.badge_back` — entire back card
|
||||
- `.badge_header` — front header area
|
||||
- `.badge_body` — front content area (name, title, affiliations, location)
|
||||
- `.badge_footer` — front footer stripe
|
||||
- `.badge_back_header` — back header area
|
||||
- `.badge_back_content` — back content area
|
||||
- `.badge_footer_center.<code>` — footer text per badge type code (for color stripes)
|
||||
|
||||
### layout field
|
||||
|
||||
The `layout` field encodes physical badge stock dimensions. Standard codes to use:
|
||||
|
||||
| Code | Dimensions | Description |
|
||||
|---|---|---|
|
||||
| `badge_3.5x5.5_pvc` | 3.5" × 5.5" (88.9 × 139.7mm) | PVC card, Zebra ZC10L |
|
||||
| `badge_4x6_fanfold` | 4" × 6" (101.6 × 152.4mm) | Fanfold paper badge |
|
||||
| `badge_4x6_fanfold_tickets` | 4" × 6" + tear-offs | Fanfold with ticket stubs |
|
||||
|
||||
The current badge component is hard-coded to `w-[4in]` / `min-h-[6.0in]`. These
|
||||
dimensions will need to be made template-driven once the `layout` field is wired up.
|
||||
For now, override via print CSS in the `style_href` stylesheet.
|
||||
|
||||
---
|
||||
|
||||
## Template-Derived Features (component behavior)
|
||||
|
||||
### badge_type_list → badge type select
|
||||
The badge type dropdown shown when editing a badge is derived from the template's
|
||||
`badge_type_list` JSON, not a hardcoded list. This was a bug (fixed 2026-03-02).
|
||||
See `ae_comp__badge_obj_view.svelte` — `badge_type_code_li` is now `$derived.by()`.
|
||||
|
||||
### "Info section" flags (exhibitor_info, presenter_info, etc.)
|
||||
These flags (`exhibitor_info`, `presenter_info`, `staff_info`, `vip_info`, `vote_info`)
|
||||
**do not exist as DB columns**. They appeared as placeholder `{#if}` blocks in the
|
||||
badge view component from Flask-era development and were never implemented.
|
||||
|
||||
The correct approach is **one template per badge audience** — an Exhibitor template will
|
||||
have exhibitor-specific `badge_type_list`, header images, and CSS. No flags needed.
|
||||
|
||||
The dead `{#if $lq__event_badge_template_obj.exhibitor_info}` blocks in
|
||||
`ae_comp__badge_obj_view.svelte` should be removed in a future cleanup pass.
|
||||
|
||||
---
|
||||
|
||||
## Properties Saved to IDB (Dexie)
|
||||
|
||||
The `properties_to_save` array in `ae_events__event_badge_template.ts` controls what
|
||||
gets cached locally. Current state — fields **NOT** in properties_to_save that exist
|
||||
in DB and may be needed:
|
||||
|
||||
- `style_href` — needed once external CSS is wired via `<svelte:head>`
|
||||
- `passcode` — not needed client-side
|
||||
- `footer_title`, `footer_left`, `footer_right` — not needed (legacy)
|
||||
- `header_background`, `footer_background` — not needed (legacy)
|
||||
- `script_src` — do not add; this field should not be used
|
||||
- `duplex` — **add when backend adds the field**
|
||||
|
||||
---
|
||||
|
||||
## Standard Template Setup (per event)
|
||||
|
||||
### 1. General Attendees template
|
||||
- `header_path`: event-specific conference header image
|
||||
- `secondary_header_path`: back-of-badge header (often same or related image)
|
||||
- `wireless_ssid` + `wireless_password`: venue WiFi
|
||||
- `show_qr_back`: `1` (back QR is standard for most events)
|
||||
- `show_qr_front`: `0` (usually off for front)
|
||||
- `badge_type_list`: full list of member/guest/staff/test types
|
||||
- `ticket_list` + `ticket_N_text`: event-specific tickets if applicable
|
||||
- `style_href`: client-specific CSS URL
|
||||
- `layout`: appropriate layout code
|
||||
- `duplex`: `1` (or `0` for single-sided events like Axonius 2026)
|
||||
|
||||
### 2. Workshop / Pre-conference template
|
||||
- Same as above but with workshop-specific header images
|
||||
- `badge_type_list`: reduced list (workshop-relevant types only)
|
||||
- `ticket_list`: may be empty `[]`
|
||||
- `duplex`: match main template
|
||||
|
||||
### 3. Exhibitor template
|
||||
- Exhibitor-specific header images
|
||||
- `badge_type_list`: exhibitor-only types (`ex_all`, `ex_booth`, `guest`, `staff`, `test`)
|
||||
- `ticket_list`: `[]` (exhibitors typically don't have event tickets)
|
||||
- `show_qr_back`: may be `0` (exhibitors scan others, they don't need their own QR prominent)
|
||||
- `wireless_ssid` + `wireless_password`: same venue WiFi
|
||||
|
||||
---
|
||||
|
||||
## Related Files
|
||||
|
||||
| File | Role |
|
||||
|---|---|
|
||||
| `ae_events__event_badge_template.ts` | API + IDB functions; `properties_to_save` |
|
||||
| `db_events.ts` | Dexie schema for `badge_template` table |
|
||||
| `templates/+page.svelte` | Template list + create/edit/delete UI |
|
||||
| `templates/ae_comp__badge_template_form.svelte` | Template create/edit form |
|
||||
| `[badge_id]/ae_comp__badge_obj_view.svelte` | Badge render — consumes template data |
|
||||
| `[badge_id]/print/+page.svelte` | Print page — loads template, hosts `<svelte:head>` CSS |
|
||||
| `documentation/MODULE__AE_Events_Badges.md` | Badge object reference |
|
||||
|
||||
---
|
||||
|
||||
## Pending / TODO
|
||||
|
||||
- [ ] Wire `style_href` via `<svelte:head>` in print page
|
||||
- [ ] Add `duplex` to `properties_to_save` once backend field exists
|
||||
- [ ] Add `duplex`-driven `print:hidden` to `badge_back` section in `ae_comp__badge_obj_view.svelte`
|
||||
- [ ] Make `layout` field drive actual card dimensions in the badge component (currently hard-coded)
|
||||
- [ ] Remove dead `exhibitor_info` / `presenter_info` / `staff_info` / `vip_info` / `vote_info` `{#if}` blocks from `ae_comp__badge_obj_view.svelte`
|
||||
- [ ] Improve `ae_comp__badge_template_form.svelte` to edit all relevant fields (currently minimal)
|
||||
@@ -65,22 +65,18 @@
|
||||
// let ae_tmp: key_val = $state({});
|
||||
let ae_triggers: key_val = $state({});
|
||||
|
||||
// ISHLT 2024 badge type codes
|
||||
let badge_type_code_li = [
|
||||
{ code: 'current_member', name: 'Member' },
|
||||
{ code: 'inactive_member', name: 'Non-Member' },
|
||||
{ code: 'current_member_trainee', name: 'Trainee Member' },
|
||||
{ code: 'inactive_member_trainee', name: 'Trainee Non-Member' },
|
||||
{ code: 'ex_all', name: 'Exhibitor All Access' },
|
||||
{ code: 'ex_booth', name: 'Exhibitor Booth Staff' },
|
||||
{ code: 'hftx', name: 'HFTX Master Academy' },
|
||||
{ code: 'mcs', name: 'MCS Master Academy' },
|
||||
{ code: 'pediatric', name: 'Pediatric' },
|
||||
{ code: 'guest', name: 'Guest' },
|
||||
{ code: 'staff', name: 'Staff' },
|
||||
{ code: 'volunteer', name: 'Volunteer' },
|
||||
{ code: 'test', name: 'Test' }
|
||||
];
|
||||
// Badge type list is derived from the template — each event/template defines its own set.
|
||||
// Falls back to empty array if template not loaded or badge_type_list is invalid JSON.
|
||||
let badge_type_code_li = $derived.by(() => {
|
||||
const raw = $lq__event_badge_template_obj?.badge_type_list;
|
||||
if (!raw) return [];
|
||||
try {
|
||||
return JSON.parse(raw);
|
||||
} catch {
|
||||
console.warn('badge_type_list is not valid JSON:', raw);
|
||||
return [];
|
||||
}
|
||||
});
|
||||
|
||||
// *** Set initial variables
|
||||
$effect(() => {
|
||||
@@ -999,7 +995,7 @@ onkeypress={() => {
|
||||
"
|
||||
title={editable_badge_type_code}
|
||||
>
|
||||
{#if edit_mode_active && badge_type_code_li}
|
||||
{#if edit_mode_active && badge_type_code_li.length > 0}
|
||||
<label
|
||||
>Badge Type:
|
||||
<select
|
||||
|
||||
Reference in New Issue
Block a user