Commit Graph

57 Commits

Author SHA1 Message Date
Zimin A.N. 171d049fc5 feat(admin-ui): поиск справочников + счётчик активных записей
Backend:
- DictionaryRecordRepository.countActiveGroupedByDictionary — один GROUP BY
  по всем справочникам с фильтром по scope, без N+1.
- DictionaryResponse.recordCount (nullable) + factory from(d, count).
- DictionaryDefinitionController передаёт счётчики в list/get,
  фильтрованные по currentScopes() пользователя.

Frontend:
- SearchInput над сеткой карточек, client-side filter по
  name/displayName/description (case-insensitive, useDeferredValue).
- Badge "N записей" (i18n plural ru/en) в углу карточки.
- "Показано N из M" рядом с поиском.
- EmptyState когда поиск пустой.
2026-05-06 08:46:45 +03:00
Zimin A.N. 7b2fe6f2d5 feat(audit): полноценный writer в audit_log + admin UI (audit + outbox DLQ)
До этого audit_log entity была определена, но никто туда не писал — реальный
аудит шёл только через Hibernate Envers (без user/IP/trace). Теперь:

- AuditLogger service пишет в той же транзакции что и CRUD (DictionaryRecordService).
  Захватывает: user (JWT sub либо preferred_username), scope, IP (X-Forwarded-For),
  UA, trace_id (MDC), request_id. Не ломает основной flow при сбое — только
  громко логирует.
- AuditLogRepository: гибкий search() с nullable фильтрами (dictionary, user,
  action, businessKey, from, to) + Pageable.
- AuditAdminController: GET /admin/audit (paginated) + /admin/audit/export.csv
  (cap 10k строк против OOM).

Frontend:
- /audit route: фильтр-панель, таблица с цветными бейджами (CREATE/UPDATE/CLOSE),
  expand-row с JSON before/after diff, footer IP/UA/req_id, кнопка экспорта CSV.
  Pagination 50/100/200.
- /outbox route: stat-cards pending/DLQ + DLQ-таблица с last_error.
- Nav links + i18n (RU + EN).
2026-05-04 15:44:14 +03:00
Zimin A.N. 0289cf5ff2 fix(admin-ui): отправляем валидный ISO datetime в validFrom/validTo + format=date-time
500 при сохранении записи — backend ждёт OffsetDateTime в DTO CreateRecordRequest,
а DatePicker отдавал просто 'yyyy-MM-dd'. Добавил helpers:
  - formatIsoDateTime(d) → 'yyyy-MM-ddT00:00:00Z'
  - ensureIsoDateTime(s) → нормализует submit-значение validFrom/validTo

Применил:
  - validFrom/validTo на submit нормализуются (если YYYY-MM-DD → +T00:00:00Z)
  - format=date-time data поля отдают datetime ISO, format=date — по-прежнему YYYY-MM-DD

Также добавил скаффолд для D-pack (history drawer + schema diff util) — не
интегрировано в роуты, доделаем в следующем коммите.
2026-05-04 03:29:17 +03:00
Zimin A.N. 987559a6ab feat(admin-ui): visual schema builder для создания/редактирования справочников
Новый функционал — заводить свои справочники через UI без code deploy:

API:
- CreateDictionaryRequest type в client.ts
- useCreateDictionary / useUpdateDictionary mutations с invalidate
  ['dictionaries'] и ['dictionary', name]

Компоненты (src/components/schema/):
- types.ts: PropertyDef + buildSchemaJson() (Draft-07) + parseSchemaJson()
  для двусторонней конвертации между UI state и schema_json
- PropertyEditor: name + kind select + required + description + type-specific
  опции (minLength/maxLength/pattern для string; minimum/maximum для number;
  enum values; uniqueItems для array). Up/Down/Trash icon buttons
- SchemaBuilder: список property cards + кнопка «добавить» + EmptyState
- DictionaryEditorDialog: модалка с 3 табами:
  * Метаданные (name/displayName/description/scope/bundle/version/locales)
  * Поля (SchemaBuilder)
  * JSON (readonly preview сгенерированной schema_json)

Поддерживаемые типы полей: string, integer, number, boolean, enum,
localized (i18n object с patternProperties), date (format=date),
datetime (format=date-time), array_string (с опц. enum + uniqueItems).

Wiring:
- /dictionaries: PageHeader actions = «+ Создать справочник» → Dialog в режиме create
  → onSuccess navigate на /dictionaries/{name}
- /dictionaries/$name: PageHeader actions расширен «Схема» (secondary) +
  «Создать запись» (primary). Dialog в edit-режиме prefilled из
  detailQuery.data + parseSchemaJson()

i18n: 30+ новых ключей в schema.* / scope.* (RU/EN)
2026-05-04 03:12:56 +03:00
Zimin A.N. ef31499bd9 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 на ошибке.
2026-05-04 03:01:08 +03:00
Zimin A.N. b1b1f05e3c 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)
2026-05-04 01:49:08 +03:00
Александр Зимин 1eba2ee59b feat(admin-ui): minimal React+Vite+TanStack scaffolding
- Vite 7 + React 19 + TanStack Router (file-based) + TanStack Query
- Tailwind v4 (CSS-first config, OKLCH brand colors)
- i18next + react-i18next с RU/EN switcher в header
- Axios client с Accept-Language interceptor + JWT placeholder
- 2 pages: /dictionaries (list cards) + /dictionaries/$name (records table)
- Dockerfile (node build + nginx serving + API proxy)
- README + .gitignore

Backend:
- Новый GET /api/v1/dictionaries в read-api (DictionaryListController),
  фильтрует по доступному scope через ScopeContext
- DictionaryListItem DTO без heavy schema_json

routeTree.gen.ts — stub; реальное содержимое генерится Vite plugin'ом при
первом 'pnpm dev'.
2026-05-04 00:55:04 +03:00