From bc426a9975b1cc172bad8ce8ff4f0212f031c4ce Mon Sep 17 00:00:00 2001 From: "Zimin A.N." Date: Thu, 21 May 2026 13:28:38 +0300 Subject: [PATCH] =?UTF-8?q?chore(admin-ui):=20=D0=B4=D0=B8=D0=B0=D0=B3?= =?UTF-8?q?=D0=BD=D0=BE=D1=81=D1=82=D0=B8=D1=87=D0=B5=D1=81=D0=BA=D0=B0?= =?UTF-8?q?=D1=8F=20=D0=B8=D0=BD=D1=81=D1=82=D1=80=D1=83=D0=BC=D0=B5=D0=BD?= =?UTF-8?q?=D1=82=D0=B0=D1=86=D0=B8=D1=8F=20=D1=81=D0=BE=D1=85=D1=80=D0=B0?= =?UTF-8?q?=D0=BD=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=B7=D0=B0=D0=BF=D0=B8=D1=81?= =?UTF-8?q?=D0=B8=20[TEMP]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ВРЕМЕННО — для поиска причины «Save ничего не делает». Откатить после диагностики (все логи помечены [ordinis-diag]). SchemaDrivenForm: console.warn в submit() — событие формы, RHF onInvalid, результат AJV, ветки return, вызов onSubmit. dictionaries.$name: логи в route.handleSubmit + createMut/updateMut mutate + onSuccess/onError со статусом ответа. Также добавлен постоянный регресс-тест: повторный submit после исправления валидации вызывает onSubmit. --- .../components/form/SchemaDrivenForm.test.tsx | 35 +++++++++++++++++++ .../src/components/form/SchemaDrivenForm.tsx | 33 ++++++++++++++++- .../src/routes/dictionaries.$name.tsx | 22 +++++++++++- 3 files changed, 88 insertions(+), 2 deletions(-) diff --git a/ordinis-admin-ui/src/components/form/SchemaDrivenForm.test.tsx b/ordinis-admin-ui/src/components/form/SchemaDrivenForm.test.tsx index 7d42d5a..6c74298 100644 --- a/ordinis-admin-ui/src/components/form/SchemaDrivenForm.test.tsx +++ b/ordinis-admin-ui/src/components/form/SchemaDrivenForm.test.tsx @@ -219,4 +219,39 @@ describe('SchemaDrivenForm', () => { ) expect(screen.getByText('Дубликат business_key')).toBeInTheDocument() }) + + // Regression: после первой неудачной AJV-валидации manual-ошибки + // (setError на data.*) застревали в formState.errors. На повторном Save + // RHF handleSubmit видел непустой errors и молча НЕ вызывал submit — + // форма «не сохранялась» после исправления полей, ошибок не видно. + it('повторный submit после исправления валидации вызывает onSubmit', async () => { + const user = userEvent.setup() + const onSubmit = vi.fn() + const { container } = renderWithI18n( + {}} + />, + ) + const save = () => screen.getByRole('button', { name: /^Сохранить$/i }) + const field = (name: string): HTMLElement => { + const el = container.querySelector(`input[name="${name}"]`) + if (!el) throw new Error(`field input not found: ${name}`) + return el as HTMLElement + } + // Попытка 1 — required code/name пустые → AJV fail, submit не проходит. + await user.click(save()) + expect(onSubmit).not.toHaveBeenCalled() + // Исправляем — заполняем оба required-поля. + await user.type(field('data.code'), 'X1') + await user.type(field('data.name'), 'Тест') + // Попытка 2 — данные валидны, должно вызвать onSubmit. + await user.click(save()) + expect(onSubmit).toHaveBeenCalledTimes(1) + }) }) diff --git a/ordinis-admin-ui/src/components/form/SchemaDrivenForm.tsx b/ordinis-admin-ui/src/components/form/SchemaDrivenForm.tsx index b15cc9b..9a8fb59 100644 --- a/ordinis-admin-ui/src/components/form/SchemaDrivenForm.tsx +++ b/ordinis-admin-ui/src/components/form/SchemaDrivenForm.tsx @@ -268,10 +268,18 @@ export const SchemaDrivenForm = ({ }, [visibleFieldCount, totalFieldCount]) const submit: SubmitHandler = (values) => { + // [ordinis-diag] DIAGNOSTIC BUILD — временная инструментация для поиска + // причины «Save ничего не делает». Удалить после диагностики. + console.warn('[ordinis-diag] submit(): RHF-валидация пройдена, handler запущен', { + mode, + businessKey: values.businessKey, + dataKeys: Object.keys(values.data ?? {}), + }) if (values.validFrom && values.validTo) { const from = new Date(values.validFrom) const to = new Date(values.validTo) if (!isNaN(from.getTime()) && !isNaN(to.getTime()) && from >= to) { + console.warn('[ordinis-diag] submit(): ЗАБЛОКИРОВАНО — validTo <= validFrom') setError('validTo', { type: 'manual', message: t('form.error.validToBeforeFrom') }) setActiveTab('identity') return @@ -279,7 +287,13 @@ export const SchemaDrivenForm = ({ } if (validator) { const ok = validator(values.data) + console.warn('[ordinis-diag] submit(): AJV-валидация →', { + ok, + errorCount: validator.errors?.length ?? 0, + errors: validator.errors, + }) if (!ok && validator.errors && validator.errors.length > 0) { + console.warn('[ordinis-diag] submit(): ЗАБЛОКИРОВАНО — AJV errors, onSubmit НЕ вызван') applyAjvErrors(validator.errors, setError) const firstField = firstFieldFromErrors(validator.errors) if (firstField) { @@ -288,10 +302,15 @@ export const SchemaDrivenForm = ({ } return } + } else { + console.warn('[ordinis-diag] submit(): validator == null (схема не скомпилировалась)') } const derivedKey = idSource && mode === 'create' ? String(values.data?.[idSource] ?? '').trim() : values.businessKey.trim() + console.warn('[ordinis-diag] submit(): вызываю onSubmit() → мутация', { + businessKey: derivedKey, + }) onSubmit({ businessKey: derivedKey, data: values.data, @@ -300,6 +319,13 @@ export const SchemaDrivenForm = ({ }) } + // [ordinis-diag] RHF onInvalid — вызывается когда handleSubmit РЕШИЛ что + // форма невалидна и НЕ вызвал submit(). Показывает что именно RHF считает + // ошибкой (включая застрявшие manual-ошибки). Удалить после диагностики. + const diagOnInvalid = (errs: unknown) => { + console.warn('[ordinis-diag] handleSubmit ЗАБЛОКИРОВАН (onInvalid) — formState.errors:', errs) + } + const tabErrorCount = countErrorsPerSection(formState.errors, buckets, sectionOrder) const visibleTabs: TabItem[] = sectionOrder .filter((id) => id === 'identity' || (buckets[id]?.length ?? 0) > 0) @@ -474,7 +500,12 @@ export const SchemaDrivenForm = ({ return (
{ + // [ordinis-diag] DIAGNOSTIC — событие submit формы вообще дошло? + // Удалить после диагностики. + console.warn('[ordinis-diag] submit event сработал (formId=' + String(formId) + ')') + void handleSubmit(submit, diagOnInvalid)(e) + }} ref={formContainerRef} >
diff --git a/ordinis-admin-ui/src/routes/dictionaries.$name.tsx b/ordinis-admin-ui/src/routes/dictionaries.$name.tsx index 6fc57e1..baa3e5f 100644 --- a/ordinis-admin-ui/src/routes/dictionaries.$name.tsx +++ b/ordinis-admin-ui/src/routes/dictionaries.$name.tsx @@ -578,6 +578,12 @@ function DictionaryDetail() { const rawRecordQuery = useRecordRaw(name, editingKey) const handleSubmit = (req: CreateRecordRequest) => { + // [ordinis-diag] DIAGNOSTIC BUILD — удалить после диагностики. + console.warn('[ordinis-diag] route.handleSubmit(): получен req от формы', { + editKind: edit.kind, + approvalRequired, + businessKey: req.businessKey, + }) // Fill validFrom at submit (review #5): defaultValues в RHF берётся // ТОЛЬКО при mount. Если user открыл modal в 12:00 и submit в 12:05 — // `validFrom` в req это значение из 12:00. Backend check `validFrom > @@ -631,14 +637,20 @@ function DictionaryDetail() { ) return } + console.warn('[ordinis-diag] route: вызываю createMut.mutate() — POST /records') createMut.mutate( { payload: submitReq, idempotencyKey: recordIdempotencyKey }, { onSuccess: () => { + console.warn('[ordinis-diag] createMut onSuccess — запись создана') setEdit({ kind: 'closed' }) resetRecordIdempotencyKey() }, onError: (err) => { + console.warn('[ordinis-diag] createMut onError', { + status: (err as { response?: { status?: number } })?.response?.status, + data: (err as { response?: { data?: unknown } })?.response?.data, + }) // Create rarely conflicts (no prior record to lock), но если backend // вернёт 409 по уникальному ключу — businessKey возьмём из payload. handleSaveError(err, submitReq.businessKey ?? '') @@ -669,6 +681,7 @@ function DictionaryDetail() { ) return } + console.warn('[ordinis-diag] route: вызываю updateMut.mutate() — PUT /records/' + edit.record.businessKey) updateMut.mutate( { businessKey: edit.record.businessKey, @@ -677,10 +690,17 @@ function DictionaryDetail() { }, { onSuccess: () => { + console.warn('[ordinis-diag] updateMut onSuccess — запись обновлена') setEdit({ kind: 'closed' }) resetRecordIdempotencyKey() }, - onError: (err) => handleSaveError(err, edit.record.businessKey), + onError: (err) => { + console.warn('[ordinis-diag] updateMut onError', { + status: (err as { response?: { status?: number } })?.response?.status, + data: (err as { response?: { data?: unknown } })?.response?.data, + }) + handleSaveError(err, edit.record.businessKey) + }, }, ) }