feat(10-03): tabs, toggles, relation fields, form lifecycle and settings pages
- FormTabs groups fields by tab (untabbed fields in the default tab) as a
segmented tablist; a tab holding invalid fields after a 422 shows a
count badge and the form switches to the first invalid field
- switch, checkbox and relation join the renderer registry: toggle cards
(role=switch, 20px checkbox) keep a numeric value numeric; relation
fields are read-only labels from meta.labels, a searchable single
select over fields/{field}/options with emptyOption first, or ordered
removable chips with an appending search (300 ms debounce, 20 per
page, more on scroll or the more action)
- FormView gets the back button, record title and update subtitle, a
sticky footer (Usuń with deleteConfirm then DELETE, Anuluj, Zapisz i
zamknij to the mapped redirectClose, Zapisz), and asks before leaving a
dirty form on any route change plus a beforeunload guard
- Settings: the rail pins Ustawienia to the bottom when /settings is
non-empty; /settings lists pages by category; /settings/:code renders
the settings schema through FormGrid and the registry, PUTs the values
and maps a 422 like the record form
- New backend::lang keys (form.more_options, tab_default, discard,
settings.back); form and settings smoke tests; boardwalk/dist rebuilt
This commit is contained in:
45
admin/src/views/SettingsIndexView.vue
Normal file
45
admin/src/views/SettingsIndexView.vue
Normal file
@@ -0,0 +1,45 @@
|
||||
<script setup lang="ts">
|
||||
import { RouterLink } from 'vue-router'
|
||||
import { t } from '../app/i18n'
|
||||
import { iconFor } from '../app/icons'
|
||||
import { settingsGroups } from '../state/useSettings'
|
||||
|
||||
// Settings index (D-21): every permitted settings page, grouped by category,
|
||||
// with its icon, label and description. The server filters the list.
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="flex flex-col gap-5">
|
||||
<header>
|
||||
<h1 class="text-[26px] font-bold tracking-[-0.02em]">{{ t('backend::lang.settings.title') }}</h1>
|
||||
</header>
|
||||
<p v-if="settingsGroups.length === 0" class="text-muted">{{ t('backend::lang.settings.empty') }}</p>
|
||||
<section
|
||||
v-for="group in settingsGroups"
|
||||
:key="group.category"
|
||||
data-settings-group
|
||||
class="flex flex-col gap-3"
|
||||
>
|
||||
<h2 v-if="group.category" class="text-[13px] font-semibold tracking-[0.02em] text-muted uppercase">
|
||||
{{ group.category }}
|
||||
</h2>
|
||||
<div class="grid grid-cols-1 gap-3 min-[900px]:grid-cols-2">
|
||||
<RouterLink
|
||||
v-for="entry in group.entries"
|
||||
:key="entry.code"
|
||||
:to="`/settings/${encodeURIComponent(entry.code)}`"
|
||||
:data-settings="entry.code"
|
||||
class="flex items-start gap-3.5 rounded-card border border-border bg-surface p-5 text-text no-underline shadow-card hover:bg-hover"
|
||||
>
|
||||
<span class="flex size-10 shrink-0 items-center justify-center rounded-inner bg-subtle text-muted">
|
||||
<component :is="iconFor(entry.icon)" :size="20" aria-hidden="true" />
|
||||
</span>
|
||||
<span class="flex min-w-0 flex-col">
|
||||
<span class="font-semibold">{{ entry.label }}</span>
|
||||
<span v-if="entry.description" class="text-[13px] text-muted">{{ entry.description }}</span>
|
||||
</span>
|
||||
</RouterLink>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
</template>
|
||||
Reference in New Issue
Block a user