Implement exhibitor search constraints and sync telemetry
- Restricted public exhibitor search to Priority (paid) items only. - Enforced 3-character search minimum for non-trusted users on landing page. - Implemented real-time sync telemetry (last refresh and countdown) in Manage tab. - Added auto-refresh logic with configurable intervals.
This commit is contained in:
@@ -362,6 +362,7 @@ export async function search__exhibit({
|
||||
fulltext_search_qry_str = null,
|
||||
enabled = 'enabled',
|
||||
hidden = 'not_hidden',
|
||||
priority = 'all',
|
||||
view = 'default',
|
||||
order_by_li = { name: 'ASC' },
|
||||
limit = 100,
|
||||
@@ -374,6 +375,7 @@ export async function search__exhibit({
|
||||
fulltext_search_qry_str?: string | null;
|
||||
enabled?: 'enabled' | 'all' | 'not_enabled';
|
||||
hidden?: 'hidden' | 'all' | 'not_hidden';
|
||||
priority?: 'all' | 'priority' | 'not_priority';
|
||||
view?: string;
|
||||
order_by_li?: any;
|
||||
limit?: number;
|
||||
@@ -398,6 +400,9 @@ export async function search__exhibit({
|
||||
if (hidden === 'hidden') search_query.and.push({ field: 'hide', op: 'eq', value: 1 });
|
||||
else if (hidden === 'not_hidden') search_query.and.push({ field: 'hide', op: 'eq', value: 0 });
|
||||
|
||||
if (priority === 'priority') search_query.and.push({ field: 'priority', op: 'eq', value: 1 });
|
||||
else if (priority === 'not_priority') search_query.and.push({ field: 'priority', op: 'eq', value: 0 });
|
||||
|
||||
try {
|
||||
const result_li = await api.search_ae_obj_v3({
|
||||
api_cfg,
|
||||
|
||||
@@ -477,6 +477,9 @@ const events_session_data_struct: key_val = {
|
||||
|
||||
submit_status__search: null, // 'searching', 'complete'
|
||||
|
||||
last_refresh_time: null as string | null,
|
||||
next_refresh_countdown: 0,
|
||||
|
||||
// The entered_passcode is the exhibit booths shared passcode for staff. This is used to initially access the lead retrieval service.
|
||||
entered_passcode: null,
|
||||
|
||||
|
||||
@@ -85,17 +85,26 @@
|
||||
const current_search_id = ++last_search_id;
|
||||
const event_id = params.event_id;
|
||||
const remote_first = params.remote_first;
|
||||
const qry_str = params.str;
|
||||
|
||||
if (!event_id) return;
|
||||
|
||||
// --- Search Constraint: Min 3 characters for non-trusted users ---
|
||||
if (!$ae_loc.trusted_access && qry_str.length < 3) {
|
||||
if (log_lvl) console.log('🛑 [Trace] Search string too short for public user.');
|
||||
untrack(() => {
|
||||
exhibit_id_li = [];
|
||||
$events_sess.leads.submit_status__search = 'idle';
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (log_lvl) console.log(`🔎 [Trace] Exhibit Search #${current_search_id}: START (remote=${remote_first}, event=${event_id}, str=${params.str})`);
|
||||
|
||||
untrack(() => {
|
||||
$events_sess.leads.submit_status__search = 'searching';
|
||||
});
|
||||
|
||||
const qry_str = params.str;
|
||||
|
||||
// 1. FAST PATH: Local IDB Search
|
||||
if (!remote_first) {
|
||||
try {
|
||||
@@ -103,6 +112,9 @@
|
||||
.where('event_id')
|
||||
.equals(event_id)
|
||||
.filter((exhibit) => {
|
||||
// Priority Filter for Public
|
||||
if (!$ae_loc.manager_access && !exhibit.priority) return false;
|
||||
|
||||
if (qry_str) {
|
||||
const name = (exhibit.name ?? '').toLowerCase();
|
||||
const code = (exhibit.code ?? '').toLowerCase();
|
||||
@@ -111,6 +123,9 @@
|
||||
!code.includes(qry_str)
|
||||
)
|
||||
return false;
|
||||
} else if (!$ae_loc.trusted_access) {
|
||||
// Don't show default results to public if no search string
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
})
|
||||
@@ -178,6 +193,7 @@
|
||||
api_cfg: $ae_api,
|
||||
event_id: event_id,
|
||||
fulltext_search_qry_str: qry_str || null,
|
||||
priority: $ae_loc.manager_access ? 'all' : 'priority',
|
||||
order_by_li,
|
||||
limit: 100
|
||||
});
|
||||
|
||||
@@ -123,6 +123,8 @@
|
||||
search_debounce_timer = setTimeout(() => {
|
||||
untrack(() => {
|
||||
handle_search_refresh(params);
|
||||
// Reset countdown on manual search
|
||||
$events_sess.leads.next_refresh_countdown = $events_loc.leads.refresh_interval_sec || 25;
|
||||
});
|
||||
}, 300);
|
||||
return () => {
|
||||
@@ -130,6 +132,25 @@
|
||||
};
|
||||
});
|
||||
|
||||
// --- Auto-Refresh Timer Logic ---
|
||||
$effect(() => {
|
||||
if (!is_signed_in) return;
|
||||
|
||||
const interval = setInterval(() => {
|
||||
untrack(() => {
|
||||
if ($events_sess.leads.next_refresh_countdown > 0) {
|
||||
$events_sess.leads.next_refresh_countdown--;
|
||||
} else {
|
||||
// Trigger refresh
|
||||
$events_loc.leads.tracking__search_version++;
|
||||
$events_sess.leads.next_refresh_countdown = $events_loc.leads.refresh_interval_sec || 25;
|
||||
}
|
||||
});
|
||||
}, 1000);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
});
|
||||
|
||||
async function handle_search_refresh(params: any) {
|
||||
const qry_key = JSON.stringify(params);
|
||||
if (qry_key === last_executed_key) return;
|
||||
@@ -268,6 +289,7 @@
|
||||
untrack(() => {
|
||||
tracking_id_li = api_ids;
|
||||
$events_sess.leads.submit_status__search = 'done';
|
||||
$events_sess.leads.last_refresh_time = new Date().toISOString();
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
import { liveQuery } from 'dexie';
|
||||
import { db_events } from '$lib/ae_events/db_events';
|
||||
import { ae_api, ae_loc } from '$lib/stores/ae_stores';
|
||||
import { events_loc } from '$lib/stores/ae_events_stores';
|
||||
import { events_loc, events_sess } from '$lib/stores/ae_events_stores';
|
||||
import { events_func } from '$lib/ae_events_functions';
|
||||
import Element_ae_crud_v2 from '$lib/elements/element_ae_crud_v2.svelte';
|
||||
import Comp_exhibit_license_list from './ae_comp__exhibit_license_list.svelte';
|
||||
@@ -340,9 +340,25 @@
|
||||
|
||||
<!-- List Refresh -->
|
||||
<div class="space-y-3">
|
||||
<div class="text-[10px] uppercase font-black opacity-40 tracking-widest">Data Synchronization</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="text-[10px] uppercase font-black opacity-40 tracking-widest">Data Synchronization</div>
|
||||
<div class="flex items-center gap-2 text-[10px] font-mono opacity-60">
|
||||
<span class="fas fa-clock"></span>
|
||||
{#if $events_sess.leads.last_refresh_time}
|
||||
Last: {new Date($events_sess.leads.last_refresh_time).toLocaleTimeString()}
|
||||
{:else}
|
||||
Waiting...
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-4 p-2 bg-surface-500/5 rounded-lg border border-surface-500/10">
|
||||
<span class="text-sm flex-1">Refresh Interval (sec)</span>
|
||||
<div class="flex-1 space-y-1">
|
||||
<span class="text-sm block">Refresh Interval (sec)</span>
|
||||
<div class="text-[9px] opacity-40 uppercase font-bold">
|
||||
Next Sync in <span class="text-primary-500">{$events_sess.leads.next_refresh_countdown}s</span>
|
||||
</div>
|
||||
</div>
|
||||
<input
|
||||
type="number"
|
||||
class="input w-20 text-right font-mono p-1 bg-transparent border-b border-surface-500/20"
|
||||
|
||||
Reference in New Issue
Block a user