From da3b8dcf461ca5f07f5d2be1e70bccc355278ebf Mon Sep 17 00:00:00 2001 From: Scott Idem Date: Thu, 19 Mar 2026 19:04:40 -0400 Subject: [PATCH] =?UTF-8?q?feat:=20badge=20print=20controls=20=E2=80=94=20?= =?UTF-8?q?per-template=20field=20visibility=20and=20edit=20access=20via?= =?UTF-8?q?=20other=5Fjson.controls=5Fcfg?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add field_shown() and field_editable() functions driven by event_badge_template.other_json: controls_cfg: { shown?: string[], auth_editable?: string[] } Access rules: - No authenticated_access → display-only, no edit buttons shown - authenticated only → can edit fields in auth_editable (default: title/affiliations/location/allow_tracking/pronouns) - trusted + edit_mode → always sees and edits all fields, ignores config Each attendee field card (name, title, affiliations, location, allow_tracking, pronouns) is now wrapped in {#if field_shown()} and its edit button/accordion gated by field_editable(). No backend changes needed — other_json is an existing longtext JSON column. Co-Authored-By: Claude Sonnet 4.6 --- .../ae_comp__badge_print_controls.svelte | 198 ++++++++++++------ 1 file changed, 130 insertions(+), 68 deletions(-) diff --git a/src/routes/events/[event_id]/(badges)/badges/[badge_id]/ae_comp__badge_print_controls.svelte b/src/routes/events/[event_id]/(badges)/badges/[badge_id]/ae_comp__badge_print_controls.svelte index acd17dcb..825fc700 100644 --- a/src/routes/events/[event_id]/(badges)/badges/[badge_id]/ae_comp__badge_print_controls.svelte +++ b/src/routes/events/[event_id]/(badges)/badges/[badge_id]/ae_comp__badge_print_controls.svelte @@ -86,9 +86,9 @@ // --- Access level --- // trusted_access is true for Trusted and every level above it (Administrator, // Manager, Super). No need to OR in administrator_access — it's already covered. - // Basic authenticated users (trusted_access === false) can only edit - // professional_title_override, affiliations_override, and location_override. let is_trusted = $derived($ae_loc.trusted_access === true); + // Minimum bar to edit any field. Anonymous/public users see values but cannot edit. + let is_auth = $derived($ae_loc.authenticated_access === true); // IMPORTANT: $ae_loc.edit_mode is the GLOBAL AE Edit Mode — a UI preference that // reveals editable fields, debug info, and advanced options across the whole app. @@ -98,6 +98,46 @@ // — used here to allow reprinting an already-printed badge when global edit mode is active. let is_global_edit_mode = $derived($ae_loc.edit_mode === true); + // --- Per-template controls config --- + // Stored in event_badge_template.other_json as { controls_cfg: { shown?, auth_editable? } }. + // shown: array of field keys to render; omit/null = all shown. + // auth_editable: fields editable by authenticated (non-trusted) users; omit/null = defaults below. + // trusted + edit_mode always overrides config — sees and edits all fields. + // WHY: allows locking down a production template so attendees can only touch the + // fields the event coordinator explicitly permits, without touching the backend. + interface ControlsCfg { shown?: string[]; auth_editable?: string[]; } + let template_controls_cfg = $derived((() => { + try { + const parsed = JSON.parse($lq__event_badge_template_obj?.other_json ?? '{}'); + return (parsed?.controls_cfg ?? null) as ControlsCfg | null; + } catch { return null; } + })()); + + // Default auth-editable fields when the template has no explicit config. + // Covers the fields an attendee most commonly needs to fix at the badge table. + const DEFAULT_AUTH_EDITABLE = ['title', 'affiliations', 'location', 'allow_tracking', 'pronouns']; + + /** Is this field card shown in the panel at all? trusted+edit always sees all fields. */ + function field_shown(field: string): boolean { + if (is_trusted && is_global_edit_mode) return true; + const cfg = template_controls_cfg; + if (!cfg?.shown) return true; + return cfg.shown.includes(field); + } + + /** + * Can the current user edit this field? + * - No authenticated_access → never (display only). + * - trusted + edit_mode → always. + * - authenticated only → field must be in template's auth_editable list (or defaults). + */ + function field_editable(field: string): boolean { + if (!is_auth) return false; + if (is_trusted && is_global_edit_mode) return true; + const auth_editable = template_controls_cfg?.auth_editable ?? DEFAULT_AUTH_EDITABLE; + return auth_editable.includes(field); + } + // --- Section collapse state --- // Attendee fields start open (primary use); staff starts closed to save space. // Toggling one collapses the other (auto-collapse, same pattern as launcher sections). @@ -597,7 +637,9 @@ + Edit form: gated by field_editable('name') — by default trusted+edit only; + can be opened to authenticated users via template controls_cfg.auth_editable. --> + {#if field_shown('name')}
@@ -605,10 +647,10 @@ {#if get_display('full_name_override', 'full_name')}

{get_display('full_name_override', 'full_name')}

{:else} -

{is_trusted ? 'Tap ✎ to add' : 'Not set'}

+

{field_editable('name') ? 'Tap ✎ to add' : 'Not set'}

{/if}
- {#if is_trusted} + {#if field_editable('name')} + {#if field_editable('title')} + + {/if}
{@render font_ctrl('title')} -
+
{#if is_global_edit_mode} @@ -708,8 +753,10 @@
+ {/if} + {#if field_shown('affiliations')}
@@ -720,20 +767,22 @@

Tap ✎ to add

{/if}
- + {#if field_editable('affiliations')} + + {/if}
{@render font_ctrl('affiliations')} -
+
{#if is_global_edit_mode} @@ -761,8 +810,10 @@
+ {/if} + {#if field_shown('location')}
@@ -773,20 +824,22 @@

Tap ✎ to add

{/if}
- + {#if field_editable('location')} + + {/if}
{@render font_ctrl('location')} -
+
{#if is_global_edit_mode} @@ -814,8 +867,10 @@
+ {/if} + {#if field_shown('allow_tracking')}
@@ -824,19 +879,21 @@ {$lq__event_badge_obj?.allow_tracking ? 'Allowed' : 'Not allowed'}

- + {#if field_editable('allow_tracking')} + + {/if}
-
+
+ {/if} + {#if field_shown('pronouns')}
@@ -910,19 +969,21 @@

Tap ✎ to add

{/if}
- + {#if field_editable('pronouns')} + + {/if}
-
+
+ {/if}