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); window.removeEventListener("mouseup", handleMouseUp);
canvas.removeEventListener("wheel", handleWheel); canvas.removeEventListener("wheel", handleWheel);
}; };
}, [camera, gl, target, clampVerticalRotation, minPhi, maxPhi]); }, [camera, gl, target, clampVerticalRotation, minPhi, maxPhi, initialRadius, maxRadius]);
return null; return null;
} }

View file

@ -27,14 +27,14 @@ interface Props {
} }
export default function BinSummary(props: Props){ export default function BinSummary(props: Props){
const {bin, devices, fans, heaters, permissions, componentDevices, componentMap, binPrefs, updateBinCallback} = 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() const isMobile = useMobile()
useEffect(() => { // useEffect(() => {
if(devices.length > 0){ // if(devices.length > 0){
setCurrentDevice(devices[0]) // setCurrentDevice(devices[0])
} // }
},[devices]) // },[devices])
const activity = (reading: string) => { const activity = (reading: string) => {
//if the device hasnt checked in in 6 hours = missing 12 hours = inactive within 6 hours = live //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>
<Grid2 size={isMobile ? 12 : 5}> <Grid2 size={isMobile ? 12 : 5}>
<Card raised sx={{height: 600}}> <Card raised sx={{height: 600}}>
<BinDetails bin={bin} updateBinCallback={updateBinCallback}/> <BinDetails
bin={bin}
updateBinCallback={updateBinCallback}
componentDevices={componentDevices}
devices={devices}
linkedComponents={componentMap}
permissions={permissions}/>
</Card> </Card>
</Grid2> </Grid2>
<Grid2 size={12}> <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 React from "react";
import { useState } from "react"; import { useState } from "react";
import BinTableView from "./binTableView"; 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 { interface Props {
bin: Bin bin: Bin
updateBinCallback?: (bin: Bin) => void 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 { interface TabPanelProps {
@ -30,7 +37,7 @@ function TabPanelMine(props: TabPanelProps) {
} }
export default function BinDetails (props: Props) { export default function BinDetails (props: Props) {
const {bin, updateBinCallback} = props const {bin, updateBinCallback, linkedComponents, componentDevices, devices, permissions} = props
const [currentTab, setCurrentTab] = useState(0) const [currentTab, setCurrentTab] = useState(0)
//this is where we will have the tabs abd tab panels for the table view, alerts, interactions, and analysis //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"> variant="fullWidth">
<Tab label={"Table View"} value={0} /> <Tab label={"Table View"} value={0} />
<Tab label={"Alerts"} value={1} /> <Tab label={"Alerts"} value={1} />
<Tab label={"Interactions"} value={2} /> <Tab label={"Controls"} value={2} />
<Tab label={"Analysis"} value={3} /> <Tab label={"Analysis"} value={3} />
</Tabs> </Tabs>
<TabPanelMine value={currentTab} index={0}> <TabPanelMine value={currentTab} index={0}>
<BinTableView bin={bin} updateBinCallback={updateBinCallback}/> <BinTableView bin={bin} updateBinCallback={updateBinCallback}/>
</TabPanelMine> </TabPanelMine>
<TabPanelMine value={currentTab} index={1}> <TabPanelMine value={currentTab} index={1}>
<BinAlerts bin={bin} componentDevices={componentDevices} devices={devices} linkedComponents={linkedComponents} permissions={permissions}/>
</TabPanelMine> </TabPanelMine>
<TabPanelMine value={currentTab} index={2}> <TabPanelMine value={currentTab} index={2}>

View file

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

View file

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