feat(admin-ui): record CRUD with json-schema-driven form

POST/PUT/DELETE формы для записей справочников. Schema-driven рендер полей
из definition.schemaJson — i18n-aware (x-localized → input на каждый supportedLocale),
enum → select, type → text/number/checkbox/array. Валидация через ajv (Draft-07,
match backend networknt validator).

- new: api/mutations.ts с Idempotency-Key (crypto.randomUUID) и query invalidation
- new: SchemaDrivenForm — react-hook-form + ajv compile per-schema, errors через
  setError по ajv instancePath
- new: Modal (Esc + backdrop click) и confirm dialog для close-операции
- nginx: regex location ^/api/v1/dictionaries/[^/]+$ → writer (admin schema fetch)
- queries: dictionaryDetailQuery возвращает full def с schemaJson
- i18n: RU/EN ключи для CRUD (create/edit/close, validFrom/validTo, confirm)
This commit is contained in:
Zimin A.N.
2026-05-04 01:49:08 +03:00
parent d5a747616a
commit b1b1f05e3c
11 changed files with 3137 additions and 36 deletions
+103 -13
View File
@@ -1,15 +1,105 @@
/* eslint-disable */
// @ts-nocheck
// Файл регенерируется TanStackRouterVite plugin'ом во время `vite dev`/`vite build`.
// Этот stub существует только для cold start — Vite plugin перепишет его перед
// собственно сборкой.
import { Route as RootRoute } from './routes/__root'
import { Route as IndexRoute } from './routes/index'
import { Route as DictionariesRoute } from './routes/dictionaries'
import { Route as DictionariesNameRoute } from './routes/dictionaries.$name'
export const routeTree = (RootRoute as any).addChildren([
IndexRoute,
DictionariesRoute,
DictionariesNameRoute,
])
// @ts-nocheck
// noinspection JSUnusedGlobalSymbols
// This file was automatically generated by TanStack Router.
// You should NOT make any changes in this file as it will be overwritten.
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
import { Route as rootRouteImport } from './routes/__root'
import { Route as DictionariesRouteImport } from './routes/dictionaries'
import { Route as IndexRouteImport } from './routes/index'
import { Route as DictionariesNameRouteImport } from './routes/dictionaries.$name'
const DictionariesRoute = DictionariesRouteImport.update({
id: '/dictionaries',
path: '/dictionaries',
getParentRoute: () => rootRouteImport,
} as any)
const IndexRoute = IndexRouteImport.update({
id: '/',
path: '/',
getParentRoute: () => rootRouteImport,
} as any)
const DictionariesNameRoute = DictionariesNameRouteImport.update({
id: '/$name',
path: '/$name',
getParentRoute: () => DictionariesRoute,
} as any)
export interface FileRoutesByFullPath {
'/': typeof IndexRoute
'/dictionaries': typeof DictionariesRouteWithChildren
'/dictionaries/$name': typeof DictionariesNameRoute
}
export interface FileRoutesByTo {
'/': typeof IndexRoute
'/dictionaries': typeof DictionariesRouteWithChildren
'/dictionaries/$name': typeof DictionariesNameRoute
}
export interface FileRoutesById {
__root__: typeof rootRouteImport
'/': typeof IndexRoute
'/dictionaries': typeof DictionariesRouteWithChildren
'/dictionaries/$name': typeof DictionariesNameRoute
}
export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath
fullPaths: '/' | '/dictionaries' | '/dictionaries/$name'
fileRoutesByTo: FileRoutesByTo
to: '/' | '/dictionaries' | '/dictionaries/$name'
id: '__root__' | '/' | '/dictionaries' | '/dictionaries/$name'
fileRoutesById: FileRoutesById
}
export interface RootRouteChildren {
IndexRoute: typeof IndexRoute
DictionariesRoute: typeof DictionariesRouteWithChildren
}
declare module '@tanstack/react-router' {
interface FileRoutesByPath {
'/dictionaries': {
id: '/dictionaries'
path: '/dictionaries'
fullPath: '/dictionaries'
preLoaderRoute: typeof DictionariesRouteImport
parentRoute: typeof rootRouteImport
}
'/': {
id: '/'
path: '/'
fullPath: '/'
preLoaderRoute: typeof IndexRouteImport
parentRoute: typeof rootRouteImport
}
'/dictionaries/$name': {
id: '/dictionaries/$name'
path: '/$name'
fullPath: '/dictionaries/$name'
preLoaderRoute: typeof DictionariesNameRouteImport
parentRoute: typeof DictionariesRoute
}
}
}
interface DictionariesRouteChildren {
DictionariesNameRoute: typeof DictionariesNameRoute
}
const DictionariesRouteChildren: DictionariesRouteChildren = {
DictionariesNameRoute: DictionariesNameRoute,
}
const DictionariesRouteWithChildren = DictionariesRoute._addFileChildren(
DictionariesRouteChildren,
)
const rootRouteChildren: RootRouteChildren = {
IndexRoute: IndexRoute,
DictionariesRoute: DictionariesRouteWithChildren,
}
export const routeTree = rootRouteImport
._addFileChildren(rootRouteChildren)
._addFileTypes<FileRouteTypes>()