459 lines
20 KiB
Svelte
459 lines
20 KiB
Svelte
<script lang="ts">
|
|
import { page } from '$app/state';
|
|
import { goto } from '$app/navigation';
|
|
import {
|
|
Lock,
|
|
Printer,
|
|
Plus,
|
|
Upload,
|
|
FileText,
|
|
BarChart2
|
|
} from '@lucide/svelte';
|
|
import { liveQuery } from 'dexie';
|
|
import { db_events, type Event } from '$lib/ae_events/db_events';
|
|
import { onMount } from 'svelte';
|
|
import { events_func } from '$lib/ae_events/ae_events_functions';
|
|
import { ae_loc, ae_api } from '$lib/stores/ae_stores';
|
|
import { badges_loc } from '$lib/stores/ae_events_stores__badges.svelte';
|
|
import AE_Comp_Editor_CodeMirror from '$lib/elements/element_editor_codemirror.svelte';
|
|
import Ae_comp_event_settings_form from './ae_comp__event_settings_form.svelte';
|
|
import Ae_comp_event_settings_basic_form from './ae_comp__event_settings_basic_form.svelte';
|
|
import Ae_comp_event_settings_abstracts_form from './ae_comp__event_settings_abstracts_form.svelte';
|
|
import { Modal } from 'flowbite-svelte';
|
|
import Comp_badge_create_form from '../(badges)/badges/ae_comp__badge_create_form.svelte';
|
|
import Comp_badge_upload_form from '../(badges)/badges/ae_comp__badge_upload_form.svelte';
|
|
|
|
let event_id = page.params.event_id as string;
|
|
let event_obj: Event | undefined | null = $state(null);
|
|
let cfg_json_view = $state('form');
|
|
let abstracts_json_view = $state('form');
|
|
|
|
// Temp string values for CodeMirror binding
|
|
// WARNING: These string buffers are used to decouple the object-based model from the string-based editor.
|
|
// Always ensure valid JSON parsing before calling handle_save to prevent data corruption.
|
|
let tmp_cfg_json_str = $state('');
|
|
let tmp_pres_mgmt_json_str = $state('');
|
|
let tmp_badges_json_str = $state('');
|
|
let tmp_abstracts_json_str = $state('');
|
|
let tmp_exhibits_json_str = $state('');
|
|
let tmp_meetings_json_str = $state('');
|
|
|
|
let show_create_badge_modal: boolean = $state(false);
|
|
let show_upload_badge_modal: boolean = $state(false);
|
|
|
|
onMount(() => {
|
|
// Guard: administrator access required. 500ms grace delay matches the /core
|
|
// layout pattern — allows the persisted store to hydrate before redirecting.
|
|
setTimeout(() => {
|
|
if (!$ae_loc.administrator_access) {
|
|
goto(`/events/${event_id}`);
|
|
}
|
|
}, 500);
|
|
|
|
const observable = liveQuery(() => db_events.event.get(event_id));
|
|
const subscription = observable.subscribe((value) => {
|
|
event_obj = value;
|
|
if (event_obj) {
|
|
tmp_cfg_json_str = JSON.stringify(event_obj.cfg_json, null, 4);
|
|
tmp_pres_mgmt_json_str = JSON.stringify(
|
|
event_obj.mod_pres_mgmt_json,
|
|
null,
|
|
4
|
|
);
|
|
tmp_badges_json_str = JSON.stringify(
|
|
event_obj.mod_badges_json,
|
|
null,
|
|
4
|
|
);
|
|
tmp_abstracts_json_str = JSON.stringify(
|
|
event_obj.mod_abstracts_json,
|
|
null,
|
|
4
|
|
);
|
|
tmp_exhibits_json_str = JSON.stringify(
|
|
event_obj.mod_exhibits_json,
|
|
null,
|
|
4
|
|
);
|
|
tmp_meetings_json_str = JSON.stringify(
|
|
event_obj.mod_meetings_json,
|
|
null,
|
|
4
|
|
);
|
|
// Mirror server-side badges config into local persisted store
|
|
if (event_obj?.mod_badges_json) {
|
|
events_func.sync_config__event_badges({
|
|
badges_cfg_remote: event_obj.mod_badges_json,
|
|
log_lvl: 0
|
|
});
|
|
}
|
|
}
|
|
});
|
|
|
|
return () => {
|
|
subscription.unsubscribe();
|
|
};
|
|
});
|
|
|
|
async function handle_save(field_name: string, data: any) {
|
|
if (!data) return;
|
|
try {
|
|
let data_kv = {};
|
|
if (field_name === 'basic_fields') {
|
|
data_kv = data;
|
|
} else {
|
|
const data_to_save =
|
|
typeof data === 'string' ? JSON.parse(data) : data;
|
|
data_kv = { [field_name]: data_to_save };
|
|
}
|
|
|
|
await events_func.update_ae_obj__event({
|
|
api_cfg: $ae_api,
|
|
event_id: event_id,
|
|
data_kv: data_kv
|
|
});
|
|
|
|
alert('Settings saved successfully!');
|
|
} catch (error) {
|
|
console.error('Error saving settings:', error);
|
|
alert('Failed to save settings. Please check if the JSON is valid.');
|
|
}
|
|
}
|
|
</script>
|
|
|
|
{#if $ae_loc.administrator_access}
|
|
<h1 class="h1">Event Settings</h1>
|
|
|
|
{#if event_obj}
|
|
<div class="space-y-4">
|
|
<details class="details" open>
|
|
<summary class="summary text-error-500 font-bold"
|
|
>Admin Tools</summary>
|
|
<div class="space-y-4 p-4">
|
|
{#if (badges_loc.current.enable_add_badge_btn ?? true) || (badges_loc.current.enable_upload_badge_li_btn ?? true)}
|
|
<div class="card rounded-md border p-4 text-center">
|
|
<h4 class="h4">Badge Operations</h4>
|
|
<div class="mt-2 flex flex-wrap justify-center gap-2">
|
|
{#if badges_loc.current.enable_add_badge_btn ?? true}
|
|
<button
|
|
type="button"
|
|
class="btn btn-primary"
|
|
onclick={() =>
|
|
(show_create_badge_modal = true)}>
|
|
<Plus size="1em" aria-hidden="true" /> Add New Badge
|
|
</button>
|
|
{/if}
|
|
{#if badges_loc.current.enable_upload_badge_li_btn ?? true}
|
|
<button
|
|
type="button"
|
|
class="btn btn-primary ml-2"
|
|
onclick={() =>
|
|
(show_upload_badge_modal = true)}>
|
|
<Upload size="1em" aria-hidden="true" /> Upload Badge
|
|
List
|
|
</button>
|
|
{/if}
|
|
</div>
|
|
</div>
|
|
{/if}
|
|
|
|
{#if badges_loc.current.enable_mass_print ?? true}
|
|
<div class="card rounded-md border p-4 text-center">
|
|
<h4 class="h4">Mass Print Options</h4>
|
|
<div class="mt-2 flex flex-wrap justify-center gap-2">
|
|
<a
|
|
href={`/events/${event_id}/badges/print_list?printed_status=not_printed`}
|
|
class="btn preset-filled-secondary">
|
|
<Printer size="1em" aria-hidden="true" /> Print All
|
|
Unprinted
|
|
</a>
|
|
<a
|
|
href={`/events/${event_id}/badges/print_list?badge_type_code=guest&printed_status=not_printed`}
|
|
class="btn preset-filled-secondary">
|
|
<Printer size="1em" aria-hidden="true" /> Print Unprinted
|
|
Guests
|
|
</a>
|
|
<a
|
|
href={`/events/${event_id}/badges/print_list`}
|
|
class="btn preset-filled-secondary">
|
|
<Printer size="1em" aria-hidden="true" /> Print All
|
|
</a>
|
|
</div>
|
|
</div>
|
|
{/if}
|
|
|
|
<div class="mt-4 flex flex-wrap justify-center gap-4">
|
|
<a
|
|
href={`/events/${event_id}/templates`}
|
|
class="btn btn-tertiary">
|
|
<FileText size="1em" aria-hidden="true" /> Manage Badge
|
|
Templates
|
|
</a>
|
|
<a
|
|
href={`/events/${event_id}/badges/stats`}
|
|
class="btn btn-tertiary">
|
|
<BarChart2 size="1em" aria-hidden="true" /> Badge Printing
|
|
Stats
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</details>
|
|
|
|
<details class="details">
|
|
<summary class="summary">Basic Info</summary>
|
|
<div class="p-4">
|
|
<Ae_comp_event_settings_basic_form
|
|
bind:event_obj
|
|
onsave={(data: any) =>
|
|
handle_save('basic_fields', data)} />
|
|
</div>
|
|
</details>
|
|
|
|
<details class="details">
|
|
<summary class="summary">General Config (cfg_json)</summary>
|
|
<div class="p-4">
|
|
<div class="flex justify-end">
|
|
<button
|
|
type="button"
|
|
class="btn btn-sm"
|
|
onclick={() => (cfg_json_view = 'form')}
|
|
>Form</button>
|
|
<button
|
|
type="button"
|
|
class="btn btn-sm"
|
|
onclick={() => (cfg_json_view = 'json')}
|
|
>JSON</button>
|
|
</div>
|
|
{#if cfg_json_view === 'form'}
|
|
<Ae_comp_event_settings_form
|
|
bind:cfg_json={event_obj.cfg_json}
|
|
onsave={(data: any) =>
|
|
handle_save('cfg_json', data)} />
|
|
{:else}
|
|
<AE_Comp_Editor_CodeMirror
|
|
readonly={false}
|
|
content={tmp_cfg_json_str}
|
|
bind:new_content={tmp_cfg_json_str}
|
|
show_line_numbers={true}
|
|
placeholder="JSON config"
|
|
class_li="p-1 preset-outlined-success-400-600 shadow-lg rounded-lg" />
|
|
<button
|
|
type="button"
|
|
class="btn preset-tonal-primary"
|
|
onclick={() => {
|
|
handle_save('cfg_json', tmp_cfg_json_str);
|
|
}}>Save</button>
|
|
{/if}
|
|
</div>
|
|
</details>
|
|
|
|
<details class="details">
|
|
<summary class="summary"
|
|
>Presentation Management (mod_pres_mgmt_json)</summary>
|
|
<div class="p-4 space-y-3">
|
|
<p class="text-sm text-surface-500">
|
|
Pres Mgmt configuration is now managed through the dedicated config page
|
|
inside Presentation Management. Use the link below (requires edit mode + manager access).
|
|
</p>
|
|
<a
|
|
href="/events/{event_obj?.event_id}/pres_mgmt/config"
|
|
class="btn preset-tonal-primary">
|
|
Go to Pres Mgmt Config →
|
|
</a>
|
|
<!-- Raw JSON fallback for debugging / emergency edits -->
|
|
<details class="mt-2">
|
|
<summary class="text-xs text-surface-400 cursor-pointer">Raw JSON (advanced)</summary>
|
|
<div class="mt-2">
|
|
<AE_Comp_Editor_CodeMirror
|
|
readonly={false}
|
|
content={tmp_pres_mgmt_json_str}
|
|
bind:new_content={tmp_pres_mgmt_json_str}
|
|
show_line_numbers={true}
|
|
placeholder="JSON config"
|
|
class_li="p-1 preset-outlined-success-400-600 shadow-lg rounded-lg" />
|
|
<button
|
|
type="button"
|
|
class="btn preset-tonal-primary mt-2"
|
|
onclick={() => {
|
|
handle_save(
|
|
'mod_pres_mgmt_json',
|
|
tmp_pres_mgmt_json_str
|
|
);
|
|
}}>Save Raw JSON</button>
|
|
</div>
|
|
</details>
|
|
</div>
|
|
</details>
|
|
|
|
<details class="details">
|
|
<summary class="summary">Badges (mod_badges_json)</summary>
|
|
<div class="p-4 space-y-3">
|
|
<p class="text-sm text-surface-500">
|
|
Manage badge search controls, QR config, access passcodes, and
|
|
per-event field edit permissions.
|
|
</p>
|
|
<a
|
|
href="/events/{event_id}/badges/config"
|
|
class="btn preset-tonal-primary">
|
|
Go to Badges Config →
|
|
</a>
|
|
<!-- Raw JSON fallback for debugging / emergency edits -->
|
|
<details class="mt-2">
|
|
<summary class="cursor-pointer text-xs text-surface-400">Raw JSON (advanced)</summary>
|
|
<div class="mt-2 space-y-2">
|
|
<AE_Comp_Editor_CodeMirror
|
|
readonly={false}
|
|
content={tmp_badges_json_str}
|
|
bind:new_content={tmp_badges_json_str}
|
|
show_line_numbers={true}
|
|
placeholder="JSON config"
|
|
class_li="p-1 preset-outlined-success-400-600 shadow-lg rounded-lg" />
|
|
<button
|
|
type="button"
|
|
class="btn preset-tonal-primary"
|
|
onclick={() => {
|
|
handle_save(
|
|
'mod_badges_json',
|
|
tmp_badges_json_str
|
|
);
|
|
}}>Save Raw JSON</button>
|
|
</div>
|
|
</details>
|
|
</div>
|
|
</details>
|
|
|
|
<details class="details">
|
|
<summary class="summary"
|
|
>Abstracts (mod_abstracts_json)</summary>
|
|
<div class="p-4">
|
|
<div class="flex justify-end">
|
|
<button
|
|
type="button"
|
|
class="btn btn-sm"
|
|
onclick={() => (abstracts_json_view = 'form')}
|
|
>Form</button>
|
|
<button
|
|
type="button"
|
|
class="btn btn-sm"
|
|
onclick={() => (abstracts_json_view = 'json')}
|
|
>JSON</button>
|
|
</div>
|
|
{#if abstracts_json_view === 'form'}
|
|
<Ae_comp_event_settings_abstracts_form
|
|
bind:mod_abstracts_json={
|
|
event_obj.mod_abstracts_json
|
|
}
|
|
onsave={(data: any) =>
|
|
handle_save('mod_abstracts_json', data)} />
|
|
{:else}
|
|
<AE_Comp_Editor_CodeMirror
|
|
readonly={false}
|
|
content={tmp_abstracts_json_str}
|
|
bind:new_content={tmp_abstracts_json_str}
|
|
show_line_numbers={true}
|
|
placeholder="JSON config"
|
|
class_li="p-1 preset-outlined-success-400-600 shadow-lg rounded-lg" />
|
|
<button
|
|
type="button"
|
|
class="btn preset-tonal-primary"
|
|
onclick={() => {
|
|
handle_save(
|
|
'mod_abstracts_json',
|
|
tmp_abstracts_json_str
|
|
);
|
|
}}>Save</button>
|
|
{/if}
|
|
</div>
|
|
</details>
|
|
|
|
<details class="details">
|
|
<summary class="summary">Exhibits (mod_exhibits_json)</summary>
|
|
<div class="p-4">
|
|
<AE_Comp_Editor_CodeMirror
|
|
readonly={false}
|
|
content={tmp_exhibits_json_str}
|
|
bind:new_content={tmp_exhibits_json_str}
|
|
show_line_numbers={true}
|
|
placeholder="JSON config"
|
|
class_li="p-1 preset-outlined-success-400-600 shadow-lg rounded-lg" />
|
|
<button
|
|
type="button"
|
|
class="btn preset-tonal-primary"
|
|
onclick={() => {
|
|
handle_save(
|
|
'mod_exhibits_json',
|
|
tmp_exhibits_json_str
|
|
);
|
|
}}>Save</button>
|
|
</div>
|
|
</details>
|
|
|
|
<details class="details">
|
|
<summary class="summary">Meetings (mod_meetings_json)</summary>
|
|
<div class="p-4">
|
|
<AE_Comp_Editor_CodeMirror
|
|
readonly={false}
|
|
content={tmp_meetings_json_str}
|
|
bind:new_content={tmp_meetings_json_str}
|
|
show_line_numbers={true}
|
|
placeholder="JSON config"
|
|
class_li="p-1 preset-outlined-success-400-600 shadow-lg rounded-lg" />
|
|
<button
|
|
type="button"
|
|
class="btn preset-tonal-primary"
|
|
onclick={() => {
|
|
handle_save(
|
|
'mod_meetings_json',
|
|
tmp_meetings_json_str
|
|
);
|
|
}}>Save</button>
|
|
</div>
|
|
</details>
|
|
</div>
|
|
{:else}
|
|
<p>Loading event data...</p>
|
|
{/if}
|
|
|
|
{#if show_create_badge_modal}
|
|
<Modal bind:open={show_create_badge_modal}>
|
|
<div class="card p-4">
|
|
<h3 class="h3">Create New Badge</h3>
|
|
<Comp_badge_create_form
|
|
{event_id}
|
|
onsuccess={() => {
|
|
show_create_badge_modal = false;
|
|
}}
|
|
oncancel={() => (show_create_badge_modal = false)} />
|
|
</div>
|
|
</Modal>
|
|
{/if}
|
|
|
|
{#if show_upload_badge_modal}
|
|
<Modal bind:open={show_upload_badge_modal}>
|
|
<div class="card p-4">
|
|
<h3 class="h3">Upload Badges (CSV)</h3>
|
|
<Comp_badge_upload_form
|
|
{event_id}
|
|
onsuccess={() => {
|
|
show_upload_badge_modal = false;
|
|
}}
|
|
oncancel={() => (show_upload_badge_modal = false)} />
|
|
</div>
|
|
</Modal>
|
|
{/if}
|
|
{:else}
|
|
<!-- Non-administrator landed here — show a brief message while the onMount redirect fires -->
|
|
<section
|
|
class="flex grow flex-col items-center justify-center space-y-4 py-20 text-center">
|
|
<div class="bg-error-500/10 rounded-full p-6">
|
|
<Lock size={64} class="text-error-500" />
|
|
</div>
|
|
<h1 class="h1 font-black">Access Restricted</h1>
|
|
<p class="max-w-md opacity-70">
|
|
Event settings require administrator access. Redirecting…
|
|
</p>
|
|
<a href={`/events/${event_id}`} class="btn preset-filled-primary"
|
|
>Return to Event</a>
|
|
</section>
|
|
{/if}
|