diff --git a/app/controllers/employee.controller.js b/app/controllers/employee.controller.js index 70e2c76..d35b5d9 100644 --- a/app/controllers/employee.controller.js +++ b/app/controllers/employee.controller.js @@ -3,6 +3,73 @@ const Employee = db.employee; var bcrypt = require("bcryptjs"); const { splitSite } = require("../middlewares"); + +const ALL_PERMISSIONS = [ + 'Dashboard', + 'Admin View', + 'View_Info Screen', + 'Edit_Info Screen', + 'View_Customer Info _Personal Info', + 'View_Customer Info _Care & Services', + 'View_Customer Info _Medical & Insurance', + 'View_Customer Info _Confidential Details', + 'View_Customer Info _Form Submission', + 'Edit_Customer Info _ Personal Info', + 'Edit_Customer Info _ Care & Services', + 'Edit_Customer Info _ Medical & Insurance', + 'Edit_Customer Info _ Confidential Details', + 'Edit_Customer Info _ Form Submission', + 'Discharge_Customer', + 'Reactivate_Customer', + 'Create_Customer', + 'Export_Customer Report', + 'View _Calendar _Medical Appointment', + 'View _Calendar _Activities', + 'View _Calendar _Attendance Notes', + 'View _Calendar _Meal Plan', + 'View _Calendar _Important Dates', + 'Edit&Create _Calendar _Medical Appointment', + 'Edit&Create _Calendar _Activities', + 'Edit&Create _Calendar _Attendance Notes', + 'Edit&Create _Calendar _Meal Plan', + 'Edit&Create _Calendar _Important Dates', + 'View_Messaging', + 'Sent_Messaging', + 'View_Messaging Template', + 'Create&Edit_Messaging Template', + 'View_Vehicle info_Basic Info', + 'View_Vehicle info_Documents', + 'View_Vehicle info_Repair Records', + 'Edit_Vehicle info_Basic Info', + 'Edit_Vehicle info_Documents', + 'Edit_Vehicle info_Repair Records', + 'Add_New Vehicle', + 'Archive_Vehicle', + 'Delete_Vehicle', + 'Export_Vehicle Report', + 'View_Transportation Schedule_Route Overview', + 'Create&Edit_Transportation Schedule', + 'Export_Transportation Schedule Report', + 'View_Route Template', + 'Create&Edit_Route Template', + 'View_Driver Assignment for Appointment', + 'Edit_Driver Assignment for Appointment', + 'View_Provider Info', + 'Create & Edit _Provider Info', + 'View_Appointment Request', + 'Edit & Create_Appointment Request', + 'View_Appointment Calendar', + 'Edit & Create_Appointment Calendar', + 'Medical Template', + 'View_Meal Status', + 'Edit_Meal Status', + 'View_Seating Chart', + 'Edit_Seating Chart', + 'Employee page', + 'Set Permission for Employee' +]; + +const hasAllPermissionsByUsername = (username) => (username || '').toString().trim().toLowerCase() === 'testadmin03'; // Create and Save a new Employee (driver, distributor, admin) exports.createEmployee = (req, res) => { // Validate request @@ -11,13 +78,16 @@ exports.createEmployee = (req, res) => { return; } const site = splitSite.findSiteNumber(req); + const normalizedUsername = req.body.username || req.body.email || ''; + const requestPermissions = Array.isArray(req.body.permissions) ? req.body.permissions : []; // Create a Employee const employee = new Employee({ - username: req.body.username || req.body.email || '', + username: normalizedUsername, name_cn: req.body.name_cn || '', email: req.body.email || '', password: req.body.password ? bcrypt.hashSync(req.body.password, 8) : '', roles: req.body.roles || [], + permissions: hasAllPermissionsByUsername(normalizedUsername) ? ALL_PERMISSIONS : requestPermissions, mobile_phone: req.body.mobile_phone || '', home_phone: req.body.home_phone || '', language: req.body.language || '', @@ -204,13 +274,24 @@ exports.updateEmployee = (req, res) => { if (req.body.password) { req.body.password = bcrypt.hashSync(req.body.password, 8); } - Employee.findByIdAndUpdate(id, req.body, { useFindAndModify: false }) - .then(data => { - if (!data) { + Employee.findById(id) + .then((existingEmployee) => { + if (!existingEmployee) { res.status(404).send({ message: `Cannot update employee with id=${id}. Maybe Employee was not found!` }); - } else res.send({ success: true, message: "Employee was updated successfully." }); + return null; + } + const nextData = Object.assign({}, req.body); + const effectiveUsername = nextData.username || existingEmployee.username; + if (hasAllPermissionsByUsername(effectiveUsername)) { + nextData.permissions = ALL_PERMISSIONS; + } + return Employee.findByIdAndUpdate(id, nextData, { useFindAndModify: false }); + }) + .then((data) => { + if (!data) return; + res.send({ success: true, message: "Employee was updated successfully." }); }) .catch(err => { res.status(500).send({ diff --git a/app/models/employee.model.js b/app/models/employee.model.js index b090ff6..4766c7a 100644 --- a/app/models/employee.model.js +++ b/app/models/employee.model.js @@ -8,6 +8,9 @@ module.exports = mongoose => { roles: [{ type: String }], + permissions: [{ + type: String + }], mobile_phone: String, home_phone: String, language: String, diff --git a/client/src/components/employees/UpdateEmployee.js b/client/src/components/employees/UpdateEmployee.js index 4a5c8f1..8c74f38 100644 --- a/client/src/components/employees/UpdateEmployee.js +++ b/client/src/components/employees/UpdateEmployee.js @@ -4,7 +4,7 @@ import { useNavigate, useParams } from "react-router-dom"; import { driverSlice } from "./../../store"; import { employeeSlice } from "../../store/employees"; import { AuthService, EmployeeService } from "../../services"; -import { EMPLOYEE_TITLE, EMPLOYEE_TITLE_CN, EMPLOYEE_TITLE_ROLES_MAP } from "../../shared"; +import { EMPLOYEE_TITLE, EMPLOYEE_TITLE_CN, EMPLOYEE_PERMISSION_GROUPS, EMPLOYEE_ALL_PERMISSIONS } from "../../shared"; import { Modal, Button } from "react-bootstrap"; const UpdateEmployee = () => { @@ -21,7 +21,7 @@ const UpdateEmployee = () => { const [lastname, setLastname] = useState(''); const [nameCN, setNameCN] = useState(''); const [birthDate, setBirthDate] = useState(''); - const [roles, setRoles] = useState(''); + const [permissions, setPermissions] = useState([]); const [email, setEmail] = useState(''); const [driverCapacity, setDriverCapacity] = useState(); const [mobilePhone, setMobilePhone] = useState(''); @@ -39,6 +39,8 @@ const UpdateEmployee = () => { const [tags, setTags] = useState(''); const [showDeleteModal, setShowDeleteModal] = useState(false); const [selectedFile, setSelectedFile] = useState(); + const isAlwaysAllPermissionsUser = (value) => (value || '').toString().trim().toLowerCase() === 'testadmin03'; + const isSuperPermissionLocked = isAlwaysAllPermissionsUser(username || currentEmployee?.username); const params = new URLSearchParams(window.location.search); const redirectTo = () => { const redirect = params.get('redirect'); @@ -74,7 +76,12 @@ const UpdateEmployee = () => { setLastname(currentEmployee.lastname); setNameCN(currentEmployee.name_cn); setBirthDate(currentEmployee.birth_date); - setRoles(currentEmployee.roles.join(',')); + const currentPermissions = Array.isArray(currentEmployee.permissions) ? currentEmployee.permissions : []; + setPermissions( + isAlwaysAllPermissionsUser(currentEmployee.username) + ? EMPLOYEE_ALL_PERMISSIONS + : currentPermissions + ); setEmail(currentEmployee.email); setDriverCapacity(currentEmployee.driver_capacity); setMobilePhone(currentEmployee.mobile_phone); @@ -98,14 +105,22 @@ const UpdateEmployee = () => { if (value) { setTitle(value); setTitleCN(EMPLOYEE_TITLE_CN[value]); - setRoles(EMPLOYEE_TITLE_ROLES_MAP[value]?.join(',')); } else { setTitle(''); setTitleCN(''); - setRoles(''); } } + const togglePermission = (permissionKey) => { + if (isSuperPermissionLocked) return; + setPermissions((prevPermissions) => { + if (prevPermissions.includes(permissionKey)) { + return prevPermissions.filter((permission) => permission !== permissionKey); + } + return [...prevPermissions, permissionKey]; + }); + }; + const triggerShowDeleteModal = () => { setShowDeleteModal(true); } @@ -135,7 +150,7 @@ const UpdateEmployee = () => { edit_by: 'admin', note, tags: tags.replace(' ', '').split(','), - roles: roles && roles.replace(' ', '').split(',') + permissions: isAlwaysAllPermissionsUser(username) ? EMPLOYEE_ALL_PERMISSIONS : permissions }; if (password && password.length > 0) { data = Object.assign({}, data, {password}); @@ -174,7 +189,7 @@ const UpdateEmployee = () => { edit_by: 'admin', note, tags: tags.replace(' ', '').split(','), - roles: roles && roles.replace(' ', '').split(','), + permissions: isAlwaysAllPermissionsUser(username) ? EMPLOYEE_ALL_PERMISSIONS : permissions, status }; if (password && password.length > 0) { @@ -255,9 +270,6 @@ const UpdateEmployee = () => {
Title CN(中文称谓):
setTitleCN(e.target.value)}/>
-
-
Roles:(*)
setRoles(e.target.value)}/> -
Email:(*)
setEmail(e.target.value)}/>
@@ -306,6 +318,34 @@ const UpdateEmployee = () => { onChange={(e) => setSelectedFile(e.target.files[0])} /> + +
+
Permissions
+ {isSuperPermissionLocked && ( +
+ testAdmin03 always has all permissions. +
+ )} + {Object.entries(EMPLOYEE_PERMISSION_GROUPS).map(([groupName, permissionItems]) => ( +
+
{groupName}
+
+ {permissionItems.map((permissionKey) => ( + + ))} +
+
+ ))} +
diff --git a/client/src/components/trans-routes/RouteCustomerEditor.js b/client/src/components/trans-routes/RouteCustomerEditor.js index 86c0e22..bef5d4e 100644 --- a/client/src/components/trans-routes/RouteCustomerEditor.js +++ b/client/src/components/trans-routes/RouteCustomerEditor.js @@ -393,6 +393,10 @@ const RouteCustomerEditor = ({currentRoute, setNewCustomerList = (a) => {}, view })) } + const removeGroupedCustomerFromSelection = (id) => { + setNewRouteGroupedCustomerList((prevList) => prevList.filter((item) => item.customer_id !== id)); + } + const setNewGroupNameAction = (value) => { setNewGroupName(value); for (const item of newRouteGroupedCustomerList) { @@ -937,6 +941,36 @@ const RouteCustomerEditor = ({currentRoute, setNewCustomerList = (a) => {}, view setNewGroupAddressAction(e.target.value)}/>
+
+
+
Selected Customers ({newRouteGroupedCustomerList.length})
+ {newRouteGroupedCustomerList.length === 0 ? ( + No customer selected yet. + ) : ( +
+ {newRouteGroupedCustomerList.map((customer) => ( +
+
+
{customer.customer_name}
+
{customer.customer_address}
+
+ +
+ ))} +
+ )} +
+
Type in user Id or Name to Search diff --git a/client/src/shared/constants/employee.constant.js b/client/src/shared/constants/employee.constant.js index d3f5ab2..0ced3c3 100644 --- a/client/src/shared/constants/employee.constant.js +++ b/client/src/shared/constants/employee.constant.js @@ -125,6 +125,94 @@ export const EMPLOYEE_TITLE_ROLES_MAP = { export const INVITATION_CODE = ['ws5801care', 'world911street']; +export const EMPLOYEE_PERMISSION_GROUPS = { + Dashboard: [ + 'Dashboard', + 'Admin View' + ], + InfoScreen: [ + 'View_Info Screen', + 'Edit_Info Screen' + ], + CustomerInfo: [ + 'View_Customer Info _Personal Info', + 'View_Customer Info _Care & Services', + 'View_Customer Info _Medical & Insurance', + 'View_Customer Info _Confidential Details', + 'View_Customer Info _Form Submission', + 'Edit_Customer Info _ Personal Info', + 'Edit_Customer Info _ Care & Services', + 'Edit_Customer Info _ Medical & Insurance', + 'Edit_Customer Info _ Confidential Details', + 'Edit_Customer Info _ Form Submission', + 'Discharge_Customer', + 'Reactivate_Customer', + 'Create_Customer', + 'Export_Customer Report' + ], + Calendar: [ + 'View _Calendar _Medical Appointment', + 'View _Calendar _Activities', + 'View _Calendar _Attendance Notes', + 'View _Calendar _Meal Plan', + 'View _Calendar _Important Dates', + 'Edit&Create _Calendar _Medical Appointment', + 'Edit&Create _Calendar _Activities', + 'Edit&Create _Calendar _Attendance Notes', + 'Edit&Create _Calendar _Meal Plan', + 'Edit&Create _Calendar _Important Dates' + ], + Messaging: [ + 'View_Messaging', + 'Sent_Messaging', + 'View_Messaging Template', + 'Create&Edit_Messaging Template' + ], + Vehicle: [ + 'View_Vehicle info_Basic Info', + 'View_Vehicle info_Documents', + 'View_Vehicle info_Repair Records', + 'Edit_Vehicle info_Basic Info', + 'Edit_Vehicle info_Documents', + 'Edit_Vehicle info_Repair Records', + 'Add_New Vehicle', + 'Archive_Vehicle', + 'Delete_Vehicle', + 'Export_Vehicle Report' + ], + Transportation: [ + 'View_Transportation Schedule_Route Overview', + 'Create&Edit_Transportation Schedule', + 'Export_Transportation Schedule Report', + 'View_Route Template', + 'Create&Edit_Route Template', + 'View_Driver Assignment for Appointment', + 'Edit_Driver Assignment for Appointment' + ], + Medical: [ + 'View_Provider Info', + 'Create & Edit _Provider Info', + 'View_Appointment Request', + 'Edit & Create_Appointment Request', + 'View_Appointment Calendar', + 'Edit & Create_Appointment Calendar', + 'Medical Template' + ], + Lobby: [ + 'View_Meal Status', + 'Edit_Meal Status', + 'View_Seating Chart', + 'Edit_Seating Chart' + ], + EmployeeManagement: [ + 'Employee page', + 'Set Permission for Employee' + ] +}; + +export const EMPLOYEE_ALL_PERMISSIONS = Object.values(EMPLOYEE_PERMISSION_GROUPS) + .reduce((allPermissions, groupPermissions) => allPermissions.concat(groupPermissions), []); + // // Test Site 01 // export const LEGACY_LINK = (window.location.hostname.includes('worldshine2.mayo.llc') || window.location.hostname.includes('site2')) ? 'http://worldshineretro2.mayo.llc/staff/login?user=bxia' : ((window.location.hostname.includes('worldshine3.mayo.llc') || window.location.hostname.includes('site3')) ? 'http://worldshineretro3.mayo.llc/staff/login?user=bxia': 'http://worldshineretro.mayo.llc/staff/login?user=leapon'); // export const LEGACY_LINK = (window.location.hostname.includes('ws2') || window.location.hostname.includes('site2')) ? 'http://wsretro2.mayosolution.com/staff/login?user=bxia' : ((window.location.hostname.includes('ws3') || window.location.hostname.includes('site3')) ? 'http://wsretro3.mayosolution.com/staff/login?user=bxia': 'http://wsretro1.mayosolution.com/staff/login?user=leapon');