Merge branch 'fix/admin-self-approve-frontend' into 'main'
fix(reviews): admin может self-approve record draft через UI See merge request 2-6/2-6-4/terravault/ordinis!290
This commit is contained in:
@@ -105,12 +105,51 @@ function tokenRoles(accessToken: string | undefined | null): string[] {
|
||||
}
|
||||
|
||||
/**
|
||||
* True если у actor'а есть указанная role или super-role {@link ROLE_ADMIN}.
|
||||
* Admin grants full workflow access — convenience override для small teams.
|
||||
* True если role — admin-эквивалент. Tail-match по `-`/`_`/`:` →
|
||||
* `ADMIN`. Зеркало backend `WorkflowRoles.isAdminRole`.
|
||||
*
|
||||
* <p>Matches: `admin`, `ordinis:admin`, `ordinis-admin`, `org:admin`,
|
||||
* `global:admin`, `app_admin`. Юзер может настроить Keycloak с любой из
|
||||
* этих conventions — UI gate должен работать одинаково.
|
||||
*/
|
||||
function isAdminRoleString(role: string): boolean {
|
||||
if (!role) return false
|
||||
const s = role.trim().toUpperCase()
|
||||
const sep = Math.max(s.lastIndexOf('-'), s.lastIndexOf('_'), s.lastIndexOf(':'))
|
||||
const tail = sep >= 0 ? s.substring(sep + 1) : s
|
||||
return tail === 'ADMIN'
|
||||
}
|
||||
|
||||
/**
|
||||
* True если у актора есть admin-эквивалентная роль (tail-match) в любом
|
||||
* claim path. Используется для UI gate'ов где нужно знать "может ли actor
|
||||
* обойти workflow restriction" — e.g. self-approve override.
|
||||
*/
|
||||
function hasAdminRole(accessToken: string | undefined | null): boolean {
|
||||
return tokenRoles(accessToken).some(isAdminRoleString)
|
||||
}
|
||||
|
||||
/**
|
||||
* True если у actor'а есть указанная role или **любая** admin-эквивалентная
|
||||
* super-role (tail-match → ADMIN). Раньше matched только exact
|
||||
* `'ordinis:admin'` — юзер с plain ролью `admin` не считался админом и UI
|
||||
* блокировал Approve button. Теперь mirror'ит backend tail-match.
|
||||
*/
|
||||
function hasRole(accessToken: string | undefined | null, role: string): boolean {
|
||||
const roles = tokenRoles(accessToken)
|
||||
return roles.includes(role) || roles.includes(ROLE_ADMIN)
|
||||
if (roles.includes(role)) return true
|
||||
return roles.some(isAdminRoleString)
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook variant: true если currentSub имеет admin-эквивалентную роль.
|
||||
* Mirror backend `WorkflowRoles.isAdmin()` — используется для self-approve
|
||||
* override и других UI gate'ов где admin = bypass workflow rules.
|
||||
*/
|
||||
export function useIsAdmin(): boolean {
|
||||
const auth = useAuth()
|
||||
if (!auth.isAuthenticated) return false
|
||||
return hasAdminRole(auth.user?.access_token)
|
||||
}
|
||||
|
||||
/** Может ли актор approve / request_changes / reject schema draft. */
|
||||
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
} from '@/ui'
|
||||
import axios from 'axios'
|
||||
import { useAuth } from '@/auth/useAuth'
|
||||
import { useIsAdmin } from '@/auth/usePermissions'
|
||||
import { ArrowCounterClockwiseIcon, CheckCircleIcon, XCircleIcon } from '@phosphor-icons/react'
|
||||
import {
|
||||
useDictionaries,
|
||||
@@ -693,11 +694,16 @@ function ReviewDrawer({ draftId, onClose }: DrawerProps) {
|
||||
// 1. `isPending` = false: terminal draft (APPROVED/REJECTED/WITHDRAWN).
|
||||
// Никаких actions — read-only diff + decision banner.
|
||||
// 2. `isMineAndPending`: maker открыл свой собственный pending draft из
|
||||
// "Мои" таба. Approve self запрещён backend'ом (self_approve_forbidden),
|
||||
// reject своего черновика бессмыслен — показываем Withdraw.
|
||||
// "Мои" таба. Для regular maker'а Approve self запрещён backend'ом
|
||||
// (self_approve_forbidden), показываем Withdraw. Admin override
|
||||
// (MR !280): backend разрешает self-approve если у actor'а admin
|
||||
// роль → frontend тоже снимает блокировку, иначе UI скрывает кнопку
|
||||
// которую backend бы пропустил.
|
||||
// 3. Иначе — reviewer flow: approve/reject как было.
|
||||
const isAdmin = useIsAdmin()
|
||||
const isPending = draft?.status === 'PENDING'
|
||||
const isMineAndPending = isPending && Boolean(currentSub) && draft?.makerId === currentSub
|
||||
const isMineAndPending =
|
||||
isPending && Boolean(currentSub) && draft?.makerId === currentSub && !isAdmin
|
||||
|
||||
const handleApprove = () => {
|
||||
if (!draftId) return
|
||||
|
||||
Reference in New Issue
Block a user