frontend/src/objectHeater/ObjectHeaterCard.tsx

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>
);
}