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:
@@ -38,6 +38,10 @@ export type RecordsFilter = {
|
||||
bbox?: string
|
||||
/** GeoJSON Polygon as raw object — serialized as JSON в query string. */
|
||||
polygon?: GeoJSON.Polygon
|
||||
/** ISO datetime для time-travel запроса (CEO plan time-travel UI).
|
||||
* Backend (read-api) findActiveAt(dict, key, at) возвращает active version
|
||||
* на этот момент. Без этого param'а — now(). */
|
||||
at?: string
|
||||
}
|
||||
|
||||
export const recordsQuery = (
|
||||
@@ -52,11 +56,13 @@ export const recordsQuery = (
|
||||
scopeCsv,
|
||||
filter?.bbox ?? null,
|
||||
filter?.polygon ? JSON.stringify(filter.polygon) : null,
|
||||
filter?.at ?? null,
|
||||
] as const,
|
||||
queryFn: async (): Promise<FlattenedRecord[]> => {
|
||||
const params: Record<string, string> = { as_scope: scopeCsv }
|
||||
if (filter?.bbox) params.bbox = filter.bbox
|
||||
if (filter?.polygon) params.polygon = JSON.stringify(filter.polygon)
|
||||
if (filter?.at) params.at = filter.at
|
||||
const { data } = await apiClient.get<FlattenedRecord[]>(
|
||||
`/${dictionaryName}/records`,
|
||||
{ params },
|
||||
|
||||
Reference in New Issue
Block a user