Polish the Site and Facility settings page
All checks were successful
CI / backend (push) Successful in 1m12s
CI / naming (push) Successful in 1s
CI / frontend (push) Successful in 7s

Booleans render as checkbox toggles, the employee/USB directory-mode
settings as selfhosted/external dropdowns, and every field has a label
and help text (raw keys and type-true/false text boxes are gone).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
cproudlock
2026-07-12 07:14:06 -04:00
parent 7bde765f1a
commit 5393846b8d
2 changed files with 40 additions and 3 deletions

View File

@@ -94,6 +94,9 @@ ADR-007 and ADR-002.
### Fixed
- Site & Facility settings page renders booleans as toggles and the
directory-mode settings as dropdowns, with labels and help text for every
field (no more raw keys or type-true/false boxes).
- System Settings tabs follow the URL: clicking a settings-rail link that
only changes the ?tab= query (Branding, Floor Map) now switches the right
panel, tab clicks update the URL, and browser back/forward restore tabs.

View File

@@ -10,7 +10,14 @@
<div v-else class="form-grid">
<label v-for="s in items" :key="s.key" class="field">
<span>{{ prettyLabel(s.key) }}</span>
<input v-model="s.value" type="text" :placeholder="fieldHelp(s.key) ? '' : s.description" />
<span v-if="s.valuetype === 'boolean'" class="bool-row">
<input type="checkbox" :checked="isTrue(s)" @change="s.value = isTrue(s) ? 'false' : 'true'" />
<span class="muted">{{ isTrue(s) ? 'Enabled' : 'Disabled' }}</span>
</span>
<select v-else-if="OPTIONS[s.key]" v-model="s.value">
<option v-for="opt in OPTIONS[s.key]" :key="opt" :value="opt">{{ opt }}</option>
</select>
<input v-else v-model="s.value" type="text" :placeholder="fieldHelp(s.key) ? '' : s.description" />
<small class="muted">{{ fieldHelp(s.key) || s.description }}</small>
</label>
@@ -45,14 +52,30 @@ const LABELS = {
pc_access_domain: 'PC Access Domain',
employeeid_pattern: 'Employee ID Pattern',
printer_hostname_template: 'Printer Hostname Template',
dualpath_single_machine: 'Dualpath as Single Machine'
dualpath_single_machine: 'Dualpath as Single Machine',
employee_directory_mode: 'Employee Directory Mode',
usb_directory_mode: 'USB Directory Mode',
setup_complete: 'Setup Complete'
}
function prettyLabel(key) {
return LABELS[key] || key
}
// Enumerated settings render as dropdowns to prevent typos.
const OPTIONS = {
employee_directory_mode: ['selfhosted', 'external'],
usb_directory_mode: ['selfhosted', 'external']
}
function isTrue(setting) {
return String(setting.value).toLowerCase() === 'true'
}
// Inline help for site fields that need more than the stored description.
const HELP = {
employee_directory_mode: 'Where employee records live: selfhosted (managed in this app, with CSV import and photo uploads) or external (a read-only HR database configured via EMPLOYEE_DB_* environment variables).',
usb_directory_mode: 'Where USB checkout data lives: selfhosted (tables in this app) or external (a separate cmmc_usb database via CMMC_USB_DB_* environment variables).',
setup_complete: 'Set automatically when the first-run setup wizard finishes. Turning it off sends admins back to the /setup wizard on next login.',
employeeid_pattern: 'Regular expression that a scanned/typed employee ID must match to be recognized. Default: ^\\d{9}$ (9 digits). An invalid regex is ignored and the default is used.',
printer_hostname_template: 'Template for generating printer hostnames from an IP. Use {ip} where the dash-separated IP goes. Example: Printer-{ip}.printer.geaerospace.net',
dualpath_single_machine: 'Treat a Dualpath pair (a dual-bay machine with one controller) as a single machine in lists, counts, and the floor map. Both bay records are always kept; detail pages stay per-bay with a sibling banner. Enter true or false. Default: true.'
@@ -109,13 +132,24 @@ onMounted(load)
.field > span {
font-weight: 600;
}
.field input {
.field input[type='text'],
.field select {
padding: 8px 10px;
border: 1px solid var(--border);
border-radius: 6px;
background: var(--bg);
color: var(--text);
}
.bool-row {
display: flex;
align-items: center;
gap: 8px;
}
.bool-row input[type='checkbox'] {
width: 18px;
height: 18px;
accent-color: var(--primary);
}
.muted {
color: var(--text-light);
font-size: 0.85rem;