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:
@@ -109,13 +109,22 @@ Two scan modes (toggled per exhibit):
|
|||||||
|
|
||||||
## Known Gaps / Not Yet Implemented
|
## 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
|
- **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.
|
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
|
- **License management access for shared-passcode users** — spec says booth staff signed in via
|
||||||
(`/event/exhibit/{id}/tracking/export`). Verify it's live on the backend before demoing export.
|
the shared passcode should be able to manage licenses. Currently gated behind
|
||||||
- **PWA install prompt** — spec calls for nudging exhibitors to install as a PWA on the Start tab.
|
`$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.ts` — exhibit load, search, create, update
|
||||||
`src/lib/ae_events/ae_events__exhibit_tracking.ts` — tracking load, search, create, update, export
|
`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`.
|
||||||
|
|||||||
@@ -33,6 +33,8 @@
|
|||||||
$events_loc.leads.tracking__qry__sort_order = 'created_desc';
|
$events_loc.leads.tracking__qry__sort_order = 'created_desc';
|
||||||
if (typeof $events_loc.leads.refresh_interval_sec === 'undefined')
|
if (typeof $events_loc.leads.refresh_interval_sec === 'undefined')
|
||||||
$events_loc.leads.refresh_interval_sec = 25;
|
$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) ---
|
// --- Sign-In State (Derived) ---
|
||||||
@@ -74,9 +76,12 @@
|
|||||||
// (API or IDB), it MUST match the selected licensee.
|
// (API or IDB), it MUST match the selected licensee.
|
||||||
let filtered_lead_li = $derived.by(() => {
|
let filtered_lead_li = $derived.by(() => {
|
||||||
const licensee_filter = search_params.licensee_email;
|
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 => {
|
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;
|
const capturer = lead.external_person_id || lead.group;
|
||||||
return capturer === licensee_filter;
|
return capturer === licensee_filter;
|
||||||
});
|
});
|
||||||
@@ -138,7 +143,8 @@
|
|||||||
sort: $events_loc.leads.tracking__qry__sort_order,
|
sort: $events_loc.leads.tracking__qry__sort_order,
|
||||||
licensee_email: licensee_email,
|
licensee_email: licensee_email,
|
||||||
exhibit_id: page.params.exhibit_id,
|
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')
|
.where('event_exhibit_id')
|
||||||
.equals(target_exhibit_id)
|
.equals(target_exhibit_id)
|
||||||
.filter((tracking) => {
|
.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 (target_licensee_email !== 'all') {
|
||||||
if (tracking.external_person_id !== target_licensee_email) return false;
|
if (tracking.external_person_id !== target_licensee_email) return false;
|
||||||
}
|
}
|
||||||
@@ -313,6 +322,7 @@
|
|||||||
event_exhibit_id: q_exhibit_id,
|
event_exhibit_id: q_exhibit_id,
|
||||||
fulltext_search_qry_str: qry_str || null,
|
fulltext_search_qry_str: qry_str || null,
|
||||||
qry_external_person_id: q_licensee_email,
|
qry_external_person_id: q_licensee_email,
|
||||||
|
hidden: params.show_hidden ? 'all' : 'not_hidden',
|
||||||
order_by_li,
|
order_by_li,
|
||||||
limit: 150
|
limit: 150
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
let { exhibit_id, log_lvl = 0 }: Props = $props();
|
let { exhibit_id, log_lvl = 0 }: Props = $props();
|
||||||
|
|
||||||
// *** Import other supporting libraries
|
// *** 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 { untrack } from 'svelte';
|
||||||
import { ae_loc } from '$lib/stores/ae_stores';
|
import { ae_loc } from '$lib/stores/ae_stores';
|
||||||
import { events_loc, events_sess } from '$lib/stores/ae_events_stores';
|
import { events_loc, events_sess } from '$lib/stores/ae_events_stores';
|
||||||
@@ -165,6 +165,27 @@
|
|||||||
<div
|
<div
|
||||||
class="flex flex-row flex-wrap items-center justify-center gap-2 opacity-70 hover:opacity-100 transition-all"
|
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}
|
{#if $ae_loc.edit_mode}
|
||||||
<label
|
<label
|
||||||
class="flex items-center gap-1 cursor-pointer bg-surface-200-800 px-2 py-1 rounded-token text-xs font-semibold"
|
class="flex items-center gap-1 cursor-pointer bg-surface-200-800 px-2 py-1 rounded-token text-xs font-semibold"
|
||||||
|
|||||||
Reference in New Issue
Block a user