Prettier for Event ID

This commit is contained in:
Scott Idem
2026-03-24 12:16:11 -04:00
parent 693486bac9
commit 6e67534454
10 changed files with 948 additions and 895 deletions

View File

@@ -1,448 +1,456 @@
<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 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_pres_mgmt_form from './ae_comp__event_settings_pres_mgmt_form.svelte';
import Ae_comp_event_settings_basic_form from './ae_comp__event_settings_basic_form.svelte';
import Ae_comp_event_settings_badges_form from './ae_comp__event_settings_badges_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';
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 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_pres_mgmt_form from './ae_comp__event_settings_pres_mgmt_form.svelte';
import Ae_comp_event_settings_basic_form from './ae_comp__event_settings_basic_form.svelte';
import Ae_comp_event_settings_badges_form from './ae_comp__event_settings_badges_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 pres_mgmt_json_view = $state('form');
let badges_json_view = $state('form');
let abstracts_json_view = $state('form');
let event_id = page.params.event_id as string;
let event_obj: Event | undefined | null = $state(null);
let cfg_json_view = $state('form');
let pres_mgmt_json_view = $state('form');
let badges_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('');
// 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);
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);
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);
}
});
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.'
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
);
}
});
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>
<h1 class="h1">Event Settings</h1>
{#if event_obj}
<div class="space-y-4">
<details class="details" open>
<summary class="summary font-bold text-error-500"
>Admin Tools</summary
>
<div class="p-4 space-y-4">
<div class="card p-4 border rounded-md text-center">
<h4 class="h4">Badge Operations</h4>
<div class="flex flex-wrap justify-center gap-2 mt-2">
<button
type="button"
class="btn btn-primary"
onclick={() => (show_create_badge_modal = true)}
>
<Plus size="1em" aria-hidden="true" /> Add New Badge
</button>
<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 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">
<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">
<button
type="button"
class="btn btn-primary"
onclick={() =>
(show_create_badge_modal = true)}>
<Plus size="1em" aria-hidden="true" /> Add New Badge
</button>
<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>
</div>
</div>
</div>
<div class="card p-4 border rounded-md text-center">
<h4 class="h4">Mass Print Options</h4>
<div class="flex flex-wrap justify-center gap-2 mt-2">
<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>
<div class="mt-4 flex flex-wrap justify-center gap-4">
<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
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/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
href={`/events/${event_id}/badges/stats`}
class="btn btn-tertiary">
<BarChart2 size="1em" aria-hidden="true" /> Badge Printing
Stats
</a>
</div>
</div>
</details>
<div class="flex flex-wrap justify-center gap-4 mt-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">
<div class="flex justify-end">
<button
type="button"
class="btn btn-sm"
onclick={() => (pres_mgmt_json_view = 'form')}
>Form</button
>
<button
type="button"
class="btn btn-sm"
onclick={() => (pres_mgmt_json_view = 'json')}
>JSON</button
>
</div>
{#if pres_mgmt_json_view === 'form'}
<Ae_comp_event_settings_pres_mgmt_form
bind:mod_pres_mgmt_json={event_obj.mod_pres_mgmt_json}
<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('mod_pres_mgmt_json', data)}
/>
{:else}
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">
<div class="flex justify-end">
<button
type="button"
class="btn btn-sm"
onclick={() => (pres_mgmt_json_view = 'form')}
>Form</button>
<button
type="button"
class="btn btn-sm"
onclick={() => (pres_mgmt_json_view = 'json')}
>JSON</button>
</div>
{#if pres_mgmt_json_view === 'form'}
<Ae_comp_event_settings_pres_mgmt_form
bind:mod_pres_mgmt_json={
event_obj.mod_pres_mgmt_json
}
onsave={(data: any) =>
handle_save('mod_pres_mgmt_json', data)} />
{:else}
<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"
onclick={() => {
handle_save(
'mod_pres_mgmt_json',
tmp_pres_mgmt_json_str
);
}}>Save</button>
{/if}
</div>
</details>
<details class="details">
<summary class="summary">Badges (mod_badges_json)</summary>
<div class="p-4">
<div class="flex justify-end">
<button
type="button"
class="btn btn-sm"
onclick={() => (badges_json_view = 'form')}
>Form</button>
<button
type="button"
class="btn btn-sm"
onclick={() => (badges_json_view = 'json')}
>JSON</button>
</div>
{#if badges_json_view === 'form'}
<Ae_comp_event_settings_badges_form
bind:mod_badges_json={event_obj.mod_badges_json}
onsave={(data: any) =>
handle_save('mod_badges_json', data)} />
{:else}
<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</button>
{/if}
</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_pres_mgmt_json_str}
bind:new_content={tmp_pres_mgmt_json_str}
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"
/>
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_pres_mgmt_json',
tmp_pres_mgmt_json_str
'mod_exhibits_json',
tmp_exhibits_json_str
);
}}>Save</button
>
{/if}
</div>
</details>
<details class="details">
<summary class="summary">Badges (mod_badges_json)</summary>
<div class="p-4">
<div class="flex justify-end">
<button
type="button"
class="btn btn-sm"
onclick={() => (badges_json_view = 'form')}>Form</button
>
<button
type="button"
class="btn btn-sm"
onclick={() => (badges_json_view = 'json')}>JSON</button
>
}}>Save</button>
</div>
{#if badges_json_view === 'form'}
<Ae_comp_event_settings_badges_form
bind:mod_badges_json={event_obj.mod_badges_json}
onsave={(data: any) =>
handle_save('mod_badges_json', data)}
/>
{:else}
</details>
<details class="details">
<summary class="summary">Meetings (mod_meetings_json)</summary>
<div class="p-4">
<AE_Comp_Editor_CodeMirror
readonly={false}
content={tmp_badges_json_str}
bind:new_content={tmp_badges_json_str}
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"
/>
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
'mod_meetings_json',
tmp_meetings_json_str
);
}}>Save</button
>
{/if}
</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
>
}}>Save</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)}
/>
</details>
</div>
</Modal>
{/if}
{:else}
<p>Loading event data...</p>
{/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}
{#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 flex-col items-center justify-center grow text-center space-y-4 py-20">
<div class="p-6 bg-error-500/10 rounded-full">
<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>
<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}

