diff --git a/assets/icons/permissionsettingsicon.svg b/assets/icons/permissionsettingsicon.svg new file mode 100644 index 00000000..78bb70fa --- /dev/null +++ b/assets/icons/permissionsettingsicon.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/assets/stylesheets/App.css b/assets/stylesheets/App.css index 433f3db1..25e86219 100644 --- a/assets/stylesheets/App.css +++ b/assets/stylesheets/App.css @@ -1328,3 +1328,89 @@ span.ant-skeleton-input.ant-skeleton-input-sm.text-skeleton { background-color: #85858541; 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; +} diff --git a/src/components/Dashboard/Management/PermissionSettings.jsx b/src/components/Dashboard/Management/PermissionSettings.jsx new file mode 100644 index 00000000..71e70e7f --- /dev/null +++ b/src/components/Dashboard/Management/PermissionSettings.jsx @@ -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: + }, + { type: 'divider' }, + { + label: 'Reload List', + key: 'reloadList', + icon: + } + ], + onClick: ({ key }) => { + if (key === 'reloadList') { + tableRef.current?.reload() + } else if (key === 'newPermissionSettings') { + setNewPermissionSettingsOpen(true) + } + } + } + + return ( + <> + + + + + + + + + + + setShowSortSidebar(!showSortSidebar)} + /> + setShowFilterSidebar(!showFilterSidebar)} + /> + + + + + + setNewPermissionSettingsOpen(false)} + footer={null} + destroyOnHidden={true} + width={700} + > + { + setNewPermissionSettingsOpen(false) + tableRef.current?.reload() + }} + reset={!newPermissionSettingsOpen} + /> + + + ) +} + +export default PermissionSettingsList diff --git a/src/components/Dashboard/Management/PermissionSettings/NewPermissionSettings.jsx b/src/components/Dashboard/Management/PermissionSettings/NewPermissionSettings.jsx new file mode 100644 index 00000000..3dc57254 --- /dev/null +++ b/src/components/Dashboard/Management/PermissionSettings/NewPermissionSettings.jsx @@ -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 ( + + {({ handleSubmit, submitLoading, objectData, formValid }) => { + const steps = [ + { + title: 'Required', + key: 'required', + content: ( + + ) + }, + { + title: 'Summary', + key: 'summary', + content: ( + + ) + } + ] + return ( + { + const result = await handleSubmit() + if (result) { + onOk() + } + }} + /> + ) + }} + + ) +} + +NewPermissionSettings.propTypes = { + onOk: PropTypes.func.isRequired, + reset: PropTypes.bool, + defaultValues: PropTypes.object +} + +export default NewPermissionSettings diff --git a/src/components/Dashboard/Management/PermissionSettings/PermissionSettingsInfo.jsx b/src/components/Dashboard/Management/PermissionSettings/PermissionSettingsInfo.jsx new file mode 100644 index 00000000..b77ab91f --- /dev/null +++ b/src/components/Dashboard/Management/PermissionSettings/PermissionSettingsInfo.jsx @@ -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 ( + <> + + + + + + + + + + + + + { + 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} + /> + + + + + + + { + setEditFormState((prev) => ({ ...prev, ...state })) + }} + > + {({ loading, isEditing, objectData }) => ( + + } + active={collapseState.info} + onToggle={(expanded) => + updateCollapseState('info', expanded) + } + collapseKey='info' + > + } + isEditing={isEditing} + type='permissionSettings' + objectData={objectData} + visibleProperties={{ + permissions: false + }} + /> + + } + active={collapseState.matrix} + onToggle={(expanded) => + updateCollapseState('matrix', expanded) + } + collapseKey='matrix' + > + + + + + + )} + + + } + active={collapseState.notes} + onToggle={(expanded) => updateCollapseState('notes', expanded)} + collapseKey='notes' + > + + + + + } + active={collapseState.auditLogs} + onToggle={(expanded) => + updateCollapseState('auditLogs', expanded) + } + collapseKey='auditLogs' + > + {objectFormState.loading ? ( + + ) : ( + + )} + + + + + + ) +} + +export default PermissionSettingsInfo diff --git a/src/components/Dashboard/Management/Users/UserInfo.jsx b/src/components/Dashboard/Management/Users/UserInfo.jsx index d65ccc59..e718865d 100644 --- a/src/components/Dashboard/Management/Users/UserInfo.jsx +++ b/src/components/Dashboard/Management/Users/UserInfo.jsx @@ -23,8 +23,12 @@ import DocumentPrintButton from '../../common/DocumentPrintButton.jsx' import UserNotifierToggle from '../../common/UserNotifierToggle.jsx' import ScrollBox from '../../common/ScrollBox.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 PersonIcon from '../../../Icons/PersonIcon.jsx' const UserInfo = () => { const location = useLocation() @@ -184,7 +188,8 @@ const UserInfo = () => { type='user' objectData={objectData} visibleProperties={{ - profileImage: false + profileImage: false, + permissions: false }} /> @@ -192,6 +197,24 @@ const UserInfo = () => { + } + active={collapseState.permissions} + onToggle={(expanded) => + updateCollapseState('permissions', expanded) + } + collapseKey='permissions' + > + + + + } @@ -206,7 +229,9 @@ const UserInfo = () => { ) : ( @@ -236,10 +261,7 @@ const UserInfo = () => { diff --git a/src/components/Dashboard/common/DashboardBreadcrumb.jsx b/src/components/Dashboard/common/DashboardBreadcrumb.jsx index e175e9cc..a464cadd 100644 --- a/src/components/Dashboard/common/DashboardBreadcrumb.jsx +++ b/src/components/Dashboard/common/DashboardBreadcrumb.jsx @@ -15,6 +15,7 @@ const breadcrumbNameMap = { finance: 'Finance', sales: 'Sales', productcategories: 'Product Categories', + permissionSettings: 'Permission Settings', overview: 'Overview', info: 'Info', design: 'Design', diff --git a/src/components/Dashboard/common/ObjectProperty.jsx b/src/components/Dashboard/common/ObjectProperty.jsx index de3594b7..68a9bd0a 100644 --- a/src/components/Dashboard/common/ObjectProperty.jsx +++ b/src/components/Dashboard/common/ObjectProperty.jsx @@ -52,6 +52,7 @@ import AddressDisplay from './AddressDisplay' import ArrayDisplay from './ArrayDisplay' import ArrayInput from './ArrayInput' import ElipsisText from './ElipsisText' +import PermissionsMatrix from './PermissionsMatrix' import { round } from '../utils/Utils' const { Text } = Typography @@ -133,6 +134,8 @@ const ObjectProperty = ({ columns = [], style = {}, hiddenPropertyWidth = '100px', + inherit = true, + scrollHeight, ...rest }) => { if (value && typeof value == 'function' && objectData) { @@ -616,6 +619,16 @@ const ObjectProperty = ({ case 'data': { return } + case 'permissionsMatrix': { + return ( + + ) + } case 'file': { if (value == null || value?.length == 0 || value == undefined) { return ( @@ -1006,6 +1019,15 @@ const ObjectProperty = ({ /> ) } + case 'permissionsMatrix': + return ( + + ) default: return } @@ -1073,7 +1095,9 @@ ObjectProperty.propTypes = { showCard: PropTypes.bool, showDownload: PropTypes.bool, style: PropTypes.object, - hiddenPropertyWidth: PropTypes.string + hiddenPropertyWidth: PropTypes.string, + inherit: PropTypes.bool, + scrollHeight: PropTypes.string } export default ObjectProperty diff --git a/src/components/Dashboard/common/PermissionCheckbox.jsx b/src/components/Dashboard/common/PermissionCheckbox.jsx new file mode 100644 index 00000000..756def49 --- /dev/null +++ b/src/components/Dashboard/common/PermissionCheckbox.jsx @@ -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 ( + + ) +} + +PermissionCheckbox.propTypes = { + checked: PropTypes.bool, + indeterminate: PropTypes.bool, + disabled: PropTypes.bool, + onChange: PropTypes.func +} + +export default PermissionCheckbox diff --git a/src/components/Dashboard/common/PermissionsMatrix.jsx b/src/components/Dashboard/common/PermissionsMatrix.jsx new file mode 100644 index 00000000..b80b966e --- /dev/null +++ b/src/components/Dashboard/common/PermissionsMatrix.jsx @@ -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) => ( + + {record.icon ? createElement(record.icon) : null} + {label} + + ) + } + + const actionColumns = actions.map((action) => ({ + title: {action.label}, + 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 ( + + + { + handleCycle(record.model.name, action.name) + }} + /> + + + ) + } + })) + + 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 ( + + ) +} + +PermissionsMatrix.propTypes = { + value: PropTypes.object, + onChange: PropTypes.func, + disabled: PropTypes.bool, + inherit: PropTypes.bool, + scrollHeight: PropTypes.string, + size: PropTypes.string +} + +export default PermissionsMatrix diff --git a/src/components/Icons/PermissionSettingsIcon.jsx b/src/components/Icons/PermissionSettingsIcon.jsx new file mode 100644 index 00000000..d23ceb15 --- /dev/null +++ b/src/components/Icons/PermissionSettingsIcon.jsx @@ -0,0 +1,8 @@ +import Icon from '@ant-design/icons' +import CustomIconSvg from '../../../assets/icons/permissionsettingsicon.svg?react' + +const PermissionSettingsIcon = (props) => ( + +) + +export default PermissionSettingsIcon diff --git a/src/components/Icons/sidebarIconMap.jsx b/src/components/Icons/sidebarIconMap.jsx index f8fa9858..875c858a 100644 --- a/src/components/Icons/sidebarIconMap.jsx +++ b/src/components/Icons/sidebarIconMap.jsx @@ -38,6 +38,7 @@ import SettingsIcon from './SettingsIcon' import AuditLogIcon from './AuditLogIcon' import DeveloperIcon from './DeveloperIcon' import PersonIcon from './PersonIcon' +import PermissionSettingsIcon from './PermissionSettingsIcon' import HostIcon from './HostIcon' import DocumentPrinterIcon from './DocumentPrinterIcon' import DocumentTemplateIcon from './DocumentTemplateIcon' @@ -95,6 +96,7 @@ const sidebarIconMap = { auditLog: , developer: , person: , + permissionSettings: , host: , documentPrinter: , documentTemplate: , diff --git a/src/database/ObjectModels.js b/src/database/ObjectModels.js index a7af3e22..42041d84 100644 --- a/src/database/ObjectModels.js +++ b/src/database/ObjectModels.js @@ -31,6 +31,7 @@ import { OrderItem } from './models/OrderItem' import { Shipment } from './models/Shipment' import { AuditLog } from './models/AuditLog' import { User } from './models/User' +import { PermissionSettings } from './models/PermissionSettings.js' import { AppPassword } from './models/AppPassword.js' import { NoteType } from './models/NoteType' import { Note } from './models/Note' @@ -83,6 +84,7 @@ export const objectModels = [ Shipment, AuditLog, User, + PermissionSettings, AppPassword, NoteType, Note, @@ -136,6 +138,7 @@ export { Shipment, AuditLog, User, + PermissionSettings, AppPassword, NoteType, 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 export const getPropertyValue = (obj, path) => { if (!obj || !path) return undefined diff --git a/src/database/models/PermissionSettings.js b/src/database/models/PermissionSettings.js new file mode 100644 index 00000000..5b13274e --- /dev/null +++ b/src/database/models/PermissionSettings.js @@ -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 + } + ] +} diff --git a/src/database/models/User.js b/src/database/models/User.js index dd5914f8..92d4985c 100644 --- a/src/database/models/User.js +++ b/src/database/models/User.js @@ -87,13 +87,22 @@ export const User = { content: () => createElement(UserInfo) } ], - columns: ['_reference', 'name', 'username', 'email', 'role', 'createdAt', 'updatedAt'], + columns: [ + '_reference', + 'name', + 'username', + 'email', + 'permissionSettings', + 'createdAt', + 'updatedAt' + ], filters: [ 'name', 'username', 'firstName', 'lastName', 'email', + 'permissionSettings', 'createdAt', 'updatedAt', '_reference' @@ -169,6 +178,23 @@ export const User = { columnWidth: 300, 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', label: 'Profile Image', diff --git a/src/database/sidebars/management.js b/src/database/sidebars/management.js index 1826257d..a482e579 100644 --- a/src/database/sidebars/management.js +++ b/src/database/sidebars/management.js @@ -125,6 +125,12 @@ const managementSidebarItems = [ label: 'Users', path: '/dashboard/management/users' }, + { + key: 'permissionSettings', + iconKey: 'permissionSettings', + label: 'Permission Settings', + path: '/dashboard/management/permissionsettings' + }, { key: 'appPasswords', iconKey: 'appPassword', diff --git a/src/routes/ManagementRoutes.jsx b/src/routes/ManagementRoutes.jsx index e024f85b..5206275f 100644 --- a/src/routes/ManagementRoutes.jsx +++ b/src/routes/ManagementRoutes.jsx @@ -43,6 +43,9 @@ const NoteInfo = lazy( () => import('../components/Dashboard/Management/Notes/NoteInfo.jsx') ) const Users = lazy(() => import('../components/Dashboard/Management/Users.jsx')) +const PermissionSettings = lazy( + () => import('../components/Dashboard/Management/PermissionSettings.jsx') +) const AppPasswords = lazy( () => import('../components/Dashboard/Management/AppPasswords.jsx') ) @@ -92,6 +95,11 @@ const ManagementRoutes = [ } />, } />, } />, + } + />, } />, } />, } />,