From fe13b651ed9d593a46b78faa42f9206b228de6ff Mon Sep 17 00:00:00 2001 From: Matt DiMeglio Date: Tue, 3 Jun 2025 18:52:40 -0400 Subject: [PATCH] Set Up bases of Roles Page --- components/common/TransferBox/TransferBox.jsx | 138 ++++++ components/common/TransferBox/index.js | 1 + components/common/index.js | 3 +- src/pages/Settings/Settings.jsx | 406 ++++++++---------- src/pages/Settings/helpers.js | 170 ++++++++ src/router/AppRouter.jsx | 77 +++- 6 files changed, 558 insertions(+), 237 deletions(-) create mode 100644 components/common/TransferBox/TransferBox.jsx create mode 100644 components/common/TransferBox/index.js create mode 100644 src/pages/Settings/helpers.js diff --git a/components/common/TransferBox/TransferBox.jsx b/components/common/TransferBox/TransferBox.jsx new file mode 100644 index 0000000..60cea62 --- /dev/null +++ b/components/common/TransferBox/TransferBox.jsx @@ -0,0 +1,138 @@ +import React, { useState } from 'react'; +import styled from '@emotion/styled'; +import CheckIcon from '@mui/icons-material/Check'; +import DoNotDisturbIcon from '@mui/icons-material/DoNotDisturb'; +import KeyboardArrowLeftIcon from '@mui/icons-material/KeyboardArrowLeft'; +import KeyboardArrowRightIcon from '@mui/icons-material/KeyboardArrowRight'; + +const CenterButton = styled('div')` + display: flex; + justify-content: center; + align-items: center; + width: 30px; + height: 30px; + border: 1px solid black; + border-radius: 5px; + margin: 5px; + cursor: ${({ buttonEnabled }) => (buttonEnabled ? 'pointer' : 'not-allowed')}; + transition: background-color 0.3s ease, color 0.3s ease; + opacity: ${({ buttonEnabled }) => (buttonEnabled ? 1 : 0.5)}; + + ${({ color, buttonEnabled }) => buttonEnabled && color && ` + &:hover { + background-color: ${color}; + color: white; + } + `} +`; + +export const TransferBox = ({ fields, leftGroup, rightGroup }) => { + const { + id, + exclusionList, + oldListLabel, + newListLabel + } = fields; + + const [itemSelected, setItemSelected] = useState({}); + const [containsSelection, setContainsSelection] = useState([]); + + return ( +
+
+
+

{oldListLabel}

+
+
+
+ {leftGroup?.map((j, index) => { + return ( +
+ {j?.value} +
+ ) + })} +
+
+
+
+ { console.log('clicked Right') }} + color='#A8A8A8' + buttonEnabled={itemSelected?.id} + > + + + { console.log('clicked Clear') }} + color='#FF6666' + buttonEnabled={containsSelection?.length > 0} + > + + + { console.log('clicked Save') }} + color='#00B33C' + buttonEnabled={containsSelection?.length > 0} + > + + + { console.log('clicked Left') }} + color='#A8A8A8' + buttonEnabled={itemSelected?.id} + > + + +
+
+
+

{newListLabel}

+
+
+
+ {rightGroup?.map((j, index) => { + return ( +
+ {j?.value} +
+ ) + })} +
+
+
+
+ ); +} \ No newline at end of file diff --git a/components/common/TransferBox/index.js b/components/common/TransferBox/index.js new file mode 100644 index 0000000..dcfddff --- /dev/null +++ b/components/common/TransferBox/index.js @@ -0,0 +1 @@ +export { TransferBox } from './TransferBox'; \ No newline at end of file diff --git a/components/common/index.js b/components/common/index.js index e36e79e..ccd04d7 100644 --- a/components/common/index.js +++ b/components/common/index.js @@ -1 +1,2 @@ -export { ToggleTabs } from './ToggleTabs'; \ No newline at end of file +export { ToggleTabs } from './ToggleTabs'; +export { TransferBox } from './TransferBox'; \ No newline at end of file diff --git a/src/pages/Settings/Settings.jsx b/src/pages/Settings/Settings.jsx index 5a409da..0b2d27d 100644 --- a/src/pages/Settings/Settings.jsx +++ b/src/pages/Settings/Settings.jsx @@ -3,7 +3,10 @@ import styled from '@emotion/styled'; import { Stack } from '@mui/material'; import EditIcon from '@mui/icons-material/Edit'; import EditOffIcon from '@mui/icons-material/EditOff'; -import { ToggleTabs, useLocalStore } from '@components'; +import CheckBoxOutlineBlankIcon from '@mui/icons-material/CheckBoxOutlineBlank'; +import CheckBoxIcon from '@mui/icons-material/CheckBox'; +import { ToggleTabs, TransferBox, useLocalStore } from '@components'; +import { settingsFields } from './helpers'; const OuterContainer = styled(Stack)` position: relative; @@ -57,7 +60,8 @@ const CardShell = styled('div')` const Card = styled('div')` background-color: #C7C7C7; - width: 1000px; + width: 50%; + min-width: 750px; padding: 20px; border-radius: 10px; margin: 5px; @@ -113,14 +117,16 @@ const InnerCard = styled('div')` flex-direction: column; padding: 10px; width: 50%; + + ${({ centered }) => centered && ` + align-items: center; + `} `; const InnerCardRow = styled('div')` display: flex; flex-direction: row; - justify-content: space-between; padding: 5px; - align-items: center; `; const InnerCardRowLabel = styled('label')` @@ -169,114 +175,13 @@ const FormInputButton = styled('input')` border-radius: 4px; `; -const settingsFields = [ - { - id: 'deptInfo', - title: 'Information', - tab: 'department', - cards: [ - { - id: 'companyInfo', - label: 'Company Information', - fields: [ - { - id: 'company', - label: 'Company Name', - type: 'text', - readOnly: true - }, - { - id: 'billing_address', - label: 'Billing Address', - type: 'text' - }, - { - id: 'town', - label: 'Town', - type: 'text' - }, - { - id: 'state', - label: 'State', - type: 'text' - }, - { - id: 'postal', - label: 'Postal Code', - type: 'text' - }, - { - id: 'country', - label: 'Country', - type: 'text' - }, - { - id: 'phone', - label: 'Phone Number', - type: 'phone' - }, - { - id: 'display_time', - label: 'Display Time', - type: 'select', - options: [ - { label: '12 Hour AM/PM', value: '12' }, - { label: '24 Hour', value: '24' } - ] - }, - { - id: 'start_day', - label: 'Calendar Start Day', - type: 'select', - options: [ - { label: 'Sunday', value: 'sunday' }, - { label: 'Monday', value: 'monday' } - ] - } - ] - } - ] - }, - { - id: 'deptPerms', - title: 'Permissions', - tab: 'department', - cards: [] - }, - { - id: 'deptMgrs', - title: 'Managers', - tab: 'department', - cards: [] - }, - { - id: 'deptSubs', - title: 'Subscriptions', - tab: 'department', - cards: [] - }, - { - id: 'personalInfo', - title: 'Information', - tab: 'personal', - cards: [] - }, - { - id: 'personalNoti', - title: 'Notifications', - tab: 'personal', - cards: [] - }, - { - id: 'deptNoti', - title: 'Notifications', - tab: 'department', - cards: [] - } -]; - export const Settings = () => { const { user, department, setDepartment } = useLocalStore(); + + const isAdministrator = user?.administrator; + const isManager = user?.manager; + const isScheduler = user?.scheduler; + const originalDepartmentRef = useRef(department); const [formValues, setFormValues] = useState(() => { const initial = {}; @@ -301,6 +206,7 @@ export const Settings = () => { } ]; + const [windowWidth, setWindowWidth] = useState(window.innerWidth); const [editMode, setEditMode] = useState(null); const [tabValue, setTabValue] = useState(tabs[0]); const [pageValue, setPageValue] = useState({}); @@ -354,6 +260,18 @@ export const Settings = () => { document.title = 'ShiftSync | Settings'; }, []); + useEffect(() => { + const handleResize = () => setWindowWidth(window.innerWidth); + window.addEventListener('resize', handleResize); + + // Initial trigger (in case something else needs it) + handleResize(); + + return () => { + window.removeEventListener('resize', handleResize); + }; + }, []); + useEffect(() => { if (pageValue?.id && pageRefs.current[pageValue.id]) { const el = pageRefs.current[pageValue.id]; @@ -364,7 +282,7 @@ export const Settings = () => { width: rect.width }); } - }, [pageValue?.id]); + }, [pageValue?.id, windowWidth, window.innerHeight]); useEffect(() => { setPageValue(settingsFields?.filter((field) => field?.tab === tabValue?.value)[0]); @@ -372,7 +290,7 @@ export const Settings = () => { return (
- {user?.administrator ? ( + {user?.administrator || user?.manager ? ( { ) : null } - {settingsFields?.filter((field) => field?.tab === tabValue?.value)?.map((field) => { + {settingsFields?.filter((field) => field?.tab === tabValue?.value)?.filter((field) => { + return ((field?.accessRequired === 'administrator' && isAdministrator) || + (field?.accessRequired === 'manager' && isManager) || + (field?.accessRequired === 'scheduler' && isScheduler) || + (field?.accessRequired === 'user' || !field?.accessRequired)) + })?.map((field) => { return ( { {pageValue?.cards?.map((card) => { - return ( - - - {card?.label} - - - -

- Employee Count: {department?.employeeCount} -

-

- Subscription Expiration: {department?.subscriptionExpiration} -

-
- -
{ - e.preventDefault(); - onSubmit(changedFields); - }} - > - - {editMode === card?.id ? ( - setEditMode(null)}> - -

View

-
- ) : ( - setEditMode(card?.id)}> - -

Edit

-
- )} -
- - - {card?.fields?.map((field) => { - let fieldType; - if (field?.type === 'text') { - fieldType = handleChange(e, 'text')} - />; - } else if (field?.type === 'phone') { - fieldType = handleChange(e, 'phone')} - pattern="\(\d{3}\) \d{3}-\d{4}" - maxlength="14" - /> - } else if (field?.type === 'select') { - fieldType = ( - - {field?.options?.map((option) => { - const inputId = `${field.id}-${option.value}`; - return ( - - handleChange(e, 'text')} - /> - - {option?.label} - - - ) - })} - + {card?.label} + + {card?.fields?.find((field) => field?.type === 'header') !== undefined && ( +
+ + + {card?.fields?.map((field) => { + return field?.type === 'header' && ( + +

+ {field?.label}: +

+

+ {department[field?.id]} +

+
) - } - return ( - - - {field?.label}: - - {fieldType} - - ) - })} - - - {card?.id === editMode && ( - - - + })} +
+
)} - -
- ) + +
{ + e.preventDefault(); + onSubmit(changedFields); + }} + > + + {!card?.removeEdit && ( + editMode === card?.id ? ( + setEditMode(null)}> + +

View

+
+ ) : ( + setEditMode(card?.id)}> + +

Edit

+
+ ) + )} +
+ + f.type === 'transfer')} + > + {card?.fields?.map((field) => { + let fieldType; + if (field?.type === 'text') { + fieldType = handleChange(e, 'text')} + />; + } else if (field?.type === 'phone') { + fieldType = handleChange(e, 'phone')} + pattern="\(\d{3}\) \d{3}-\d{4}" + maxlength="14" + /> + } else if (field?.type === 'select') { + fieldType = ( + + {field?.options?.map((option) => { + const inputId = `${field.id}-${option.value}`; + return ( + + handleChange(e, 'text')} + /> + + {option?.label} + + + ) + })} + + ) + } else if (field?.type === 'transfer') { + fieldType = + } + return field?.type !== 'header' && ( + + {field?.label ? ( + + {field?.label}: + + ) : null } + {fieldType} + + ) + })} + + + {card?.id === editMode && ( + + + + )} +
+ + ) + } + return null; })}
diff --git a/src/pages/Settings/helpers.js b/src/pages/Settings/helpers.js new file mode 100644 index 0000000..1a5b1c0 --- /dev/null +++ b/src/pages/Settings/helpers.js @@ -0,0 +1,170 @@ +export const settingsFields = [ + { + id: 'deptInfo', + title: 'Information', + tab: 'department', + cards: [ + { + id: 'companyInfo', + label: 'Company Information', + fields: [ + { + id: 'employee_count', + label: 'Employee Count', + type: 'header', + readOnly: true + }, + { + id: 'subs_expiration', + label: 'Subscription Expiration', + type: 'header', + readOnly: true + }, + { + id: 'company', + label: 'Company Name', + type: 'text', + readOnly: true + }, + { + id: 'billing_address', + label: 'Billing Address', + type: 'text' + }, + { + id: 'town', + label: 'Town', + type: 'text' + }, + { + id: 'state', + label: 'State', + type: 'text' + }, + { + id: 'postal', + label: 'Postal Code', + type: 'text' + }, + { + id: 'country', + label: 'Country', + type: 'text' + }, + { + id: 'phone', + label: 'Phone Number', + type: 'phone' + }, + { + id: 'display_time', + label: 'Display Time', + type: 'select', + options: [ + { label: '12 Hour AM/PM', value: '12' }, + { label: '24 Hour', value: '24' } + ] + }, + { + id: 'start_day', + label: 'Calendar Start Day', + type: 'select', + options: [ + { label: 'Sunday', value: 'sunday' }, + { label: 'Monday', value: 'monday' } + ] + } + ], + accessRequired: 'administrator' + } + ], + accessRequired: 'administrator' + }, + { + id: 'deptRoles', + title: 'Roles', + tab: 'department', + cards: [ + { + id: 'companyAdmins', + label: 'Company Administrators', + fields: [ + { + id: 'administrators', + type: 'transfer', + exclusionList: [], + origList: 'users', + oldListLabel: 'Users', + newListLabel: 'Administrators' + }, + ], + accessRequired: 'administrator', + removeEdit: true + }, + { + id: 'companyMgrs', + label: 'Company Managers', + fields: [ + { + id: 'managers', + type: 'transfer', + exclusionList: [ + 'administrators' + ], + origList: 'users', + oldListLabel: 'Users', + newListLabel: 'Managers' + }, + ], + accessRequired: 'administrator', + removeEdit: true + }, + { + id: 'companySched', + label: 'Company Schedulers', + fields: [ + { + id: 'schedulers', + type: 'transfer', + exclusionList: [ + 'administrators', + 'managers' + ], + origList: 'users', + oldListLabel: 'Users', + newListLabel: 'Schedulers' + }, + ], + accessRequired: 'manager', + removeEdit: true + } + ], + accessRequired: 'manager' + }, + { + id: 'deptSubs', + title: 'Subscriptions', + tab: 'department', + cards: [], + accessRequired: 'administrator' + }, + { + id: 'personalInfo', + title: 'Information', + tab: 'personal', + cards: [] + }, + { + id: 'personalNoti', + title: 'Notifications', + tab: 'personal', + cards: [] + }, + { + id: 'deptNoti', + title: 'Notifications', + tab: 'department', + cards: [], + accessRequired: 'administrator' + } +]; \ No newline at end of file diff --git a/src/router/AppRouter.jsx b/src/router/AppRouter.jsx index 888dfde..d2ec69c 100644 --- a/src/router/AppRouter.jsx +++ b/src/router/AppRouter.jsx @@ -19,26 +19,85 @@ const dept = { company_logo: '', employee_count: 1, subscription_expiration: '10/01/2025', - schedulers: [], - managers: [], + schedulers: [3], + managers: [2], administrators: [1] }; +const users = [ + { + id: 1, + first_name: 'ShiftSync-Administrator', + last_name: 'Test-User', + email: 'testuserA@shift-sync.com', + is_ss_admin: false + }, + { + id: 2, + first_name: 'ShiftSync-Manager', + last_name: 'Test-User', + email: 'testuserM@shift-sync.com', + is_ss_admin: false + }, + { + id: 3, + first_name: 'ShiftSync-Scheduler', + last_name: 'Test-User', + email: 'testuserS@shift-sync.com', + is_ss_admin: false + }, + { + id: 4, + first_name: 'ShiftSync-User', + last_name: 'Test-User', + email: 'testuserU@shift-sync.com', + is_ss_admin: false + }, +] + const AppRouter = () => { const { user, setUser, setDepartment } = useLocalStore(); const [userChanged, setUserChanged] = useState(false); useEffect(() => { - setDepartment(dept); + // await call for getting the count of employees and any other calls to db. + const employee_count = 1; + const subs_expiration = '10/22/2025'; setUser({ - id: 1, - first_name: 'ShiftSync-Manager', - last_name: 'Test-User', - email: 'testuser@shift-sync.com', + ...users[0], scheduler: dept?.schedulers?.includes(1), manager: dept?.managers?.includes(1), - administrator: dept?.administrators?.includes(1), - is_ss_admin: false + administrator: dept?.administrators?.includes(1) + }); + const newAdministrators = dept?.administrators?.map((admin) => { + const user = users?.find((user) => { + return user?.id === admin; + }); + return { id: user?.id, value: `${user?.last_name}, ${user?.first_name}` }; + }); + const newManagers = dept?.managers?.map((manager) => { + const user = users?.find((user) => { + return user?.id === manager; + }); + return { id: user?.id, value: `${user?.last_name}, ${user?.first_name}` }; + }); + const newSchedulers = dept?.schedulers?.map((scheduler) => { + const user = users?.find((user) => { + return user?.id === scheduler; + }); + return { id: user?.id, value: `${user?.last_name}, ${user?.first_name}` }; + }); + const newUsers = users?.map((user) => { + return { id: user?.id, value: `${user?.last_name}, ${user?.first_name}` }; + }); + setDepartment({ + ...dept, + users: newUsers, + schedulers: newSchedulers, + managers: newManagers, + administrators: newAdministrators, + employee_count, + subs_expiration }); }, []);