feat: leads — add show/hide hidden records toggle + update stale README gaps

- Add Eye/EyeOff toggle to exhibit tracking search bar; wired to
  existing $events_loc.leads.show_hidden store field
- Guard + init the show_hidden field in +page.svelte
- Add show_hidden to search_params so toggling triggers a refresh
- Apply hide filter in local IDB search path (skip hidden unless toggled)
- Pass hidden: 'not_hidden' | 'all' to API search__exhibit_tracking call
- Apply hide filter in filtered_lead_li for the broad liveQuery fallback path
- README: remove stale allow_tracking/PWA/export gaps; add Implemented section;
  fix ae_events_functions import path (moved to ae_events/ subdir)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Scott Idem
2026-03-20 11:09:16 -04:00
parent 2c570c82dc
commit 9a43879535
3 changed files with 51 additions and 11 deletions

View File

@@ -109,13 +109,22 @@ Two scan modes (toggled per exhibit):
## Known Gaps / Not Yet Implemented
- **`allow_tracking` gate** — spec says badge must have `allow_tracking = true` before a lead can
be added. Neither the QR scanner nor manual search enforce this yet.
- **Payment / Stripe** — `ae_comp__exhibit_payment.svelte` is a stub. The payment tab can be
hidden via the "Show Payment Tab" toggle in the Manage tab's App Settings.
- **Export endpoint** — `download_export__event_exhibit_tracking` calls a legacy V1 endpoint
(`/event/exhibit/{id}/tracking/export`). Verify it's live on the backend before demoing export.
- **PWA install prompt** — spec calls for nudging exhibitors to install as a PWA on the Start tab.
- **License management access for shared-passcode users** — spec says booth staff signed in via
the shared passcode should be able to manage licenses. Currently gated behind
`$ae_loc.administrator_access` only (in `ae_tab__manage.svelte`).
## Implemented (previously listed as gaps)
- **`allow_tracking` gate** — enforced in both `ae_comp__lead_qr_scanner.svelte` and
`ae_comp__lead_manual_search.svelte`. Badges without `allow_tracking = true` are blocked.
- **Show/hide hidden records** — toggle in `ae_comp__exhibit_tracking_search.svelte`; filters
local IDB search, API search (`hidden` param), and `filtered_lead_li` in `+page.svelte`.
- **Export endpoint** — `download_export__event_exhibit_tracking` uses V3 action endpoint
`/v3/action/event_exhibit/{id}/tracking_export`. Gated on `leads_api_access === true`.
- **PWA install prompt** — `element_pwa_install_prompt.svelte` placed on the Start tab.
Handles Chrome/Android native install and iOS Safari manual instructions.
---
@@ -123,4 +132,4 @@ Two scan modes (toggled per exhibit):
`src/lib/ae_events/ae_events__exhibit.ts` — exhibit load, search, create, update
`src/lib/ae_events/ae_events__exhibit_tracking.ts` — tracking load, search, create, update, export
Both are aggregated into `events_func` via `src/lib/ae_events_functions.ts`.
Both are aggregated into `events_func` via `src/lib/ae_events/ae_events_functions.ts`.

View File

@@ -33,6 +33,8 @@
$events_loc.leads.tracking__qry__sort_order = 'created_desc';
if (typeof $events_loc.leads.refresh_interval_sec === 'undefined')
$events_loc.leads.refresh_interval_sec = 25;
if (typeof $events_loc.leads.show_hidden === 'undefined')
$events_loc.leads.show_hidden = false;
}
// --- Sign-In State (Derived) ---
@@ -74,9 +76,12 @@
// (API or IDB), it MUST match the selected licensee.
let filtered_lead_li = $derived.by(() => {
const licensee_filter = search_params.licensee_email;
if (licensee_filter === 'all') return raw_lead_li;
const show_hidden = search_params.show_hidden;
return raw_lead_li.filter(lead => {
// Exclude hidden leads unless show_hidden is toggled on
if (!show_hidden && lead.hide) return false;
if (licensee_filter === 'all') return true;
const capturer = lead.external_person_id || lead.group;
return capturer === licensee_filter;
});
@@ -138,7 +143,8 @@
sort: $events_loc.leads.tracking__qry__sort_order,
licensee_email: licensee_email,
exhibit_id: page.params.exhibit_id,
remote_first: $events_loc.leads.tracking__qry__remote_first
remote_first: $events_loc.leads.tracking__qry__remote_first,
show_hidden: $events_loc.leads.show_hidden ?? false
};
});
@@ -205,7 +211,10 @@
.where('event_exhibit_id')
.equals(target_exhibit_id)
.filter((tracking) => {
// 1. Licensee Email Filter
// 1. Hide filter — exclude hidden records unless show_hidden is on
if (!params.show_hidden && tracking.hide) return false;
// 2. Licensee Email Filter
if (target_licensee_email !== 'all') {
if (tracking.external_person_id !== target_licensee_email) return false;
}
@@ -313,6 +322,7 @@
event_exhibit_id: q_exhibit_id,
fulltext_search_qry_str: qry_str || null,
qry_external_person_id: q_licensee_email,
hidden: params.show_hidden ? 'all' : 'not_hidden',
order_by_li,
limit: 150
});

View File

@@ -7,7 +7,7 @@
let { exhibit_id, log_lvl = 0 }: Props = $props();
// *** Import other supporting libraries
import { Library, LoaderCircle, RemoveFormatting, Search } from '@lucide/svelte';
import { Eye, EyeOff, Library, LoaderCircle, RemoveFormatting, Search } from '@lucide/svelte';
import { untrack } from 'svelte';
import { ae_loc } from '$lib/stores/ae_stores';
import { events_loc, events_sess } from '$lib/stores/ae_events_stores';
@@ -165,6 +165,27 @@
<div
class="flex flex-row flex-wrap items-center justify-center gap-2 opacity-70 hover:opacity-100 transition-all"
>
<!-- Show/Hide hidden records toggle — always visible -->
<button
type="button"
class="flex items-center gap-1 cursor-pointer px-2 py-1 rounded-token text-xs font-semibold transition-colors"
class:preset-tonal-warning={$events_loc.leads.show_hidden}
class:preset-tonal-surface={!$events_loc.leads.show_hidden}
onclick={() => {
$events_loc.leads.show_hidden = !$events_loc.leads.show_hidden;
handle_search_trigger();
}}
title={$events_loc.leads.show_hidden ? 'Showing hidden leads — click to hide them' : 'Hidden leads excluded — click to show all'}
>
{#if $events_loc.leads.show_hidden}
<Eye size="1em" />
<span>Showing Hidden</span>
{:else}
<EyeOff size="1em" />
<span>Hidden Excluded</span>
{/if}
</button>
{#if $ae_loc.edit_mode}
<label
class="flex items-center gap-1 cursor-pointer bg-surface-200-800 px-2 py-1 rounded-token text-xs font-semibold"