diff --git a/ordinis-admin-ui/src/components/layout/Sidebar.tsx b/ordinis-admin-ui/src/components/layout/Sidebar.tsx index 78c3a99..7e6af56 100644 --- a/ordinis-admin-ui/src/components/layout/Sidebar.tsx +++ b/ordinis-admin-ui/src/components/layout/Sidebar.tsx @@ -16,6 +16,7 @@ import { useAuth } from 'react-oidc-context' import { useCanMutate } from '@/auth/useCanMutate' import { useDictionaries, useMyDrafts } from '@/api/queries' import { Sheet, SheetContent } from '@/ui' +import { ThemeSwitch } from '@/components/layout/ThemeSwitch' import { cn } from '@/lib/utils' /** @@ -238,20 +239,22 @@ function SidebarFooter({ return ( <> - {/* Language toggle row (только когда expanded). */} + {/* Language toggle + Theme switch row (только когда expanded). + * ThemeSwitch и LanguageSwitch переехали из TopBar per user + * ("свитч темы можно тоже в профиль перенести"). */} {!collapsed && (
- - {t('sidebar.lang', { defaultValue: 'Язык' })} - +
+ +
)} diff --git a/ordinis-admin-ui/src/components/layout/TopBar.tsx b/ordinis-admin-ui/src/components/layout/TopBar.tsx index f77edcb..370b56e 100644 --- a/ordinis-admin-ui/src/components/layout/TopBar.tsx +++ b/ordinis-admin-ui/src/components/layout/TopBar.tsx @@ -1,10 +1,9 @@ import { useTranslation } from 'react-i18next' import { Link, useLocation, useNavigate } from '@tanstack/react-router' import { useDictionaries, useDictionaryDetail } from '@/api/queries' -import { ThemeSwitch } from '@/components/layout/ThemeSwitch' import { VersionBadge } from '@/components/version/VersionBadge' import { SearchInput } from '@/ui/components/search-input' -import { Menu } from 'lucide-react' +import { Bell, Menu } from 'lucide-react' import { useEffect, useRef, useState } from 'react' /** @@ -21,28 +20,24 @@ export function TopBar({ onMenuClick }: { onMenuClick?: () => void }) { const navigate = useNavigate() const location = useLocation() const isCatalog = location.pathname === '/dictionaries' - // Catalog mode: bind input to URL ?q= (live filter). Else: local state submitting к /search. const catalogQ = isCatalog ? ((location.search as Record)?.q as string | undefined) ?? '' : '' const [searchValue, setSearchValue] = useState(catalogQ) const inputRef = useRef(null) + const breadcrumb = useBreadcrumb() - // Sync local state с URL когда navigate на /dictionaries (catalog). + // Sync local input value c URL `?q=` when navigating between routes. useEffect(() => { setSearchValue(catalogQ) }, [catalogQ]) - const breadcrumb = useBreadcrumb() - - // ⌘K / Ctrl+K shortcut — focus search input from anywhere. Skip когда - // активен другой input/textarea (юзер печатает в record edit form). + // ⌘K / Ctrl+K focus shortcut. useEffect(() => { const handler = (e: KeyboardEvent) => { if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'k') { const target = e.target as HTMLElement if (target && (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA')) { - // Уже в input — пусть default browser shortcut работает. if (target === inputRef.current) return } e.preventDefault() @@ -55,10 +50,8 @@ export function TopBar({ onMenuClick }: { onMenuClick?: () => void }) { }, []) // Context-aware search: - // - На /dictionaries (catalog) каждый keystroke обновляет URL `?q=` чтобы - // filter работал live. Submit (Enter) — no-op (фильтр уже applied). - // - На остальных routes input — local state, submit redirect'ит на /search - // с records search. + // - На /dictionaries — live filter каталога через URL `?q=` + // - На остальных routes — local state, submit redirect'ит на /search const handleSearchInput = (e: React.ChangeEvent) => { const v = e.target.value setSearchValue(v) @@ -78,7 +71,7 @@ export function TopBar({ onMenuClick }: { onMenuClick?: () => void }) { e.preventDefault() const q = searchValue.trim() if (q.length === 0) return - if (isCatalog) return // На catalog уже live filter, submit не нужен. + if (isCatalog) return void navigate({ to: '/search', search: { q } }) } @@ -151,7 +144,9 @@ export function TopBar({ onMenuClick }: { onMenuClick?: () => void }) { })} - {/* Search — center, max-width per handoff */} + {/* Search center — context-aware (catalog mode = live filter, else = + * records search submit). Двойной X fixed в SearchInput component + * (type="text" вместо "search" — убрал native browser X). */}
void }) { /> - {/* Right cluster — Docs + VersionBadge + ThemeSwitch only. - * AuthBadge + LanguageSwitch переехали в Sidebar footer per user - * feedback ("вход в сайдбар перенесем", "ru/en в сайдбаре"). */} + {/* Right cluster — broadcast slot per user: "руководство и версию + * оставить в топбар чтобы подсвечивать новинки. туда же и сообщения". + * Docs + VersionBadge + Notifications. ThemeSwitch / Lang / Auth + * переехали в Sidebar footer. */} ) } +/** + * Placeholder notifications bell — будет показывать N unread сообщений + * (webhook errors, draft reviews, system notices). Click → drawer/popup. + * Сейчас static bell без counter — wire'ится когда backend notifications + * stream появится. + */ +function NotificationsBell() { + const { t } = useTranslation() + return ( + + ) +} + type Crumb = { label: string to?: string diff --git a/ordinis-admin-ui/src/routes/dictionaries.index.tsx b/ordinis-admin-ui/src/routes/dictionaries.index.tsx index ebdf6d5..0ea9637 100644 --- a/ordinis-admin-ui/src/routes/dictionaries.index.tsx +++ b/ordinis-admin-ui/src/routes/dictionaries.index.tsx @@ -231,10 +231,12 @@ function DictionariesPage() { {/* Toolbar — top section of the panel, tinted bg + bottom border. * Узкая: px-3 py-1.5 (~6px vertical) — per user feedback ("бар поужать"). */}
- {/* Catalog search — filter dicts by name/displayName/description. */} + {/* Catalog search — filter dicts by name/displayName/description. + * type="text" (не "search") чтобы избежать native X — мы рендерим + * свой clear button если нужно. */}
setSearch({ q: e.target.value || undefined }) diff --git a/ordinis-admin-ui/src/ui/components/search-input.tsx b/ordinis-admin-ui/src/ui/components/search-input.tsx index f14d3b9..ab3b04b 100644 --- a/ordinis-admin-ui/src/ui/components/search-input.tsx +++ b/ordinis-admin-ui/src/ui/components/search-input.tsx @@ -23,7 +23,10 @@ export const SearchInput = React.forwardRef( />