Add Significant Updates to the Settings Page & Edit for Only Specific Card
This commit is contained in:
parent
d2b3163c67
commit
6afe93aa04
4 changed files with 367 additions and 26 deletions
|
|
@ -21,7 +21,7 @@ export const Footer = () => {
|
||||||
return (
|
return (
|
||||||
<FooterContainer>
|
<FooterContainer>
|
||||||
<FooterText>{`© ${new Date().getFullYear()} ShiftSync`}</FooterText>
|
<FooterText>{`© ${new Date().getFullYear()} ShiftSync`}</FooterText>
|
||||||
<FooterText>{department?.name}</FooterText>
|
<FooterText>{department?.company}</FooterText>
|
||||||
</FooterContainer>
|
</FooterContainer>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -2,4 +2,5 @@
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
text-align: initial;
|
||||||
}
|
}
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
import React, { useEffect, useRef, useState } from 'react';
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { Link } from 'react-router-dom';
|
|
||||||
import { Stack } from '@mui/material';
|
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 { ToggleTabs, useLocalStore } from '@components';
|
||||||
|
|
||||||
const OuterContainer = styled(Stack)`
|
const OuterContainer = styled(Stack)`
|
||||||
|
|
@ -46,62 +47,224 @@ const SlidingIndicator = styled('div')`
|
||||||
transition: left 0.3s ease, width 0.3s ease;
|
transition: left 0.3s ease, width 0.3s ease;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const CardShell = styled('div')`
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 10px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Card = styled('div')`
|
||||||
|
background-color: #C7C7C7;
|
||||||
|
width: 50%;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 10px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const CardTitle = styled('p')`
|
||||||
|
color: white;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 20px;
|
||||||
|
font-family: "WDXL Lubrifont TC";
|
||||||
|
padding-bottom: 5px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const CardHeader = styled('div')`
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
flex-direction: row;
|
||||||
|
padding: 10px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const EditArea = styled('div')`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-end;
|
||||||
|
padding-top: 10px;
|
||||||
|
color: #4D79FF;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const EditTextButton = styled('div')`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
gap: 4px;
|
||||||
|
padding-bottom: 2px;
|
||||||
|
border-bottom: 2px solid transparent;
|
||||||
|
transition: border-bottom 0.2s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-bottom: 2px solid #4D79FF;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const CardBody = styled('div')`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const InnerCard = styled('div')`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 10px;
|
||||||
|
width: 50%;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const InnerCardRow = styled('div')`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 5px;
|
||||||
|
align-items: center;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const InnerCardRowLabel = styled('label')`
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
text-align: right;
|
||||||
|
width: 60%;
|
||||||
|
padding-right: 10px;
|
||||||
|
font-size: 14px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const InnerCardRowInput = styled('input')`
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
width: 100%;
|
||||||
|
padding: 4px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const InnerCardRadioInput = styled('input')`
|
||||||
|
padding-right: 5px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const InnerCardRadioLabel = styled('label')`
|
||||||
|
font-size: 14px;
|
||||||
|
padding-left: 5px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const InnerCardRowRadioDiv = styled('div')`
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-evenly;
|
||||||
|
width: 100%;
|
||||||
|
padding: 4px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const FormRadioButtonLabel = styled('label')``;
|
||||||
|
|
||||||
|
const FormInputButtonDiv = styled('div')`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-end;
|
||||||
|
padding-top: 10px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const FormInputButton = styled('input')`
|
||||||
|
padding: 4px 10px;
|
||||||
|
border-radius: 4px;
|
||||||
|
`;
|
||||||
|
|
||||||
const settingsFields = [
|
const settingsFields = [
|
||||||
{
|
{
|
||||||
id: 'dept-info',
|
id: 'deptInfo',
|
||||||
title: 'Information',
|
title: 'Information',
|
||||||
tab: 'department',
|
tab: 'department',
|
||||||
cards: [
|
cards: [
|
||||||
{
|
{
|
||||||
id: 'company-info',
|
id: 'companyInfo',
|
||||||
label: 'Company Information',
|
label: 'Company Information',
|
||||||
fields: [
|
fields: [
|
||||||
{
|
{
|
||||||
id: 'company',
|
id: 'company',
|
||||||
label: 'Company Name',
|
label: 'Company Name',
|
||||||
|
type: 'text',
|
||||||
|
readOnly: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'billingAddress',
|
||||||
|
label: 'Billing Address',
|
||||||
type: 'text'
|
type: 'text'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'billing-address',
|
id: 'town',
|
||||||
label: 'Billing Address',
|
label: 'Town',
|
||||||
type: 'text'
|
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: 'displayTime',
|
||||||
|
label: 'Display Time',
|
||||||
|
type: 'select',
|
||||||
|
options: [
|
||||||
|
{ label: '12 Hour AM/PM', value: '12' },
|
||||||
|
{ label: '24 Hour', value: '24' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'startDay',
|
||||||
|
label: 'Calendar Start Day',
|
||||||
|
type: 'select',
|
||||||
|
options: [
|
||||||
|
{ label: 'Sunday', value: 'sunday' },
|
||||||
|
{ label: 'Monday', value: 'monday' }
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'dept-perms',
|
id: 'deptPerms',
|
||||||
title: 'Permissions',
|
title: 'Permissions',
|
||||||
tab: 'department',
|
tab: 'department',
|
||||||
cards: []
|
cards: []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'dept-mgrs',
|
id: 'deptMgrs',
|
||||||
title: 'Managers',
|
title: 'Managers',
|
||||||
tab: 'department',
|
tab: 'department',
|
||||||
cards: []
|
cards: []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'dept-subs',
|
id: 'deptSubs',
|
||||||
title: 'Subscriptions',
|
title: 'Subscriptions',
|
||||||
tab: 'department',
|
tab: 'department',
|
||||||
cards: []
|
cards: []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'personal-info',
|
id: 'personalInfo',
|
||||||
title: 'Information',
|
title: 'Information',
|
||||||
tab: 'personal',
|
tab: 'personal',
|
||||||
cards: []
|
cards: []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'personal-noti',
|
id: 'personalNoti',
|
||||||
title: 'Notifications',
|
title: 'Notifications',
|
||||||
tab: 'personal',
|
tab: 'personal',
|
||||||
cards: []
|
cards: []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'dept-noti',
|
id: 'deptNoti',
|
||||||
title: 'Notifications',
|
title: 'Notifications',
|
||||||
tab: 'department',
|
tab: 'department',
|
||||||
cards: []
|
cards: []
|
||||||
|
|
@ -109,7 +272,19 @@ const settingsFields = [
|
||||||
];
|
];
|
||||||
|
|
||||||
export const Settings = () => {
|
export const Settings = () => {
|
||||||
const { user } = useLocalStore();
|
const { user, department, setDepartment } = useLocalStore();
|
||||||
|
const originalDepartmentRef = useRef(department);
|
||||||
|
const [formValues, setFormValues] = useState(() => {
|
||||||
|
const initial = {};
|
||||||
|
settingsFields.forEach(section => {
|
||||||
|
section.cards.forEach(card => {
|
||||||
|
card.fields.forEach(field => {
|
||||||
|
initial[field.id] = department?.[field.id] || '';
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return initial;
|
||||||
|
});
|
||||||
const pageRefs = useRef({});
|
const pageRefs = useRef({});
|
||||||
const tabs = [
|
const tabs = [
|
||||||
{
|
{
|
||||||
|
|
@ -122,12 +297,54 @@ export const Settings = () => {
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
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({});
|
||||||
const [indicatorStyle, setIndicatorStyle] = useState({ left: 0, width: 0 });
|
const [indicatorStyle, setIndicatorStyle] = useState({ left: 0, width: 0 });
|
||||||
|
|
||||||
console.log('pageValue: ', pageValue);
|
const getChangedFields = (original, current) => {
|
||||||
|
const changed = {};
|
||||||
|
for (const key in current) {
|
||||||
|
if (current[key] !== original[key]) {
|
||||||
|
changed[key] = current[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return changed;
|
||||||
|
};
|
||||||
|
|
||||||
|
const changedFields = getChangedFields(originalDepartmentRef.current, formValues);
|
||||||
|
const hasChanges = Object.keys(changedFields).length > 0;
|
||||||
|
|
||||||
|
const onSubmit = (data) => {
|
||||||
|
setDepartment({
|
||||||
|
...department,
|
||||||
|
...data
|
||||||
|
});
|
||||||
|
originalDepartmentRef.current = {
|
||||||
|
...originalDepartmentRef.current,
|
||||||
|
...data
|
||||||
|
};
|
||||||
|
setEditMode(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatPhoneNumber = (value) => {
|
||||||
|
const cleaned = value.replace(/\D/g, '').slice(0, 10); // Only digits, max 10
|
||||||
|
const length = cleaned.length;
|
||||||
|
|
||||||
|
if (length === 0) return '';
|
||||||
|
if (length < 4) return `(${cleaned}`;
|
||||||
|
if (length < 7) return `(${cleaned.slice(0, 3)}) ${cleaned.slice(3)}`;
|
||||||
|
return `(${cleaned.slice(0, 3)}) ${cleaned.slice(3, 6)}-${cleaned.slice(6)}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const handleChange = (e, type) => {
|
||||||
|
let { name, value } = e.target;
|
||||||
|
if (type === 'phone') {
|
||||||
|
value = formatPhoneNumber(value);
|
||||||
|
}
|
||||||
|
setFormValues(prev => ({ ...prev, [name]: value }));
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
document.title = 'ShiftSync | Settings';
|
document.title = 'ShiftSync | Settings';
|
||||||
|
|
@ -176,16 +393,128 @@ export const Settings = () => {
|
||||||
<SlidingIndicator style={indicatorStyle} />
|
<SlidingIndicator style={indicatorStyle} />
|
||||||
</OuterContainer>
|
</OuterContainer>
|
||||||
<Border />
|
<Border />
|
||||||
{pageValue?.cards?.map((card) => {
|
<CardShell>
|
||||||
return (<div>
|
{pageValue?.cards?.map((card) => {
|
||||||
{card?.label}
|
return (
|
||||||
{card?.fields?.map((field) => {
|
<Card
|
||||||
return (
|
key={`${card?.id}-card`}
|
||||||
<p>{field?.label}</p>
|
>
|
||||||
)
|
<CardTitle
|
||||||
})}
|
key={`${card?.id}-card-title`}
|
||||||
</div>)
|
>
|
||||||
})}
|
{card?.label}
|
||||||
|
</CardTitle>
|
||||||
|
<Border key={`${card?.id}-border-1`}/>
|
||||||
|
<CardHeader
|
||||||
|
key={`${card?.id}-card-header`}
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
Employee Count: {department?.employeeCount}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Subscription Expiration: {department?.subscriptionExpiration}
|
||||||
|
</p>
|
||||||
|
</CardHeader>
|
||||||
|
<Border key={`${card?.id}-border-2`}/>
|
||||||
|
<form
|
||||||
|
onSubmit={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
onSubmit(changedFields);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<EditArea>
|
||||||
|
{editMode === card?.id ? (
|
||||||
|
<EditTextButton onClick={() => setEditMode(null)}>
|
||||||
|
<EditOffIcon sx={{ fontSize: 20 }} />
|
||||||
|
<p>View</p>
|
||||||
|
</EditTextButton>
|
||||||
|
) : (
|
||||||
|
<EditTextButton onClick={() => setEditMode(card?.id)}>
|
||||||
|
<EditIcon sx={{ fontSize: 20 }} />
|
||||||
|
<p>Edit</p>
|
||||||
|
</EditTextButton>
|
||||||
|
)}
|
||||||
|
</EditArea>
|
||||||
|
<CardBody
|
||||||
|
key={`${card?.id}-card-body`}
|
||||||
|
>
|
||||||
|
<InnerCard
|
||||||
|
key={`${card?.id}-inner-card`}
|
||||||
|
>
|
||||||
|
{card?.fields?.map((field) => {
|
||||||
|
let fieldType;
|
||||||
|
if (field?.type === 'text') {
|
||||||
|
fieldType = <InnerCardRowInput
|
||||||
|
name={field?.id}
|
||||||
|
disabled={card?.id !== editMode || field?.readOnly}
|
||||||
|
value={formValues[field?.id] ?? 'Not Provided'}
|
||||||
|
onChange={(e) => handleChange(e, 'text')}
|
||||||
|
/>;
|
||||||
|
} else if (field?.type === 'phone') {
|
||||||
|
fieldType = <InnerCardRowInput
|
||||||
|
type="tel"
|
||||||
|
name="phone"
|
||||||
|
placeholder="(123) 456-7890"
|
||||||
|
value={formValues[field?.id]}
|
||||||
|
disabled={card?.id !== editMode || field?.readOnly}
|
||||||
|
onChange={(e) => handleChange(e, 'phone')}
|
||||||
|
pattern="\(\d{3}\) \d{3}-\d{4}"
|
||||||
|
maxlength="14"
|
||||||
|
/>
|
||||||
|
} else if (field?.type === 'select') {
|
||||||
|
fieldType = (
|
||||||
|
<InnerCardRowRadioDiv>
|
||||||
|
{field?.options?.map((option) => {
|
||||||
|
const inputId = `${field.id}-${option.value}`;
|
||||||
|
return (
|
||||||
|
<FormRadioButtonLabel htmlFor={inputId} key={inputId}>
|
||||||
|
<InnerCardRadioInput
|
||||||
|
type="radio"
|
||||||
|
id={inputId}
|
||||||
|
name={field?.id}
|
||||||
|
value={option?.value}
|
||||||
|
checked={formValues[field.id] === option.value}
|
||||||
|
disabled={card?.id !== editMode || field?.readOnly}
|
||||||
|
onChange={(e) => handleChange(e, 'text')}
|
||||||
|
/>
|
||||||
|
<InnerCardRadioLabel htmlFor={inputId} key={inputId}>
|
||||||
|
{option?.label}
|
||||||
|
</InnerCardRadioLabel>
|
||||||
|
</FormRadioButtonLabel>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</InnerCardRowRadioDiv>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<InnerCardRow key={`${field?.id}-row`}>
|
||||||
|
<InnerCardRowLabel>
|
||||||
|
{field?.label}:
|
||||||
|
</InnerCardRowLabel>
|
||||||
|
{fieldType}
|
||||||
|
</InnerCardRow>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</InnerCard>
|
||||||
|
</CardBody>
|
||||||
|
{card?.id === editMode && (
|
||||||
|
<FormInputButtonDiv>
|
||||||
|
<FormInputButton
|
||||||
|
type="submit"
|
||||||
|
value="Save"
|
||||||
|
disabled={!hasChanges}
|
||||||
|
style={{
|
||||||
|
backgroundColor: hasChanges ? '#4D79FF' : '',
|
||||||
|
color: hasChanges ? 'white' : ''
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormInputButtonDiv>
|
||||||
|
)}
|
||||||
|
</form>
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</CardShell>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,19 @@ import { useLocalStore } from '@components';
|
||||||
|
|
||||||
const dept = {
|
const dept = {
|
||||||
id: 1,
|
id: 1,
|
||||||
name: 'Darien EMS - Post 53',
|
company: 'Darien EMS - Post 53',
|
||||||
Abv: 'DEMS',
|
Abv: 'DEMS',
|
||||||
|
billingAddress: '0 Ledge Road',
|
||||||
|
town: 'Darien',
|
||||||
|
state: 'Connecticut',
|
||||||
|
postal: '06820',
|
||||||
|
country: 'United States',
|
||||||
|
phone: '',
|
||||||
|
displayTime: '12',
|
||||||
|
startDay: 'sunday',
|
||||||
|
companyLogo: '',
|
||||||
|
employeeCount: 1,
|
||||||
|
subscriptionExpiration: '10/01/2025',
|
||||||
schedulers: [],
|
schedulers: [],
|
||||||
managers: [],
|
managers: [],
|
||||||
administrators: [1]
|
administrators: [1]
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue