- TemplateManager with LEGO-style building block composition (5 block types) - BlindStructureEditor with full level fields, mixed game, reorder, add/delete - StructureWizard generates structures from player count, chips, duration params - More page with navigable menu to all sub-pages (admin-gated operators section) - Templates page with DataTable list, create/edit/duplicate/delete actions - Structures page with DataTable list, wizard integration, and editor - Settings page with venue config, currency, receipts, theme toggle (Mocha/Latte) - Audit log page with filterable DataTable, detail panel, and undo capability Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
395 lines
9 KiB
Svelte
395 lines
9 KiB
Svelte
<script lang="ts">
|
|
/**
|
|
* Venue settings page.
|
|
*
|
|
* Venue name, currency, rounding denomination, receipt mode,
|
|
* theme toggle (Mocha/Latte), operator management (admin only).
|
|
*/
|
|
|
|
import { auth } from '$lib/stores/auth.svelte';
|
|
import { toast } from '$lib/stores/toast.svelte';
|
|
|
|
/** Settings state. */
|
|
let venueName = $state('My Poker Room');
|
|
let currencyCode = $state('EUR');
|
|
let currencySymbol = $state('\u20AC');
|
|
let roundingDenom = $state(5);
|
|
let receiptMode = $state<'off' | 'digital' | 'print' | 'both'>('off');
|
|
let currentTheme = $state(
|
|
typeof document !== 'undefined'
|
|
? document.documentElement.getAttribute('data-theme') ?? 'mocha'
|
|
: 'mocha'
|
|
);
|
|
|
|
const CURRENCY_OPTIONS = [
|
|
{ code: 'EUR', symbol: '\u20AC' },
|
|
{ code: 'USD', symbol: '$' },
|
|
{ code: 'GBP', symbol: '\u00A3' },
|
|
{ code: 'DKK', symbol: 'kr' },
|
|
{ code: 'SEK', symbol: 'kr' },
|
|
{ code: 'NOK', symbol: 'kr' },
|
|
{ code: 'CHF', symbol: 'CHF' }
|
|
];
|
|
|
|
const RECEIPT_OPTIONS: { value: typeof receiptMode; label: string }[] = [
|
|
{ value: 'off', label: 'Off' },
|
|
{ value: 'digital', label: 'Digital' },
|
|
{ value: 'print', label: 'Print' },
|
|
{ value: 'both', label: 'Both' }
|
|
];
|
|
|
|
function handleCurrencyChange(event: Event): void {
|
|
const code = (event.target as HTMLSelectElement).value;
|
|
const cur = CURRENCY_OPTIONS.find((c) => c.code === code);
|
|
if (cur) {
|
|
currencyCode = cur.code;
|
|
currencySymbol = cur.symbol;
|
|
}
|
|
}
|
|
|
|
function toggleTheme(): void {
|
|
currentTheme = currentTheme === 'mocha' ? 'latte' : 'mocha';
|
|
if (typeof document !== 'undefined') {
|
|
document.documentElement.setAttribute('data-theme', currentTheme);
|
|
}
|
|
toast.info(`Theme switched to ${currentTheme === 'mocha' ? 'Mocha (dark)' : 'Latte (light)'}`);
|
|
}
|
|
|
|
function handleSave(): void {
|
|
toast.success('Settings saved.');
|
|
}
|
|
</script>
|
|
|
|
<div class="page-content">
|
|
<h2>Venue Settings</h2>
|
|
<p class="text-secondary">Configure your poker room.</p>
|
|
|
|
<div class="settings-form">
|
|
<!-- Venue Name -->
|
|
<div class="field-group">
|
|
<label class="field-label" for="venue-name">Venue Name</label>
|
|
<input
|
|
id="venue-name"
|
|
type="text"
|
|
class="field-input touch-target"
|
|
bind:value={venueName}
|
|
placeholder="e.g., Lucky Aces Poker Club"
|
|
/>
|
|
</div>
|
|
|
|
<!-- Currency -->
|
|
<div class="field-group">
|
|
<label class="field-label" for="currency-select">Currency</label>
|
|
<select
|
|
id="currency-select"
|
|
class="field-select touch-target"
|
|
value={currencyCode}
|
|
onchange={handleCurrencyChange}
|
|
>
|
|
{#each CURRENCY_OPTIONS as cur}
|
|
<option value={cur.code}>{cur.code} ({cur.symbol})</option>
|
|
{/each}
|
|
</select>
|
|
</div>
|
|
|
|
<!-- Rounding Denomination -->
|
|
<div class="field-group">
|
|
<label class="field-label" for="rounding-denom">Rounding Denomination ({currencySymbol})</label>
|
|
<input
|
|
id="rounding-denom"
|
|
type="number"
|
|
class="field-input number touch-target"
|
|
bind:value={roundingDenom}
|
|
min="1"
|
|
max="100"
|
|
/>
|
|
</div>
|
|
|
|
<!-- Receipt Mode -->
|
|
<div class="field-group">
|
|
<label class="field-label">Receipt Mode</label>
|
|
<div class="radio-group">
|
|
{#each RECEIPT_OPTIONS as opt}
|
|
<label class="radio-label touch-target">
|
|
<input
|
|
type="radio"
|
|
name="receipt-mode"
|
|
value={opt.value}
|
|
bind:group={receiptMode}
|
|
/>
|
|
<span>{opt.label}</span>
|
|
</label>
|
|
{/each}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Theme Toggle -->
|
|
<div class="field-group">
|
|
<label class="field-label">Theme</label>
|
|
<button class="theme-toggle touch-target" onclick={toggleTheme}>
|
|
<span class="theme-preview" class:active-theme={currentTheme === 'mocha'}>
|
|
Mocha (Dark)
|
|
</span>
|
|
<span class="theme-divider">/</span>
|
|
<span class="theme-preview" class:active-theme={currentTheme === 'latte'}>
|
|
Latte (Light)
|
|
</span>
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Save -->
|
|
<button class="save-btn touch-target" onclick={handleSave}>
|
|
Save Settings
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Operator Management (admin only) -->
|
|
{#if auth.isAdmin}
|
|
<div class="admin-section">
|
|
<h3 class="section-title">Operators</h3>
|
|
<p class="text-secondary">Manage floor staff (admin only).</p>
|
|
|
|
<div class="operator-list">
|
|
<div class="operator-item">
|
|
<div class="operator-info">
|
|
<span class="operator-name">{auth.operator?.name ?? 'Admin'}</span>
|
|
<span class="operator-role">admin</span>
|
|
</div>
|
|
<span class="current-badge">You</span>
|
|
</div>
|
|
<div class="operator-item">
|
|
<div class="operator-info">
|
|
<span class="operator-name">Floor Manager</span>
|
|
<span class="operator-role">floor</span>
|
|
</div>
|
|
<button class="edit-pin-btn touch-target">
|
|
Change PIN
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
|
|
<style>
|
|
.page-content {
|
|
padding: var(--space-4);
|
|
}
|
|
|
|
h2 {
|
|
font-size: var(--text-2xl);
|
|
font-weight: 700;
|
|
color: var(--color-text);
|
|
margin-bottom: var(--space-1);
|
|
}
|
|
|
|
.text-secondary {
|
|
color: var(--color-text-secondary);
|
|
font-size: var(--text-sm);
|
|
margin-bottom: var(--space-6);
|
|
}
|
|
|
|
.settings-form {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: var(--space-4);
|
|
max-width: 500px;
|
|
}
|
|
|
|
.field-group {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: var(--space-1);
|
|
}
|
|
|
|
.field-label {
|
|
font-size: var(--text-sm);
|
|
font-weight: 600;
|
|
color: var(--color-text-secondary);
|
|
}
|
|
|
|
.field-input {
|
|
padding: var(--space-2) var(--space-3);
|
|
font-size: var(--text-base);
|
|
color: var(--color-text);
|
|
background-color: var(--color-surface);
|
|
border: 1px solid var(--color-border);
|
|
border-radius: var(--radius-md);
|
|
outline: none;
|
|
font-family: inherit;
|
|
}
|
|
|
|
.field-input:focus {
|
|
border-color: var(--color-primary);
|
|
}
|
|
|
|
.field-select {
|
|
padding: var(--space-2) var(--space-3);
|
|
font-size: var(--text-base);
|
|
color: var(--color-text);
|
|
background-color: var(--color-surface);
|
|
border: 1px solid var(--color-border);
|
|
border-radius: var(--radius-md);
|
|
outline: none;
|
|
font-family: inherit;
|
|
}
|
|
|
|
.field-select:focus {
|
|
border-color: var(--color-primary);
|
|
}
|
|
|
|
/* Radio group */
|
|
.radio-group {
|
|
display: flex;
|
|
gap: var(--space-3);
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.radio-label {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--space-2);
|
|
font-size: var(--text-sm);
|
|
color: var(--color-text);
|
|
cursor: pointer;
|
|
padding: var(--space-2) var(--space-3);
|
|
background-color: var(--color-surface);
|
|
border: 1px solid var(--color-border);
|
|
border-radius: var(--radius-md);
|
|
}
|
|
|
|
.radio-label:has(input:checked) {
|
|
border-color: var(--color-primary);
|
|
background-color: var(--color-surface-hover);
|
|
}
|
|
|
|
.radio-label input[type='radio'] {
|
|
accent-color: var(--color-primary);
|
|
}
|
|
|
|
/* Theme toggle */
|
|
.theme-toggle {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--space-2);
|
|
padding: var(--space-3);
|
|
font-size: var(--text-sm);
|
|
font-family: inherit;
|
|
color: var(--color-text);
|
|
background-color: var(--color-surface);
|
|
border: 1px solid var(--color-border);
|
|
border-radius: var(--radius-md);
|
|
cursor: pointer;
|
|
}
|
|
|
|
.theme-toggle:hover {
|
|
background-color: var(--color-surface-hover);
|
|
}
|
|
|
|
.theme-preview {
|
|
color: var(--color-text-muted);
|
|
}
|
|
|
|
.theme-preview.active-theme {
|
|
color: var(--color-primary);
|
|
font-weight: 600;
|
|
}
|
|
|
|
.theme-divider {
|
|
color: var(--color-text-muted);
|
|
}
|
|
|
|
/* Save button */
|
|
.save-btn {
|
|
padding: var(--space-3);
|
|
font-size: var(--text-sm);
|
|
font-weight: 600;
|
|
color: var(--color-bg);
|
|
background-color: var(--color-primary);
|
|
border: none;
|
|
border-radius: var(--radius-md);
|
|
cursor: pointer;
|
|
}
|
|
|
|
.save-btn:hover {
|
|
opacity: 0.9;
|
|
}
|
|
|
|
/* Admin section */
|
|
.admin-section {
|
|
margin-top: var(--space-8);
|
|
padding-top: var(--space-6);
|
|
border-top: 1px solid var(--color-border);
|
|
}
|
|
|
|
.section-title {
|
|
font-size: var(--text-lg);
|
|
font-weight: 700;
|
|
color: var(--color-text);
|
|
margin-bottom: var(--space-1);
|
|
}
|
|
|
|
.operator-list {
|
|
display: flex;
|
|
flex-direction: column;
|
|
background-color: var(--color-surface);
|
|
border: 1px solid var(--color-border);
|
|
border-radius: var(--radius-lg);
|
|
overflow: hidden;
|
|
}
|
|
|
|
.operator-item {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
padding: var(--space-3) var(--space-4);
|
|
border-bottom: 1px solid var(--color-border);
|
|
}
|
|
|
|
.operator-item:last-child {
|
|
border-bottom: none;
|
|
}
|
|
|
|
.operator-info {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 2px;
|
|
}
|
|
|
|
.operator-name {
|
|
font-size: var(--text-sm);
|
|
font-weight: 600;
|
|
color: var(--color-text);
|
|
}
|
|
|
|
.operator-role {
|
|
font-size: var(--text-xs);
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.05em;
|
|
color: var(--color-text-muted);
|
|
}
|
|
|
|
.current-badge {
|
|
font-size: var(--text-xs);
|
|
font-weight: 600;
|
|
color: var(--color-success);
|
|
padding: 2px var(--space-2);
|
|
background-color: rgba(166, 227, 161, 0.1);
|
|
border-radius: var(--radius-sm);
|
|
}
|
|
|
|
.edit-pin-btn {
|
|
padding: var(--space-1) var(--space-3);
|
|
font-size: var(--text-xs);
|
|
font-weight: 600;
|
|
color: var(--color-text);
|
|
background: none;
|
|
border: 1px solid var(--color-border);
|
|
border-radius: var(--radius-md);
|
|
cursor: pointer;
|
|
}
|
|
|
|
.edit-pin-btn:hover {
|
|
background-color: var(--color-surface-hover);
|
|
}
|
|
</style>
|