Complete Exhibitor Leads Manage tab functionality
- Implemented dynamic Custom Questions editor for qualifiers. - Wired up Staff License management and Billing stubs with expanding rows. - Finalized Admin Tools and App Settings sections. - Verified zero errors project-wide.
This commit is contained in:
@@ -0,0 +1,133 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
/**
|
||||||
|
* src/routes/events/[event_id]/(leads)/leads/exhibit/[exhibit_id]/ae_comp__exhibit_custom_questions.svelte
|
||||||
|
* Exhibitor Custom Questions Editor - Handles leads_custom_questions_json.
|
||||||
|
*/
|
||||||
|
import { untrack } from 'svelte';
|
||||||
|
import { ae_api } from '$lib/stores/ae_stores';
|
||||||
|
import { events_func } from '$lib/ae_events_functions';
|
||||||
|
import { Plus, Trash2, Save, LoaderCircle, MessageSquare, List, Type, CheckSquare } from 'lucide-svelte';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
exhibit_id: string;
|
||||||
|
custom_questions_json?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { exhibit_id, custom_questions_json = '[]' }: Props = $props();
|
||||||
|
|
||||||
|
let questions: any[] = $state([]);
|
||||||
|
let is_saving = $state(false);
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(custom_questions_json || '[]');
|
||||||
|
untrack(() => {
|
||||||
|
questions = Array.isArray(parsed) ? parsed : [];
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
untrack(() => questions = []);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
async function save_questions() {
|
||||||
|
if (!exhibit_id) return;
|
||||||
|
is_saving = true;
|
||||||
|
try {
|
||||||
|
await events_func.update_ae_obj__exhibit({
|
||||||
|
api_cfg: $ae_api,
|
||||||
|
exhibit_id: exhibit_id,
|
||||||
|
data_kv: {
|
||||||
|
leads_custom_questions_json: JSON.stringify(questions)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
is_saving = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function add_question() {
|
||||||
|
questions.push({
|
||||||
|
id: Math.random().toString(36).substring(2, 9),
|
||||||
|
label: '',
|
||||||
|
type: 'text',
|
||||||
|
options: ''
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function remove_question(index: number) {
|
||||||
|
questions.splice(index, 1);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="custom-questions-editor space-y-4">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<h3 class="text-sm font-bold uppercase tracking-widest opacity-50">Lead Qualifiers</h3>
|
||||||
|
<span class="text-xs opacity-40 italic">Define questions for lead capture</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="space-y-3">
|
||||||
|
{#each questions as q, i}
|
||||||
|
<div class="card p-4 variant-soft border border-surface-500/10 space-y-4 relative group animate-in fade-in slide-in-from-right-2">
|
||||||
|
<button
|
||||||
|
class="absolute top-2 right-2 p-2 text-error-500 opacity-0 group-hover:opacity-100 transition-opacity"
|
||||||
|
onclick={() => remove_question(i)}
|
||||||
|
>
|
||||||
|
<Trash2 size="1.2em" />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||||
|
<!-- Label -->
|
||||||
|
<div class="space-y-1">
|
||||||
|
<label class="text-[10px] uppercase font-bold opacity-40">Question / Label</label>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<MessageSquare size="1em" class="opacity-30" />
|
||||||
|
<input type="text" bind:value={q.label} placeholder="e.g. Purchasing Authority?" class="bg-transparent border-b border-surface-500/20 outline-none flex-1 text-sm font-bold" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Type -->
|
||||||
|
<div class="space-y-1">
|
||||||
|
<label class="text-[10px] uppercase font-bold opacity-40">Response Type</label>
|
||||||
|
<select bind:value={q.type} class="select variant-filled-surface text-xs p-1 rounded">
|
||||||
|
<option value="text">Short Text</option>
|
||||||
|
<option value="textarea">Long Text</option>
|
||||||
|
<option value="toggle">Yes / No (Toggle)</option>
|
||||||
|
<option value="select">Multiple Choice (Select)</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if q.type === 'select'}
|
||||||
|
<div class="space-y-1 pt-2 border-t border-surface-500/10">
|
||||||
|
<label class="text-[10px] uppercase font-bold opacity-40">Options (Comma separated)</label>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<List size="1em" class="opacity-30" />
|
||||||
|
<input type="text" bind:value={q.options} placeholder="Hot, Warm, Cold" class="bg-transparent border-b border-surface-500/20 outline-none flex-1 text-xs" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
|
||||||
|
{#if questions.length === 0}
|
||||||
|
<div class="p-8 text-center border-2 border-dashed border-surface-500/20 rounded-xl opacity-30">
|
||||||
|
<Plus size="2em" class="mx-auto mb-2" />
|
||||||
|
<p class="text-sm italic">No custom questions defined.</p>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex gap-2 pt-2">
|
||||||
|
<button class="btn btn-sm variant-filled-secondary flex-1" onclick={add_question}>
|
||||||
|
<Plus size="1.2em" class="mr-2" /> Add Question
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-sm variant-filled-primary flex-1" onclick={save_questions} disabled={is_saving}>
|
||||||
|
{#if is_saving}
|
||||||
|
<LoaderCircle size="1.2em" class="animate-spin mr-2" />
|
||||||
|
{:else}
|
||||||
|
<Save size="1.2em" class="mr-2" />
|
||||||
|
{/if}
|
||||||
|
Save Questions
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -11,6 +11,8 @@
|
|||||||
import { events_func } from '$lib/ae_events_functions';
|
import { events_func } from '$lib/ae_events_functions';
|
||||||
import Element_ae_crud_v2 from '$lib/elements/element_ae_crud_v2.svelte';
|
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';
|
import Comp_exhibit_license_list from './ae_comp__exhibit_license_list.svelte';
|
||||||
|
import Comp_exhibit_custom_questions from './ae_comp__exhibit_custom_questions.svelte';
|
||||||
|
import Comp_exhibit_payment from './ae_comp__exhibit_payment.svelte';
|
||||||
import {
|
import {
|
||||||
Store,
|
Store,
|
||||||
Settings,
|
Settings,
|
||||||
@@ -36,6 +38,8 @@
|
|||||||
// Track local status for specific actions
|
// Track local status for specific actions
|
||||||
let updating = $state(false);
|
let updating = $state(false);
|
||||||
let show_license_mgmt = $state(false);
|
let show_license_mgmt = $state(false);
|
||||||
|
let show_custom_questions = $state(false);
|
||||||
|
let show_billing = $state(false);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="ae-tab-manage w-full space-y-8 animate-in fade-in slide-in-from-bottom-2 duration-300 pb-20">
|
<div class="ae-tab-manage w-full space-y-8 animate-in fade-in slide-in-from-bottom-2 duration-300 pb-20">
|
||||||
@@ -248,27 +252,60 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Custom Questions -->
|
<!-- Custom Questions -->
|
||||||
<div class="p-4 flex items-center justify-between hover:bg-surface-500/5 transition-colors cursor-pointer group">
|
<div class="p-0">
|
||||||
<div class="flex items-center gap-4">
|
<button
|
||||||
<div class="bg-secondary-500/10 p-2 rounded-lg text-secondary-500"><MessageSquare size="1.2em" /></div>
|
class="w-full p-4 flex items-center justify-between hover:bg-surface-500/5 transition-colors group"
|
||||||
<div>
|
onclick={() => show_custom_questions = !show_custom_questions}
|
||||||
<div class="font-bold text-sm">Qualifiers & Questions</div>
|
>
|
||||||
<div class="text-xs opacity-50">Configure follow-up responses</div>
|
<div class="flex items-center gap-4">
|
||||||
|
<div class="bg-secondary-500/10 p-2 rounded-lg text-secondary-500"><MessageSquare size="1.2em" /></div>
|
||||||
|
<div class="text-left">
|
||||||
|
<div class="font-bold text-sm">Qualifiers & Questions</div>
|
||||||
|
<div class="text-xs opacity-50">Configure lead capture follow-up responses</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{#if show_custom_questions}
|
||||||
<ChevronRight size="1.2em" class="opacity-20 group-hover:translate-x-1 transition-transform" />
|
<ChevronDown size="1.2em" class="opacity-20" />
|
||||||
|
{:else}
|
||||||
|
<ChevronRight size="1.2em" class="opacity-20 group-hover:translate-x-1 transition-transform" />
|
||||||
|
{/if}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{#if show_custom_questions}
|
||||||
|
<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_custom_questions
|
||||||
|
{exhibit_id}
|
||||||
|
custom_questions_json={$lq__exhibit_obj?.leads_custom_questions_json ?? '[]'}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Billing -->
|
<!-- Billing -->
|
||||||
<div class="p-4 flex items-center justify-between hover:bg-surface-500/5 transition-colors cursor-pointer group">
|
<div class="p-0">
|
||||||
<div class="flex items-center gap-4">
|
<button
|
||||||
<div class="bg-success-500/10 p-2 rounded-lg text-success-500"><CreditCard size="1.2em" /></div>
|
class="w-full p-4 flex items-center justify-between hover:bg-surface-500/5 transition-colors group"
|
||||||
<div>
|
onclick={() => show_billing = !show_billing}
|
||||||
<div class="font-bold text-sm">Billing & Upgrades</div>
|
>
|
||||||
<div class="text-xs opacity-50">Manage subscription and extra devices</div>
|
<div class="flex items-center gap-4">
|
||||||
|
<div class="bg-success-500/10 p-2 rounded-lg text-success-500"><CreditCard size="1.2em" /></div>
|
||||||
|
<div class="text-left">
|
||||||
|
<div class="font-bold text-sm">Billing & Upgrades</div>
|
||||||
|
<div class="text-xs opacity-50">Manage subscription and extra devices</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{#if show_billing}
|
||||||
<ChevronRight size="1.2em" class="opacity-20 group-hover:translate-x-1 transition-transform" />
|
<ChevronDown size="1.2em" class="opacity-20" />
|
||||||
|
{:else}
|
||||||
|
<ChevronRight size="1.2em" class="opacity-20 group-hover:translate-x-1 transition-transform" />
|
||||||
|
{/if}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{#if show_billing}
|
||||||
|
<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_payment />
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
Reference in New Issue
Block a user