Add Permission Settings Management Component
All checks were successful
farmcontrol/farmcontrol-ui/pipeline/head This commit looks good
All checks were successful
farmcontrol/farmcontrol-ui/pipeline/head This commit looks good
- Introduced a new PermissionSettings management component, including a list view and modal for creating new permission settings. - Implemented a permissions matrix for managing user permissions with checkboxes for allow, deny, and inherit states. - Added associated styles in App.css for the permissions matrix table and checkbox components. - Created new icons and integrated them into the sidebar and relevant components for better visual representation. - Updated routing and sidebar configuration to include the new Permission Settings section in the management dashboard.
This commit is contained in:
parent
6a4074674d
commit
d928cf45b0
10
assets/icons/permissionsettingsicon.svg
Normal file
10
assets/icons/permissionsettingsicon.svg
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg width="100%" height="100%" viewBox="0 0 64 64" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||||
|
<g transform="matrix(0.841213,0,0,0.841213,4.798602,1)">
|
||||||
|
<path d="M36.588,72.726C34.194,73.377 31.611,73.703 28.844,73.703C13.719,73.703 3.719,63.672 3.719,47.781L3.719,19.625C3.719,15.344 6.688,12.281 10.828,12.281C11.781,12.281 12.703,12.578 13.391,13.078L13.391,11.063C13.391,6.969 16.281,4.109 20.422,4.109C21.328,4.109 22.25,4.375 22.969,4.875C23.938,1.875 26.453,0 29.734,0C33.109,0 35.578,1.828 36.5,4.922C37.328,4.406 38.297,4.109 39.328,4.109C43.781,4.109 46.812,7.062 46.812,11.5L46.812,33.875C46.812,33.892 46.813,33.908 46.815,33.924C44.424,34.416 42.172,35.289 40.125,36.477L40.125,12.188C40.125,11.25 39.453,10.578 38.547,10.578C37.641,10.578 37.016,11.25 37.016,12.188L37.016,34.062C37.016,35.656 35.688,36.922 34.109,36.922C32.5,36.922 31.203,35.656 31.203,34.062L31.203,8.078C31.203,7.141 30.547,6.469 29.656,6.469C28.734,6.469 28.078,7.141 28.078,8.078L28.078,35.047C28.078,36.688 26.766,37.906 25.172,37.906C23.609,37.906 22.266,36.688 22.266,35.047L22.266,12.188C22.266,11.25 21.609,10.578 20.688,10.578C19.781,10.578 19.172,11.25 19.172,12.188L19.172,36.734C19.172,38.359 17.828,39.594 16.234,39.594C14.688,39.594 13.344,38.359 13.344,36.734L13.344,20.438C13.344,19.5 12.688,18.844 11.781,18.844C10.859,18.844 10.234,19.5 10.234,20.438L10.234,47.172C10.234,59.594 17.219,66.797 28.641,66.797C29.698,66.797 30.716,66.745 31.696,66.64C32.963,68.945 34.624,71.005 36.588,72.726ZM47.27,33.835L48.328,31.234C49.422,28.875 51.656,27.609 53.984,27.609C58.031,27.609 60.953,30.641 60.953,34.594C60.953,34.923 60.935,35.258 60.9,35.6C58.562,34.495 56.004,33.783 53.31,33.549C53.272,33.545 53.234,33.542 53.195,33.54C52.586,33.49 51.97,33.464 51.347,33.464C49.955,33.464 48.592,33.592 47.27,33.835Z"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(0.468542,0,0,0.468542,31.985381,32)">
|
||||||
|
<path d="M31.328,68.297L37,68.297C39.25,68.297 40.953,66.922 41.469,64.75L42.953,58.516C43.781,58.234 44.578,57.906 45.281,57.578L50.734,60.922C52.594,62.078 54.828,61.891 56.391,60.328L60.375,56.359C61.938,54.797 62.109,52.469 60.922,50.609L57.609,45.219C57.953,44.5 58.266,43.75 58.5,42.984L64.797,41.484C66.969,40.984 68.328,39.266 68.328,37.016L68.328,31.469C68.328,29.25 66.969,27.531 64.797,27.016L58.578,25.531C58.297,24.688 57.969,23.906 57.656,23.25L61,17.734C62.156,15.797 62,13.641 60.406,12.062L56.344,8.047C54.781,6.641 52.75,6.344 50.891,7.391L45.281,10.859C44.594,10.5 43.812,10.188 42.969,9.891L41.469,3.562C40.938,1.391 39.25,0 37,0L31.328,0C29.078,0 27.406,1.391 26.859,3.578L25.359,9.828C24.531,10.109 23.719,10.438 23,10.812L17.438,7.391C15.562,6.344 13.484,6.547 11.953,8.078L7.922,12.062C6.328,13.641 6.156,15.797 7.344,17.734L10.656,23.25C10.359,23.906 10.031,24.688 9.766,25.531L3.547,27.016C1.359,27.531 0,29.25 0,31.469L0,37.016C0,39.266 1.359,40.984 3.547,41.484L9.828,42.984C10.078,43.75 10.375,44.5 10.703,45.219L7.406,50.609C6.219,52.469 6.406,54.797 7.969,56.359L11.938,60.328C13.484,61.891 15.75,62.078 17.609,60.922L23.031,57.578C23.766,57.906 24.531,58.234 25.359,58.516L26.859,64.75C27.375,66.922 29.078,68.297 31.328,68.297ZM34.172,44.922C28.219,44.922 23.406,40.109 23.406,34.156C23.406,28.203 28.219,23.391 34.172,23.391C40.109,23.391 44.922,28.203 44.922,34.156C44.922,40.109 40.109,44.922 34.172,44.922Z" style="fill-rule:nonzero;"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 3.7 KiB |
@ -1328,3 +1328,89 @@ span.ant-skeleton-input.ant-skeleton-input-sm.text-skeleton {
|
|||||||
background-color: #85858541;
|
background-color: #85858541;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.permissions-matrix-table .ant-table-tbody .ant-table-cell {
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.permissions-matrix-table .ant-table-thead > tr > th {
|
||||||
|
vertical-align: bottom;
|
||||||
|
padding-top: 12px;
|
||||||
|
padding-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.permissions-matrix-table .ant-table-thead .ant-table-column-title {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
justify-content: center;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.permissions-matrix-header {
|
||||||
|
writing-mode: vertical-rl;
|
||||||
|
transform: rotate(180deg);
|
||||||
|
white-space: nowrap;
|
||||||
|
line-height: 1.2;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.permission-checkbox {
|
||||||
|
position: relative;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
cursor: pointer;
|
||||||
|
vertical-align: middle;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.permission-checkbox:disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: 0.45;
|
||||||
|
}
|
||||||
|
|
||||||
|
.permission-checkbox:focus-visible {
|
||||||
|
outline: 2px solid var(--color-primary);
|
||||||
|
outline-offset: 2px;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.permission-checkbox-face {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
transition: opacity 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.permission-checkbox-inherit {
|
||||||
|
border: 1.5px solid;
|
||||||
|
background: transparent;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.permission-checkbox[data-state='inherit'] .permission-checkbox-inherit,
|
||||||
|
.permission-checkbox[data-state='allow'] .permission-checkbox-allow,
|
||||||
|
.permission-checkbox[data-state='deny'] .permission-checkbox-deny {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.permission-checkbox-face .anticon {
|
||||||
|
display: flex;
|
||||||
|
line-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.permission-checkbox-face .anticon svg path {
|
||||||
|
stroke: currentColor;
|
||||||
|
stroke-width: 8px;
|
||||||
|
}
|
||||||
|
|||||||
125
src/components/Dashboard/Management/PermissionSettings.jsx
Normal file
125
src/components/Dashboard/Management/PermissionSettings.jsx
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
import { useState, useRef } from 'react'
|
||||||
|
import { Button, Flex, Space, Modal, Dropdown } from 'antd'
|
||||||
|
import NewPermissionSettings from './PermissionSettings/NewPermissionSettings'
|
||||||
|
import ObjectTable from '../common/ObjectTable'
|
||||||
|
import PlusIcon from '../../Icons/PlusIcon'
|
||||||
|
import ReloadIcon from '../../Icons/ReloadIcon'
|
||||||
|
import useColumnVisibility from '../hooks/useColumnVisibility'
|
||||||
|
import ObjectTableViewButton from '../common/ObjectTableViewButton'
|
||||||
|
import FilterSidebarButton from '../common/FilterSidebarButton'
|
||||||
|
import SortSidebarButton from '../common/SortSidebarButton'
|
||||||
|
import useViewMode from '../hooks/useViewMode'
|
||||||
|
import useFilterSidebarVisibility from '../hooks/useFilterSidebarVisibility'
|
||||||
|
import useSortSidebarVisibility from '../hooks/useSortSidebarVisibility'
|
||||||
|
import ColumnViewButton from '../common/ColumnViewButton'
|
||||||
|
import ExportListButton from '../common/ExportListButton'
|
||||||
|
|
||||||
|
const PermissionSettingsList = () => {
|
||||||
|
const [newPermissionSettingsOpen, setNewPermissionSettingsOpen] =
|
||||||
|
useState(false)
|
||||||
|
const tableRef = useRef()
|
||||||
|
|
||||||
|
const [viewMode, setViewMode] = useViewMode('permissionSettings')
|
||||||
|
|
||||||
|
const [columnVisibility, setColumnVisibility] =
|
||||||
|
useColumnVisibility('permissionSettings')
|
||||||
|
|
||||||
|
const [showFilterSidebar, setShowFilterSidebar] =
|
||||||
|
useFilterSidebarVisibility('PermissionSettings')
|
||||||
|
|
||||||
|
const [showSortSidebar, setShowSortSidebar] =
|
||||||
|
useSortSidebarVisibility('PermissionSettings')
|
||||||
|
|
||||||
|
const actionItems = {
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
label: 'New Permission Settings',
|
||||||
|
key: 'newPermissionSettings',
|
||||||
|
icon: <PlusIcon />
|
||||||
|
},
|
||||||
|
{ type: 'divider' },
|
||||||
|
{
|
||||||
|
label: 'Reload List',
|
||||||
|
key: 'reloadList',
|
||||||
|
icon: <ReloadIcon />
|
||||||
|
}
|
||||||
|
],
|
||||||
|
onClick: ({ key }) => {
|
||||||
|
if (key === 'reloadList') {
|
||||||
|
tableRef.current?.reload()
|
||||||
|
} else if (key === 'newPermissionSettings') {
|
||||||
|
setNewPermissionSettingsOpen(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Flex vertical={'true'} gap='large' className='h-100'>
|
||||||
|
<Flex justify={'space-between'} style={{ minHeight: 0 }}>
|
||||||
|
<Space size='small'>
|
||||||
|
<Dropdown menu={actionItems}>
|
||||||
|
<Button>Actions</Button>
|
||||||
|
</Dropdown>
|
||||||
|
<ColumnViewButton
|
||||||
|
type='permissionSettings'
|
||||||
|
loading={false}
|
||||||
|
visibleState={columnVisibility}
|
||||||
|
updateVisibleState={setColumnVisibility}
|
||||||
|
/>
|
||||||
|
<ExportListButton objectType='permissionSettings' />
|
||||||
|
</Space>
|
||||||
|
<Space>
|
||||||
|
<SortSidebarButton
|
||||||
|
active={showSortSidebar}
|
||||||
|
onClick={() => setShowSortSidebar(!showSortSidebar)}
|
||||||
|
/>
|
||||||
|
<FilterSidebarButton
|
||||||
|
active={showFilterSidebar}
|
||||||
|
onClick={() => setShowFilterSidebar(!showFilterSidebar)}
|
||||||
|
/>
|
||||||
|
<ObjectTableViewButton
|
||||||
|
viewMode={viewMode}
|
||||||
|
setViewMode={setViewMode}
|
||||||
|
showEndingDivider={true}
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
|
</Flex>
|
||||||
|
<ObjectTable
|
||||||
|
ref={tableRef}
|
||||||
|
visibleColumns={columnVisibility}
|
||||||
|
type='permissionSettings'
|
||||||
|
cards={viewMode === 'cards'}
|
||||||
|
showFilterSidebar={showFilterSidebar}
|
||||||
|
showSortSidebar={showSortSidebar}
|
||||||
|
expandHeight={true}
|
||||||
|
saveFilterInSession={true}
|
||||||
|
saveFilterInUrl={true}
|
||||||
|
useFilterInSession={true}
|
||||||
|
useFilterInUrl={true}
|
||||||
|
saveSortInSession={true}
|
||||||
|
saveSortInUrl={true}
|
||||||
|
useSortInSession={true}
|
||||||
|
useSortInUrl={true}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
<Modal
|
||||||
|
open={newPermissionSettingsOpen}
|
||||||
|
onCancel={() => setNewPermissionSettingsOpen(false)}
|
||||||
|
footer={null}
|
||||||
|
destroyOnHidden={true}
|
||||||
|
width={700}
|
||||||
|
>
|
||||||
|
<NewPermissionSettings
|
||||||
|
onOk={() => {
|
||||||
|
setNewPermissionSettingsOpen(false)
|
||||||
|
tableRef.current?.reload()
|
||||||
|
}}
|
||||||
|
reset={!newPermissionSettingsOpen}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PermissionSettingsList
|
||||||
@ -0,0 +1,77 @@
|
|||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import ObjectInfo from '../../common/ObjectInfo'
|
||||||
|
import NewObjectForm from '../../common/NewObjectForm'
|
||||||
|
import WizardView from '../../common/WizardView'
|
||||||
|
|
||||||
|
const NewPermissionSettings = ({ onOk, defaultValues }) => {
|
||||||
|
return (
|
||||||
|
<NewObjectForm
|
||||||
|
type={'permissionSettings'}
|
||||||
|
defaultValues={{ permissions: {}, ...defaultValues }}
|
||||||
|
>
|
||||||
|
{({ handleSubmit, submitLoading, objectData, formValid }) => {
|
||||||
|
const steps = [
|
||||||
|
{
|
||||||
|
title: 'Required',
|
||||||
|
key: 'required',
|
||||||
|
content: (
|
||||||
|
<ObjectInfo
|
||||||
|
type='permissionSettings'
|
||||||
|
column={1}
|
||||||
|
bordered={false}
|
||||||
|
isEditing={true}
|
||||||
|
required={true}
|
||||||
|
objectData={objectData}
|
||||||
|
labelWidth={100}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Summary',
|
||||||
|
key: 'summary',
|
||||||
|
content: (
|
||||||
|
<ObjectInfo
|
||||||
|
type='permissionSettings'
|
||||||
|
column={1}
|
||||||
|
bordered={false}
|
||||||
|
visibleProperties={{
|
||||||
|
_id: false,
|
||||||
|
_reference: false,
|
||||||
|
createdAt: false,
|
||||||
|
updatedAt: false,
|
||||||
|
permissions: false
|
||||||
|
}}
|
||||||
|
objectPropertyProps={{ scrollHeight: '320px' }}
|
||||||
|
labelWidth={130}
|
||||||
|
isEditing={false}
|
||||||
|
objectData={objectData}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
return (
|
||||||
|
<WizardView
|
||||||
|
steps={steps}
|
||||||
|
loading={submitLoading}
|
||||||
|
formValid={formValid}
|
||||||
|
title='New Permission Settings'
|
||||||
|
onSubmit={async () => {
|
||||||
|
const result = await handleSubmit()
|
||||||
|
if (result) {
|
||||||
|
onOk()
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</NewObjectForm>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
NewPermissionSettings.propTypes = {
|
||||||
|
onOk: PropTypes.func.isRequired,
|
||||||
|
reset: PropTypes.bool,
|
||||||
|
defaultValues: PropTypes.object
|
||||||
|
}
|
||||||
|
|
||||||
|
export default NewPermissionSettings
|
||||||
@ -0,0 +1,256 @@
|
|||||||
|
import { useRef, useState } from 'react'
|
||||||
|
import { useLocation } from 'react-router-dom'
|
||||||
|
import { Card, Flex, Space } from 'antd'
|
||||||
|
import { LoadingOutlined } from '@ant-design/icons'
|
||||||
|
import useCollapseState from '../../hooks/useCollapseState'
|
||||||
|
import NotesPanel from '../../common/NotesPanel'
|
||||||
|
import InfoCollapse from '../../common/InfoCollapse'
|
||||||
|
import ObjectInfo from '../../common/ObjectInfo'
|
||||||
|
import ViewButton from '../../common/ViewButton'
|
||||||
|
import InfoCircleIcon from '../../../Icons/InfoCircleIcon.jsx'
|
||||||
|
import NoteIcon from '../../../Icons/NoteIcon.jsx'
|
||||||
|
import AuditLogIcon from '../../../Icons/AuditLogIcon.jsx'
|
||||||
|
import ObjectForm from '../../common/ObjectForm'
|
||||||
|
import EditButtons from '../../common/EditButtons'
|
||||||
|
import ObjectTableNavigationButtons from '../../common/ObjectTableNavigationButtons.jsx'
|
||||||
|
import ActivityIndicator from '../../common/ActivityIndicator.jsx'
|
||||||
|
import ActionHandler from '../../common/ActionHandler.jsx'
|
||||||
|
import ObjectActions from '../../common/ObjectActions.jsx'
|
||||||
|
import ObjectTable from '../../common/ObjectTable.jsx'
|
||||||
|
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
|
||||||
|
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
|
||||||
|
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
|
||||||
|
import {
|
||||||
|
getModelByName,
|
||||||
|
getModelProperty
|
||||||
|
} from '../../../../database/ObjectModels.js'
|
||||||
|
import ScrollBox from '../../common/ScrollBox.jsx'
|
||||||
|
import ObjectProperty from '../../common/ObjectProperty.jsx'
|
||||||
|
import PermissionSettingsIcon from '../../../Icons/PermissionSettingsIcon.jsx'
|
||||||
|
|
||||||
|
const PermissionSettingsInfo = () => {
|
||||||
|
const location = useLocation()
|
||||||
|
const objectFormRef = useRef(null)
|
||||||
|
const actionHandlerRef = useRef(null)
|
||||||
|
const permissionSettingsId = new URLSearchParams(location.search).get(
|
||||||
|
'permissionSettingsId'
|
||||||
|
)
|
||||||
|
const [collapseState, updateCollapseState] = useCollapseState(
|
||||||
|
'PermissionSettingsInfo',
|
||||||
|
{
|
||||||
|
info: true,
|
||||||
|
notes: true,
|
||||||
|
matrix: true,
|
||||||
|
auditLogs: false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
const [objectFormState, setEditFormState] = useState({
|
||||||
|
isEditing: false,
|
||||||
|
editLoading: false,
|
||||||
|
formValid: false,
|
||||||
|
lock: null,
|
||||||
|
loading: false,
|
||||||
|
editDisabled: false,
|
||||||
|
objectData: {}
|
||||||
|
})
|
||||||
|
|
||||||
|
const actions = {
|
||||||
|
edit: () => {
|
||||||
|
objectFormRef?.current?.startEditing?.()
|
||||||
|
return false
|
||||||
|
},
|
||||||
|
cancelEdit: () => {
|
||||||
|
objectFormRef?.current?.cancelEditing?.()
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
finishEdit: () => {
|
||||||
|
objectFormRef?.current?.handleUpdate?.()
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
delete: () => {
|
||||||
|
objectFormRef?.current?.handleDelete?.()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Flex
|
||||||
|
gap='large'
|
||||||
|
vertical='true'
|
||||||
|
style={{ maxHeight: '100%', minHeight: 0 }}
|
||||||
|
>
|
||||||
|
<Flex justify={'space-between'}>
|
||||||
|
<Space size='small'>
|
||||||
|
<Space size='small'>
|
||||||
|
<ObjectActions
|
||||||
|
type='permissionSettings'
|
||||||
|
id={permissionSettingsId}
|
||||||
|
disabled={objectFormState.loading}
|
||||||
|
objectData={objectFormState.objectData}
|
||||||
|
/>
|
||||||
|
<ViewButton
|
||||||
|
disabled={objectFormState.loading}
|
||||||
|
items={[
|
||||||
|
{ key: 'info', label: 'Permission Settings Information' },
|
||||||
|
{ key: 'notes', label: 'Notes' },
|
||||||
|
{ key: 'auditLogs', label: 'Audit Logs' }
|
||||||
|
]}
|
||||||
|
visibleState={collapseState}
|
||||||
|
updateVisibleState={updateCollapseState}
|
||||||
|
/>
|
||||||
|
<UserNotifierToggle
|
||||||
|
type='permissionSettings'
|
||||||
|
objectData={objectFormState.objectData}
|
||||||
|
disabled={objectFormState.loading}
|
||||||
|
/>
|
||||||
|
<DocumentPrintButton
|
||||||
|
type='permissionSettings'
|
||||||
|
objectData={objectFormState.objectData}
|
||||||
|
disabled={objectFormState.loading}
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
|
<ActivityIndicator
|
||||||
|
activities={objectFormState.activities}
|
||||||
|
showStartingDivider={true}
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
|
<Space>
|
||||||
|
<EditButtons
|
||||||
|
isEditing={objectFormState.isEditing}
|
||||||
|
handleUpdate={() => {
|
||||||
|
actionHandlerRef.current.callAction('finishEdit')
|
||||||
|
}}
|
||||||
|
cancelEditing={() => {
|
||||||
|
actionHandlerRef.current.callAction('cancelEdit')
|
||||||
|
}}
|
||||||
|
startEditing={() => {
|
||||||
|
actionHandlerRef.current.callAction('edit')
|
||||||
|
}}
|
||||||
|
editLoading={objectFormState.editLoading}
|
||||||
|
formValid={objectFormState.formValid}
|
||||||
|
disabled={
|
||||||
|
objectFormState.beingEditedByOther ||
|
||||||
|
objectFormState.loading ||
|
||||||
|
objectFormState.editDisabled
|
||||||
|
}
|
||||||
|
loading={objectFormState.editLoading}
|
||||||
|
/>
|
||||||
|
<ObjectTableNavigationButtons
|
||||||
|
disabled={objectFormState.loading || objectFormState.isEditing}
|
||||||
|
_id={permissionSettingsId}
|
||||||
|
objectType='permissionSettings'
|
||||||
|
showEndingDivider={true}
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
|
</Flex>
|
||||||
|
<ScrollBox>
|
||||||
|
<Flex vertical gap={'large'}>
|
||||||
|
<ActionHandler
|
||||||
|
actions={actions}
|
||||||
|
loading={objectFormState.loading}
|
||||||
|
ref={actionHandlerRef}
|
||||||
|
>
|
||||||
|
<ObjectForm
|
||||||
|
id={permissionSettingsId}
|
||||||
|
type='permissionSettings'
|
||||||
|
style={{ height: '100%' }}
|
||||||
|
ref={objectFormRef}
|
||||||
|
setCurrentObject={true}
|
||||||
|
onStateChange={(state) => {
|
||||||
|
setEditFormState((prev) => ({ ...prev, ...state }))
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{({ loading, isEditing, objectData }) => (
|
||||||
|
<Flex vertical gap={'large'}>
|
||||||
|
<InfoCollapse
|
||||||
|
title='Permission Settings Information'
|
||||||
|
icon={<InfoCircleIcon />}
|
||||||
|
active={collapseState.info}
|
||||||
|
onToggle={(expanded) =>
|
||||||
|
updateCollapseState('info', expanded)
|
||||||
|
}
|
||||||
|
collapseKey='info'
|
||||||
|
>
|
||||||
|
<ObjectInfo
|
||||||
|
loading={loading}
|
||||||
|
indicator={<LoadingOutlined />}
|
||||||
|
isEditing={isEditing}
|
||||||
|
type='permissionSettings'
|
||||||
|
objectData={objectData}
|
||||||
|
visibleProperties={{
|
||||||
|
permissions: false
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</InfoCollapse>
|
||||||
|
<InfoCollapse
|
||||||
|
title='Permission Settings Matrix'
|
||||||
|
icon={<PermissionSettingsIcon />}
|
||||||
|
active={collapseState.matrix}
|
||||||
|
onToggle={(expanded) =>
|
||||||
|
updateCollapseState('matrix', expanded)
|
||||||
|
}
|
||||||
|
collapseKey='matrix'
|
||||||
|
>
|
||||||
|
<Card>
|
||||||
|
<ObjectProperty
|
||||||
|
{...getModelProperty(
|
||||||
|
'permissionSettings',
|
||||||
|
'permissions'
|
||||||
|
)}
|
||||||
|
isEditing={isEditing}
|
||||||
|
objectData={objectData}
|
||||||
|
loading={loading}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
</InfoCollapse>
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
|
</ObjectForm>
|
||||||
|
</ActionHandler>
|
||||||
|
<InfoCollapse
|
||||||
|
title='Notes'
|
||||||
|
icon={<NoteIcon />}
|
||||||
|
active={collapseState.notes}
|
||||||
|
onToggle={(expanded) => updateCollapseState('notes', expanded)}
|
||||||
|
collapseKey='notes'
|
||||||
|
>
|
||||||
|
<Card>
|
||||||
|
<NotesPanel
|
||||||
|
_id={permissionSettingsId}
|
||||||
|
type='permissionSettings'
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
</InfoCollapse>
|
||||||
|
<InfoCollapse
|
||||||
|
title='Audit Logs'
|
||||||
|
icon={<AuditLogIcon />}
|
||||||
|
active={collapseState.auditLogs}
|
||||||
|
onToggle={(expanded) =>
|
||||||
|
updateCollapseState('auditLogs', expanded)
|
||||||
|
}
|
||||||
|
collapseKey='auditLogs'
|
||||||
|
>
|
||||||
|
{objectFormState.loading ? (
|
||||||
|
<InfoCollapsePlaceholder />
|
||||||
|
) : (
|
||||||
|
<ObjectTable
|
||||||
|
type='auditLog'
|
||||||
|
masterFilter={{
|
||||||
|
parent:
|
||||||
|
getModelByName('permissionSettings').prefix +
|
||||||
|
':' +
|
||||||
|
permissionSettingsId
|
||||||
|
}}
|
||||||
|
visibleColumns={{ _id: false, parent: false }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</InfoCollapse>
|
||||||
|
</Flex>
|
||||||
|
</ScrollBox>
|
||||||
|
</Flex>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PermissionSettingsInfo
|
||||||
@ -23,8 +23,12 @@ import DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
|
|||||||
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
|
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
|
||||||
import ScrollBox from '../../common/ScrollBox.jsx'
|
import ScrollBox from '../../common/ScrollBox.jsx'
|
||||||
import ObjectProperty from '../../common/ObjectProperty.jsx'
|
import ObjectProperty from '../../common/ObjectProperty.jsx'
|
||||||
import { getModelProperty, getModelByName } from '../../../../database/ObjectModels.js'
|
import {
|
||||||
|
getModelProperty,
|
||||||
|
getModelByName
|
||||||
|
} from '../../../../database/ObjectModels.js'
|
||||||
import { useMediaQuery } from 'react-responsive'
|
import { useMediaQuery } from 'react-responsive'
|
||||||
|
import PersonIcon from '../../../Icons/PersonIcon.jsx'
|
||||||
|
|
||||||
const UserInfo = () => {
|
const UserInfo = () => {
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
@ -184,7 +188,8 @@ const UserInfo = () => {
|
|||||||
type='user'
|
type='user'
|
||||||
objectData={objectData}
|
objectData={objectData}
|
||||||
visibleProperties={{
|
visibleProperties={{
|
||||||
profileImage: false
|
profileImage: false,
|
||||||
|
permissions: false
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
@ -192,6 +197,24 @@ const UserInfo = () => {
|
|||||||
</ObjectForm>
|
</ObjectForm>
|
||||||
</InfoCollapse>
|
</InfoCollapse>
|
||||||
</ActionHandler>
|
</ActionHandler>
|
||||||
|
<InfoCollapse
|
||||||
|
title='User Permissions'
|
||||||
|
icon={<PersonIcon />}
|
||||||
|
active={collapseState.permissions}
|
||||||
|
onToggle={(expanded) =>
|
||||||
|
updateCollapseState('permissions', expanded)
|
||||||
|
}
|
||||||
|
collapseKey='permissions'
|
||||||
|
>
|
||||||
|
<Card>
|
||||||
|
<ObjectProperty
|
||||||
|
{...getModelProperty('user', 'permissions')}
|
||||||
|
isEditing={objectFormState.isEditing}
|
||||||
|
objectData={objectFormState.objectData}
|
||||||
|
loading={objectFormState.loading}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
</InfoCollapse>
|
||||||
<InfoCollapse
|
<InfoCollapse
|
||||||
title='App Passwords'
|
title='App Passwords'
|
||||||
icon={<AppPasswordIcon />}
|
icon={<AppPasswordIcon />}
|
||||||
@ -206,7 +229,9 @@ const UserInfo = () => {
|
|||||||
) : (
|
) : (
|
||||||
<ObjectTable
|
<ObjectTable
|
||||||
type='appPassword'
|
type='appPassword'
|
||||||
masterFilter={{ user: getModelByName('user').prefix + ':' + userId }}
|
masterFilter={{
|
||||||
|
user: getModelByName('user').prefix + ':' + userId
|
||||||
|
}}
|
||||||
visibleColumns={{ user: false }}
|
visibleColumns={{ user: false }}
|
||||||
ref={appPasswordsTableRef}
|
ref={appPasswordsTableRef}
|
||||||
/>
|
/>
|
||||||
@ -236,10 +261,7 @@ const UserInfo = () => {
|
|||||||
<ObjectTable
|
<ObjectTable
|
||||||
type='auditLog'
|
type='auditLog'
|
||||||
masterFilter={{
|
masterFilter={{
|
||||||
parent:
|
parent: getModelByName('user').prefix + ':' + userId
|
||||||
getModelByName('user').prefix +
|
|
||||||
':' +
|
|
||||||
userId
|
|
||||||
}}
|
}}
|
||||||
visibleColumns={{ _id: false, parent: false }}
|
visibleColumns={{ _id: false, parent: false }}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -15,6 +15,7 @@ const breadcrumbNameMap = {
|
|||||||
finance: 'Finance',
|
finance: 'Finance',
|
||||||
sales: 'Sales',
|
sales: 'Sales',
|
||||||
productcategories: 'Product Categories',
|
productcategories: 'Product Categories',
|
||||||
|
permissionSettings: 'Permission Settings',
|
||||||
overview: 'Overview',
|
overview: 'Overview',
|
||||||
info: 'Info',
|
info: 'Info',
|
||||||
design: 'Design',
|
design: 'Design',
|
||||||
|
|||||||
@ -52,6 +52,7 @@ import AddressDisplay from './AddressDisplay'
|
|||||||
import ArrayDisplay from './ArrayDisplay'
|
import ArrayDisplay from './ArrayDisplay'
|
||||||
import ArrayInput from './ArrayInput'
|
import ArrayInput from './ArrayInput'
|
||||||
import ElipsisText from './ElipsisText'
|
import ElipsisText from './ElipsisText'
|
||||||
|
import PermissionsMatrix from './PermissionsMatrix'
|
||||||
import { round } from '../utils/Utils'
|
import { round } from '../utils/Utils'
|
||||||
|
|
||||||
const { Text } = Typography
|
const { Text } = Typography
|
||||||
@ -133,6 +134,8 @@ const ObjectProperty = ({
|
|||||||
columns = [],
|
columns = [],
|
||||||
style = {},
|
style = {},
|
||||||
hiddenPropertyWidth = '100px',
|
hiddenPropertyWidth = '100px',
|
||||||
|
inherit = true,
|
||||||
|
scrollHeight,
|
||||||
...rest
|
...rest
|
||||||
}) => {
|
}) => {
|
||||||
if (value && typeof value == 'function' && objectData) {
|
if (value && typeof value == 'function' && objectData) {
|
||||||
@ -616,6 +619,16 @@ const ObjectProperty = ({
|
|||||||
case 'data': {
|
case 'data': {
|
||||||
return <DataTree data={value} />
|
return <DataTree data={value} />
|
||||||
}
|
}
|
||||||
|
case 'permissionsMatrix': {
|
||||||
|
return (
|
||||||
|
<PermissionsMatrix
|
||||||
|
value={value}
|
||||||
|
disabled={true}
|
||||||
|
inherit={inherit}
|
||||||
|
scrollHeight={scrollHeight}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
case 'file': {
|
case 'file': {
|
||||||
if (value == null || value?.length == 0 || value == undefined) {
|
if (value == null || value?.length == 0 || value == undefined) {
|
||||||
return (
|
return (
|
||||||
@ -1006,6 +1019,15 @@ const ObjectProperty = ({
|
|||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
case 'permissionsMatrix':
|
||||||
|
return (
|
||||||
|
<PermissionsMatrix
|
||||||
|
inherit={inherit}
|
||||||
|
scrollHeight={scrollHeight}
|
||||||
|
disabled={disabled}
|
||||||
|
{...inputProps}
|
||||||
|
/>
|
||||||
|
)
|
||||||
default:
|
default:
|
||||||
return <Input placeholder={label} {...inputProps} />
|
return <Input placeholder={label} {...inputProps} />
|
||||||
}
|
}
|
||||||
@ -1073,7 +1095,9 @@ ObjectProperty.propTypes = {
|
|||||||
showCard: PropTypes.bool,
|
showCard: PropTypes.bool,
|
||||||
showDownload: PropTypes.bool,
|
showDownload: PropTypes.bool,
|
||||||
style: PropTypes.object,
|
style: PropTypes.object,
|
||||||
hiddenPropertyWidth: PropTypes.string
|
hiddenPropertyWidth: PropTypes.string,
|
||||||
|
inherit: PropTypes.bool,
|
||||||
|
scrollHeight: PropTypes.string
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ObjectProperty
|
export default ObjectProperty
|
||||||
|
|||||||
60
src/components/Dashboard/common/PermissionCheckbox.jsx
Normal file
60
src/components/Dashboard/common/PermissionCheckbox.jsx
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import { theme } from 'antd'
|
||||||
|
import CheckIcon from '../../Icons/CheckIcon'
|
||||||
|
import XMarkIcon from '../../Icons/XMarkIcon'
|
||||||
|
|
||||||
|
const ICON_STYLE = {
|
||||||
|
fontSize: 7,
|
||||||
|
color: '#ffffff'
|
||||||
|
}
|
||||||
|
|
||||||
|
const PermissionCheckbox = ({
|
||||||
|
checked = false,
|
||||||
|
indeterminate = false,
|
||||||
|
disabled = false,
|
||||||
|
onChange
|
||||||
|
}) => {
|
||||||
|
const { token } = theme.useToken()
|
||||||
|
const state = indeterminate ? 'inherit' : checked ? 'allow' : 'deny'
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
type='button'
|
||||||
|
className='permission-checkbox'
|
||||||
|
data-state={state}
|
||||||
|
disabled={disabled}
|
||||||
|
aria-checked={indeterminate ? 'mixed' : checked}
|
||||||
|
role='checkbox'
|
||||||
|
onClick={(event) => {
|
||||||
|
if (disabled || typeof onChange !== 'function') return
|
||||||
|
onChange(event)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className='permission-checkbox-face permission-checkbox-inherit'
|
||||||
|
style={{ borderColor: token.colorBorder }}
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
className='permission-checkbox-face permission-checkbox-allow'
|
||||||
|
style={{ backgroundColor: token.colorSuccess }}
|
||||||
|
>
|
||||||
|
<CheckIcon style={ICON_STYLE} />
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
className='permission-checkbox-face permission-checkbox-deny'
|
||||||
|
style={{ backgroundColor: token.colorError }}
|
||||||
|
>
|
||||||
|
<XMarkIcon style={ICON_STYLE} />
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
PermissionCheckbox.propTypes = {
|
||||||
|
checked: PropTypes.bool,
|
||||||
|
indeterminate: PropTypes.bool,
|
||||||
|
disabled: PropTypes.bool,
|
||||||
|
onChange: PropTypes.func
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PermissionCheckbox
|
||||||
178
src/components/Dashboard/common/PermissionsMatrix.jsx
Normal file
178
src/components/Dashboard/common/PermissionsMatrix.jsx
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
import { useMemo, useCallback, createElement } from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import { Table, Flex, Tooltip } from 'antd'
|
||||||
|
import { useMediaQuery } from 'react-responsive'
|
||||||
|
import { ElectronContext } from '../context/ElectronContext'
|
||||||
|
import { useContext } from 'react'
|
||||||
|
import {
|
||||||
|
getPermissionMatrixModels,
|
||||||
|
getPermissionMatrixActions,
|
||||||
|
modelHasPermissionAction
|
||||||
|
} from '../../../database/ObjectModels'
|
||||||
|
import PermissionCheckbox from './PermissionCheckbox'
|
||||||
|
|
||||||
|
const DEFAULT_SCROLL_HEIGHT = 'calc(var(--unit-100vh) - 258px)'
|
||||||
|
|
||||||
|
const getPermissionState = (permissions, modelName, actionName) => {
|
||||||
|
const value = permissions?.[modelName]?.[actionName]
|
||||||
|
if (value === true) return true
|
||||||
|
if (value === false) return false
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const setPermissionState = (permissions, modelName, actionName, next) => {
|
||||||
|
const current =
|
||||||
|
permissions && typeof permissions === 'object' ? permissions : {}
|
||||||
|
const modelPermissions = {
|
||||||
|
...(current[modelName] && typeof current[modelName] === 'object'
|
||||||
|
? current[modelName]
|
||||||
|
: {})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (next === null) {
|
||||||
|
delete modelPermissions[actionName]
|
||||||
|
} else {
|
||||||
|
modelPermissions[actionName] = next
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextPermissions = { ...current }
|
||||||
|
if (Object.keys(modelPermissions).length === 0) {
|
||||||
|
delete nextPermissions[modelName]
|
||||||
|
} else {
|
||||||
|
nextPermissions[modelName] = modelPermissions
|
||||||
|
}
|
||||||
|
return nextPermissions
|
||||||
|
}
|
||||||
|
|
||||||
|
const cyclePermissionState = (current, inherit) => {
|
||||||
|
if (!inherit) {
|
||||||
|
return current === true ? false : true
|
||||||
|
}
|
||||||
|
if (current === true) return false
|
||||||
|
if (current === false) return null
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
const PermissionsMatrix = ({
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
disabled = false,
|
||||||
|
inherit = true,
|
||||||
|
scrollHeight = DEFAULT_SCROLL_HEIGHT,
|
||||||
|
size = 'middle'
|
||||||
|
}) => {
|
||||||
|
const { isElectron } = useContext(ElectronContext)
|
||||||
|
const isMobile = useMediaQuery({ maxWidth: 768 })
|
||||||
|
const models = useMemo(() => getPermissionMatrixModels(), [])
|
||||||
|
const actions = useMemo(() => getPermissionMatrixActions(), [])
|
||||||
|
const permissions = value && typeof value === 'object' ? value : {}
|
||||||
|
|
||||||
|
var adjustedScrollHeight = scrollHeight
|
||||||
|
if (isMobile) {
|
||||||
|
adjustedScrollHeight = 'calc(var(--unit-100vh) - 298px)'
|
||||||
|
}
|
||||||
|
if (isElectron) {
|
||||||
|
adjustedScrollHeight = 'calc(var(--unit-100vh) - 238px)'
|
||||||
|
}
|
||||||
|
if (isMobile && isElectron) {
|
||||||
|
adjustedScrollHeight = 'calc(var(--unit-100vh) - 282px)'
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCycle = useCallback(
|
||||||
|
(modelName, actionName) => {
|
||||||
|
if (disabled || typeof onChange !== 'function') return
|
||||||
|
const current = getPermissionState(permissions, modelName, actionName)
|
||||||
|
const next = cyclePermissionState(current, inherit)
|
||||||
|
onChange(setPermissionState(permissions, modelName, actionName, next))
|
||||||
|
},
|
||||||
|
[disabled, inherit, onChange, permissions]
|
||||||
|
)
|
||||||
|
|
||||||
|
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
|
||||||
|
)
|
||||||
|
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>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
return [modelColumn, ...actionColumns]
|
||||||
|
}, [actions, disabled, handleCycle, inherit, isMobile, permissions])
|
||||||
|
|
||||||
|
const dataSource = useMemo(
|
||||||
|
() =>
|
||||||
|
models.map((model) => ({
|
||||||
|
key: model.name,
|
||||||
|
label: model.label,
|
||||||
|
icon: model.icon,
|
||||||
|
model
|
||||||
|
})),
|
||||||
|
[models]
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Table
|
||||||
|
className='dashboard-table permissions-matrix-table'
|
||||||
|
dataSource={dataSource}
|
||||||
|
columns={columns}
|
||||||
|
pagination={false}
|
||||||
|
rowKey='key'
|
||||||
|
bordered={true}
|
||||||
|
size={size}
|
||||||
|
scroll={{ x: 'max-content', y: adjustedScrollHeight }}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
PermissionsMatrix.propTypes = {
|
||||||
|
value: PropTypes.object,
|
||||||
|
onChange: PropTypes.func,
|
||||||
|
disabled: PropTypes.bool,
|
||||||
|
inherit: PropTypes.bool,
|
||||||
|
scrollHeight: PropTypes.string,
|
||||||
|
size: PropTypes.string
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PermissionsMatrix
|
||||||
8
src/components/Icons/PermissionSettingsIcon.jsx
Normal file
8
src/components/Icons/PermissionSettingsIcon.jsx
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import Icon from '@ant-design/icons'
|
||||||
|
import CustomIconSvg from '../../../assets/icons/permissionsettingsicon.svg?react'
|
||||||
|
|
||||||
|
const PermissionSettingsIcon = (props) => (
|
||||||
|
<Icon component={CustomIconSvg} {...props} />
|
||||||
|
)
|
||||||
|
|
||||||
|
export default PermissionSettingsIcon
|
||||||
@ -38,6 +38,7 @@ import SettingsIcon from './SettingsIcon'
|
|||||||
import AuditLogIcon from './AuditLogIcon'
|
import AuditLogIcon from './AuditLogIcon'
|
||||||
import DeveloperIcon from './DeveloperIcon'
|
import DeveloperIcon from './DeveloperIcon'
|
||||||
import PersonIcon from './PersonIcon'
|
import PersonIcon from './PersonIcon'
|
||||||
|
import PermissionSettingsIcon from './PermissionSettingsIcon'
|
||||||
import HostIcon from './HostIcon'
|
import HostIcon from './HostIcon'
|
||||||
import DocumentPrinterIcon from './DocumentPrinterIcon'
|
import DocumentPrinterIcon from './DocumentPrinterIcon'
|
||||||
import DocumentTemplateIcon from './DocumentTemplateIcon'
|
import DocumentTemplateIcon from './DocumentTemplateIcon'
|
||||||
@ -95,6 +96,7 @@ const sidebarIconMap = {
|
|||||||
auditLog: <AuditLogIcon />,
|
auditLog: <AuditLogIcon />,
|
||||||
developer: <DeveloperIcon />,
|
developer: <DeveloperIcon />,
|
||||||
person: <PersonIcon />,
|
person: <PersonIcon />,
|
||||||
|
permissionSettings: <PermissionSettingsIcon />,
|
||||||
host: <HostIcon />,
|
host: <HostIcon />,
|
||||||
documentPrinter: <DocumentPrinterIcon />,
|
documentPrinter: <DocumentPrinterIcon />,
|
||||||
documentTemplate: <DocumentTemplateIcon />,
|
documentTemplate: <DocumentTemplateIcon />,
|
||||||
|
|||||||
@ -31,6 +31,7 @@ import { OrderItem } from './models/OrderItem'
|
|||||||
import { Shipment } from './models/Shipment'
|
import { Shipment } from './models/Shipment'
|
||||||
import { AuditLog } from './models/AuditLog'
|
import { AuditLog } from './models/AuditLog'
|
||||||
import { User } from './models/User'
|
import { User } from './models/User'
|
||||||
|
import { PermissionSettings } from './models/PermissionSettings.js'
|
||||||
import { AppPassword } from './models/AppPassword.js'
|
import { AppPassword } from './models/AppPassword.js'
|
||||||
import { NoteType } from './models/NoteType'
|
import { NoteType } from './models/NoteType'
|
||||||
import { Note } from './models/Note'
|
import { Note } from './models/Note'
|
||||||
@ -83,6 +84,7 @@ export const objectModels = [
|
|||||||
Shipment,
|
Shipment,
|
||||||
AuditLog,
|
AuditLog,
|
||||||
User,
|
User,
|
||||||
|
PermissionSettings,
|
||||||
AppPassword,
|
AppPassword,
|
||||||
NoteType,
|
NoteType,
|
||||||
Note,
|
Note,
|
||||||
@ -136,6 +138,7 @@ export {
|
|||||||
Shipment,
|
Shipment,
|
||||||
AuditLog,
|
AuditLog,
|
||||||
User,
|
User,
|
||||||
|
PermissionSettings,
|
||||||
AppPassword,
|
AppPassword,
|
||||||
NoteType,
|
NoteType,
|
||||||
Note,
|
Note,
|
||||||
@ -227,6 +230,35 @@ export function searchModelsByLabel(label) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const HIDDEN_PERMISSION_ACTIONS = new Set(['cancelEdit', 'finishEdit'])
|
||||||
|
|
||||||
|
export function getPermissionMatrixModels() {
|
||||||
|
return objectModels.filter((model) => model?.name && model.name !== 'unknown')
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getPermissionMatrixActions() {
|
||||||
|
const seen = new Map()
|
||||||
|
getPermissionMatrixModels().forEach((model) => {
|
||||||
|
;(model.actions || []).forEach((action) => {
|
||||||
|
if (!action?.name || action.type === 'divider') return
|
||||||
|
if (HIDDEN_PERMISSION_ACTIONS.has(action.name)) return
|
||||||
|
if (!seen.has(action.name)) {
|
||||||
|
seen.set(action.name, {
|
||||||
|
name: action.name,
|
||||||
|
label: action.label || action.name
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return [...seen.values()]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function modelHasPermissionAction(model, actionName) {
|
||||||
|
return (model?.actions || []).some(
|
||||||
|
(action) => action?.name === actionName && action.type !== 'divider'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// Utility function to get nested object values
|
// Utility function to get nested object values
|
||||||
export const getPropertyValue = (obj, path) => {
|
export const getPropertyValue = (obj, path) => {
|
||||||
if (!obj || !path) return undefined
|
if (!obj || !path) return undefined
|
||||||
|
|||||||
133
src/database/models/PermissionSettings.js
Normal file
133
src/database/models/PermissionSettings.js
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
import { createElement, lazy } from 'react'
|
||||||
|
|
||||||
|
const PermissionSettingsInfo = lazy(
|
||||||
|
() =>
|
||||||
|
import('../../components/Dashboard/Management/PermissionSettings/PermissionSettingsInfo')
|
||||||
|
)
|
||||||
|
import PermissionSettingsIcon from '../../components/Icons/PermissionSettingsIcon'
|
||||||
|
import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
|
||||||
|
import EditIcon from '../../components/Icons/EditIcon'
|
||||||
|
import CheckIcon from '../../components/Icons/CheckIcon'
|
||||||
|
import XMarkIcon from '../../components/Icons/XMarkIcon'
|
||||||
|
import BinIcon from '../../components/Icons/BinIcon'
|
||||||
|
|
||||||
|
export const PermissionSettings = {
|
||||||
|
name: 'permissionSettings',
|
||||||
|
label: 'Permission Settings',
|
||||||
|
labelPlural: 'Permission Settings',
|
||||||
|
url: '/dashboard/management/permissionsettings',
|
||||||
|
prefix: 'PMS',
|
||||||
|
endpoint: 'permissionsettings',
|
||||||
|
icon: PermissionSettingsIcon,
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
name: 'info',
|
||||||
|
type: 'page',
|
||||||
|
pageName: 'info',
|
||||||
|
label: 'Info',
|
||||||
|
default: true,
|
||||||
|
row: true,
|
||||||
|
icon: InfoCircleIcon
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'edit',
|
||||||
|
type: 'page',
|
||||||
|
pageName: 'info',
|
||||||
|
label: 'Edit',
|
||||||
|
row: true,
|
||||||
|
icon: EditIcon,
|
||||||
|
visible: (objectData) => {
|
||||||
|
return !(objectData?._isEditing && objectData?._isEditing == true)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'cancelEdit',
|
||||||
|
label: 'Cancel Edits',
|
||||||
|
type: 'page',
|
||||||
|
pageName: 'info',
|
||||||
|
icon: XMarkIcon,
|
||||||
|
visible: (objectData) => {
|
||||||
|
return objectData?._isEditing && objectData?._isEditing == true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'finishEdit',
|
||||||
|
label: 'Save Edits',
|
||||||
|
type: 'page',
|
||||||
|
pageName: 'info',
|
||||||
|
icon: CheckIcon,
|
||||||
|
visible: (objectData) => {
|
||||||
|
return objectData?._isEditing && objectData?._isEditing == true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ type: 'divider' },
|
||||||
|
{
|
||||||
|
name: 'delete',
|
||||||
|
type: 'page',
|
||||||
|
pageName: 'info',
|
||||||
|
label: 'Delete',
|
||||||
|
icon: BinIcon,
|
||||||
|
danger: true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
pages: [
|
||||||
|
{
|
||||||
|
name: 'info',
|
||||||
|
content: () => createElement(PermissionSettingsInfo)
|
||||||
|
}
|
||||||
|
],
|
||||||
|
columns: ['_reference', 'name', 'createdAt', 'updatedAt'],
|
||||||
|
filters: ['name', 'createdAt', 'updatedAt', '_reference'],
|
||||||
|
sorters: ['name', 'createdAt', '_id', 'updatedAt'],
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
name: '_id',
|
||||||
|
label: 'ID',
|
||||||
|
columnFixed: 'left',
|
||||||
|
type: 'id',
|
||||||
|
objectType: 'permissionSettings',
|
||||||
|
showCopy: true,
|
||||||
|
columnWidth: 140
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'createdAt',
|
||||||
|
label: 'Created At',
|
||||||
|
type: 'dateTime',
|
||||||
|
readOnly: true,
|
||||||
|
columnWidth: 175
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '_reference',
|
||||||
|
label: 'Reference',
|
||||||
|
type: 'reference',
|
||||||
|
columnFixed: 'left',
|
||||||
|
objectType: 'permissionSettings',
|
||||||
|
showCopy: true,
|
||||||
|
readOnly: true,
|
||||||
|
columnWidth: 180
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'updatedAt',
|
||||||
|
label: 'Updated At',
|
||||||
|
type: 'dateTime',
|
||||||
|
readOnly: true,
|
||||||
|
columnWidth: 175
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'name',
|
||||||
|
label: 'Name',
|
||||||
|
columnFixed: 'left',
|
||||||
|
required: true,
|
||||||
|
type: 'text',
|
||||||
|
columnWidth: 200
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'permissions',
|
||||||
|
label: 'Permissions',
|
||||||
|
type: 'permissionsMatrix',
|
||||||
|
inherit: true,
|
||||||
|
span: 2,
|
||||||
|
columnWidth: 200
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -87,13 +87,22 @@ export const User = {
|
|||||||
content: () => createElement(UserInfo)
|
content: () => createElement(UserInfo)
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
columns: ['_reference', 'name', 'username', 'email', 'role', 'createdAt', 'updatedAt'],
|
columns: [
|
||||||
|
'_reference',
|
||||||
|
'name',
|
||||||
|
'username',
|
||||||
|
'email',
|
||||||
|
'permissionSettings',
|
||||||
|
'createdAt',
|
||||||
|
'updatedAt'
|
||||||
|
],
|
||||||
filters: [
|
filters: [
|
||||||
'name',
|
'name',
|
||||||
'username',
|
'username',
|
||||||
'firstName',
|
'firstName',
|
||||||
'lastName',
|
'lastName',
|
||||||
'email',
|
'email',
|
||||||
|
'permissionSettings',
|
||||||
'createdAt',
|
'createdAt',
|
||||||
'updatedAt',
|
'updatedAt',
|
||||||
'_reference'
|
'_reference'
|
||||||
@ -169,6 +178,23 @@ export const User = {
|
|||||||
columnWidth: 300,
|
columnWidth: 300,
|
||||||
type: 'email'
|
type: 'email'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'permissionSettings',
|
||||||
|
label: 'Permission Settings',
|
||||||
|
type: 'objectList',
|
||||||
|
objectType: 'permissionSettings',
|
||||||
|
span: 2,
|
||||||
|
columnWidth: 250
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'permissions',
|
||||||
|
label: 'Permissions',
|
||||||
|
type: 'permissionsMatrix',
|
||||||
|
inherit: false,
|
||||||
|
readOnly: true,
|
||||||
|
span: 2,
|
||||||
|
columnWidth: 200
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'profileImage',
|
name: 'profileImage',
|
||||||
label: 'Profile Image',
|
label: 'Profile Image',
|
||||||
|
|||||||
@ -125,6 +125,12 @@ const managementSidebarItems = [
|
|||||||
label: 'Users',
|
label: 'Users',
|
||||||
path: '/dashboard/management/users'
|
path: '/dashboard/management/users'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: 'permissionSettings',
|
||||||
|
iconKey: 'permissionSettings',
|
||||||
|
label: 'Permission Settings',
|
||||||
|
path: '/dashboard/management/permissionsettings'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: 'appPasswords',
|
key: 'appPasswords',
|
||||||
iconKey: 'appPassword',
|
iconKey: 'appPassword',
|
||||||
|
|||||||
@ -43,6 +43,9 @@ const NoteInfo = lazy(
|
|||||||
() => import('../components/Dashboard/Management/Notes/NoteInfo.jsx')
|
() => import('../components/Dashboard/Management/Notes/NoteInfo.jsx')
|
||||||
)
|
)
|
||||||
const Users = lazy(() => import('../components/Dashboard/Management/Users.jsx'))
|
const Users = lazy(() => import('../components/Dashboard/Management/Users.jsx'))
|
||||||
|
const PermissionSettings = lazy(
|
||||||
|
() => import('../components/Dashboard/Management/PermissionSettings.jsx')
|
||||||
|
)
|
||||||
const AppPasswords = lazy(
|
const AppPasswords = lazy(
|
||||||
() => import('../components/Dashboard/Management/AppPasswords.jsx')
|
() => import('../components/Dashboard/Management/AppPasswords.jsx')
|
||||||
)
|
)
|
||||||
@ -92,6 +95,11 @@ const ManagementRoutes = [
|
|||||||
<Route key='vendors' path='management/vendors' element={<Vendors />} />,
|
<Route key='vendors' path='management/vendors' element={<Vendors />} />,
|
||||||
<Route key='hosts' path='management/hosts' element={<Hosts />} />,
|
<Route key='hosts' path='management/hosts' element={<Hosts />} />,
|
||||||
<Route key='users' path='management/users' element={<Users />} />,
|
<Route key='users' path='management/users' element={<Users />} />,
|
||||||
|
<Route
|
||||||
|
key='permissionsettings'
|
||||||
|
path='management/permissionsettings'
|
||||||
|
element={<PermissionSettings />}
|
||||||
|
/>,
|
||||||
<Route key='files' path='management/files' element={<Files />} />,
|
<Route key='files' path='management/files' element={<Files />} />,
|
||||||
<Route key='materials' path='management/materials' element={<Materials />} />,
|
<Route key='materials' path='management/materials' element={<Materials />} />,
|
||||||
<Route key='couriers' path='management/couriers' element={<Couriers />} />,
|
<Route key='couriers' path='management/couriers' element={<Couriers />} />,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user