Fix Lead List filtering and resolve Svelte 5 subscription crash
- Refactored Lead List to use direct observable subscription for better reactivity. - Implemented in-memory 'Hard Guard' filter to ensure licensee selection is strictly enforced. - Fixed 'TypeError: s.subscribe is not a function' by removing legacy $ prefix from resolved props. - Resolved TypeScript typing errors in Lead Detail and Search components. - Migrated Badge Search icons to Lucide.
This commit is contained in:
@@ -442,8 +442,8 @@ export async function search__exhibit_tracking({
|
||||
log_lvl = 0
|
||||
}: {
|
||||
api_cfg: any;
|
||||
event_id: string;
|
||||
event_exhibit_id: string;
|
||||
event_id: string | undefined;
|
||||
event_exhibit_id: string | undefined;
|
||||
fulltext_search_qry_str?: string | null;
|
||||
qry_group?: string | null;
|
||||
qry_external_person_id?: string | null;
|
||||
@@ -460,6 +460,8 @@ export async function search__exhibit_tracking({
|
||||
console.log(`*** search__exhibit_tracking() *** exhibit_id=${event_exhibit_id} ft=${fulltext_search_qry_str}`);
|
||||
}
|
||||
|
||||
if (!event_id || !event_exhibit_id) return [];
|
||||
|
||||
const search_query: any = {
|
||||
q: fulltext_search_qry_str || '',
|
||||
and: [
|
||||
|
||||
@@ -171,9 +171,9 @@
|
||||
class="btn btn-lg preset-tonal-success border border-success-500 hover:preset-tonal-success text-2xl font-bold w-48 transition-all"
|
||||
>
|
||||
{#if $events_sess.badges.search_status === 'loading'}
|
||||
<span class="fas fa-spinner fa-spin mx-1"></span>
|
||||
<LoaderCircle class="animate-spin mx-1" />
|
||||
{:else}
|
||||
<span class="fas fa-search mx-1"></span>
|
||||
<Search class="mx-1" />
|
||||
{/if}
|
||||
Search
|
||||
</button>
|
||||
@@ -222,7 +222,7 @@
|
||||
}}
|
||||
class="btn btn-sm preset-tonal-primary border border-primary-500"
|
||||
>
|
||||
<span class="fas fa-qrcode mr-1"></span>
|
||||
<QrCode size="1em" class="mr-1" />
|
||||
QR Scan
|
||||
</button>
|
||||
{:else}
|
||||
@@ -235,7 +235,7 @@
|
||||
}}
|
||||
class="btn btn-sm preset-tonal-primary border border-primary-500"
|
||||
>
|
||||
<span class="fas fa-search mr-1"></span>
|
||||
<Search size="1em" class="mr-1" />
|
||||
Search
|
||||
</button>
|
||||
{/if}
|
||||
@@ -250,9 +250,9 @@
|
||||
title="Toggle using the ID list or not."
|
||||
>
|
||||
{#if $events_loc.badges.use_id_li}
|
||||
<span class="fas fa-toggle-on text-green-600 mr-1"></span>
|
||||
<Check size="1.2em" class="text-green-600 mr-1" />
|
||||
{:else}
|
||||
<span class="fas fa-toggle-off text-red-600 mr-1"></span>
|
||||
<X size="1.2em" class="text-red-600 mr-1" />
|
||||
{/if}
|
||||
Use ID List
|
||||
</button>
|
||||
|
||||
@@ -71,23 +71,43 @@
|
||||
$events_loc.leads.tab[exhibit_id] = new_tab;
|
||||
}
|
||||
|
||||
let tracking_id_li: Array<string> = $state([]); let search_debounce_timer: any = null;
|
||||
let tracking_id_li: Array<string> = $state([]);
|
||||
let search_debounce_timer: any = null;
|
||||
let last_search_id = 0;
|
||||
let last_executed_key = '';
|
||||
let log_lvl = 1;
|
||||
|
||||
// Stable LiveQuery Pattern
|
||||
let lq__event_exhibit_tracking_obj_li = $derived.by(() => {
|
||||
// --- NEW: Direct Reactive List Pattern ---
|
||||
let raw_lead_li: any[] = $state([]);
|
||||
|
||||
// Final filtered list that the UI actually sees
|
||||
// Applying the HARD GUARD here ensures that no matter where the data came from
|
||||
// (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;
|
||||
|
||||
return raw_lead_li.filter(lead => {
|
||||
const capturer = lead.external_person_id || lead.group;
|
||||
return capturer === licensee_filter;
|
||||
});
|
||||
});
|
||||
|
||||
// Subscribe to the Lead List
|
||||
$effect(() => {
|
||||
const ids = tracking_id_li;
|
||||
const exhibit_id = page.params.exhibit_id;
|
||||
const has_search = !!$events_loc.leads.tracking__qry__search_text;
|
||||
|
||||
return liveQuery(async () => {
|
||||
const observable = liveQuery(async () => {
|
||||
// 1. Specific IDs provided (from API Search or Manual Entry)
|
||||
if (Array.isArray(ids) && ids.length > 0) {
|
||||
const results = await db_events.exhibit_tracking.bulkGet(ids);
|
||||
return results.filter((item) => item !== undefined);
|
||||
}
|
||||
|
||||
if (exhibit_id && !$events_loc.leads.tracking__qry__search_text) {
|
||||
// 2. Fallback broad search (Initial load or no search text)
|
||||
if (exhibit_id && !has_search) {
|
||||
return await db_events.exhibit_tracking
|
||||
.where('event_exhibit_id')
|
||||
.equals(exhibit_id)
|
||||
@@ -97,10 +117,16 @@
|
||||
|
||||
return [];
|
||||
});
|
||||
|
||||
const subscription = observable.subscribe(res => {
|
||||
raw_lead_li = res;
|
||||
});
|
||||
|
||||
return () => subscription.unsubscribe();
|
||||
});
|
||||
|
||||
// Exhibit Info
|
||||
let lq__exhibit_obj = liveQuery(() => {
|
||||
const lq__exhibit_obj = liveQuery(() => {
|
||||
const exhibit_id = page.params.exhibit_id;
|
||||
if (!exhibit_id) return undefined;
|
||||
return db_events.exhibit.get(exhibit_id);
|
||||
@@ -183,16 +209,18 @@
|
||||
// 1. FAST PATH: Local IDB Search
|
||||
if (!remote_first) {
|
||||
try {
|
||||
const target_exhibit_id = exhibit_id;
|
||||
const target_licensee_email = params.licensee_email;
|
||||
|
||||
let local_results = await db_events.exhibit_tracking
|
||||
.where('event_exhibit_id')
|
||||
.equals(exhibit_id)
|
||||
.equals(target_exhibit_id)
|
||||
.filter((tracking) => {
|
||||
// 1. Licensee Email Filter
|
||||
if (params.licensee_email !== 'all') {
|
||||
if (tracking.external_person_id !== params.licensee_email) return false;
|
||||
if (target_licensee_email !== 'all') {
|
||||
if (tracking.external_person_id !== target_licensee_email) return false;
|
||||
}
|
||||
|
||||
// 2. Text Search Filter
|
||||
if (qry_str) {
|
||||
const name = (
|
||||
tracking.event_badge_full_name ?? ''
|
||||
@@ -284,12 +312,16 @@
|
||||
order_by_li = { created_on: 'DESC' };
|
||||
}
|
||||
|
||||
const q_event_id: string = page.params.event_id ?? '';
|
||||
const q_exhibit_id: string = exhibit_id ?? '';
|
||||
const q_licensee_email: string | null = (params.licensee_email !== 'all') ? (params.licensee_email ?? '') : null;
|
||||
|
||||
const results = await events_func.search__exhibit_tracking({
|
||||
api_cfg: $ae_api,
|
||||
event_id: page.params.event_id || '',
|
||||
event_exhibit_id: exhibit_id,
|
||||
event_id: q_event_id,
|
||||
event_exhibit_id: q_exhibit_id,
|
||||
fulltext_search_qry_str: qry_str || null,
|
||||
qry_external_person_id: params.licensee_email !== 'all' ? params.licensee_email : null,
|
||||
qry_external_person_id: q_licensee_email,
|
||||
order_by_li,
|
||||
limit: 150
|
||||
});
|
||||
@@ -463,7 +495,7 @@
|
||||
<p class="text-xl">Searching leads...</p>
|
||||
</div>
|
||||
{:else}
|
||||
<Comp_exhibit_tracking_obj_li {lq__event_exhibit_tracking_obj_li} />
|
||||
<Comp_exhibit_tracking_obj_li lq__event_exhibit_tracking_obj_li={filtered_lead_li} />
|
||||
{/if}
|
||||
</div>
|
||||
{:else if active_tab === 'manage'}
|
||||
|
||||
@@ -25,11 +25,11 @@
|
||||
</script>
|
||||
|
||||
<div class="ae_comp__exhibit_tracking_obj_li w-full px-2 sm:px-4">
|
||||
{#if !$lq__event_exhibit_tracking_obj_li}
|
||||
{#if !lq__event_exhibit_tracking_obj_li}
|
||||
<div class="flex justify-center p-10">
|
||||
<span class="fas fa-spinner fa-spin fa-2x opacity-20"></span>
|
||||
</div>
|
||||
{:else if $lq__event_exhibit_tracking_obj_li.length === 0}
|
||||
{:else if lq__event_exhibit_tracking_obj_li.length === 0}
|
||||
<div class="card p-8 text-center variant-soft-surface">
|
||||
<p class="text-xl opacity-50">No leads found yet.</p>
|
||||
<p class="text-sm opacity-50 mt-2">
|
||||
@@ -40,12 +40,12 @@
|
||||
<div class="space-y-4">
|
||||
<div class="flex justify-between items-center px-2">
|
||||
<span class="text-sm font-semibold opacity-50">
|
||||
{$lq__event_exhibit_tracking_obj_li.length} Leads Collected
|
||||
{lq__event_exhibit_tracking_obj_li.length} Leads Collected
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 gap-4">
|
||||
{#each $lq__event_exhibit_tracking_obj_li as event_tracking_obj}
|
||||
{#each lq__event_exhibit_tracking_obj_li as event_tracking_obj}
|
||||
<a
|
||||
href={`/events/${page.params.event_id}/leads/exhibit/${event_tracking_obj.event_exhibit_id}/lead/${event_tracking_obj.event_exhibit_tracking_id}`}
|
||||
class="card card-hover p-4 variant-filled-surface border-l-4 border-primary-500 flex flex-col md:flex-row gap-4 items-start md:items-center"
|
||||
|
||||
@@ -179,7 +179,7 @@
|
||||
<Element_ae_crud_v2
|
||||
api_cfg={$ae_api}
|
||||
object_type="event_exhibit_tracking"
|
||||
object_id={exhibit_tracking_id}
|
||||
object_id={exhibit_tracking_id ?? ''}
|
||||
field_name="exhibitor_notes"
|
||||
field_type="textarea"
|
||||
current_field_value={$lq__lead_obj.exhibitor_notes}
|
||||
@@ -221,7 +221,7 @@
|
||||
<Element_ae_crud_v2
|
||||
api_cfg={$ae_api}
|
||||
object_type="event_exhibit_tracking"
|
||||
object_id={exhibit_tracking_id}
|
||||
object_id={exhibit_tracking_id ?? ''}
|
||||
field_name="priority"
|
||||
field_type="boolean"
|
||||
current_field_value={$lq__lead_obj.priority}
|
||||
@@ -250,7 +250,7 @@
|
||||
<Element_ae_crud_v2
|
||||
api_cfg={$ae_api}
|
||||
object_type="event_exhibit_tracking"
|
||||
object_id={exhibit_tracking_id}
|
||||
object_id={exhibit_tracking_id ?? ''}
|
||||
field_name="enable"
|
||||
field_type="boolean"
|
||||
current_field_value={$lq__lead_obj.enable}
|
||||
|
||||
Reference in New Issue
Block a user