From 827c7ac62efd718ba7b04af1aba05df188f42ea9 Mon Sep 17 00:00:00 2001 From: Scott Idem Date: Mon, 2 Mar 2026 15:43:49 -0500 Subject: [PATCH] fix(badges): wire badge_type_list from template instead of hardcoded ISHLT list MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../MODULE__AE_Events_Badge_Templates.md | 276 ++++++++++++++++++ .../[badge_id]/ae_comp__badge_obj_view.svelte | 30 +- 2 files changed, 289 insertions(+), 17 deletions(-) create mode 100644 documentation/MODULE__AE_Events_Badge_Templates.md diff --git a/documentation/MODULE__AE_Events_Badge_Templates.md b/documentation/MODULE__AE_Events_Badge_Templates.md new file mode 100644 index 00000000..6d7bea23 --- /dev/null +++ b/documentation/MODULE__AE_Events_Badge_Templates.md @@ -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 +`` element via `` when `style_href` is populated: + +```svelte + + {#if $lq__event_badge_template_obj?.style_href} + + {/if} + +``` + +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.` — 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 `` +- `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 `` CSS | +| `documentation/MODULE__AE_Events_Badges.md` | Badge object reference | + +--- + +## Pending / TODO + +- [ ] Wire `style_href` via `` 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) diff --git a/src/routes/events/[event_id]/(badges)/badges/[badge_id]/ae_comp__badge_obj_view.svelte b/src/routes/events/[event_id]/(badges)/badges/[badge_id]/ae_comp__badge_obj_view.svelte index d990b518..c0101dc6 100644 --- a/src/routes/events/[event_id]/(badges)/badges/[badge_id]/ae_comp__badge_obj_view.svelte +++ b/src/routes/events/[event_id]/(badges)/badges/[badge_id]/ae_comp__badge_obj_view.svelte @@ -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}