409 lines
13 KiB
TypeScript
409 lines
13 KiB
TypeScript
import {
|
|
Avatar,
|
|
Box,
|
|
Button,
|
|
Card,
|
|
CardContent,
|
|
Grid2 as Grid,
|
|
ListItemAvatar,
|
|
ListItemText,
|
|
Theme,
|
|
Typography,
|
|
//useTheme
|
|
} from "@mui/material";
|
|
import { GetDefaultDateRange } from "common/time/DateRange";
|
|
import UnitMeasurementSummary from "component/UnitMeasurementSummary";
|
|
import { useThemeType } from "hooks";
|
|
import { Component, Device } from "models";
|
|
import { ObjectHeater } from "models/ObjectHeater";
|
|
import { UnitMeasurement } from "models/UnitMeasurement";
|
|
import moment from "moment";
|
|
import { GetComponentIcon } from "pbHelpers/ComponentType";
|
|
import ObjectHeaterIcon from "products/Construction/ObjectHeaterIcon";
|
|
import { pond, quack } from "protobuf-ts/pond";
|
|
import { useGlobalState, useMutationAPI } from "providers";
|
|
import React, { useEffect, useState } from "react";
|
|
import FuelDark from "assets/components/fuelDark.png";
|
|
import FuelLight from "assets/components/fuelLight.png";
|
|
//import { useHistory } from "react-router";
|
|
import { useNavigate } from "react-router-dom";
|
|
import Link from "@mui/icons-material/Link";
|
|
import EventBlocker from "common/EventBlocker";
|
|
import { GetDeviceProductIcon } from "products/DeviceProduct";
|
|
import { makeStyles } from "@mui/styles";
|
|
|
|
interface Props {
|
|
heaterData: pond.ObjectHeaterData;
|
|
linkDevice: (heater: ObjectHeater, linkedDevices: pond.ComprehensiveDevice[]) => void;
|
|
}
|
|
|
|
const useStyles = makeStyles((theme: Theme) => ({
|
|
avatarIcon: {
|
|
width: theme.spacing(3),
|
|
height: theme.spacing(3)
|
|
},
|
|
listAvatar: {
|
|
minWidth: theme.spacing(5)
|
|
},
|
|
avatarImage: {
|
|
width: theme.spacing(3),
|
|
height: theme.spacing(3)
|
|
},
|
|
textOverflow: {
|
|
whiteSpace: "nowrap",
|
|
overflow: "hidden",
|
|
textOverflow: "ellipsis",
|
|
textTransform: "capitalize"
|
|
},
|
|
listText: {
|
|
margin: 0,
|
|
height: "100%"
|
|
}
|
|
})
|
|
);
|
|
|
|
export default function ObjectHeaterCard(props: Props) {
|
|
const { heaterData, linkDevice } = props;
|
|
const [heater, setHeater] = useState<ObjectHeater>(ObjectHeater.create());
|
|
const [connectedDevice, setConnectedDevice] = useState<Device>();
|
|
const mutationAPI = useMutationAPI();
|
|
const [heaterComponent, setHeaterComponent] = useState<Component>();
|
|
const [heaterTempComponent, setHeaterTempComponent] = useState<Component>();
|
|
const [siteTempComponent, setSiteTempComponent] = useState<Component>();
|
|
const [mutationsLoading, setMutationsLoading] = useState(false);
|
|
const classes = useStyles();
|
|
const themeType = useThemeType();
|
|
const [{ user }] = useGlobalState();
|
|
const [lastFuelCalc, setLastFuelCalc] = useState<pond.ValueAt>();
|
|
const [lastFuelMutation, setLastFuelMutation] = useState<pond.LinearMutation>();
|
|
const [allComponentsRecent, setAllComponentsRecent] = useState(true);
|
|
//const history = useHistory();
|
|
const navigate = useNavigate();
|
|
//const theme = useTheme();
|
|
|
|
useEffect(() => {
|
|
let heater = ObjectHeater.any(heaterData.heater);
|
|
setHeater(heater);
|
|
if (!mutationsLoading && heater) {
|
|
let fuelMutation: pond.LinearMutation | undefined;
|
|
heater.settings.mutations.forEach(mutation => {
|
|
if (mutation.mutation === pond.Mutator.MUTATOR_FUEL_LEVEL) {
|
|
fuelMutation = mutation;
|
|
}
|
|
});
|
|
if (fuelMutation !== undefined) {
|
|
mutationAPI
|
|
.linearMutation(
|
|
fuelMutation,
|
|
GetDefaultDateRange()
|
|
.end.add(-1, "hour")
|
|
.toISOString(),
|
|
GetDefaultDateRange().end.toISOString()
|
|
)
|
|
.then(resp => {
|
|
resp.data.values.forEach(val => {
|
|
setLastFuelCalc(val);
|
|
});
|
|
setLastFuelMutation(fuelMutation);
|
|
setMutationsLoading(false);
|
|
});
|
|
}
|
|
}
|
|
if (heaterData.devices[0]) {
|
|
setConnectedDevice(Device.any(heaterData.devices[0].device));
|
|
heaterData.devices[0].components.forEach(c => {
|
|
//loop through the components and assign them accordingly
|
|
let component = Component.any(c);
|
|
if (
|
|
component.type() === quack.ComponentType.COMPONENT_TYPE_BOOLEAN_OUTPUT &&
|
|
component.subType() === quack.BooleanOutputSubtype.BOOLEAN_OUTPUT_SUBTYPE_HEATER
|
|
) {
|
|
setHeaterComponent(component);
|
|
component.lastMeasurement.forEach(um => {
|
|
if (
|
|
um.timestamps.length > 0 &&
|
|
um.type === quack.MeasurementType.MEASUREMENT_TYPE_BOOLEAN
|
|
) {
|
|
if (
|
|
moment().diff(moment(um.timestamps[0]), "milliseconds") >
|
|
component.settings.reportPeriodMs
|
|
) {
|
|
setAllComponentsRecent(false);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
if (component.type() === quack.ComponentType.COMPONENT_TYPE_TEMPERATURE) {
|
|
setHeaterTempComponent(component);
|
|
component.lastMeasurement.forEach(um => {
|
|
if (
|
|
um.timestamps.length > 0 &&
|
|
um.type === quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE
|
|
) {
|
|
if (
|
|
moment().diff(moment(um.timestamps[0]), "milliseconds") >
|
|
component.settings.reportPeriodMs
|
|
) {
|
|
setAllComponentsRecent(false);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
if (component.type() === quack.ComponentType.COMPONENT_TYPE_DHT) {
|
|
setSiteTempComponent(component);
|
|
component.lastMeasurement.forEach(um => {
|
|
if (
|
|
um.timestamps.length > 0 &&
|
|
um.type === quack.MeasurementType.MEASUREMENT_TYPE_BOOLEAN
|
|
) {
|
|
if (
|
|
moment().diff(moment(um.timestamps[0]), "milliseconds") >
|
|
component.settings.reportPeriodMs
|
|
) {
|
|
setAllComponentsRecent(false);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
});
|
|
}
|
|
}, [heaterData, mutationAPI]); //eslint-disable-line react-hooks/exhaustive-deps
|
|
|
|
const goToHeater = () => {
|
|
//history.push("/objectHeaters/" + heater.key);
|
|
let path = "/objectHeaters/" + heater.key;
|
|
navigate(path, { state: {heater: heater} });
|
|
};
|
|
|
|
const componentDisplay = (component?: Component) => {
|
|
return (
|
|
<Grid container direction="row" wrap="nowrap" alignContent="center" alignItems="center">
|
|
{component ? (
|
|
<React.Fragment>
|
|
<Grid>
|
|
<Avatar
|
|
variant="square"
|
|
alt="icon"
|
|
src={GetComponentIcon(component.type(), component.subType(), themeType)}
|
|
style={{ float: "left" }}
|
|
className={classes.avatarIcon}
|
|
/>
|
|
</Grid>
|
|
<Grid style={{ paddingLeft: 5 }}>
|
|
<UnitMeasurementSummary
|
|
component={component}
|
|
reading={UnitMeasurement.convertLastMeasurement(
|
|
component.lastMeasurement.map(um => UnitMeasurement.any(um, user))
|
|
)}
|
|
omitTime
|
|
dense
|
|
centered
|
|
onlyRecent
|
|
/>
|
|
</Grid>
|
|
</React.Fragment>
|
|
) : (
|
|
"Component Not Found"
|
|
)}
|
|
</Grid>
|
|
);
|
|
};
|
|
|
|
//// TODO-CS: Make a mutator describer to put the get functions into ////
|
|
const getMutationIcon = (mutation: pond.Mutator, themeType: string) => {
|
|
if (mutation === pond.Mutator.MUTATOR_FUEL_LEVEL) {
|
|
return themeType === "light" ? FuelDark : FuelLight;
|
|
}
|
|
return "";
|
|
};
|
|
|
|
const getMutationColour = (mutation: pond.Mutator) => {
|
|
if (mutation === pond.Mutator.MUTATOR_FUEL_LEVEL) {
|
|
return "lightBlue";
|
|
}
|
|
return "";
|
|
};
|
|
|
|
const getMutationUnit = (mutation: pond.Mutator) => {
|
|
if (mutation === pond.Mutator.MUTATOR_FUEL_LEVEL) {
|
|
return "%";
|
|
}
|
|
return "";
|
|
};
|
|
|
|
//// /\ /\ /\ /\ /\ /\ /\ /\ /\ /\ /\ /\ /\ /\ /\ /\ /\ /\ /\ /\ /\ ////
|
|
|
|
const fuelMutationDisplay = () => {
|
|
if (!lastFuelMutation) {
|
|
return (
|
|
<Grid container direction="row" wrap="nowrap" alignContent="center" alignItems="center">
|
|
<Grid>
|
|
<Avatar
|
|
variant="square"
|
|
alt="icon"
|
|
src={getMutationIcon(pond.Mutator.MUTATOR_FUEL_LEVEL, themeType)}
|
|
style={{ float: "left" }}
|
|
className={classes.avatarIcon}
|
|
/>
|
|
</Grid>
|
|
<Grid style={{ paddingLeft: 5 }}>
|
|
N/A
|
|
</Grid>
|
|
</Grid>
|
|
);
|
|
}
|
|
if (lastFuelCalc) {
|
|
return (
|
|
<Grid container>
|
|
<Grid>
|
|
<Avatar
|
|
variant="square"
|
|
alt="icon"
|
|
src={getMutationIcon(pond.Mutator.MUTATOR_FUEL_LEVEL, themeType)}
|
|
style={{ float: "left" }}
|
|
className={classes.avatarIcon}
|
|
/>
|
|
</Grid>
|
|
<Grid style={{ paddingLeft: 5 }}>
|
|
<Typography variant="caption" color="textPrimary">
|
|
<span>
|
|
<span style={{ color: getMutationColour(pond.Mutator.MUTATOR_FUEL_LEVEL) }}>
|
|
{(lastFuelCalc.value / 10).toFixed(2) +
|
|
getMutationUnit(pond.Mutator.MUTATOR_FUEL_LEVEL)}
|
|
</span>
|
|
</span>
|
|
</Typography>
|
|
</Grid>
|
|
</Grid>
|
|
);
|
|
}
|
|
return (
|
|
<Grid container direction="row" wrap="nowrap" alignContent="center" alignItems="center">
|
|
<Grid>
|
|
<Avatar
|
|
variant="square"
|
|
alt="icon"
|
|
src={getMutationIcon(pond.Mutator.MUTATOR_FUEL_LEVEL, themeType)}
|
|
style={{ float: "left" }}
|
|
className={classes.avatarIcon}
|
|
/>
|
|
</Grid>
|
|
<Grid style={{ paddingLeft: 5 }}>
|
|
No Recent Data
|
|
</Grid>
|
|
</Grid>
|
|
);
|
|
};
|
|
|
|
//content of the card if a device is linked to the heater
|
|
const cardBody = (device?: Device) => {
|
|
const productIcon = GetDeviceProductIcon(device ?? Device.create());
|
|
|
|
if(device){
|
|
return (
|
|
<React.Fragment>
|
|
<Box height="100%" display="flex" alignItems="center" flexWrap="nowrap" padding={2}>
|
|
<ListItemAvatar className={classes.listAvatar}>
|
|
<Avatar
|
|
variant="square"
|
|
src={productIcon}
|
|
className={classes.avatarImage}
|
|
alt="product"
|
|
/>
|
|
</ListItemAvatar>
|
|
<Grid container direction="column">
|
|
<Grid>
|
|
<ListItemText
|
|
primary={heater.name}
|
|
primaryTypographyProps={{
|
|
className: classes.textOverflow,
|
|
variant: "subtitle1"
|
|
}}
|
|
className={classes.listText}
|
|
/>
|
|
</Grid>
|
|
<Grid>
|
|
<ListItemText
|
|
primary={"Device: " + device.id()}
|
|
primaryTypographyProps={{
|
|
className: classes.textOverflow,
|
|
variant: "subtitle2"
|
|
}}
|
|
className={classes.listText}
|
|
/>
|
|
</Grid>
|
|
<Grid>
|
|
<ListItemText
|
|
primary={"Last Checked In: " + moment(device.status.lastActive).fromNow()}
|
|
primaryTypographyProps={{
|
|
className: classes.textOverflow,
|
|
variant: "subtitle2"
|
|
}}
|
|
className={classes.listText}
|
|
/>
|
|
</Grid>
|
|
</Grid>
|
|
</Box>
|
|
{allComponentsRecent ? (
|
|
<CardContent>
|
|
<Grid
|
|
container
|
|
direction="row"
|
|
alignContent="center"
|
|
alignItems="center">
|
|
<Grid size={3}>{componentDisplay(heaterComponent)}</Grid>
|
|
<Grid size={3}>{componentDisplay(heaterTempComponent)}</Grid>
|
|
<Grid size={3}>{componentDisplay(siteTempComponent)}</Grid>
|
|
<Grid size={3}>{fuelMutationDisplay()}</Grid>
|
|
</Grid>
|
|
</CardContent>
|
|
) : (
|
|
<Box
|
|
display="flex"
|
|
justifyContent="center"
|
|
alignItems="center"
|
|
style={{ backgroundColor: "orange", padding: 10, borderRadius: "0px 0px 10px 10px" }}>
|
|
A Component Is Not Reporting
|
|
</Box>
|
|
)}
|
|
</React.Fragment>
|
|
);
|
|
}else {
|
|
return (
|
|
<React.Fragment>
|
|
<CardContent style={{ padding: 20 }}>
|
|
<Grid
|
|
container
|
|
direction="row"
|
|
alignContent="center"
|
|
alignItems="center">
|
|
<Grid size={4}>
|
|
<EventBlocker>
|
|
<Button
|
|
variant="contained"
|
|
onClick={() => {
|
|
linkDevice(heater, heaterData.devices);
|
|
}}>
|
|
<Link />
|
|
</Button>
|
|
</EventBlocker>
|
|
</Grid>
|
|
<Grid size={7}>
|
|
<Typography>{heater.name}</Typography>
|
|
</Grid>
|
|
<Grid size={1}>
|
|
<ObjectHeaterIcon />
|
|
</Grid>
|
|
</Grid>
|
|
</CardContent>
|
|
</React.Fragment>
|
|
)
|
|
}
|
|
};
|
|
|
|
return (
|
|
<Card raised onClick={goToHeater} style={{ margin: 5 }}>
|
|
{cardBody(connectedDevice)}
|
|
</Card>
|
|
);
|
|
}
|