From 2657469964e24ffbeb905024532120395f6e797c Mon Sep 17 00:00:00 2001 From: Mo Tarbin Date: Sun, 30 Jun 2024 18:55:39 -0400 Subject: move to Donetick Org, First commit frontend --- src/views/ChoreEdit/ChoreEdit.jsx | 744 ++++++++++++++++++++++++++++ src/views/ChoreEdit/RepeatSection.jsx | 496 +++++++++++++++++++ src/views/ChoreEdit/ThingTriggerSection.jsx | 230 +++++++++ 3 files changed, 1470 insertions(+) create mode 100644 src/views/ChoreEdit/ChoreEdit.jsx create mode 100644 src/views/ChoreEdit/RepeatSection.jsx create mode 100644 src/views/ChoreEdit/ThingTriggerSection.jsx (limited to 'src/views/ChoreEdit') diff --git a/src/views/ChoreEdit/ChoreEdit.jsx b/src/views/ChoreEdit/ChoreEdit.jsx new file mode 100644 index 0000000..e8eb17d --- /dev/null +++ b/src/views/ChoreEdit/ChoreEdit.jsx @@ -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 ( + + {/* + Edit Chore + */} + + + Descritpion : + What is this chore about? + setName(e.target.value)} /> + {errors.name} + + + + Assignees : + Who can do this chore? + + + {performers?.map((item, index) => ( + + 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} + /> + + ))} + + + + {Boolean(errors.assignee)} + + + {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 + <> + + Assigned : + + Who is assigned the next due chore? + + + + + + Picking Mode : + + How to pick the next assignee for the following chore? + + + + + {ASSIGN_STRATEGIES.map((item, idx) => ( + + setAssignStrategy(item)} + overlay + disableIcon + variant='soft' + label={item + .split('_') + .map(x => x.charAt(0).toUpperCase() + x.slice(1)) + .join(' ')} + /> + + ))} + + + + + )} + { + if (thingUpdate === null) { + setThingTrigger(null) + return + } + setThingTrigger({ + triggerState: thingUpdate.triggerState, + condition: thingUpdate.condition, + thingID: thingUpdate.thing.id, + }) + }} + OnTriggerValidate={setIsThingValid} + isAttemptToSave={attemptToSave} + selectedThing={thingTrigger} + /> + + + + {REPEAT_ON_TYPE.includes(frequencyType) ? 'Start date' : 'Due date'} : + + {frequencyType === 'trigger' && !dueDate && ( + + Due Date will be set when the trigger of the thing is met + + )} + + {NO_DUE_DATE_REQUIRED_TYPE.includes(frequencyType) && ( + + { + 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' + /> + + task needs to be completed by a specific time. + + + )} + {dueDate && ( + + + {REPEAT_ON_TYPE.includes(frequencyType) + ? 'When does this chore start?' + : 'When is the next first time this chore is due?'} + + { + setDueDate(e.target.value) + }} + /> + {errors.dueDate} + + )} + + {!['once', 'no_repeat'].includes(frequencyType) && ( + + Scheduling Preferences: + + How to reschedule the next due date? + + + div': { p: 1 } }}> + + setIsRolling(false)} + label='Reschedule from due date' + /> + + the next task will be scheduled from the original due date, even + if the previous task was completed late + + + + setIsRolling(true)} + label='Reschedule from completion date' + /> + + the next task will be scheduled from the actual completion date + of the previous task + + + + + )} + + Notifications : + + Get Reminders when this task is due or completed + {!isPlusAccount(userProfile) && ( + + Not available in Basic Plan + + )} + + + + { + setIsNotificable(e.target.checked) + }} + defaultChecked={isNotificable} + checked={isNotificable} + value={isNotificable} + disabled={!isPlusAccount(userProfile)} + overlay + label='Notify for this task' + /> + + Receive notifications for this task + + + + {isNotificable && ( + div': { p: 2, borderRadius: 'md', display: 'flex' }, + }} + > + + + What things should trigger the notification? + + {[ + { + 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 => ( + + { + setNotificationMetadata({ + ...notificationMetadata, + [item.id]: !notificationMetadata[item.id], + }) + }} + checked={ + notificationMetadata ? notificationMetadata[item.id] : false + } + label={item.title} + key={item.title} + /> + {item.description} + + ))} + + + )} + + Labels : + + Things to remember about this chore or to tag it + + { + 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) + }} + /> + + {choreId > 0 && ( + + + + Created by{' '} + + {performers.find(f => f.id === createdBy)?.displayName} + {' '} + {moment(chore.createdAt).fromNow()} + + {(chore.updatedAt && updatedBy > 0 && ( + <> + + + + Updated by{' '} + + {performers.find(f => f.id === updatedBy)?.displayName} + {' '} + {moment(chore.updatedAt).fromNow()} + + + )) || <>} + + + )} + + + {/* + + */} + + {choreId > 0 && ( + + )} + + + + + {/* */} + + ) +} + +export default ChoreEdit diff --git a/src/views/ChoreEdit/RepeatSection.jsx b/src/views/ChoreEdit/RepeatSection.jsx new file mode 100644 index 0000000..cb680eb --- /dev/null +++ b/src/views/ChoreEdit/RepeatSection.jsx @@ -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 ( + + Every: + { + if (e.target.value < 1) { + e.target.value = 1 + } + onFrequencyUpdate(e.target.value) + }} + /> + + + ) + case 'days_of_the_week': + return ( + + + + {[ + 'monday', + 'tuesday', + 'wednesday', + 'thursday', + 'friday', + 'saturday', + 'sunday', + ].map(item => ( + + { + 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)} + /> + + ))} + + + + ) + case 'day_of_the_month': + return ( + + + on the + { + 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) + }} + /> + of the following month/s: + + + + {[ + 'january', + 'february', + 'march', + 'april', + 'may', + 'june', + 'july', + 'august', + 'september', + 'october', + 'november', + 'december', + ].map(item => ( + + { + // 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)} + /> + + ))} + + + + ) + + 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 ( + + Repeat : + + { + 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' + /> + + Is this something needed to be done regularly? + + + {!['once', 'trigger'].includes(frequencyType) && ( + <> + + How often should it be repeated? + + + {FREQUANCY_TYPES_RADIOS.map((item, index) => ( + + { + 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('_', ' ') + } + /> + + ))} + + {FREQUENCY_TYPE_MESSAGE[frequencyType]} + {frequencyType === 'custom' || + (REPEAT_ON_TYPE.includes(frequencyType) && ( + <> + + + Repeat on: + + 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 => ( + { + 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', + }, + }), + }, + }), + }} + /> + ))} + + + + + + + + ))} + + {frequencyError} + + + + )} + + { + 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' + /> + + Is this something that should be done when a thing state changes?{' '} + {!isPlusAccount(userProfile) && ( + + Not available in Basic Plan + + )} + + + {frequencyType === 'trigger' && ( + + )} + + ) +} + +export default RepeatSection diff --git a/src/views/ChoreEdit/ThingTriggerSection.jsx b/src/views/ChoreEdit/ThingTriggerSection.jsx new file mode 100644 index 0000000..7a040ad --- /dev/null +++ b/src/views/ChoreEdit/ThingTriggerSection.jsx @@ -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 ( + + + Trigger a task when a thing state changes to a desired state + + {things.length !== 0 && ( + + it's look like you don't have any things yet, create a thing to + trigger a task when the state changes. + {' '} + to create a thing + + )} + + setSelectedThing(newValue)} + getOptionLabel={option => option.name} + renderOption={(props, option) => ( + + + + + {option.name} + + + + + type: {option.type}{' '} + state: {option.state} + + + + + )} + renderInput={params => ( + + )} + /> + + + Create a condition to trigger a task when the thing state changes to + desired state + + {selectedThing?.type == 'boolean' && ( + + + When the state of {selectedThing.name} changes as specified below, + the task will become due. + + + + )} + {selectedThing?.type == 'number' && ( + + + When the state of {selectedThing.name} changes as specified below, + the task will become due. + + + + State is + + setTriggerState(e.target.value)} + sx={{ width: '50%' }} + /> + + + )} + {selectedThing?.type == 'text' && ( + + + When the state of {selectedThing.name} changes as specified below, + the task will become due. + + + setTriggerState(e.target.value)} + label='Enter the text to trigger the task' + /> + + )} + + ) +} + +export default ThingTriggerSection -- cgit