Enhance PermissionsMatrix functionality and UI
- Introduced new utility functions for normalizing and aggregating permission states, improving the management of user permissions. - Added row and column cycling functionality to allow users to easily toggle permission states. - Enhanced the rendering of checkboxes with tooltips for better user feedback on permission states. - Updated the layout of the PermissionsMatrix to improve visual clarity and user interaction.
This commit is contained in:
parent
e54106eb6d
commit
daf2e1aab4
@ -53,6 +53,77 @@ const cyclePermissionState = (current, inherit) => {
|
||||
return true
|
||||
}
|
||||
|
||||
const normalizePermissionState = (state, inherit) => {
|
||||
if (!inherit && state === null) return false
|
||||
return state
|
||||
}
|
||||
|
||||
const getAggregatePermissionState = (states) => {
|
||||
if (states.length === 0) return null
|
||||
const first = states[0]
|
||||
for (let i = 1; i < states.length; i++) {
|
||||
if (states[i] !== first) return 'mixed'
|
||||
}
|
||||
return first
|
||||
}
|
||||
|
||||
const getRowPermissionStates = (permissions, model, actions, inherit) =>
|
||||
actions
|
||||
.filter((action) => modelHasPermissionAction(model, action.name))
|
||||
.map((action) =>
|
||||
normalizePermissionState(
|
||||
getPermissionState(permissions, model.name, action.name),
|
||||
inherit
|
||||
)
|
||||
)
|
||||
|
||||
const getColumnPermissionStates = (permissions, models, action, inherit) =>
|
||||
models
|
||||
.filter((model) => modelHasPermissionAction(model, action.name))
|
||||
.map((model) =>
|
||||
normalizePermissionState(
|
||||
getPermissionState(permissions, model.name, action.name),
|
||||
inherit
|
||||
)
|
||||
)
|
||||
|
||||
const setRowPermissionState = (permissions, model, actions, next) => {
|
||||
let nextPermissions = permissions
|
||||
actions.forEach((action) => {
|
||||
if (modelHasPermissionAction(model, action.name)) {
|
||||
nextPermissions = setPermissionState(
|
||||
nextPermissions,
|
||||
model.name,
|
||||
action.name,
|
||||
next
|
||||
)
|
||||
}
|
||||
})
|
||||
return nextPermissions
|
||||
}
|
||||
|
||||
const setColumnPermissionState = (permissions, models, action, next) => {
|
||||
let nextPermissions = permissions
|
||||
models.forEach((model) => {
|
||||
if (modelHasPermissionAction(model, action.name)) {
|
||||
nextPermissions = setPermissionState(
|
||||
nextPermissions,
|
||||
model.name,
|
||||
action.name,
|
||||
next
|
||||
)
|
||||
}
|
||||
})
|
||||
return nextPermissions
|
||||
}
|
||||
|
||||
const getCheckboxTitle = (state, inherit) => {
|
||||
if (state === 'mixed') return 'Mixed'
|
||||
if (inherit && state === null) return 'Inherit'
|
||||
if (state === true) return 'Allow'
|
||||
return 'Deny'
|
||||
}
|
||||
|
||||
const PermissionsMatrix = ({
|
||||
value,
|
||||
onChange,
|
||||
@ -88,58 +159,140 @@ const PermissionsMatrix = ({
|
||||
[disabled, inherit, onChange, permissions]
|
||||
)
|
||||
|
||||
const handleCycleRow = useCallback(
|
||||
(model) => {
|
||||
if (disabled || typeof onChange !== 'function') return
|
||||
const aggregate = getAggregatePermissionState(
|
||||
getRowPermissionStates(permissions, model, actions, inherit)
|
||||
)
|
||||
const next = cyclePermissionState(
|
||||
aggregate === 'mixed' ? null : aggregate,
|
||||
inherit
|
||||
)
|
||||
onChange(setRowPermissionState(permissions, model, actions, next))
|
||||
},
|
||||
[actions, disabled, inherit, onChange, permissions]
|
||||
)
|
||||
|
||||
const handleCycleColumn = useCallback(
|
||||
(action) => {
|
||||
if (disabled || typeof onChange !== 'function') return
|
||||
const aggregate = getAggregatePermissionState(
|
||||
getColumnPermissionStates(permissions, models, action, inherit)
|
||||
)
|
||||
const next = cyclePermissionState(
|
||||
aggregate === 'mixed' ? null : aggregate,
|
||||
inherit
|
||||
)
|
||||
onChange(setColumnPermissionState(permissions, models, action, next))
|
||||
},
|
||||
[disabled, inherit, models, onChange, permissions]
|
||||
)
|
||||
|
||||
const renderCheckbox = useCallback(
|
||||
(state, onCycle) => {
|
||||
const mixed = state === 'mixed'
|
||||
const checked = state === true
|
||||
const indeterminate = inherit && state === null
|
||||
|
||||
return (
|
||||
<Tooltip title={getCheckboxTitle(state, inherit)} arrow={false}>
|
||||
<span>
|
||||
<PermissionCheckbox
|
||||
checked={checked}
|
||||
indeterminate={indeterminate}
|
||||
mixed={mixed}
|
||||
disabled={disabled}
|
||||
onChange={onCycle}
|
||||
/>
|
||||
</span>
|
||||
</Tooltip>
|
||||
)
|
||||
},
|
||||
[disabled, inherit]
|
||||
)
|
||||
|
||||
const columns = useMemo(() => {
|
||||
const modelColumn = {
|
||||
title: '',
|
||||
key: 'model',
|
||||
dataIndex: 'label',
|
||||
fixed: isMobile ? undefined : 'left',
|
||||
width: 220,
|
||||
render: (label, record) => (
|
||||
<Flex align='center' gap='small'>
|
||||
{record.icon ? createElement(record.icon) : null}
|
||||
{label}
|
||||
</Flex>
|
||||
)
|
||||
}
|
||||
|
||||
const actionColumns = actions.map((action) => ({
|
||||
title: <span className='permissions-matrix-header'>{action.label}</span>,
|
||||
key: action.name,
|
||||
width: 48,
|
||||
align: 'center',
|
||||
render: (_, record) => {
|
||||
if (!modelHasPermissionAction(record.model, action.name)) {
|
||||
return null
|
||||
}
|
||||
const state = getPermissionState(
|
||||
permissions,
|
||||
record.model.name,
|
||||
action.name
|
||||
width: 248,
|
||||
render: (label, record) => {
|
||||
const aggregate = getAggregatePermissionState(
|
||||
getRowPermissionStates(permissions, record.model, actions, inherit)
|
||||
)
|
||||
const checked = state === true
|
||||
const indeterminate = inherit && state === null
|
||||
const title = indeterminate ? 'Inherit' : checked ? 'Allow' : 'Deny'
|
||||
|
||||
return (
|
||||
<Tooltip title={title} arrow={false}>
|
||||
<span>
|
||||
<PermissionCheckbox
|
||||
checked={checked}
|
||||
indeterminate={indeterminate}
|
||||
disabled={disabled}
|
||||
onChange={() => {
|
||||
handleCycle(record.model.name, action.name)
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
</Tooltip>
|
||||
<Flex
|
||||
align='center'
|
||||
gap='small'
|
||||
justify='space-between'
|
||||
style={{ width: '100%', paddingRight: '10px' }}
|
||||
>
|
||||
<Flex align='center' gap='small' style={{ marginLeft: '6px' }}>
|
||||
{record.icon ? createElement(record.icon) : null}
|
||||
{label}
|
||||
</Flex>
|
||||
<div style={{ paddingBottom: '1px' }}>
|
||||
{renderCheckbox(aggregate, () => {
|
||||
handleCycleRow(record.model)
|
||||
})}
|
||||
</div>
|
||||
</Flex>
|
||||
)
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
const actionColumns = actions.map((action) => {
|
||||
const columnAggregate = getAggregatePermissionState(
|
||||
getColumnPermissionStates(permissions, models, action, inherit)
|
||||
)
|
||||
|
||||
return {
|
||||
title: (
|
||||
<Flex vertical align='center' gap={12} justify='flex-end'>
|
||||
<span className='permissions-matrix-header'>{action.label}</span>
|
||||
<div style={{ paddingBottom: '2px' }}>
|
||||
{renderCheckbox(columnAggregate, () => {
|
||||
handleCycleColumn(action)
|
||||
})}
|
||||
</div>
|
||||
</Flex>
|
||||
),
|
||||
key: action.name,
|
||||
width: 48,
|
||||
align: 'center',
|
||||
render: (_, record) => {
|
||||
if (!modelHasPermissionAction(record.model, action.name)) {
|
||||
return null
|
||||
}
|
||||
const state = getPermissionState(
|
||||
permissions,
|
||||
record.model.name,
|
||||
action.name
|
||||
)
|
||||
|
||||
return renderCheckbox(state, () => {
|
||||
handleCycle(record.model.name, action.name)
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return [modelColumn, ...actionColumns]
|
||||
}, [actions, disabled, handleCycle, inherit, isMobile, permissions])
|
||||
}, [
|
||||
actions,
|
||||
handleCycle,
|
||||
handleCycleColumn,
|
||||
handleCycleRow,
|
||||
inherit,
|
||||
isMobile,
|
||||
models,
|
||||
permissions,
|
||||
renderCheckbox
|
||||
])
|
||||
|
||||
const dataSource = useMemo(
|
||||
() =>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user