diff --git a/src/lib/elements/element_qr_scanner_v3.svelte b/src/lib/elements/element_qr_scanner_v3.svelte index 7bcde1fb..688919b1 100644 --- a/src/lib/elements/element_qr_scanner_v3.svelte +++ b/src/lib/elements/element_qr_scanner_v3.svelte @@ -27,7 +27,7 @@ let { start_qr_scanner = $bindable(true), on_qr_scan_result, - qr_fps = 10, + qr_fps = 25, qr_facing_mode = 'environment' }: Props = $props(); @@ -90,9 +90,14 @@ fps: qr_fps, // Use a percentage of the viewfinder so it scales on any screen size qrbox: (w: number, h: number) => { - const side = Math.floor(Math.min(w, h) * 0.82); + const side = Math.floor(Math.min(w, h) * 0.88); return { width: side, height: side }; - } + }, + // Use native BarcodeDetector API on Chrome/Edge — significantly faster + // than the JS ZXing fallback used on Firefox/older Safari. + // Cast: experimentalFeatures exists at runtime but is missing from the + // html5-qrcode TypeScript type definitions for this version. + ...({ experimentalFeatures: { useBarCodeDetectorIfSupported: true } } as { experimentalFeatures: { useBarCodeDetectorIfSupported: boolean } }) }, on_scan_success, on_scan_error 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 b2fb381f..f51f2499 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 @@ -186,7 +186,7 @@ if (layout === 'badge_3.5x5.5_pvc') { // 3.5" × 5.5" PVC card — single-sided, compact return { - grp_name_title: '1.8in', + grp_name_title: '1.6in', grp_name_title_flex: 'around', name: '1.4in', title: '0.55in', @@ -446,7 +446,7 @@ m-0 p-0 px-1 overflow-clip - flex flex-col gap-1 + flex flex-col items-stretch justify-between " > diff --git a/src/routes/events/[event_id]/(leads)/leads/exhibit/[exhibit_id]/ae_comp__lead_manual_search.svelte b/src/routes/events/[event_id]/(leads)/leads/exhibit/[exhibit_id]/ae_comp__lead_manual_search.svelte index f44185ff..82f470cf 100644 --- a/src/routes/events/[event_id]/(leads)/leads/exhibit/[exhibit_id]/ae_comp__lead_manual_search.svelte +++ b/src/routes/events/[event_id]/(leads)/leads/exhibit/[exhibit_id]/ae_comp__lead_manual_search.svelte @@ -6,7 +6,7 @@ import { page } from '$app/state'; import { liveQuery } from 'dexie'; import { db_events } from '$lib/ae_events/db_events'; - import { ae_api } from '$lib/stores/ae_stores'; + import { ae_api, ae_loc } from '$lib/stores/ae_stores'; import { events_loc } from '$lib/stores/ae_events_stores'; import { events_func } from '$lib/ae_events/ae_events_functions'; import { Eye, LoaderCircle, Search, ShieldOff, UserPlus } from '@lucide/svelte'; @@ -86,8 +86,10 @@ adding_id = badge_id; add_error_id = ''; - // Use the actual signed-in licensed user's email (stored in auth_exhibit_kv) - const user_email = $events_loc.leads.auth_exhibit_kv?.[exhibit_id]?.key || 'shared_passcode'; + const kv = $events_loc.leads.auth_exhibit_kv?.[exhibit_id]; + const user_email = kv?.type === 'licensed' && kv.key ? kv.key + : kv?.type === 'shared' ? 'shared_passcode' + : $ae_loc.access_type || 'anonymous'; try { const result = await events_func.create_ae_obj__exhibit_tracking({ diff --git a/src/routes/events/[event_id]/(leads)/leads/exhibit/[exhibit_id]/ae_comp__lead_qr_scanner.svelte b/src/routes/events/[event_id]/(leads)/leads/exhibit/[exhibit_id]/ae_comp__lead_qr_scanner.svelte index 4eaffbbe..9a755208 100644 --- a/src/routes/events/[event_id]/(leads)/leads/exhibit/[exhibit_id]/ae_comp__lead_qr_scanner.svelte +++ b/src/routes/events/[event_id]/(leads)/leads/exhibit/[exhibit_id]/ae_comp__lead_qr_scanner.svelte @@ -16,7 +16,7 @@ import { events_func } from '$lib/ae_events/ae_events_functions'; import Element_qr_scanner_v3 from '$lib/elements/element_qr_scanner_v3.svelte'; import { ae_util } from '$lib/ae_utils/ae_utils'; - import { Camera, CircleAlert, CircleCheck, Eye, LoaderCircle, RefreshCw, RotateCcw, ShieldOff, UserPlus, X } from '@lucide/svelte'; + import { Camera, CircleAlert, CircleCheck, Eye, LoaderCircle, RefreshCw, RotateCcw, ShieldOff, X } from '@lucide/svelte'; import { SvelteMap } from 'svelte/reactivity'; import type { ae_EventBadge } from '$lib/types/ae_types'; @@ -102,7 +102,7 @@ } } - async function confirm_add_lead() { + async function confirm_add_lead(dest: 'scan_next' | 'view_lead' = 'scan_next') { if (!found_badge || !found_badge.event_badge_id) { console.warn('[leads] Guard failed — event_badge_id missing. found_badge:', found_badge); return; @@ -110,8 +110,14 @@ scanning_status = 'adding'; - // Use the actual signed-in licensed user's email - const user_email = $events_loc.leads.auth_exhibit_kv?.[exhibit_id]?.key || 'shared_passcode'; + // Resolve who is capturing this lead: + // licensed exhibit user → their email (kv.key) + // shared passcode → 'shared_passcode' label (don't store the actual passcode) + // Aether user (no kv) → access_type string ('trusted', 'manager', 'super', etc.) + const kv = $events_loc.leads.auth_exhibit_kv?.[exhibit_id]; + const user_email = kv?.type === 'licensed' && kv.key ? kv.key + : kv?.type === 'shared' ? 'shared_passcode' + : $ae_loc.access_type || 'anonymous'; try { const result = await events_func.create_ae_obj__exhibit_tracking({ @@ -128,11 +134,11 @@ scanning_status = 'success'; if (on_lead_added) on_lead_added(found_badge); - if (scan_qualify === 'qualify' && new_tracking_id) { - // Qualify mode: navigate directly to lead detail to fill in notes/qualifiers + if (dest === 'view_lead' && new_tracking_id) { + // View Lead: navigate directly to lead detail to fill in notes/qualifiers goto(`/events/${page.params.event_id}/leads/exhibit/${exhibit_id}/lead/${new_tracking_id}`); } else { - // Rapid/auto mode: auto-reset after 2 seconds to scan the next person + // Scan Next / auto mode: auto-reset after 2 seconds to scan the next person setTimeout(reset_scanner, 2000); } } else { @@ -167,7 +173,7 @@ } } - async function confirm_reenable_lead() { + async function confirm_reenable_lead(dest: 'scan_next' | 'view_lead' = 'scan_next') { // Re-activate a lead that was previously removed (enable=false). // existing_tracking_id is already set from the map or the API fallback search. if (!existing_tracking_id) return; @@ -185,9 +191,11 @@ new_tracking_id = existing_tracking_id; scanning_status = 'success'; if (on_lead_added && found_badge) on_lead_added(found_badge); - // Re-enabled lead: success card shows "View Details" link — user navigates manually. - // Auto-reset after 2s so the scanner is ready for the next badge. - setTimeout(reset_scanner, 2000); + if (dest === 'view_lead' && new_tracking_id) { + goto(`/events/${page.params.event_id}/leads/exhibit/${exhibit_id}/lead/${new_tracking_id}`); + } else { + setTimeout(reset_scanner, 2000); + } } else { scanning_status = 'error'; error_msg = 'Failed to restore lead. Please try again.'; @@ -247,23 +255,25 @@

This lead was removed. Re-activate to restore their record including any saved notes and responses.

- - - - - View Existing Record - + +
+ + +
+ +
+ + +