added tons of files for component functionality; manual component adding added temporarily
This commit is contained in:
parent
58830d480e
commit
45ff49bcea
121 changed files with 25883 additions and 65 deletions
344
src/interactions/InteractionsOverview.tsx
Normal file
344
src/interactions/InteractionsOverview.tsx
Normal file
|
|
@ -0,0 +1,344 @@
|
|||
import {
|
||||
Button,
|
||||
Card,
|
||||
CardActions,
|
||||
CardContent,
|
||||
CardHeader,
|
||||
darken,
|
||||
Grid2 as Grid,
|
||||
IconButton,
|
||||
Slider,
|
||||
Theme,
|
||||
ToggleButton,
|
||||
ToggleButtonGroup,
|
||||
Tooltip,
|
||||
Typography
|
||||
} from "@mui/material";
|
||||
import { Settings } from "@mui/icons-material";
|
||||
// import { ToggleButton, ToggleButtonGroup } from "@material-ui/lab";
|
||||
import { useInteractionsAPI, usePrevious, useSnackbar } from "hooks";
|
||||
import { cloneDeep } from "lodash";
|
||||
import { Component, Device, Interaction } from "models";
|
||||
import moment from "moment";
|
||||
import { componentIDToString } from "pbHelpers/Component";
|
||||
import {
|
||||
abbreviatedWeekdays,
|
||||
friendlyTimeOfDayDescriptor,
|
||||
interactionConditionText,
|
||||
interactionResultText,
|
||||
isSource,
|
||||
timeOfDayDescriptor
|
||||
} from "pbHelpers/Interaction";
|
||||
import { describeMeasurement } from "pbHelpers/MeasurementDescriber";
|
||||
import { canWrite } from "pbHelpers/Permission";
|
||||
import { pond } from "protobuf-ts/pond";
|
||||
import { quack } from "protobuf-ts/quack";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import InteractionSettings from "./InteractionSettings";
|
||||
import { makeStyles } from "@mui/styles";
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) => {
|
||||
return ({
|
||||
card: {
|
||||
backgroundColor: darken(theme.palette.background.paper, 0.05)
|
||||
},
|
||||
header: {
|
||||
padding: theme.spacing(1),
|
||||
paddingBottom: 0
|
||||
},
|
||||
content: {
|
||||
padding: `${theme.spacing(1)}px !important`
|
||||
},
|
||||
actions: {
|
||||
marginTop: "auto",
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
justifyContent: "flex-end",
|
||||
padding: theme.spacing(1),
|
||||
paddingTop: 0
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
interface Props {
|
||||
device: Device;
|
||||
component: Component;
|
||||
components: Component[];
|
||||
interactions: Interaction[];
|
||||
permissions: pond.Permission[];
|
||||
refreshCallback: () => void;
|
||||
}
|
||||
|
||||
export default function InteractionsOverview(props: Props) {
|
||||
const classes = useStyles();
|
||||
const interactionsAPI = useInteractionsAPI();
|
||||
const { success, error } = useSnackbar();
|
||||
const { device, component, components, permissions, refreshCallback } = props;
|
||||
const prevComponents = usePrevious(components);
|
||||
const prevInteractions = usePrevious(props.interactions);
|
||||
const [interactions, setInteractions] = useState<Interaction[]>(props.interactions);
|
||||
const [dirtyInteractions, setDirtyInteractions] = useState<Map<number, boolean>>(new Map());
|
||||
const [mappedComponents, setMappedComponents] = useState<Map<string, Component>>(new Map());
|
||||
const [renderToggle, setRenderToggle] = useState(false);
|
||||
const [selectedInteraction, setSelectedInteraction] = useState<Interaction | undefined>(
|
||||
undefined
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (components !== prevComponents) {
|
||||
let initMappedComponents: Map<string, Component> = new Map();
|
||||
components.forEach((component: Component) => {
|
||||
initMappedComponents.set(component.locationString(), component);
|
||||
});
|
||||
setMappedComponents(initMappedComponents);
|
||||
}
|
||||
|
||||
if (props.interactions !== prevInteractions) {
|
||||
setInteractions(cloneDeep(props.interactions));
|
||||
}
|
||||
}, [components, prevComponents, props.interactions, prevInteractions]);
|
||||
|
||||
const openInteractionSettings = (interaction: Interaction) => {
|
||||
setSelectedInteraction(interaction);
|
||||
};
|
||||
|
||||
const closeInteractionSettings = () => {
|
||||
setSelectedInteraction(undefined);
|
||||
};
|
||||
|
||||
const interactionConditions = (
|
||||
source: Component | undefined,
|
||||
conditions: pond.IInteractionCondition[] | null | undefined,
|
||||
interactionIndex: number
|
||||
) => {
|
||||
let sourceType = source && source.settings.type;
|
||||
let sourceSubtype = source && source.settings.subtype;
|
||||
let items: any[] = [];
|
||||
if (!conditions) return items;
|
||||
conditions.forEach((condition: pond.IInteractionCondition, conditionIndex: number) => {
|
||||
let measurementType = condition.measurementType;
|
||||
let measurement = describeMeasurement(condition.measurementType, sourceType, sourceSubtype);
|
||||
items.push(
|
||||
<Grid container size={{ sm: 12 }} key={conditionIndex} alignItems="center">
|
||||
<Grid size={{ xs: 12, sm: 6 }}>
|
||||
<Typography variant="caption" gutterBottom>
|
||||
{interactionConditionText(source, condition, conditionIndex > 0)}
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12, sm: 6 }}>
|
||||
{measurementType === quack.MeasurementType.MEASUREMENT_TYPE_BOOLEAN ? (
|
||||
<ToggleButtonGroup
|
||||
value={measurement.toDisplay(condition.value ? condition.value : 0)}
|
||||
exclusive
|
||||
size="small"
|
||||
onChange={(_, value) =>
|
||||
updateConditionValue(
|
||||
source,
|
||||
interactionIndex,
|
||||
conditionIndex,
|
||||
parseInt(value) === 1 ? 1 : 0
|
||||
)
|
||||
}>
|
||||
<ToggleButton value={0} size="small">
|
||||
{measurement.convertWithUnits(0)}
|
||||
</ToggleButton>
|
||||
<ToggleButton value={1} size="small">
|
||||
{measurement.convertWithUnits(1)}
|
||||
</ToggleButton>
|
||||
</ToggleButtonGroup>
|
||||
) : (
|
||||
<Slider
|
||||
disabled={!canWrite(permissions)}
|
||||
valueLabelDisplay="auto"
|
||||
color="secondary"
|
||||
min={measurement.min()}
|
||||
max={measurement.max()}
|
||||
step={measurement.step()}
|
||||
track={false}
|
||||
value={measurement.toDisplay(condition.value ? condition.value : 0)}
|
||||
onChange={(event, value) =>
|
||||
updateConditionValue(source, interactionIndex, conditionIndex, value)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
});
|
||||
return items;
|
||||
};
|
||||
|
||||
const updateConditionValue = (
|
||||
source: Component | undefined,
|
||||
interactionIndex: number,
|
||||
conditionIndex: number,
|
||||
value: number | number[]
|
||||
) => {
|
||||
if (interactions.length > interactionIndex) {
|
||||
let updatedDirtyInteractions = cloneDeep(dirtyInteractions);
|
||||
let updatedInteractions = cloneDeep(interactions);
|
||||
let interaction = updatedInteractions[interactionIndex];
|
||||
let conditions = interaction.settings.conditions;
|
||||
if (conditions.length > conditionIndex) {
|
||||
if (typeof value === "number") {
|
||||
let condition = conditions[conditionIndex];
|
||||
let sourceType = source && source.settings.type;
|
||||
let sourceSubtype = source && source.settings.subtype;
|
||||
let describer = describeMeasurement(condition.measurementType, sourceType, sourceSubtype);
|
||||
condition.value = describer.toStored(value);
|
||||
updatedDirtyInteractions.set(interactionIndex, true);
|
||||
}
|
||||
setInteractions(updatedInteractions);
|
||||
setDirtyInteractions(updatedDirtyInteractions);
|
||||
setRenderToggle(!renderToggle);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const submitInteraction = (
|
||||
deviceID: number,
|
||||
index: number,
|
||||
settings: pond.IInteractionSettings
|
||||
) => {
|
||||
interactionsAPI
|
||||
.updateInteraction(Number(deviceID), settings)
|
||||
.then((response: any) => {
|
||||
let updatedDirtyInteractions = cloneDeep(dirtyInteractions);
|
||||
updatedDirtyInteractions.set(index, false);
|
||||
setDirtyInteractions(updatedDirtyInteractions);
|
||||
success("Successfully updated the interaction for " + component.name());
|
||||
refreshCallback();
|
||||
})
|
||||
.catch((err: any) => {
|
||||
error("Error occurred while updating the interaction for " + component.name());
|
||||
});
|
||||
};
|
||||
|
||||
const cancel = (index: number) => {
|
||||
let updatedDirtyInteractions = cloneDeep(dirtyInteractions);
|
||||
updatedDirtyInteractions.set(index, false);
|
||||
setDirtyInteractions(updatedDirtyInteractions);
|
||||
setInteractions(cloneDeep(props.interactions));
|
||||
};
|
||||
|
||||
const interactionItem = (interaction: Interaction, index: number) => {
|
||||
let key = interaction.key();
|
||||
let source = mappedComponents.get(componentIDToString(interaction.settings.source));
|
||||
let sink = mappedComponents.get(componentIDToString(interaction.settings.sink));
|
||||
let statusText =
|
||||
!interaction.status.synced && interaction.status.lastUpdate
|
||||
? "Pending " +
|
||||
moment(interaction.status.lastUpdate && interaction.status.lastUpdate).fromNow()
|
||||
: "";
|
||||
let schedule = pond.InteractionSchedule.create(
|
||||
interaction.settings.schedule !== null ? interaction.settings.schedule : undefined
|
||||
);
|
||||
let timezone = schedule && schedule.timezone ? schedule.timezone : moment.tz.guess();
|
||||
let todStart = (schedule.timeOfDayStart ? schedule.timeOfDayStart : "00:00").split(":");
|
||||
let todEnd = (schedule.timeOfDayEnd ? schedule.timeOfDayEnd : "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: "allDay" | "before" | "after" | "from" = timeOfDayDescriptor(schedule);
|
||||
let showStart = descriptor === "after" || descriptor === "from";
|
||||
let showEnd = descriptor === "before" || descriptor === "from";
|
||||
let showBoth = showStart && showEnd;
|
||||
let everyDay = schedule.weekdays && schedule.weekdays.length === 7;
|
||||
let action = <React.Fragment />;
|
||||
if (canWrite(permissions)) {
|
||||
action = (
|
||||
<Tooltip title="Settings">
|
||||
<IconButton onClick={() => openInteractionSettings(interaction)}>
|
||||
<Settings />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Grid size={{ xs: 12 }} key={key}>
|
||||
<Card elevation={0} className={classes.card}>
|
||||
<CardHeader
|
||||
className={classes.header}
|
||||
titleTypographyProps={{ variant: "subtitle2" }}
|
||||
title={interactionResultText(interaction, sink)}
|
||||
subheader={statusText}
|
||||
subheaderTypographyProps={{ variant: "caption" }}
|
||||
action={action}
|
||||
/>
|
||||
<CardContent className={classes.content}>
|
||||
{interaction.settings.conditions && (
|
||||
<Grid container>
|
||||
{interactionConditions(source, interaction.settings.conditions, index)}
|
||||
</Grid>
|
||||
)}
|
||||
{(descriptor !== "allDay" || !everyDay) && (
|
||||
<Grid container justifyContent="center">
|
||||
<Typography variant="caption" color="textSecondary">
|
||||
{!everyDay ? abbreviatedWeekdays(schedule) : "Operational"}{" "}
|
||||
{descriptor !== "allDay" && friendlyTimeOfDayDescriptor(descriptor)}{" "}
|
||||
{showStart && start.format("h:mma")} {showBoth && "-"}{" "}
|
||||
{showEnd && end.format("h:mma")}
|
||||
</Typography>
|
||||
</Grid>
|
||||
)}
|
||||
</CardContent>
|
||||
{dirtyInteractions.get(index) && (
|
||||
<CardActions className={classes.actions}>
|
||||
<Button size="small" color="primary" onClick={() => cancel(index)}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
size="small"
|
||||
color="primary"
|
||||
onClick={() => submitInteraction(device.id(), index, interaction.settings)}>
|
||||
Submit
|
||||
</Button>
|
||||
</CardActions>
|
||||
)}
|
||||
</Card>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
const interactionList = (component: Component, interactions: Interaction[]) => {
|
||||
let items: any = [];
|
||||
interactions.forEach((interaction: Interaction, index: number) => {
|
||||
if (isSource(component.location(), interaction)) {
|
||||
items.push(interactionItem(interaction, index));
|
||||
}
|
||||
});
|
||||
|
||||
if (items.length <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return items;
|
||||
};
|
||||
|
||||
return (
|
||||
<Grid container spacing={1}>
|
||||
{interactionList(component, interactions)}
|
||||
<InteractionSettings
|
||||
device={device}
|
||||
components={components}
|
||||
initialComponent={component}
|
||||
initialInteraction={selectedInteraction}
|
||||
mode="update"
|
||||
isDialogOpen={selectedInteraction !== undefined}
|
||||
closeDialogCallback={() => closeInteractionSettings()}
|
||||
refreshCallback={refreshCallback}
|
||||
canEdit={canWrite(permissions)}
|
||||
/>
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue