feat(editor): left info panel + 3-col layout per handoff prototype
Handoff prototype design/compact.html показывает editor как 3-col layout:
[Sidebar 220 (global nav)] [InfoPanel 220 (dict metadata)] [main content].
Текущая implementation была 2-col (nav + everything).
NEW src/components/editor/EditorInfoPanel.tsx:
- Scope badge (PUBLIC/INTERNAL/RESTRICTED) — top
- ОПИСАНИЕ section — caption + dict.description body
- Metadata 2x2 grid: Bundle / Версия / Записей / Локали
(mono text-ink-2, cap labels)
- СВЯЗИ section с двумя группами:
→ ссылается outgoing FK list (name links к /dictionaries/$name)
← используют incoming refBy list (deduplicated sourceDict names)
- sticky lg:top-2 self-start чтобы panel оставалась видна при scroll
- lg:max-h-[calc(100vh-7rem)] overflow-y-auto если metadata высокий
src/routes/dictionaries.$name.tsx:
- Обернул содержимое (banner + tabs + tab content) в:
<div className='grid grid-cols-1 lg:grid-cols-[220px_1fr] gap-4 lg:gap-6'>
<EditorInfoPanel ... />
<div>{banner + tabs + content}</div>
</div>
- На <lg переключается в stacked (info above content) — single column
благодаря grid-cols-1.
PageHeader остаётся выше grid (full-width breadcrumb + title + actions).
Action buttons (AOI / Time-travel / Schema edit / Создать) живут в
PageHeader.actions slot — handoff prototype показывает их в info panel,
но рядом с title лучше discoverable. Может уйти в info panel в future
refactor.
Tests: 116 pass. TS strict clean.
This commit is contained in:
@@ -42,6 +42,7 @@ import { CascadeConfirmDialog } from '@/components/lineage/CascadeConfirmDialog'
|
||||
import { RecordHistoryDrawer } from '@/components/record/RecordHistoryDrawer'
|
||||
import { RecordDrawer } from '@/components/record/RecordDrawer'
|
||||
import { WorkflowBanner } from '@/components/editor/WorkflowBanner'
|
||||
import { EditorInfoPanel } from '@/components/editor/EditorInfoPanel'
|
||||
import { AoiPickerDialog, type AoiResult } from '@/components/geo/AoiPickerDialog'
|
||||
import { TimeTravelPicker } from '@/components/timetravel/TimeTravelPicker'
|
||||
import { nowIsoLocal } from '@/lib/dates'
|
||||
@@ -621,15 +622,23 @@ function DictionaryDetail() {
|
||||
}
|
||||
/>
|
||||
|
||||
{/* WorkflowBanner — status (live/review/draft) above tab bar per handoff Screen 2. */}
|
||||
{detailQuery.data && (
|
||||
<WorkflowBanner detail={detailQuery.data} pendingCount={pendingByKey.size} />
|
||||
)}
|
||||
{/* 3-col layout per handoff prototype design/compact.html:
|
||||
[Sidebar (220px global)] [InfoPanel 220px] [main content]
|
||||
Info panel — sticky слева с dict metadata + relations.
|
||||
На <lg viewport schedule переключается в stacked (info above content). */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-[220px_1fr] gap-4 lg:gap-6 mt-2">
|
||||
{detailQuery.data && (
|
||||
<EditorInfoPanel detail={detailQuery.data} recordCount={totalRecords} />
|
||||
)}
|
||||
|
||||
{/* Editor tabs per handoff Stage 3.4:
|
||||
Records | Relations | Fields | JSON | Events | History.
|
||||
URL-synced через ?tab=. Default — records (records table + filters). */}
|
||||
{detailQuery.data && (
|
||||
<div className="min-w-0 space-y-4">
|
||||
{/* WorkflowBanner — status (live/review/draft) above tab bar per handoff Screen 2. */}
|
||||
{detailQuery.data && (
|
||||
<WorkflowBanner detail={detailQuery.data} pendingCount={pendingByKey.size} />
|
||||
)}
|
||||
|
||||
{/* Editor tabs per handoff Stage 3.4 */}
|
||||
{detailQuery.data && (
|
||||
<EditorTabBar
|
||||
active={urlSearch.tab ?? 'records'}
|
||||
onChange={(tab) =>
|
||||
@@ -945,6 +954,9 @@ function DictionaryDetail() {
|
||||
</>
|
||||
)}
|
||||
{/* === end records tab content === */}
|
||||
</div>
|
||||
</div>
|
||||
{/* === end 3-col grid === */}
|
||||
|
||||
<RecordDrawer
|
||||
open={edit.kind === 'create' || edit.kind === 'edit'}
|
||||
|
||||
Reference in New Issue
Block a user