move to Donetick Org, First commit frontend
This commit is contained in:
commit
2657469964
105 changed files with 21572 additions and 0 deletions
744
src/views/ChoreEdit/ChoreEdit.jsx
Normal file
744
src/views/ChoreEdit/ChoreEdit.jsx
Normal file
|
@ -0,0 +1,744 @@
|
|||
import {
|
||||
Box,
|
||||
Button,
|
||||
Card,
|
||||
Checkbox,
|
||||
Chip,
|
||||
Container,
|
||||
Divider,
|
||||
FormControl,
|
||||
FormHelperText,
|
||||
Input,
|
||||
List,
|
||||
ListItem,
|
||||
Option,
|
||||
Radio,
|
||||
RadioGroup,
|
||||
Select,
|
||||
Sheet,
|
||||
Typography,
|
||||
} from '@mui/joy'
|
||||
import moment from 'moment'
|
||||
import { useContext, useEffect, useState } from 'react'
|
||||
import { useNavigate, useParams } from 'react-router-dom'
|
||||
import { UserContext } from '../../contexts/UserContext'
|
||||
import {
|
||||
CreateChore,
|
||||
DeleteChore,
|
||||
GetAllCircleMembers,
|
||||
GetChoreByID,
|
||||
GetChoreHistory,
|
||||
GetThings,
|
||||
SaveChore,
|
||||
} from '../../utils/Fetcher'
|
||||
import { isPlusAccount } from '../../utils/Helpers'
|
||||
import FreeSoloCreateOption from '../components/AutocompleteSelect'
|
||||
import ConfirmationModal from '../Modals/Inputs/ConfirmationModal'
|
||||
import RepeatSection from './RepeatSection'
|
||||
const ASSIGN_STRATEGIES = [
|
||||
'random',
|
||||
'least_assigned',
|
||||
'least_completed',
|
||||
'keep_last_assigned',
|
||||
]
|
||||
const REPEAT_ON_TYPE = ['interval', 'days_of_the_week', 'day_of_the_month']
|
||||
|
||||
const NO_DUE_DATE_REQUIRED_TYPE = ['no_repeat', 'once']
|
||||
const NO_DUE_DATE_ALLOWED_TYPE = ['trigger']
|
||||
const ChoreEdit = () => {
|
||||
const { userProfile, setUserProfile } = useContext(UserContext)
|
||||
const [chore, setChore] = useState([])
|
||||
const [choresHistory, setChoresHistory] = useState([])
|
||||
const [userHistory, setUserHistory] = useState({})
|
||||
const { choreId } = useParams()
|
||||
const [name, setName] = useState('')
|
||||
const [confirmModelConfig, setConfirmModelConfig] = useState({})
|
||||
const [assignees, setAssignees] = useState([])
|
||||
const [performers, setPerformers] = useState([])
|
||||
const [assignStrategy, setAssignStrategy] = useState(ASSIGN_STRATEGIES[2])
|
||||
const [dueDate, setDueDate] = useState(null)
|
||||
const [completed, setCompleted] = useState(false)
|
||||
const [completedDate, setCompletedDate] = useState('')
|
||||
const [assignedTo, setAssignedTo] = useState(-1)
|
||||
const [frequencyType, setFrequencyType] = useState('once')
|
||||
const [frequency, setFrequency] = useState(1)
|
||||
const [frequencyMetadata, setFrequencyMetadata] = useState({})
|
||||
const [labels, setLabels] = useState([])
|
||||
const [allUserThings, setAllUserThings] = useState([])
|
||||
const [thingTrigger, setThingTrigger] = useState({})
|
||||
const [isThingValid, setIsThingValid] = useState(false)
|
||||
|
||||
const [notificationMetadata, setNotificationMetadata] = useState({})
|
||||
|
||||
const [isRolling, setIsRolling] = useState(false)
|
||||
const [isNotificable, setIsNotificable] = useState(false)
|
||||
const [isActive, setIsActive] = useState(true)
|
||||
const [updatedBy, setUpdatedBy] = useState(0)
|
||||
const [createdBy, setCreatedBy] = useState(0)
|
||||
const [errors, setErrors] = useState({})
|
||||
const [attemptToSave, setAttemptToSave] = useState(false)
|
||||
|
||||
const Navigate = useNavigate()
|
||||
|
||||
const HandleValidateChore = () => {
|
||||
const errors = {}
|
||||
|
||||
if (name.trim() === '') {
|
||||
errors.name = 'Name is required'
|
||||
}
|
||||
if (assignees.length === 0) {
|
||||
errors.assignees = 'At least 1 assignees is required'
|
||||
}
|
||||
if (assignedTo < 0) {
|
||||
errors.assignedTo = 'Assigned to is required'
|
||||
}
|
||||
if (frequencyType === 'interval' && frequency < 1) {
|
||||
errors.frequency = 'Frequency is required'
|
||||
}
|
||||
if (
|
||||
frequencyType === 'days_of_the_week' &&
|
||||
frequencyMetadata['days']?.length === 0
|
||||
) {
|
||||
errors.frequency = 'At least 1 day is required'
|
||||
}
|
||||
if (
|
||||
frequencyType === 'day_of_the_month' &&
|
||||
frequencyMetadata['months']?.length === 0
|
||||
) {
|
||||
errors.frequency = 'At least 1 month is required'
|
||||
}
|
||||
if (
|
||||
dueDate === null &&
|
||||
!NO_DUE_DATE_REQUIRED_TYPE.includes(frequencyType) &&
|
||||
!NO_DUE_DATE_ALLOWED_TYPE.includes(frequencyType)
|
||||
) {
|
||||
if (REPEAT_ON_TYPE.includes(frequencyType)) {
|
||||
errors.dueDate = 'Start date is required'
|
||||
} else {
|
||||
errors.dueDate = 'Due date is required'
|
||||
}
|
||||
}
|
||||
if (frequencyType === 'trigger') {
|
||||
if (!isThingValid) {
|
||||
errors.thingTrigger = 'Thing trigger is invalid'
|
||||
}
|
||||
}
|
||||
|
||||
// if there is any error then return false:
|
||||
setErrors(errors)
|
||||
if (Object.keys(errors).length > 0) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
const HandleSaveChore = () => {
|
||||
setAttemptToSave(true)
|
||||
if (!HandleValidateChore()) {
|
||||
console.log('validation failed')
|
||||
console.log(errors)
|
||||
return
|
||||
}
|
||||
const chore = {
|
||||
id: Number(choreId),
|
||||
name: name,
|
||||
assignees: assignees,
|
||||
dueDate: dueDate ? new Date(dueDate).toISOString() : null,
|
||||
frequencyType: frequencyType,
|
||||
frequency: Number(frequency),
|
||||
frequencyMetadata: frequencyMetadata,
|
||||
assignedTo: assignedTo,
|
||||
assignStrategy: assignStrategy,
|
||||
isRolling: isRolling,
|
||||
isActive: isActive,
|
||||
notification: isNotificable,
|
||||
labels: labels,
|
||||
notificationMetadata: notificationMetadata,
|
||||
thingTrigger: thingTrigger,
|
||||
}
|
||||
let SaveFunction = CreateChore
|
||||
if (choreId > 0) {
|
||||
SaveFunction = SaveChore
|
||||
}
|
||||
|
||||
SaveFunction(chore).then(response => {
|
||||
if (response.status === 200) {
|
||||
Navigate(`/my/chores`)
|
||||
} else {
|
||||
alert('Failed to save chore')
|
||||
}
|
||||
})
|
||||
}
|
||||
useEffect(() => {
|
||||
//fetch performers:
|
||||
GetAllCircleMembers()
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
setPerformers(data.res)
|
||||
})
|
||||
GetThings().then(response => {
|
||||
response.json().then(data => {
|
||||
setAllUserThings(data.res)
|
||||
})
|
||||
})
|
||||
// fetch chores:
|
||||
if (choreId > 0) {
|
||||
GetChoreByID(choreId)
|
||||
.then(response => {
|
||||
if (response.status !== 200) {
|
||||
alert('You are not authorized to view this chore.')
|
||||
Navigate('/my/chores')
|
||||
return null
|
||||
} else {
|
||||
return response.json()
|
||||
}
|
||||
})
|
||||
.then(data => {
|
||||
setChore(data.res)
|
||||
setName(data.res.name ? data.res.name : '')
|
||||
setAssignees(data.res.assignees ? data.res.assignees : [])
|
||||
setAssignedTo(data.res.assignedTo)
|
||||
setFrequencyType(
|
||||
data.res.frequencyType ? data.res.frequencyType : 'once',
|
||||
)
|
||||
|
||||
setFrequencyMetadata(JSON.parse(data.res.frequencyMetadata))
|
||||
setFrequency(data.res.frequency)
|
||||
|
||||
setNotificationMetadata(JSON.parse(data.res.notificationMetadata))
|
||||
setLabels(data.res.labels ? data.res.labels.split(',') : [])
|
||||
|
||||
setAssignStrategy(
|
||||
data.res.assignStrategy
|
||||
? data.res.assignStrategy
|
||||
: ASSIGN_STRATEGIES[2],
|
||||
)
|
||||
setIsRolling(data.res.isRolling)
|
||||
setIsActive(data.res.isActive)
|
||||
// parse the due date to a string from this format "2021-10-10T00:00:00.000Z"
|
||||
// use moment.js or date-fns to format the date for to be usable in the input field:
|
||||
setDueDate(
|
||||
data.res.nextDueDate
|
||||
? moment(data.res.nextDueDate).format('YYYY-MM-DDTHH:mm:ss')
|
||||
: null,
|
||||
)
|
||||
setUpdatedBy(data.res.updatedBy)
|
||||
setCreatedBy(data.res.createdBy)
|
||||
setIsNotificable(data.res.notification)
|
||||
setThingTrigger(data.res.thingChore)
|
||||
// setDueDate(data.res.dueDate)
|
||||
// setCompleted(data.res.completed)
|
||||
// setCompletedDate(data.res.completedDate)
|
||||
})
|
||||
|
||||
// fetch chores history:
|
||||
GetChoreHistory(choreId)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
setChoresHistory(data.res)
|
||||
const newUserChoreHistory = {}
|
||||
data.res.forEach(choreHistory => {
|
||||
if (newUserChoreHistory[choreHistory.completedBy]) {
|
||||
newUserChoreHistory[choreHistory.completedBy] += 1
|
||||
} else {
|
||||
newUserChoreHistory[choreHistory.completedBy] = 1
|
||||
}
|
||||
})
|
||||
|
||||
setUserHistory(newUserChoreHistory)
|
||||
})
|
||||
}
|
||||
// set focus on the first input field:
|
||||
else {
|
||||
// new task/ chore set focus on the first input field:
|
||||
document.querySelector('input').focus()
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
// if frequancy type change to somthing need a due date then set it to the current date:
|
||||
if (!NO_DUE_DATE_REQUIRED_TYPE.includes(frequencyType) && !dueDate) {
|
||||
setDueDate(moment(new Date()).format('YYYY-MM-DDTHH:mm:00'))
|
||||
}
|
||||
if (NO_DUE_DATE_ALLOWED_TYPE.includes(frequencyType)) {
|
||||
setDueDate(null)
|
||||
}
|
||||
}, [frequencyType])
|
||||
|
||||
useEffect(() => {
|
||||
if (assignees.length === 1) {
|
||||
setAssignedTo(assignees[0].userId)
|
||||
}
|
||||
}, [assignees])
|
||||
|
||||
useEffect(() => {
|
||||
if (performers.length > 0 && assignees.length === 0) {
|
||||
setAssignees([
|
||||
{
|
||||
userId: userProfile.id,
|
||||
},
|
||||
])
|
||||
}
|
||||
}, [performers])
|
||||
|
||||
// if user resolve the error trigger validation to remove the error message from the respective field
|
||||
useEffect(() => {
|
||||
if (attemptToSave) {
|
||||
HandleValidateChore()
|
||||
}
|
||||
}, [assignees, name, frequencyMetadata, attemptToSave, dueDate])
|
||||
|
||||
const handleDelete = () => {
|
||||
setConfirmModelConfig({
|
||||
isOpen: true,
|
||||
title: 'Delete Chore',
|
||||
confirmText: 'Delete',
|
||||
cancelText: 'Cancel',
|
||||
message: 'Are you sure you want to delete this chore?',
|
||||
onClose: isConfirmed => {
|
||||
if (isConfirmed === true) {
|
||||
DeleteChore(choreId).then(response => {
|
||||
if (response.status === 200) {
|
||||
Navigate('/my/chores')
|
||||
} else {
|
||||
alert('Failed to delete chore')
|
||||
}
|
||||
})
|
||||
}
|
||||
setConfirmModelConfig({})
|
||||
},
|
||||
})
|
||||
}
|
||||
return (
|
||||
<Container maxWidth='md'>
|
||||
{/* <Typography level='h3' mb={1.5}>
|
||||
Edit Chore
|
||||
</Typography> */}
|
||||
<Box>
|
||||
<FormControl error={errors.name}>
|
||||
<Typography level='h4'>Descritpion :</Typography>
|
||||
<Typography level='h5'>What is this chore about?</Typography>
|
||||
<Input value={name} onChange={e => setName(e.target.value)} />
|
||||
<FormHelperText error>{errors.name}</FormHelperText>
|
||||
</FormControl>
|
||||
</Box>
|
||||
<Box mt={2}>
|
||||
<Typography level='h4'>Assignees :</Typography>
|
||||
<Typography level='h5'>Who can do this chore?</Typography>
|
||||
<Card>
|
||||
<List
|
||||
orientation='horizontal'
|
||||
wrap
|
||||
sx={{
|
||||
'--List-gap': '8px',
|
||||
'--ListItem-radius': '20px',
|
||||
}}
|
||||
>
|
||||
{performers?.map((item, index) => (
|
||||
<ListItem key={item.id}>
|
||||
<Checkbox
|
||||
// disabled={index === 0}
|
||||
checked={assignees.find(a => a.userId == item.id) != null}
|
||||
onClick={() => {
|
||||
if (assignees.find(a => a.userId == item.id)) {
|
||||
setAssignees(assignees.filter(i => i.userId !== item.id))
|
||||
} else {
|
||||
setAssignees([...assignees, { userId: item.id }])
|
||||
}
|
||||
}}
|
||||
overlay
|
||||
disableIcon
|
||||
variant='soft'
|
||||
label={item.displayName}
|
||||
/>
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
</Card>
|
||||
<FormControl error={Boolean(errors.assignee)}>
|
||||
<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='h4'>Assigned :</Typography>
|
||||
<Typography level='h5'>
|
||||
Who is assigned the next due chore?
|
||||
</Typography>
|
||||
|
||||
<Select
|
||||
placeholder={
|
||||
assignees.length === 0
|
||||
? 'No Assignees yet can perform this chore'
|
||||
: 'Select an assignee for this chore'
|
||||
}
|
||||
disabled={assignees.length === 0}
|
||||
value={assignedTo > -1 ? assignedTo : null}
|
||||
>
|
||||
{performers
|
||||
?.filter(p => assignees.find(a => a.userId == p.userId))
|
||||
.map((item, index) => (
|
||||
<Option
|
||||
value={item.id}
|
||||
key={item.displayName}
|
||||
onClick={() => {
|
||||
setAssignedTo(item.id)
|
||||
}}
|
||||
>
|
||||
{item.displayName}
|
||||
{/* <Chip size='sm' color='neutral' variant='soft'>
|
||||
</Chip> */}
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
</Box>
|
||||
<Box mt={2}>
|
||||
<Typography level='h4'>Picking Mode :</Typography>
|
||||
<Typography level='h5'>
|
||||
How to pick the next assignee for the following chore?
|
||||
</Typography>
|
||||
|
||||
<Card>
|
||||
<List
|
||||
orientation='horizontal'
|
||||
wrap
|
||||
sx={{
|
||||
'--List-gap': '8px',
|
||||
'--ListItem-radius': '20px',
|
||||
}}
|
||||
>
|
||||
{ASSIGN_STRATEGIES.map((item, idx) => (
|
||||
<ListItem key={item}>
|
||||
<Checkbox
|
||||
// disabled={index === 0}
|
||||
checked={assignStrategy === item}
|
||||
onClick={() => setAssignStrategy(item)}
|
||||
overlay
|
||||
disableIcon
|
||||
variant='soft'
|
||||
label={item
|
||||
.split('_')
|
||||
.map(x => x.charAt(0).toUpperCase() + x.slice(1))
|
||||
.join(' ')}
|
||||
/>
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
</Card>
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
<RepeatSection
|
||||
frequency={frequency}
|
||||
onFrequencyUpdate={setFrequency}
|
||||
frequencyType={frequencyType}
|
||||
onFrequencyTypeUpdate={setFrequencyType}
|
||||
frequencyMetadata={frequencyMetadata}
|
||||
onFrequencyMetadataUpdate={setFrequencyMetadata}
|
||||
frequencyError={errors?.frequency}
|
||||
allUserThings={allUserThings}
|
||||
onTriggerUpdate={thingUpdate => {
|
||||
if (thingUpdate === null) {
|
||||
setThingTrigger(null)
|
||||
return
|
||||
}
|
||||
setThingTrigger({
|
||||
triggerState: thingUpdate.triggerState,
|
||||
condition: thingUpdate.condition,
|
||||
thingID: thingUpdate.thing.id,
|
||||
})
|
||||
}}
|
||||
OnTriggerValidate={setIsThingValid}
|
||||
isAttemptToSave={attemptToSave}
|
||||
selectedThing={thingTrigger}
|
||||
/>
|
||||
|
||||
<Box mt={2}>
|
||||
<Typography level='h4'>
|
||||
{REPEAT_ON_TYPE.includes(frequencyType) ? 'Start date' : 'Due date'} :
|
||||
</Typography>
|
||||
{frequencyType === 'trigger' && !dueDate && (
|
||||
<Typography level='body-sm'>
|
||||
Due Date will be set when the trigger of the thing is met
|
||||
</Typography>
|
||||
)}
|
||||
|
||||
{NO_DUE_DATE_REQUIRED_TYPE.includes(frequencyType) && (
|
||||
<FormControl sx={{ mt: 1 }}>
|
||||
<Checkbox
|
||||
onChange={e => {
|
||||
if (e.target.checked) {
|
||||
setDueDate(moment(new Date()).format('YYYY-MM-DDTHH:mm:00'))
|
||||
} else {
|
||||
setDueDate(null)
|
||||
}
|
||||
}}
|
||||
defaultChecked={dueDate !== null}
|
||||
checked={dueDate !== null}
|
||||
value={dueDate !== null}
|
||||
overlay
|
||||
label='Give this task a due date'
|
||||
/>
|
||||
<FormHelperText>
|
||||
task needs to be completed by a specific time.
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
)}
|
||||
{dueDate && (
|
||||
<FormControl error={Boolean(errors.dueDate)}>
|
||||
<Typography level='h5'>
|
||||
{REPEAT_ON_TYPE.includes(frequencyType)
|
||||
? 'When does this chore start?'
|
||||
: 'When is the next first time this chore is due?'}
|
||||
</Typography>
|
||||
<Input
|
||||
type='datetime-local'
|
||||
value={dueDate}
|
||||
onChange={e => {
|
||||
setDueDate(e.target.value)
|
||||
}}
|
||||
/>
|
||||
<FormHelperText>{errors.dueDate}</FormHelperText>
|
||||
</FormControl>
|
||||
)}
|
||||
</Box>
|
||||
{!['once', 'no_repeat'].includes(frequencyType) && (
|
||||
<Box mt={2}>
|
||||
<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
|
||||
overlay
|
||||
checked={!isRolling}
|
||||
onClick={() => setIsRolling(false)}
|
||||
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
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
<FormControl>
|
||||
<Radio
|
||||
overlay
|
||||
checked={isRolling}
|
||||
onClick={() => setIsRolling(true)}
|
||||
label='Reschedule from completion date'
|
||||
/>
|
||||
<FormHelperText>
|
||||
the next task will be scheduled from the actual completion date
|
||||
of the previous task
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
</RadioGroup>
|
||||
</Box>
|
||||
)}
|
||||
<Box mt={2}>
|
||||
<Typography level='h4'>Notifications : </Typography>
|
||||
<Typography level='h5'>
|
||||
Get Reminders when this task is due or completed
|
||||
{!isPlusAccount(userProfile) && (
|
||||
<Chip variant='soft' color='warning'>
|
||||
Not available in Basic Plan
|
||||
</Chip>
|
||||
)}
|
||||
</Typography>
|
||||
|
||||
<FormControl sx={{ mt: 1 }}>
|
||||
<Checkbox
|
||||
onChange={e => {
|
||||
setIsNotificable(e.target.checked)
|
||||
}}
|
||||
defaultChecked={isNotificable}
|
||||
checked={isNotificable}
|
||||
value={isNotificable}
|
||||
disabled={!isPlusAccount(userProfile)}
|
||||
overlay
|
||||
label='Notify for this task'
|
||||
/>
|
||||
<FormHelperText
|
||||
sx={{
|
||||
opacity: !isPlusAccount(userProfile) ? 0.5 : 1,
|
||||
}}
|
||||
>
|
||||
Receive notifications for this task
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
</Box>
|
||||
{isNotificable && (
|
||||
<Box
|
||||
ml={4}
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: 2,
|
||||
|
||||
'& > div': { p: 2, borderRadius: 'md', display: 'flex' },
|
||||
}}
|
||||
>
|
||||
<Card variant='outlined'>
|
||||
<Typography level='h5'>
|
||||
What things should trigger the notification?
|
||||
</Typography>
|
||||
{[
|
||||
{
|
||||
title: 'Due Date/Time',
|
||||
description: 'A simple reminder that a task is due',
|
||||
id: 'dueDate',
|
||||
},
|
||||
// {
|
||||
// title: 'Upon Completion',
|
||||
// description: 'A notification when a task is completed',
|
||||
// id: 'completion',
|
||||
// },
|
||||
{
|
||||
title: 'Predued',
|
||||
description: 'before a task is due in few hours',
|
||||
id: 'predue',
|
||||
},
|
||||
{
|
||||
title: 'Overdue',
|
||||
description: 'A notification when a task is overdue',
|
||||
},
|
||||
{
|
||||
title: 'Nagging',
|
||||
description: 'Daily reminders until the task is completed',
|
||||
id: 'nagging',
|
||||
},
|
||||
].map(item => (
|
||||
<FormControl sx={{ mb: 1 }} key={item.id}>
|
||||
<Checkbox
|
||||
overlay
|
||||
onClick={() => {
|
||||
setNotificationMetadata({
|
||||
...notificationMetadata,
|
||||
[item.id]: !notificationMetadata[item.id],
|
||||
})
|
||||
}}
|
||||
checked={
|
||||
notificationMetadata ? notificationMetadata[item.id] : false
|
||||
}
|
||||
label={item.title}
|
||||
key={item.title}
|
||||
/>
|
||||
<FormHelperText>{item.description}</FormHelperText>
|
||||
</FormControl>
|
||||
))}
|
||||
</Card>
|
||||
</Box>
|
||||
)}
|
||||
<Box mt={2}>
|
||||
<Typography level='h4'>Labels :</Typography>
|
||||
<Typography level='h5'>
|
||||
Things to remember about this chore or to tag it
|
||||
</Typography>
|
||||
<FreeSoloCreateOption
|
||||
options={labels}
|
||||
onSelectChange={changes => {
|
||||
const newLabels = []
|
||||
changes.map(change => {
|
||||
// if type is string :
|
||||
if (typeof change === 'string') {
|
||||
// add the change to the labels array:
|
||||
newLabels.push(change)
|
||||
} else {
|
||||
newLabels.push(change.inputValue)
|
||||
}
|
||||
})
|
||||
setLabels(newLabels)
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
{choreId > 0 && (
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.5 }}>
|
||||
<Sheet
|
||||
sx={{
|
||||
p: 2,
|
||||
borderRadius: 'md',
|
||||
boxShadow: 'sm',
|
||||
}}
|
||||
>
|
||||
<Typography level='body1'>
|
||||
Created by{' '}
|
||||
<Chip variant='solid'>
|
||||
{performers.find(f => f.id === createdBy)?.displayName}
|
||||
</Chip>{' '}
|
||||
{moment(chore.createdAt).fromNow()}
|
||||
</Typography>
|
||||
{(chore.updatedAt && updatedBy > 0 && (
|
||||
<>
|
||||
<Divider sx={{ my: 1 }} />
|
||||
|
||||
<Typography level='body1'>
|
||||
Updated by{' '}
|
||||
<Chip variant='solid'>
|
||||
{performers.find(f => f.id === updatedBy)?.displayName}
|
||||
</Chip>{' '}
|
||||
{moment(chore.updatedAt).fromNow()}
|
||||
</Typography>
|
||||
</>
|
||||
)) || <></>}
|
||||
</Sheet>
|
||||
</Box>
|
||||
)}
|
||||
<Divider sx={{ mb: 9 }} />
|
||||
|
||||
{/* <Box mt={2} alignSelf={'flex-start'} display='flex' gap={2}>
|
||||
<Button onClick={SaveChore}>Save</Button>
|
||||
</Box> */}
|
||||
<Sheet
|
||||
variant='outlined'
|
||||
sx={{
|
||||
position: 'fixed',
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
p: 2, // padding
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-end',
|
||||
gap: 2,
|
||||
'z-index': 1000,
|
||||
bgcolor: 'background.body',
|
||||
boxShadow: 'md', // Add a subtle shadow
|
||||
}}
|
||||
>
|
||||
{choreId > 0 && (
|
||||
<Button
|
||||
color='danger'
|
||||
variant='solid'
|
||||
onClick={() => {
|
||||
// confirm before deleting:
|
||||
handleDelete()
|
||||
}}
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
color='neutral'
|
||||
variant='outlined'
|
||||
onClick={() => {
|
||||
window.history.back()
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button color='primary' variant='solid' onClick={HandleSaveChore}>
|
||||
{choreId > 0 ? 'Save' : 'Create'}
|
||||
</Button>
|
||||
</Sheet>
|
||||
<ConfirmationModal config={confirmModelConfig} />
|
||||
{/* <ChoreHistory ChoreHistory={choresHistory} UsersData={performers} /> */}
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
export default ChoreEdit
|
496
src/views/ChoreEdit/RepeatSection.jsx
Normal file
496
src/views/ChoreEdit/RepeatSection.jsx
Normal file
|
@ -0,0 +1,496 @@
|
|||
import {
|
||||
Box,
|
||||
Card,
|
||||
Checkbox,
|
||||
Chip,
|
||||
FormControl,
|
||||
FormHelperText,
|
||||
Grid,
|
||||
Input,
|
||||
List,
|
||||
ListItem,
|
||||
Option,
|
||||
Radio,
|
||||
RadioGroup,
|
||||
Select,
|
||||
Typography,
|
||||
} from '@mui/joy'
|
||||
import { useContext, useState } from 'react'
|
||||
import { UserContext } from '../../contexts/UserContext'
|
||||
import { isPlusAccount } from '../../utils/Helpers'
|
||||
import ThingTriggerSection from './ThingTriggerSection'
|
||||
|
||||
const FREQUANCY_TYPES_RADIOS = [
|
||||
'daily',
|
||||
'weekly',
|
||||
'monthly',
|
||||
'yearly',
|
||||
'adaptive',
|
||||
'custom',
|
||||
]
|
||||
|
||||
const FREQUENCY_TYPE_MESSAGE = {
|
||||
adaptive:
|
||||
'This chore will be scheduled dynamically based on previous completion dates.',
|
||||
custom: 'This chore will be scheduled based on a custom frequency.',
|
||||
}
|
||||
const REPEAT_ON_TYPE = ['interval', 'days_of_the_week', 'day_of_the_month']
|
||||
const FREQUANCY_TYPES = [
|
||||
'once',
|
||||
'daily',
|
||||
'weekly',
|
||||
'monthly',
|
||||
'yearly',
|
||||
'adaptive',
|
||||
...REPEAT_ON_TYPE,
|
||||
]
|
||||
const MONTH_WITH_NO_31_DAYS = [
|
||||
// TODO: Handle these months if day is 31
|
||||
'february',
|
||||
'april',
|
||||
'june',
|
||||
'september',
|
||||
'november',
|
||||
]
|
||||
const RepeatOnSections = ({
|
||||
frequencyType,
|
||||
frequency,
|
||||
onFrequencyUpdate,
|
||||
onFrequencyTypeUpdate,
|
||||
frequencyMetadata,
|
||||
onFrequencyMetadataUpdate,
|
||||
things,
|
||||
}) => {
|
||||
const [months, setMonths] = useState({})
|
||||
// const [dayOftheMonth, setDayOftheMonth] = useState(1)
|
||||
const [daysOfTheWeek, setDaysOfTheWeek] = useState({})
|
||||
const [monthsOfTheYear, setMonthsOfTheYear] = useState({})
|
||||
const [intervalUnit, setIntervalUnit] = useState('days')
|
||||
|
||||
switch (frequencyType) {
|
||||
case 'interval':
|
||||
return (
|
||||
<Grid item sm={12} sx={{ display: 'flex', alignItems: 'center' }}>
|
||||
<Typography level='h5'>Every: </Typography>
|
||||
<Input
|
||||
type='number'
|
||||
value={frequency}
|
||||
onChange={e => {
|
||||
if (e.target.value < 1) {
|
||||
e.target.value = 1
|
||||
}
|
||||
onFrequencyUpdate(e.target.value)
|
||||
}}
|
||||
/>
|
||||
<Select placeholder='Unit' value={intervalUnit}>
|
||||
{['hours', 'days', 'weeks', 'months', 'years'].map(item => (
|
||||
<Option
|
||||
key={item}
|
||||
value={item}
|
||||
onClick={() => {
|
||||
setIntervalUnit(item)
|
||||
onFrequencyMetadataUpdate({
|
||||
unit: item,
|
||||
})
|
||||
}}
|
||||
>
|
||||
{item.charAt(0).toUpperCase() + item.slice(1)}
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
</Grid>
|
||||
)
|
||||
case 'days_of_the_week':
|
||||
return (
|
||||
<Grid item sm={12} sx={{ display: 'flex', alignItems: 'center' }}>
|
||||
<Card>
|
||||
<List
|
||||
orientation='horizontal'
|
||||
wrap
|
||||
sx={{
|
||||
'--List-gap': '8px',
|
||||
'--ListItem-radius': '20px',
|
||||
}}
|
||||
>
|
||||
{[
|
||||
'monday',
|
||||
'tuesday',
|
||||
'wednesday',
|
||||
'thursday',
|
||||
'friday',
|
||||
'saturday',
|
||||
'sunday',
|
||||
].map(item => (
|
||||
<ListItem key={item}>
|
||||
<Checkbox
|
||||
// disabled={index === 0}
|
||||
|
||||
checked={frequencyMetadata?.days?.includes(item) || false}
|
||||
onClick={() => {
|
||||
const newDaysOfTheWeek = frequencyMetadata['days'] || []
|
||||
if (newDaysOfTheWeek.includes(item)) {
|
||||
newDaysOfTheWeek.splice(
|
||||
newDaysOfTheWeek.indexOf(item),
|
||||
1,
|
||||
)
|
||||
} else {
|
||||
newDaysOfTheWeek.push(item)
|
||||
}
|
||||
|
||||
onFrequencyMetadataUpdate({
|
||||
days: newDaysOfTheWeek.sort(),
|
||||
})
|
||||
}}
|
||||
overlay
|
||||
disableIcon
|
||||
variant='soft'
|
||||
label={item.charAt(0).toUpperCase() + item.slice(1)}
|
||||
/>
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
</Card>
|
||||
</Grid>
|
||||
)
|
||||
case 'day_of_the_month':
|
||||
return (
|
||||
<Grid
|
||||
item
|
||||
sm={12}
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'space-between',
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
mb: 1.5,
|
||||
}}
|
||||
>
|
||||
<Typography>on the </Typography>
|
||||
<Input
|
||||
sx={{ width: '80px' }}
|
||||
type='number'
|
||||
value={frequency}
|
||||
onChange={e => {
|
||||
if (e.target.value < 1) {
|
||||
e.target.value = 1
|
||||
} else if (e.target.value > 31) {
|
||||
e.target.value = 31
|
||||
}
|
||||
// setDayOftheMonth(e.target.value)
|
||||
|
||||
onFrequencyUpdate(e.target.value)
|
||||
}}
|
||||
/>
|
||||
<Typography>of the following month/s: </Typography>
|
||||
</Box>
|
||||
<Card>
|
||||
<List
|
||||
orientation='horizontal'
|
||||
wrap
|
||||
sx={{
|
||||
'--List-gap': '8px',
|
||||
'--ListItem-radius': '20px',
|
||||
}}
|
||||
>
|
||||
{[
|
||||
'january',
|
||||
'february',
|
||||
'march',
|
||||
'april',
|
||||
'may',
|
||||
'june',
|
||||
'july',
|
||||
'august',
|
||||
'september',
|
||||
'october',
|
||||
'november',
|
||||
'december',
|
||||
].map(item => (
|
||||
<ListItem key={item}>
|
||||
<Checkbox
|
||||
// disabled={index === 0}
|
||||
checked={frequencyMetadata?.months?.includes(item)}
|
||||
// checked={months[item] || false}
|
||||
// onClick={() => {
|
||||
// const newMonthsOfTheYear = {
|
||||
// ...monthsOfTheYear,
|
||||
// }
|
||||
// newMonthsOfTheYear[item] = !newMonthsOfTheYear[item]
|
||||
// onFrequencyMetadataUpdate({
|
||||
// months: newMonthsOfTheYear,
|
||||
// })
|
||||
// setMonthsOfTheYear(newMonthsOfTheYear)
|
||||
// }}
|
||||
onClick={() => {
|
||||
const newMonthsOfTheYear =
|
||||
frequencyMetadata['months'] || []
|
||||
if (newMonthsOfTheYear.includes(item)) {
|
||||
newMonthsOfTheYear.splice(
|
||||
newMonthsOfTheYear.indexOf(item),
|
||||
1,
|
||||
)
|
||||
} else {
|
||||
newMonthsOfTheYear.push(item)
|
||||
}
|
||||
|
||||
onFrequencyMetadataUpdate({
|
||||
months: newMonthsOfTheYear.sort(),
|
||||
})
|
||||
console.log('newMonthsOfTheYear', newMonthsOfTheYear)
|
||||
// setDaysOfTheWeek(newDaysOfTheWeek)
|
||||
}}
|
||||
overlay
|
||||
disableIcon
|
||||
variant='soft'
|
||||
label={item.charAt(0).toUpperCase() + item.slice(1)}
|
||||
/>
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
</Card>
|
||||
</Grid>
|
||||
)
|
||||
|
||||
default:
|
||||
return <></>
|
||||
}
|
||||
}
|
||||
|
||||
const RepeatSection = ({
|
||||
frequencyType,
|
||||
frequency,
|
||||
onFrequencyUpdate,
|
||||
onFrequencyTypeUpdate,
|
||||
frequencyMetadata,
|
||||
onFrequencyMetadataUpdate,
|
||||
frequencyError,
|
||||
allUserThings,
|
||||
onTriggerUpdate,
|
||||
OnTriggerValidate,
|
||||
isAttemptToSave,
|
||||
selectedThing,
|
||||
}) => {
|
||||
const [repeatOn, setRepeatOn] = useState('interval')
|
||||
const { userProfile, setUserProfile } = useContext(UserContext)
|
||||
return (
|
||||
<Box mt={2}>
|
||||
<Typography level='h4'>Repeat :</Typography>
|
||||
<FormControl sx={{ mt: 1 }}>
|
||||
<Checkbox
|
||||
onChange={e => {
|
||||
onFrequencyTypeUpdate(e.target.checked ? 'daily' : 'once')
|
||||
if (e.target.checked) {
|
||||
onTriggerUpdate(null)
|
||||
}
|
||||
}}
|
||||
defaultChecked={!['once', 'trigger'].includes(frequencyType)}
|
||||
checked={!['once', 'trigger'].includes(frequencyType)}
|
||||
value={!['once', 'trigger'].includes(frequencyType)}
|
||||
overlay
|
||||
label='Repeat this task'
|
||||
/>
|
||||
<FormHelperText>
|
||||
Is this something needed to be done regularly?
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
{!['once', 'trigger'].includes(frequencyType) && (
|
||||
<>
|
||||
<Card sx={{ mt: 1 }}>
|
||||
<Typography level='h5'>How often should it be repeated?</Typography>
|
||||
|
||||
<List
|
||||
orientation='horizontal'
|
||||
wrap
|
||||
sx={{
|
||||
'--List-gap': '8px',
|
||||
'--ListItem-radius': '20px',
|
||||
}}
|
||||
>
|
||||
{FREQUANCY_TYPES_RADIOS.map((item, index) => (
|
||||
<ListItem key={item}>
|
||||
<Checkbox
|
||||
// disabled={index === 0}
|
||||
checked={
|
||||
item === frequencyType ||
|
||||
(item === 'custom' &&
|
||||
REPEAT_ON_TYPE.includes(frequencyType))
|
||||
}
|
||||
// defaultChecked={item === frequencyType}
|
||||
onClick={() => {
|
||||
if (item === 'custom') {
|
||||
onFrequencyTypeUpdate(REPEAT_ON_TYPE[0])
|
||||
onFrequencyUpdate(1)
|
||||
onFrequencyMetadataUpdate({
|
||||
unit: 'days',
|
||||
})
|
||||
return
|
||||
}
|
||||
onFrequencyTypeUpdate(item)
|
||||
}}
|
||||
overlay
|
||||
disableIcon
|
||||
variant='soft'
|
||||
label={
|
||||
item.charAt(0).toUpperCase() +
|
||||
item.slice(1).replace('_', ' ')
|
||||
}
|
||||
/>
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
<Typography>{FREQUENCY_TYPE_MESSAGE[frequencyType]}</Typography>
|
||||
{frequencyType === 'custom' ||
|
||||
(REPEAT_ON_TYPE.includes(frequencyType) && (
|
||||
<>
|
||||
<Grid container spacing={1} mt={2}>
|
||||
<Grid item>
|
||||
<Typography>Repeat on:</Typography>
|
||||
<Box
|
||||
sx={{ display: 'flex', alignItems: 'center', gap: 2 }}
|
||||
>
|
||||
<RadioGroup
|
||||
orientation='horizontal'
|
||||
aria-labelledby='segmented-controls-example'
|
||||
name='justify'
|
||||
// value={justify}
|
||||
// onChange={event => setJustify(event.target.value)}
|
||||
sx={{
|
||||
minHeight: 48,
|
||||
padding: '4px',
|
||||
borderRadius: '12px',
|
||||
bgcolor: 'neutral.softBg',
|
||||
'--RadioGroup-gap': '4px',
|
||||
'--Radio-actionRadius': '8px',
|
||||
mb: 1,
|
||||
}}
|
||||
>
|
||||
{REPEAT_ON_TYPE.map(item => (
|
||||
<Radio
|
||||
key={item}
|
||||
color='neutral'
|
||||
checked={item === frequencyType}
|
||||
onClick={() => {
|
||||
if (
|
||||
item === 'day_of_the_month' ||
|
||||
item === 'interval'
|
||||
) {
|
||||
onFrequencyUpdate(1)
|
||||
}
|
||||
onFrequencyTypeUpdate(item)
|
||||
if (item === 'days_of_the_week') {
|
||||
onFrequencyMetadataUpdate({ days: [] })
|
||||
} else if (item === 'day_of_the_month') {
|
||||
onFrequencyMetadataUpdate({ months: [] })
|
||||
} else if (item === 'interval') {
|
||||
onFrequencyMetadataUpdate({ unit: 'days' })
|
||||
}
|
||||
// setRepeatOn(item)
|
||||
}}
|
||||
value={item}
|
||||
disableIcon
|
||||
label={item
|
||||
.split('_')
|
||||
.map((i, idx) => {
|
||||
// first or last word
|
||||
if (
|
||||
idx === 0 ||
|
||||
idx === item.split('_').length - 1
|
||||
) {
|
||||
return (
|
||||
i.charAt(0).toUpperCase() + i.slice(1)
|
||||
)
|
||||
}
|
||||
return i
|
||||
})
|
||||
.join(' ')}
|
||||
variant='plain'
|
||||
sx={{
|
||||
px: 2,
|
||||
alignItems: 'center',
|
||||
}}
|
||||
slotProps={{
|
||||
action: ({ checked }) => ({
|
||||
sx: {
|
||||
...(checked && {
|
||||
bgcolor: 'background.surface',
|
||||
boxShadow: 'sm',
|
||||
'&:hover': {
|
||||
bgcolor: 'background.surface',
|
||||
},
|
||||
}),
|
||||
},
|
||||
}),
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</RadioGroup>
|
||||
</Box>
|
||||
</Grid>
|
||||
|
||||
<RepeatOnSections
|
||||
frequency={frequency}
|
||||
onFrequencyUpdate={onFrequencyUpdate}
|
||||
frequencyType={frequencyType}
|
||||
onFrequencyTypeUpdate={onFrequencyTypeUpdate}
|
||||
frequencyMetadata={frequencyMetadata || {}}
|
||||
onFrequencyMetadataUpdate={onFrequencyMetadataUpdate}
|
||||
things={allUserThings}
|
||||
/>
|
||||
</Grid>
|
||||
</>
|
||||
))}
|
||||
<FormControl error={Boolean(frequencyError)}>
|
||||
<FormHelperText error>{frequencyError}</FormHelperText>
|
||||
</FormControl>
|
||||
</Card>
|
||||
</>
|
||||
)}
|
||||
<FormControl sx={{ mt: 1 }}>
|
||||
<Checkbox
|
||||
onChange={e => {
|
||||
onFrequencyTypeUpdate(e.target.checked ? 'trigger' : 'once')
|
||||
// if unchecked, set selectedThing to null:
|
||||
if (!e.target.checked) {
|
||||
onTriggerUpdate(null)
|
||||
}
|
||||
}}
|
||||
defaultChecked={frequencyType === 'trigger'}
|
||||
checked={frequencyType === 'trigger'}
|
||||
value={frequencyType === 'trigger'}
|
||||
disabled={!isPlusAccount(userProfile)}
|
||||
overlay
|
||||
label='Trigger this task based on a thing state'
|
||||
/>
|
||||
<FormHelperText
|
||||
sx={{
|
||||
opacity: !isPlusAccount(userProfile) ? 0.5 : 1,
|
||||
}}
|
||||
>
|
||||
Is this something that should be done when a thing state changes?{' '}
|
||||
{!isPlusAccount(userProfile) && (
|
||||
<Chip variant='soft' color='warning'>
|
||||
Not available in Basic Plan
|
||||
</Chip>
|
||||
)}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
{frequencyType === 'trigger' && (
|
||||
<ThingTriggerSection
|
||||
things={allUserThings}
|
||||
onTriggerUpdate={onTriggerUpdate}
|
||||
onValidate={OnTriggerValidate}
|
||||
isAttemptToSave={isAttemptToSave}
|
||||
selected={selectedThing}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
export default RepeatSection
|
230
src/views/ChoreEdit/ThingTriggerSection.jsx
Normal file
230
src/views/ChoreEdit/ThingTriggerSection.jsx
Normal file
|
@ -0,0 +1,230 @@
|
|||
import { Widgets } from '@mui/icons-material'
|
||||
import {
|
||||
Autocomplete,
|
||||
Box,
|
||||
Button,
|
||||
Card,
|
||||
Chip,
|
||||
FormControl,
|
||||
FormLabel,
|
||||
Input,
|
||||
ListItem,
|
||||
ListItemContent,
|
||||
ListItemDecorator,
|
||||
Option,
|
||||
Select,
|
||||
TextField,
|
||||
Typography,
|
||||
} from '@mui/joy'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
const isValidTrigger = (thing, condition, triggerState) => {
|
||||
const newErrors = {}
|
||||
if (!thing || !triggerState) {
|
||||
newErrors.thing = 'Please select a thing and trigger state'
|
||||
return false
|
||||
}
|
||||
if (thing.type === 'boolean') {
|
||||
if (['true', 'false'].includes(triggerState)) {
|
||||
return true
|
||||
} else {
|
||||
newErrors.type = 'Boolean type does not require a condition'
|
||||
return false
|
||||
}
|
||||
}
|
||||
if (thing.type === 'number') {
|
||||
if (isNaN(triggerState)) {
|
||||
newErrors.triggerState = 'Trigger state must be a number'
|
||||
return false
|
||||
}
|
||||
if (['eq', 'neq', 'gt', 'gte', 'lt', 'lte'].includes(condition)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
if (thing.type === 'text') {
|
||||
if (typeof triggerState === 'string') {
|
||||
return true
|
||||
}
|
||||
}
|
||||
newErrors.triggerState = 'Trigger state must be a number'
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
const ThingTriggerSection = ({
|
||||
things,
|
||||
onTriggerUpdate,
|
||||
onValidate,
|
||||
selected,
|
||||
isAttepmtingToSave,
|
||||
}) => {
|
||||
const [selectedThing, setSelectedThing] = useState(null)
|
||||
const [condition, setCondition] = useState(null)
|
||||
const [triggerState, setTriggerState] = useState(null)
|
||||
const navigate = useNavigate()
|
||||
|
||||
useEffect(() => {
|
||||
if (selected) {
|
||||
setSelectedThing(things?.find(t => t.id === selected.thingId))
|
||||
setCondition(selected.condition)
|
||||
setTriggerState(selected.triggerState)
|
||||
}
|
||||
}, [things])
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedThing && triggerState) {
|
||||
onTriggerUpdate({
|
||||
thing: selectedThing,
|
||||
condition: condition,
|
||||
triggerState: triggerState,
|
||||
})
|
||||
}
|
||||
if (isValidTrigger(selectedThing, condition, triggerState)) {
|
||||
onValidate(true)
|
||||
} else {
|
||||
onValidate(false)
|
||||
}
|
||||
}, [selectedThing, condition, triggerState])
|
||||
|
||||
return (
|
||||
<Card sx={{ mt: 1 }}>
|
||||
<Typography level='h5'>
|
||||
Trigger a task when a thing state changes to a desired state
|
||||
</Typography>
|
||||
{things.length !== 0 && (
|
||||
<Typography level='body-sm'>
|
||||
it's look like you don't have any things yet, create a thing to
|
||||
trigger a task when the state changes.
|
||||
<Button
|
||||
startDecorator={<Widgets />}
|
||||
size='sm'
|
||||
onClick={() => {
|
||||
navigate('/things')
|
||||
}}
|
||||
>
|
||||
Go to Things
|
||||
</Button>{' '}
|
||||
to create a thing
|
||||
</Typography>
|
||||
)}
|
||||
<FormControl error={isAttepmtingToSave && !selectedThing}>
|
||||
<Autocomplete
|
||||
options={things}
|
||||
value={selectedThing}
|
||||
onChange={(e, newValue) => setSelectedThing(newValue)}
|
||||
getOptionLabel={option => option.name}
|
||||
renderOption={(props, option) => (
|
||||
<ListItem {...props}>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
p: 1,
|
||||
}}
|
||||
>
|
||||
<ListItemDecorator sx={{ alignSelf: 'flex-start' }}>
|
||||
<Typography level='body-lg' textColor='primary'>
|
||||
{option.name}
|
||||
</Typography>
|
||||
</ListItemDecorator>
|
||||
<ListItemContent>
|
||||
<Typography level='body2' textColor='text.secondary'>
|
||||
<Chip>type: {option.type}</Chip>{' '}
|
||||
<Chip>state: {option.state}</Chip>
|
||||
</Typography>
|
||||
</ListItemContent>
|
||||
</Box>
|
||||
</ListItem>
|
||||
)}
|
||||
renderInput={params => (
|
||||
<TextField {...params} label='Select a thing' />
|
||||
)}
|
||||
/>
|
||||
</FormControl>
|
||||
<Typography level='body-sm'>
|
||||
Create a condition to trigger a task when the thing state changes to
|
||||
desired state
|
||||
</Typography>
|
||||
{selectedThing?.type == 'boolean' && (
|
||||
<Box>
|
||||
<Typography level='body-sm'>
|
||||
When the state of {selectedThing.name} changes as specified below,
|
||||
the task will become due.
|
||||
</Typography>
|
||||
<Select
|
||||
value={triggerState}
|
||||
onChange={e => {
|
||||
if (e?.target.value === 'true' || e?.target.value === 'false')
|
||||
setTriggerState(e.target.value)
|
||||
else setTriggerState('false')
|
||||
}}
|
||||
>
|
||||
{['true', 'false'].map(state => (
|
||||
<Option
|
||||
key={state}
|
||||
value={state}
|
||||
onClick={() => setTriggerState(state)}
|
||||
>
|
||||
{state.charAt(0).toUpperCase() + state.slice(1)}
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
</Box>
|
||||
)}
|
||||
{selectedThing?.type == 'number' && (
|
||||
<Box>
|
||||
<Typography level='body-sm'>
|
||||
When the state of {selectedThing.name} changes as specified below,
|
||||
the task will become due.
|
||||
</Typography>
|
||||
|
||||
<Box sx={{ display: 'flex', gap: 1, direction: 'row' }}>
|
||||
<Typography level='body-sm'>State is</Typography>
|
||||
<Select value={condition} sx={{ width: '50%' }}>
|
||||
{[
|
||||
{ name: 'Equal', value: 'eq' },
|
||||
{ name: 'Not equal', value: 'neq' },
|
||||
{ name: 'Greater than', value: 'gt' },
|
||||
{ name: 'Greater than or equal', value: 'gte' },
|
||||
{ name: 'Less than', value: 'lt' },
|
||||
{ name: 'Less than or equal', value: 'lte' },
|
||||
].map(condition => (
|
||||
<Option
|
||||
key={condition.value}
|
||||
value={condition.value}
|
||||
onClick={() => setCondition(condition.value)}
|
||||
>
|
||||
{condition.name}
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
<Input
|
||||
type='number'
|
||||
value={triggerState}
|
||||
onChange={e => setTriggerState(e.target.value)}
|
||||
sx={{ width: '50%' }}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
{selectedThing?.type == 'text' && (
|
||||
<Box>
|
||||
<Typography level='body-sm'>
|
||||
When the state of {selectedThing.name} changes as specified below,
|
||||
the task will become due.
|
||||
</Typography>
|
||||
|
||||
<Input
|
||||
value={triggerState}
|
||||
onChange={e => setTriggerState(e.target.value)}
|
||||
label='Enter the text to trigger the task'
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
export default ThingTriggerSection
|
Loading…
Add table
Add a link
Reference in a new issue