Badges: controls panel UX polish — remove identity card, elevate print btn, style buttons
This commit is contained in:
@@ -239,49 +239,50 @@
|
||||
Field card snippets
|
||||
============================================================ -->
|
||||
|
||||
<!-- Reusable font size controls row -->
|
||||
<!-- Compact font size row. "Font" text label removed (sr-only for a11y).
|
||||
Only +/−/value/reset visible — saves vertical space in the tight panel. -->
|
||||
{#snippet font_ctrl(key: 'name' | 'title' | 'affiliations' | 'location')}
|
||||
{@const cur =
|
||||
key === 'name' ? font_size_name :
|
||||
key === 'title' ? font_size_title :
|
||||
key === 'affiliations' ? font_size_affiliations : font_size_location}
|
||||
<div class="flex items-center gap-1 border-t border-gray-100 dark:border-gray-800 px-3 py-1.5"
|
||||
<div class="flex items-center gap-1 border-t border-gray-100 dark:border-gray-800 px-2 py-1"
|
||||
role="group"
|
||||
aria-label="{key} font size controls"
|
||||
>
|
||||
<span class="text-[10px] text-gray-400 uppercase w-8 shrink-0">Font</span>
|
||||
<span class="sr-only">Font size for {key}:</span>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-xs px-2 min-h-0 h-7 text-base leading-none"
|
||||
class="btn btn-xs preset-tonal-surface px-2 min-h-0 h-6 text-base leading-none"
|
||||
onclick={() => font_size_adjust(key, -FONT_SIZE_STEP)}
|
||||
disabled={cur !== null && cur <= FONT_SIZE_MIN[key]}
|
||||
title="Decrease {key} font size"
|
||||
title="Smaller font"
|
||||
aria-label="Decrease {key} font size"
|
||||
>−</button>
|
||||
<span class="font-mono text-xs w-12 text-center tabular-nums" aria-live="polite">
|
||||
<span class="font-mono text-[11px] w-9 text-center tabular-nums text-gray-500" aria-live="polite">
|
||||
{cur !== null ? `${cur}px` : 'Auto'}
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-xs px-2 min-h-0 h-7 text-base leading-none"
|
||||
class="btn btn-xs preset-tonal-surface px-2 min-h-0 h-6 text-base leading-none"
|
||||
onclick={() => font_size_adjust(key, FONT_SIZE_STEP)}
|
||||
disabled={cur !== null && cur >= FONT_SIZE_MAX[key]}
|
||||
title="Increase {key} font size"
|
||||
title="Larger font"
|
||||
aria-label="Increase {key} font size"
|
||||
>+</button>
|
||||
{#if cur !== null}
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-xs px-1.5 min-h-0 h-7 opacity-60"
|
||||
class="btn btn-xs preset-tonal-surface px-1.5 min-h-0 h-6 opacity-60"
|
||||
onclick={() => font_size_reset(key)}
|
||||
title="Reset {key} font size to auto"
|
||||
title="Reset to auto"
|
||||
aria-label="Reset {key} font size to auto"
|
||||
>↺</button>
|
||||
{/if}
|
||||
</div>
|
||||
{/snippet}
|
||||
|
||||
<!-- Reusable save/cancel buttons (used inside accordion edit forms) -->
|
||||
<!-- Save/cancel row (inside accordion edit forms) -->
|
||||
{#snippet field_actions(field_key: string, on_save: () => void, on_cancel: () => void)}
|
||||
<div class="flex gap-2 mt-2">
|
||||
<button
|
||||
@@ -292,7 +293,7 @@
|
||||
class:preset-tonal-error={field_save_status[field_key] === 'error'}
|
||||
disabled={field_save_status[field_key] === 'saving'}
|
||||
onclick={on_save}
|
||||
title="Save changes to this field"
|
||||
title="Save changes"
|
||||
aria-label="Save changes"
|
||||
>
|
||||
{#if field_save_status[field_key] === 'saving'}
|
||||
@@ -309,7 +310,7 @@
|
||||
type="button"
|
||||
class="btn btn-sm preset-tonal-surface"
|
||||
onclick={on_cancel}
|
||||
title="Cancel editing"
|
||||
title="Cancel"
|
||||
aria-label="Cancel editing"
|
||||
><X size="14" /></button>
|
||||
</div>
|
||||
@@ -319,31 +320,14 @@
|
||||
Main panel
|
||||
============================================================ -->
|
||||
|
||||
<!-- Identity card: quick visual confirmation of who this badge is for.
|
||||
Helps volunteers confirm they have the right badge before printing. -->
|
||||
{#if $lq__event_badge_obj}
|
||||
<div class="mb-3 pb-3 border-b border-gray-200 dark:border-gray-700">
|
||||
<p class="text-[10px] uppercase tracking-widest text-gray-400 font-semibold mb-1">Badge Station</p>
|
||||
<p class="font-bold text-sm leading-snug truncate">
|
||||
{$lq__event_badge_obj.full_name_override ?? $lq__event_badge_obj.full_name ?? '—'}
|
||||
</p>
|
||||
{#if badge_type_display}
|
||||
<p class="text-xs text-gray-500 mt-0.5">{badge_type_display}</p>
|
||||
{/if}
|
||||
<p class="text-[10px] font-mono text-gray-300 dark:text-gray-600 mt-0.5 uppercase tracking-wider">
|
||||
#{$lq__event_badge_obj.event_badge_id}
|
||||
</p>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Print button — canonical print action. Increments print_count, fires
|
||||
window.print(), then navigates back to badge search. Only shown to Trusted+
|
||||
when not yet printed, OR when global AE Edit Mode is active (allows reprints). -->
|
||||
<!-- Print button — canonical action: increments print_count, fires window.print(),
|
||||
then navigates back to badge search. Trusted+ only; edit mode allows reprints. -->
|
||||
{#if can_print}
|
||||
<div class="mb-3 pb-3 border-b border-gray-200 dark:border-gray-700">
|
||||
<!-- Tinted card visually lifts the print action above the field list -->
|
||||
<div class="mb-3 p-2 rounded-lg bg-primary-50/40 dark:bg-primary-950/20 border border-primary-200/50 dark:border-primary-800/30">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm w-full flex items-center justify-center gap-2"
|
||||
class="btn w-full flex items-center justify-center gap-2"
|
||||
class:preset-filled-primary={print_status === 'idle'}
|
||||
class:preset-tonal-surface={print_status === 'loading'}
|
||||
class:preset-filled-success={print_status === 'done'}
|
||||
@@ -372,27 +356,27 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="space-y-2 text-sm">
|
||||
<div class="space-y-1.5 text-sm">
|
||||
|
||||
<!-- Section header: fields the attendee can review/edit at the kiosk -->
|
||||
<p class="text-[9px] uppercase tracking-widest text-gray-300 dark:text-gray-600 font-semibold px-1 pb-0.5">Attendee info</p>
|
||||
<p class="text-[9px] uppercase tracking-widest text-gray-300 dark:text-gray-600 font-semibold px-0.5 pb-0.5">Attendee info</p>
|
||||
|
||||
<!-- === NAME === -->
|
||||
<!-- Editable by Trusted+ only; all users have font controls -->
|
||||
<div class="border border-gray-200 dark:border-gray-700 rounded-lg overflow-hidden">
|
||||
<div class="flex items-start gap-2 px-3 pt-2 pb-1.5">
|
||||
<!-- === NAME ===
|
||||
Font controls: always visible (all can adjust for readability at kiosk).
|
||||
Edit form: Trusted+ only — accordion opens only when is_trusted. -->
|
||||
<div class="field-card rounded-lg overflow-hidden" class:field-card--active={active_field === 'name'}>
|
||||
<div class="flex items-center gap-2 px-2 pt-1.5 pb-1">
|
||||
<div class="flex-1 min-w-0">
|
||||
<p class="text-[10px] uppercase tracking-widest text-gray-400 font-semibold mb-0.5">Name</p>
|
||||
<p class="field-label">Name</p>
|
||||
{#if get_display('full_name_override', 'full_name')}
|
||||
<p class="font-bold leading-snug truncate">{get_display('full_name_override', 'full_name')}</p>
|
||||
{:else}
|
||||
<p class="text-gray-400 italic text-xs">Not set{is_trusted ? ' — tap ✎ to add' : ''}</p>
|
||||
<p class="text-gray-400 italic text-xs">{is_trusted ? 'Tap ✎ to add' : 'Not set'}</p>
|
||||
{/if}
|
||||
</div>
|
||||
{#if is_trusted}
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-xs preset-tonal-surface shrink-0 mt-0.5"
|
||||
class="btn btn-xs preset-tonal-primary shrink-0 transition-colors"
|
||||
onclick={() => toggle_field('name')}
|
||||
aria-expanded={active_field === 'name'}
|
||||
aria-controls="field-form-name"
|
||||
@@ -404,43 +388,48 @@
|
||||
{/if}
|
||||
</div>
|
||||
{@render font_ctrl('name')}
|
||||
{#if active_field === 'name' && is_trusted}
|
||||
<div id="field-form-name" class="px-3 pb-3 pt-2 border-t border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800">
|
||||
<label for="ctrl-full-name" class="text-xs text-gray-500 block mb-1">
|
||||
Name override <span class="text-gray-400">(empty = use original)</span>
|
||||
</label>
|
||||
<input
|
||||
id="ctrl-full-name"
|
||||
name="ctrl-full-name"
|
||||
type="text"
|
||||
class="input w-full"
|
||||
bind:value={edit_full_name_override}
|
||||
placeholder={$lq__event_badge_obj?.full_name ?? 'Full name'}
|
||||
/>
|
||||
{@render field_actions(
|
||||
'name',
|
||||
() => save_field('name', { full_name_override: edit_full_name_override || null }),
|
||||
() => cancel_field('name')
|
||||
)}
|
||||
<!-- Accordion drawer: grid-template-rows 0fr→1fr animates height smoothly.
|
||||
Always in DOM, only opens if is_trusted (edit button present above). -->
|
||||
<div class="ctrl-accordion" class:open={active_field === 'name' && is_trusted}>
|
||||
<div class="ctrl-accordion-inner">
|
||||
<div id="field-form-name" class="px-2 pb-2 pt-1.5 border-t border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800">
|
||||
{#if is_global_edit_mode}
|
||||
<label for="ctrl-full-name" class="text-[10px] text-gray-400 block mb-1">
|
||||
Name override — empty clears back to original
|
||||
</label>
|
||||
{/if}
|
||||
<input
|
||||
id="ctrl-full-name"
|
||||
name="ctrl-full-name"
|
||||
type="text"
|
||||
class="input w-full"
|
||||
bind:value={edit_full_name_override}
|
||||
placeholder={$lq__event_badge_obj?.full_name ?? 'Full name'}
|
||||
/>
|
||||
{@render field_actions(
|
||||
'name',
|
||||
() => save_field('name', { full_name_override: edit_full_name_override || null }),
|
||||
() => cancel_field('name')
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- === PROFESSIONAL TITLE === -->
|
||||
<!-- Editable by all authenticated users -->
|
||||
<div class="border border-gray-200 dark:border-gray-700 rounded-lg overflow-hidden">
|
||||
<div class="flex items-start gap-2 px-3 pt-2 pb-1.5">
|
||||
<div class="field-card rounded-lg overflow-hidden" class:field-card--active={active_field === 'title'}>
|
||||
<div class="flex items-center gap-2 px-2 pt-1.5 pb-1">
|
||||
<div class="flex-1 min-w-0">
|
||||
<p class="text-[10px] uppercase tracking-widest text-gray-400 font-semibold mb-0.5">Professional Title</p>
|
||||
<p class="field-label">Title</p>
|
||||
{#if get_display('professional_title_override', 'professional_title')}
|
||||
<p class="leading-snug truncate">{get_display('professional_title_override', 'professional_title')}</p>
|
||||
{:else}
|
||||
<p class="text-gray-400 italic text-xs">Not set — tap ✎ to add</p>
|
||||
<p class="text-gray-400 italic text-xs">Tap ✎ to add</p>
|
||||
{/if}
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-xs preset-tonal-surface shrink-0 mt-0.5"
|
||||
class="btn btn-xs preset-tonal-primary shrink-0 transition-colors"
|
||||
onclick={() => toggle_field('title')}
|
||||
aria-expanded={active_field === 'title'}
|
||||
aria-controls="field-form-title"
|
||||
@@ -451,43 +440,44 @@
|
||||
</button>
|
||||
</div>
|
||||
{@render font_ctrl('title')}
|
||||
{#if active_field === 'title'}
|
||||
<div id="field-form-title" class="px-3 pb-3 pt-2 border-t border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800">
|
||||
<label for="ctrl-pro-title" class="text-xs text-gray-500 block mb-1">
|
||||
Professional title override
|
||||
</label>
|
||||
<input
|
||||
id="ctrl-pro-title"
|
||||
name="ctrl-pro-title"
|
||||
type="text"
|
||||
class="input w-full"
|
||||
bind:value={edit_professional_title_override}
|
||||
placeholder={$lq__event_badge_obj?.professional_title ?? 'Professional title'}
|
||||
/>
|
||||
{@render field_actions(
|
||||
'title',
|
||||
() => save_field('title', { professional_title_override: edit_professional_title_override || null }),
|
||||
() => cancel_field('title')
|
||||
)}
|
||||
<div class="ctrl-accordion" class:open={active_field === 'title'}>
|
||||
<div class="ctrl-accordion-inner">
|
||||
<div id="field-form-title" class="px-2 pb-2 pt-1.5 border-t border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800">
|
||||
{#if is_global_edit_mode}
|
||||
<label for="ctrl-pro-title" class="text-[10px] text-gray-400 block mb-1">Professional title</label>
|
||||
{/if}
|
||||
<input
|
||||
id="ctrl-pro-title"
|
||||
name="ctrl-pro-title"
|
||||
type="text"
|
||||
class="input w-full"
|
||||
bind:value={edit_professional_title_override}
|
||||
placeholder={$lq__event_badge_obj?.professional_title ?? 'Professional title'}
|
||||
/>
|
||||
{@render field_actions(
|
||||
'title',
|
||||
() => save_field('title', { professional_title_override: edit_professional_title_override || null }),
|
||||
() => cancel_field('title')
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- === AFFILIATIONS === -->
|
||||
<!-- Editable by all authenticated users; textarea (multi-line) -->
|
||||
<div class="border border-gray-200 dark:border-gray-700 rounded-lg overflow-hidden">
|
||||
<div class="flex items-start gap-2 px-3 pt-2 pb-1.5">
|
||||
<div class="field-card rounded-lg overflow-hidden" class:field-card--active={active_field === 'affiliations'}>
|
||||
<div class="flex items-center gap-2 px-2 pt-1.5 pb-1">
|
||||
<div class="flex-1 min-w-0">
|
||||
<p class="text-[10px] uppercase tracking-widest text-gray-400 font-semibold mb-0.5">Affiliations</p>
|
||||
<p class="field-label">Affiliations</p>
|
||||
{#if get_display('affiliations_override', 'affiliations')}
|
||||
<p class="leading-snug line-clamp-2">{get_display('affiliations_override', 'affiliations')}</p>
|
||||
{:else}
|
||||
<p class="text-gray-400 italic text-xs">Not set — tap ✎ to add</p>
|
||||
<p class="text-gray-400 italic text-xs">Tap ✎ to add</p>
|
||||
{/if}
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-xs preset-tonal-surface shrink-0 mt-0.5"
|
||||
class="btn btn-xs preset-tonal-primary shrink-0 transition-colors"
|
||||
onclick={() => toggle_field('affiliations')}
|
||||
aria-expanded={active_field === 'affiliations'}
|
||||
aria-controls="field-form-affiliations"
|
||||
@@ -498,43 +488,44 @@
|
||||
</button>
|
||||
</div>
|
||||
{@render font_ctrl('affiliations')}
|
||||
{#if active_field === 'affiliations'}
|
||||
<div id="field-form-affiliations" class="px-3 pb-3 pt-2 border-t border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800">
|
||||
<label for="ctrl-affiliations" class="text-xs text-gray-500 block mb-1">
|
||||
Affiliations override
|
||||
</label>
|
||||
<textarea
|
||||
id="ctrl-affiliations"
|
||||
name="ctrl-affiliations"
|
||||
class="textarea w-full"
|
||||
rows="2"
|
||||
bind:value={edit_affiliations_override}
|
||||
placeholder={$lq__event_badge_obj?.affiliations ?? 'Organization / affiliations'}
|
||||
></textarea>
|
||||
{@render field_actions(
|
||||
'affiliations',
|
||||
() => save_field('affiliations', { affiliations_override: edit_affiliations_override || null }),
|
||||
() => cancel_field('affiliations')
|
||||
)}
|
||||
<div class="ctrl-accordion" class:open={active_field === 'affiliations'}>
|
||||
<div class="ctrl-accordion-inner">
|
||||
<div id="field-form-affiliations" class="px-2 pb-2 pt-1.5 border-t border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800">
|
||||
{#if is_global_edit_mode}
|
||||
<label for="ctrl-affiliations" class="text-[10px] text-gray-400 block mb-1">Affiliations / organization</label>
|
||||
{/if}
|
||||
<textarea
|
||||
id="ctrl-affiliations"
|
||||
name="ctrl-affiliations"
|
||||
class="textarea w-full"
|
||||
rows="2"
|
||||
bind:value={edit_affiliations_override}
|
||||
placeholder={$lq__event_badge_obj?.affiliations ?? 'Organization / affiliations'}
|
||||
></textarea>
|
||||
{@render field_actions(
|
||||
'affiliations',
|
||||
() => save_field('affiliations', { affiliations_override: edit_affiliations_override || null }),
|
||||
() => cancel_field('affiliations')
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- === LOCATION === -->
|
||||
<!-- Editable by all authenticated users -->
|
||||
<div class="border border-gray-200 dark:border-gray-700 rounded-lg overflow-hidden">
|
||||
<div class="flex items-start gap-2 px-3 pt-2 pb-1.5">
|
||||
<div class="field-card rounded-lg overflow-hidden" class:field-card--active={active_field === 'location'}>
|
||||
<div class="flex items-center gap-2 px-2 pt-1.5 pb-1">
|
||||
<div class="flex-1 min-w-0">
|
||||
<p class="text-[10px] uppercase tracking-widest text-gray-400 font-semibold mb-0.5">Location</p>
|
||||
<p class="field-label">Location</p>
|
||||
{#if get_display('location_override', 'location')}
|
||||
<p class="leading-snug truncate">{get_display('location_override', 'location')}</p>
|
||||
{:else}
|
||||
<p class="text-gray-400 italic text-xs">Not set — tap ✎ to add</p>
|
||||
<p class="text-gray-400 italic text-xs">Tap ✎ to add</p>
|
||||
{/if}
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-xs preset-tonal-surface shrink-0 mt-0.5"
|
||||
class="btn btn-xs preset-tonal-primary shrink-0 transition-colors"
|
||||
onclick={() => toggle_field('location')}
|
||||
aria-expanded={active_field === 'location'}
|
||||
aria-controls="field-form-location"
|
||||
@@ -545,86 +536,88 @@
|
||||
</button>
|
||||
</div>
|
||||
{@render font_ctrl('location')}
|
||||
{#if active_field === 'location'}
|
||||
<div id="field-form-location" class="px-3 pb-3 pt-2 border-t border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800">
|
||||
<label for="ctrl-location" class="text-xs text-gray-500 block mb-1">
|
||||
Location override
|
||||
</label>
|
||||
<input
|
||||
id="ctrl-location"
|
||||
name="ctrl-location"
|
||||
type="text"
|
||||
class="input w-full"
|
||||
bind:value={edit_location_override}
|
||||
placeholder={$lq__event_badge_obj?.location ?? 'City, State / Country'}
|
||||
/>
|
||||
{@render field_actions(
|
||||
'location',
|
||||
() => save_field('location', { location_override: edit_location_override || null }),
|
||||
() => cancel_field('location')
|
||||
)}
|
||||
<div class="ctrl-accordion" class:open={active_field === 'location'}>
|
||||
<div class="ctrl-accordion-inner">
|
||||
<div id="field-form-location" class="px-2 pb-2 pt-1.5 border-t border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800">
|
||||
{#if is_global_edit_mode}
|
||||
<label for="ctrl-location" class="text-[10px] text-gray-400 block mb-1">City / State / Country</label>
|
||||
{/if}
|
||||
<input
|
||||
id="ctrl-location"
|
||||
name="ctrl-location"
|
||||
type="text"
|
||||
class="input w-full"
|
||||
bind:value={edit_location_override}
|
||||
placeholder={$lq__event_badge_obj?.location ?? 'City, State / Country'}
|
||||
/>
|
||||
{@render field_actions(
|
||||
'location',
|
||||
() => save_field('location', { location_override: edit_location_override || null }),
|
||||
() => cancel_field('location')
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- === ALLOW TRACKING === -->
|
||||
<!-- Editable by all authenticated users -->
|
||||
<div class="border border-gray-200 dark:border-gray-700 rounded-lg overflow-hidden">
|
||||
<div class="flex items-start gap-2 px-3 pt-2 pb-1.5">
|
||||
<!-- === ALLOW TRACKING (Lead Scanning) === -->
|
||||
<div class="field-card rounded-lg overflow-hidden" class:field-card--active={active_field === 'allow_tracking'}>
|
||||
<div class="flex items-center gap-2 px-2 py-1.5">
|
||||
<div class="flex-1 min-w-0">
|
||||
<p class="text-[10px] uppercase tracking-widest text-gray-400 font-semibold mb-0.5">Lead Scanning</p>
|
||||
<p class="field-label">Lead Scanning</p>
|
||||
<p class="leading-snug text-xs">
|
||||
{$lq__event_badge_obj?.allow_tracking ? 'Allowed' : 'Not allowed'}
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-xs preset-tonal-surface shrink-0 mt-0.5"
|
||||
class="btn btn-xs preset-tonal-primary shrink-0 transition-colors"
|
||||
onclick={() => toggle_field('allow_tracking')}
|
||||
aria-expanded={active_field === 'allow_tracking'}
|
||||
aria-controls="field-form-allow-tracking"
|
||||
title="Edit lead scanning preference"
|
||||
aria-label="Edit lead scanning preference"
|
||||
aria-label="Edit lead scanning"
|
||||
>
|
||||
{#if active_field === 'allow_tracking'}<ChevronDown size="12" />{:else}<Pencil size="12" />{/if}
|
||||
</button>
|
||||
</div>
|
||||
{#if active_field === 'allow_tracking'}
|
||||
<div id="field-form-allow-tracking" class="px-3 pb-3 pt-2 border-t border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800">
|
||||
<label class="flex items-center gap-2 cursor-pointer select-none">
|
||||
<input
|
||||
id="ctrl-allow-tracking"
|
||||
name="ctrl-allow-tracking"
|
||||
type="checkbox"
|
||||
class="checkbox"
|
||||
bind:checked={edit_allow_tracking}
|
||||
/>
|
||||
<span class="text-xs">Allow exhibitor lead scanning</span>
|
||||
</label>
|
||||
{@render field_actions(
|
||||
'allow_tracking',
|
||||
() => save_field('allow_tracking', { allow_tracking: edit_allow_tracking }),
|
||||
() => cancel_field('allow_tracking')
|
||||
)}
|
||||
<div class="ctrl-accordion" class:open={active_field === 'allow_tracking'}>
|
||||
<div class="ctrl-accordion-inner">
|
||||
<div id="field-form-allow-tracking" class="px-2 pb-2 pt-1.5 border-t border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800">
|
||||
<label class="flex items-center gap-2 cursor-pointer select-none">
|
||||
<input
|
||||
id="ctrl-allow-tracking"
|
||||
name="ctrl-allow-tracking"
|
||||
type="checkbox"
|
||||
class="checkbox"
|
||||
bind:checked={edit_allow_tracking}
|
||||
/>
|
||||
<span class="text-xs">Allow exhibitor lead scanning</span>
|
||||
</label>
|
||||
{@render field_actions(
|
||||
'allow_tracking',
|
||||
() => save_field('allow_tracking', { allow_tracking: edit_allow_tracking }),
|
||||
() => cancel_field('allow_tracking')
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- === PRONOUNS === -->
|
||||
<!-- Attendee-level field: shown to all authenticated users, not just staff -->
|
||||
<div class="border border-gray-200 dark:border-gray-700 rounded-lg overflow-hidden">
|
||||
<div class="flex items-start gap-2 px-3 pt-2 pb-2">
|
||||
<div class="field-card rounded-lg overflow-hidden" class:field-card--active={active_field === 'pronouns'}>
|
||||
<div class="flex items-center gap-2 px-2 py-1.5">
|
||||
<div class="flex-1 min-w-0">
|
||||
<p class="text-[10px] uppercase tracking-widest text-gray-400 font-semibold mb-0.5">Pronouns</p>
|
||||
<p class="field-label">Pronouns</p>
|
||||
{#if get_display('pronouns_override', 'pronouns')}
|
||||
<p class="leading-snug truncate">{get_display('pronouns_override', 'pronouns')}</p>
|
||||
{:else}
|
||||
<p class="text-gray-400 italic text-xs">Not set — tap ✎ to add</p>
|
||||
<p class="text-gray-400 italic text-xs">Tap ✎ to add</p>
|
||||
{/if}
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-xs preset-tonal-surface shrink-0 mt-0.5"
|
||||
class="btn btn-xs preset-tonal-primary shrink-0 transition-colors"
|
||||
onclick={() => toggle_field('pronouns')}
|
||||
aria-expanded={active_field === 'pronouns'}
|
||||
aria-controls="field-form-pronouns"
|
||||
@@ -634,56 +627,52 @@
|
||||
{#if active_field === 'pronouns'}<ChevronDown size="12" />{:else}<Pencil size="12" />{/if}
|
||||
</button>
|
||||
</div>
|
||||
{#if active_field === 'pronouns'}
|
||||
<div id="field-form-pronouns" class="px-3 pb-3 pt-2 border-t border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800">
|
||||
<label for="ctrl-pronouns" class="text-xs text-gray-500 block mb-1">
|
||||
Pronouns override
|
||||
</label>
|
||||
<input
|
||||
id="ctrl-pronouns"
|
||||
name="ctrl-pronouns"
|
||||
type="text"
|
||||
class="input w-full"
|
||||
bind:value={edit_pronouns_override}
|
||||
placeholder={$lq__event_badge_obj?.pronouns ?? 'e.g. they/them'}
|
||||
/>
|
||||
{@render field_actions(
|
||||
'pronouns',
|
||||
() => save_field('pronouns', { pronouns_override: edit_pronouns_override || null }),
|
||||
() => cancel_field('pronouns')
|
||||
)}
|
||||
<div class="ctrl-accordion" class:open={active_field === 'pronouns'}>
|
||||
<div class="ctrl-accordion-inner">
|
||||
<div id="field-form-pronouns" class="px-2 pb-2 pt-1.5 border-t border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800">
|
||||
<input
|
||||
id="ctrl-pronouns"
|
||||
name="ctrl-pronouns"
|
||||
type="text"
|
||||
class="input w-full"
|
||||
bind:value={edit_pronouns_override}
|
||||
placeholder={$lq__event_badge_obj?.pronouns ?? 'e.g. they/them'}
|
||||
/>
|
||||
{@render field_actions(
|
||||
'pronouns',
|
||||
() => save_field('pronouns', { pronouns_override: edit_pronouns_override || null }),
|
||||
() => cancel_field('pronouns')
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- === TRUSTED-ONLY FIELDS === -->
|
||||
<!-- === STAFF-ONLY FIELDS === -->
|
||||
{#if is_trusted}
|
||||
|
||||
<!-- Divider + label: visually separates attendee-editable fields from staff tools -->
|
||||
<div class="pt-2 pb-1">
|
||||
<p class="text-[9px] uppercase tracking-widest text-gray-300 dark:text-gray-600 font-semibold px-1 flex items-center gap-1">
|
||||
<span class="flex-1 border-t border-gray-200 dark:border-gray-700"></span>
|
||||
Staff adjustments
|
||||
<span class="flex-1 border-t border-gray-200 dark:border-gray-700"></span>
|
||||
</p>
|
||||
<!-- Divider between attendee and staff fields -->
|
||||
<div class="pt-1.5 pb-0.5 flex items-center gap-1.5">
|
||||
<span class="flex-1 border-t border-gray-200 dark:border-gray-700"></span>
|
||||
<span class="text-[9px] uppercase tracking-widest text-gray-300 dark:text-gray-600 font-semibold whitespace-nowrap">Staff adjustments</span>
|
||||
<span class="flex-1 border-t border-gray-200 dark:border-gray-700"></span>
|
||||
</div>
|
||||
|
||||
<!-- === BADGE TYPE === -->
|
||||
<!-- Only shown when template has a badge_type_list defined -->
|
||||
<!-- === BADGE TYPE === (only when template defines badge_type_list) -->
|
||||
{#if badge_type_code_li.length > 0}
|
||||
<div class="border border-gray-200 dark:border-gray-700 rounded-lg overflow-hidden">
|
||||
<div class="flex items-start gap-2 px-3 pt-2 pb-2">
|
||||
<div class="field-card rounded-lg overflow-hidden" class:field-card--active={active_field === 'badge_type'}>
|
||||
<div class="flex items-center gap-2 px-2 py-1.5">
|
||||
<div class="flex-1 min-w-0">
|
||||
<p class="text-[10px] uppercase tracking-widest text-gray-400 font-semibold mb-0.5">Badge Type</p>
|
||||
<p class="field-label">Badge Type</p>
|
||||
{#if badge_type_display}
|
||||
<p class="leading-snug truncate">{badge_type_display}</p>
|
||||
{:else}
|
||||
<p class="text-gray-400 italic text-xs">Not set — tap ✎ to assign</p>
|
||||
<p class="text-gray-400 italic text-xs">Tap ✎ to assign</p>
|
||||
{/if}
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-xs preset-tonal-surface shrink-0 mt-0.5"
|
||||
class="btn btn-xs preset-tonal-primary shrink-0 transition-colors"
|
||||
onclick={() => toggle_field('badge_type')}
|
||||
aria-expanded={active_field === 'badge_type'}
|
||||
aria-controls="field-form-badge-type"
|
||||
@@ -693,39 +682,92 @@
|
||||
{#if active_field === 'badge_type'}<ChevronDown size="12" />{:else}<Pencil size="12" />{/if}
|
||||
</button>
|
||||
</div>
|
||||
{#if active_field === 'badge_type'}
|
||||
<div id="field-form-badge-type" class="px-3 pb-3 pt-2 border-t border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800">
|
||||
<label for="ctrl-badge-type" class="text-xs text-gray-500 block mb-1">
|
||||
Badge type override
|
||||
</label>
|
||||
<select
|
||||
id="ctrl-badge-type"
|
||||
name="ctrl-badge-type"
|
||||
class="select w-full"
|
||||
bind:value={edit_badge_type_code}
|
||||
>
|
||||
<option value={null}>— Select badge type —</option>
|
||||
{#each badge_type_code_li as item (item.code)}
|
||||
<option value={item.code}>{item.name}</option>
|
||||
{/each}
|
||||
</select>
|
||||
{@render field_actions(
|
||||
'badge_type',
|
||||
() => save_field('badge_type', {
|
||||
badge_type_code_override: edit_badge_type_code,
|
||||
// Keep badge_type_override in sync (name from template list).
|
||||
// See edge-case note in PROJECT__AE_Events_Badges_Review_Print.md.
|
||||
badge_type_override: edit_badge_type_code
|
||||
? (badge_type_code_li.find(item => item.code === edit_badge_type_code)?.name ?? edit_badge_type_code)
|
||||
: null
|
||||
}),
|
||||
() => cancel_field('badge_type')
|
||||
)}
|
||||
<div class="ctrl-accordion" class:open={active_field === 'badge_type'}>
|
||||
<div class="ctrl-accordion-inner">
|
||||
<div id="field-form-badge-type" class="px-2 pb-2 pt-1.5 border-t border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800">
|
||||
<select
|
||||
id="ctrl-badge-type"
|
||||
name="ctrl-badge-type"
|
||||
class="select w-full"
|
||||
bind:value={edit_badge_type_code}
|
||||
>
|
||||
<option value={null}>— Select badge type —</option>
|
||||
{#each badge_type_code_li as item (item.code)}
|
||||
<option value={item.code}>{item.name}</option>
|
||||
{/each}
|
||||
</select>
|
||||
{@render field_actions(
|
||||
'badge_type',
|
||||
() => save_field('badge_type', {
|
||||
badge_type_code_override: edit_badge_type_code,
|
||||
// Keep badge_type_override in sync with the name.
|
||||
// See PROJECT__AE_Events_Badges_Review_Print.md for edge-case notes.
|
||||
badge_type_override: edit_badge_type_code
|
||||
? (badge_type_code_li.find(item => item.code === edit_badge_type_code)?.name ?? edit_badge_type_code)
|
||||
: null
|
||||
}),
|
||||
() => cancel_field('badge_type')
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{/if}<!-- end is_trusted -->
|
||||
|
||||
</div>
|
||||
|
||||
<style>
|
||||
/* ---- Field card: visual "zoom" (elevation + ring) on the active card ----
|
||||
box-shadow is used so the effect doesn't affect layout (no reflow).
|
||||
The ring is rendered as an outer glow — does NOT require removing overflow:hidden.
|
||||
transition-* properties animate the change smoothly when toggling. */
|
||||
.field-card {
|
||||
border: 1px solid rgb(229 231 235); /* gray-200 */
|
||||
transition:
|
||||
border-color 160ms ease-out,
|
||||
box-shadow 160ms ease-out;
|
||||
}
|
||||
:global(.dark) .field-card {
|
||||
border-color: rgb(55 65 81); /* gray-700 */
|
||||
}
|
||||
.field-card--active {
|
||||
border-color: rgb(99 102 241 / 0.45); /* indigo-500, muted */
|
||||
box-shadow:
|
||||
0 0 0 3px rgb(99 102 241 / 0.18), /* soft outer glow — the "zoom ring" */
|
||||
0 4px 14px rgb(0 0 0 / 0.11); /* elevation shadow */
|
||||
}
|
||||
:global(.dark) .field-card--active {
|
||||
border-color: rgb(129 140 248 / 0.45); /* indigo-400 */
|
||||
box-shadow:
|
||||
0 0 0 3px rgb(129 140 248 / 0.14),
|
||||
0 4px 14px rgb(0 0 0 / 0.35);
|
||||
}
|
||||
|
||||
/* Compact section label used on each field row header */
|
||||
.field-label {
|
||||
font-size: 9px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.1em;
|
||||
color: rgb(156 163 175); /* gray-400 */
|
||||
font-weight: 600;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
/* ---- Smooth accordion: CSS grid-template-rows trick ----
|
||||
0fr → the row collapses to 0 (hidden).
|
||||
1fr → the row expands to its natural content height.
|
||||
The inner wrapper needs overflow:hidden so min-content is truly 0 when collapsed. */
|
||||
.ctrl-accordion {
|
||||
display: grid;
|
||||
grid-template-rows: 0fr;
|
||||
transition: grid-template-rows 185ms ease-out;
|
||||
}
|
||||
.ctrl-accordion.open {
|
||||
grid-template-rows: 1fr;
|
||||
}
|
||||
.ctrl-accordion-inner {
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -58,6 +58,16 @@
|
||||
let print_count = $derived($lq__event_badge_obj?.print_count ?? 0);
|
||||
let is_printed = $derived(print_count >= 1);
|
||||
|
||||
// Badge type for header — simplified chain (no template list lookup).
|
||||
// Full lookup with template list is in ae_comp__badge_print_controls.
|
||||
let badge_type_display = $derived(
|
||||
$lq__event_badge_obj?.badge_type_override
|
||||
?? $lq__event_badge_obj?.badge_type
|
||||
?? $lq__event_badge_obj?.badge_type_code_override
|
||||
?? $lq__event_badge_obj?.badge_type_code
|
||||
?? ''
|
||||
);
|
||||
|
||||
function build_review_url(): string {
|
||||
// TODO: append ?passcode=... when person_passcode is added to the event_badge schema
|
||||
return `/events/${$lq__event_badge_obj?.event_id}/badges/${$lq__event_badge_obj?.event_badge_id}/review`;
|
||||
@@ -168,8 +178,10 @@
|
||||
<span class="text-xs font-medium text-green-600 dark:text-green-400 whitespace-nowrap shrink-0">Ready</span>
|
||||
{/if}
|
||||
</div>
|
||||
{#if $events_loc?.title}
|
||||
<p class="text-xs text-gray-500 truncate">{$events_loc.title}</p>
|
||||
{#if badge_type_display || $events_loc?.title}
|
||||
<p class="text-xs text-gray-500 truncate leading-tight">
|
||||
{badge_type_display}{badge_type_display && $events_loc?.title ? ' · ' : ''}{$events_loc?.title ?? ''}
|
||||
</p>
|
||||
{:else if is_printed && $lq__event_badge_obj.print_last_datetime}
|
||||
<p class="text-xs text-gray-400">
|
||||
Last printed {new Date($lq__event_badge_obj.print_last_datetime).toLocaleString()}
|
||||
|
||||
Reference in New Issue
Block a user