2 Commits

Author SHA1 Message Date
Scott Idem
acf0a13955 fix(badges): update badge type list and fix filter-only search
Update badge type codes for Axonius 2026 (replaces ISHLT 2024 list).
Added TODO to drive this from event templates in the future.

Fix printed status and badge type filters not working without a text
query. The min_chars guard was blocking all filter-only searches,
causing "Printed" and "Not Printed" to always return empty results.
Now bypasses min_chars when any non-default filter is active (printed
status, type code, or affiliations), since selecting a filter is
explicit user intent regardless of the text query.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-13 18:42:04 -04:00
Scott Idem
5826b21821 feat(badges): allow public_access to print first-print badges
Badge print kiosks authenticate at the public_access level (site-wide
passcode). Previously the print gate was trusted_access, meaning kiosk
operators had to sign in at the trusted level just to print.

Changed in both the list view and the badge detail controls panel:
- First print: public_access and above (kiosk use case)
- Reprint: still requires trusted_access + edit_mode

ae_comp__badge_obj_li.svelte: added is_public derived; updated
can_print and the print button #if condition.

ae_comp__badge_print_controls.svelte: added is_public derived; updated
can_print comment and logic.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-13 18:01:47 -04:00
4 changed files with 22 additions and 18 deletions

View File

@@ -218,7 +218,10 @@ async function handle_search_refresh(params: any) {
const min_chars = params.min_chars;
// Defense-in-depth: enforce min_chars even if the search component lets one through.
if (qry_str.length < min_chars) {
// Exception: if the user has set a non-default filter (printed status, type, affiliations),
// that is an explicit intent — run the search even without a text query.
const has_active_filters = printed_status !== 'all' || !!type_code || !!aff_str;
if (qry_str.length < min_chars && !has_active_filters) {
untrack(() => {
event_badge_id_li = [];
$events_sess.badges.search_status = 'done';

View File

@@ -99,6 +99,9 @@ let {
// 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.
let is_trusted = $derived($ae_loc.trusted_access === true);
// public_access — site-wide passcode or higher. Badge print kiosks run at this level.
// Sufficient for first-print only; reprints require trusted + edit_mode.
let is_public = $derived($ae_loc.public_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);
@@ -200,8 +203,9 @@ function toggle_section(which: 'attendee' | 'staff') {
let print_count = $derived($lq__event_badge_obj?.print_count ?? 0);
let is_printed = $derived(print_count >= 1);
// Print is available to Trusted+ when: not yet printed, OR global edit mode is on (reprint).
let can_print = $derived(is_trusted && (!is_printed || is_global_edit_mode));
// Print is available to Public+ for first prints (kiosk use case).
// Reprints require Trusted + global edit mode.
let can_print = $derived((is_public && !is_printed) || (is_trusted && is_global_edit_mode));
type PrintStatus = 'idle' | 'loading' | 'done' | 'error';
let print_status: PrintStatus = $state('idle');

View File

@@ -40,6 +40,7 @@ let copy_status: Record<string, 'idle' | 'copied'> = $state({});
// Access level shortcuts
let is_trusted = $derived($ae_loc.trusted_access === true);
let is_public = $derived($ae_loc.public_access === true); // public passcode or higher — may print first prints
let is_edit_mode = $derived($ae_loc.edit_mode === true);
/**
@@ -140,7 +141,7 @@ let visible_badge_obj_li = $derived(
event_badge_obj?.full_name_override ??
event_badge_obj?.full_name ??
`${event_badge_obj?.given_name ?? ''} ${event_badge_obj?.family_name ?? ''}`.trim()}
{@const can_print = is_trusted && (!is_printed || is_edit_mode)}
{@const can_print = (is_public && !is_printed) || (is_trusted && is_edit_mode)}
{@const print_href = `/events/${event_badge_obj?.event_id}/badges/${event_badge_obj?.event_badge_id}/print`}
{@const review_href = build_review_url(event_badge_obj)}
@@ -242,8 +243,8 @@ let visible_badge_obj_li = $derived(
<!-- Right: up to 4 action buttons -->
<div class="flex shrink-0 flex-row items-center gap-1">
<!-- 1. Print Badge: Trusted+, not yet printed OR in Edit Mode (reprint) -->
{#if is_trusted && (!is_printed || is_edit_mode)}
<!-- 1. Print Badge: Public+ first print; Trusted + Edit Mode for reprint -->
{#if (is_public && !is_printed) || (is_trusted && is_edit_mode)}
<a
href={`/events/${event_badge_obj?.event_id}/badges/${event_badge_obj?.event_badge_id}/print`}
class="hover:text-primary-800-200 hover:bg-primary-200-800 active:bg-surface-200-700 flex items-center gap-1 px-3 py-2 text-base font-bold transition-colors duration-1000 hover:duration-300 min-w-0 preset-tonal-primary rounded-lg border border-primary-200-800

View File

@@ -32,21 +32,17 @@ import { ae_util } from '$lib/ae_utils/ae_utils';
// })
// );
// ISHLT 2024 badge type codes
// Axonius 2026 badge type codes
// TODO: drive this from the event's badge templates so it's event-agnostic
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: 'attendee', name: 'In-Person Attendee' },
{ code: 'sponsor', name: 'Adapt26 Sponsor' },
{ code: 'test', name: 'Test' },
{ code: 'staff', name: 'Staff' },
{ code: 'guest', name: 'Guest' },
{ code: 'volunteer', name: 'Volunteer' },
{ code: 'test', name: 'Test' }
{ code: 'member', name: 'Member' },
{ code: 'nonmember', name: 'Non-Member' },
];
// Resolve the minimum characters required for the current user's access tier.