Add Significant Updates to the Settings Page & Edit for Only Specific Card

This commit is contained in:
Matt DiMeglio 2025-05-29 14:20:45 -04:00
parent d2b3163c67
commit 6afe93aa04
4 changed files with 367 additions and 26 deletions

View file

@ -21,7 +21,7 @@ export const Footer = () => {
return (
<FooterContainer>
<FooterText>{`© ${new Date().getFullYear()} ShiftSync`}</FooterText>
<FooterText>{department?.name}</FooterText>
<FooterText>{department?.company}</FooterText>
</FooterContainer>
)
}

View file

@ -2,4 +2,5 @@
margin: 0;
padding: 0;
box-sizing: border-box;
text-align: initial;
}

View file

@ -1,7 +1,8 @@
import React, { useEffect, useRef, useState } from 'react';
import styled from '@emotion/styled';
import { Link } from 'react-router-dom';
import { Stack } from '@mui/material';
import EditIcon from '@mui/icons-material/Edit';
import EditOffIcon from '@mui/icons-material/EditOff';
import { ToggleTabs, useLocalStore } from '@components';
const OuterContainer = styled(Stack)`
@ -46,62 +47,224 @@ const SlidingIndicator = styled('div')`
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 = [
{
id: 'dept-info',
id: 'deptInfo',
title: 'Information',
tab: 'department',
cards: [
{
id: 'company-info',
id: 'companyInfo',
label: 'Company Information',
fields: [
{
id: 'company',
label: 'Company Name',
type: 'text',
readOnly: true
},
{
id: 'billingAddress',
label: 'Billing Address',
type: 'text'
},
{
id: 'billing-address',
label: 'Billing Address',
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: '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',
tab: 'department',
cards: []
},
{
id: 'dept-mgrs',
id: 'deptMgrs',
title: 'Managers',
tab: 'department',
cards: []
},
{
id: 'dept-subs',
id: 'deptSubs',
title: 'Subscriptions',
tab: 'department',
cards: []
},
{
id: 'personal-info',
id: 'personalInfo',
title: 'Information',
tab: 'personal',
cards: []
},
{
id: 'personal-noti',
id: 'personalNoti',
title: 'Notifications',
tab: 'personal',
cards: []
},
{
id: 'dept-noti',
id: 'deptNoti',
title: 'Notifications',
tab: 'department',
cards: []
@ -109,7 +272,19 @@ const settingsFields = [
];
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 tabs = [
{
@ -122,12 +297,54 @@ export const Settings = () => {
}
];
const [editMode, setEditMode] = useState(null);
const [tabValue, setTabValue] = useState(tabs[0]);
const [pageValue, setPageValue] = useState({});
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(() => {
document.title = 'ShiftSync | Settings';
@ -176,16 +393,128 @@ export const Settings = () => {
<SlidingIndicator style={indicatorStyle} />
</OuterContainer>
<Border />
{pageValue?.cards?.map((card) => {
return (<div>
{card?.label}
{card?.fields?.map((field) => {
return (
<p>{field?.label}</p>
)
})}
</div>)
})}
<CardShell>
{pageValue?.cards?.map((card) => {
return (
<Card
key={`${card?.id}-card`}
>
<CardTitle
key={`${card?.id}-card-title`}
>
{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>
);
};

View file

@ -6,8 +6,19 @@ import { useLocalStore } from '@components';
const dept = {
id: 1,
name: 'Darien EMS - Post 53',
company: 'Darien EMS - Post 53',
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: [],
managers: [],
administrators: [1]