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
|
log_lvl = 0
|
||||||
}: {
|
}: {
|
||||||
api_cfg: any;
|
api_cfg: any;
|
||||||
event_id: string;
|
event_id: string | undefined;
|
||||||
event_exhibit_id: string;
|
event_exhibit_id: string | undefined;
|
||||||
fulltext_search_qry_str?: string | null;
|
fulltext_search_qry_str?: string | null;
|
||||||
qry_group?: string | null;
|
qry_group?: string | null;
|
||||||
qry_external_person_id?: 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}`);
|
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 = {
|
const search_query: any = {
|
||||||
q: fulltext_search_qry_str || '',
|
q: fulltext_search_qry_str || '',
|
||||||
and: [
|
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"
|
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'}
|
{#if $events_sess.badges.search_status === 'loading'}
|
||||||
<span class="fas fa-spinner fa-spin mx-1"></span>
|
<LoaderCircle class="animate-spin mx-1" />
|
||||||
{:else}
|
{:else}
|
||||||
<span class="fas fa-search mx-1"></span>
|
<Search class="mx-1" />
|
||||||
{/if}
|
{/if}
|
||||||
Search
|
Search
|
||||||
</button>
|
</button>
|
||||||
@@ -222,7 +222,7 @@
|
|||||||
}}
|
}}
|
||||||
class="btn btn-sm preset-tonal-primary border border-primary-500"
|
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
|
QR Scan
|
||||||
</button>
|
</button>
|
||||||
{:else}
|
{:else}
|
||||||
@@ -235,7 +235,7 @@
|
|||||||
}}
|
}}
|
||||||
class="btn btn-sm preset-tonal-primary border border-primary-500"
|
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
|
Search
|
||||||
</button>
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
@@ -250,9 +250,9 @@
|
|||||||
title="Toggle using the ID list or not."
|
title="Toggle using the ID list or not."
|
||||||
>
|
>
|
||||||
{#if $events_loc.badges.use_id_li}
|
{#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}
|
{:else}
|
||||||
<span class="fas fa-toggle-off text-red-600 mr-1"></span>
|
<X size="1.2em" class="text-red-600 mr-1" />
|
||||||
{/if}
|
{/if}
|
||||||
Use ID List
|
Use ID List
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -71,23 +71,43 @@
|
|||||||
$events_loc.leads.tab[exhibit_id] = new_tab;
|
$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_search_id = 0;
|
||||||
let last_executed_key = '';
|
let last_executed_key = '';
|
||||||
let log_lvl = 1;
|
let log_lvl = 1;
|
||||||
|
|
||||||
// Stable LiveQuery Pattern
|
// --- NEW: Direct Reactive List Pattern ---
|
||||||
let lq__event_exhibit_tracking_obj_li = $derived.by(() => {
|
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 ids = tracking_id_li;
|
||||||
const exhibit_id = page.params.exhibit_id;
|
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) {
|
if (Array.isArray(ids) && ids.length > 0) {
|
||||||
const results = await db_events.exhibit_tracking.bulkGet(ids);
|
const results = await db_events.exhibit_tracking.bulkGet(ids);
|
||||||
return results.filter((item) => item !== undefined);
|
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
|
return await db_events.exhibit_tracking
|
||||||
.where('event_exhibit_id')
|
.where('event_exhibit_id')
|
||||||
.equals(exhibit_id)
|
.equals(exhibit_id)
|
||||||
@@ -97,10 +117,16 @@
|
|||||||
|
|
||||||
return [];
|
return [];
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const subscription = observable.subscribe(res => {
|
||||||
|
raw_lead_li = res;
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => subscription.unsubscribe();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Exhibit Info
|
// Exhibit Info
|
||||||
let lq__exhibit_obj = liveQuery(() => {
|
const lq__exhibit_obj = liveQuery(() => {
|
||||||
const exhibit_id = page.params.exhibit_id;
|
const exhibit_id = page.params.exhibit_id;
|
||||||
if (!exhibit_id) return undefined;
|
if (!exhibit_id) return undefined;
|
||||||
return db_events.exhibit.get(exhibit_id);
|
return db_events.exhibit.get(exhibit_id);
|
||||||
@@ -183,16 +209,18 @@
|
|||||||
// 1. FAST PATH: Local IDB Search
|
// 1. FAST PATH: Local IDB Search
|
||||||
if (!remote_first) {
|
if (!remote_first) {
|
||||||
try {
|
try {
|
||||||
|
const target_exhibit_id = exhibit_id;
|
||||||
|
const target_licensee_email = params.licensee_email;
|
||||||
|
|
||||||
let local_results = await db_events.exhibit_tracking
|
let local_results = await db_events.exhibit_tracking
|
||||||
.where('event_exhibit_id')
|
.where('event_exhibit_id')
|
||||||
.equals(exhibit_id)
|
.equals(target_exhibit_id)
|
||||||
.filter((tracking) => {
|
.filter((tracking) => {
|
||||||
// 1. Licensee Email Filter
|
// 1. Licensee Email Filter
|
||||||
if (params.licensee_email !== 'all') {
|
if (target_licensee_email !== 'all') {
|
||||||
if (tracking.external_person_id !== params.licensee_email) return false;
|
if (tracking.external_person_id !== target_licensee_email) return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Text Search Filter
|
|
||||||
if (qry_str) {
|
if (qry_str) {
|
||||||
const name = (
|
const name = (
|
||||||
tracking.event_badge_full_name ?? ''
|
tracking.event_badge_full_name ?? ''
|
||||||
@@ -284,12 +312,16 @@
|
|||||||
order_by_li = { created_on: 'DESC' };
|
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({
|
const results = await events_func.search__exhibit_tracking({
|
||||||
api_cfg: $ae_api,
|
api_cfg: $ae_api,
|
||||||
event_id: page.params.event_id || '',
|
event_id: q_event_id,
|
||||||
event_exhibit_id: 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: params.licensee_email !== 'all' ? params.licensee_email : null,
|
qry_external_person_id: q_licensee_email,
|
||||||
order_by_li,
|
order_by_li,
|
||||||
limit: 150
|
limit: 150
|
||||||
});
|
});
|
||||||
@@ -463,7 +495,7 @@
|
|||||||
<p class="text-xl">Searching leads...</p>
|
<p class="text-xl">Searching leads...</p>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{: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}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{:else if active_tab === 'manage'}
|
{:else if active_tab === 'manage'}
|
||||||
|
|||||||
@@ -25,11 +25,11 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="ae_comp__exhibit_tracking_obj_li w-full px-2 sm:px-4">
|
<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">
|
<div class="flex justify-center p-10">
|
||||||
<span class="fas fa-spinner fa-spin fa-2x opacity-20"></span>
|
<span class="fas fa-spinner fa-spin fa-2x opacity-20"></span>
|
||||||
</div>
|
</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">
|
<div class="card p-8 text-center variant-soft-surface">
|
||||||
<p class="text-xl opacity-50">No leads found yet.</p>
|
<p class="text-xl opacity-50">No leads found yet.</p>
|
||||||
<p class="text-sm opacity-50 mt-2">
|
<p class="text-sm opacity-50 mt-2">
|
||||||
@@ -40,12 +40,12 @@
|
|||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<div class="flex justify-between items-center px-2">
|
<div class="flex justify-between items-center px-2">
|
||||||
<span class="text-sm font-semibold opacity-50">
|
<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>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid grid-cols-1 gap-4">
|
<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
|
<a
|
||||||
href={`/events/${page.params.event_id}/leads/exhibit/${event_tracking_obj.event_exhibit_id}/lead/${event_tracking_obj.event_exhibit_tracking_id}`}
|
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"
|
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
|
<Element_ae_crud_v2
|
||||||
api_cfg={$ae_api}
|
api_cfg={$ae_api}
|
||||||
object_type="event_exhibit_tracking"
|
object_type="event_exhibit_tracking"
|
||||||
object_id={exhibit_tracking_id}
|
object_id={exhibit_tracking_id ?? ''}
|
||||||
field_name="exhibitor_notes"
|
field_name="exhibitor_notes"
|
||||||
field_type="textarea"
|
field_type="textarea"
|
||||||
current_field_value={$lq__lead_obj.exhibitor_notes}
|
current_field_value={$lq__lead_obj.exhibitor_notes}
|
||||||
@@ -221,7 +221,7 @@
|
|||||||
<Element_ae_crud_v2
|
<Element_ae_crud_v2
|
||||||
api_cfg={$ae_api}
|
api_cfg={$ae_api}
|
||||||
object_type="event_exhibit_tracking"
|
object_type="event_exhibit_tracking"
|
||||||
object_id={exhibit_tracking_id}
|
object_id={exhibit_tracking_id ?? ''}
|
||||||
field_name="priority"
|
field_name="priority"
|
||||||
field_type="boolean"
|
field_type="boolean"
|
||||||
current_field_value={$lq__lead_obj.priority}
|
current_field_value={$lq__lead_obj.priority}
|
||||||
@@ -250,7 +250,7 @@
|
|||||||
<Element_ae_crud_v2
|
<Element_ae_crud_v2
|
||||||
api_cfg={$ae_api}
|
api_cfg={$ae_api}
|
||||||
object_type="event_exhibit_tracking"
|
object_type="event_exhibit_tracking"
|
||||||
object_id={exhibit_tracking_id}
|
object_id={exhibit_tracking_id ?? ''}
|
||||||
field_name="enable"
|
field_name="enable"
|
||||||
field_type="boolean"
|
field_type="boolean"
|
||||||
current_field_value={$lq__lead_obj.enable}
|
current_field_value={$lq__lead_obj.enable}
|
||||||
|
|||||||
Reference in New Issue
Block a user