feat(10-03): open, edit and save a record with toast and 422 feedback
- The SPA loads the backend::lang bundle before /auth/me, sets the document language from meta.locale and renders plural messages with Intl.PluralRules; interpolate mirrors phrasebook for :name/:Name/:NAME - Create and record routes; mapWinterUrl maps recordUrl and redirects onto the controller's list, create and record routes only - List rows open their record; FormView loads the form schema and the record, shows context-allowed fields in the span grid, saves values keyed by field name and toasts the resolved saved message - A 422 puts each message under its field (aria-invalid, aria-describedby), shows the plural banner, focuses the first invalid field in schema order and clears a field's error on change - The D-05 registry maps text, textarea, number and dropdown; any other type renders the unsupported-field box with the type in DM Mono - The admin OpenAPI document declares the write request bodies (AdminRecord, AdminIDsRequest) and the list filter query as a deepObject, so the typed client can send them - New backend::lang form.load_failed key; boardwalk/dist rebuilt
This commit is contained in:
34
admin/src/components/form/fields/UnsupportedField.vue
Normal file
34
admin/src/components/form/fields/UnsupportedField.vue
Normal file
@@ -0,0 +1,34 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { Puzzle } from '@lucide/vue'
|
||||
import { t } from '../../../app/i18n'
|
||||
import type { FieldControlProps } from '../registry'
|
||||
|
||||
// D-05 fallback for any field type without a renderer. The translated
|
||||
// sentence is split around the type so the type renders in DM Mono, as text.
|
||||
const props = defineProps<FieldControlProps>()
|
||||
|
||||
const MARK = '\u0000'
|
||||
|
||||
const parts = computed(() => {
|
||||
const [before = '', after = ''] = t('backend::lang.form.unsupported_field', { type: MARK }).split(MARK)
|
||||
return { before, after }
|
||||
})
|
||||
|
||||
const hasType = computed(() => t('backend::lang.form.unsupported_field', { type: MARK }).includes(MARK))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
:id="controlId"
|
||||
data-unsupported-field
|
||||
:aria-describedby="describedBy || undefined"
|
||||
class="flex min-h-input items-center gap-2.5 rounded-control border-[1.5px] border-dashed border-border-strong bg-subtle px-3.5 text-[13px] text-muted"
|
||||
>
|
||||
<Puzzle :size="16" aria-hidden="true" />
|
||||
<span v-if="hasType"
|
||||
>{{ parts.before }}<code class="font-mono text-text">{{ props.field.type }}</code>{{ parts.after }}</span
|
||||
>
|
||||
<span v-else>{{ t('backend::lang.form.unsupported_field') }} <code class="font-mono text-text">{{ props.field.type }}</code></span>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user