frontend/src/objects/objectInteractions/NewObjectInteraction.tsx

1051 lines
No EOL
36 KiB
TypeScript

import { Button, Checkbox, DialogActions, DialogContent, DialogTitle, FormControl, FormControlLabel, FormHelperText, FormLabel, Grid2, IconButton, InputAdornment, List, ListItem, ListItemIcon, ListItemText, MenuItem, Select, Switch, TextField, Theme, Tooltip, Typography } from "@mui/material";
import { makeStyles } from "@mui/styles";
import classNames from "classnames";
import ResponsiveDialog from "common/ResponsiveDialog";
import { Option } from "common/SearchSelect";
import { capitalize, cloneDeep } from "lodash";
import { Component, Device, Interaction } from "models";
import { extension, getMeasurements } from "pbHelpers/ComponentType";
import { ComponentType, Measurement, Operator } from "pbHelpers/Enums";
import { describeMeasurement, MeasurementDescriber } from "pbHelpers/MeasurementDescriber";
import { pond } from "protobuf-ts/pond";
import { quack } from "protobuf-ts/quack";
import React from "react";
import { useEffect, useState } from "react";
import AddIcon from "@mui/icons-material/AddCircle";
import RemoveIcon from "@mui/icons-material/RemoveCircle";
import { green, red } from "@mui/material/colors";
import { or } from "utils";
import moment from "moment";
import { timeOfDayDescriptor } from "pbHelpers/Interaction";
import { TimePicker } from "@mui/x-date-pickers";
import { useInteractionsAPI, useSnackbar } from "hooks";
import { useGlobalState } from "providers";
interface Props {
open: boolean
onClose: (refreshCallback: boolean) => void
linkedComponents: Map<string, Component>
componentDevices: Map<string, number>
//if we pass in a device id we could filter the components to only be for that device, this could be used for the control part
device?: Device
}
const useStyles = makeStyles((theme: Theme) => {
return (
{
borderedContainer: {
padding: theme.spacing(1),
marginBottom: theme.spacing(2),
border: "1px solid rgba(255, 255, 255, 0.12)",
borderRadius: "4px"
},
greenButton: {
color: green["600"]
},
redButton: {
color: red["600"]
},
noPadding: {
padding: 0
},
forPrompt: {
fontSize: "0.875em",
marginTop: "10px"
},
timeRange: {
fontSize: "0.875em",
marginTop: "20px"
},
}
)
})
export default function NewObjectInteraction(props: Props){
const { open, onClose, device, linkedComponents, componentDevices } = props
const classes = useStyles()
const [{ as }] = useGlobalState();
const {openSnack} = useSnackbar();
const interactionsAPI = useInteractionsAPI();
const [newAlertComponentType, setNewAlertComponentType] = useState<quack.ComponentType>(
quack.ComponentType.COMPONENT_TYPE_INVALID
);
const [conditions, setConditions] = useState<pond.InteractionCondition[]>([]);
const [selectedAlertComponents, setSelectedAlertComponents] = useState<string[]>([]);
const [validOptions, setValidOptions] = useState<Option[]>([])
const [validComponents, setValidComponents] = useState<Component[]>([])
//condition value strings - so that decimals are easier to type into the field
const [valStrings, setValStrings] = useState(["0", "0"]);
const [numConditions, setNumConditions] = useState(0)
const [nodeOptions, setNodeOptions] = useState<JSX.Element[]>([]);
const [subtypeDropdown, setSubtypeDropdown] = useState<number>(0);
const [nodeOne, setNodeOne] = useState(0)
const [nodeTwo, setNodeTwo] = useState(0)
const [maxConditions, setMaxConditions] = useState(2)
const [resultType, setResultType] = useState<quack.InteractionResultType>(quack.InteractionResultType.INTERACTION_RESULT_TYPE_REPORT)
const [sinkMotor, setSinkMotor] = useState(false)
const [selectedSink, setSelectedSink] = useState("")//the key of the component to be the sink
const [sinkOptions, setSinkOptions] = useState<Component[]>([])
const [resultMode, setResultMode] = useState(0)
const [resultValue, setResultValue] = useState("")
const [dutyCycleEnabled, setDutyCycleEnabled] = useState(false)
const [dutyCycle, setDutyCycle] = useState<string>("");
const [reporting, setReporting] = useState(true)
const [notify, setNotify] = useState(true)
//schedule variables
const [schedule, setSchedule] = useState(
pond.InteractionSchedule.create({
weekdays: ["sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday"]
})
);
useEffect(() => {
//if the device is passed in need to filter out possible components
let validCompList: Component[] = []
let validOptions: Option[] = []
let sinkOptions: Component[] = []
if(device){
setMaxConditions(device.maxConditions())
}
linkedComponents.forEach((comp, key) => {
if(componentDevices.get(key) === device?.id() || !device){
let ext = extension(comp.type(), comp.subType())
if(ext.isController){
sinkOptions.push(comp)
}
validCompList.push(comp)
}
})
let included: quack.ComponentType[] = []
validCompList.forEach(comp => {
if(included.includes(comp.type())) return
let ext = extension(comp.type(), comp.subType())
if(device){
if(!ext.isController){
included.push(comp.type())
validOptions.push({
label: ext.friendlyName,
value: comp.type()
})
}
}else{
included.push(comp.type())
validOptions.push({
label: ext.friendlyName,
value: comp.type()
})
}
})
setValidOptions(validOptions)
setValidComponents(validCompList)
setSinkOptions(sinkOptions)
},[device, linkedComponents, componentDevices])
//use the selected components to determine the lowest amount of nodes for the options
useEffect(() => {
setNodeOne(0);
setNodeTwo(0);
setSubtypeDropdown(0);
let nodeCount = 12; //start with the maximum number of nodes for a cable
validComponents.forEach(comp => {
if (selectedAlertComponents.includes(comp.key())) {
if (comp.lastMeasurement[0] && comp.lastMeasurement[0].values[0]) {
let nodes = comp.lastMeasurement[0].values[0].values.length;
nodeCount = nodes < nodeCount ? nodes : nodeCount;
}
}
});
let options = [];
for (let i = 0; i <= nodeCount; i++) {
options.push(
<MenuItem key={i} value={i}>
{i === 0 ? "None" : "Node " + i}
</MenuItem>
);
}
setNodeOptions(options);
}, [validComponents, selectedAlertComponents]);
const close = (refresh: boolean) => {
//reset the state variables to their default values
setNewAlertComponentType(quack.ComponentType.COMPONENT_TYPE_INVALID);
setConditions([]);
setSelectedAlertComponents([]);
setValidComponents([])
setValStrings(["0", "0"]);
setNumConditions(0)
setNodeOptions([]);
setSubtypeDropdown(0);
setNodeOne(0)
setNodeTwo(0)
setMaxConditions(2)
setResultType(quack.InteractionResultType.INTERACTION_RESULT_TYPE_REPORT)
setSinkMotor(false)
setSelectedSink("")//the key of the component to be the sink
setSinkOptions([])
setResultMode(0)
setResultValue("")
setDutyCycleEnabled(false)
setDutyCycle("");
setReporting(true)
setNotify(true)
onClose(refresh)
}
const availableMeasurementTypes = (type: quack.ComponentType): quack.MeasurementType[] => {
return getMeasurements(type).map(m => m.measurementType);
};
const initialConditions = (type: quack.ComponentType): pond.InteractionCondition[] => {
let measurementType = availableMeasurementTypes(type)[0];
let condition = pond.InteractionCondition.create({
measurementType: measurementType,
comparison: measurementType === Measurement.boolean ? Operator.equals : Operator.less,
value: 0
});
return [condition];
};
//display the components that match the type that was selected for the new alert
const listMatchingComponents = () => {
let matching: Component[] = [];
validComponents.forEach(comp => {
if (comp.type() === newAlertComponentType) {
matching.push(comp);
}
});
return (
<List>
{matching.map(comp => (
<ListItem key={comp.key()}>
<ListItemIcon>
<Checkbox
checked={selectedAlertComponents.includes(comp.key())}
onChange={(_, checked) => {
let selected = selectedAlertComponents;
if (checked) {
selected.push(comp.key());
} else {
selected.splice(selected.indexOf(comp.key()), 1);
}
setSelectedAlertComponents([...selected]);
}}
/>
</ListItemIcon>
<ListItemText>{comp.name()}</ListItemText>
</ListItem>
))}
</List>
);
};
const measurementTypeMenuItems = () => {
return availableMeasurementTypes(newAlertComponentType).map(measurementType => (
<MenuItem key={measurementType} value={measurementType}>
{describeMeasurement(measurementType).label()}
</MenuItem>
));
};
const changeCondition = (newCondition: pond.InteractionCondition, index: number) => {
let c = conditions;
c[index] = newCondition;
setConditions([...c]);
};
const conditionGroup = (condition: pond.InteractionCondition, index: number) => {
let measurementType = condition.measurementType;
let describer = describeMeasurement(measurementType, newAlertComponentType);
let isBoolean = condition.measurementType === Measurement.boolean;
return (
<React.Fragment key={index}>
<Grid2
key={"interaction"}
container
direction="row"
alignItems="center"
spacing={2}>
{index > 0 && (
<Grid2 size={12}>
<Typography align="center">AND</Typography>
</Grid2>
)}
<Grid2 size={{ xs:10, sm:4 }}>
<TextField
select
id="measurementType"
required={true}
label=""
helperText="Measurement"
//error={!isMeasurementTypeValid(interaction.settings.conditions[index].measurementType)}
//disabled={!isSourceValid(interaction.settings.source) || !canEdit}
value={condition.measurementType}
onChange={event => {
condition.measurementType = +event.target.value;
changeCondition(condition, index);
}}
autoFocus={false}
margin="dense"
variant="standard"
fullWidth
InputLabelProps={{ shrink: true }}>
{measurementTypeMenuItems()}
</TextField>
</Grid2>
<Grid2 size={{ xs:10, sm:3 }}>
<TextField
select
id="comparison"
required={true}
label=""
helperText="Comparator"
//disabled={!isSourceValid(interaction.settings.source) || isBoolean || !canEdit}
value={condition.comparison}
onChange={event => {
condition.comparison = +event.target.value;
changeCondition(condition, index);
}}
autoFocus={false}
margin="dense"
variant="standard"
fullWidth
InputLabelProps={{ shrink: true }}>
{!isBoolean && [
<MenuItem key={2} value={Operator.less}>
{"is below"}
</MenuItem>,
<MenuItem key={3} value={Operator.greater}>
{"is above"}
</MenuItem>
]}
<MenuItem key={1} value={Operator.equals}>
{"is exactly"}
</MenuItem>
</TextField>
</Grid2>
<Grid2 size={{xs:8, sm:4}}>
<TextField
select={isBoolean}
id="value"
required={true}
type={"text"}
label=""
helperText="Value"
error={isNaN(+valStrings[index])}
value={valStrings[index]}
onChange={event => {
let strings = valStrings;
strings[index] = event.target.value;
setValStrings([...strings]);
if (!isNaN(+valStrings[index])) {
condition.value = describer.toStored(+event.target.value);
changeCondition(condition, index);
}
}}
autoFocus={false}
margin="dense"
variant="standard"
fullWidth
InputProps={{
endAdornment: <InputAdornment position="end">{describer.unit()}</InputAdornment>
}}
InputLabelProps={{ shrink: true }}>
{isBoolean && [
<MenuItem key={0} value={0}>
{describer.enumerations()[0]}
</MenuItem>,
<MenuItem key={1} value={1}>
{describer.enumerations()[1]}
</MenuItem>
]}
</TextField>
</Grid2>
<Grid2 size={{xs:2, sm:1}}>
<Tooltip title="Remove this condition">
<IconButton
color="default"
disabled={numConditions === 1}
aria-label="Remove condition"
onClick={() => {
conditions.splice(index, 1)
setNumConditions(conditions.length)
}}
className={classNames(classes.redButton, classes.noPadding)}>
<RemoveIcon />
</IconButton>
</Tooltip>
</Grid2>
</Grid2>
{nodeOptions.length > 2 && (
<Grid2
key={"nodes"}
container
direction="row"
alignItems="center"
spacing={2}>
<Grid2 size={{xs:10, sm:4}}>
<TextField
select
id="subtype"
label="Subtype"
//disabled={!isSourceValid(interaction.settings.source) || !canEdit}
value={subtypeDropdown}
onChange={event => {
setSubtypeDropdown(+event.target.value);
}}
margin="dense"
variant="standard"
fullWidth
InputLabelProps={{ shrink: true }}>
<MenuItem key={0} value={0}>
Any
</MenuItem>
<MenuItem key={1} value={1}>
Average
</MenuItem>
<MenuItem key={2} value={2}>
Single Node
</MenuItem>
<MenuItem key={3} value={3}>
Node Diff
</MenuItem>
<MenuItem key={4} value={4}>
Up To
</MenuItem>
</TextField>
</Grid2>
<Grid2 size={{xs:10, sm:4}}>
{(subtypeDropdown === 2 || subtypeDropdown === 3 || subtypeDropdown === 4) && (
<TextField
select
id="interactionType"
label="Node 1"
//disabled={!isSourceValid(interaction.settings.source) || !canEdit}
value={nodeOne}
onChange={event => {
setNodeOne(+event.target.value);
}}
margin="dense"
variant="standard"
fullWidth
InputLabelProps={{ shrink: true }}>
{nodeOptions}
</TextField>
)}
</Grid2>
<Grid2 size={{xs:10, sm:4}}>
{subtypeDropdown === 3 && (
<TextField
select
id="interactionType"
label="Node 2"
//disabled={!isSourceValid(interaction.settings.source) || !canEdit}
value={nodeTwo}
onChange={event => {
setNodeTwo(+event.target.value);
}}
margin="dense"
variant="standard"
fullWidth
InputLabelProps={{ shrink: true }}>
{nodeOptions}
</TextField>
)}
</Grid2>
</Grid2>
)}
</React.Fragment>
);
};
const conditionInput = () => {
return (
<FormControl
component={"fieldset" as "div"}
className={classes.borderedContainer}
fullWidth
disabled={selectedAlertComponents.length === 0}>
<FormLabel component={"legend" as "caption"}>Conditions</FormLabel>
{selectedAlertComponents.length > 0 ? (
<React.Fragment>
{conditions.map((condition, i) => conditionGroup(condition, i))}
</React.Fragment>
) : (
<FormHelperText>You must select a source before adding conditions</FormHelperText>
)}
<Tooltip title="Add another condition">
<IconButton
color="primary"
disabled={numConditions >= maxConditions}
aria-label="Add another condition"
onClick={() => {
let newConditions = conditions.concat(initialConditions(newAlertComponentType));
setConditions(newConditions);
setNumConditions(newConditions.length)
}}
className={classNames(classes.greenButton, classes.noPadding)}>
<AddIcon />
</IconButton>
</Tooltip>
</FormControl>
);
};
const sinkSelector = () => {
return (
<TextField
select
id="sink"
label=""
value={selectedSink}
onChange={(e) => {
setSelectedSink(e.target.value)
setSinkMotor(linkedComponents.get(e.target.value)?.type() === ComponentType.stepperMotor)
}}
fullWidth
autoFocus={false}
margin="dense"
variant="standard"
InputLabelProps={{ shrink: true }}>
<MenuItem key={""} value={""}>
Select Controller
</MenuItem>
{sinkOptions.map(comp => (
<MenuItem key={comp.key()} value={comp.key()}>
{comp.name()}
</MenuItem>
))}
</TextField>
);
}
const describeSink = (): MeasurementDescriber => {
const sink = linkedComponents.get(selectedSink);
return describeMeasurement(
Measurement.boolean,
or(sink, Component.create()).settings.type,
or(sink, Component.create()).settings.subtype
);
};
const dutyCycleInput = () => {
return (
<React.Fragment>
<Grid2 size={{ xs: 4, sm: 4 }}>
<FormControlLabel
control={
<Checkbox
id="enableDutyCycle"
checked={dutyCycleEnabled}
onChange={(_, checked) => {
setDutyCycleEnabled(checked)
}}
value="enableDutyCycle"
color="secondary"
/>
}
label="Once every"
/>
</Grid2>
<Grid2 size={{ xs: 8, sm: 8 }}>
<TextField
id="dutyCycle"
value={dutyCycle}
onChange={(e) => {
setDutyCycle(e.target.value)
}}
fullWidth
autoFocus={false}
margin="dense"
variant="standard"
disabled={!dutyCycleEnabled}
error={isNaN(Number(dutyCycle)) || Number(dutyCycle) < 0}
InputLabelProps={{
shrink: true
}}
InputProps={{
endAdornment: <InputAdornment position="end">seconds</InputAdornment>
}}></TextField>
</Grid2>
</React.Fragment>
);
};
const notificationInput = () => {
return (
<React.Fragment>
{resultType !== quack.InteractionResultType.INTERACTION_RESULT_TYPE_REPORT && (
<Grid2 size={{xs:6, sm:3}}>
<FormControlLabel
control={
<Checkbox
id="notificationReports"
checked={reporting}
onChange={(_, checked) => {
setReporting(checked)
}}
value="notificationReports"
color="secondary"
/>
}
label="Report"
/>
</Grid2>
)}
<Grid2 size={{xs: resultType !== quack.InteractionResultType.INTERACTION_RESULT_TYPE_TOGGLE ? 6 : 12, sm: 3}} >
<FormControlLabel
control={
<Checkbox
id="interactionNotification"
checked={notify}
onChange={(_, checked) => {
setNotify(checked)
}}
value="interactionNotification"
color="secondary"
/>
}
label="Notify"
disabled={!reporting}
/>
</Grid2>
{notify && (
<Grid2 size={{ xs: 12, sm: 12 }}>
<Typography color="textSecondary" variant="subtitle1">
Everyone with notifications enabled for this device will receive an email and/or SMS
each time this result occurs
</Typography>
</Grid2>
)}
</React.Fragment>
);
};
const resultInput = () => {
return (
<FormControl component={"fieldset" as "div"} className={classes.borderedContainer} fullWidth>
<FormLabel component={"legend" as "caption"}>Result</FormLabel>
<Grid2 container direction="row" spacing={2}>
<Grid2 size={{ xs: 12, sm: 3 }}>
<TextField
select
id="resultType"
required={true}
label=""
value={resultType}
onChange={(e) => {
setResultType(+e.target.value as quack.InteractionResultType)
}}
autoFocus={false}
margin="dense"
variant="standard"
fullWidth
InputLabelProps={{ shrink: true }}>
<MenuItem value={quack.InteractionResultType.INTERACTION_RESULT_TYPE_REPORT}>Report</MenuItem>
<MenuItem value={quack.InteractionResultType.INTERACTION_RESULT_TYPE_SET}>Set</MenuItem>
<MenuItem value={quack.InteractionResultType.INTERACTION_RESULT_TYPE_TOGGLE}>Toggle</MenuItem>
<MenuItem value={quack.InteractionResultType.INTERACTION_RESULT_TYPE_RUN}>Run</MenuItem>
</TextField>
</Grid2>
{resultType === quack.InteractionResultType.INTERACTION_RESULT_TYPE_RUN && (
<React.Fragment>
<Grid2 size={{ xs: 12, sm: sinkMotor ? 8 : 4 }}>
{sinkSelector()}
</Grid2>
{sinkMotor && (
<Grid2 size={{ xs: 12, sm: 5 }} container justifyContent="center">
<TextField
select
id="mode"
label=""
value={resultMode}
onChange={(e) => {
setResultMode(+e.target.value)
}}
fullWidth
autoFocus={false}
margin="dense"
variant="standard"
InputLabelProps={{ shrink: true }}>
<MenuItem key={"Clockwise"} value={0}>
Clockwise
</MenuItem>
<MenuItem key={"Counter Clockwise"} value={1}>
Counter Clockwise
</MenuItem>
</TextField>
</Grid2>
)}
<Grid2 size={{ xs: 2, sm: 1 }}>
<Typography variant="subtitle1" color="textSecondary" className={classes.forPrompt}>
for
</Typography>
</Grid2>
<Grid2 size={{ xs: 10, sm: sinkMotor ? 6 : 4 }} container justifyContent="center">
<TextField
id="runDuration"
required={true}
label=""
helperText=""
error={isNaN(parseFloat(resultValue))}
value={resultValue}
onChange={(e) => {
setResultValue(e.target.value)
}}
autoFocus={false}
margin="dense"
variant="standard"
fullWidth
InputProps={{
endAdornment: <InputAdornment position="end">sec</InputAdornment>
}}
InputLabelProps={{ shrink: true }}
/>
</Grid2>
</React.Fragment>
)}
{resultType === quack.InteractionResultType.INTERACTION_RESULT_TYPE_SET && (
<React.Fragment>
<Grid2 size={{ xs: 12, sm: 4 }}>
{sinkSelector()}
</Grid2>
{linkedComponents.get(selectedSink)?.type() !==
quack.ComponentType.COMPONENT_TYPE_INTERNAL_FUNCTION && (
<Grid2 size={{ xs: 2, sm: 1 }}>
<Typography
variant="subtitle1"
color="textSecondary"
className={classes.forPrompt}>
to
</Typography>
</Grid2>
)}
{linkedComponents.get(selectedSink)?.type() !==
quack.ComponentType.COMPONENT_TYPE_INTERNAL_FUNCTION && (
<Grid2 size={{ xs: 10, sm: 3 }} container justifyContent="center">
<TextField
select
id="resultValue"
label=""
value={resultValue}
onChange={(e) => {
setResultValue(e.target.value)
}}
fullWidth
autoFocus={false}
margin="dense"
variant="standard"
InputLabelProps={{ shrink: true }}>
{extension(linkedComponents.get(selectedSink)?.type() ?? quack.ComponentType.COMPONENT_TYPE_INVALID).states.map((state, i) => (
<MenuItem key={state} value={i}>
{state}
</MenuItem>
))}
</TextField>
</Grid2>
)}
</React.Fragment>
)}
{resultType === quack.InteractionResultType.INTERACTION_RESULT_TYPE_TOGGLE && (
<React.Fragment>
<Grid2 size={{ xs: 12, sm: 5 }}>
{sinkSelector()}
</Grid2>
<Grid2 size={{ xs: 12, sm: 3 }} container justifyContent="center">
<FormControlLabel
control={
<Switch
id="resultValue"
checked={parseFloat(resultValue) === 1}
onChange={(_, checked) => {
if(checked){
setResultValue('1')
}else{
setResultValue('0')
}
}}
color="secondary"
/>
}
label={describeSink().enumerations()[isNaN(parseFloat(resultValue))?0:parseFloat(resultValue)]}
/>
</Grid2>
</React.Fragment>
)}
{resultType === quack.InteractionResultType.INTERACTION_RESULT_TYPE_RUN &&
dutyCycleInput()}
{notificationInput()}
</Grid2>
</FormControl>
);
};
const toggleDaySelected = (day: string, event: any) => {
let updatedSchedule = cloneDeep(schedule);
if (event.target.checked) {
if (!updatedSchedule.weekdays.includes(day)) {
updatedSchedule.weekdays.push(day);
}
} else {
updatedSchedule.weekdays.splice(updatedSchedule.weekdays.indexOf(day), 1);
}
setSchedule(updatedSchedule);
};
const daySelector = (day: string, size: any) => {
//const schedule = or(interaction.settings.schedule, pond.InteractionSchedule.create());
return (
<Grid2 container size={{xs: size, sm:1}} alignItems="center">
<FormControlLabel
control={
<Checkbox
id={day}
checked={schedule.weekdays.includes(day)}
onChange={event => toggleDaySelected(day, event)}
value={day}
color="secondary"
/>
}
label={capitalize(day.substr(0, 2))}
labelPlacement="bottom"
/>
</Grid2>
);
};
const setScheduleTime = (id: string, event: any) => {
let updatedSchedule = cloneDeep(schedule);
if (id === "shortcut") {
let value = event.target.value;
if (value === "allDay") {
updatedSchedule.timeOfDayStart = "00:00";
updatedSchedule.timeOfDayEnd = "24:00";
} else if (value === "before") {
updatedSchedule.timeOfDayStart = "00:00";
if (!updatedSchedule.timeOfDayEnd || updatedSchedule.timeOfDayEnd === "24:00") {
updatedSchedule.timeOfDayEnd = "23:59";
}
} else if (value === "after") {
updatedSchedule.timeOfDayEnd = "24:00";
if (updatedSchedule.timeOfDayStart === "00:00") {
updatedSchedule.timeOfDayStart = "00:01";
}
} else {
if (updatedSchedule.timeOfDayStart === "00:00") {
updatedSchedule.timeOfDayStart = "00:01";
}
if (updatedSchedule.timeOfDayEnd === "24:00") {
updatedSchedule.timeOfDayEnd = "23:59";
}
}
} else {
let timeOfDay =
event
.hour()
.toString()
.padStart(2, "0") +
":" +
event
.minute()
.toString()
.padStart(2, "0");
if (id === "start") {
updatedSchedule.timeOfDayStart = timeOfDay;
} else if (id === "end") {
if (timeOfDay === "00:00") {
timeOfDay = "24:00";
}
updatedSchedule.timeOfDayEnd = timeOfDay;
}
}
updatedSchedule.timezone = moment.tz.guess();
setSchedule(updatedSchedule);
};
const scheduler = () => {
//let schedule = pond.InteractionSchedule.create();
let timezone = moment.tz.guess();
let todStart = "00:00".split(":");
let todEnd = "23:59".split(":");
let start = moment
.tz(timezone)
.startOf("day")
.add(todStart[0], "hours")
.add(todStart[1], "minutes")
.local();
let end = moment
.tz(timezone)
.startOf("day")
.add(todEnd[0], "hours")
.add(todEnd[1], "minutes")
.local();
let descriptor = timeOfDayDescriptor(schedule);
let showStart = descriptor === "after" || descriptor === "from";
let showEnd = descriptor === "before" || descriptor === "from";
let showBoth = showStart && showEnd;
return (
<FormControl component={"fieldset" as "div"} className={classes.borderedContainer} fullWidth>
<FormLabel component={"legend" as "caption"}>Schedule</FormLabel>
<Grid2 container direction="row" spacing={0}>
{daySelector("sunday", 3)}
{daySelector("monday", 3)}
{daySelector("tuesday", 3)}
{daySelector("wednesday", 3)}
{daySelector("thursday", 4)}
{daySelector("friday", 4)}
{daySelector("saturday", 4)}
</Grid2>
<Grid2 container direction="row" spacing={2} alignItems="center">
<Grid2 size={{xs:12, sm:3}}>
<TextField
select
id="timeOfDayShortcut"
value={descriptor}
onChange={(event: any) => setScheduleTime("shortcut", event)}
fullWidth
autoFocus={false}
margin="normal"
variant="standard"
InputLabelProps={{ shrink: true }}>
<MenuItem key="allDay" value="allDay">
All Day
</MenuItem>
<MenuItem key="before" value="before">
Before
</MenuItem>
<MenuItem key="after" value="after">
After
</MenuItem>
<MenuItem key="from" value="from">
From
</MenuItem>
</TextField>
</Grid2>
{showStart && (
<Grid2 size={{xs:5, sm:3}}>
<TimePicker
//renderInput={props => <TextField {...props} helperText="" />}
value={start}
onChange={(event: any) => setScheduleTime("start", event)}
/>
</Grid2>
)}
{showBoth && (
<Grid2 size={{xs:2,sm:1}}>
<Typography
variant="subtitle1"
color="textSecondary"
className={classes.timeRange}
align="right">
to
</Typography>
</Grid2>
)}
{showEnd && (
<Grid2 size={{xs:5, sm:3}}>
<TimePicker
//renderInput={props => <TextField {...props} helperText="" />}
value={end}
onChange={(event: any) => setScheduleTime("end", event)}
/>
</Grid2>
)}
</Grid2>
</FormControl>
);
};
const addInteractionToComponents = () => {
//array of device and component ids ie, [4:9-5-12, 4:9-5-13, 3:9-5-12]
let compIds: string[] = [];
selectedAlertComponents.forEach(comp => {
let devID = componentDevices.get(comp);
let component = linkedComponents.get(comp);
if (devID && component) {
compIds.push(devID + ":" + component.locationString());
}
});
let newInteraction = pond.InteractionSettings.create();
newInteraction.conditions = conditions;
newInteraction.instance = 1;
newInteraction.schedule = schedule;
newInteraction.subtype = Interaction.create().subtypeFromNodes(nodeOne, nodeTwo);
newInteraction.sink = linkedComponents.get(selectedSink)?.location()
newInteraction.result = pond.InteractionResult.create({
type: resultType,
mode: resultMode,
value: isNaN(parseFloat(resultValue)) ? 0 : parseFloat(resultValue),
dutyCycle: isNaN(parseFloat(dutyCycle)) ? 0 : parseFloat(dutyCycle)
});
newInteraction.notifications = pond.InteractionNotifications.create({
reports: reporting,
notify: reporting && notify
});
interactionsAPI
.addInteractionToComponents(compIds, newInteraction, as)
.then(_resp => {
openSnack("interaction added to selected components");
})
.catch(_err => {
openSnack("there was a problem adding the interaction to the selected components");
}).finally(() => {
close(true)
});
};
return (
<ResponsiveDialog
open={open}
onClose={() => {
close(false);
}}>
<DialogTitle>Set New Alert</DialogTitle>
<DialogContent>
{/* Dropdown to select the component type to add the interaction to */}
<Select
id="componentType"
label="Component Type"
fullWidth
displayEmpty
value={newAlertComponentType}
onChange={e => {
setNewAlertComponentType(e.target.value as quack.ComponentType);
setSelectedAlertComponents([]);
setConditions(initialConditions(e.target.value as quack.ComponentType));
}}>
<MenuItem key={0} value={0}>
Select Component Type
</MenuItem>
{validOptions.map(option => (
<MenuItem key={option.value} value={option.value}>
{option.label}
</MenuItem>
))}
</Select>
{listMatchingComponents()}
{conditionInput()}
{device && resultInput()}
{scheduler()}
</DialogContent>
<DialogActions>
<Button onClick={() => close(false)} variant="contained">
Cancel
</Button>
<Button
//disabled={selectedAlertComponents.length === 0}
onClick={addInteractionToComponents}
variant="contained"
color="primary">
Confirm
</Button>
</DialogActions>
</ResponsiveDialog>
)
}