fix(admin-ui): DatePicker + nested object + raw record fetch для Edit
Несколько связанных багов в форме записей:
DatePicker:
- launch_date / decay_date (format=date) и validFrom/validTo рендерились как
raw text input → теперь @nstart/ui DatePicker с RU локалью.
- DatePickerProvider добавлен в main.tsx с DATE_PICKER_LOCALE_RU.
- Far-future infinity dates (year>=9999) скрываются (пустое поле).
- ISO date format на submit: yyyy-MM-dd.
Nested object → JsonField fallback:
- orbit (type=object с raan_deg/eccentricity/...) и antennas (array of object)
показывали [object Object] в text input. Добавлен JsonField — TextArea
с JSON.stringify/parse, локальный state для невалидного промежуточного
ввода + inline parse error.
Edit raw record fetch:
- Read-api flatten'ит i18n в строку выбранной локали → form для edit получал
data.name = 'Terra' вместо {ru-RU, en-US}. На Edit теперь fetch raw record
через writer GET /api/v1/dictionaries/{name}/records/{businessKey}.
- nginx regex location ~ ^/api/v1/dictionaries/ → writer (вместо узкого regex
только для /dictionaries/{name}). GET schema, GET raw record, mutations —
всё на writer. Read-api держит /api/v1/{name}/records/* (другой URL pattern).
- LoadingBlock пока тащим raw запись + Alert на ошибке.
This commit is contained in:
@@ -21,7 +21,7 @@ import {
|
||||
TextInput,
|
||||
} from '@nstart/ui'
|
||||
import { PlusIcon, PencilSimpleIcon, XCircleIcon } from '@phosphor-icons/react'
|
||||
import { useDictionaryDetail, useRecords } from '@/api/queries'
|
||||
import { useDictionaryDetail, useRecordRaw, useRecords } from '@/api/queries'
|
||||
import { useCreateRecord, useUpdateRecord, useCloseRecord } from '@/api/mutations'
|
||||
import type { CreateRecordRequest, FlattenedRecord } from '@/api/client'
|
||||
import { SchemaDrivenForm } from '@/components/form/SchemaDrivenForm'
|
||||
@@ -48,6 +48,9 @@ function DictionaryDetail() {
|
||||
const [edit, setEdit] = useState<EditState>({ kind: 'closed' })
|
||||
const [closeReason, setCloseReason] = useState('')
|
||||
|
||||
const editingKey = edit.kind === 'edit' ? edit.record.businessKey : undefined
|
||||
const rawRecordQuery = useRecordRaw(name, editingKey)
|
||||
|
||||
const handleSubmit = (req: CreateRecordRequest) => {
|
||||
if (edit.kind === 'create') {
|
||||
createMut.mutate(req, { onSuccess: () => setEdit({ kind: 'closed' }) })
|
||||
@@ -188,24 +191,43 @@ function DictionaryDetail() {
|
||||
panelClassName="max-h-[calc(100vh-2rem)] my-4 flex flex-col"
|
||||
bodyClassName="overflow-y-auto flex-1"
|
||||
>
|
||||
{detailQuery.data && (edit.kind === 'create' || edit.kind === 'edit') && (
|
||||
{detailQuery.data && edit.kind === 'create' && (
|
||||
<SchemaDrivenForm
|
||||
schema={detailQuery.data.schemaJson}
|
||||
supportedLocales={detailQuery.data.supportedLocales}
|
||||
defaultLocale={detailQuery.data.defaultLocale}
|
||||
mode={edit.kind}
|
||||
mode="create"
|
||||
isPending={Boolean(activeMutation?.isPending)}
|
||||
serverError={serverError}
|
||||
defaultValues={
|
||||
edit.kind === 'edit'
|
||||
? {
|
||||
businessKey: edit.record.businessKey,
|
||||
data: edit.record.data,
|
||||
validFrom: toDatetimeLocal(edit.record.validFrom),
|
||||
validTo: toDatetimeLocal(edit.record.validTo),
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
onSubmit={handleSubmit}
|
||||
onCancel={() => setEdit({ kind: 'closed' })}
|
||||
/>
|
||||
)}
|
||||
|
||||
{detailQuery.data && edit.kind === 'edit' && rawRecordQuery.isLoading && (
|
||||
<LoadingBlock size="md" label={t('loading')} />
|
||||
)}
|
||||
|
||||
{detailQuery.data && edit.kind === 'edit' && rawRecordQuery.error && (
|
||||
<Alert variant="error" title={t('error.failed')}>
|
||||
{String(rawRecordQuery.error)}
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{detailQuery.data && edit.kind === 'edit' && rawRecordQuery.data && (
|
||||
<SchemaDrivenForm
|
||||
schema={detailQuery.data.schemaJson}
|
||||
supportedLocales={detailQuery.data.supportedLocales}
|
||||
defaultLocale={detailQuery.data.defaultLocale}
|
||||
mode="edit"
|
||||
isPending={Boolean(activeMutation?.isPending)}
|
||||
serverError={serverError}
|
||||
defaultValues={{
|
||||
businessKey: rawRecordQuery.data.businessKey,
|
||||
data: rawRecordQuery.data.data,
|
||||
validFrom: toIsoDate(rawRecordQuery.data.validFrom),
|
||||
validTo: toIsoDate(rawRecordQuery.data.validTo),
|
||||
}}
|
||||
onSubmit={handleSubmit}
|
||||
onCancel={() => setEdit({ kind: 'closed' })}
|
||||
/>
|
||||
@@ -260,12 +282,13 @@ function DictionaryDetail() {
|
||||
)
|
||||
}
|
||||
|
||||
const toDatetimeLocal = (iso: string | undefined): string => {
|
||||
const toIsoDate = (iso: string | undefined): string => {
|
||||
if (!iso) return ''
|
||||
const d = new Date(iso)
|
||||
if (isNaN(d.getTime())) return ''
|
||||
if (d.getFullYear() >= 9999) return ''
|
||||
const pad = (n: number) => String(n).padStart(2, '0')
|
||||
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}`
|
||||
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`
|
||||
}
|
||||
|
||||
const serverErrorMessage = (err: unknown): string | null => {
|
||||
|
||||
Reference in New Issue
Block a user