feat(badges): print/review pages, 4-button list, Lucide icons, permissions doc

Badge search results list (ae_comp__badge_obj_li):
- 4 action buttons per row: Print, Review (nav link), Copy Link (clipboard), Email Link
- Visibility rules: unprinted-only for non-edit mode; all non-hidden for trusted+edit
- Plain name display (User/EyeOff icon) — name is no longer a print link
- Obscured email for non-trusted users
- Debug row (ID, CR, UP, PC, FP, LP) in edit mode
- All icons converted to Lucide (Font Awesome removed)

Badge print page (/print):
- 3 header action buttons: Print Now, Review (nav), Email Link
- Removed old [badge_id]/+page.svelte placeholder (moved to trash)
- Added is_trusted, is_edit_mode, print state derived vars
- "Already printed Nx — last [timestamp]" warning inline with name
- Removed unused imports (browser, onMount, events_slct)

Badge review page (/review):
- 3 header action buttons: Print (nav), Copy Link (clipboard), Email Link
- Added events_loc for email placeholder + title event name
- Added is_edit_mode, print_count, is_printed, copy_status
- FA icons replaced with Lucide (ShieldCheck, UserCheck, User)
- Title now includes event name (was missing)

Infrastructure:
- print/+page.ts and review/+page.ts added (non-blocking badge loaders)
- ae_comp__badge_review_form.svelte stub created (fields pending)
- Fixed: components no longer write to $ae_loc.edit_mode (critical bug)

Docs:
- NEW: AE__Permissions_and_Security.md — full permissions hierarchy reference
- NEW: PROJECT__AE_Events_Badges_Review_Print.md — agent task brief for review form + print font controls
- UPDATED: MODULE__AE_Events_Badges.md rev 5 — field permissions spec, header buttons, still-needed list by priority

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Scott Idem
2026-02-27 15:12:22 -05:00
parent ee500a9ad5
commit c4e85b1fe3
15 changed files with 2046 additions and 412 deletions

View File

@@ -38,7 +38,7 @@
let show_upload_badge_modal: boolean = $state(false);
// Guard: Only allow administrators in edit mode
if (!$ae_loc.administrator_access || !$ae_loc.edit_mode) {
if (!$ae_loc.administrator_access) {
if (browser) {
alert(
'Access Denied: Administrative privileges and Edit Mode required.'

View File

@@ -8,6 +8,73 @@
let { mod_badges_json = $bindable({}), onsave }: Props = $props();
/**
* edit_permissions — controls which fields each access level may edit in the badge review form.
* Stored as mod_badges_json.edit_permissions.
*
* Structure:
* authenticated.can_edit — fields attendees (passcode-validated) may edit
* trusted.can_edit — fields trusted staff may edit
* administrator.can_edit — '*' (all) or a specific field list
*
* Default attendee fields: full_name_override, professional_title_override,
* affiliations_override, location_override
* Default trusted fields: above + email, badge_type_code
*/
const all_attendee_fields = [
{ key: 'full_name_override', label: 'Full Name (override)' },
{ key: 'professional_title_override', label: 'Professional Title (override)' },
{ key: 'affiliations_override', label: 'Affiliations (override)' },
{ key: 'location_override', label: 'Location (override)' }
];
const all_staff_fields = [
...all_attendee_fields,
{ key: 'email', label: 'Email' },
{ key: 'badge_type_code', label: 'Badge Type Code' }
];
// Ensure edit_permissions sub-object exists
function ensure_permissions() {
if (!mod_badges_json) return;
if (!mod_badges_json.edit_permissions) {
mod_badges_json.edit_permissions = {};
}
if (!mod_badges_json.edit_permissions.authenticated) {
mod_badges_json.edit_permissions.authenticated = {
can_edit: ['full_name_override', 'professional_title_override', 'affiliations_override', 'location_override']
};
}
if (!mod_badges_json.edit_permissions.trusted) {
mod_badges_json.edit_permissions.trusted = {
can_edit: ['full_name_override', 'professional_title_override', 'affiliations_override', 'location_override', 'email', 'badge_type_code']
};
}
if (!mod_badges_json.edit_permissions.administrator) {
mod_badges_json.edit_permissions.administrator = { can_edit: '*' };
}
}
function is_field_enabled(level: 'authenticated' | 'trusted', field_key: string): boolean {
const cfg = mod_badges_json?.edit_permissions?.[level]?.can_edit;
if (!cfg) return false;
if (cfg === '*') return true;
return Array.isArray(cfg) && cfg.includes(field_key);
}
function toggle_field(level: 'authenticated' | 'trusted', field_key: string) {
ensure_permissions();
if (!mod_badges_json?.edit_permissions?.[level]) return;
let fields: string[] = mod_badges_json.edit_permissions[level].can_edit;
if (!Array.isArray(fields)) fields = [];
if (fields.includes(field_key)) {
mod_badges_json.edit_permissions[level].can_edit = fields.filter((f: string) => f !== field_key);
} else {
mod_badges_json.edit_permissions[level].can_edit = [...fields, field_key];
}
}
function save() {
if (onsave && mod_badges_json) onsave(mod_badges_json);
}
@@ -100,7 +167,61 @@
</label>
</div>
</div>
{/if}
<details class="space-y-3">
<summary class="cursor-pointer font-medium text-sm">
Badge Review — Editable Field Permissions
</summary>
<div class="space-y-4 pt-2 pl-2">
<p class="text-xs text-gray-500">
Controls which fields each access level may edit on the Badge Review page.
Staff (Trusted) defaults include all attendee fields plus Email and Badge Type Code.
Administrators can always edit everything.
</p>
<!-- Attendee (passcode-validated) -->
<div class="space-y-1">
<p class="text-sm font-medium">Attendees (passcode link)</p>
<div class="grid grid-cols-2 gap-x-4 gap-y-1">
{#each all_attendee_fields as field}
<label class="label flex items-center gap-2 text-sm">
<input
type="checkbox"
class="checkbox"
checked={is_field_enabled('authenticated', field.key)}
onchange={() => toggle_field('authenticated', field.key)}
/>
<span>{field.label}</span>
</label>
{/each}
</div>
</div>
<!-- Staff (Trusted) -->
<div class="space-y-1">
<p class="text-sm font-medium">Staff (Trusted access)</p>
<div class="grid grid-cols-2 gap-x-4 gap-y-1">
{#each all_staff_fields as field}
<label class="label flex items-center gap-2 text-sm">
<input
type="checkbox"
class="checkbox"
checked={is_field_enabled('trusted', field.key)}
onchange={() => toggle_field('trusted', field.key)}
/>
<span>{field.label}</span>
</label>
{/each}
</div>
</div>
<!-- Administrator note -->
<p class="text-xs text-gray-400 italic">
Administrators always have access to all fields (not configurable).
</p>
</div>
</details>
{/if} <!-- end {#if mod_badges_json} -->
<button type="button" class="btn preset-tonal-primary" onclick={save}
>Save</button