diff --git a/ordinis-admin-ui/src/components/layout/Sidebar.tsx b/ordinis-admin-ui/src/components/layout/Sidebar.tsx index 0d5f690..5664f80 100644 --- a/ordinis-admin-ui/src/components/layout/Sidebar.tsx +++ b/ordinis-admin-ui/src/components/layout/Sidebar.tsx @@ -14,6 +14,10 @@ import { } from 'lucide-react' import { useAuth } from 'react-oidc-context' import { useCanMutate } from '@/auth/useCanMutate' +import { + useCanReviewRecord, + useCanReviewSchema, +} from '@/auth/usePermissions' import { useDictionaries, useMyDrafts, @@ -52,12 +56,24 @@ type NavItem = { badge?: React.ReactNode /** Только аутентифицированным пользователям. */ authRequired?: boolean + /** + * Visible только если user имеет хотя бы одну из workflow ролей + * (или {@code ordinis:admin} super-role). Hide если нет. + */ + reviewerOnly?: boolean } export function Sidebar() { const { t } = useTranslation() const auth = useAuth() const canMutate = useCanMutate() + // Phase 1d role-based sidebar visibility. /reviews пункт показываем + // только тем у кого есть schema:reviewer OR record:reviewer OR + // ordinis:admin super-role. Иначе пункт скрыт — у user'а нет смысла + // его видеть (clicking всё равно даст empty queue / 403). + const canReviewSchema = useCanReviewSchema() + const canReviewRecord = useCanReviewRecord() + const isReviewer = canReviewSchema || canReviewRecord const dictsQuery = useDictionaries() // Loaded just for сидбар counter — light request, кэшируется. const myDrafts = useMyDrafts(0, 1) @@ -105,6 +121,7 @@ export function Sidebar() { icon: ClipboardList, badge: reviewsCount > 0 ? reviewsCount : undefined, authRequired: true, + reviewerOnly: true, }, { to: '/outbox', @@ -129,7 +146,11 @@ export function Sidebar() { const visibleSections = sections .map((s) => ({ ...s, - items: s.items.filter((i) => !i.authRequired || canMutate), + items: s.items.filter((i) => { + if (i.authRequired && !canMutate) return false + if (i.reviewerOnly && !isReviewer) return false + return true + }), })) .filter((s) => s.items.length > 0) @@ -418,6 +439,9 @@ export function MobileSidebar({ open, onClose }: { open: boolean; onClose: () => const { t } = useTranslation() const auth = useAuth() const canMutate = useCanMutate() + const canReviewSchema = useCanReviewSchema() + const canReviewRecord = useCanReviewRecord() + const isReviewer = canReviewSchema || canReviewRecord const dictsQuery = useDictionaries() const myDrafts = useMyDrafts(0, 1) const reviews = useReviewsBadgeCount(auth.isAuthenticated) @@ -455,6 +479,7 @@ export function MobileSidebar({ open, onClose }: { open: boolean; onClose: () => icon: ClipboardList, badge: reviewsCount > 0 ? reviewsCount : undefined, authRequired: true, + reviewerOnly: true, }, { to: '/outbox', @@ -476,7 +501,14 @@ export function MobileSidebar({ open, onClose }: { open: boolean; onClose: () => }, ] const visibleSections = sections - .map((s) => ({ ...s, items: s.items.filter((i) => !i.authRequired || canMutate) })) + .map((s) => ({ + ...s, + items: s.items.filter((i) => { + if (i.authRequired && !canMutate) return false + if (i.reviewerOnly && !isReviewer) return false + return true + }), + })) .filter((s) => s.items.length > 0) return (