Badges: center badge vertically + horizontally in print preview; scaffold print_margin_cfg

This commit is contained in:
Scott Idem
2026-03-12 17:05:41 -04:00
parent c3a4ff45c7
commit 11a6d5d35c
3 changed files with 373 additions and 359 deletions

View File

@@ -0,0 +1,192 @@
<script lang="ts">
/**
* AE_Record_Controls.svelte
* GENERIC Aether Record Management Controls
* Manages: priority, hide, enable, alert, delete/disable
*
* Emits events — NO API calls. Parent is responsible for:
* 1. Calling the API (update_ae_obj_v3, delete_ae_obj_id__*)
* 2. Refreshing the object from cache/API
* 3. Navigating away on delete
*
* Usage:
* <AE_Record_Controls
* obj={$lq__event_session_obj}
* obj_label="session"
* allow_delete={$ae_loc.manager_access}
* allow_disable={$ae_loc.administrator_access}
* on_toggle={(field, val) => { ... }}
* on_delete={(method) => { ... }}
* />
*/
import {
Star,
Eye,
EyeOff,
ToggleLeft,
ToggleRight,
Bell,
BellOff,
Trash2,
MinusCircle,
Settings
} from '@lucide/svelte';
interface Props {
// The object whose flags are being displayed (read-only — parent owns state)
obj: any;
// Human-readable label for confirm dialogs ("session", "presenter", "location", etc.)
obj_label?: string;
// Visibility — hide any control that doesn't apply for this object type
show_alert?: boolean;
show_priority?: boolean;
show_enable?: boolean;
show_hide?: boolean;
show_labels?: boolean;
// Permission gates — parent passes booleans derived from $ae_loc
allow_delete?: boolean; // Hard permanent delete (manager+)
allow_disable?: boolean; // Soft disable/remove (administrator+)
// Callbacks — parent handles API + refresh + navigation
on_toggle?: (field: string, new_val: boolean) => void;
on_delete?: (method: 'delete' | 'disable') => void;
// Styling
container_class?: string;
}
let {
obj,
obj_label = 'record',
show_alert = true,
show_priority = true,
show_enable = true,
show_hide = true,
show_labels = true,
allow_delete = false,
allow_disable = false,
on_toggle,
on_delete,
container_class = 'flex flex-row flex-wrap gap-1 items-center justify-evenly py-2 border-y border-surface-500/10'
}: Props = $props();
function toggle(field: string) {
if (on_toggle) on_toggle(field, !obj?.[field]);
}
function handle_delete(method: 'delete' | 'disable') {
const msg =
method === 'delete'
? `Permanently delete this ${obj_label}? This cannot be undone.`
: `Remove (disable) this ${obj_label}?`;
if (!confirm(msg)) return;
if (on_delete) on_delete(method);
}
</script>
<div class={container_class}>
{#if show_labels}
<span class="text-xs text-surface-500 flex items-center gap-1 uppercase font-bold tracking-wider mr-2">
<Settings size="1.1em" /> Controls:
</span>
{/if}
<!-- Priority -->
{#if show_priority}
<button
type="button"
onclick={() => toggle('priority')}
class="btn-icon btn-icon-sm transition"
class:preset-filled-warning-500={obj?.priority}
class:preset-tonal-secondary={!obj?.priority}
class:hover:preset-filled-warning-500={!obj?.priority}
title={obj?.priority ? 'Remove priority flag' : 'Mark as priority'}
>
<Star
size="1.2em"
class={obj?.priority ? 'fill-current' : 'opacity-50'}
/>
</button>
{/if}
<!-- Hide / Visible -->
{#if show_hide}
<button
type="button"
onclick={() => toggle('hide')}
class="btn-icon btn-icon-sm transition"
class:preset-filled-warning-500={obj?.hide}
class:preset-tonal-secondary={!obj?.hide}
class:hover:preset-filled-warning-500={!obj?.hide}
title={obj?.hide ? 'Unhide this record' : 'Hide this record'}
>
{#if obj?.hide}
<EyeOff size="1.2em" class="text-warning-500" />
{:else}
<Eye size="1.2em" class="opacity-60" />
{/if}
</button>
{/if}
<!-- Enable / Disable -->
{#if show_enable}
<button
type="button"
onclick={() => toggle('enable')}
class="btn-icon btn-icon-sm transition"
class:preset-filled-success-500={obj?.enable}
class:preset-filled-error-500={!obj?.enable}
class:hover:preset-filled-success-500={!obj?.enable}
title={obj?.enable ? 'Disable this record' : 'Enable this record'}
>
{#if obj?.enable}
<ToggleRight size="1.2em" class="text-success-300" />
{:else}
<ToggleLeft size="1.2em" class="text-error-300" />
{/if}
</button>
{/if}
<!-- Alert -->
{#if show_alert}
<button
type="button"
onclick={() => toggle('alert')}
class="btn-icon btn-icon-sm transition"
class:preset-filled-error-500={obj?.alert}
class:preset-tonal-secondary={!obj?.alert}
class:hover:preset-filled-error-500={!obj?.alert}
title={obj?.alert ? 'Remove alert status' : 'Mark as alert'}
>
{#if obj?.alert}
<Bell size="1.2em" class="text-error-300" />
{:else}
<BellOff size="1.2em" class="opacity-40" />
{/if}
</button>
{/if}
<!-- Delete / Disable buttons — only shown when permission granted -->
{#if allow_delete}
<button
type="button"
onclick={() => handle_delete('delete')}
class="btn-icon btn-icon-sm preset-filled-error-500 hover:preset-filled-error-600 transition"
title="Permanently delete this {obj_label}"
>
<Trash2 size="1.2em" />
</button>
{:else if allow_disable}
<button
type="button"
onclick={() => handle_delete('disable')}
class="btn-icon btn-icon-sm preset-filled-warning-500 hover:preset-filled-warning-600 transition"
title="Disable / soft-remove this {obj_label}"
>
<MinusCircle size="1.2em" />
</button>
{/if}
</div>