View File

@@ -1,16 +1,16 @@
<script lang="ts">
import type { key_val } from '$lib/stores/ae_stores';
import type { key_val } from '$lib/stores/ae_stores';
interface Props {
mod_abstracts_json: key_val | null | undefined;
onsave?: (data: key_val) => void;
}
interface Props {
mod_abstracts_json: key_val | null | undefined;
onsave?: (data: key_val) => void;
}
let { mod_abstracts_json = $bindable({}), onsave }: Props = $props();
let { mod_abstracts_json = $bindable({}), onsave }: Props = $props();
function save() {
if (onsave && mod_abstracts_json) onsave(mod_abstracts_json);
}
function save() {
if (onsave && mod_abstracts_json) onsave(mod_abstracts_json);
}
</script>
<div class="space-y-4">
@@ -22,8 +22,7 @@
<input
type="number"
class="input"
bind:value={mod_abstracts_json.name_char_limit}
/>
bind:value={mod_abstracts_json.name_char_limit} />
</label>
</div>
<div>
@@ -32,8 +31,7 @@
<input
type="number"
class="input"
bind:value={mod_abstracts_json.text_char_limit}
/>
bind:value={mod_abstracts_json.text_char_limit} />
</label>
</div>
<div>
@@ -42,8 +40,7 @@
<input
type="datetime-local"
class="input"
bind:value={mod_abstracts_json.deadline_new}
/>
bind:value={mod_abstracts_json.deadline_new} />
</label>
</div>
<div>
@@ -52,8 +49,7 @@
<input
type="datetime-local"
class="input"
bind:value={mod_abstracts_json.deadline_updates}
/>
bind:value={mod_abstracts_json.deadline_updates} />
</label>
</div>
<div>
@@ -61,8 +57,9 @@
<input
type="checkbox"
class="checkbox"
bind:checked={mod_abstracts_json.confirm_email_w_link}
/>
bind:checked={
mod_abstracts_json.confirm_email_w_link
} />
<span>Confirm Email with Link</span>
</label>
</div>
@@ -72,8 +69,7 @@
<input
type="email"
class="input"
bind:value={mod_abstracts_json.confirm_from_email}
/>
bind:value={mod_abstracts_json.confirm_from_email} />
</label>
</div>
<div>
@@ -82,8 +78,7 @@
<input
type="text"
class="input"
bind:value={mod_abstracts_json.confirm_from_name}
/>
bind:value={mod_abstracts_json.confirm_from_name} />
</label>
</div>
<div>
@@ -92,14 +87,14 @@
<input
type="email"
class="input"
bind:value={mod_abstracts_json.confirm_to_email_override}
/>
bind:value={
mod_abstracts_json.confirm_to_email_override
} />
</label>
</div>
</div>
{/if}
<button type="button" class="btn preset-tonal-primary" onclick={save}
>Save</button
>
>Save</button>
</div>

View File

@@ -1,83 +1,106 @@
<script lang="ts">
import type { key_val } from '$lib/stores/ae_stores';
import type { key_val } from '$lib/stores/ae_stores';
interface Props {
mod_badges_json: key_val | null | undefined;
onsave?: (data: key_val) => void;
interface Props {
mod_badges_json: key_val | null | undefined;
onsave?: (data: key_val) => void;
}
let { mod_badges_json = $bindable({}), onsave }: Props = $props();
/**
* edit_permissions — controls which fields each access level may edit in the badge review form.
* Stored as mod_badges_json.edit_permissions.
*
* Structure:
* authenticated.can_edit — fields attendees (passcode-validated) may edit
* trusted.can_edit — fields trusted staff may edit
* administrator.can_edit — '*' (all) or a specific field list
*
* Default attendee fields: full_name_override, professional_title_override,
* affiliations_override, location_override
* Default trusted fields: above + email, badge_type_code
*/
const all_attendee_fields = [
{ key: 'full_name_override', label: 'Full Name (override)' },
{
key: 'professional_title_override',
label: 'Professional Title (override)'
},
{ key: 'affiliations_override', label: 'Affiliations (override)' },
{ key: 'location_override', label: 'Location (override)' }
];
const all_staff_fields = [
...all_attendee_fields,
{ key: 'email', label: 'Email' },
{ key: 'badge_type_code', label: 'Badge Type Code' }
];
// Ensure edit_permissions sub-object exists
function ensure_permissions() {
if (!mod_badges_json) return;
if (!mod_badges_json.edit_permissions) {
mod_badges_json.edit_permissions = {};
}
let { mod_badges_json = $bindable({}), onsave }: Props = $props();
/**
* edit_permissions — controls which fields each access level may edit in the badge review form.
* Stored as mod_badges_json.edit_permissions.
*
* Structure:
* authenticated.can_edit — fields attendees (passcode-validated) may edit
* trusted.can_edit — fields trusted staff may edit
* administrator.can_edit — '*' (all) or a specific field list
*
* Default attendee fields: full_name_override, professional_title_override,
* affiliations_override, location_override
* Default trusted fields: above + email, badge_type_code
*/
const all_attendee_fields = [
{ key: 'full_name_override', label: 'Full Name (override)' },
{ key: 'professional_title_override', label: 'Professional Title (override)' },
{ key: 'affiliations_override', label: 'Affiliations (override)' },
{ key: 'location_override', label: 'Location (override)' }
];
const all_staff_fields = [
...all_attendee_fields,
{ key: 'email', label: 'Email' },
{ key: 'badge_type_code', label: 'Badge Type Code' }
];
// Ensure edit_permissions sub-object exists
function ensure_permissions() {
if (!mod_badges_json) return;
if (!mod_badges_json.edit_permissions) {
mod_badges_json.edit_permissions = {};
}
if (!mod_badges_json.edit_permissions.authenticated) {
mod_badges_json.edit_permissions.authenticated = {
can_edit: ['full_name_override', 'professional_title_override', 'affiliations_override', 'location_override']
};
}
if (!mod_badges_json.edit_permissions.trusted) {
mod_badges_json.edit_permissions.trusted = {
can_edit: ['full_name_override', 'professional_title_override', 'affiliations_override', 'location_override', 'email', 'badge_type_code']
};
}
if (!mod_badges_json.edit_permissions.administrator) {
mod_badges_json.edit_permissions.administrator = { can_edit: '*' };
}
if (!mod_badges_json.edit_permissions.authenticated) {
mod_badges_json.edit_permissions.authenticated = {
can_edit: [
'full_name_override',
'professional_title_override',
'affiliations_override',
'location_override'
]
};
}
function is_field_enabled(level: 'authenticated' | 'trusted', field_key: string): boolean {
const cfg = mod_badges_json?.edit_permissions?.[level]?.can_edit;
if (!cfg) return false;
if (cfg === '*') return true;
return Array.isArray(cfg) && cfg.includes(field_key);
if (!mod_badges_json.edit_permissions.trusted) {
mod_badges_json.edit_permissions.trusted = {
can_edit: [
'full_name_override',
'professional_title_override',
'affiliations_override',
'location_override',
'email',
'badge_type_code'
]
};
}
function toggle_field(level: 'authenticated' | 'trusted', field_key: string) {
ensure_permissions();
if (!mod_badges_json?.edit_permissions?.[level]) return;
let fields: string[] = mod_badges_json.edit_permissions[level].can_edit;
if (!Array.isArray(fields)) fields = [];
if (fields.includes(field_key)) {
mod_badges_json.edit_permissions[level].can_edit = fields.filter((f: string) => f !== field_key);
} else {
mod_badges_json.edit_permissions[level].can_edit = [...fields, field_key];
}
if (!mod_badges_json.edit_permissions.administrator) {
mod_badges_json.edit_permissions.administrator = { can_edit: '*' };
}
}
function save() {
if (onsave && mod_badges_json) onsave(mod_badges_json);
function is_field_enabled(
level: 'authenticated' | 'trusted',
field_key: string
): boolean {
const cfg = mod_badges_json?.edit_permissions?.[level]?.can_edit;
if (!cfg) return false;
if (cfg === '*') return true;
return Array.isArray(cfg) && cfg.includes(field_key);
}
function toggle_field(level: 'authenticated' | 'trusted', field_key: string) {
ensure_permissions();
if (!mod_badges_json?.edit_permissions?.[level]) return;
let fields: string[] = mod_badges_json.edit_permissions[level].can_edit;
if (!Array.isArray(fields)) fields = [];
if (fields.includes(field_key)) {
mod_badges_json.edit_permissions[level].can_edit = fields.filter(
(f: string) => f !== field_key
);
} else {
mod_badges_json.edit_permissions[level].can_edit = [
...fields,
field_key
];
}
}
function save() {
if (onsave && mod_badges_json) onsave(mod_badges_json);
}
</script>
<div class="space-y-4">
@@ -88,8 +111,7 @@
<input
type="checkbox"
class="checkbox"
bind:checked={mod_badges_json.badge_id_only_search}
/>
bind:checked={mod_badges_json.badge_id_only_search} />
<span>Badge ID Only Search</span>
</label>
</div>
@@ -98,8 +120,7 @@
<input
type="checkbox"
class="checkbox"
bind:checked={mod_badges_json.enable_mass_print}
/>
bind:checked={mod_badges_json.enable_mass_print} />
<span>Enable Mass Print</span>
</label>
</div>
@@ -108,8 +129,7 @@
<input
type="checkbox"
class="checkbox"
bind:checked={mod_badges_json.enable_add_badge_btn}
/>
bind:checked={mod_badges_json.enable_add_badge_btn} />
<span>Enable Add Badge Button</span>
</label>
</div>
@@ -118,8 +138,9 @@
<input
type="checkbox"
class="checkbox"
bind:checked={mod_badges_json.enable_upload_badge_li_btn}
/>
bind:checked={
mod_badges_json.enable_upload_badge_li_btn
} />
<span>Enable Upload Badge List Button</span>
</label>
</div>
@@ -128,8 +149,7 @@
<input
type="checkbox"
class="checkbox"
bind:checked={mod_badges_json.enable_search_qr}
/>
bind:checked={mod_badges_json.enable_search_qr} />
<span>Enable Search by QR</span>
</label>
</div>
@@ -142,8 +162,7 @@
<input
type="text"
class="input"
bind:value={mod_badges_json.qr_type}
/>
bind:value={mod_badges_json.qr_type} />
</label>
</div>
<div>
@@ -152,8 +171,7 @@
<input
type="text"
class="input"
bind:value={mod_badges_json.trusted_passcode}
/>
bind:value={mod_badges_json.trusted_passcode} />
</label>
</div>
<div>
@@ -162,20 +180,20 @@
<input
type="text"
class="input"
bind:value={mod_badges_json.administrator_passcode}
/>
bind:value={mod_badges_json.administrator_passcode} />
</label>
</div>
</div>
<details class="space-y-3">
<summary class="cursor-pointer font-medium text-sm">
<summary class="cursor-pointer text-sm font-medium">
Badge Review — Editable Field Permissions
</summary>
<div class="space-y-4 pt-2 pl-2">
<p class="text-xs text-gray-500">
Controls which fields each access level may edit on the Badge Review page.
Staff (Trusted) defaults include all attendee fields plus Email and Badge Type Code.
Controls which fields each access level may edit on the
Badge Review page. Staff (Trusted) defaults include all
attendee fields plus Email and Badge Type Code.
Administrators can always edit everything.
</p>
@@ -184,13 +202,20 @@
<p class="text-sm font-medium">Attendees (passcode link)</p>
<div class="grid grid-cols-2 gap-x-4 gap-y-1">
{#each all_attendee_fields as field (field.key)}
<label class="label flex items-center gap-2 text-sm">
<label
class="label flex items-center gap-2 text-sm">
<input
type="checkbox"
class="checkbox"
checked={is_field_enabled('authenticated', field.key)}
onchange={() => toggle_field('authenticated', field.key)}
/>
checked={is_field_enabled(
'authenticated',
field.key
)}
onchange={() =>
toggle_field(
'authenticated',
field.key
)} />
<span>{field.label}</span>
</label>
{/each}
@@ -202,13 +227,17 @@
<p class="text-sm font-medium">Staff (Trusted access)</p>
<div class="grid grid-cols-2 gap-x-4 gap-y-1">
{#each all_staff_fields as field (field.key)}
<label class="label flex items-center gap-2 text-sm">
<label
class="label flex items-center gap-2 text-sm">
<input
type="checkbox"
class="checkbox"
checked={is_field_enabled('trusted', field.key)}
onchange={() => toggle_field('trusted', field.key)}
/>
checked={is_field_enabled(
'trusted',
field.key
)}
onchange={() =>
toggle_field('trusted', field.key)} />
<span>{field.label}</span>
</label>
{/each}
@@ -217,13 +246,14 @@
<!-- Administrator note -->
<p class="text-xs text-gray-400 italic">
Administrators always have access to all fields (not configurable).
Administrators always have access to all fields (not
configurable).
</p>
</div>
</details>
{/if} <!-- end {#if mod_badges_json} -->
{/if}
<!-- end {#if mod_badges_json} -->
<button type="button" class="btn preset-tonal-primary" onclick={save}
>Save</button
>
>Save</button>
</div>

View File

@@ -1,16 +1,16 @@
<script lang="ts">
import type { key_val } from '$lib/stores/ae_stores';
import type { key_val } from '$lib/stores/ae_stores';
interface Props {
event_obj: key_val;
onsave?: (data: key_val) => void;
}
interface Props {
event_obj: key_val;
onsave?: (data: key_val) => void;
}
let { event_obj = $bindable(), onsave }: Props = $props();
let { event_obj = $bindable(), onsave }: Props = $props();
function save() {
if (onsave) onsave(event_obj);
}
function save() {
if (onsave) onsave(event_obj);
}
</script>
<div class="space-y-4">
@@ -31,8 +31,7 @@
<input
type="checkbox"
class="checkbox"
bind:checked={event_obj.conference}
/>
bind:checked={event_obj.conference} />
<span>Conference</span>
</label>
</div>
@@ -62,8 +61,7 @@
<input
type="datetime-local"
class="input"
bind:value={event_obj.start_datetime}
/>
bind:value={event_obj.start_datetime} />
</label>
</div>
<div>
@@ -72,8 +70,7 @@
<input
type="datetime-local"
class="input"
bind:value={event_obj.end_datetime}
/>
bind:value={event_obj.end_datetime} />
</label>
</div>
<div>
@@ -83,6 +80,5 @@
</label>
</div>
<button type="button" class="btn preset-tonal-primary" onclick={save}
>Save</button
>
>Save</button>
</div>

View File

@@ -1,16 +1,16 @@
<script lang="ts">
import type { key_val } from '$lib/stores/ae_stores';
import type { key_val } from '$lib/stores/ae_stores';
interface Props {
cfg_json: key_val | null | undefined;
onsave?: (data: key_val) => void;
}
interface Props {
cfg_json: key_val | null | undefined;
onsave?: (data: key_val) => void;
}
let { cfg_json = $bindable({}), onsave }: Props = $props();
let { cfg_json = $bindable({}), onsave }: Props = $props();
function save() {
if (onsave && cfg_json) onsave(cfg_json);
}
function save() {
if (onsave && cfg_json) onsave(cfg_json);
}
</script>
<div class="space-y-4">
@@ -18,17 +18,22 @@
<div>
<label class="label">
<span>Short Name</span>
<input type="text" class="input" bind:value={cfg_json.short_name} />
<input
type="text"
class="input"
bind:value={cfg_json.short_name} />
</label>
</div>
<div>
<label class="label">
<span>Medium Name</span>
<input type="text" class="input" bind:value={cfg_json.med_name} />
<input
type="text"
class="input"
bind:value={cfg_json.med_name} />
</label>
</div>
{/if}
<button type="button" class="btn preset-tonal-primary" onclick={save}
>Save</button
>
>Save</button>
</div>

View File

@@ -1,16 +1,16 @@
<script lang="ts">
import type { key_val } from '$lib/stores/ae_stores';
import type { key_val } from '$lib/stores/ae_stores';
interface Props {
mod_pres_mgmt_json: key_val | null | undefined;
onsave?: (data: key_val) => void;
}
interface Props {
mod_pres_mgmt_json: key_val | null | undefined;
onsave?: (data: key_val) => void;
}
let { mod_pres_mgmt_json = $bindable({}), onsave }: Props = $props();
let { mod_pres_mgmt_json = $bindable({}), onsave }: Props = $props();
function save() {
if (onsave && mod_pres_mgmt_json) onsave(mod_pres_mgmt_json);
}
function save() {
if (onsave && mod_pres_mgmt_json) onsave(mod_pres_mgmt_json);
}
</script>
<div class="space-y-4">
@@ -21,8 +21,7 @@
<input
type="checkbox"
class="checkbox"
bind:checked={mod_pres_mgmt_json.lock_config}
/>
bind:checked={mod_pres_mgmt_json.lock_config} />
<span>Lock Config</span>
</label>
</div>
@@ -31,8 +30,7 @@
<input
type="checkbox"
class="checkbox"
bind:checked={mod_pres_mgmt_json.hide__location_code}
/>
bind:checked={mod_pres_mgmt_json.hide__location_code} />
<span>Hide Location Code</span>
</label>
</div>
@@ -41,8 +39,9 @@
<input
type="checkbox"
class="checkbox"
bind:checked={mod_pres_mgmt_json.hide__presentation_code}
/>
bind:checked={
mod_pres_mgmt_json.hide__presentation_code
} />
<span>Hide Presentation Code</span>
</label>
</div>
@@ -51,8 +50,9 @@
<input
type="checkbox"
class="checkbox"
bind:checked={mod_pres_mgmt_json.hide__presenter_code}
/>
bind:checked={
mod_pres_mgmt_json.hide__presenter_code
} />
<span>Hide Presenter Code</span>
</label>
</div>
@@ -61,8 +61,7 @@
<input
type="checkbox"
class="checkbox"
bind:checked={mod_pres_mgmt_json.hide__session_code}
/>
bind:checked={mod_pres_mgmt_json.hide__session_code} />
<span>Hide Session Code</span>
</label>
</div>
@@ -71,8 +70,7 @@
<input
type="checkbox"
class="checkbox"
bind:checked={mod_pres_mgmt_json.limit__navigation}
/>
bind:checked={mod_pres_mgmt_json.limit__navigation} />
<span>Limit Navigation</span>
</label>
</div>
@@ -81,8 +79,7 @@
<input
type="checkbox"
class="checkbox"
bind:checked={mod_pres_mgmt_json.limit__options}
/>
bind:checked={mod_pres_mgmt_json.limit__options} />
<span>Limit Options</span>
</label>
</div>
@@ -91,8 +88,9 @@
<input
type="checkbox"
class="checkbox"
bind:checked={mod_pres_mgmt_json.require__presenter_agree}
/>
bind:checked={
mod_pres_mgmt_json.require__presenter_agree
} />
<span>Require Presenter Agreement</span>
</label>
</div>
@@ -101,8 +99,9 @@
<input
type="checkbox"
class="checkbox"
bind:checked={mod_pres_mgmt_json.require__session_agree}
/>
bind:checked={
mod_pres_mgmt_json.require__session_agree
} />
<span>Require Session Agreement</span>
</label>
</div>
@@ -115,8 +114,9 @@
<input
type="text"
class="input"
bind:value={mod_pres_mgmt_json.label__person_external_id}
/>
bind:value={
mod_pres_mgmt_json.label__person_external_id
} />
</label>
</div>
<div>
@@ -125,8 +125,9 @@
<input
type="text"
class="input"
bind:value={mod_pres_mgmt_json.label__session_poc_type}
/>
bind:value={
mod_pres_mgmt_json.label__session_poc_type
} />
</label>
</div>
<div>
@@ -135,14 +136,14 @@
<input
type="text"
class="input"
bind:value={mod_pres_mgmt_json.label__session_poc_name}
/>
bind:value={
mod_pres_mgmt_json.label__session_poc_name
} />
</label>
</div>
</div>
{/if}
<button type="button" class="btn preset-tonal-primary" onclick={save}
>Save</button
>
>Save</button>
</div>