frontend/src/component/MeasurementSummary.tsx
2025-02-24 11:46:16 -06:00

284 lines
9.6 KiB
TypeScript

import {
Box,
Grid,
Typography,
Theme
} from "@mui/material";
import { makeStyles } from "@mui/styles";
import { Component } from "models";
import moment from "moment";
import { getMeasurementSummary, Summary } from "pbHelpers/ComponentType";
import { pond } from "protobuf-ts/pond";
import { quack } from "protobuf-ts/quack";
import React, { useEffect, useState } from "react";
import { or } from "utils/types";
interface Props {
component: Component;
reading?: pond.Measurement | null;
tableCell?: boolean;
centered?: boolean;
dense?: boolean;
omitTime?: boolean;
}
const useStyles = makeStyles((theme: Theme) => {
return ({
"@keyframes ripple": {
from: {
transform: "scale(.8)",
opacity: 1
},
to: {
transform: "scale(1.4)",
opacity: 0
}
},
on: {
boxShadow: `0 0 0 2px ${theme.palette.background.paper}`,
animationName: "$ripple",
animationDuration: "1.2s",
animationTimingFunction: "ease-in-out",
animationIterationCount: "infinite"
},
off: {
backgroundColor: "transparent"
}
})
});
//TODO: deprecated as unit measurements use a new measurement summary file
export default function MeasurementSummary(props: Props) {
const { component, reading, tableCell, centered, dense } = props;
const [summaries, setSummaries] = useState<Summary[]>([]);
const [timestamp, setTimestamp] = useState("");
const classes = useStyles();
const grainCableFilters = () => {
const { component } = props;
return {
grainType: component.settings.grainType,
grainFilledTo: component.settings.grainFilledTo
};
};
useEffect(() => {
async function getSummary() {
if (
reading &&
reading.measurement &&
reading.measurement.id &&
reading.measurement.id.type !== null &&
reading.measurement.id.type !== undefined
) {
let id = quack.ComponentID.fromObject(reading.measurement.id);
let filters = { isTableCellMode: tableCell };
if (id.type === quack.ComponentType.COMPONENT_TYPE_GRAIN_CABLE) {
filters = { ...filters, ...grainCableFilters() };
}
await getMeasurementSummary(
id.type,
or(component.settings.subtype, 0),
quack.Measurement.create(reading.measurement ? reading.measurement : undefined),
filters
).then(summaries => {
setSummaries(summaries);
});
setTimestamp(reading.timestamp);
}
}
getSummary();
}, [component, reading]); // eslint-disable-line react-hooks/exhaustive-deps
const noSummary = () => {
return (
<Typography variant="body2" color="textPrimary">
No Data
</Typography>
);
};
const getSummaryComponent = (summaries: Summary[]): any => {
if (summaries.length < 1) {
return noSummary();
}
return (
<Grid container>
<Grid item xs={12}>
<Typography variant="body2" color="textPrimary">
{summaries.map((summary: Summary, index: number) => {
let sumVal = summary.value;
if (component.settings.type === quack.ComponentType.COMPONENT_TYPE_EDGE_TRIGGERED) {
if (
component.settings.subtype ===
quack.EdgeTriggeredSubtype.EDGE_TRIGGERED_SUBTYPE_WIND_SPEED &&
!isNaN(Number(sumVal))
) {
//make sure sumVal is a number
sumVal = Number(sumVal);
//get the period of time that all the measurements are in and convert it to seconds
let measurementInterval = Number(component.settings.measurementPeriodMs) / 1000;
//formula to determine the km/h
sumVal = (sumVal / measurementInterval) * 2.4;
//round to 2 decimal places and add the units
sumVal = Math.round(sumVal * 100) / 100 + "km/h";
}
}
return (
<span key={index}>
{summary.label + ": "} <span style={{ color: summary.colour }}>{sumVal}</span>{" "}
<br />
</span>
);
})}
</Typography>
</Grid>
{!props.omitTime && (
<Grid item xs={12}>
<Typography color="textSecondary" variant="caption">
{moment(timestamp).fromNow()}
</Typography>
</Grid>
)}
</Grid>
);
};
const getTableCellSummaryComponent = (summaries: Summary[]): any => {
if (summaries.length < 1) {
return <span>Unknown</span>;
}
const blacklist: Array<any> = [];
const filteredSummaries = summaries.filter((item: any) => !blacklist.includes(item.label));
return (
<Grid container direction="row" justifyContent={centered ? "center" : "flex-start"} spacing={1}>
{filteredSummaries.map((summary: Summary, i: number) => {
let sumVal = summary.value;
if (component.settings.type === quack.ComponentType.COMPONENT_TYPE_EDGE_TRIGGERED) {
if (
component.settings.subtype ===
quack.EdgeTriggeredSubtype.EDGE_TRIGGERED_SUBTYPE_WIND_SPEED &&
!isNaN(Number(sumVal))
) {
//make sure sumVal is a number
sumVal = Number(sumVal);
//get the period of time that all the measurements are in and convert it to seconds
let measurementInterval = Number(component.settings.measurementPeriodMs) / 1000;
//formula to determine the km/h
sumVal = (sumVal / measurementInterval) * 2.4;
//round to 2 decimal places and add the units
sumVal = Math.round(sumVal * 100) / 100 + "km/h";
}
}
return (
<Grid item key={summary.label}>
{summary.label + ": "}
<span style={{ color: summary.colour }}>{sumVal}</span>
{i + 1 !== filteredSummaries.length && ","}
</Grid>
);
})}
</Grid>
);
};
const getDenseSummary = (summaries: Summary[]): any => {
if (summaries.length < 1) {
return null;
}
const blacklist: Array<any> = ["Temperature (Air)", "Moisture (Air)"];
const filteredSummaries = summaries.filter(
(item: any) =>
!blacklist.includes(item.label) && (item.label !== "Grain Type" || item.value !== "None")
);
let overlays: any = [];
return (
<Box width={"100%"} component="div">
{filteredSummaries.map((summary: Summary, i: number) => {
let sumVal = summary.value;
if (component.settings.type === quack.ComponentType.COMPONENT_TYPE_EDGE_TRIGGERED) {
if (
component.settings.subtype ===
quack.EdgeTriggeredSubtype.EDGE_TRIGGERED_SUBTYPE_WIND_SPEED &&
!isNaN(Number(sumVal))
) {
//make sure sumVal is a number
sumVal = Number(sumVal);
//get the period of time that all the measurements are in and convert it to seconds
let measurementInterval = Number(component.settings.measurementPeriodMs) / 1000;
//formula to determine the km/h
sumVal = (sumVal / measurementInterval) * 2.4;
//round to 2 decimal places and add the units
sumVal = Math.round(sumVal * 100) / 100 + "km/h";
}
}
if (component.settings.hasOverlays) {
let overlay: pond.ComponentOverlays = pond.ComponentOverlays.create();
let found = false;
component.settings.overlays.forEach(ov => {
if (ov.measurementType === summary.type) {
//check if the sum value is within the overlay min-max
let numVal = parseFloat(sumVal);
if (numVal >= ov.min && numVal <= ov.max) {
overlay = ov;
found = true;
}
}
});
if (found) {
overlays.push(
<React.Fragment key={"overlay" + i}>
<Grid item>
<Box
width="20px"
height="20px"
borderRadius="50%"
className={classes.on}
style={{ background: overlay.colour }}
/>
</Grid>
<Grid item>
<Typography
variant="caption"
component="span"
style={{ marginLeft: 5, marginRight: 5 }}>
{overlay.message}
</Typography>
</Grid>
</React.Fragment>
);
}
}
return (
<Typography key={summary.label} variant="caption" component="span">
<span style={{ color: summary.colour }}>{sumVal}</span>
{i + 1 !== filteredSummaries.length && ", "}
</Typography>
);
})}
{!props.omitTime && (
<Box component="span">
{" - "}
<Typography color="textSecondary" variant="caption">
{moment(timestamp).fromNow()}
</Typography>
</Box>
)}
<Grid container direction="row">
{overlays}
</Grid>
</Box>
);
};
if (summaries.length <= 0) return noSummary();
if (dense) return getDenseSummary(summaries);
if (tableCell) return getTableCellSummaryComponent(summaries);
return getSummaryComponent(summaries);
}