component page graphs now render
This commit is contained in:
parent
e5bc90e00e
commit
c62bfbf916
10 changed files with 908 additions and 38 deletions
|
|
@ -1,9 +1,8 @@
|
||||||
import { Box, Button, useTheme } from "@material-ui/core";
|
import { Box, Button, useTheme } from "@mui/material";
|
||||||
import MaterialChartTooltip from "charts/MaterialChartTooltip";
|
import MaterialChartTooltip from "charts/MaterialChartTooltip";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import { MeasurementDescriber } from "pbHelpers/MeasurementDescriber";
|
import { MeasurementDescriber } from "pbHelpers/MeasurementDescriber";
|
||||||
import { useGlobalState } from "providers";
|
import { useEffect, useState } from "react";
|
||||||
import React, { useEffect, useState } from "react";
|
|
||||||
import {
|
import {
|
||||||
Legend,
|
Legend,
|
||||||
Line,
|
Line,
|
||||||
|
|
@ -67,7 +66,6 @@ export default function MultiLineGraph(props: Props) {
|
||||||
const [refLeft, setRefLeft] = useState<number | undefined>();
|
const [refLeft, setRefLeft] = useState<number | undefined>();
|
||||||
const [refRight, setRefRight] = useState<number | undefined>();
|
const [refRight, setRefRight] = useState<number | undefined>();
|
||||||
const [xDomain, setXDomain] = useState<string[] | number[]>(["dataMin", "dataMax"]);
|
const [xDomain, setXDomain] = useState<string[] | number[]>(["dataMin", "dataMax"]);
|
||||||
const [{ newStructure }] = useGlobalState();
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (newXDomain) {
|
if (newXDomain) {
|
||||||
|
|
@ -210,8 +208,7 @@ export default function MultiLineGraph(props: Props) {
|
||||||
{...props}
|
{...props}
|
||||||
valueFormatter={value => {
|
valueFormatter={value => {
|
||||||
return describer.convertWithUnits(
|
return describer.convertWithUnits(
|
||||||
roundTo(parseFloat(String(value)), 2),
|
roundTo(parseFloat(String(value)), 2)
|
||||||
newStructure
|
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
94
src/common/NextMeasurementChip.tsx
Normal file
94
src/common/NextMeasurementChip.tsx
Normal file
|
|
@ -0,0 +1,94 @@
|
||||||
|
import { Chip, Theme, Tooltip } from "@mui/material";
|
||||||
|
import { Schedule } from "@mui/icons-material";
|
||||||
|
import moment from "moment";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { makeStyles } from "@mui/styles";
|
||||||
|
import { yellow } from "@mui/material/colors";
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme: Theme) => {
|
||||||
|
return ({
|
||||||
|
notReporting: {
|
||||||
|
color: theme.palette.text.secondary
|
||||||
|
},
|
||||||
|
reporting: {
|
||||||
|
color: yellow["900"]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
lastMeasurement: string;
|
||||||
|
periodMs: number;
|
||||||
|
size?: "small" | "medium";
|
||||||
|
}
|
||||||
|
|
||||||
|
export function NextMeasurementChip(props: Props) {
|
||||||
|
const { lastMeasurement, periodMs, size } = props;
|
||||||
|
const classes = useStyles();
|
||||||
|
const [countdown, setCountdown] = useState(0);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let timer = setInterval(() => {
|
||||||
|
if (countdown > 0) {
|
||||||
|
setCountdown(countdown - 1);
|
||||||
|
} else {
|
||||||
|
const now = moment();
|
||||||
|
let last = moment(lastMeasurement !== "" ? lastMeasurement : 0);
|
||||||
|
let diff = now.diff(last, "seconds");
|
||||||
|
const periodS = periodMs / 1000.0;
|
||||||
|
let rem = periodS - diff;
|
||||||
|
if (rem < 0) rem = 0;
|
||||||
|
setCountdown(rem);
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
|
return () => clearInterval(timer);
|
||||||
|
}, [countdown, lastMeasurement, periodMs]);
|
||||||
|
|
||||||
|
const getTooltip = () => {
|
||||||
|
let tooltip = "Not reporting measurements";
|
||||||
|
if (periodMs > 0) {
|
||||||
|
if (periodMs < 60000) {
|
||||||
|
tooltip =
|
||||||
|
"Reports a measurement every " + moment.duration(periodMs, "ms").asSeconds() + " seconds";
|
||||||
|
} else {
|
||||||
|
tooltip =
|
||||||
|
"Reports a measurement every " +
|
||||||
|
moment
|
||||||
|
.duration(periodMs, "ms")
|
||||||
|
.humanize()
|
||||||
|
.replace("an ", "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tooltip;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getLabel = () => {
|
||||||
|
let label = "Not reporting";
|
||||||
|
if (periodMs > 0) {
|
||||||
|
if (countdown < 5) {
|
||||||
|
label = "Measuring soon";
|
||||||
|
} else if (countdown <= 60) {
|
||||||
|
label = "Measuring in a minute";
|
||||||
|
} else {
|
||||||
|
label =
|
||||||
|
"Measuring " +
|
||||||
|
moment()
|
||||||
|
.utc()
|
||||||
|
.to(moment.utc().add(countdown, "s"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return label;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tooltip title={getTooltip()}>
|
||||||
|
<Chip
|
||||||
|
variant="outlined"
|
||||||
|
label={getLabel()}
|
||||||
|
icon={<Schedule className={periodMs > 0 ? classes.reporting : classes.notReporting} />}
|
||||||
|
size={size}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -4,16 +4,18 @@ import {
|
||||||
DialogContent,
|
DialogContent,
|
||||||
Grid,
|
Grid,
|
||||||
TextField,
|
TextField,
|
||||||
|
Theme,
|
||||||
Typography
|
Typography
|
||||||
} from "@material-ui/core";
|
} from "@mui/material";
|
||||||
import { createStyles, makeStyles, Theme } from "@material-ui/core/styles";
|
|
||||||
import moment, { Moment } from "moment";
|
import moment, { Moment } from "moment";
|
||||||
import { DateRange as DateIcon } from "@material-ui/icons";
|
import { DateRange as DateIcon } from "@mui/icons-material";
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import ResponsiveDialog from "common/ResponsiveDialog";
|
import ResponsiveDialog from "common/ResponsiveDialog";
|
||||||
import { DateRange, DateRangeDelimiter, StaticDateRangePicker } from "@material-ui/pickers";
|
// import { DateRange, DateRangeDelimiter, StaticDateRangePicker } from "@material-ui/pickers";
|
||||||
import { DateRangePreset, GetDefaultDateRange, SetDefaultPreset } from "./DateRange";
|
import { DateRangePreset, GetDefaultDateRange, SetDefaultPreset } from "./DateRange";
|
||||||
import { useThemeType } from "hooks";
|
import { useThemeType } from "hooks";
|
||||||
|
import { makeStyles } from "@mui/styles";
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
startDate: Moment;
|
startDate: Moment;
|
||||||
|
|
@ -21,8 +23,11 @@ interface Props {
|
||||||
updateDateRange: (start: Moment, end: Moment, live: boolean) => void;
|
updateDateRange: (start: Moment, end: Moment, live: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const useStyles = makeStyles((theme: Theme) =>
|
// Define a type similar to DateRange<Moment>
|
||||||
createStyles({
|
type DateRange<T> = [T | null, T | null];
|
||||||
|
|
||||||
|
const useStyles = makeStyles((_theme: Theme) => {
|
||||||
|
return ({
|
||||||
activeButtonLight: {
|
activeButtonLight: {
|
||||||
border: "1px solid black"
|
border: "1px solid black"
|
||||||
},
|
},
|
||||||
|
|
@ -30,7 +35,7 @@ const useStyles = makeStyles((theme: Theme) =>
|
||||||
border: "1px solid white"
|
border: "1px solid white"
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
);
|
});
|
||||||
|
|
||||||
export default function TimeBar(props: Props) {
|
export default function TimeBar(props: Props) {
|
||||||
const { updateDateRange, startDate, endDate } = props;
|
const { updateDateRange, startDate, endDate } = props;
|
||||||
|
|
@ -83,7 +88,7 @@ export default function TimeBar(props: Props) {
|
||||||
onClose={() => setDateRangeDialog(false)}
|
onClose={() => setDateRangeDialog(false)}
|
||||||
aria-labelledby="date-range-dialog">
|
aria-labelledby="date-range-dialog">
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<StaticDateRangePicker
|
{/* <StaticDateRangePicker
|
||||||
disableFuture
|
disableFuture
|
||||||
value={dateRange}
|
value={dateRange}
|
||||||
onChange={date => setDateRange(date)}
|
onChange={date => setDateRange(date)}
|
||||||
|
|
@ -94,7 +99,8 @@ export default function TimeBar(props: Props) {
|
||||||
<TextField {...endProps} />
|
<TextField {...endProps} />
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
)}
|
)}
|
||||||
/>
|
/> */}
|
||||||
|
DATE PICKER GOES HERE
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={() => setDateRangeDialog(false)} color="primary">
|
<Button onClick={() => setDateRangeDialog(false)} color="primary">
|
||||||
|
|
|
||||||
|
|
@ -273,8 +273,6 @@ export default function ComponentSettings(props: Props) {
|
||||||
|
|
||||||
const hasAvailablePositions = (component: Component) => {
|
const hasAvailablePositions = (component: Component) => {
|
||||||
// return true // TODO: actually fix this
|
// return true // TODO: actually fix this
|
||||||
console.log(component.settings.addressType)
|
|
||||||
console.log(quack.AddressType.ADDRESS_TYPE_INCREMENTAL)
|
|
||||||
if (component.settings.addressType === quack.AddressType.ADDRESS_TYPE_INCREMENTAL) {
|
if (component.settings.addressType === quack.AddressType.ADDRESS_TYPE_INCREMENTAL) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,18 @@
|
||||||
import {
|
import {
|
||||||
Chip,
|
Chip,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
makeStyles,
|
|
||||||
createStyles,
|
|
||||||
Theme,
|
Theme,
|
||||||
Avatar,
|
Avatar,
|
||||||
useTheme
|
useTheme,
|
||||||
} from "@material-ui/core";
|
} from "@mui/material";
|
||||||
import { purple } from "@material-ui/core/colors";
|
|
||||||
import DefaultIcon from "@material-ui/icons/Memory";
|
|
||||||
import { getDescription, GetComponentIcon } from "pbHelpers/ComponentType";
|
import { getDescription, GetComponentIcon } from "pbHelpers/ComponentType";
|
||||||
import React from "react";
|
|
||||||
import { Component } from "models";
|
import { Component } from "models";
|
||||||
|
import { purple } from "@mui/material/colors";
|
||||||
|
import { makeStyles } from "@mui/styles";
|
||||||
|
import { Memory as DefaultIcon } from "@mui/icons-material"
|
||||||
|
|
||||||
const useStyles = makeStyles((theme: Theme) =>
|
const useStyles = makeStyles((theme: Theme) => {
|
||||||
createStyles({
|
return ({
|
||||||
icon: {
|
icon: {
|
||||||
color: purple["500"]
|
color: purple["500"]
|
||||||
},
|
},
|
||||||
|
|
@ -23,7 +21,7 @@ const useStyles = makeStyles((theme: Theme) =>
|
||||||
width: theme.spacing(2.5)
|
width: theme.spacing(2.5)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
);
|
});
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
component: Component;
|
component: Component;
|
||||||
|
|
@ -36,7 +34,7 @@ export default function ComponentTypeChip(props: Props) {
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
|
|
||||||
const icon = () => {
|
const icon = () => {
|
||||||
let componentIcon = GetComponentIcon(type, subtype, theme.palette.type);
|
let componentIcon = GetComponentIcon(type, subtype, theme.palette.mode);
|
||||||
return componentIcon ? (
|
return componentIcon ? (
|
||||||
<Avatar
|
<Avatar
|
||||||
variant="square"
|
variant="square"
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,10 @@
|
||||||
import { Box, Grid, Typography, makeStyles, createStyles, Theme } from "@material-ui/core";
|
import {
|
||||||
|
Box,
|
||||||
|
Grid,
|
||||||
|
Typography,
|
||||||
|
Theme
|
||||||
|
} from "@mui/material";
|
||||||
|
import { makeStyles } from "@mui/styles";
|
||||||
import { Component } from "models";
|
import { Component } from "models";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import { getMeasurementSummary, Summary } from "pbHelpers/ComponentType";
|
import { getMeasurementSummary, Summary } from "pbHelpers/ComponentType";
|
||||||
|
|
@ -16,8 +22,8 @@ interface Props {
|
||||||
omitTime?: boolean;
|
omitTime?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const useStyles = makeStyles((theme: Theme) =>
|
const useStyles = makeStyles((theme: Theme) => {
|
||||||
createStyles({
|
return ({
|
||||||
"@keyframes ripple": {
|
"@keyframes ripple": {
|
||||||
from: {
|
from: {
|
||||||
transform: "scale(.8)",
|
transform: "scale(.8)",
|
||||||
|
|
@ -39,7 +45,7 @@ const useStyles = makeStyles((theme: Theme) =>
|
||||||
backgroundColor: "transparent"
|
backgroundColor: "transparent"
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
);
|
});
|
||||||
|
|
||||||
//TODO: deprecated as unit measurements use a new measurement summary file
|
//TODO: deprecated as unit measurements use a new measurement summary file
|
||||||
export default function MeasurementSummary(props: Props) {
|
export default function MeasurementSummary(props: Props) {
|
||||||
|
|
@ -148,7 +154,7 @@ export default function MeasurementSummary(props: Props) {
|
||||||
const blacklist: Array<any> = [];
|
const blacklist: Array<any> = [];
|
||||||
const filteredSummaries = summaries.filter((item: any) => !blacklist.includes(item.label));
|
const filteredSummaries = summaries.filter((item: any) => !blacklist.includes(item.label));
|
||||||
return (
|
return (
|
||||||
<Grid container direction="row" justify={centered ? "center" : "flex-start"} spacing={1}>
|
<Grid container direction="row" justifyContent={centered ? "center" : "flex-start"} spacing={1}>
|
||||||
{filteredSummaries.map((summary: Summary, i: number) => {
|
{filteredSummaries.map((summary: Summary, i: number) => {
|
||||||
let sumVal = summary.value;
|
let sumVal = summary.value;
|
||||||
if (component.settings.type === quack.ComponentType.COMPONENT_TYPE_EDGE_TRIGGERED) {
|
if (component.settings.type === quack.ComponentType.COMPONENT_TYPE_EDGE_TRIGGERED) {
|
||||||
|
|
|
||||||
87
src/interactions/InteractionChip.tsx
Normal file
87
src/interactions/InteractionChip.tsx
Normal file
|
|
@ -0,0 +1,87 @@
|
||||||
|
import { Chip, Tooltip } from "@mui/material";
|
||||||
|
import { Link as LinkIcon } from "@mui/icons-material";
|
||||||
|
import React from "react";
|
||||||
|
import { pond } from "protobuf-ts/pond";
|
||||||
|
import { quack } from "protobuf-ts/quack";
|
||||||
|
import { Component, Interaction } from "models";
|
||||||
|
|
||||||
|
// const styles = (theme: Theme) => ({
|
||||||
|
// accepted: {
|
||||||
|
// color: amber["300"]
|
||||||
|
// },
|
||||||
|
// pending: {
|
||||||
|
// color: theme.palette.text.secondary
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
interaction: Interaction;
|
||||||
|
component: Component;
|
||||||
|
components: Component[];
|
||||||
|
onClick: Function;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface State {}
|
||||||
|
|
||||||
|
class InteractionChip extends React.Component<Props, State> {
|
||||||
|
label = () => {
|
||||||
|
const { interaction, component, components } = this.props;
|
||||||
|
let ourID = JSON.stringify(
|
||||||
|
quack.ComponentID.create({
|
||||||
|
type: component.settings.type,
|
||||||
|
addressType: component.settings.addressType,
|
||||||
|
address: component.settings.address
|
||||||
|
})
|
||||||
|
);
|
||||||
|
if (component.settings.address === 0) {
|
||||||
|
ourID = JSON.stringify(
|
||||||
|
quack.ComponentID.create({
|
||||||
|
type: component.settings.type,
|
||||||
|
addressType: component.settings.addressType
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let sourceID = JSON.stringify((interaction.settings as pond.InteractionSettings).source);
|
||||||
|
let sinkID = JSON.stringify((interaction.settings as pond.InteractionSettings).sink);
|
||||||
|
for (let i = 0; i < components.length; i++) {
|
||||||
|
let c = components[i];
|
||||||
|
let otherID = JSON.stringify(
|
||||||
|
quack.ComponentID.create({
|
||||||
|
type: c.settings.type,
|
||||||
|
addressType: c.settings.addressType,
|
||||||
|
address: c.settings.address
|
||||||
|
})
|
||||||
|
);
|
||||||
|
if (c.settings.address === 0) {
|
||||||
|
otherID = JSON.stringify(
|
||||||
|
quack.ComponentID.create({
|
||||||
|
type: c.settings.type,
|
||||||
|
addressType: c.settings.addressType
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (ourID === otherID) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (sourceID === otherID || sinkID === otherID) {
|
||||||
|
return c.name();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "Cloud";
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { /*classes,*/ interaction, onClick } = this.props;
|
||||||
|
const label = this.label();
|
||||||
|
const pending = !interaction.status.synced;
|
||||||
|
// const iconClass = pending ? classes.pending : classes.accepted;
|
||||||
|
const title = pending ? "Pending interaction with " + label : "Interacting with " + label;
|
||||||
|
return (
|
||||||
|
<Tooltip title={title}>
|
||||||
|
<Chip onClick={() => onClick()} label={label} icon={<LinkIcon /*className={iconClass}*/ />} />
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default InteractionChip;
|
||||||
|
|
@ -13,6 +13,7 @@ import DevicePage from "pages/Device";
|
||||||
import GroupsPage from "pages/Groups";
|
import GroupsPage from "pages/Groups";
|
||||||
import GroupPage from "pages/Group";
|
import GroupPage from "pages/Group";
|
||||||
import { ErrorBoundary } from "react-error-boundary";
|
import { ErrorBoundary } from "react-error-boundary";
|
||||||
|
import DeviceComponent from "pages/DeviceComponent";
|
||||||
|
|
||||||
const DeviceHistory = lazy(() => import("pages/DeviceHistory"));
|
const DeviceHistory = lazy(() => import("pages/DeviceHistory"));
|
||||||
|
|
||||||
|
|
@ -72,6 +73,10 @@ export default function Router(props: Props) {
|
||||||
path="/:deviceID" // "/settings/basic"
|
path="/:deviceID" // "/settings/basic"
|
||||||
element={<DevicePage />}
|
element={<DevicePage />}
|
||||||
/>
|
/>
|
||||||
|
<Route
|
||||||
|
path="/:deviceID/components/:componentID" // "/settings/basic"
|
||||||
|
element={<DeviceComponent />}
|
||||||
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/:deviceID/history" // "/settings/basic"
|
path="/:deviceID/history" // "/settings/basic"
|
||||||
element={<DeviceHistory />}
|
element={<DeviceHistory />}
|
||||||
|
|
|
||||||
|
|
@ -73,7 +73,6 @@ export default function DevicePage() {
|
||||||
resp.data.components.forEach(comp => {
|
resp.data.components.forEach(comp => {
|
||||||
newComps.push(Component.create(comp))
|
newComps.push(Component.create(comp))
|
||||||
})
|
})
|
||||||
console.log(resp.data.components)
|
|
||||||
let available = FindAvailablePositions(
|
let available = FindAvailablePositions(
|
||||||
newComps,
|
newComps,
|
||||||
device.settings.product
|
device.settings.product
|
||||||
|
|
@ -139,10 +138,6 @@ export default function DevicePage() {
|
||||||
.finally(() => setLoading(false));
|
.finally(() => setLoading(false));
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
console.log(components)
|
|
||||||
}, [components])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadDevice()
|
loadDevice()
|
||||||
}, [deviceID])
|
}, [deviceID])
|
||||||
|
|
|
||||||
684
src/pages/DeviceComponent.tsx
Normal file
684
src/pages/DeviceComponent.tsx
Normal file
|
|
@ -0,0 +1,684 @@
|
||||||
|
import { Box, Grid2 as Grid, Skeleton, Theme, Tooltip } from "@mui/material";
|
||||||
|
import { makeStyles } from "@mui/styles";
|
||||||
|
import { NextMeasurementChip } from "common/NextMeasurementChip";
|
||||||
|
import ResponsiveTable from "common/ResponsiveTable";
|
||||||
|
// import { getTableIcons } from "common/ResponsiveTable";
|
||||||
|
import SmartBreadcrumb from "common/SmartBreadcrumb";
|
||||||
|
import StatusChip from "common/StatusChip";
|
||||||
|
import { GetDefaultDateRange } from "common/time/DateRange";
|
||||||
|
import ComponentActions from "component/ComponentActions";
|
||||||
|
import ComponentChart from "component/ComponentChart";
|
||||||
|
import ComponentTypeChip from "component/ComponentTypeChip";
|
||||||
|
// import MeasurementSummary from "component/MeasurementSummary";
|
||||||
|
// import UnitMeasurementSummary from "component/UnitMeasurementSummary";
|
||||||
|
import {
|
||||||
|
useComponentAPI,
|
||||||
|
useDeviceAPI,
|
||||||
|
useGroupAPI,
|
||||||
|
useInteractionsAPI,
|
||||||
|
usePrevious,
|
||||||
|
useSnackbar,
|
||||||
|
useUserAPI
|
||||||
|
} from "hooks";
|
||||||
|
import InteractionChip from "interactions/InteractionChip";
|
||||||
|
import InteractionSettings from "interactions/InteractionSettings";
|
||||||
|
// import MaterialTable from "material-table";
|
||||||
|
import { Component, Device, deviceScope, Group, Interaction } from "models";
|
||||||
|
import { convertedUnitMeasurement, MeasurementsFor, UnitMeasurement } from "models/UnitMeasurement";
|
||||||
|
import moment, { Moment } from "moment";
|
||||||
|
// import { MatchParams } from "navigation/Routes";
|
||||||
|
import PageContainer from "pages/PageContainer";
|
||||||
|
import { getContextKeys, getContextTypes } from "pbHelpers/Context";
|
||||||
|
import {
|
||||||
|
DeviceAvailabilityMap,
|
||||||
|
FindAvailablePositions,
|
||||||
|
OffsetAvailabilityMap
|
||||||
|
} from "pbHelpers/DeviceAvailability";
|
||||||
|
import { getDefaultInteraction } from "pbHelpers/Interaction";
|
||||||
|
import { pond } from "protobuf-ts/pond";
|
||||||
|
import { useGlobalState, useTeamAPI } from "providers";
|
||||||
|
import { useCallback, useEffect, useRef, useState } from "react";
|
||||||
|
import { useParams } from "react-router-dom";
|
||||||
|
// import { useRouteMatch } from "react-router";
|
||||||
|
import { or } from "utils/types";
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme: Theme) => {
|
||||||
|
return ({
|
||||||
|
toolbar: {
|
||||||
|
marginTop: theme.spacing(1),
|
||||||
|
paddingRight: theme.spacing(1),
|
||||||
|
paddingLeft: theme.spacing(1),
|
||||||
|
[theme.breakpoints.up("sm")]: {
|
||||||
|
paddingRight: theme.spacing(1),
|
||||||
|
paddingLeft: theme.spacing(2)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
toolbarTitle: {
|
||||||
|
whiteSpace: "nowrap",
|
||||||
|
overflow: "hidden",
|
||||||
|
textOverflow: "ellipsis"
|
||||||
|
},
|
||||||
|
contentContainer: {
|
||||||
|
padding: theme.spacing(1),
|
||||||
|
[theme.breakpoints.up("sm")]: {
|
||||||
|
paddingRight: theme.spacing(2)
|
||||||
|
},
|
||||||
|
[theme.breakpoints.up("lg")]: {
|
||||||
|
paddingRight: theme.spacing(3)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
overviewContainer: {
|
||||||
|
position: "relative",
|
||||||
|
maxWidth: "100%",
|
||||||
|
overflowX: "auto",
|
||||||
|
marginBottom: theme.spacing(2)
|
||||||
|
},
|
||||||
|
verticalSpacing: {
|
||||||
|
marginBottom: theme.spacing(2)
|
||||||
|
},
|
||||||
|
tablePagination: {
|
||||||
|
overflow: "auto"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
export default function DeviceComponent() {
|
||||||
|
const classes = useStyles();
|
||||||
|
// const match = useRouteMatch<MatchParams>();
|
||||||
|
const deviceAPI = useDeviceAPI();
|
||||||
|
const componentAPI = useComponentAPI();
|
||||||
|
const { error } = useSnackbar();
|
||||||
|
const groupAPI = useGroupAPI();
|
||||||
|
const interactionsAPI = useInteractionsAPI();
|
||||||
|
const userAPI = useUserAPI();
|
||||||
|
const teamAPI = useTeamAPI();
|
||||||
|
// const deviceID = parseInt(match.params.deviceID, 10);
|
||||||
|
// const componentID = match.params.componentID;
|
||||||
|
// const groupID = or(parseInt(match.params.groupID, 10), undefined);
|
||||||
|
const deviceID = +(useParams<{ deviceID: string }>()?.deviceID ?? "0");
|
||||||
|
const componentID = useParams<{ componentID: string }>()?.componentID ?? "";
|
||||||
|
const groupID = +(useParams<{ groupID: string }>()?.groupID ?? "");
|
||||||
|
const [isFirstRender, setIsFirstRender] = useState<boolean>(true);
|
||||||
|
const [loadingInitial, setLoadingInitial] = useState<boolean>(false);
|
||||||
|
const defaultDateRange = GetDefaultDateRange();
|
||||||
|
const [startDate, setStartDate] = useState<Moment>(defaultDateRange.start);
|
||||||
|
const prevStartDate = usePrevious(startDate);
|
||||||
|
const [endDate, setEndDate] = useState<Moment>(defaultDateRange.end);
|
||||||
|
const prevEndDate = usePrevious(endDate);
|
||||||
|
const live = useRef(defaultDateRange.live);
|
||||||
|
const [device, setDevice] = useState<Device>(Device.create());
|
||||||
|
const [group, setGroup] = useState<Group | undefined>(Group.create());
|
||||||
|
const [component, setComponent] = useState<Component>(Component.create());
|
||||||
|
const [components, setComponents] = useState<Component[]>([]);
|
||||||
|
const [sampling, setSampling] = useState(false);
|
||||||
|
const liveSamples = useRef<pond.Measurement[]>([]);
|
||||||
|
const [samples, setSamples] = useState<pond.Measurement[]>([]);
|
||||||
|
const [permissions, setPermissions] = useState<pond.Permission[]>([]);
|
||||||
|
const [interactions, setInteractions] = useState<Interaction[]>([]);
|
||||||
|
const [selectedInteraction, setSelectedInteraction] = useState<Interaction>(
|
||||||
|
getDefaultInteraction()
|
||||||
|
);
|
||||||
|
const [pageSize, setPageSize] = useState<number>(5);
|
||||||
|
const [page, setPage] = useState(1)
|
||||||
|
const [order, setOrder] = useState("desc")
|
||||||
|
const [rows, setRows] = useState<convertedUnitMeasurement[]>([])
|
||||||
|
const [total, setTotal] = useState<number>(0)
|
||||||
|
const prevRowsPerPage = usePrevious(pageSize);
|
||||||
|
const [availablePositions, setAvailablePositions] = useState<DeviceAvailabilityMap>(new Map());
|
||||||
|
const [availableOffsets, setAvailableOffsets] = useState<OffsetAvailabilityMap>(new Map());
|
||||||
|
const tableRef: any = useRef();
|
||||||
|
let isInteractionSettingsOpen =
|
||||||
|
JSON.stringify(or(selectedInteraction, getDefaultInteraction())) !==
|
||||||
|
JSON.stringify(getDefaultInteraction());
|
||||||
|
const [{ user, as }] = useGlobalState();
|
||||||
|
const [unitMeasurements, setUnitMeasurements] = useState<UnitMeasurement[]>([]);
|
||||||
|
const [rm, setRM] = useState<pond.Measurement>(pond.Measurement.create());
|
||||||
|
const [recentUnitMeasurement, setRecentUnitMeasurement] = useState<UnitMeasurement[]>([]);
|
||||||
|
const [recentGoodUnitMeasurement, setRecentGoodUnitMeasurement] = useState<UnitMeasurement[]>([]);
|
||||||
|
const [{ showErrors }] = useGlobalState();
|
||||||
|
const sampleLimit = 200;
|
||||||
|
const [deviceComponentPrefs, setDeviceComponentPrefs] = useState<
|
||||||
|
pond.DeviceComponentPreferences
|
||||||
|
>();
|
||||||
|
|
||||||
|
console.log("device component")
|
||||||
|
|
||||||
|
/*const getContextKeys = useCallback(() => {
|
||||||
|
if (groupID === undefined || groupID < 1) return undefined;
|
||||||
|
return [groupID.toString(), deviceID.toString()];
|
||||||
|
}, [groupID, deviceID]);
|
||||||
|
|
||||||
|
const getContextTypes = useCallback(() => {
|
||||||
|
if (groupID === undefined || groupID < 1) return undefined;
|
||||||
|
return ["group", "device"];
|
||||||
|
}, [groupID]);*/
|
||||||
|
|
||||||
|
const setDefaultState = () => {
|
||||||
|
setDevice(Device.create());
|
||||||
|
setGroup(undefined);
|
||||||
|
setInteractions([]);
|
||||||
|
setSelectedInteraction(getDefaultInteraction());
|
||||||
|
setComponents([]);
|
||||||
|
setComponent(Component.create());
|
||||||
|
setPermissions([]);
|
||||||
|
setAvailablePositions(new Map());
|
||||||
|
setAvailableOffsets(new Map());
|
||||||
|
};
|
||||||
|
|
||||||
|
const loadUnitMeasurements = useCallback(() => {
|
||||||
|
setUnitMeasurements([]);
|
||||||
|
setSampling(true);
|
||||||
|
componentAPI
|
||||||
|
.sampleUnitMeasurements(
|
||||||
|
deviceID,
|
||||||
|
componentID,
|
||||||
|
startDate,
|
||||||
|
endDate,
|
||||||
|
sampleLimit,
|
||||||
|
false,
|
||||||
|
getContextKeys(),
|
||||||
|
getContextTypes(),
|
||||||
|
showErrors
|
||||||
|
)
|
||||||
|
.then(resp => {
|
||||||
|
if (resp.data.measurements) {
|
||||||
|
let newUnitMeasurements = resp.data.measurements.map((um: any) =>
|
||||||
|
UnitMeasurement.any(um, user)
|
||||||
|
);
|
||||||
|
setUnitMeasurements([...newUnitMeasurements]);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
setSampling(false);
|
||||||
|
});
|
||||||
|
}, [componentAPI, deviceID, componentID, user, startDate, endDate, showErrors]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (live.current && liveSamples) {
|
||||||
|
setSamples(liveSamples.current);
|
||||||
|
} else {
|
||||||
|
loadUnitMeasurements();
|
||||||
|
}
|
||||||
|
}, [loadUnitMeasurements]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (component.key() !== "") {
|
||||||
|
interactionsAPI
|
||||||
|
.listInteractionsByComponent(deviceID, component.location())
|
||||||
|
.then((rInteractions: Interaction[]) => {
|
||||||
|
setInteractions(rInteractions);
|
||||||
|
})
|
||||||
|
.catch((err: any) => {
|
||||||
|
console.log(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [component, deviceID, interactionsAPI]);
|
||||||
|
|
||||||
|
const loadState = useCallback(() => {
|
||||||
|
if (user.settings.id === "") return;
|
||||||
|
setLoadingInitial(true);
|
||||||
|
deviceAPI
|
||||||
|
.get(deviceID, false, getContextKeys().slice(0, -1), getContextTypes().slice(0, -1))
|
||||||
|
.then((response: any) => {
|
||||||
|
const rDevice = Device.any(response.data);
|
||||||
|
setDevice(rDevice);
|
||||||
|
let componentsPromise = componentAPI.list(
|
||||||
|
deviceID,
|
||||||
|
false,
|
||||||
|
getContextKeys(),
|
||||||
|
getContextTypes(),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
let userPromise = userAPI.getUser(user.settings.id, deviceScope(deviceID.toString()));
|
||||||
|
let prefPromise = deviceAPI.listDeviceComponentPreferences(deviceID);
|
||||||
|
Promise.all([componentsPromise, userPromise, prefPromise])
|
||||||
|
.then((responses: any) => {
|
||||||
|
const rawComponents: Array<any> = or(responses[0].data.components, []).sort(
|
||||||
|
(a: any, b: any) => {
|
||||||
|
if (or(a.settings.name, "").toLowerCase() < or(b.settings.name, "").toLowerCase()) {
|
||||||
|
return -1;
|
||||||
|
} else if (
|
||||||
|
or(a.settings.name, "").toLowerCase() > or(b.settings.name, "").toLowerCase()
|
||||||
|
) {
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
let rComponents: Component[] = [];
|
||||||
|
rawComponents.forEach((rawComponent: any) => {
|
||||||
|
rComponents.push(Component.any(rawComponent));
|
||||||
|
});
|
||||||
|
setComponents(rComponents);
|
||||||
|
let rComponent = rComponents.find(c => componentID === c.key());
|
||||||
|
setComponent(rComponent ? rComponent : Component.create());
|
||||||
|
if (rComponent?.status.lastMeasurement) {
|
||||||
|
setRM(rComponent.status.lastMeasurement);
|
||||||
|
}
|
||||||
|
if (rComponent?.lastMeasurement) {
|
||||||
|
let measurements = rComponent.lastMeasurement.map(um =>
|
||||||
|
UnitMeasurement.any(um, user)
|
||||||
|
);
|
||||||
|
setRecentUnitMeasurement(measurements);
|
||||||
|
}
|
||||||
|
if (rComponent?.status?.lastGoodMeasurement) {
|
||||||
|
let measurements = rComponent.status.lastGoodMeasurement.map(um =>
|
||||||
|
UnitMeasurement.any(um, user)
|
||||||
|
);
|
||||||
|
setRecentGoodUnitMeasurement(measurements);
|
||||||
|
}
|
||||||
|
|
||||||
|
setPermissions(responses[1].permissions);
|
||||||
|
let available = FindAvailablePositions(rComponents, rDevice.settings.product);
|
||||||
|
setAvailablePositions(available.GetAvailability());
|
||||||
|
setAvailableOffsets(available.offsetAvailability);
|
||||||
|
|
||||||
|
let allPrefs = responses[2].data.preferences;
|
||||||
|
Object.keys(allPrefs).forEach(compoundKey => {
|
||||||
|
let componentKey = compoundKey.split(":")[1]
|
||||||
|
? compoundKey.split(":")[1]
|
||||||
|
: compoundKey;
|
||||||
|
if (componentID === componentKey) {
|
||||||
|
setDeviceComponentPrefs(
|
||||||
|
pond.DeviceComponentPreferences.fromObject(allPrefs[compoundKey])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.log(err);
|
||||||
|
setDefaultState();
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
setLoadingInitial(false);
|
||||||
|
if (groupID > 0) {
|
||||||
|
groupAPI
|
||||||
|
.getGroup(groupID)
|
||||||
|
.then((response: any) => {
|
||||||
|
setGroup(Group.any(response.data));
|
||||||
|
})
|
||||||
|
.catch((error: any) => {
|
||||||
|
setGroup(undefined);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (as) {
|
||||||
|
teamAPI.getTeam(as, deviceScope(deviceID.toString())).then(resp => {
|
||||||
|
//if (resp.permissions.length > permissions.length) {
|
||||||
|
setPermissions(resp.permissions);
|
||||||
|
//}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((err: any) => {
|
||||||
|
setDefaultState();
|
||||||
|
console.log(err);
|
||||||
|
});
|
||||||
|
}, [
|
||||||
|
componentAPI,
|
||||||
|
componentID,
|
||||||
|
deviceAPI,
|
||||||
|
deviceID,
|
||||||
|
groupAPI,
|
||||||
|
groupID,
|
||||||
|
userAPI,
|
||||||
|
as,
|
||||||
|
teamAPI,
|
||||||
|
user
|
||||||
|
]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isFirstRender) {
|
||||||
|
loadState();
|
||||||
|
setIsFirstRender(false);
|
||||||
|
} else if (prevStartDate !== startDate || prevEndDate !== endDate) {
|
||||||
|
if (tableRef && tableRef.current) {
|
||||||
|
tableRef.current.onChangePage(undefined, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
component,
|
||||||
|
endDate,
|
||||||
|
isFirstRender,
|
||||||
|
loadState,
|
||||||
|
prevEndDate,
|
||||||
|
prevRowsPerPage,
|
||||||
|
prevStartDate,
|
||||||
|
pageSize,
|
||||||
|
startDate,
|
||||||
|
tableRef
|
||||||
|
]);
|
||||||
|
|
||||||
|
const interactionChips = (
|
||||||
|
component: Component,
|
||||||
|
interactions: Interaction[],
|
||||||
|
components: Component[]
|
||||||
|
) => {
|
||||||
|
if (!component || !interactions || !components || interactions.length <= 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
let chips: any = [];
|
||||||
|
interactions.forEach((interaction: Interaction) => {
|
||||||
|
let rawInteraction = Interaction.clone(interaction);
|
||||||
|
let key =
|
||||||
|
JSON.stringify(rawInteraction.settings.source) +
|
||||||
|
JSON.stringify(rawInteraction.settings.sink) +
|
||||||
|
rawInteraction.settings.instance;
|
||||||
|
chips.push(
|
||||||
|
<Grid key={key}>
|
||||||
|
<InteractionChip
|
||||||
|
interaction={interaction}
|
||||||
|
component={component}
|
||||||
|
components={components}
|
||||||
|
onClick={() => selectInteraction(rawInteraction)}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
return chips;
|
||||||
|
};
|
||||||
|
|
||||||
|
const status = () => {
|
||||||
|
if (loadingInitial)
|
||||||
|
return (
|
||||||
|
<Box width={1} margin={1}>
|
||||||
|
<Skeleton variant="text" width="50%" />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Grid
|
||||||
|
container
|
||||||
|
spacing={1}
|
||||||
|
flex-direction="row"
|
||||||
|
justifyContent="flex-start"
|
||||||
|
alignItems="center"
|
||||||
|
wrap="nowrap"
|
||||||
|
className={classes.overviewContainer}>
|
||||||
|
{!component.status.synced && (
|
||||||
|
<Grid >
|
||||||
|
<StatusChip key="status" status="pending" />
|
||||||
|
</Grid>
|
||||||
|
)}
|
||||||
|
<Grid >
|
||||||
|
{/* TODO: use the unit measurement timestamp */}
|
||||||
|
<NextMeasurementChip
|
||||||
|
lastMeasurement={or(rm, { timestamp: "" }).timestamp}
|
||||||
|
periodMs={component.settings.reportPeriodMs}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid >
|
||||||
|
<ComponentTypeChip component={component} />
|
||||||
|
</Grid>
|
||||||
|
{interactionChips(component, interactions, components)}
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const selectInteraction = (newInteraction: Interaction) => {
|
||||||
|
setSelectedInteraction(newInteraction);
|
||||||
|
};
|
||||||
|
|
||||||
|
// This triggers table rerender when the show errors variable is flipped
|
||||||
|
useEffect(() => {
|
||||||
|
if (tableRef && tableRef.current) {
|
||||||
|
tableRef.current.onChangePage(undefined, 0);
|
||||||
|
}
|
||||||
|
}, [showErrors, tableRef]);
|
||||||
|
|
||||||
|
const load = () => {
|
||||||
|
componentAPI.listUnitMeasurements(
|
||||||
|
deviceID,
|
||||||
|
componentID,
|
||||||
|
startDate,
|
||||||
|
endDate,
|
||||||
|
pageSize,
|
||||||
|
page * pageSize,
|
||||||
|
order,
|
||||||
|
undefined,
|
||||||
|
getContextKeys(),
|
||||||
|
getContextTypes(),
|
||||||
|
showErrors
|
||||||
|
)
|
||||||
|
.then(response => {
|
||||||
|
let total: number = response.data.total ? response.data.total : 0;
|
||||||
|
let rows = UnitMeasurement.convertMeasurements(
|
||||||
|
response.data.measurements.map(um => UnitMeasurement.any(um, user))
|
||||||
|
);
|
||||||
|
// let maxPage = total / pageSize;
|
||||||
|
// if (page > maxPage) {
|
||||||
|
// tableRef.current.isOutsidePageNumbers();
|
||||||
|
// }
|
||||||
|
setRows(rows)
|
||||||
|
setTotal(total)
|
||||||
|
// resolve({
|
||||||
|
// data: rows,
|
||||||
|
// page: page,
|
||||||
|
// totalCount: total
|
||||||
|
// });
|
||||||
|
})
|
||||||
|
.catch((err: any) => {
|
||||||
|
|
||||||
|
// resolve({
|
||||||
|
// data: [],
|
||||||
|
// page: 0,
|
||||||
|
// totalCount: 0
|
||||||
|
// });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// const unitMeasurementsTable = () => {
|
||||||
|
// interface Row {
|
||||||
|
// timestamp: string;
|
||||||
|
// measurementsFor: MeasurementsFor[];
|
||||||
|
// }
|
||||||
|
// return (
|
||||||
|
// <Grid item xs={12} className={classes.verticalSpacing}>
|
||||||
|
// <MaterialTable
|
||||||
|
// key="unitMeasurements"
|
||||||
|
// tableRef={tableRef}
|
||||||
|
// // icons={getTableIcons()}
|
||||||
|
// columns={[
|
||||||
|
// {
|
||||||
|
// title: "Timestamp",
|
||||||
|
// type: "datetime",
|
||||||
|
// defaultSort: "desc",
|
||||||
|
// render: (row: Row) => {
|
||||||
|
// return (
|
||||||
|
// <Tooltip title={moment(row.timestamp).calendar()}>
|
||||||
|
// <span>{moment(row.timestamp).fromNow()}</span>
|
||||||
|
// </Tooltip>
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// title: "Measurement",
|
||||||
|
// sorting: false,
|
||||||
|
// field: "measurement",
|
||||||
|
// render: (row: Row) => (
|
||||||
|
// <UnitMeasurementSummary
|
||||||
|
// component={component}
|
||||||
|
// reading={row}
|
||||||
|
// tableCell={true}
|
||||||
|
// excludedNodes={deviceComponentPrefs?.excludedNodes}
|
||||||
|
// />
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
// ]}
|
||||||
|
// options={{
|
||||||
|
// paging: true,
|
||||||
|
// pageSize: pageSize,
|
||||||
|
// pageSizeOptions: [5, 10, 20],
|
||||||
|
// padding: "default",
|
||||||
|
// showTitle: true,
|
||||||
|
// toolbar: true,
|
||||||
|
// sorting: !live.current,
|
||||||
|
// search: false,
|
||||||
|
// headerStyle: { position: "inherit", borderBottom: "1px solid rgba(81, 81, 81, 1)" }
|
||||||
|
// }}
|
||||||
|
// localization={{
|
||||||
|
// body: { emptyDataSourceMessage: "No measurements found" }
|
||||||
|
// }}
|
||||||
|
// data={query =>
|
||||||
|
// new Promise(resolve => {
|
||||||
|
// let pageSize = query.pageSize;
|
||||||
|
// let page = query.page;
|
||||||
|
// let orderBy =
|
||||||
|
// query.orderBy && query.orderBy.field ? query.orderBy.field.toString() : "timestamp";
|
||||||
|
// componentAPI
|
||||||
|
// .listUnitMeasurements(
|
||||||
|
// deviceID,
|
||||||
|
// componentID,
|
||||||
|
// startDate,
|
||||||
|
// endDate,
|
||||||
|
// pageSize,
|
||||||
|
// page * pageSize,
|
||||||
|
// orderBy,
|
||||||
|
// undefined,
|
||||||
|
// getContextKeys(),
|
||||||
|
// getContextTypes(),
|
||||||
|
// showErrors
|
||||||
|
// )
|
||||||
|
// .then(response => {
|
||||||
|
// let total: number = response.data.total ? response.data.total : 0;
|
||||||
|
// let rows = UnitMeasurement.convertMeasurements(
|
||||||
|
// response.data.measurements.map(um => UnitMeasurement.any(um, user))
|
||||||
|
// );
|
||||||
|
// let maxPage = total / pageSize;
|
||||||
|
// if (page > maxPage) {
|
||||||
|
// tableRef.current.isOutsidePageNumbers();
|
||||||
|
// }
|
||||||
|
// resolve({
|
||||||
|
// data: rows,
|
||||||
|
// page: page,
|
||||||
|
// totalCount: total
|
||||||
|
// });
|
||||||
|
// })
|
||||||
|
// .catch((err: any) => {
|
||||||
|
// resolve({
|
||||||
|
// data: [],
|
||||||
|
// page: 0,
|
||||||
|
// totalCount: 0
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
// />
|
||||||
|
// </Grid>
|
||||||
|
// );
|
||||||
|
// };
|
||||||
|
|
||||||
|
const handleRowsPerPageChange = (event: any) => {
|
||||||
|
const newRowsPerPage = parseInt(event.target.value, 10); // Get selected value
|
||||||
|
setPageSize(newRowsPerPage);
|
||||||
|
setPage(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
const measurementsTable = () => {
|
||||||
|
return (
|
||||||
|
<Grid size={{ xs: 12 }} className={classes.verticalSpacing}>
|
||||||
|
<ResponsiveTable
|
||||||
|
rows={rows}
|
||||||
|
total={total}
|
||||||
|
page={page}
|
||||||
|
pageSize={pageSize}
|
||||||
|
setPage={setPage}
|
||||||
|
handleRowsPerPageChange={handleRowsPerPageChange}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PageContainer>
|
||||||
|
<Grid
|
||||||
|
container
|
||||||
|
direction="row"
|
||||||
|
justifyContent="flex-start"
|
||||||
|
alignContent="center"
|
||||||
|
className={classes.toolbar}>
|
||||||
|
<Grid
|
||||||
|
size={{ xs: 8, sm: 9 }}
|
||||||
|
container
|
||||||
|
justifyContent="flex-start"
|
||||||
|
alignContent="center"
|
||||||
|
className={classes.toolbarTitle}>
|
||||||
|
<SmartBreadcrumb
|
||||||
|
deviceName={device.name()}
|
||||||
|
componentName={component.name()}
|
||||||
|
groupName={group ? group.name() : undefined}
|
||||||
|
loading={loadingInitial}
|
||||||
|
reload={loadState}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Grid size={{ xs: 4, sm: 3 }} container justifyContent="flex-end" alignContent="center">
|
||||||
|
<ComponentActions
|
||||||
|
device={device}
|
||||||
|
components={components}
|
||||||
|
component={component}
|
||||||
|
permissions={permissions}
|
||||||
|
availablePositions={availablePositions}
|
||||||
|
availableOffsets={availableOffsets}
|
||||||
|
refreshCallback={loadState}
|
||||||
|
initialStartDate={startDate}
|
||||||
|
initialEndDate={endDate}
|
||||||
|
deviceComponentPreferences={deviceComponentPrefs}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Grid
|
||||||
|
container
|
||||||
|
direction="column"
|
||||||
|
justifyContent="flex-start"
|
||||||
|
alignItems="center"
|
||||||
|
className={classes.contentContainer}>
|
||||||
|
<Grid container size={{ xs: 12 }} justifyContent="flex-end">
|
||||||
|
{status()}
|
||||||
|
</Grid>
|
||||||
|
<Grid container size={{ xs: 12 }}>
|
||||||
|
{
|
||||||
|
<Grid size={{ xs: 12 }} className={classes.verticalSpacing}>
|
||||||
|
<ComponentChart
|
||||||
|
deviceKey={deviceID.toString()}
|
||||||
|
componentKey={componentID}
|
||||||
|
component={component}
|
||||||
|
interactions={interactions}
|
||||||
|
sampling={sampling}
|
||||||
|
unitMeasurements={unitMeasurements}
|
||||||
|
sampleLimit={sampleLimit}
|
||||||
|
deviceComponentPreferences={deviceComponentPrefs}
|
||||||
|
updateDate={(newStartDate, newEndDate, newLive) => {
|
||||||
|
setStartDate(newStartDate);
|
||||||
|
setEndDate(newEndDate);
|
||||||
|
live.current = !!newLive;
|
||||||
|
}}
|
||||||
|
allowLive={true}
|
||||||
|
recentUnitMeasurement={
|
||||||
|
showErrors ? recentUnitMeasurement : recentGoodUnitMeasurement
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
}
|
||||||
|
</Grid>
|
||||||
|
<Grid container size={{ xs: 12 }}>
|
||||||
|
{/* {newStructure ? unitMeasurementsTable() : measurementsTable()} */}
|
||||||
|
{measurementsTable()}
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
<InteractionSettings
|
||||||
|
device={device}
|
||||||
|
components={components.map((component: Component) => Component.clone(component))}
|
||||||
|
initialComponent={component}
|
||||||
|
initialInteraction={selectedInteraction}
|
||||||
|
mode="update"
|
||||||
|
isDialogOpen={isInteractionSettingsOpen}
|
||||||
|
closeDialogCallback={() => selectInteraction(getDefaultInteraction())}
|
||||||
|
refreshCallback={loadState}
|
||||||
|
canEdit={permissions.includes(pond.Permission.PERMISSION_WRITE)}
|
||||||
|
/>
|
||||||
|
</PageContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue