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

@@ -14,12 +14,16 @@
* Supports same-group and cross-group (YOLO) conversions.
*/
let { data }: { data: { units: Unit[]; groups: Group[]; yoloLabel: string; yoloDescription: string } } = $props();
let { data }: { data: { units: Unit[]; groups: Group[]; yoloLabel: string; yoloDescription: string; yoloVisibility: 'auto' | 'never' } } = $props();
let units: Unit[] = $derived(data.units);
let groups: Group[] = $derived(data.groups);
let yoloLabel: string = $derived(data.yoloLabel);
let yoloDescription: string = $derived(data.yoloDescription);
let yoloVisibility: 'auto' | 'never' = $derived(data.yoloVisibility);
let showYoloToggle: boolean = $derived(
yoloVisibility !== 'never' && groups.length > 1
);
// ── Reactive state ──────────────────────────────────────────────────────────
let inputValue = $state<number>(1);
@@ -134,7 +138,7 @@
}
// ── Derived: results per group for display — single O(n) pass ──────────────
let orderedResults: { groupLabel: string | null; items: ResultItem[] }[] = $derived.by(() => {
let orderedResults: { groupLabel: string | null; alwaysShowLabel: boolean; items: ResultItem[] }[] = $derived.by(() => {
// Build a Map of groupId → items in a single pass over results
const byGroup = new Map<string | null, ResultItem[]>();
for (const r of results) {
@@ -144,18 +148,18 @@
bucket.push(r);
}
// Emit sections in group-definition order, then orphaned at end
const sections: { groupLabel: string | null; items: ResultItem[] }[] = [];
const sections: { groupLabel: string | null; alwaysShowLabel: boolean; items: ResultItem[] }[] = [];
for (const group of groups) {
const items = byGroup.get(group.id);
if (items?.length) {
const sorted = group.sortOrder === 'alpha'
? [...items].sort((a, b) => a.unit.label.localeCompare(b.unit.label))
: items;
sections.push({ groupLabel: group.label, items: sorted });
sections.push({ groupLabel: group.label, alwaysShowLabel: group.alwaysShowLabel ?? false, items: sorted });
}
}
const orphaned = byGroup.get(null);
if (orphaned?.length) sections.push({ groupLabel: 'Ungrouped', items: orphaned });
if (orphaned?.length) sections.push({ groupLabel: 'Ungrouped', alwaysShowLabel: false, items: orphaned });
return sections;
});
</script>
@@ -182,7 +186,8 @@
{/if}
</div>
<div class="yolo-toggle-row">
<div class="yolo-toggle-row" class:yolo-hidden={!showYoloToggle}>
{#if showYoloToggle}
<label class="yolo-label">
<input type="checkbox" bind:checked={yoloMode} role="switch" />
<span class="yolo-label-text">{yoloLabel}</span>
@@ -190,6 +195,7 @@
<small class="yolo-desc">{yoloDescription}</small>
{/if}
</label>
{/if}
</div>
<ConverterCard
@@ -210,6 +216,10 @@
<style>
.yolo-toggle-row {
margin-bottom: 1rem;
min-height: 0;
}
.yolo-toggle-row.yolo-hidden {
margin-bottom: 0;
}
.yolo-label {