feat: yoloVisibility config, alwaysShowLabel on groups, site settings panel

- AppConfig gains yoloVisibility: 'auto' | 'never' (default: auto)
- Group gains alwaysShowLabel?: boolean
- YOLO toggle only renders when showYoloToggle is true (auto + >1 group)
- Group divider shows when >1 group OR alwaysShowLabel is set
- New /admin/api/config GET+PUT endpoint for yolo settings
- Admin: alwaysShowLabel checkbox in group form
- Admin: ⚙ Settings panel with YOLO label, description, and visibility controls
This commit is contained in:
Falkan
2026-03-18 20:15:45 -04:00
parent 2c27edc487
commit b020b74c31
8 changed files with 158 additions and 21 deletions

View File

@@ -7,7 +7,7 @@
// ── State ────────────────────────────────────────────────────────────────────
let unitsData = $state<UnitsData>({ units: [], groups: [] });
let selectedItem = $state<{ type: 'unit' | 'group'; id: string } | null>(null);
let selectedItem = $state<{ type: 'unit' | 'group' | 'settings'; id: string } | null>(null);
let checkedUnitIds = $state<Set<string>>(new Set());
let bulkAction = $state<string>(''); // '' | 'move' | 'delete'
let bulkMoveTargetGroupId = $state<string>('');
@@ -32,6 +32,50 @@
let groupFormSnapshot = $state<Partial<Group>>({});
let groupFieldError = $state<string | null>(null); // which field has error
// Site config state
let configYoloLabel = $state('YOLO mode');
let configYoloDescription = $state('(cross-group conversions)');
let configYoloVisibility = $state<'auto' | 'never'>('auto');
let configSaving = $state(false);
let configError = $state<string | null>(null);
let configSuccess = $state<string | null>(null);
async function loadConfig() {
const res = await fetch(`${api}/config`);
if (!res.ok) return;
const c = await res.json();
configYoloLabel = c.yoloLabel ?? 'YOLO mode';
configYoloDescription = c.yoloDescription ?? '(cross-group conversions)';
configYoloVisibility = c.yoloVisibility ?? 'auto';
}
async function saveConfig() {
configSaving = true;
configError = null;
configSuccess = null;
try {
const res = await fetch(`${api}/config`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
yoloLabel: configYoloLabel,
yoloDescription: configYoloDescription,
yoloVisibility: configYoloVisibility,
})
});
if (!res.ok) { configError = 'Save failed'; }
else { configSuccess = 'Saved.'; }
} catch { configError = 'Network error'; }
configSaving = false;
}
function onConfigKeydown(e: KeyboardEvent) {
if (e.key === 'Enter' && (e.target as HTMLElement).tagName !== 'TEXTAREA') {
e.preventDefault();
saveConfig();
}
}
// ── Auto-ID state ─────────────────────────────────────────────────────────────
let unitIdManuallyEdited = $state(false);
let groupIdManuallyEdited = $state(false);
@@ -195,7 +239,7 @@
expandedGroups = new Set([...expandedGroups, ...groups.map((g) => g.id)]);
}
$effect(() => { loadData(); });
$effect(() => { loadData(); loadConfig(); });
async function extractError(res: Response, fallback: string): Promise<string> {
const body = await res.json().catch(() => ({}));
@@ -842,6 +886,7 @@
<div class="tree-footer">
<button onclick={() => startNewUnit()}>+ Add Unit</button>
<button class="outline" onclick={startNewGroup}>+ Add Group</button>
<button class="outline secondary" onclick={() => { selectedItem = { type: 'settings', id: 'settings' }; }}> Settings</button>
</div>
</aside>
@@ -953,6 +998,10 @@
<option value="alpha">Alphabetical</option>
</select>
</label>
<label class="checkbox-label">
<input type="checkbox" bind:checked={groupForm.alwaysShowLabel} />
<span>Always show group label</span>
</label>
<label class="checkbox-label">
<input type="checkbox" bind:checked={groupForm.hidden} />
<span>Don't show on front end</span>
@@ -971,6 +1020,35 @@
</div>
</div><!-- /onkeydown group form -->
{:else if selectedItem?.type === 'settings'}
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div onkeydown={onConfigKeydown}>
<h3>Site Settings</h3>
{#if configError}<p class="form-msg form-error" role="alert">{configError}</p>{/if}
{#if configSuccess}<p class="form-msg form-success" role="status">{configSuccess}</p>{/if}
<fieldset>
<legend>YOLO Mode Toggle</legend>
<label>
Label
<input type="text" bind:value={configYoloLabel} placeholder="YOLO mode" />
</label>
<label>
Description <small class="muted">(shown inline after label)</small>
<input type="text" bind:value={configYoloDescription} placeholder="(cross-group conversions)" />
</label>
<label>
Visibility
<select bind:value={configYoloVisibility}>
<option value="auto">Auto — show when more than one group is present</option>
<option value="never">Never — always hidden</option>
</select>
</label>
</fieldset>
<div class="form-actions">
<button onclick={saveConfig} disabled={configSaving}>{configSaving ? 'Saving…' : 'Save'}</button>
</div>
</div>
{:else}
<p class="select-hint">Select a unit or group to edit, or add a new one.</p>
{/if}