feat(sidebar): role-based visibility для /reviews
Phase 1d enhancement: 'Согласования' пункт сайдбара скрыт если у user'а нет approver/reviewer ролей. Раньше показывался любому authenticated — оператор без reviewer прав видел queue, кликал и получал пустую страницу (или 403 если бы пробовал approve). Visibility rules: - 'Мои черновики' — auth (maker может быть любой) - 'Согласования' — auth + (ordinis:schema:reviewer OR ordinis:record:reviewer OR ordinis:admin super-role) — иначе hidden - 'Outbox' / 'Webhooks' / 'Аудит' — auth (admin-y но backend не gated отдельной ролью) Новый NavItem.reviewerOnly flag — фильтр в visibleSections учитывает оба flag'а (authRequired + reviewerOnly). Применено и к desktop Sidebar и к MobileSidebar (DRY refactor — отдельный task).
This commit is contained in:
@@ -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 (
|
||||
|
||||
Reference in New Issue
Block a user