2026-03-29 12:30:57 +00:00
|
|
|
|
import { FaChevronRight, FaChevronDown } from 'react-icons/fa'
|
2026-02-24 20:44:16 +00:00
|
|
|
|
import Container from '@/components/shared/Container'
|
|
|
|
|
|
import { Button, Checkbox, Dialog, Input, Menu, toast } from '@/components/ui'
|
|
|
|
|
|
import { useConfig } from '@/components/ui/ConfigProvider'
|
|
|
|
|
|
import Notification from '@/components/ui/Notification'
|
|
|
|
|
|
import {
|
|
|
|
|
|
GetPermissionListResultDto,
|
|
|
|
|
|
PermissionGrantInfoDto,
|
|
|
|
|
|
PermissionGroupDto,
|
|
|
|
|
|
PermissionWithGroupName,
|
|
|
|
|
|
PermissionWithStyle,
|
|
|
|
|
|
} from '@/proxy/admin/models'
|
2026-03-30 10:53:50 +00:00
|
|
|
|
import { getPermissions, getRoles, updatePermissions } from '@/services/identity.service'
|
2026-02-24 20:44:16 +00:00
|
|
|
|
import { useStoreActions, useStoreState } from '@/store'
|
|
|
|
|
|
import { useLocalization } from '@/utils/hooks/useLocalization'
|
|
|
|
|
|
import { ChangeEvent, useEffect, useMemo, useState } from 'react'
|
|
|
|
|
|
|
2026-03-29 12:30:57 +00:00
|
|
|
|
type OpenState = Record<string, boolean>
|
|
|
|
|
|
|
2026-02-24 20:44:16 +00:00
|
|
|
|
function RolesPermission({
|
|
|
|
|
|
open,
|
|
|
|
|
|
onDialogClose,
|
|
|
|
|
|
name,
|
|
|
|
|
|
}: {
|
|
|
|
|
|
open: boolean
|
|
|
|
|
|
onDialogClose: () => void
|
|
|
|
|
|
name: string
|
|
|
|
|
|
}) {
|
2026-03-29 12:30:57 +00:00
|
|
|
|
// Collapse/expand state
|
|
|
|
|
|
const [openPermissions, setOpenPermissions] = useState<OpenState>({})
|
|
|
|
|
|
|
|
|
|
|
|
// Parent izinleri aç/kapat
|
|
|
|
|
|
const togglePermission = (permName: string) => {
|
|
|
|
|
|
setOpenPermissions((prev) => ({ ...prev, [permName]: !prev[permName] }))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Parent izin mi?
|
|
|
|
|
|
function isParent(permission: PermissionWithStyle) {
|
|
|
|
|
|
return selectedGroupPermissions.some((p) => p.parentName === permission.name)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Belirli parent'ın alt izinleri
|
|
|
|
|
|
function getChildren(parentName: string) {
|
|
|
|
|
|
if (!parentName) return []
|
|
|
|
|
|
return selectedGroupPermissions.filter((p) => p.parentName === parentName)
|
|
|
|
|
|
}
|
2026-02-24 20:44:16 +00:00
|
|
|
|
const providerName = 'R'
|
|
|
|
|
|
const { translate } = useLocalization()
|
|
|
|
|
|
const { getConfig } = useStoreActions((a) => a.abpConfig)
|
|
|
|
|
|
|
|
|
|
|
|
const [isLoading, setIsLoading] = useState(false)
|
|
|
|
|
|
const [permissionList, setPermissionList] = useState<GetPermissionListResultDto>()
|
|
|
|
|
|
const [selectedGroup, setSelectedGroup] = useState<PermissionGroupDto | undefined>()
|
|
|
|
|
|
const [selectedGroupPermissions, setSelectedGroupPermissions] = useState<PermissionWithStyle[]>(
|
|
|
|
|
|
[],
|
|
|
|
|
|
)
|
|
|
|
|
|
const [searchTerm, setSearchTerm] = useState('')
|
|
|
|
|
|
const mode = useStoreState((state) => state.theme.mode)
|
|
|
|
|
|
const tenant = useStoreState((state) => state.auth.tenant)
|
|
|
|
|
|
|
|
|
|
|
|
const { direction } = useConfig()
|
|
|
|
|
|
const className = `m${direction[0]}-`
|
|
|
|
|
|
|
|
|
|
|
|
const fetchDataPermissions = async () => {
|
|
|
|
|
|
if (!name) return
|
|
|
|
|
|
|
|
|
|
|
|
const response = await getPermissions(providerName, name)
|
|
|
|
|
|
const data = response.data
|
|
|
|
|
|
|
|
|
|
|
|
const tenantGroup = tenant?.menuGroup?.trim()
|
|
|
|
|
|
if (!tenantGroup) {
|
|
|
|
|
|
console.warn('Tenant menuGroup boş, tüm izinler gösterilecek')
|
|
|
|
|
|
setPermissionList(data)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const filteredGroups = data.groups
|
|
|
|
|
|
.map((group: any) => {
|
2026-03-11 12:20:40 +00:00
|
|
|
|
const filteredPermissions = group.permissions.filter((perm: any) => {
|
|
|
|
|
|
if (!perm.menuGroup) return false
|
2026-03-18 21:38:06 +00:00
|
|
|
|
const groups =
|
|
|
|
|
|
typeof perm.menuGroup === 'string'
|
|
|
|
|
|
? perm.menuGroup.split('|').map((g: string) => g.trim())
|
|
|
|
|
|
: perm.menuGroup
|
2026-03-11 12:20:40 +00:00
|
|
|
|
return groups.includes(tenantGroup)
|
|
|
|
|
|
})
|
2026-02-24 20:44:16 +00:00
|
|
|
|
|
|
|
|
|
|
return { ...group, permissions: filteredPermissions }
|
|
|
|
|
|
})
|
|
|
|
|
|
.filter((group: any) => group.permissions.length > 0)
|
|
|
|
|
|
|
|
|
|
|
|
const filteredData = { ...data, groups: filteredGroups }
|
|
|
|
|
|
|
|
|
|
|
|
setPermissionList(filteredData)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const changeGroup = (groupName?: string) => {
|
|
|
|
|
|
const group = permissionList?.groups.find((a: any) => a.name === groupName)
|
|
|
|
|
|
|
|
|
|
|
|
if (!group) {
|
|
|
|
|
|
setSelectedGroup(undefined)
|
|
|
|
|
|
setSelectedGroupPermissions([])
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
setSelectedGroup(group)
|
|
|
|
|
|
|
|
|
|
|
|
const selectedGroupPerm = group.permissions.map(
|
|
|
|
|
|
(permission: any) =>
|
|
|
|
|
|
({
|
|
|
|
|
|
...permission,
|
|
|
|
|
|
class: className + findMargin(group.permissions, permission) * 4,
|
|
|
|
|
|
}) as PermissionWithStyle,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
setSelectedGroupPermissions(selectedGroupPerm)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const onDialogOk = async () => {
|
2026-03-18 21:38:06 +00:00
|
|
|
|
try {
|
|
|
|
|
|
setIsLoading(true)
|
|
|
|
|
|
|
2026-02-24 20:44:16 +00:00
|
|
|
|
if (permissionList?.groups && name) {
|
|
|
|
|
|
const listPermissions = await getPermissions(providerName, name)
|
|
|
|
|
|
const unChangedPermissions = getPermissionsWithGroupName(listPermissions.data?.groups)
|
|
|
|
|
|
const changedPermissions = getPermissionsWithGroupName(permissionList.groups)
|
|
|
|
|
|
|
2026-03-18 21:38:06 +00:00
|
|
|
|
const updatePermList = changedPermissions
|
|
|
|
|
|
.filter(
|
|
|
|
|
|
(per) =>
|
|
|
|
|
|
(unChangedPermissions.find((unChanged) => unChanged.name === per.name) || {})
|
|
|
|
|
|
.isGranted !== per.isGranted,
|
2026-02-24 20:44:16 +00:00
|
|
|
|
)
|
|
|
|
|
|
.map(({ name, isGranted }) => ({ name, isGranted }))
|
|
|
|
|
|
|
2026-03-18 21:38:06 +00:00
|
|
|
|
// 🔴 KRİTİK NOKTA
|
|
|
|
|
|
await updatePermissions(providerName, name, {
|
|
|
|
|
|
permissions: updatePermList,
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ artık backend kesin güncel
|
|
|
|
|
|
await getConfig(false)
|
|
|
|
|
|
|
2026-02-24 20:44:16 +00:00
|
|
|
|
toast.push(<Notification title={'Permission updated'} type="success" />, {
|
|
|
|
|
|
placement: 'top-end',
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
onDialogClose()
|
2026-03-18 21:38:06 +00:00
|
|
|
|
} catch (err) {
|
|
|
|
|
|
console.error(err)
|
|
|
|
|
|
} finally {
|
2026-02-24 20:44:16 +00:00
|
|
|
|
setIsLoading(false)
|
2026-03-18 21:38:06 +00:00
|
|
|
|
}
|
2026-02-24 20:44:16 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-29 12:30:57 +00:00
|
|
|
|
// --- Expand/Collapse All fonksiyonları (tüm state ve yardımcı fonksiyonlardan sonra) ---
|
|
|
|
|
|
const handleExpandAll = () => {
|
|
|
|
|
|
const expanded: OpenState = {}
|
|
|
|
|
|
function expandRecursively(perms: PermissionWithStyle[]) {
|
|
|
|
|
|
perms.forEach((perm) => {
|
|
|
|
|
|
if (isParent(perm)) {
|
|
|
|
|
|
expanded[perm.name || ''] = true
|
|
|
|
|
|
// Alt parent'ları da recursive aç
|
|
|
|
|
|
const children = getChildren(perm.name || '')
|
|
|
|
|
|
expandRecursively(children)
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
// Tüm parent ve alt parent'ları recursive olarak aç
|
|
|
|
|
|
expandRecursively(selectedGroupPermissions)
|
|
|
|
|
|
setOpenPermissions(expanded)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const handleCollapseAll = () => {
|
|
|
|
|
|
setOpenPermissions({})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-24 20:44:16 +00:00
|
|
|
|
function getPermissionsWithGroupName(groups: PermissionGroupDto[]): PermissionWithGroupName[] {
|
|
|
|
|
|
return groups.reduce(
|
|
|
|
|
|
(acc, val) => [
|
|
|
|
|
|
...acc,
|
|
|
|
|
|
...val.permissions.map<PermissionWithGroupName>((p: any) => ({
|
|
|
|
|
|
...p,
|
|
|
|
|
|
groupName: val.name || '',
|
|
|
|
|
|
})),
|
|
|
|
|
|
],
|
|
|
|
|
|
[] as PermissionWithGroupName[],
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function findMargin(
|
|
|
|
|
|
permissions: PermissionGrantInfoDto[],
|
|
|
|
|
|
permission: PermissionGrantInfoDto,
|
|
|
|
|
|
level = 0,
|
|
|
|
|
|
): number {
|
|
|
|
|
|
const parentPermission = permissions.find((per) => per.name === permission.parentName)
|
|
|
|
|
|
if (parentPermission) {
|
|
|
|
|
|
return findMargin(permissions, parentPermission, level + 1)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
return level
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const isAllSelected = useMemo(
|
|
|
|
|
|
() =>
|
|
|
|
|
|
permissionList?.groups.every((group: any) =>
|
|
|
|
|
|
group.permissions.every((permission: any) => permission.isGranted),
|
|
|
|
|
|
),
|
|
|
|
|
|
[permissionList],
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
const isAllSelectedForGroup = useMemo(
|
|
|
|
|
|
() => selectedGroupPermissions.every((permission) => permission.isGranted),
|
|
|
|
|
|
[selectedGroupPermissions],
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2026-03-29 12:30:57 +00:00
|
|
|
|
// Collapse/expand için recursive düzende izinleri döndür
|
2026-02-24 20:44:16 +00:00
|
|
|
|
const filteredPermissions = useMemo(() => {
|
|
|
|
|
|
const lowerTerm = searchTerm.toLowerCase()
|
|
|
|
|
|
|
2026-03-29 12:30:57 +00:00
|
|
|
|
// Filtreli veya tüm parent izinler
|
|
|
|
|
|
const parents = selectedGroupPermissions.filter(
|
2026-02-24 20:44:16 +00:00
|
|
|
|
(p) =>
|
2026-03-29 12:30:57 +00:00
|
|
|
|
!p.parentName &&
|
|
|
|
|
|
(searchTerm === '' ||
|
|
|
|
|
|
translate('::' + p.displayName)
|
|
|
|
|
|
.toLowerCase()
|
|
|
|
|
|
.includes(lowerTerm)),
|
2026-02-24 20:44:16 +00:00
|
|
|
|
)
|
|
|
|
|
|
|
2026-03-29 12:30:57 +00:00
|
|
|
|
// Recursive children builder (her seviye için doğru level)
|
|
|
|
|
|
function buildTree(
|
|
|
|
|
|
parent: PermissionWithStyle,
|
|
|
|
|
|
level: number,
|
|
|
|
|
|
): (PermissionWithStyle & { level: number })[] {
|
|
|
|
|
|
const arr = [{ ...parent, level }]
|
|
|
|
|
|
const parentKey = parent.name || ''
|
|
|
|
|
|
if (openPermissions[parentKey] || searchTerm) {
|
|
|
|
|
|
const children = getChildren(parentKey)
|
|
|
|
|
|
children.forEach((child) => {
|
|
|
|
|
|
arr.push(...buildTree(child, level + 1))
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
return arr
|
|
|
|
|
|
}
|
2026-02-24 20:44:16 +00:00
|
|
|
|
|
2026-03-29 12:30:57 +00:00
|
|
|
|
// Tüm parentlar ve altlarını sırayla ekle
|
|
|
|
|
|
let result: (PermissionWithStyle & { level: number })[] = []
|
|
|
|
|
|
parents.forEach((parent) => {
|
|
|
|
|
|
result = result.concat(buildTree(parent, 0))
|
2026-02-24 20:44:16 +00:00
|
|
|
|
})
|
|
|
|
|
|
return result
|
2026-03-29 12:30:57 +00:00
|
|
|
|
}, [selectedGroupPermissions, searchTerm, translate, openPermissions])
|
2026-02-24 20:44:16 +00:00
|
|
|
|
|
|
|
|
|
|
const onSelectAll = (value: boolean, e: ChangeEvent<HTMLInputElement>) => {
|
|
|
|
|
|
if (!permissionList) {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const currentGroupName = selectedGroup?.name
|
|
|
|
|
|
setSelectedGroup(undefined)
|
|
|
|
|
|
setSelectedGroupPermissions([])
|
|
|
|
|
|
|
|
|
|
|
|
if (e.target.name === 'group') {
|
|
|
|
|
|
permissionList?.groups
|
|
|
|
|
|
.find((group: any) => group.name === selectedGroup?.name)
|
|
|
|
|
|
?.permissions.forEach((permission: any) => {
|
|
|
|
|
|
permission.isGranted = value
|
|
|
|
|
|
})
|
|
|
|
|
|
} else {
|
|
|
|
|
|
permissionList?.groups.forEach((group: any) => {
|
|
|
|
|
|
group.permissions.forEach((permission: any) => {
|
|
|
|
|
|
permission.isGranted = value
|
|
|
|
|
|
})
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
setPermissionList({ ...permissionList })
|
|
|
|
|
|
|
|
|
|
|
|
changeGroup(currentGroupName)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function onClickCheckbox(clickedPermission: PermissionGrantInfoDto) {
|
|
|
|
|
|
if (!permissionList) {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const groupPerm = selectedGroupPermissions.map((per) => {
|
|
|
|
|
|
if (clickedPermission.name === per.name) {
|
|
|
|
|
|
return { ...per, isGranted: !per.isGranted }
|
|
|
|
|
|
} else if (clickedPermission.name === per.parentName && clickedPermission.isGranted) {
|
|
|
|
|
|
return { ...per, isGranted: false }
|
|
|
|
|
|
} else if (clickedPermission.parentName === per.name && !clickedPermission.isGranted) {
|
|
|
|
|
|
return { ...per, isGranted: true }
|
|
|
|
|
|
}
|
|
|
|
|
|
return per
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
const permGroup = permissionList?.groups.find((a: any) => a.name === selectedGroup?.name)
|
|
|
|
|
|
if (permGroup) {
|
|
|
|
|
|
permGroup.permissions = groupPerm
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
setPermissionList({ ...permissionList })
|
|
|
|
|
|
setSelectedGroupPermissions(groupPerm)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
fetchDataPermissions()
|
|
|
|
|
|
}, [name])
|
|
|
|
|
|
|
2026-03-30 10:53:50 +00:00
|
|
|
|
// --- Copy Permissions State and Logic ---
|
|
|
|
|
|
const [roleList, setRoleList] = useState<string[]>([])
|
|
|
|
|
|
|
|
|
|
|
|
// --- Copy Permissions Dialog State ---
|
|
|
|
|
|
const [copyDialogOpen, setCopyDialogOpen] = useState(false)
|
|
|
|
|
|
const [copyDialogRole, setCopyDialogRole] = useState('')
|
|
|
|
|
|
|
|
|
|
|
|
// Fetch all roles for select (except current)
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
async function fetchRoles() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const res = await getRoles()
|
|
|
|
|
|
setRoleList(res.data.items?.map((r: any) => r.name) || [])
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
setRoleList([])
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
fetchRoles()
|
|
|
|
|
|
}, [copyDialogOpen])
|
|
|
|
|
|
|
|
|
|
|
|
// Dialog üzerinden kopyalama işlemi
|
|
|
|
|
|
const handleCopyDialogConfirm = async () => {
|
|
|
|
|
|
if (!copyDialogRole || !permissionList) return
|
|
|
|
|
|
setIsLoading(true)
|
|
|
|
|
|
try {
|
|
|
|
|
|
const res = await getPermissions(providerName, copyDialogRole)
|
|
|
|
|
|
const sourcePerms = getPermissionsWithGroupName(res.data?.groups)
|
|
|
|
|
|
const grantedNames = new Set(sourcePerms.filter((p) => p.isGranted).map((p) => p.name))
|
|
|
|
|
|
permissionList.groups.forEach((group) => {
|
|
|
|
|
|
group.permissions.forEach((perm) => {
|
|
|
|
|
|
perm.isGranted = grantedNames.has(perm.name)
|
|
|
|
|
|
})
|
|
|
|
|
|
})
|
|
|
|
|
|
setPermissionList({ ...permissionList })
|
|
|
|
|
|
changeGroup(selectedGroup?.name)
|
|
|
|
|
|
toast.push(<Notification title={translate('::PermissionsCopied')} type="success" />, {
|
|
|
|
|
|
placement: 'top-end',
|
|
|
|
|
|
})
|
|
|
|
|
|
setCopyDialogOpen(false)
|
|
|
|
|
|
setCopyDialogRole('')
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
toast.push(<Notification title={translate('::CopyFailed')} type="danger" />, {
|
|
|
|
|
|
placement: 'top-end',
|
|
|
|
|
|
})
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
setIsLoading(false)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-24 20:44:16 +00:00
|
|
|
|
return permissionList ? (
|
|
|
|
|
|
<Container>
|
|
|
|
|
|
<Dialog
|
2026-03-23 07:25:00 +00:00
|
|
|
|
width="min(900px, 95vw)"
|
2026-02-24 20:44:16 +00:00
|
|
|
|
isOpen={open}
|
|
|
|
|
|
onAfterOpen={() => changeGroup(permissionList?.groups[0].name)}
|
|
|
|
|
|
onClose={onDialogClose}
|
|
|
|
|
|
onRequestClose={onDialogClose}
|
|
|
|
|
|
>
|
|
|
|
|
|
<h5 className="mb-1">
|
|
|
|
|
|
{translate('::Permission')} - {name}
|
|
|
|
|
|
</h5>
|
|
|
|
|
|
<hr className="mt-1 mb-2"></hr>
|
|
|
|
|
|
|
|
|
|
|
|
<div className="flex flex-col md:flex-row gap-4 mb-1">
|
2026-03-23 07:25:00 +00:00
|
|
|
|
<div className="w-full md:w-1/3">
|
2026-02-24 20:44:16 +00:00
|
|
|
|
<Checkbox name="all" checked={isAllSelected} onChange={onSelectAll}>
|
|
|
|
|
|
{translate('AbpPermissionManagement::SelectAllInAllTabs')}
|
|
|
|
|
|
</Checkbox>
|
|
|
|
|
|
</div>
|
2026-03-23 07:25:00 +00:00
|
|
|
|
<div className="w-full md:w-2/3">
|
2026-02-24 20:44:16 +00:00
|
|
|
|
<Checkbox name="group" checked={isAllSelectedForGroup} onChange={onSelectAll}>
|
|
|
|
|
|
{translate('AbpPermissionManagement::SelectAllInThisTab')}
|
|
|
|
|
|
</Checkbox>
|
2026-03-29 12:30:57 +00:00
|
|
|
|
|
|
|
|
|
|
<Button size="sm" variant="plain" onClick={handleExpandAll} icon={<FaChevronRight />}>
|
|
|
|
|
|
{translate('::ListForms.ListFormEdit.ExpandAll')}
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
<Button size="sm" variant="plain" onClick={handleCollapseAll} icon={<FaChevronDown />}>
|
|
|
|
|
|
{translate('::ListForms.ListFormEdit.CollapseAll')}
|
|
|
|
|
|
</Button>
|
2026-02-24 20:44:16 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div className="flex flex-col md:flex-row gap-4">
|
2026-03-23 07:25:00 +00:00
|
|
|
|
<div className="w-full md:w-1/3 max-h-[450px] overflow-y-auto">
|
2026-02-24 20:44:16 +00:00
|
|
|
|
<hr className="mb-2"></hr>
|
|
|
|
|
|
<Menu
|
|
|
|
|
|
className="w-full"
|
|
|
|
|
|
variant={mode}
|
|
|
|
|
|
defaultActiveKeys={[selectedGroup?.displayName ?? '']}
|
|
|
|
|
|
>
|
|
|
|
|
|
{permissionList?.groups.map((group: any) => (
|
|
|
|
|
|
<Menu.MenuItem
|
|
|
|
|
|
key={group.name}
|
|
|
|
|
|
className="break-all whitespace-normal"
|
|
|
|
|
|
eventKey={group.name}
|
|
|
|
|
|
onSelect={changeGroup}
|
|
|
|
|
|
>
|
|
|
|
|
|
{translate('::' + group.displayName)} (
|
|
|
|
|
|
{group.permissions.filter((a: any) => a.isGranted).length})
|
|
|
|
|
|
</Menu.MenuItem>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</Menu>
|
|
|
|
|
|
</div>
|
2026-03-23 07:25:00 +00:00
|
|
|
|
<div className="w-full md:w-2/3 max-h-[450px] overflow-y-auto">
|
2026-02-24 20:44:16 +00:00
|
|
|
|
<hr className="mb-2"></hr>
|
2026-03-29 12:30:57 +00:00
|
|
|
|
<div className="flex items-center gap-2 mb-2">
|
|
|
|
|
|
<Input
|
|
|
|
|
|
size="sm"
|
|
|
|
|
|
className=""
|
|
|
|
|
|
placeholder={translate('::Search')}
|
|
|
|
|
|
value={searchTerm}
|
|
|
|
|
|
onChange={(e) => setSearchTerm(e.target.value)}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
2026-02-24 20:44:16 +00:00
|
|
|
|
<div className="my-1">
|
2026-03-29 12:30:57 +00:00
|
|
|
|
{filteredPermissions.map((permission) => {
|
|
|
|
|
|
const isParentPerm = isParent(permission)
|
|
|
|
|
|
const permKey = permission.name || ''
|
|
|
|
|
|
const children = getChildren(permKey)
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div key={permission.name} className={`ml-${permission.level * 4} group`}>
|
|
|
|
|
|
<div className="flex items-center gap-2 px-2 py-0.5 rounded-md hover:bg-gray-50 transition-all">
|
|
|
|
|
|
{/* Expand Icon */}
|
|
|
|
|
|
{isParentPerm ? (
|
|
|
|
|
|
<button
|
|
|
|
|
|
onClick={(e) => {
|
|
|
|
|
|
e.stopPropagation()
|
|
|
|
|
|
togglePermission(permKey)
|
|
|
|
|
|
}}
|
|
|
|
|
|
className="w-5 h-5 flex items-center justify-center rounded hover:bg-gray-200 transition"
|
|
|
|
|
|
>
|
|
|
|
|
|
{openPermissions[permKey] || searchTerm ? (
|
|
|
|
|
|
<FaChevronDown className="text-gray-500 text-xs" />
|
|
|
|
|
|
) : (
|
|
|
|
|
|
<FaChevronRight className="text-gray-500 text-xs" />
|
|
|
|
|
|
)}
|
|
|
|
|
|
</button>
|
|
|
|
|
|
) : (
|
|
|
|
|
|
<div className="w-5" />
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
{/* Checkbox */}
|
|
|
|
|
|
<Checkbox
|
|
|
|
|
|
name={permission.name}
|
|
|
|
|
|
checked={permission.isGranted}
|
|
|
|
|
|
onChange={() => onClickCheckbox(permission)}
|
|
|
|
|
|
>
|
|
|
|
|
|
<span className="text-sm text-gray-700 group-hover:text-gray-900 transition">
|
|
|
|
|
|
{translate('::' + permission.displayName)}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</Checkbox>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)
|
|
|
|
|
|
})}
|
2026-02-24 20:44:16 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2026-03-30 10:53:50 +00:00
|
|
|
|
<div className="flex justify-between items-center mt-6">
|
|
|
|
|
|
<div className="flex items-center">
|
|
|
|
|
|
<Button variant="solid" color="sky-500" onClick={() => setCopyDialogOpen(true)}>
|
|
|
|
|
|
{translate('::AbpIdentity.Roles.CopyPermissions')}
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="text-right">
|
|
|
|
|
|
<Button className="ltr:mr-2 rtl:ml-2" variant="plain" onClick={onDialogClose}>
|
|
|
|
|
|
{translate('::Cancel')}
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
<Button variant="solid" loading={isLoading} onClick={onDialogOk}>
|
|
|
|
|
|
{isLoading ? translate('::SavingWithThreeDot') : translate('::Save')}
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</Dialog>
|
|
|
|
|
|
|
|
|
|
|
|
{/* Copy Permissions Dialog */}
|
|
|
|
|
|
<Dialog
|
|
|
|
|
|
isOpen={copyDialogOpen}
|
|
|
|
|
|
width="min(400px, 95vw)"
|
|
|
|
|
|
onClose={() => setCopyDialogOpen(false)}
|
|
|
|
|
|
>
|
|
|
|
|
|
<h5 className="mb-2">{translate('::AbpIdentity.Roles.CopyPermissions')}</h5>
|
|
|
|
|
|
<div className="mb-4">
|
|
|
|
|
|
<select
|
|
|
|
|
|
className="border rounded px-2 py-1 w-full"
|
|
|
|
|
|
value={copyDialogRole}
|
|
|
|
|
|
onChange={(e) => setCopyDialogRole(e.target.value)}
|
|
|
|
|
|
>
|
|
|
|
|
|
<option value="">{translate('::App.Select')}</option>
|
|
|
|
|
|
{roleList
|
|
|
|
|
|
.filter((role) => role !== name)
|
|
|
|
|
|
.map((role) => (
|
|
|
|
|
|
<option key={role} value={role}>
|
|
|
|
|
|
{role}
|
|
|
|
|
|
</option>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</select>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="flex justify-end gap-2">
|
|
|
|
|
|
<Button variant="plain" onClick={() => setCopyDialogOpen(false)}>
|
2026-02-24 20:44:16 +00:00
|
|
|
|
{translate('::Cancel')}
|
|
|
|
|
|
</Button>
|
2026-03-30 10:53:50 +00:00
|
|
|
|
<Button
|
|
|
|
|
|
variant="solid"
|
|
|
|
|
|
onClick={handleCopyDialogConfirm}
|
|
|
|
|
|
disabled={!copyDialogRole || isLoading}
|
|
|
|
|
|
loading={isLoading}
|
|
|
|
|
|
>
|
|
|
|
|
|
{translate('::Copy')}
|
2026-02-24 20:44:16 +00:00
|
|
|
|
</Button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</Dialog>
|
|
|
|
|
|
</Container>
|
|
|
|
|
|
) : (
|
|
|
|
|
|
<></>
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export default RolesPermission
|