Set Up bases of Roles Page

This commit is contained in:
Matt DiMeglio 2025-06-03 18:52:40 -04:00
parent ea06cc04e6
commit fe13b651ed
6 changed files with 558 additions and 237 deletions

View file

@ -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 (
<div style={{ display: 'flex', flexDirection: 'row', width: '100%', justifyContent: 'space-around' }}>
<div style={{ display: 'flex', flexDirection: 'column', width: '100%', alignItems: 'center', padding: '5px' }}>
<div style={{ paddingBottom: '10px' }}>
<h2>{oldListLabel}</h2>
</div>
<div style={{ display: 'flex', width: '200px', height: '250px', border: '1px solid black', borderRadius: '5px', backgroundColor: '#E8E8E8' }}>
<div
style={{
display: 'flex',
flexDirection: 'column',
padding: '5px',
}}
>
{leftGroup?.map((j, index) => {
return (
<div
key={index}
style={{
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
width: '80%',
padding: '2px',
}}
title={j?.value}
>
<span>{j?.value}</span>
</div>
)
})}
</div>
</div>
</div>
<div style={{ display: 'flex', flexDirection: 'column', width: '25%', alignItems: 'center', justifyContent: 'center' }}>
<CenterButton
onClick={() => { console.log('clicked Right') }}
color='#A8A8A8'
buttonEnabled={itemSelected?.id}
>
<KeyboardArrowRightIcon />
</CenterButton>
<CenterButton
onClick={() => { console.log('clicked Clear') }}
color='#FF6666'
buttonEnabled={containsSelection?.length > 0}
>
<DoNotDisturbIcon />
</CenterButton>
<CenterButton
onClick={() => { console.log('clicked Save') }}
color='#00B33C'
buttonEnabled={containsSelection?.length > 0}
>
<CheckIcon />
</CenterButton>
<CenterButton
onClick={() => { console.log('clicked Left') }}
color='#A8A8A8'
buttonEnabled={itemSelected?.id}
>
<KeyboardArrowLeftIcon />
</CenterButton>
</div>
<div style={{ display: 'flex', flexDirection: 'column', width: '100%', alignItems: 'center', padding: '5px' }}>
<div style={{ paddingBottom: '10px' }}>
<h2>{newListLabel}</h2>
</div>
<div style={{ display: 'flex', width: '200px', height: '250px', border: '1px solid black', borderRadius: '5px', backgroundColor: '#E8E8E8' }}>
<div
style={{
display: 'flex',
flexDirection: 'column',
padding: '5px',
}}
>
{rightGroup?.map((j, index) => {
return (
<div
key={index}
style={{
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
width: '80%',
padding: '2px',
}}
title={j?.value}
>
<span>{j?.value}</span>
</div>
)
})}
</div>
</div>
</div>
</div>
);
}

View file

@ -0,0 +1 @@
export { TransferBox } from './TransferBox';

View file

@ -1 +1,2 @@
export { ToggleTabs } from './ToggleTabs'; export { ToggleTabs } from './ToggleTabs';
export { TransferBox } from './TransferBox';

View file

