feat(admin-ui): time-travel UI — view dictionary as-of any past date
CEO plan v1 stretch — закрывает gap "Time-travel UI (slider)".
Backend уже поддерживает ?at=<ISO> в read-api endpoints (DictionaryReadController):
findActiveAt(dict, key, at) возвращает active version на этот момент.
Был только UI gap.
UI:
- New toolbar button "Просмотр на дату" с ClockCounterClockwiseIcon.
aria-pressed когда timeTravelAt активен.
- Datetime picker раскрывается inline (не модал — quick interactions).
Native <input type=datetime-local>, конвертируется ↔ ISO.
- Active state ambient banner (orbit/8 background) если ?at= задан + picker
закрыт — показывает "Просмотр на: {date}" + "Сейчас" клир link.
- "Закрыть" кнопка для picker'а; "Сейчас" сбрасывает at.
URL searchParams:
- DictSearch type расширен `at?: string` (ISO datetime).
- validateSearch проверяет `Date.parse(at)` — invalid timestamps drop'ятся.
- Share-friendly: "ordinis.k8s.265.../dictionaries/spacecraft?at=2025-12-31T00:00:00Z"
работает per F5 + ссылка коллеге.
Records query:
- RecordsFilter type +at?: string.
- recordsQuery передаёт ?at=… backend'у в дополнение к as_scope/bbox/polygon.
- queryKey включает at — switching между моментами trigger'ит refetch.
i18n RU/EN: timeTravel.{button,label,viewing,clear,close}.
Verify:
- pnpm tsc --noEmit: clean.
- pnpm test (vitest): 89/89 PASS.
- pnpm build: clean.
Backend смену не требует — DictionaryReadController.list() уже принимал
@RequestParam(value = "at") OffsetDateTime at и роут'ил в RecordReadService.
This commit is contained in:
@@ -257,6 +257,12 @@ i18n
|
||||
'lineage.refs.openSourceRecord': 'Открыть запись {{key}} в источнике',
|
||||
'lineage.refs.stale': 'обновлено {{minutes}} мин назад',
|
||||
'lineage.refs.staleHint': 'Данные из materialized view; обновляются раз в минуту. Закрытые между обновлениями записи отфильтрованы по valid_to.',
|
||||
// === Time-travel (CEO plan v1 stretch) ===
|
||||
'timeTravel.button': 'Просмотр на дату',
|
||||
'timeTravel.label': 'Активная версия на момент:',
|
||||
'timeTravel.viewing': 'Просмотр на момент: {{date}}',
|
||||
'timeTravel.clear': 'Сейчас',
|
||||
'timeTravel.close': 'Закрыть',
|
||||
// === Cascade close (Phase 3) ===
|
||||
'cascade.title': 'Каскадное закрытие',
|
||||
'cascade.summary.willClose_one': 'Будет закрыта {{total}} запись (включая «{{target}}»). Подтверждение требуется.',
|
||||
@@ -602,6 +608,12 @@ i18n
|
||||
'lineage.refs.openSourceRecord': 'Open referencing record {{key}}',
|
||||
'lineage.refs.stale': 'refreshed {{minutes}} min ago',
|
||||
'lineage.refs.staleHint': 'Data from materialized view, refreshed once per minute. Records closed between refreshes are filtered by valid_to.',
|
||||
// === Time-travel (CEO plan v1 stretch) ===
|
||||
'timeTravel.button': 'View as of date',
|
||||
'timeTravel.label': 'Active version as of:',
|
||||
'timeTravel.viewing': 'Viewing as of: {{date}}',
|
||||
'timeTravel.clear': 'Now',
|
||||
'timeTravel.close': 'Close',
|
||||
// === Cascade close (Phase 3) ===
|
||||
'cascade.title': 'Cascade close',
|
||||
'cascade.summary.willClose_one': '{{total}} record will be closed (including «{{target}}»). Confirmation required.',
|
||||
|
||||
Reference in New Issue
Block a user