From a57cf520a428b5d72966818967327fa714f05728 Mon Sep 17 00:00:00 2001 From: "Zimin A.N." Date: Mon, 11 May 2026 16:33:40 +0300 Subject: [PATCH] feat(editor): left info panel + 3-col layout per handoff prototype MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) в:
{banner + tabs + content}
- На Содержимое (top → bottom): + * + * + *

Action buttons (Экспорт / Time-travel) идут в PageHeader actions slot, + * не в info panel — handoff prototype показывает им в info panel но семантика + * редизайна: actions около title для discoverability. + */ +export type EditorInfoPanelProps = { + detail: DictionaryDetail + recordCount: number +} + +export function EditorInfoPanel({ detail, recordCount }: EditorInfoPanelProps) { + const { t } = useTranslation() + const { data: refByRaw } = useDictionaryDependents(detail.name) + + const outgoingNames = useMemo(() => extractOutgoingFk(detail.schemaJson), [detail.schemaJson]) + const incomingNames = useMemo(() => { + const seen = new Set() + for (const dep of refByRaw ?? []) seen.add(dep.sourceDict) + return Array.from(seen).sort() + }, [refByRaw]) + + return ( +

+ ) +} + +function Section({ + label, + children, +}: { + label: React.ReactNode + children: React.ReactNode +}) { + return ( +
+
{label}
+ {children} +
+ ) +} + +function MetaCell({ + label, + children, +}: { + label: React.ReactNode + children: React.ReactNode +}) { + return ( +
+
{label}
+
{children}
+
+ ) +} + +const extractOutgoingFk = ( + schema: import('@/api/client').JsonSchema | undefined, +): string[] => { + if (!schema?.properties) return [] + const targets = new Set() + for (const prop of Object.values(schema.properties)) { + const ref = prop['x-references'] + if (typeof ref === 'string' && ref.length > 0) { + const [target] = ref.split('.') + if (target) targets.add(target) + } + } + return Array.from(targets).sort() +} diff --git a/ordinis-admin-ui/src/routes/dictionaries.$name.tsx b/ordinis-admin-ui/src/routes/dictionaries.$name.tsx index aaa3d7d..7cdfda3 100644 --- a/ordinis-admin-ui/src/routes/dictionaries.$name.tsx +++ b/ordinis-admin-ui/src/routes/dictionaries.$name.tsx @@ -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 && ( - - )} + {/* 3-col layout per handoff prototype design/compact.html: + [Sidebar (220px global)] [InfoPanel 220px] [main content] + Info panel — sticky слева с dict metadata + relations. + На + {detailQuery.data && ( + + )} - {/* Editor tabs per handoff Stage 3.4: - Records | Relations | Fields | JSON | Events | History. - URL-synced через ?tab=. Default — records (records table + filters). */} - {detailQuery.data && ( +
+ {/* WorkflowBanner — status (live/review/draft) above tab bar per handoff Screen 2. */} + {detailQuery.data && ( + + )} + + {/* Editor tabs per handoff Stage 3.4 */} + {detailQuery.data && ( @@ -945,6 +954,9 @@ function DictionaryDetail() { )} {/* === end records tab content === */} +
+ + {/* === end 3-col grid === */}