@ -3,7 +3,10 @@ import styled from '@emotion/styled';
import { Stack } from '@mui/material'; import { Stack } from '@mui/material';
import EditIcon from '@mui/icons-material/Edit'; import EditIcon from '@mui/icons-material/Edit';
import EditOffIcon from '@mui/icons-material/EditOff'; 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)` const OuterContainer = styled(Stack)`
position: relative; position: relative;
@ -57,7 +60,8 @@ const CardShell = styled('div')`
const Card = styled('div')` const Card = styled('div')`
background-color: #C7C7C7; background-color: #C7C7C7;
width: 1000px; width: 50%;
min-width: 750px;
padding: 20px; padding: 20px;
border-radius: 10px; border-radius: 10px;
margin: 5px; margin: 5px;
@ -113,14 +117,16 @@ const InnerCard = styled('div')`
flex-direction: column; flex-direction: column;
padding: 10px; padding: 10px;
width: 50%; width: 50%;
${({ centered }) => centered && `
align-items: center;
`}
`; `;
const InnerCardRow = styled('div')` const InnerCardRow = styled('div')`
display: flex; display: flex;
flex-direction: row; flex-direction: row;
justify-content: space-between;
padding: 5px; padding: 5px;
align-items: center;
`; `;
const InnerCardRowLabel = styled('label')` const InnerCardRowLabel = styled('label')`
@ -169,114 +175,13 @@ const FormInputButton = styled('input')`
border-radius: 4px; 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 = () => { export const Settings = () => {
const { user, department, setDepartment } = useLocalStore(); const { user, department, setDepartment } = useLocalStore();
const isAdministrator = user?.administrator;
const isManager = user?.manager;
const isScheduler = user?.scheduler;
const originalDepartmentRef = useRef(department); const originalDepartmentRef = useRef(department);
const [formValues, setFormValues] = useState(() => { const [formValues, setFormValues] = useState(() => {
const initial = {}; const initial = {};
@ -301,6 +206,7 @@ export const Settings = () => {
} }
]; ];
const [windowWidth, setWindowWidth] = useState(window.innerWidth);
const [editMode, setEditMode] = useState(null); const [editMode, setEditMode] = useState(null);
const [tabValue, setTabValue] = useState(tabs[0]); const [tabValue, setTabValue] = useState(tabs[0]);
const [pageValue, setPageValue] = useState({}); const [pageValue, setPageValue] = useState({});
@ -354,6 +260,18 @@ export const Settings = () => {
document.title = 'ShiftSync | 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(() => { useEffect(() => {
if (pageValue?.id && pageRefs.current[pageValue.id]) { if (pageValue?.id && pageRefs.current[pageValue.id]) {
const el = pageRefs.current[pageValue.id]; const el = pageRefs.current[pageValue.id];
@ -364,7 +282,7 @@ export const Settings = () => {
width: rect.width width: rect.width
}); });
} }
}, [pageValue?.id]); }, [pageValue?.id, windowWidth, window.innerHeight]);
useEffect(() => { useEffect(() => {
setPageValue(settingsFields?.filter((field) => field?.tab === tabValue?.value)[0]); setPageValue(settingsFields?.filter((field) => field?.tab === tabValue?.value)[0]);
@ -372,7 +290,7 @@ export const Settings = () => {
return ( return (
<div> <div>
{user?.administrator ? ( {user?.administrator || user?.manager ? (
<OuterContainer> <OuterContainer>
<ToggleTabs <ToggleTabs
tabs={tabs} tabs={tabs}
@ -383,7 +301,12 @@ export const Settings = () => {
</OuterContainer> </OuterContainer>
) : null } ) : null }
<OuterContainer> <OuterContainer>
{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 ( return (
<Tab <Tab
key={field?.id} key={field?.id}
@ -399,6 +322,11 @@ export const Settings = () => {
<Border /> <Border />
<CardShell> <CardShell>
{pageValue?.cards?.map((card) => { {pageValue?.cards?.map((card) => {
if (
(card?.accessRequired === 'administrator' && isAdministrator) ||
(card?.accessRequired === 'manager' && isManager) ||
(card?.accessRequired === 'scheduler' && isScheduler)
) {
return ( return (
<Card <Card
key={`${card?.id}-card`} key={`${card?.id}-card`}
@ -408,17 +336,27 @@ export const Settings = () => {
> >
{card?.label} {card?.label}
</CardTitle> </CardTitle>
{card?.fields?.find((field) => field?.type === 'header') !== undefined && (
<div>
<Border key={`${card?.id}-border-1`}/> <Border key={`${card?.id}-border-1`}/>
<CardHeader <CardHeader
key={`${card?.id}-card-header`} key={`${card?.id}-card-header`}
> >
<p> {card?.fields?.map((field) => {
Employee Count: {department?.employeeCount} return field?.type === 'header' && (
<InnerCardRow key={`${field?.id}-row`}>
<p style={{ fontSize: 14 }}>
{field?.label}:
</p> </p>
<p> <p style={{ paddingLeft: 10, fontSize: 14 }}>
Subscription Expiration: {department?.subscriptionExpiration} {department[field?.id]}
</p> </p>
</InnerCardRow>
)
})}
</CardHeader> </CardHeader>
</div>
)}
<Border key={`${card?.id}-border-2`}/> <Border key={`${card?.id}-border-2`}/>
<form <form
onSubmit={(e) => { onSubmit={(e) => {
@ -427,7 +365,8 @@ export const Settings = () => {
}} }}
> >
<EditArea> <EditArea>
{editMode === card?.id ? ( {!card?.removeEdit && (
editMode === card?.id ? (
<EditTextButton onClick={() => setEditMode(null)}> <EditTextButton onClick={() => setEditMode(null)}>
<EditOffIcon sx={{ fontSize: 20 }} /> <EditOffIcon sx={{ fontSize: 20 }} />
<p>View</p> <p>View</p>
@ -437,6 +376,7 @@ export const Settings = () => {
<EditIcon sx={{ fontSize: 20 }} /> <EditIcon sx={{ fontSize: 20 }} />
<p>Edit</p> <p>Edit</p>
</EditTextButton> </EditTextButton>
)
)} )}
</EditArea> </EditArea>
<CardBody <CardBody
@ -444,6 +384,7 @@ export const Settings = () => {
> >
<InnerCard <InnerCard
key={`${card?.id}-inner-card`} key={`${card?.id}-inner-card`}
centered={card.fields.some(f => f.type === 'transfer')}
> >
{card?.fields?.map((field) => { {card?.fields?.map((field) => {
let fieldType; let fieldType;
@ -489,12 +430,21 @@ export const Settings = () => {
})} })}
</InnerCardRowRadioDiv> </InnerCardRowRadioDiv>
) )
} else if (field?.type === 'transfer') {
fieldType = <TransferBox
style={{ display: 'flex', flexDirection: 'row' }}
fields={field}
leftGroup={department[field?.origList]}
rightGroup={department[field?.id]}
/>
} }
return ( return field?.type !== 'header' && (
<InnerCardRow key={`${field?.id}-row`}> <InnerCardRow key={`${field?.id}-row`}>
{field?.label ? (
<InnerCardRowLabel> <InnerCardRowLabel>
{field?.label}: {field?.label}:
</InnerCardRowLabel> </InnerCardRowLabel>
) : null }
{fieldType} {fieldType}
</InnerCardRow> </InnerCardRow>
) )
@ -517,6 +467,8 @@ export const Settings = () => {
</form> </form>
</Card> </Card>
) )
}
return null;
})} })}
</CardShell> </CardShell>
</div> </div>

View file

@ -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'
}
];

View file

@ -19,26 +19,85 @@ const dept = {
company_logo: '', company_logo: '',
employee_count: 1, employee_count: 1,
subscription_expiration: '10/01/2025', subscription_expiration: '10/01/2025',
schedulers: [], schedulers: [3],
managers: [], managers: [2],
administrators: [1] 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 AppRouter = () => {
const { user, setUser, setDepartment } = useLocalStore(); const { user, setUser, setDepartment } = useLocalStore();
const [userChanged, setUserChanged] = useState(false); const [userChanged, setUserChanged] = useState(false);
useEffect(() => { 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({ setUser({
id: 1, ...users[0],
first_name: 'ShiftSync-Manager',
last_name: 'Test-User',
email: 'testuser@shift-sync.com',
scheduler: dept?.schedulers?.includes(1), scheduler: dept?.schedulers?.includes(1),
manager: dept?.managers?.includes(1), manager: dept?.managers?.includes(1),
administrator: dept?.administrators?.includes(1), administrator: dept?.administrators?.includes(1)
is_ss_admin: false });
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
}); });
}, []); }, []);