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 = [
} />,
} />,
} />,
+ }
+ />,
} />,
} />,
} />,