diff --git a/assets/icons/persongroupicon.svg b/assets/icons/persongroupicon.svg
new file mode 100644
index 00000000..f9f288c1
--- /dev/null
+++ b/assets/icons/persongroupicon.svg
@@ -0,0 +1,8 @@
+
+
+
diff --git a/assets/stylesheets/App.css b/assets/stylesheets/App.css
index 8805b3cd..fb0a5b03 100644
--- a/assets/stylesheets/App.css
+++ b/assets/stylesheets/App.css
@@ -1405,10 +1405,18 @@ span.ant-skeleton-input.ant-skeleton-input-sm.text-skeleton {
.permission-checkbox[data-state='inherit'] .permission-checkbox-inherit,
.permission-checkbox[data-state='allow'] .permission-checkbox-allow,
-.permission-checkbox[data-state='deny'] .permission-checkbox-deny {
+.permission-checkbox[data-state='deny'] .permission-checkbox-deny,
+.permission-checkbox[data-state='mixed'] .permission-checkbox-mixed {
opacity: 1;
}
+.permission-checkbox-square {
+ width: 7px;
+ height: 7px;
+ background-color: #ffffff;
+ border-radius: 1px;
+}
+
.permission-checkbox-face .anticon {
display: flex;
line-height: 0;
diff --git a/src/components/Dashboard/Management/PermissionSettings.jsx b/src/components/Dashboard/Management/PermissionSettings.jsx
index 71e70e7f..02ad21b9 100644
--- a/src/components/Dashboard/Management/PermissionSettings.jsx
+++ b/src/components/Dashboard/Management/PermissionSettings.jsx
@@ -1,6 +1,6 @@
import { useState, useRef } from 'react'
import { Button, Flex, Space, Modal, Dropdown } from 'antd'
-import NewPermissionSettings from './PermissionSettings/NewPermissionSettings'
+import NewPermissionSetting from './PermissionSettings/NewPermissionSetting'
import ObjectTable from '../common/ObjectTable'
import PlusIcon from '../../Icons/PlusIcon'
import ReloadIcon from '../../Icons/ReloadIcon'
@@ -19,10 +19,10 @@ const PermissionSettingsList = () => {
useState(false)
const tableRef = useRef()
- const [viewMode, setViewMode] = useViewMode('permissionSettings')
+ const [viewMode, setViewMode] = useViewMode('permissionSetting')
const [columnVisibility, setColumnVisibility] =
- useColumnVisibility('permissionSettings')
+ useColumnVisibility('permissionSetting')
const [showFilterSidebar, setShowFilterSidebar] =
useFilterSidebarVisibility('PermissionSettings')
@@ -62,12 +62,12 @@ const PermissionSettingsList = () => {
-
+
{
{
destroyOnHidden={true}
width={700}
>
- {
setNewPermissionSettingsOpen(false)
tableRef.current?.reload()
diff --git a/src/components/Dashboard/Management/PermissionSettings/NewPermissionSettings.jsx b/src/components/Dashboard/Management/PermissionSettings/NewPermissionSetting.jsx
similarity index 88%
rename from src/components/Dashboard/Management/PermissionSettings/NewPermissionSettings.jsx
rename to src/components/Dashboard/Management/PermissionSettings/NewPermissionSetting.jsx
index 3dc57254..3823d35d 100644
--- a/src/components/Dashboard/Management/PermissionSettings/NewPermissionSettings.jsx
+++ b/src/components/Dashboard/Management/PermissionSettings/NewPermissionSetting.jsx
@@ -3,10 +3,10 @@ import ObjectInfo from '../../common/ObjectInfo'
import NewObjectForm from '../../common/NewObjectForm'
import WizardView from '../../common/WizardView'
-const NewPermissionSettings = ({ onOk, defaultValues }) => {
+const NewPermissionSetting = ({ onOk, defaultValues }) => {
return (
{({ handleSubmit, submitLoading, objectData, formValid }) => {
@@ -16,7 +16,7 @@ const NewPermissionSettings = ({ onOk, defaultValues }) => {
key: 'required',
content: (
{
key: 'summary',
content: (
{
)
}
-NewPermissionSettings.propTypes = {
+NewPermissionSetting.propTypes = {
onOk: PropTypes.func.isRequired,
reset: PropTypes.bool,
defaultValues: PropTypes.object
}
-export default NewPermissionSettings
+export default NewPermissionSetting
diff --git a/src/components/Dashboard/Management/PermissionSettings/PermissionSettingsInfo.jsx b/src/components/Dashboard/Management/PermissionSettings/PermissionSettingInfo.jsx
similarity index 89%
rename from src/components/Dashboard/Management/PermissionSettings/PermissionSettingsInfo.jsx
rename to src/components/Dashboard/Management/PermissionSettings/PermissionSettingInfo.jsx
index 4d2201ee..7d22b591 100644
--- a/src/components/Dashboard/Management/PermissionSettings/PermissionSettingsInfo.jsx
+++ b/src/components/Dashboard/Management/PermissionSettings/PermissionSettingInfo.jsx
@@ -26,17 +26,17 @@ import {
} from '../../../../database/ObjectModels.js'
import ScrollBox from '../../common/ScrollBox.jsx'
import ObjectProperty from '../../common/ObjectProperty.jsx'
-import PermissionSettingsIcon from '../../../Icons/PermissionSettingsIcon.jsx'
+import PermissionSettingIcon from '../../../Icons/PermissionSettingIcon.jsx'
-const PermissionSettingsInfo = () => {
+const PermissionSettingInfo = () => {
const location = useLocation()
const objectFormRef = useRef(null)
const actionHandlerRef = useRef(null)
- const permissionSettingsId = new URLSearchParams(location.search).get(
- 'permissionSettingsId'
+ const permissionSettingId = new URLSearchParams(location.search).get(
+ 'permissionSettingId'
)
const [collapseState, updateCollapseState] = useCollapseState(
- 'PermissionSettingsInfo',
+ 'PermissionSettingInfo',
{
info: true,
notes: true,
@@ -84,8 +84,8 @@ const PermissionSettingsInfo = () => {
@@ -100,12 +100,12 @@ const PermissionSettingsInfo = () => {
updateVisibleState={updateCollapseState}
/>
@@ -138,8 +138,8 @@ const PermissionSettingsInfo = () => {
/>
@@ -152,8 +152,8 @@ const PermissionSettingsInfo = () => {
ref={actionHandlerRef}
>
{
loading={loading}
indicator={}
isEditing={isEditing}
- type='permissionSettings'
+ type='permissionSetting'
objectData={objectData}
visibleProperties={{
permissions: false
@@ -185,7 +185,7 @@ const PermissionSettingsInfo = () => {
}
+ icon={}
active={collapseState.matrix}
onToggle={(expanded) =>
updateCollapseState('matrix', expanded)
@@ -196,7 +196,7 @@ const PermissionSettingsInfo = () => {
{
>
@@ -240,9 +240,9 @@ const PermissionSettingsInfo = () => {
type='auditLog'
masterFilter={{
parent:
- getModelByName('permissionSettings').prefix +
+ getModelByName('permissionSetting').prefix +
':' +
- permissionSettingsId
+ permissionSettingId
}}
visibleColumns={{ _id: false, parent: false }}
/>
@@ -255,4 +255,4 @@ const PermissionSettingsInfo = () => {
)
}
-export default PermissionSettingsInfo
+export default PermissionSettingInfo
diff --git a/src/components/Dashboard/Management/UserGroups.jsx b/src/components/Dashboard/Management/UserGroups.jsx
new file mode 100644
index 00000000..ad655497
--- /dev/null
+++ b/src/components/Dashboard/Management/UserGroups.jsx
@@ -0,0 +1,123 @@
+import { useState, useRef } from 'react'
+import { Button, Flex, Space, Modal, Dropdown } from 'antd'
+import NewUserGroup from './UserGroups/NewUserGroup'
+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 UserGroups = () => {
+ const [newUserGroupOpen, setNewUserGroupOpen] = useState(false)
+ const tableRef = useRef()
+
+ const [viewMode, setViewMode] = useViewMode('userGroup')
+
+ const [columnVisibility, setColumnVisibility] = useColumnVisibility('userGroup')
+
+ const [showFilterSidebar, setShowFilterSidebar] =
+ useFilterSidebarVisibility('UserGroups')
+
+ const [showSortSidebar, setShowSortSidebar] =
+ useSortSidebarVisibility('UserGroups')
+
+ const actionItems = {
+ items: [
+ {
+ label: 'New User Group',
+ key: 'newUserGroup',
+ icon:
+ },
+ { type: 'divider' },
+ {
+ label: 'Reload List',
+ key: 'reloadList',
+ icon:
+ }
+ ],
+ onClick: ({ key }) => {
+ if (key === 'reloadList') {
+ tableRef.current?.reload()
+ } else if (key === 'newUserGroup') {
+ setNewUserGroupOpen(true)
+ }
+ }
+ }
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+ setShowSortSidebar(!showSortSidebar)}
+ />
+ setShowFilterSidebar(!showFilterSidebar)}
+ />
+
+
+
+
+
+ setNewUserGroupOpen(false)}
+ footer={null}
+ destroyOnHidden={true}
+ width={700}
+ >
+ {
+ setNewUserGroupOpen(false)
+ tableRef.current?.reload()
+ }}
+ reset={!newUserGroupOpen}
+ />
+
+ >
+ )
+}
+
+export default UserGroups
diff --git a/src/components/Dashboard/Management/UserGroups/NewUserGroup.jsx b/src/components/Dashboard/Management/UserGroups/NewUserGroup.jsx
new file mode 100644
index 00000000..257bd750
--- /dev/null
+++ b/src/components/Dashboard/Management/UserGroups/NewUserGroup.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 NewUserGroup = ({ 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()
+ }
+ }}
+ />
+ )
+ }}
+
+ )
+}
+
+NewUserGroup.propTypes = {
+ onOk: PropTypes.func.isRequired,
+ reset: PropTypes.bool,
+ defaultValues: PropTypes.object
+}
+
+export default NewUserGroup
diff --git a/src/components/Dashboard/Management/UserGroups/UserGroupInfo.jsx b/src/components/Dashboard/Management/UserGroups/UserGroupInfo.jsx
new file mode 100644
index 00000000..7a8a5c40
--- /dev/null
+++ b/src/components/Dashboard/Management/UserGroups/UserGroupInfo.jsx
@@ -0,0 +1,265 @@
+import { useRef, useState } from 'react'
+import { useLocation } from 'react-router-dom'
+import { Card, Flex, Space, Spin } 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 ScrollBox from '../../common/ScrollBox.jsx'
+import ObjectProperty from '../../common/ObjectProperty.jsx'
+import {
+ getModelProperty,
+ getModelByName
+} from '../../../../database/ObjectModels.js'
+import PersonGroupIcon from '../../../Icons/PersonGroupIcon.jsx'
+import PersonIcon from '../../../Icons/PersonIcon.jsx'
+
+const UserGroupInfo = () => {
+ const location = useLocation()
+ const objectFormRef = useRef(null)
+ const actionHandlerRef = useRef(null)
+ const userGroupId = new URLSearchParams(location.search).get('userGroupId')
+ const [collapseState, updateCollapseState] = useCollapseState(
+ 'UserGroupInfo',
+ {
+ info: true,
+ permissions: true,
+ users: true,
+ notes: 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}
+ />
+
+
+
+
+
+
+ }
+ active={collapseState.info}
+ onToggle={(expanded) => updateCollapseState('info', expanded)}
+ collapseKey='info'
+ >
+ {
+ setEditFormState((prev) => ({ ...prev, ...state }))
+ }}
+ >
+ {({ loading, isEditing, objectData }) => (
+ }
+ isEditing={isEditing}
+ type='userGroup'
+ labelWidth='190px'
+ objectData={objectData}
+ visibleProperties={{
+ permissions: false
+ }}
+ />
+ )}
+
+
+
+ }
+ active={collapseState.permissions}
+ onToggle={(expanded) =>
+ updateCollapseState('permissions', expanded)
+ }
+ collapseKey='permissions'
+ >
+ }
+ >
+
+
+
+
+
+ }
+ active={collapseState.users}
+ onToggle={(expanded) => updateCollapseState('users', expanded)}
+ collapseKey='users'
+ >
+ {!userGroupId ? (
+
+ ) : (
+
+ )}
+
+ }
+ active={collapseState.notes}
+ onToggle={(expanded) => updateCollapseState('notes', expanded)}
+ collapseKey='notes'
+ >
+
+
+
+
+ }
+ active={collapseState.auditLogs}
+ onToggle={(expanded) => updateCollapseState('auditLogs', expanded)}
+ collapseKey='auditLogs'
+ >
+ {objectFormState.loading ? (
+
+ ) : (
+
+ )}
+
+
+
+
+ )
+}
+
+export default UserGroupInfo
diff --git a/src/components/Dashboard/Management/Users/UserInfo.jsx b/src/components/Dashboard/Management/Users/UserInfo.jsx
index ab627945..2f38cc68 100644
--- a/src/components/Dashboard/Management/Users/UserInfo.jsx
+++ b/src/components/Dashboard/Management/Users/UserInfo.jsx
@@ -187,6 +187,7 @@ const UserInfo = () => {
isEditing={isEditing}
type='user'
objectData={objectData}
+ labelWidth='190px'
visibleProperties={{
profileImage: false,
permissions: false
diff --git a/src/components/Dashboard/common/DashboardBreadcrumb.jsx b/src/components/Dashboard/common/DashboardBreadcrumb.jsx
index a464cadd..d4e4a2dd 100644
--- a/src/components/Dashboard/common/DashboardBreadcrumb.jsx
+++ b/src/components/Dashboard/common/DashboardBreadcrumb.jsx
@@ -14,8 +14,6 @@ const breadcrumbNameMap = {
developer: 'Developer',
finance: 'Finance',
sales: 'Sales',
- productcategories: 'Product Categories',
- permissionSettings: 'Permission Settings',
overview: 'Overview',
info: 'Info',
design: 'Design',
diff --git a/src/components/Dashboard/common/NewObjectForm.jsx b/src/components/Dashboard/common/NewObjectForm.jsx
index 4004433a..aba99983 100644
--- a/src/components/Dashboard/common/NewObjectForm.jsx
+++ b/src/components/Dashboard/common/NewObjectForm.jsx
@@ -3,7 +3,7 @@ import { Form } from 'antd'
import { ApiServerContext } from '../context/ApiServerContext'
import { useMessageContext } from '../context/MessageContext'
import PropTypes from 'prop-types'
-import merge from 'lodash/merge'
+import mergeWith from 'lodash/mergeWith'
import set from 'lodash/set'
import { getModelByName } from '../../../database/ObjectModels'
@@ -18,6 +18,15 @@ const buildObjectFromEntries = (entries = []) => {
}, {})
}
+const arrayReplaceCustomizer = (objValue, srcValue, key) => {
+ if (Array.isArray(srcValue)) {
+ return srcValue
+ }
+ if (key === 'permissions' && srcValue !== undefined) {
+ return srcValue
+ }
+}
+
/**
* NewObjectForm is a reusable form component for creating new objects.
* It handles form validation, submission, and error handling logic.
@@ -164,9 +173,16 @@ const NewObjectForm = ({ type, style, defaultValues = {}, children }) => {
// Calculate computed values for initial data
const computedEntries = calculateComputedValues(defaultValues, model)
const computedValuesObject = buildObjectFromEntries(computedEntries)
- const initialFormData = merge({}, defaultValues, computedValuesObject)
+ const initialFormData = mergeWith(
+ {},
+ defaultValues,
+ computedValuesObject,
+ arrayReplaceCustomizer
+ )
form.setFieldsValue(initialFormData)
- setObjectData((prev) => merge({}, prev, initialFormData))
+ setObjectData((prev) =>
+ mergeWith({}, prev, initialFormData, arrayReplaceCustomizer)
+ )
return validateForm()
}
}, [form, defaultValues, calculateComputedValues, model, validateForm])
@@ -181,7 +197,12 @@ const NewObjectForm = ({ type, style, defaultValues = {}, children }) => {
setSubmitLoading(true)
const computedEntries = calculateComputedValues(objectData, model)
const computedValuesObject = buildObjectFromEntries(computedEntries)
- const computedObjectData = merge({}, objectData, computedValuesObject)
+ const computedObjectData = mergeWith(
+ {},
+ objectData,
+ computedValuesObject,
+ arrayReplaceCustomizer
+ )
const newObject = await createObject(type, computedObjectData)
showSuccess('Object created successfully')
return newObject
@@ -207,7 +228,12 @@ const NewObjectForm = ({ type, style, defaultValues = {}, children }) => {
style={style}
onValuesChange={(_changedValues, allFormValues) => {
// Calculate computed values based on current form data
- const currentFormData = merge({}, objectData || {}, allFormValues)
+ const currentFormData = mergeWith(
+ {},
+ objectData || {},
+ allFormValues,
+ arrayReplaceCustomizer
+ )
const computedEntries = calculateComputedValues(currentFormData, model)
if (Array.isArray(computedEntries) && computedEntries.length > 0) {
@@ -229,9 +255,14 @@ const NewObjectForm = ({ type, style, defaultValues = {}, children }) => {
// Merge all values (user input + computed values)
const computedValuesObject = buildObjectFromEntries(computedEntries)
- const allValues = merge({}, allFormValues, computedValuesObject)
+ const allValues = mergeWith(
+ {},
+ allFormValues,
+ computedValuesObject,
+ arrayReplaceCustomizer
+ )
setObjectData((prev) => {
- return merge({}, prev, allValues)
+ return mergeWith({}, prev, allValues, arrayReplaceCustomizer)
})
}}
>
diff --git a/src/components/Dashboard/common/ObjectForm.jsx b/src/components/Dashboard/common/ObjectForm.jsx
index bc708207..9e6c9e89 100644
--- a/src/components/Dashboard/common/ObjectForm.jsx
+++ b/src/components/Dashboard/common/ObjectForm.jsx
@@ -20,10 +20,13 @@ import { useLocation, useNavigate } from 'react-router-dom'
import PropTypes from 'prop-types'
import { useActions } from '../context/ActionsContext'
-const arrayReplaceCustomizer = (objValue, srcValue) => {
+const arrayReplaceCustomizer = (objValue, srcValue, key) => {
if (Array.isArray(srcValue)) {
return srcValue
}
+ if (key === 'permissions' && srcValue !== undefined) {
+ return srcValue
+ }
}
const getUserId = (user) => user?._id || user
@@ -339,10 +342,11 @@ const ObjectForm = forwardRef(
model
)
const computedValuesObject = buildObjectFromEntries(computedEntries)
- const resetFormData = merge(
+ const resetFormData = mergeWith(
{},
serverObjectData.current,
- computedValuesObject
+ computedValuesObject,
+ arrayReplaceCustomizer
)
setIsEditing(false)
isEditingRef.current = false
@@ -470,7 +474,12 @@ const ObjectForm = forwardRef(
// Calculate and set computed values on initial load
const computedEntries = calculateComputedValues(data, model)
const computedValuesObject = buildObjectFromEntries(computedEntries)
- const initialFormData = merge({}, data, computedValuesObject)
+ const initialFormData = mergeWith(
+ {},
+ data,
+ computedValuesObject,
+ arrayReplaceCustomizer
+ )
setObjectData({ ...initialFormData, _isEditing: isEditingRef.current })
form.setFieldsValue(initialFormData)
setFetchLoading(false)
@@ -828,9 +837,9 @@ const ObjectForm = forwardRef(
const mergedFormValues = mergeWith(
{},
allFormValues,
+ computedValuesObject,
arrayReplaceCustomizer
)
- merge(mergedFormValues, computedValuesObject)
mergedFormValues._isEditing = isEditingRef.current
diff --git a/src/components/Dashboard/common/PermissionCheckbox.jsx b/src/components/Dashboard/common/PermissionCheckbox.jsx
index 756def49..d831c72a 100644
--- a/src/components/Dashboard/common/PermissionCheckbox.jsx
+++ b/src/components/Dashboard/common/PermissionCheckbox.jsx
@@ -11,11 +11,18 @@ const ICON_STYLE = {
const PermissionCheckbox = ({
checked = false,
indeterminate = false,
+ mixed = false,
disabled = false,
onChange
}) => {
const { token } = theme.useToken()
- const state = indeterminate ? 'inherit' : checked ? 'allow' : 'deny'
+ const state = mixed
+ ? 'mixed'
+ : indeterminate
+ ? 'inherit'
+ : checked
+ ? 'allow'
+ : 'deny'
return (
)
}
@@ -53,6 +66,7 @@ const PermissionCheckbox = ({
PermissionCheckbox.propTypes = {
checked: PropTypes.bool,
indeterminate: PropTypes.bool,
+ mixed: PropTypes.bool,
disabled: PropTypes.bool,
onChange: PropTypes.func
}
diff --git a/src/components/Icons/PermissionSettingsIcon.jsx b/src/components/Icons/PermissionSettingIcon.jsx
similarity index 67%
rename from src/components/Icons/PermissionSettingsIcon.jsx
rename to src/components/Icons/PermissionSettingIcon.jsx
index d23ceb15..4c245c89 100644
--- a/src/components/Icons/PermissionSettingsIcon.jsx
+++ b/src/components/Icons/PermissionSettingIcon.jsx
@@ -1,8 +1,8 @@
import Icon from '@ant-design/icons'
import CustomIconSvg from '../../../assets/icons/permissionsettingsicon.svg?react'
-const PermissionSettingsIcon = (props) => (
+const PermissionSettingIcon = (props) => (
)
-export default PermissionSettingsIcon
+export default PermissionSettingIcon
diff --git a/src/components/Icons/PersonGroupIcon.jsx b/src/components/Icons/PersonGroupIcon.jsx
new file mode 100644
index 00000000..5394521c
--- /dev/null
+++ b/src/components/Icons/PersonGroupIcon.jsx
@@ -0,0 +1,6 @@
+import Icon from '@ant-design/icons'
+import CustomIconSvg from '../../../assets/icons/persongroupicon.svg?react'
+
+const PersonGroupIcon = (props) =>
+
+export default PersonGroupIcon
diff --git a/src/components/Icons/sidebarIconMap.jsx b/src/components/Icons/sidebarIconMap.jsx
index 875c858a..c31016f0 100644
--- a/src/components/Icons/sidebarIconMap.jsx
+++ b/src/components/Icons/sidebarIconMap.jsx
@@ -38,7 +38,8 @@ import SettingsIcon from './SettingsIcon'
import AuditLogIcon from './AuditLogIcon'
import DeveloperIcon from './DeveloperIcon'
import PersonIcon from './PersonIcon'
-import PermissionSettingsIcon from './PermissionSettingsIcon'
+import PersonGroupIcon from './PersonGroupIcon'
+import PermissionSettingIcon from './PermissionSettingIcon'
import HostIcon from './HostIcon'
import DocumentPrinterIcon from './DocumentPrinterIcon'
import DocumentTemplateIcon from './DocumentTemplateIcon'
@@ -96,7 +97,8 @@ const sidebarIconMap = {
auditLog: ,
developer: ,
person: ,
- permissionSettings: ,
+ userGroup: ,
+ permissionSetting: ,
host: ,
documentPrinter: ,
documentTemplate: ,
diff --git a/src/database/ObjectModels.js b/src/database/ObjectModels.js
index 42041d84..b4a74897 100644
--- a/src/database/ObjectModels.js
+++ b/src/database/ObjectModels.js
@@ -31,7 +31,8 @@ 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 { UserGroup } from './models/UserGroup.js'
+import { PermissionSetting } from './models/PermissionSetting.js'
import { AppPassword } from './models/AppPassword.js'
import { NoteType } from './models/NoteType'
import { Note } from './models/Note'
@@ -84,7 +85,8 @@ export const objectModels = [
Shipment,
AuditLog,
User,
- PermissionSettings,
+ UserGroup,
+ PermissionSetting,
AppPassword,
NoteType,
Note,
@@ -138,7 +140,8 @@ export {
Shipment,
AuditLog,
User,
- PermissionSettings,
+ UserGroup,
+ PermissionSetting,
AppPassword,
NoteType,
Note,
diff --git a/src/database/models/PermissionSettings.js b/src/database/models/PermissionSetting.js
similarity index 86%
rename from src/database/models/PermissionSettings.js
rename to src/database/models/PermissionSetting.js
index 5b13274e..b38510da 100644
--- a/src/database/models/PermissionSettings.js
+++ b/src/database/models/PermissionSetting.js
@@ -1,24 +1,24 @@
import { createElement, lazy } from 'react'
-const PermissionSettingsInfo = lazy(
+const PermissionSettingInfo = lazy(
() =>
- import('../../components/Dashboard/Management/PermissionSettings/PermissionSettingsInfo')
+ import('../../components/Dashboard/Management/PermissionSettings/PermissionSettingInfo')
)
-import PermissionSettingsIcon from '../../components/Icons/PermissionSettingsIcon'
+import PermissionSettingIcon from '../../components/Icons/PermissionSettingIcon'
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',
+export const PermissionSetting = {
+ name: 'permissionSetting',
+ label: 'Permission Setting',
labelPlural: 'Permission Settings',
url: '/dashboard/management/permissionsettings',
prefix: 'PMS',
endpoint: 'permissionsettings',
- icon: PermissionSettingsIcon,
+ icon: PermissionSettingIcon,
actions: [
{
name: 'info',
@@ -73,7 +73,7 @@ export const PermissionSettings = {
pages: [
{
name: 'info',
- content: () => createElement(PermissionSettingsInfo)
+ content: () => createElement(PermissionSettingInfo)
}
],
columns: ['_reference', 'name', 'createdAt', 'updatedAt'],
@@ -85,7 +85,7 @@ export const PermissionSettings = {
label: 'ID',
columnFixed: 'left',
type: 'id',
- objectType: 'permissionSettings',
+ objectType: 'permissionSetting',
showCopy: true,
columnWidth: 140
},
@@ -101,7 +101,7 @@ export const PermissionSettings = {
label: 'Reference',
type: 'reference',
columnFixed: 'left',
- objectType: 'permissionSettings',
+ objectType: 'permissionSetting',
showCopy: true,
readOnly: true,
columnWidth: 180
diff --git a/src/database/models/User.js b/src/database/models/User.js
index 92d4985c..37b39322 100644
--- a/src/database/models/User.js
+++ b/src/database/models/User.js
@@ -13,6 +13,7 @@ import EditIcon from '../../components/Icons/EditIcon'
import CheckIcon from '../../components/Icons/CheckIcon'
import XMarkIcon from '../../components/Icons/XMarkIcon'
import PlusIcon from '../../components/Icons/PlusIcon'
+import { calculateUserPermissions } from '../permissions.js'
export const User = {
name: 'user',
@@ -92,6 +93,7 @@ export const User = {
'name',
'username',
'email',
+ 'groups',
'permissionSettings',
'createdAt',
'updatedAt'
@@ -102,6 +104,7 @@ export const User = {
'firstName',
'lastName',
'email',
+ 'groups',
'permissionSettings',
'createdAt',
'updatedAt',
@@ -178,11 +181,19 @@ export const User = {
columnWidth: 300,
type: 'email'
},
+ {
+ name: 'groups',
+ label: 'Groups',
+ type: 'objectList',
+ objectType: 'userGroup',
+ span: 2,
+ columnWidth: 250
+ },
{
name: 'permissionSettings',
label: 'Permission Settings',
type: 'objectList',
- objectType: 'permissionSettings',
+ objectType: 'permissionSetting',
span: 2,
columnWidth: 250
},
@@ -193,7 +204,8 @@ export const User = {
inherit: false,
readOnly: true,
span: 2,
- columnWidth: 200
+ columnWidth: 200,
+ value: calculateUserPermissions
},
{
name: 'profileImage',
diff --git a/src/database/models/UserGroup.js b/src/database/models/UserGroup.js
new file mode 100644
index 00000000..2fe9a53a
--- /dev/null
+++ b/src/database/models/UserGroup.js
@@ -0,0 +1,142 @@
+import { createElement, lazy } from 'react'
+
+const UserGroupInfo = lazy(
+ () => import('../../components/Dashboard/Management/UserGroups/UserGroupInfo')
+)
+import PersonGroupIcon from '../../components/Icons/PersonGroupIcon'
+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'
+import { calculateUserGroupPermissions } from '../permissions.js'
+
+export const UserGroup = {
+ name: 'userGroup',
+ label: 'User Group',
+ labelPlural: 'User Groups',
+ url: '/dashboard/management/usergroups',
+ prefix: 'UGP',
+ icon: PersonGroupIcon,
+ 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(UserGroupInfo)
+ }
+ ],
+ columns: ['_reference', 'name', 'permissionSettings', 'createdAt', 'updatedAt'],
+ filters: ['name', 'permissionSettings', 'createdAt', 'updatedAt', '_reference'],
+ sorters: ['name', 'createdAt', '_id', 'updatedAt'],
+ properties: [
+ {
+ name: '_id',
+ label: 'ID',
+ columnFixed: 'left',
+ type: 'id',
+ objectType: 'userGroup',
+ showCopy: true,
+ columnWidth: 140
+ },
+ {
+ name: 'createdAt',
+ label: 'Created At',
+ type: 'dateTime',
+ readOnly: true,
+ columnWidth: 175
+ },
+ {
+ name: '_reference',
+ label: 'Reference',
+ columnFixed: 'left',
+ type: 'reference',
+ objectType: 'userGroup',
+ 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: 'permissionSettings',
+ label: 'Permission Settings',
+ type: 'objectList',
+ objectType: 'permissionSetting',
+ span: 2,
+ columnWidth: 250
+ },
+ {
+ name: 'permissions',
+ label: 'Permissions',
+ type: 'permissionsMatrix',
+ inherit: false,
+ readOnly: true,
+ span: 2,
+ columnWidth: 200,
+ value: calculateUserGroupPermissions
+ }
+ ]
+}
diff --git a/src/database/permissions.js b/src/database/permissions.js
new file mode 100644
index 00000000..52cbf461
--- /dev/null
+++ b/src/database/permissions.js
@@ -0,0 +1,38 @@
+export const applyPermissionSettingsList = (settingsList = []) => {
+ const permissions = {}
+
+ for (const setting of settingsList) {
+ const matrix = setting?.permissions
+ if (!matrix || typeof matrix !== 'object' || Array.isArray(matrix)) {
+ continue
+ }
+
+ for (const [modelName, actions] of Object.entries(matrix)) {
+ if (!actions || typeof actions !== 'object' || Array.isArray(actions)) {
+ continue
+ }
+ if (!permissions[modelName]) {
+ permissions[modelName] = {}
+ }
+ for (const [actionName, value] of Object.entries(actions)) {
+ if (value === true || value === false) {
+ permissions[modelName][actionName] = value
+ }
+ }
+ if (Object.keys(permissions[modelName]).length === 0) {
+ delete permissions[modelName]
+ }
+ }
+ }
+
+ return permissions
+}
+
+export const calculateUserPermissions = (objectData = {}) =>
+ applyPermissionSettingsList([
+ ...(objectData.groups || []),
+ ...(objectData.permissionSettings || [])
+ ])
+
+export const calculateUserGroupPermissions = (objectData = {}) =>
+ applyPermissionSettingsList(objectData.permissionSettings)
diff --git a/src/database/sidebars/management.js b/src/database/sidebars/management.js
index a482e579..d81bf317 100644
--- a/src/database/sidebars/management.js
+++ b/src/database/sidebars/management.js
@@ -125,9 +125,15 @@ const managementSidebarItems = [
label: 'Users',
path: '/dashboard/management/users'
},
+ {
+ key: 'userGroups',
+ iconKey: 'userGroup',
+ label: 'User Groups',
+ path: '/dashboard/management/usergroups'
+ },
{
key: 'permissionSettings',
- iconKey: 'permissionSettings',
+ iconKey: 'permissionSetting',
label: 'Permission Settings',
path: '/dashboard/management/permissionsettings'
},
diff --git a/src/routes/ManagementRoutes.jsx b/src/routes/ManagementRoutes.jsx
index 5206275f..b7c2a5a2 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 UserGroups = lazy(
+ () => import('../components/Dashboard/Management/UserGroups.jsx')
+)
const PermissionSettings = lazy(
() => import('../components/Dashboard/Management/PermissionSettings.jsx')
)
@@ -95,6 +98,11 @@ const ManagementRoutes = [
} />,
} />,
} />,
+ }
+ />,