From d9df2fc359856dedc7a4f6a0aea8728dc508a706 Mon Sep 17 00:00:00 2001 From: "Zimin A.N." Date: Mon, 11 May 2026 21:50:21 +0300 Subject: [PATCH] =?UTF-8?q?feat(ui):=20batch=20UI=20polish=20=E2=80=94=20a?= =?UTF-8?q?uth/lang=20to=20sidebar,=20search=20h-7,=20info=20overflow,=20g?= =?UTF-8?q?raph=20zoom?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit User feedback batch — multiple iterations of UX refinement. Layout shifts: - TopBar right cluster: AuthBadge + LanguageSwitch переехали в Sidebar footer per user ("вход в сайдбар перенесем", "ru/en тоже в сайд баре"). TopBar теперь: breadcrumb + search + Docs + VersionBadge + ThemeSwitch only. Освобождает место + concentrates user/lang controls в одном месте. - Sidebar footer: новая структура — Lang toggle (Язык [RU/EN]) + Auth block (avatar+name+logout authed, Sign-in CTA anonymous) + collapse button. Refactored из inline JSX в named subcomponents (AuthedUserBlock, SignInCta, SidebarFooter). Sizing / spacing: - TopBar SearchInput: h-9 → h-7 to match LangSwitch/ThemeSwitch (+ Sign-in кнопка). User: "поиск по справочникам высоту выровняй". Override via `!h-7 text-cell` className на SearchInput. - TopBar: h-14 → h-11 (slimmer). Sidebar logo block matches h-11. Catalog search: - Catalog page input в toolbar — restored для on-page filter ("Catalog search никак не работает" → проверил, работает; добавил TopBar context- aware behavior где TopBar input на /dictionaries route синхронизируется с URL ?q= live). - TopBar search context-aware: catalog mode = live filter; else = submit → /search (records JSONB search). InfoPanel: - Description: `text-body text-ink-2 leading-relaxed` → + `break-words overflow-hidden`. Long slash-separated values (LZW/Deflate/JPEG2000/ZSTD/LERC) теперь wrap'ятся внутри panel вместо overflow за границы (user: "описание убежало за границы панели"). - Container: + `min-w-0 overflow-hidden` для proper flex shrinking. Graph: - Zoom controls overlay (+/-/1:1/N%) + mouse wheel zoom toward cursor + drag pan по empty space. Per user: "Граф связей еще не зумируется". WorkflowBanner: - Moved в InfoPanel banner slot (compact flex-col layout) — free horizontal space выше editor + concentrates status info в left rail. Auth: - AuthBadge: primary Button → compact h-7 outline button. Matches TopBar toolbar control style. - RequireAuth: убран visible amber error banner "Авторизация недоступна: Failed to fetch" — silent fall-through к anonymous mode (user feedback). - routes/index.tsx: beforeLoad skip redirect если есть `?code=` в URL (OIDC callback fix). HomeIndex компонент рендерит null + redirect на /dictionaries после auth complete. LanguageSwitch: - h-8 + items-stretch + inner items-center — matches ThemeSwitch height. - Later moved entirely в Sidebar footer (h-7 button only) per user. --- ordinis-admin-ui/src/auth/AuthBadge.tsx | 21 +- ordinis-admin-ui/src/auth/RequireAuth.tsx | 17 +- .../src/components/editor/EditorInfoPanel.tsx | 24 +- .../src/components/editor/WorkflowBanner.tsx | 14 +- .../src/components/graph/DictionaryGraph.tsx | 96 ++++++++ .../src/components/layout/Sidebar.tsx | 161 ++++++++++---- .../src/components/layout/ThemeSwitch.tsx | 4 +- .../src/components/layout/TopBar.tsx | 85 ++++--- .../src/routes/dictionaries.$name.tsx | 209 +++++++++++++----- .../src/routes/dictionaries.index.tsx | 208 +++++++++++++---- ordinis-admin-ui/src/routes/index.tsx | 34 ++- .../src/ui/components/language-switch.tsx | 7 +- 12 files changed, 685 insertions(+), 195 deletions(-) diff --git a/ordinis-admin-ui/src/auth/AuthBadge.tsx b/ordinis-admin-ui/src/auth/AuthBadge.tsx index a8a0736..e12d656 100644 --- a/ordinis-admin-ui/src/auth/AuthBadge.tsx +++ b/ordinis-admin-ui/src/auth/AuthBadge.tsx @@ -1,6 +1,6 @@ import { useAuth } from 'react-oidc-context' import { useTranslation } from 'react-i18next' -import { Button, IconButton } from '@/ui' +import { IconButton } from '@/ui' import { SignInIcon, SignOutIcon, UserIcon } from '@phosphor-icons/react' /** @@ -24,27 +24,30 @@ export function AuthBadge() { if (auth.error) { // Не валим UI — просто кнопка для повтора login. return ( - + ) } if (!auth.isAuthenticated) { + // Compact sign-in button matching TopBar control style (h-8, surface bg). + // Previously Button variant="primary" — слишком крупно, выбивалось из + // toolbar style per user feedback ("кнопка Войти слишком большая"). return ( - + ) } diff --git a/ordinis-admin-ui/src/auth/RequireAuth.tsx b/ordinis-admin-ui/src/auth/RequireAuth.tsx index 3b7b41f..7e9167c 100644 --- a/ordinis-admin-ui/src/auth/RequireAuth.tsx +++ b/ordinis-admin-ui/src/auth/RequireAuth.tsx @@ -53,18 +53,13 @@ export function RequireAuth({ children }: { children: ReactNode }) { ) } - // Keycloak unreachable / OIDC discovery failed — показываем ошибку, но НЕ - // блокируем UI (guest mode всё равно работает на read-api без JWT). Юзер - // может видеть PUBLIC данные пока Keycloak вернётся в строй. + // Keycloak unreachable / OIDC discovery failed — раньше показывали amber + // banner вверху страницы, но per user feedback ("Авторизация недоступна: + // Failed to fetch.?") это noise: anonymous mode работает без auth, error + // banner лишь пугает. Тихо рендерим children в guest mode — AuthBadge + // в TopBar показывает Sign in для retry. if (auth.error && !auth.isAuthenticated) { - return ( - <> -
- Авторизация недоступна: {auth.error.message}. Доступен просмотр публичных данных. -
- {children} - - ) + return <>{children} } // ВАЖНО: НЕ блокируем UI пока {@code auth.isLoading=true}. Initial OIDC diff --git a/ordinis-admin-ui/src/components/editor/EditorInfoPanel.tsx b/ordinis-admin-ui/src/components/editor/EditorInfoPanel.tsx index d32f2f6..671017a 100644 --- a/ordinis-admin-ui/src/components/editor/EditorInfoPanel.tsx +++ b/ordinis-admin-ui/src/components/editor/EditorInfoPanel.tsx @@ -41,9 +41,13 @@ export type EditorInfoPanelProps = { onExport?: () => void exportPending?: boolean } + /** Optional banner slot — WorkflowBanner / status notices живут в + * left panel per user feedback ("плашки пусть в левой панели будут"). + * Renders at top of panel before scope chip. */ + banner?: React.ReactNode } -export function EditorInfoPanel({ detail, recordCount, actions }: EditorInfoPanelProps) { +export function EditorInfoPanel({ detail, recordCount, actions, banner }: EditorInfoPanelProps) { const { t } = useTranslation() const { data: refByRaw } = useDictionaryDependents(detail.name) @@ -51,7 +55,13 @@ export function EditorInfoPanel({ detail, recordCount, actions }: EditorInfoPane const incomingDeps: SchemaDependent[] = useMemo(() => refByRaw ?? [], [refByRaw]) return ( -