added the alerts to the bin details part of the bin page, also fixed bug in the new alert dialog

This commit is contained in:
csawatzky 2026-05-19 16:30:22 -06:00
parent f72aeb802f
commit 6ccc1ba263
6 changed files with 167 additions and 14 deletions

View file

@ -230,7 +230,7 @@ export default function OrbitCameraControls(props: Props) {
window.removeEventListener("mouseup", handleMouseUp);
canvas.removeEventListener("wheel", handleWheel);
};
}, [camera, gl, target, clampVerticalRotation, minPhi, maxPhi]);
}, [camera, gl, target, clampVerticalRotation, minPhi, maxPhi, initialRadius, maxRadius]);
return null;
}

View file

@ -27,14 +27,14 @@ interface Props {
}
export default function BinSummary(props: Props){
const {bin, devices, fans, heaters, permissions, componentDevices, componentMap, binPrefs, updateBinCallback} = props
const [currentDevice, setCurrentDevice] = useState<Device | undefined>(undefined)
//const [currentDevice, setCurrentDevice] = useState<Device | undefined>(undefined)
const isMobile = useMobile()
useEffect(() => {
if(devices.length > 0){
setCurrentDevice(devices[0])
}
},[devices])
// useEffect(() => {
// if(devices.length > 0){
// setCurrentDevice(devices[0])
// }
// },[devices])
const activity = (reading: string) => {
//if the device hasnt checked in in 6 hours = missing 12 hours = inactive within 6 hours = live
@ -173,7 +173,13 @@ export default function BinSummary(props: Props){
</Grid2>
<Grid2 size={isMobile ? 12 : 5}>
<Card raised sx={{height: 600}}>
<BinDetails bin={bin} updateBinCallback={updateBinCallback}/>
<BinDetails
bin={bin}
updateBinCallback={updateBinCallback}
componentDevices={componentDevices}
devices={devices}
linkedComponents={componentMap}
permissions={permissions}/>
</Card>
</Grid2>
<Grid2 size={12}>

View file

@ -0,0 +1,137 @@
import { LinearProgress } from "@mui/material";
import { Bin, Component, Device, Interaction } from "models";
import Alerts, { Alert } from "objects/objectInteractions/Alerts";
import NewObjectInteraction from "objects/objectInteractions/NewObjectInteraction";
import { extension } from "pbHelpers/ComponentType";
import { pond } from "protobuf-ts/pond";
import { useGlobalState, useInteractionsAPI } from "providers";
import React, { useCallback, useEffect, useState } from "react";
interface Props {
linkedComponents: Map<string, Component>; //the component key to the component object
componentDevices: Map<string, number>;
bin: Bin
devices: Device[];
permissions: pond.Permission[]
}
export default function BinAlerts(props: Props){
const {linkedComponents, componentDevices, permissions} = props
const [{as}] = useGlobalState();
const interactionsAPI = useInteractionsAPI();
//list of alerts, each alert contains a list of interactions with matching conditions
const [alerts, setAlerts] = useState<Alert[]>([]);
const [loading, setLoading] = useState(false)
const [newAlert, setNewAlert] = useState(false)
const matchConditions = (interaction: Interaction, set: Alert) => {
//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 load = useCallback(()=>{
//load the interactions for the linked components
setLoading(true)
const newInteractions: Interaction[] = [];
const interactionSourceMap = new Map<string, string>();
const sinkOptions: Component[] = [];
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 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);
}
}
}
});
setAlerts(alerts);
}).catch(err => {
console.error("Interaction fetch error:", err)
}).finally(() => {
setLoading(false)
})
},[linkedComponents, interactionsAPI, componentDevices, as])
useEffect(()=>{
load()
},[load])
return (
<React.Fragment>
<NewObjectInteraction
open={newAlert}
onClose={(refresh) => {
setNewAlert(false)
if(refresh){
load()
}
}}
linkedComponents={linkedComponents}
componentDevices={componentDevices}/>
{loading ?
<LinearProgress />
:
<Alerts alerts={alerts} permissions={permissions} addNew={() => {setNewAlert(true)}}/>
}
</React.Fragment>
)
}

View file

@ -2,11 +2,18 @@ import { Box, Tab, Tabs } from "@mui/material";
import React from "react";
import { useState } from "react";
import BinTableView from "./binTableView";
import { Bin } from "models";
import { Bin, Component, Device } from "models";
import Alerts from "objects/objectInteractions/Alerts";
import BinAlerts from "./binAlerts";
import { pond } from "protobuf-ts/pond";
interface Props {
bin: Bin
updateBinCallback?: (bin: Bin) => void
linkedComponents: Map<string, Component>; //the component key to the component object
componentDevices: Map<string, number>;
devices: Device[];
permissions: pond.Permission[]
}
interface TabPanelProps {
@ -30,7 +37,7 @@ function TabPanelMine(props: TabPanelProps) {
}
export default function BinDetails (props: Props) {
const {bin, updateBinCallback} = props
const {bin, updateBinCallback, linkedComponents, componentDevices, devices, permissions} = props
const [currentTab, setCurrentTab] = useState(0)
//this is where we will have the tabs abd tab panels for the table view, alerts, interactions, and analysis
@ -45,14 +52,14 @@ export default function BinDetails (props: Props) {
variant="fullWidth">
<Tab label={"Table View"} value={0} />
<Tab label={"Alerts"} value={1} />
<Tab label={"Interactions"} value={2} />
<Tab label={"Controls"} value={2} />
<Tab label={"Analysis"} value={3} />
</Tabs>
<TabPanelMine value={currentTab} index={0}>
<BinTableView bin={bin} updateBinCallback={updateBinCallback}/>
</TabPanelMine>
<TabPanelMine value={currentTab} index={1}>
<BinAlerts bin={bin} componentDevices={componentDevices} devices={devices} linkedComponents={linkedComponents} permissions={permissions}/>
</TabPanelMine>
<TabPanelMine value={currentTab} index={2}>

View file

@ -179,7 +179,6 @@ export default function NewObjectInteraction(props: Props){
setNewAlertComponentType(quack.ComponentType.COMPONENT_TYPE_INVALID);
setConditions([]);
setSelectedAlertComponents([]);
setValidComponents([])
setValStrings(["0", "0"]);
setNumConditions(0)
setNodeOptions([]);
@ -190,7 +189,6 @@ export default function NewObjectInteraction(props: Props){
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)
@ -215,12 +213,15 @@ export default function NewObjectInteraction(props: Props){
//display the components that match the type that was selected for the new alert
const listMatchingComponents = () => {
console.log("listing")
let matching: Component[] = [];
console.log(validComponents)
validComponents.forEach(comp => {
if (comp.type() === newAlertComponentType) {
matching.push(comp);
}
});
console.log(matching)
return (
<List>
{matching.map(comp => (
@ -1018,6 +1019,7 @@ export default function NewObjectInteraction(props: Props){
displayEmpty
value={newAlertComponentType}
onChange={e => {
console.log("type change")
setNewAlertComponentType(e.target.value as quack.ComponentType);
setSelectedAlertComponents([]);
setConditions(initialConditions(e.target.value as quack.ComponentType));

View file

@ -126,6 +126,7 @@ export default function BinV2(){
attachedDevIds.push(resp.data.componentDevices[k]);
}
});
setComponentDevices(newComponentDevices);
let newInteractionDevices = new Map<string, number>();