feat(editor): match redesign prototype layout — TopBar title + tab counts + InfoPanel actions

Per /Users/zimin/Downloads/redesign/screens/03-editor-records.png редизайн
prototype editor layout отличается от current MVP:

1. TopBar absorbs editor title + version (was в PageHeader)
2. PageHeader удалён — title и breadcrumb теперь только в TopBar
3. Tab labels получили counts (Записи 127, Связи 6, Поля 12)
4. Action buttons распределены per role:
   - Time-travel + AOI → InfoPanel bottom (close к data context)
   - Schema edit + +Запись → tab toolbar right (close к tab actions)

Changes:

src/components/layout/TopBar.tsx
  - Breadcrumb получил subtitle slot — mono '<name> · v<version>'
  - useBreadcrumb fetches dict detail для editor route '/dictionaries/$name'
  - Render: '← Справочники / Космические аппараты satellites · v1.0.0'

src/api/queries.ts
  - useDictionaryDetail принимает name: string | undefined + enabled flag
  - Guard для editor breadcrumb когда route не matches dict

src/routes/dictionaries.$name.tsx
  - Removed <PageHeader> — title/breadcrumb теперь в TopBar
  - EditorInfoPanel получает actions prop:
    onTimeTravel + timeTravelActive + onAoi + aoiActive
  - EditorTabBar получает counts (per-tab badge) + actions slot
    (right-aligned Schema + +Запись buttons)
  - extractOutgoingFkCount helper для relations count

src/components/editor/EditorInfoPanel.tsx
  - actions prop с Time-travel + AOI buttons
  - Rendered в panel выше Relations section, между metadata и FK list
  - aria-pressed когда active (time-travel или AOI с filter)

src/components/editor/EditorTabBar (inline in route)
  - counts prop: Partial<Record<EditorTab, number>>
  - actions prop: ReactNode для right-aligned cluster
  - inline count badge — text-mono text-mute next to label

Tests: 116 pass. TS strict clean.
This commit is contained in:
Zimin A.N.
2026-05-11 17:45:06 +03:00
parent ac80c3f4a1
commit ec99010697
4 changed files with 152 additions and 87 deletions
+2 -1
View File
@@ -502,7 +502,8 @@ export const useLiveRecord = (dict: string | undefined, businessKey: string | un
})
export const useDictionaries = () => useQuery(dictionariesQuery)
export const useDictionaryDetail = (name: string) => useQuery(dictionaryDetailQuery(name))
export const useDictionaryDetail = (name: string | undefined) =>
useQuery({ ...dictionaryDetailQuery(name ?? ''), enabled: Boolean(name) })
export const useRecords = (
dictionaryName: string,
scopeCsv: string,