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:
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user