sozsoft-platform/ui/src/views/admin/user-management/UsersPermission.tsx
2026-10-21 23:40:03 +03:00

485 lines
16 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import classNames from 'classnames'
import { FaChevronRight, FaChevronDown } from 'react-icons/fa'
import { Badge, Button, Checkbox, Dialog, Input, Menu } from '@/components/ui'
import { useConfig } from '@/components/ui/ConfigProvider'
import { useDialogContext } from '@/components/ui/Dialog/Dialog.context'
import {
GetPermissionListResultDto,
PermissionGrantInfoDto,
PermissionGroupDto,
PermissionWithGroupName,
PermissionWithStyle,
} from '@/proxy/admin/models'
import { getPermissions, updatePermissions } from '@/services/identity.service'
import { useStoreActions, useStoreState } from '@/store'
import { useLocalization } from '@/utils/hooks/useLocalization'
import { ChangeEvent, useCallback, useEffect, useMemo, useState } from 'react'
import { notifySuccess } from '@/utils/notify'
type OpenState = Record<string, boolean>
function UserPermissionDialogContent({
name,
translate,
permissionList,
selectedGroup,
filteredPermissions,
isAllSelected,
isAllSelectedForGroup,
isLoading,
searchTerm,
openPermissions,
mode,
providerName,
onSelectAll,
onDialogClose,
onDialogOk,
handleExpandAll,
handleCollapseAll,
changeGroup,
togglePermission,
isParent,
onClickCheckbox,
setSearchTerm,
}: any) {
const { isMaximized } = useDialogContext()
return (
<>
<h5 className="mb-1 shrink-0">
{translate('::Sas.Platform.Permission')} - {name}
</h5>
<hr className="mt-1 mb-2 shrink-0"></hr>
<div className="flex flex-col md:flex-row gap-4 mb-1 shrink-0">
<div className="w-full md:w-1/3">
<Checkbox name="all" checked={isAllSelected} onChange={onSelectAll}>
{translate('AbpPermissionManagement::SelectAllInAllTabs')}
</Checkbox>
</div>
<div className="w-full md:w-2/3">
<Checkbox name="group" checked={isAllSelectedForGroup} onChange={onSelectAll}>
{translate('AbpPermissionManagement::SelectAllInThisTab')}
</Checkbox>
<Button size="sm" variant="plain" onClick={handleExpandAll} icon={<FaChevronRight />}>
{translate('::Sas.Platform.ExpandAll')}
</Button>
<Button size="sm" variant="plain" onClick={handleCollapseAll} icon={<FaChevronDown />}>
{translate('::Sas.Platform.CollapseAll')}
</Button>
</div>
</div>
<div
className={classNames(
'flex flex-col md:flex-row gap-4',
isMaximized ? 'flex-1 min-h-0' : '',
)}
>
<div
className={classNames(
'w-full md:w-1/3 overflow-y-auto',
isMaximized ? 'min-h-0' : 'max-h-[450px]',
)}
>
<hr className="mb-2"></hr>
<Menu 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(group.name)}
>
{translate('::' + group.displayName)} (
{group.permissions.filter((a: any) => a.isGranted).length})
</Menu.MenuItem>
))}
</Menu>
</div>
<div
className={classNames(
'w-full md:w-2/3 overflow-y-auto flex flex-col',
isMaximized ? 'min-h-0' : 'max-h-[450px]',
)}
>
<hr className="mb-2 shrink-0"></hr>
<div className="flex items-center gap-2 mb-2 shrink-0">
<Input
size="sm"
placeholder={translate('::Sas.Platform.Search')}
value={searchTerm}
onChange={(e: any) => setSearchTerm(e.target.value)}
/>
</div>
<div className="my-1">
{filteredPermissions.map((permission: any) => {
const isParentPerm = isParent(permission)
const permKey = permission.name || ''
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 dark:hover:bg-gray-700">
{isParentPerm ? (
<Button
type="button"
onClick={(e) => {
e.stopPropagation()
togglePermission(permKey)
}}
variant="default"
shape="none"
className="h-5! w-5! rounded-sm! border-transparent! bg-transparent! p-0! transition hover:bg-transparent! active:bg-transparent! dark:hover:bg-transparent! dark:active:bg-transparent!"
>
{openPermissions[permKey] || searchTerm ? (
<FaChevronDown className="text-gray-500 text-xs" />
) : (
<FaChevronRight className="text-gray-500 text-xs" />
)}
</Button>
) : (
<div className="w-5" />
)}
<Checkbox
name={permission.name}
checked={permission.isGranted}
onChange={() => onClickCheckbox(permission)}
disabled={permission.grantedProviders.some(
(provider: any) => provider.providerName === 'R',
)}
>
<span className="text-sm text-gray-700 group-hover:text-gray-900 transition dark:text-gray-300 dark:group-hover:text-gray-100">
{translate('::' + permission.displayName)}
{permission.grantedProviders.map((provider: any) => {
const badgeContent =
provider.providerName !== providerName
? `${provider.providerName}: ${provider.providerKey}`
: provider.providerName
return (
<Badge
key={provider.providerKey}
className="m-2"
content={badgeContent}
/>
)
})}
</span>
</Checkbox>
</div>
</div>
)
})}
</div>
</div>
</div>
<Dialog.Footer className="flex justify-end items-center mt-4 pt-3 border-t gap-2">
<Button variant="plain" onClick={onDialogClose}>
{translate('::Sas.Platform.Cancel')}
</Button>
<Button variant="solid" loading={isLoading} onClick={onDialogOk}>
{isLoading ? translate('::Sas.Platform.Saving') : translate('::Sas.Platform.Save')}
</Button>
</Dialog.Footer>
</>
)
}
function UsersPermission({
open,
onDialogClose,
name,
id,
}: {
open: boolean
onDialogClose: () => void
name: string
id: string
}) {
const providerName = 'U'
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[]>(
[],
)
// 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
const getChildren = useCallback(
(parentName: string) =>
parentName ? selectedGroupPermissions.filter((p) => p.parentName === parentName) : [],
[selectedGroupPermissions],
)
// --- Expand/Collapse All fonksiyonları ---
const handleExpandAll = () => {
const expanded: OpenState = {}
function expandRecursively(perms: PermissionWithStyle[]) {
perms.forEach((perm) => {
if (isParent(perm)) {
expanded[perm.name || ''] = true
const children = getChildren(perm.name || '')
expandRecursively(children)
}
})
}
expandRecursively(selectedGroupPermissions)
setOpenPermissions(expanded)
}
const handleCollapseAll = () => {
setOpenPermissions({})
}
const mode = useStoreState((state) => state.theme.mode)
const { direction } = useConfig()
const className = `m${direction[0]}-`
const fetchDataPermissions = useCallback(async () => {
if (!id) {
return
}
const response = await getPermissions(providerName, id)
setPermissionList(response.data)
}, [id])
const changeGroup = (groupName?: string) => {
const group = permissionList?.groups.find((a) => a.name === groupName)
if (!group) {
setSelectedGroup(undefined)
setSelectedGroupPermissions([])
return
}
setSelectedGroup(group)
const selectedGroupPerm = group.permissions.map(
(permission) =>
({
...permission,
class: className + findMargin(group.permissions, permission) * 4,
}) as PermissionWithStyle,
)
setSelectedGroupPermissions(selectedGroupPerm)
}
const onDialogOk = async () => {
try {
setIsLoading(true)
if (permissionList?.groups && id) {
const listPermissions = await getPermissions(providerName, id)
const savedGrants = new Map(
getPermissionsWithGroupName(listPermissions.data?.groups).map((p) => [p.name, p.isGranted]),
)
const updatePermList = getPermissionsWithGroupName(permissionList.groups)
.filter((per) => savedGrants.get(per.name) !== per.isGranted)
.map(({ name, isGranted }) => ({ name, isGranted }))
if (updatePermList.length) {
await updatePermissions(providerName, id, {
permissions: updatePermList,
})
await getConfig(false)
}
notifySuccess('Permission updated')
}
onDialogClose()
} catch (err) {
console.error(err)
} finally {
setIsLoading(false)
}
}
function getPermissionsWithGroupName(groups: PermissionGroupDto[]): PermissionWithGroupName[] {
return groups.flatMap((group) =>
group.permissions.map<PermissionWithGroupName>((p) => ({
...p,
groupName: group.name || '',
})),
)
}
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) =>
group.permissions.every((permission) => permission.isGranted),
),
[permissionList],
)
const isAllSelectedForGroup = useMemo(
() => selectedGroupPermissions.every((permission) => permission.isGranted),
[selectedGroupPermissions],
)
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) => group.name === selectedGroup?.name)
?.permissions.forEach((permission) => {
permission.isGranted = value
})
} else {
permissionList?.groups.forEach((group) => {
group.permissions.forEach((permission) => {
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) => a.name === selectedGroup?.name)
if (permGroup) {
permGroup.permissions = groupPerm
}
setPermissionList({ ...permissionList })
setSelectedGroupPermissions(groupPerm)
}
useEffect(() => {
fetchDataPermissions()
}, [fetchDataPermissions])
// --- Search ve recursive treeview ---
// Collapse/expand için recursive düzende izinleri döndür
const [searchTerm, setSearchTerm] = useState('')
const filteredPermissions = useMemo(() => {
const lowerTerm = searchTerm.toLowerCase()
// Filtreli veya tüm parent izinler
const parents = selectedGroupPermissions.filter(
(p) =>
!p.parentName &&
(searchTerm === '' ||
translate('::' + p.displayName)
.toLowerCase()
.includes(lowerTerm)),
)
// 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
}
let result: (PermissionWithStyle & { level: number })[] = []
parents.forEach((parent) => {
result = result.concat(buildTree(parent, 0))
})
return result
}, [selectedGroupPermissions, searchTerm, translate, openPermissions, getChildren])
return permissionList ? (
<Dialog
width="min(900px, 95vw)"
isOpen={open}
onAfterOpen={() => changeGroup(permissionList?.groups[0].name)}
onClose={onDialogClose}
onRequestClose={onDialogClose}
>
<Dialog.Body className="flex flex-col">
<UserPermissionDialogContent
name={name}
translate={translate}
permissionList={permissionList}
selectedGroup={selectedGroup}
filteredPermissions={filteredPermissions}
isAllSelected={isAllSelected}
isAllSelectedForGroup={isAllSelectedForGroup}
isLoading={isLoading}
searchTerm={searchTerm}
openPermissions={openPermissions}
mode={mode}
providerName={providerName}
onSelectAll={onSelectAll}
onDialogClose={onDialogClose}
onDialogOk={onDialogOk}
handleExpandAll={handleExpandAll}
handleCollapseAll={handleCollapseAll}
changeGroup={changeGroup}
togglePermission={togglePermission}
isParent={isParent}
onClickCheckbox={onClickCheckbox}
setSearchTerm={setSearchTerm}
/>
</Dialog.Body>
</Dialog>
) : (
<></>
)
}
export default UsersPermission