Refactor ChoreEdit component: reorganize sections, enhance UI elements, and improve task assignment features

This commit is contained in:
Mo Tarbin
2025-08-16 01:41:54 -04:00
parent c52e83fe10
commit 720987197d

View File

@@ -1,4 +1,4 @@
import { Add } from '@mui/icons-material'
import { Add, HorizontalRule } from '@mui/icons-material'
import {
Box,
Button,
@@ -409,26 +409,29 @@ const ChoreEdit = () => {
}
return (
<Container maxWidth='md'>
{/* <Typography level='h3' mb={1.5}>
Edit Chore
{/* Section 1: Basic Information */}
<Box mb={4}>
{/* <Typography
level='h3'
mb={2}
sx={{ borderBottom: '2px solid', borderColor: 'primary.main', pb: 1 }}
>
Basic Information
</Typography> */}
<Box>
<Box mb={3}>
<FormControl error={errors.name}>
<Typography level='h4'>Name :</Typography>
<Typography level='h4'>Name</Typography>
<Typography level='h5'>What is the name of this chore?</Typography>
<Input value={name} onChange={e => setName(e.target.value)} />
<FormHelperText error>{errors.name}</FormHelperText>
</FormControl>
</Box>
<Box mt={2}>
<FormControl error={errors.description}>
<Typography level='h4'>Additional Details :</Typography>
<Typography level='h5'>What is this task about?</Typography>
{/* <Textarea
value={description}
onChange={e => setDescription(e.target.value)}
/> */}
<Box mb={3}>
<FormControl error={errors.description}>
<Typography level='h4'>Description</Typography>
<Typography level='h5'>What is this task about?</Typography>
<RichTextEditor
value={description}
onChange={setDescription}
@@ -438,8 +441,175 @@ const ChoreEdit = () => {
<FormHelperText error>{errors.description}</FormHelperText>
</FormControl>
</Box>
<Box mt={2}>
<Typography level='h4'>Assignees :</Typography>
<Box mb={3}>
<Typography level='h4'>Priority</Typography>
<Typography level='h5'>How important is this task?</Typography>
{/* Priority Chip Selection */}
<Box
sx={{
display: 'flex',
flexWrap: 'wrap',
gap: 2,
mt: 2,
}}
>
{/* Priority Chips P1-P4 */}
{Priorities.map(priorityItem => (
<Chip
key={priorityItem.value}
variant={priority === priorityItem.value ? 'solid' : 'outlined'}
color={
priority === priorityItem.value
? priorityItem.color || 'primary'
: 'neutral'
}
size='lg'
onClick={() => setPriority(priorityItem.value)}
startDecorator={priorityItem.icon}
sx={{
fontWeight: 'md',
cursor: 'pointer',
minHeight: 34,
}}
>
{priorityItem.name}
</Chip>
))}
{/* No Priority Chip */}
<Chip
variant={priority === 0 ? 'solid' : 'outlined'}
color='neutral'
size='lg'
onClick={() => setPriority(0)}
startDecorator={<HorizontalRule />}
sx={{
fontWeight: 'md',
cursor: 'pointer',
minHeight: 34,
}}
>
No Priority
</Chip>
</Box>
</Box>
<Box mb={3}>
<Typography level='h4'>Labels</Typography>
<Typography level='h5'>
Things to remember about this task or to tag it
</Typography>
<Select
multiple
onChange={(event, newValue) => {
setLabelsV2(userLabels.filter(l => newValue.indexOf(l.name) > -1))
}}
value={labelsV2?.map(l => l.name)}
renderValue={selected => (
<Box sx={{ display: 'flex', gap: '0.25rem' }}>
{labelsV2.map(selectedOption => {
return (
<Chip
variant='soft'
color='primary'
key={selectedOption.id}
size='lg'
sx={{
background: selectedOption.color,
color: getTextColorFromBackgroundColor(
selectedOption.color,
),
}}
>
{selectedOption.name}
</Chip>
)
})}
</Box>
)}
sx={{ minWidth: '15rem' }}
slotProps={{
listbox: {
sx: {
width: '100%',
},
},
}}
>
{userLabels &&
userLabels.map(label => (
<Option key={label.id + label.name} value={label.name}>
<div
style={{
width: '20 px',
height: '20 px',
borderRadius: '50%',
background: label.color,
}}
/>
{label.name}
</Option>
))}
<MenuItem
key={'addNewLabel'}
value={' New Label'}
onClick={() => {
setAddLabelModalOpen(true)
}}
>
<Add />
Add New Label
</MenuItem>
</Select>
</Box>
<Box>
<Typography level='h4'>Sub Tasks</Typography>
{/* <FormControl sx={{ mt: 1 }}>
<Checkbox
onChange={e => {
if (e.target.checked) {
setSubTasks([])
} else {
setSubTasks(null)
}
}}
overlay
checked={subTasks != null}
label='Add sub tasks to this task'
/>
<FormHelperText>Break this task into smaller steps</FormHelperText>
</FormControl> */}
<Card
variant='outlined'
sx={{
p: 1,
mt: 2,
}}
>
<SubTasks
editMode={true}
tasks={subTasks ? subTasks : []}
setTasks={setSubTasks}
choreId={choreId}
/>
</Card>
</Box>
</Box>
{/* Section 2: Assignment & Responsibility */}
<Box mb={4}>
<Typography
level='h3'
mb={2}
sx={{ borderBottom: '2px solid', borderColor: 'primary.main', pb: 1 }}
>
Assignment
</Typography>
<Box mb={3}>
<Typography level='h4'>Assignees</Typography>
<Typography level='h5'>Who can do this task?</Typography>
<Card>
<List
@@ -453,8 +623,9 @@ const ChoreEdit = () => {
{performers?.map((item, index) => (
<ListItem key={item.id}>
<Checkbox
// disabled={index === 0}
checked={assignees.find(a => a.userId == item.userId) != null}
checked={
assignees.find(a => a.userId == item.userId) != null
}
onClick={() => {
if (assignees.some(a => a.userId === item.userId)) {
const newAssignees = assignees.filter(
@@ -478,17 +649,12 @@ const ChoreEdit = () => {
<FormHelperText error>{Boolean(errors.assignee)}</FormHelperText>
</FormControl>
</Box>
{assignees.length > 1 && (
// this wrap the details that needed if we have more than one assingee
// we need to pick the next assignedTo and also the strategy to pick the next assignee.
// if we have only one then no need to display this section
<>
<Box mt={2}>
<Typography level='title-lg'>Assigned :</Typography>
<Typography level='body-md'>
Who is assigned the next due?
</Typography>
{assignees.length > 1 && (
<>
<Box mb={3}>
<Typography level='h4'>Currently Assigned To</Typography>
<Typography level='h5'>Who is assigned the next due?</Typography>
<Select
placeholder={
assignees.length === 0
@@ -509,18 +675,16 @@ const ChoreEdit = () => {
}}
>
{item.displayName}
{/* <Chip size='sm' color='neutral' variant='soft'>
</Chip> */}
</Option>
))}
</Select>
</Box>
<Box mt={2}>
<Typography level='title-lg'>Picking Mode :</Typography>
<Typography level='body-md'>
<Box>
<Typography level='h4'>Assignment Strategy</Typography>
<Typography level='h5'>
How to pick the next assignee for the following task?
</Typography>
<Card>
<List
orientation='horizontal'
@@ -533,7 +697,6 @@ const ChoreEdit = () => {
{ASSIGN_STRATEGIES.map((item, idx) => (
<ListItem key={item}>
<Checkbox
// disabled={index === 0}
checked={assignStrategy === item}
onClick={() => setAssignStrategy(item)}
overlay
@@ -551,6 +714,18 @@ const ChoreEdit = () => {
</Box>
</>
)}
</Box>
{/* Section 3: Schedule & Timing */}
<Box mb={4}>
<Typography
level='h3'
mb={2}
sx={{ borderBottom: '2px solid', borderColor: 'primary.main', pb: 1 }}
>
Schedule & Timing
</Typography>
<RepeatSection
frequency={frequency}
onFrequencyUpdate={setFrequency}
@@ -576,9 +751,9 @@ const ChoreEdit = () => {
selectedThing={thingTrigger}
/>
<Box mt={2}>
<Typography level='title-lg'>
{REPEAT_ON_TYPE.includes(frequencyType) ? 'Start date' : 'Due date'} :
<Box mt={3} mb={3}>
<Typography level='h4'>
{REPEAT_ON_TYPE.includes(frequencyType) ? 'Start Date' : 'Due Date'}
</Typography>
{frequencyType === 'trigger' && !dueDate && (
<Typography level='body-sm'>
@@ -621,8 +796,11 @@ const ChoreEdit = () => {
<FormHelperText>{errors.dueDate}</FormHelperText>
</FormControl>
)}
</Box>
{dueDate && (
<>
<Box mb={3}>
<Typography level='h4'>Completion Window</Typography>
<FormControl orientation='horizontal'>
<Switch
checked={completionWindow != -1}
@@ -636,17 +814,14 @@ const ChoreEdit = () => {
}}
color={completionWindow !== -1 ? 'success' : 'neutral'}
variant={completionWindow !== -1 ? 'solid' : 'outlined'}
// endDecorator={points !== -1 ? 'On' : 'Off'}
sx={{
mr: 2,
}}
/>
<div>
{/* <FormLabel>Completion window (hours)</FormLabel> */}
<Typography level='body-md'>
Completion window (hours)
</Typography>
<FormHelperText sx={{ mt: 0 }}>
{"Set a time window that task can't be completed before"}
</FormHelperText>
@@ -661,12 +836,10 @@ const ChoreEdit = () => {
}}
>
<Typography level='body-sm'>Hours:</Typography>
<Input
type='number'
value={completionWindow}
sx={{ maxWidth: 100 }}
// add min points is 0 and max is 1000
slotProps={{
input: {
min: 0,
@@ -681,16 +854,15 @@ const ChoreEdit = () => {
</Box>
</Card>
)}
</>
)}
</Box>
)}
{!['once', 'no_repeat'].includes(frequencyType) && (
<Box mt={2}>
<Typography level='title-lg'>Scheduling Preferences: </Typography>
<Typography level='body-md'>
<Box>
<Typography level='h4'>Scheduling Preferences</Typography>
<Typography level='h5'>
How to reschedule the next due date?
</Typography>
<RadioGroup name='tiers' sx={{ gap: 1, '& > div': { p: 1 } }}>
<FormControl>
<Radio
@@ -700,8 +872,8 @@ const ChoreEdit = () => {
label='Reschedule from due date'
/>
<FormHelperText>
the next task will be scheduled from the original due date, even
if the previous task was completed late
the next task will be scheduled from the original due date,
even if the previous task was completed late
</FormHelperText>
</FormControl>
<FormControl>
@@ -712,23 +884,17 @@ const ChoreEdit = () => {
label='Reschedule from completion date'
/>
<FormHelperText>
the next task will be scheduled from the actual completion date
of the previous task
the next task will be scheduled from the actual completion
date of the previous task
</FormHelperText>
</FormControl>
</RadioGroup>
</Box>
)}
<Box mt={2}>
<Typography level='title-lg'>Notifications : </Typography>
<Typography level='body-md'>
Get Reminders when this task is due or completed
{!isPlusAccount(userProfile) && (
<Chip variant='soft' color='warning'>
Plus Feature
</Chip>
)}
</Typography>
{/* Section 3.1: Notifications */}
<Box mb={3}>
<Typography level='h4'>Notifications</Typography>
{!isPlusAccount(userProfile) && (
<Typography level='body-sm' color='warning' sx={{ mb: 1 }}>
Task notifications are not available in the Basic plan. Upgrade to
@@ -740,7 +906,6 @@ const ChoreEdit = () => {
<Checkbox
onChange={e => {
setIsNotificable(e.target.checked)
// if unchecking, reset notification metadata:
if (!e.target.checked) {
setNotificationMetadata({})
}
@@ -760,18 +925,19 @@ const ChoreEdit = () => {
</FormHelperText>
</FormControl>
</Box>
{isNotificable && (
<Box
sx={{
display: 'flex',
flexDirection: 'column',
gap: 2,
'& > div': { p: 2, borderRadius: 'md', display: 'flex' },
}}
>
<Card variant='outlined'>
<Typography level='body-md'>Notification Schedule:</Typography>
<Typography level='h4' mb={2}>
Notification Schedule
</Typography>
<Box sx={{ p: 0.5 }}>
<NotificationTemplate
onChange={metadata => {
@@ -786,7 +952,10 @@ const ChoreEdit = () => {
value={notificationMetadata}
/>
</Box>
<Typography level='body-md'> Who to Notify:</Typography>
<Typography level='h4' mt={3} mb={2}>
Who to Notify
</Typography>
<FormControl>
<Checkbox
overlay
@@ -828,7 +997,6 @@ const ChoreEdit = () => {
}}
>
<Typography level='body-sm'>Telegram Group ID:</Typography>
<Input
type='number'
value={notificationMetadata?.circleGroupID}
@@ -845,145 +1013,20 @@ const ChoreEdit = () => {
</Card>
</Box>
)}
<Box mt={2}>
<Typography level='title-lg'>Labels :</Typography>
<Typography level='body-md'>
Things to remember about this task or to tag it
</Typography>
<Select
multiple
onChange={(event, newValue) => {
setLabelsV2(userLabels.filter(l => newValue.indexOf(l.name) > -1))
}}
value={labelsV2?.map(l => l.name)}
renderValue={selected => (
<Box sx={{ display: 'flex', gap: '0.25rem' }}>
{labelsV2.map(selectedOption => {
return (
<Chip
variant='soft'
color='primary'
key={selectedOption.id}
size='lg'
sx={{
background: selectedOption.color,
color: getTextColorFromBackgroundColor(
selectedOption.color,
),
}}
>
{selectedOption.name}
</Chip>
)
})}
</Box>
)}
sx={{ minWidth: '15rem' }}
slotProps={{
listbox: {
sx: {
width: '100%',
},
},
}}
>
{userLabels &&
userLabels
// .map(l => l.name)
.map(label => (
<Option key={label.id + label.name} value={label.name}>
<div
style={{
width: '20 px',
height: '20 px',
borderRadius: '50%',
background: label.color,
}}
/>
{label.name}
</Option>
))}
<MenuItem
key={'addNewLabel'}
value={' New Label'}
onClick={() => {
setAddLabelModalOpen(true)
}}
>
<Add />
Add New Label
</MenuItem>
</Select>
</Box>
<Box mt={2}>
<Typography level='title-lg'>Priority :</Typography>
<Typography level='body-md'>How important is this task?</Typography>
<Select
onChange={(event, newValue) => {
setPriority(newValue)
}}
value={priority}
sx={{ minWidth: '15rem' }}
slotProps={{
listbox: {
sx: {
width: '100%',
},
},
}}
>
{Priorities.map(priority => (
<Option key={priority.id + priority.name} value={priority.value}>
<div
style={{
width: '20 px',
height: '20 px',
borderRadius: '50%',
background: priority.color,
}}
/>
{priority.name}
</Option>
))}
<Option value={0}>No Priority</Option>
</Select>
</Box>
<Box mt={2}>
<Typography level='title-lg' gutterBottom>
Others :
{/* Section 4: Task Settings */}
<Box mb={4}>
<Typography
level='h3'
mb={2}
sx={{ borderBottom: '2px solid', borderColor: 'primary.main', pb: 1 }}
>
Task Settings
</Typography>
<FormControl sx={{ mt: 1 }}>
<Checkbox
onChange={e => {
if (e.target.checked) {
setSubTasks([])
} else {
setSubTasks(null)
}
}}
overlay
checked={subTasks != null}
label='Sub Tasks'
/>
<FormHelperText>Add sub tasks to this task</FormHelperText>
</FormControl>
{subTasks != null && (
<Card
variant='outlined'
sx={{
p: 1,
}}
>
<SubTasks
editMode={true}
tasks={subTasks}
setTasks={setSubTasks}
choreId={choreId}
/>
</Card>
)}
<Box mb={3}>
<Typography level='h4'>Points System</Typography>
<FormControl sx={{ mt: 1 }}>
<Checkbox
onChange={e => {
@@ -995,16 +1038,15 @@ const ChoreEdit = () => {
}}
checked={points > -1}
overlay
label='Assign Points'
label='Assign points for completion'
/>
<FormHelperText>
Assign points to this task and user will earn points when they
completed it
</FormHelperText>
</FormControl>
{points != -1 && (
<Card variant='outlined'>
<Card variant='outlined' sx={{ mt: 2 }}>
<Box
sx={{
mt: 0,
@@ -1012,12 +1054,10 @@ const ChoreEdit = () => {
}}
>
<Typography level='body-sm'>Points:</Typography>
<Input
type='number'
value={points}
sx={{ maxWidth: 100 }}
// add min points is 0 and max is 1000
slotProps={{
input: {
min: 0,
@@ -1032,6 +1072,10 @@ const ChoreEdit = () => {
</Box>
</Card>
)}
</Box>
<Box mb={3}>
<Typography level='h4'>Approval Requirement</Typography>
<FormControl sx={{ mt: 1 }}>
<Checkbox
onChange={e => {
@@ -1039,7 +1083,7 @@ const ChoreEdit = () => {
}}
checked={requireApproval}
overlay
label='Require Approval'
label='Require admin approval'
/>
<FormHelperText>
This task will need approval from an admin before being marked as
@@ -1047,12 +1091,12 @@ const ChoreEdit = () => {
</FormHelperText>
</FormControl>
</Box>
<Box mt={2} mb={2}>
<Typography level='title-lg'>Visibility:</Typography>
<Typography level='body-md' sx={{ mb: 2 }}>
<Box>
<Typography level='h4'>Visibility</Typography>
<Typography level='h5' sx={{ mb: 2 }}>
Choose who can see this task
</Typography>
<RadioGroup
name='isPrivate'
value={isPrivate}
@@ -1065,7 +1109,6 @@ const ChoreEdit = () => {
<Radio overlay value={false} label='Public' />
<FormHelperText>Everyone in your circle</FormHelperText>
</FormControl>
<FormControl>
<Radio overlay value={true} label='Private' />
<FormHelperText>
@@ -1074,6 +1117,8 @@ const ChoreEdit = () => {
</FormControl>
</RadioGroup>
</Box>
</Box>
{choreId > 0 && (
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.5, mt: 3 }}>
<Sheet