feat(10-04): search, link and unlink related records through the relation manager

- relation-manager registered in the field registry; renders only on an
  existing record, never on create, and is never part of the save body
- RelationManager: relation schema label and comment, debounced search,
  selectable linked list (DataTable relation variant), toolbar buttons in
  declared order, confirmed unlink with plural messages and toasts
- RelationPickerModal: Reka Dialog (aria-modal, focus trap, Esc) over the
  candidates endpoint five per page, selection kept across pages, Dodaj (N)
  POSTs link, focus returns to the opener
- admin OpenAPI documents search, sort, dir, page and per_page on the linked
  and candidate relation routes so the SPA sends them typed
- neutral acme.demo.widgets members fixtures and relation smoke tests
This commit is contained in:
Jakub Zych
2026-09-27 17:18:14 +02:00
parent 6e1b6dd5bf
commit f4e97cccad
26 changed files with 1509 additions and 33 deletions

View File

@@ -10,6 +10,7 @@ import { mapWinterUrl } from '../app/winterUrl'
import FormErrorBanner from '../components/form/FormErrorBanner.vue'
import FormGrid from '../components/form/FormGrid.vue'
import FormTabs from '../components/form/FormTabs.vue'
import { needsRecord } from '../components/form/registry'
import {
DEFAULT_TAB,
contextAllows,
@@ -62,7 +63,13 @@ const confirm = useConfirm()
// delete, so the dirty guard does not ask.
let leaving = false
const fields = computed(() => (schema.value?.fields ?? []).filter((field) => contextAllows(field, mode)))
// A relation manager needs a saved record: on create it is dropped with its
// tab even when the YAML forgets `context: update` (D-05, design screen 5).
const fields = computed(() =>
(schema.value?.fields ?? []).filter(
(field) => contextAllows(field, mode) && (mode === 'update' || !needsRecord(field.type)),
),
)
const fieldNames = computed(() => fields.value.map((field) => field.name))
const tabs = computed<TabItem[]>(() => {
@@ -334,6 +341,7 @@ void load()
:errors="errors"
:labels="labels"
:source="path"
:record-id="recordId"
:id-prefix="ID_PREFIX"
@update="update"
/>