Merge branch 'feat/sidebar-role-based-visibility' into 'main'
feat(sidebar): role-based visibility для /reviews See merge request 2-6/2-6-4/terravault/ordinis!166
This commit is contained in:
@@ -14,6 +14,10 @@ import {
|
|||||||
} from 'lucide-react'
|
} from 'lucide-react'
|
||||||
import { useAuth } from 'react-oidc-context'
|
import { useAuth } from 'react-oidc-context'
|
||||||
import { useCanMutate } from '@/auth/useCanMutate'
|
import { useCanMutate } from '@/auth/useCanMutate'
|
||||||
|
import {
|
||||||
|
useCanReviewRecord,
|
||||||
|
useCanReviewSchema,
|
||||||
|
} from '@/auth/usePermissions'
|
||||||
import {
|
import {
|
||||||
useDictionaries,
|
useDictionaries,
|
||||||
useMyDrafts,
|
useMyDrafts,
|
||||||
@@ -52,12 +56,24 @@ type NavItem = {
|
|||||||
badge?: React.ReactNode
|
badge?: React.ReactNode
|
||||||
/** Только аутентифицированным пользователям. */
|
/** Только аутентифицированным пользователям. */
|
||||||
authRequired?: boolean
|
authRequired?: boolean
|
||||||
|
/**
|
||||||
|
* Visible только если user имеет хотя бы одну из workflow ролей
|
||||||
|
* (или {@code ordinis:admin} super-role). Hide если нет.
|
||||||
|
*/
|
||||||
|
reviewerOnly?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Sidebar() {
|
export function Sidebar() {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const auth = useAuth()
|
const auth = useAuth()
|
||||||
const canMutate = useCanMutate()
|
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()
|
const dictsQuery = useDictionaries()
|
||||||
// Loaded just for сидбар counter — light request, кэшируется.
|
// Loaded just for сидбар counter — light request, кэшируется.
|
||||||
const myDrafts = useMyDrafts(0, 1)
|
const myDrafts = useMyDrafts(0, 1)
|
||||||
@@ -105,6 +121,7 @@ export function Sidebar() {
|
|||||||
icon: ClipboardList,
|
icon: ClipboardList,
|
||||||
badge: reviewsCount > 0 ? reviewsCount : undefined,
|
badge: reviewsCount > 0 ? reviewsCount : undefined,
|
||||||
authRequired: true,
|
authRequired: true,
|
||||||
|
reviewerOnly: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
to: '/outbox',
|
to: '/outbox',
|
||||||
@@ -129,7 +146,11 @@ export function Sidebar() {
|
|||||||
const visibleSections = sections
|
const visibleSections = sections
|
||||||
.map((s) => ({
|
.map((s) => ({
|
||||||
...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)
|
.filter((s) => s.items.length > 0)
|
||||||
|
|
||||||
@@ -418,6 +439,9 @@ export function MobileSidebar({ open, onClose }: { open: boolean; onClose: () =>
|
|||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const auth = useAuth()
|
const auth = useAuth()
|
||||||
const canMutate = useCanMutate()
|
const canMutate = useCanMutate()
|
||||||
|
const canReviewSchema = useCanReviewSchema()
|
||||||
|
const canReviewRecord = useCanReviewRecord()
|
||||||
|
const isReviewer = canReviewSchema || canReviewRecord
|
||||||
const dictsQuery = useDictionaries()
|
const dictsQuery = useDictionaries()
|
||||||
const myDrafts = useMyDrafts(0, 1)
|
const myDrafts = useMyDrafts(0, 1)
|
||||||
const reviews = useReviewsBadgeCount(auth.isAuthenticated)
|
const reviews = useReviewsBadgeCount(auth.isAuthenticated)
|
||||||
@@ -455,6 +479,7 @@ export function MobileSidebar({ open, onClose }: { open: boolean; onClose: () =>
|
|||||||
icon: ClipboardList,
|
icon: ClipboardList,
|
||||||
badge: reviewsCount > 0 ? reviewsCount : undefined,
|
badge: reviewsCount > 0 ? reviewsCount : undefined,
|
||||||
authRequired: true,
|
authRequired: true,
|
||||||
|
reviewerOnly: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
to: '/outbox',
|
to: '/outbox',
|
||||||
@@ -476,7 +501,14 @@ export function MobileSidebar({ open, onClose }: { open: boolean; onClose: () =>
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
const visibleSections = sections
|
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)
|
.filter((s) => s.items.length > 0)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
Reference in New Issue
Block a user