refactoring object interactions into multiple components and taking into account controller interactions having to be on the same device where the alerts dont matter
This commit is contained in:
parent
bdddcfc103
commit
3cdddb928f
7 changed files with 459 additions and 2 deletions
|
|
@ -1,6 +1,7 @@
|
||||||
import { pond } from "protobuf-ts/pond";
|
import { pond } from "protobuf-ts/pond";
|
||||||
import { or } from "utils/types";
|
import { or } from "utils/types";
|
||||||
import { cloneDeep } from "lodash";
|
import { cloneDeep } from "lodash";
|
||||||
|
import { Result } from "pbHelpers/Enums";
|
||||||
|
|
||||||
export class Interaction {
|
export class Interaction {
|
||||||
public settings: pond.InteractionSettings = pond.InteractionSettings.create();
|
public settings: pond.InteractionSettings = pond.InteractionSettings.create();
|
||||||
|
|
@ -53,6 +54,18 @@ export class Interaction {
|
||||||
return subtype;
|
return subtype;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//these two are not mutually exclusive, it is possible to be an alert and a control
|
||||||
|
public isAlert(): boolean {
|
||||||
|
if(this.settings.result?.type === Result.report) return true
|
||||||
|
if(this.settings.notifications?.reports && this.settings.notifications.notify) return true
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
public isControl(): boolean {
|
||||||
|
if(this.settings.result?.type !== Result.report) return true //as of right now all interactions that are not report are some sort of control
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
public static upToSubtype(nodeNumber: number): number {
|
public static upToSubtype(nodeNumber: number): number {
|
||||||
let subtype = 0;
|
let subtype = 0;
|
||||||
switch (nodeNumber) {
|
switch (nodeNumber) {
|
||||||
|
|
|
||||||
74
src/objects/objectInteractions/Alerts.tsx
Normal file
74
src/objects/objectInteractions/Alerts.tsx
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
import { ExpandMore } from "@mui/icons-material";
|
||||||
|
import { Accordion, AccordionDetails, AccordionSummary, Box, Grid2, List, ListItem, ListSubheader, Typography } from "@mui/material";
|
||||||
|
import { Component } from "models";
|
||||||
|
import { describeMeasurement } from "pbHelpers/MeasurementDescriber";
|
||||||
|
import { pond, quack } from "protobuf-ts/pond";
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import NewObjectInteraction from "./NewObjectInteraction";
|
||||||
|
|
||||||
|
export interface Alert {
|
||||||
|
conditions: pond.InteractionCondition[];
|
||||||
|
sourceComponents: Component[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
alerts: Alert[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Alerts(props: Props){
|
||||||
|
const {alerts} = props
|
||||||
|
const [openNewAlert, setOpenNewAlert] = useState(false)
|
||||||
|
|
||||||
|
const readableCondition = (condition: pond.InteractionCondition) => {
|
||||||
|
let describer = describeMeasurement(condition.measurementType);
|
||||||
|
let type = describer.label();
|
||||||
|
let comparison =
|
||||||
|
condition.comparison === quack.RelationalOperator.RELATIONAL_OPERATOR_EQUAL_TO
|
||||||
|
? "Exactly"
|
||||||
|
: condition.comparison === quack.RelationalOperator.RELATIONAL_OPERATOR_GREATER_THAN
|
||||||
|
? "Above"
|
||||||
|
: "Below";
|
||||||
|
let value = describer.toDisplay(condition.value);
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Typography>{type + " " + comparison + " " + value + describer.GetUnit()}</Typography>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const alertAccordion = (alert: Alert) => {
|
||||||
|
return (
|
||||||
|
<Accordion style={{ width: "100%" }}>
|
||||||
|
<AccordionSummary expandIcon={<ExpandMore />}>
|
||||||
|
<Grid2 container direction="column">
|
||||||
|
{alert.conditions.map((condition, i) => (
|
||||||
|
<Grid2 key={i}>
|
||||||
|
{readableCondition(condition)}
|
||||||
|
</Grid2>
|
||||||
|
))}
|
||||||
|
</Grid2>
|
||||||
|
</AccordionSummary>
|
||||||
|
<AccordionDetails>
|
||||||
|
<List>
|
||||||
|
<ListSubheader>Reporting Components</ListSubheader>
|
||||||
|
{alert.sourceComponents.map((comp, i) => (
|
||||||
|
<ListItem key={i}>{comp.name()}</ListItem>
|
||||||
|
))}
|
||||||
|
</List>
|
||||||
|
</AccordionDetails>
|
||||||
|
</Accordion>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<NewObjectInteraction open={openNewAlert} onClose={()=>{}}/>
|
||||||
|
<List style={{ margin: 5 }}>
|
||||||
|
<ListSubheader>Alerts</ListSubheader>
|
||||||
|
{alerts.map((alert, i) => (
|
||||||
|
<ListItem key={i}>{alertAccordion(alert)}</ListItem>
|
||||||
|
))}
|
||||||
|
</List>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
61
src/objects/objectInteractions/Controls.tsx
Normal file
61
src/objects/objectInteractions/Controls.tsx
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
import { Accordion, AccordionDetails, AccordionSummary, List, ListItem, ListSubheader } from "@mui/material";
|
||||||
|
import { Component, Device } from "models";
|
||||||
|
import { pond } from "protobuf-ts/pond";
|
||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
export interface Control {
|
||||||
|
conditions: pond.InteractionCondition[];
|
||||||
|
sourceComponents: Component[];
|
||||||
|
result: pond.InteractionResult | null;
|
||||||
|
sinkComponent: Component
|
||||||
|
device: Device
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
controls: Control[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Controls(props: Props){
|
||||||
|
const { controls } = props
|
||||||
|
const [deviceControls, setDeviceControls] = useState<Map<number, Control[]>>(new Map())
|
||||||
|
const [deviceNameMap, setDeviceNameMap] = useState<Map<number, string>>(new Map())
|
||||||
|
|
||||||
|
useEffect(()=>{
|
||||||
|
let map: Map<number, Control[]> = new Map()
|
||||||
|
let nameMap: Map<number, string> = new Map()
|
||||||
|
controls.forEach(control => {
|
||||||
|
let mappedControls = map.get(control.device.id())
|
||||||
|
if(mappedControls){
|
||||||
|
mappedControls.push(control)
|
||||||
|
}else{
|
||||||
|
map.set(control.device.id(), [control])
|
||||||
|
}
|
||||||
|
if(!nameMap.get(control.device.id())){
|
||||||
|
nameMap.set(control.device.id(), control.device.name())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
setDeviceControls(map)
|
||||||
|
setDeviceNameMap(nameMap)
|
||||||
|
},[controls])
|
||||||
|
|
||||||
|
const controlAccordion = (control: Control) => {
|
||||||
|
return (
|
||||||
|
<Accordion>
|
||||||
|
<AccordionSummary>{control.device.name()}</AccordionSummary>
|
||||||
|
<AccordionDetails></AccordionDetails>
|
||||||
|
</Accordion>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<List style={{ margin: 5 }}>
|
||||||
|
<ListSubheader>Controls</ListSubheader>
|
||||||
|
{controls.map((control, i) => (
|
||||||
|
<ListItem key={i}>{controlAccordion(control)}</ListItem>
|
||||||
|
))}
|
||||||
|
</List>
|
||||||
|
</React.Fragment>
|
||||||
|
)
|
||||||
|
}
|
||||||
94
src/objects/objectInteractions/NewObjectInteraction.tsx
Normal file
94
src/objects/objectInteractions/NewObjectInteraction.tsx
Normal file
|
|
@ -0,0 +1,94 @@
|
||||||
|
import { Button, DialogActions, DialogContent, DialogTitle, MenuItem, Select } from "@mui/material";
|
||||||
|
import ResponsiveDialog from "common/ResponsiveDialog";
|
||||||
|
import { getMeasurements } from "pbHelpers/ComponentType";
|
||||||
|
import { Measurement, Operator } from "pbHelpers/Enums";
|
||||||
|
import { pond } from "protobuf-ts/pond";
|
||||||
|
import { quack } from "protobuf-ts/quack";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
open: boolean
|
||||||
|
onClose: (refreshCallback: boolean) => void
|
||||||
|
//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?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function NewObjectInteraction(props: Props){
|
||||||
|
const { open, onClose } = props
|
||||||
|
const [newAlertComponentType, setNewAlertComponentType] = useState<quack.ComponentType>(
|
||||||
|
quack.ComponentType.COMPONENT_TYPE_INVALID
|
||||||
|
);
|
||||||
|
const [conditions, setConditions] = useState<pond.InteractionCondition[]>([]);
|
||||||
|
const [selectedAlertComponents, setSelectedAlertComponents] = useState<string[]>([]);
|
||||||
|
|
||||||
|
useEffect(() => {},[])
|
||||||
|
|
||||||
|
|
||||||
|
const close = (refresh: boolean) => {
|
||||||
|
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];
|
||||||
|
};
|
||||||
|
|
||||||
|
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>
|
||||||
|
{typeOptions.map(option => (
|
||||||
|
<MenuItem key={option.value} value={option.value}>
|
||||||
|
{option.label}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
{/* list of checkboxes for the components that match that type */}
|
||||||
|
{listMatchingComponents()}
|
||||||
|
{/* have the conditions set here */}
|
||||||
|
{conditionInput()}
|
||||||
|
{/* have the schedule set here */}
|
||||||
|
{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>
|
||||||
|
)
|
||||||
|
}
|
||||||
0
src/objects/objectInteractions/Notifications.tsx
Normal file
0
src/objects/objectInteractions/Notifications.tsx
Normal file
204
src/objects/objectInteractions/ObjectInteractions.tsx
Normal file
204
src/objects/objectInteractions/ObjectInteractions.tsx
Normal file
|
|
@ -0,0 +1,204 @@
|
||||||
|
import { Box, Button, Card, Typography } from "@mui/material";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import Alerts, { Alert } from "./Alerts";
|
||||||
|
import Controls, { Control } from "./Controls";
|
||||||
|
import { Component, Device, Interaction } from "models";
|
||||||
|
import { quack } from "protobuf-ts/quack";
|
||||||
|
import { Option } from "common/SearchSelect";
|
||||||
|
import { useInteractionsAPI } from "hooks";
|
||||||
|
import { useGlobalState } from "providers";
|
||||||
|
import { extension, getFriendlyName } from "pbHelpers/ComponentType";
|
||||||
|
import { sameComponentID } from "pbHelpers/Component";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
linkedComponents: Map<string, Component>; //the component key to the component object
|
||||||
|
componentDevices: Map<string, number>;
|
||||||
|
devices: Device[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ObjectInteractions(props: Props) {
|
||||||
|
const { linkedComponents, componentDevices, devices } = props
|
||||||
|
const [{as}] = useGlobalState()
|
||||||
|
//list of alerts, each alert contains a list of interactions with matching conditions
|
||||||
|
const [alerts, setAlerts] = useState<Alert[]>([]);
|
||||||
|
//list of interactions relating to a controller
|
||||||
|
const [controls, setControls] = useState<Control[]>([]);
|
||||||
|
const interactionsAPI = useInteractionsAPI();
|
||||||
|
const [sinkOptions, setSinkOptions] = useState<Component[]>([])
|
||||||
|
const [typeOptions, setTypeOptions] = useState<Option[]>([]);
|
||||||
|
|
||||||
|
|
||||||
|
const matchConditions = (interaction: Interaction, set: Alert | Control) => {
|
||||||
|
//if the number of conditions are not the same they are different
|
||||||
|
if (interaction.settings.conditions.length !== set.conditions.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
//continue with the comparison
|
||||||
|
let matching = true;
|
||||||
|
interaction.settings.conditions.forEach((condition, i) => {
|
||||||
|
if (
|
||||||
|
condition.comparison !== set.conditions[i].comparison ||
|
||||||
|
condition.measurementType !== set.conditions[i].measurementType ||
|
||||||
|
condition.value !== set.conditions[i].value
|
||||||
|
) {
|
||||||
|
matching = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return matching;
|
||||||
|
};
|
||||||
|
|
||||||
|
const matchResult = (interaction: Interaction, set: Control) => {
|
||||||
|
let interactionResult = interaction.settings.result
|
||||||
|
let setResult = set.result
|
||||||
|
let matching = true
|
||||||
|
// if anything in the result for the interaction is different from the set make matching false
|
||||||
|
if(interactionResult?.type !== setResult?.type ||
|
||||||
|
interactionResult?.mode !== setResult?.mode ||
|
||||||
|
interactionResult?.value !== setResult?.value ||
|
||||||
|
interactionResult?.dutyCycle !== setResult?.dutyCycle
|
||||||
|
){
|
||||||
|
matching = false
|
||||||
|
}
|
||||||
|
return matching
|
||||||
|
}
|
||||||
|
|
||||||
|
const findDevice = (deviceID: number) => {
|
||||||
|
for (let device of devices) {
|
||||||
|
if (device.id() === deviceID) return device
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const findSinkComponent = (sinks: Component[], interactionSink: quack.ComponentID, sourceDevice: number) => {
|
||||||
|
for (let component of sinks) {
|
||||||
|
if (sameComponentID(component.location(), interactionSink) && sourceDevice === componentDevices.get(component.key())){
|
||||||
|
return component
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const newInteractions: Interaction[] = [];
|
||||||
|
const interactionSourceMap = new Map<string, string>();
|
||||||
|
const includedOps: quack.ComponentType[] = [];
|
||||||
|
const typeOps: Option[] = [];
|
||||||
|
const sinkOptions: Component[] = [];
|
||||||
|
const alerts: Alert[] = [];
|
||||||
|
const controls: Control[] = [];
|
||||||
|
|
||||||
|
const run = async () => {
|
||||||
|
// Run all component API fetches in parallel
|
||||||
|
await Promise.all(
|
||||||
|
[...linkedComponents].map(async ([key, comp]) => {
|
||||||
|
const device = componentDevices.get(key);
|
||||||
|
|
||||||
|
if (device) {
|
||||||
|
const resp = await interactionsAPI.listInteractionsByComponent(
|
||||||
|
device,
|
||||||
|
comp.location(),
|
||||||
|
undefined,
|
||||||
|
as
|
||||||
|
);
|
||||||
|
|
||||||
|
resp.forEach(interaction => interactionSourceMap.set(interaction.key(), key));
|
||||||
|
newInteractions.push(...resp);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect component types
|
||||||
|
if (!includedOps.includes(comp.type())) {
|
||||||
|
includedOps.push(comp.type());
|
||||||
|
typeOps.push({
|
||||||
|
label: getFriendlyName(comp.type()),
|
||||||
|
value: comp.type(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect controller components
|
||||||
|
if (extension(comp.type()).isController) {
|
||||||
|
sinkOptions.push(comp);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
run().then(() => {
|
||||||
|
newInteractions.forEach(interaction => {
|
||||||
|
//alert and control are not mutally exclusive, it is possible to be both if the control is sending notifications
|
||||||
|
if (interaction.isAlert()) {
|
||||||
|
// Find matching alert
|
||||||
|
let similarAlertIndex = alerts.findIndex(alert =>
|
||||||
|
matchConditions(interaction, alert)
|
||||||
|
);
|
||||||
|
|
||||||
|
const compKey = interactionSourceMap.get(interaction.key());
|
||||||
|
const component = compKey ? linkedComponents.get(compKey) : undefined;
|
||||||
|
|
||||||
|
if (component) {
|
||||||
|
if (similarAlertIndex === -1) {
|
||||||
|
alerts.push({
|
||||||
|
conditions: interaction.settings.conditions,
|
||||||
|
sourceComponents: [component],
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
alerts[similarAlertIndex].sourceComponents.push(component);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
if (interaction.isControl()) {
|
||||||
|
if (!interaction.settings.sink) return //if there is no sink it cant be a control so move on to the next
|
||||||
|
const compKey = interactionSourceMap.get(interaction.key());
|
||||||
|
if (!compKey) return //we have to have a valid component key
|
||||||
|
const component = linkedComponents.get(compKey);
|
||||||
|
const deviceID = componentDevices.get(compKey);
|
||||||
|
if (!deviceID) return //we have to know which device it is for
|
||||||
|
const controlDevice = findDevice(deviceID)
|
||||||
|
if (!controlDevice) return //failed to get the device from the array
|
||||||
|
const sinkComponent = findSinkComponent(sinkOptions, interaction.settings.sink, deviceID)
|
||||||
|
if (!sinkComponent) return //failed to get the correct sink component
|
||||||
|
|
||||||
|
let similarControlIndex = controls.findIndex(control =>
|
||||||
|
matchConditions(interaction, control) &&
|
||||||
|
matchResult(interaction, control) &&
|
||||||
|
sameComponentID(interaction.settings.sink, control.sinkComponent.location()) && //if the sink for the interaction is the same address as the existing control
|
||||||
|
deviceID === control.device.id() //if the device id for source component is the same as the device id in the control
|
||||||
|
);
|
||||||
|
|
||||||
|
if (component && interaction.settings.result) {
|
||||||
|
if (similarControlIndex === -1) {
|
||||||
|
controls.push({
|
||||||
|
conditions: interaction.settings.conditions,
|
||||||
|
result: interaction.settings.result,
|
||||||
|
sourceComponents: [component],
|
||||||
|
sinkComponent: sinkComponent,
|
||||||
|
device: controlDevice
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
controls[similarControlIndex].sourceComponents.push(component);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
setTypeOptions(typeOps);
|
||||||
|
setSinkOptions(sinkOptions);
|
||||||
|
setAlerts(alerts);
|
||||||
|
console.log(alerts)
|
||||||
|
setControls(controls);
|
||||||
|
console.log(controls)
|
||||||
|
}).catch(err => {
|
||||||
|
console.error("Interaction fetch error:", err)
|
||||||
|
})
|
||||||
|
}, [linkedComponents, interactionsAPI, componentDevices, as]);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card raised style={{ margin: 5 }}>
|
||||||
|
<Box padding={1}>
|
||||||
|
<Typography style={{ fontWeight: 650, fontSize: 25 }}>Interactions</Typography>
|
||||||
|
<Controls controls={controls}/>
|
||||||
|
<Alerts alerts={alerts}/>
|
||||||
|
{/* <Notifications /> */}
|
||||||
|
</Box>
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -69,6 +69,7 @@ import TaskViewer from "tasks/TaskViewer";
|
||||||
import ButtonGroup from "common/ButtonGroup";
|
import ButtonGroup from "common/ButtonGroup";
|
||||||
import BinTransactions from "bin/BinTransactions";
|
import BinTransactions from "bin/BinTransactions";
|
||||||
import { CO2 } from "models/CO2";
|
import { CO2 } from "models/CO2";
|
||||||
|
import ObjectInteractions from "objects/objectInteractions/ObjectInteractions";
|
||||||
|
|
||||||
interface TabPanelProps {
|
interface TabPanelProps {
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
|
|
@ -896,11 +897,16 @@ export default function Bin(props: Props) {
|
||||||
</Box>
|
</Box>
|
||||||
{detail === "alerts" && (
|
{detail === "alerts" && (
|
||||||
<Box>
|
<Box>
|
||||||
<ObjectAlerts
|
{/* <ObjectAlerts
|
||||||
linkedComponents={components}
|
linkedComponents={components}
|
||||||
componentDevices={componentDevices}
|
componentDevices={componentDevices}
|
||||||
objectType={pond.ObjectType.OBJECT_TYPE_BIN}
|
objectType={pond.ObjectType.OBJECT_TYPE_BIN}
|
||||||
objectKey={bin.key()}
|
objectKey={bin.key()}
|
||||||
|
/> */}
|
||||||
|
<ObjectInteractions
|
||||||
|
linkedComponents={components}
|
||||||
|
componentDevices={componentDevices}
|
||||||
|
devices={devices}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
|
|
@ -1033,11 +1039,16 @@ export default function Bin(props: Props) {
|
||||||
</TabPanelMine>
|
</TabPanelMine>
|
||||||
<TabPanelMine value={mobileTab} index={4}>
|
<TabPanelMine value={mobileTab} index={4}>
|
||||||
<Box>
|
<Box>
|
||||||
<ObjectAlerts
|
{/* <ObjectAlerts
|
||||||
linkedComponents={components}
|
linkedComponents={components}
|
||||||
componentDevices={componentDevices}
|
componentDevices={componentDevices}
|
||||||
objectType={pond.ObjectType.OBJECT_TYPE_BIN}
|
objectType={pond.ObjectType.OBJECT_TYPE_BIN}
|
||||||
objectKey={bin.key()}
|
objectKey={bin.key()}
|
||||||
|
/> */}
|
||||||
|
<ObjectInteractions
|
||||||
|
linkedComponents={components}
|
||||||
|
componentDevices={componentDevices}
|
||||||
|
devices={devices}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</TabPanelMine>
|
</TabPanelMine>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue