Harden Licensed User dropdown logic and resolve data parsing errors
- Implemented dual-format parsing for 'license_li_json' (handles raw string and IDB objects). - Added fallback lookup by 'event_exhibit_id_random' for robust record resolution. - Fixed reactivity for licensee dropdown population. - Hardened default selection logic based on Aether access levels.
This commit is contained in:
@@ -57,8 +57,13 @@
|
|||||||
} else {
|
} else {
|
||||||
// 2. Licensed User logic
|
// 2. Licensed User logic
|
||||||
try {
|
try {
|
||||||
const licenses = JSON.parse($lq__exhibit_obj.license_li_json || '[]');
|
// Determine raw JSON string
|
||||||
const found = licenses.find((l: any) => l.email.toLowerCase() === email.toLowerCase().trim());
|
const raw_json = $lq__exhibit_obj?.license_li_json;
|
||||||
|
|
||||||
|
// Parse if string, otherwise use empty array
|
||||||
|
const licenses = typeof raw_json === 'string' ? JSON.parse(raw_json || '[]') : (Array.isArray(raw_json) ? raw_json : []);
|
||||||
|
|
||||||
|
const found = licenses.find((l: any) => l.email?.toLowerCase() === email.toLowerCase().trim());
|
||||||
|
|
||||||
if (found && found.passcode === user_passcode) {
|
if (found && found.passcode === user_passcode) {
|
||||||
// SUCCESS
|
// SUCCESS
|
||||||
|
|||||||
@@ -24,7 +24,16 @@
|
|||||||
let exhibit_obj: any = $state(null);
|
let exhibit_obj: any = $state(null);
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
const observable = liveQuery(() => db_events.exhibit.get(exhibit_id));
|
const observable = liveQuery(async () => {
|
||||||
|
if (!exhibit_id) return null;
|
||||||
|
// 1. Try primary lookup
|
||||||
|
let res = await db_events.exhibit.get(exhibit_id);
|
||||||
|
// 2. Fallback to random ID index
|
||||||
|
if (!res) {
|
||||||
|
res = await db_events.exhibit.where('event_exhibit_id_random').equals(exhibit_id).first();
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
});
|
||||||
const subscription = observable.subscribe((value) => {
|
const subscription = observable.subscribe((value) => {
|
||||||
exhibit_obj = value;
|
exhibit_obj = value;
|
||||||
});
|
});
|
||||||
@@ -34,23 +43,30 @@
|
|||||||
// Reactive list derived from the exhibit state (Licensed Exhibit Leads Users)
|
// Reactive list derived from the exhibit state (Licensed Exhibit Leads Users)
|
||||||
let licensee_li = $derived.by(() => {
|
let licensee_li = $derived.by(() => {
|
||||||
try {
|
try {
|
||||||
const licenses = JSON.parse(exhibit_obj?.license_li_json || '[]');
|
const raw = exhibit_obj?.license_li_json;
|
||||||
return Array.isArray(licenses) ? licenses : [];
|
if (!raw) return [];
|
||||||
|
|
||||||
|
// If it's already an array, return it. If it's a string, parse it.
|
||||||
|
if (Array.isArray(raw)) return raw;
|
||||||
|
if (typeof raw === 'string') return JSON.parse(raw || '[]');
|
||||||
|
|
||||||
|
return [];
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
console.error('Failed to parse licensee_li_json', e);
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Default selection logic: Aether Admins go to "all", Licensees go to "my"
|
// Default selection logic: Aether Admins go to "all", Licensees go to "my"
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
// Wait for object to load
|
// Wait for object to load and check if initialized
|
||||||
if (!exhibit_obj) return;
|
if (!exhibit_obj) return;
|
||||||
|
|
||||||
if ($events_loc.leads.tracking__qry__licensee_email === 'all' && !$ae_loc.administrator_access) {
|
untrack(() => {
|
||||||
untrack(() => {
|
if ($events_loc.leads.tracking__qry__licensee_email === 'all' && !$ae_loc.administrator_access) {
|
||||||
$events_loc.leads.tracking__qry__licensee_email = 'my';
|
$events_loc.leads.tracking__qry__licensee_email = 'my';
|
||||||
});
|
}
|
||||||
}
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function handle_search_trigger() {
|
function handle_search_trigger() {
|
||||||
|
|||||||
@@ -220,36 +220,38 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card p-0 divide-y divide-surface-500/10 overflow-hidden shadow-md">
|
<div class="card p-0 divide-y divide-surface-500/10 overflow-hidden shadow-md">
|
||||||
<!-- Licenses -->
|
<!-- Licenses (Administrator Access Only for now) -->
|
||||||
<div class="p-0">
|
{#if $ae_loc.administrator_access}
|
||||||
<button
|
<div class="p-0">
|
||||||
class="w-full p-4 flex items-center justify-between hover:bg-surface-500/5 transition-colors group"
|
<button
|
||||||
onclick={() => show_license_mgmt = !show_license_mgmt}
|
class="w-full p-4 flex items-center justify-between hover:bg-surface-500/5 transition-colors group"
|
||||||
>
|
onclick={() => show_license_mgmt = !show_license_mgmt}
|
||||||
<div class="flex items-center gap-4">
|
>
|
||||||
<div class="bg-primary-500/10 p-2 rounded-lg text-primary-500"><Users size="1.2em" /></div>
|
<div class="flex items-center gap-4">
|
||||||
<div class="text-left">
|
<div class="bg-primary-500/10 p-2 rounded-lg text-primary-500"><Users size="1.2em" /></div>
|
||||||
<div class="font-bold text-sm">Staff Licenses</div>
|
<div class="text-left">
|
||||||
<div class="text-xs opacity-50">Manage assigned staff and codes</div>
|
<div class="font-bold text-sm">Exhibit Staff Licenses</div>
|
||||||
|
<div class="text-xs opacity-50">Manage assigned staff and codes (Admin Only)</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{#if show_license_mgmt}
|
||||||
{#if show_license_mgmt}
|
<ChevronDown size="1.2em" class="opacity-20" />
|
||||||
<ChevronDown size="1.2em" class="opacity-20" />
|
{:else}
|
||||||
{:else}
|
<ChevronRight size="1.2em" class="opacity-20 group-hover:translate-x-1 transition-transform" />
|
||||||
<ChevronRight size="1.2em" class="opacity-20 group-hover:translate-x-1 transition-transform" />
|
{/if}
|
||||||
{/if}
|
</button>
|
||||||
</button>
|
|
||||||
|
|
||||||
{#if show_license_mgmt}
|
{#if show_license_mgmt}
|
||||||
<div class="p-4 bg-surface-500/5 border-t border-surface-500/10 animate-in fade-in slide-in-from-top-2">
|
<div class="p-4 bg-surface-500/5 border-t border-surface-500/10 animate-in fade-in slide-in-from-top-2">
|
||||||
<Comp_exhibit_license_list
|
<Comp_exhibit_license_list
|
||||||
{exhibit_id}
|
{exhibit_id}
|
||||||
license_li_json={$lq__exhibit_obj?.license_li_json ?? '[]'}
|
license_li_json={$lq__exhibit_obj?.license_li_json ?? '[]'}
|
||||||
license_max={$lq__exhibit_obj?.license_max}
|
license_max={$lq__exhibit_obj?.license_max}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<!-- Custom Questions -->
|
<!-- Custom Questions -->
|
||||||
<div class="p-0">
|
<div class="p-0">
|
||||||
|
|||||||
@@ -7,6 +7,8 @@
|
|||||||
import { liveQuery } from 'dexie';
|
import { liveQuery } from 'dexie';
|
||||||
import { db_events } from '$lib/ae_events/db_events';
|
import { db_events } from '$lib/ae_events/db_events';
|
||||||
import { ae_util } from '$lib/ae_utils/ae_utils';
|
import { ae_util } from '$lib/ae_utils/ae_utils';
|
||||||
|
import { ae_api, ae_loc } from '$lib/stores/ae_stores';
|
||||||
|
import Element_ae_crud_v2 from '$lib/elements/element_ae_crud_v2.svelte';
|
||||||
import {
|
import {
|
||||||
User,
|
User,
|
||||||
Mail,
|
Mail,
|
||||||
@@ -20,7 +22,9 @@
|
|||||||
ShieldCheck,
|
ShieldCheck,
|
||||||
Star,
|
Star,
|
||||||
LoaderCircle,
|
LoaderCircle,
|
||||||
ListTodo
|
ListTodo,
|
||||||
|
Edit,
|
||||||
|
Eye
|
||||||
} from 'lucide-svelte';
|
} from 'lucide-svelte';
|
||||||
|
|
||||||
const exhibit_tracking_id = $derived(page.params.exhibit_tracking_id);
|
const exhibit_tracking_id = $derived(page.params.exhibit_tracking_id);
|
||||||
@@ -32,6 +36,16 @@
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let lq__exhibit_obj = $derived(
|
||||||
|
liveQuery(async () => {
|
||||||
|
const exhibit_id = page.params.exhibit_id;
|
||||||
|
if (!exhibit_id) return null;
|
||||||
|
return await db_events.exhibit.get(exhibit_id);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
let is_edit_mode = $state(false);
|
||||||
|
|
||||||
// Helper to format date using Aether utility
|
// Helper to format date using Aether utility
|
||||||
function format_date(date: any) {
|
function format_date(date: any) {
|
||||||
if (!date) return '';
|
if (!date) return '';
|
||||||
@@ -57,12 +71,29 @@
|
|||||||
<h1 class="text-lg font-bold">Lead Profile</h1>
|
<h1 class="text-lg font-bold">Lead Profile</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if $lq__lead_obj?.priority}
|
<div class="flex items-center gap-2">
|
||||||
<span class="badge variant-filled-warning font-bold flex items-center gap-1">
|
{#if $lq__lead_obj}
|
||||||
<Star size="1em" fill="currentColor" />
|
<button
|
||||||
Priority
|
class="btn btn-sm"
|
||||||
</span>
|
class:variant-filled-primary={is_edit_mode}
|
||||||
{/if}
|
class:variant-ghost-surface={!is_edit_mode}
|
||||||
|
onclick={() => is_edit_mode = !is_edit_mode}
|
||||||
|
>
|
||||||
|
{#if is_edit_mode}
|
||||||
|
<Eye size="1.2em" class="mr-1" /> View
|
||||||
|
{:else}
|
||||||
|
<Edit size="1.2em" class="mr-1" /> Edit
|
||||||
|
{/if}
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if $lq__lead_obj?.priority}
|
||||||
|
<span class="badge variant-filled-warning font-bold flex items-center gap-1">
|
||||||
|
<Star size="1em" fill="currentColor" />
|
||||||
|
Priority
|
||||||
|
</span>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div class="w-full max-w-5xl p-4 sm:p-6 space-y-6">
|
<div class="w-full max-w-5xl p-4 sm:p-6 space-y-6">
|
||||||
@@ -144,7 +175,18 @@
|
|||||||
<h3 class="text-lg font-bold uppercase tracking-wider">Exhibitor Notes</h3>
|
<h3 class="text-lg font-bold uppercase tracking-wider">Exhibitor Notes</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="bg-surface-500/5 p-5 rounded-xl border border-surface-500/10 min-h-[120px]">
|
<div class="bg-surface-500/5 p-5 rounded-xl border border-surface-500/10 min-h-[120px]">
|
||||||
{#if $lq__lead_obj.exhibitor_notes}
|
{#if is_edit_mode}
|
||||||
|
<Element_ae_crud_v2
|
||||||
|
api_cfg={$ae_api}
|
||||||
|
object_type="event_exhibit_tracking"
|
||||||
|
object_id={exhibit_tracking_id}
|
||||||
|
field_name="exhibitor_notes"
|
||||||
|
field_type="textarea"
|
||||||
|
current_field_value={$lq__lead_obj.exhibitor_notes}
|
||||||
|
textarea_rows={6}
|
||||||
|
display_block={true}
|
||||||
|
/>
|
||||||
|
{:else if $lq__lead_obj.exhibitor_notes}
|
||||||
<p class="whitespace-pre-wrap leading-relaxed">{$lq__lead_obj.exhibitor_notes}</p>
|
<p class="whitespace-pre-wrap leading-relaxed">{$lq__lead_obj.exhibitor_notes}</p>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="h-full flex items-center justify-center italic opacity-30 text-sm">
|
<div class="h-full flex items-center justify-center italic opacity-30 text-sm">
|
||||||
@@ -172,6 +214,20 @@
|
|||||||
<span class="text-sm opacity-60">Captured By</span>
|
<span class="text-sm opacity-60">Captured By</span>
|
||||||
<span class="font-mono text-[10px]">{$lq__lead_obj.external_person_id || 'Unknown'}</span>
|
<span class="font-mono text-[10px]">{$lq__lead_obj.external_person_id || 'Unknown'}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{#if is_edit_mode}
|
||||||
|
<div class="flex justify-between items-center pt-2 border-t border-surface-500/10">
|
||||||
|
<span class="text-xs opacity-60 font-bold">Priority Lead</span>
|
||||||
|
<Element_ae_crud_v2
|
||||||
|
api_cfg={$ae_api}
|
||||||
|
object_type="event_exhibit_tracking"
|
||||||
|
object_id={exhibit_tracking_id}
|
||||||
|
field_name="priority"
|
||||||
|
field_type="boolean"
|
||||||
|
current_field_value={$lq__lead_obj.priority}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -188,11 +244,25 @@
|
|||||||
|
|
||||||
<!-- Status Indicator -->
|
<!-- Status Indicator -->
|
||||||
<div class="card p-4 flex items-center gap-3 variant-soft shadow-sm">
|
<div class="card p-4 flex items-center gap-3 variant-soft shadow-sm">
|
||||||
<ShieldCheck size="1.5em" class={$lq__lead_obj.enable ? 'text-success-500' : 'text-error-500'} />
|
{#if is_edit_mode}
|
||||||
<div>
|
<div class="flex-1 flex items-center justify-between">
|
||||||
<div class="font-bold">{$lq__lead_obj.enable ? 'Record Enabled' : 'Record Disabled'}</div>
|
<div class="font-bold text-sm">Enabled</div>
|
||||||
<div class="text-[10px] opacity-50 uppercase font-black">Visibility Status</div>
|
<Element_ae_crud_v2
|
||||||
</div>
|
api_cfg={$ae_api}
|
||||||
|
object_type="event_exhibit_tracking"
|
||||||
|
object_id={exhibit_tracking_id}
|
||||||
|
field_name="enable"
|
||||||
|
field_type="boolean"
|
||||||
|
current_field_value={$lq__lead_obj.enable}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<ShieldCheck size="1.5em" class={$lq__lead_obj.enable ? 'text-success-500' : 'text-error-500'} />
|
||||||
|
<div>
|
||||||
|
<div class="font-bold">{$lq__lead_obj.enable ? 'Record Enabled' : 'Record Disabled'}</div>
|
||||||
|
<div class="text-[10px] opacity-50 uppercase font-black">Visibility Status</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user