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 moment from "moment";
|
||||
import { MeasurementDescriber } from "pbHelpers/MeasurementDescriber";
|
||||
import { useGlobalState } from "providers";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import {
|
||||
Legend,
|
||||
Line,
|
||||
|
|
@ -67,7 +66,6 @@ export default function MultiLineGraph(props: Props) {
|
|||
const [refLeft, setRefLeft] = useState<number | undefined>();
|
||||
const [refRight, setRefRight] = useState<number | undefined>();
|
||||
const [xDomain, setXDomain] = useState<string[] | number[]>(["dataMin", "dataMax"]);
|
||||
const [{ newStructure }] = useGlobalState();
|
||||
|
||||
useEffect(() => {
|
||||
if (newXDomain) {
|
||||
|
|
@ -210,8 +208,7 @@ export default function MultiLineGraph(props: Props) {
|
|||
{...props}
|
||||
valueFormatter={value => {
|
||||
return describer.convertWithUnits(
|
||||
roundTo(parseFloat(String(value)), 2),
|
||||
newStructure
|
||||
roundTo(parseFloat(String(value)), 2)
|
||||
);
|
||||
}}
|
||||
/>
|
||||
|
|
|
|||
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,
|
||||
Grid,
|
||||
TextField,
|
||||
Theme,
|
||||
Typography
|
||||
} from "@material-ui/core";
|
||||
import { createStyles, makeStyles, Theme } from "@material-ui/core/styles";
|
||||
} from "@mui/material";
|
||||
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 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 { useThemeType } from "hooks";
|
||||
import { makeStyles } from "@mui/styles";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
interface Props {
|
||||
startDate: Moment;
|
||||
|
|
@ -21,8 +23,11 @@ interface Props {
|
|||
updateDateRange: (start: Moment, end: Moment, live: boolean) => void;
|
||||
}
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
// Define a type similar to DateRange<Moment>
|
||||
type DateRange<T> = [T | null, T | null];
|
||||
|
||||
const useStyles = makeStyles((_theme: Theme) => {
|
||||
return ({
|
||||
activeButtonLight: {
|
||||
border: "1px solid black"
|
||||
},
|
||||
|
|
@ -30,7 +35,7 @@ const useStyles = makeStyles((theme: Theme) =>
|
|||
border: "1px solid white"
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
export default function TimeBar(props: Props) {
|
||||
const { updateDateRange, startDate, endDate } = props;
|
||||
|
|
@ -83,7 +88,7 @@ export default function TimeBar(props: Props) {
|
|||
onClose={() => setDateRangeDialog(false)}
|
||||
aria-labelledby="date-range-dialog">
|
||||
<DialogContent>
|
||||
<StaticDateRangePicker
|
||||
{/* <StaticDateRangePicker
|
||||
disableFuture
|
||||
value={dateRange}
|
||||
onChange={date => setDateRange(date)}
|
||||
|
|
@ -94,7 +99,8 @@ export default function TimeBar(props: Props) {
|
|||
<TextField {...endProps} />
|
||||
</React.Fragment>
|
||||
)}
|
||||
/>
|
||||
/> */}
|
||||
DATE PICKER GOES HERE
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => setDateRangeDialog(false)} color="primary">
|
||||
|
|
|
|||
|
|
@ -273,8 +273,6 @@ export default function ComponentSettings(props: Props) {
|
|||
|
||||
const hasAvailablePositions = (component: Component) => {
|
||||
// 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) {
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,20 +1,18 @@
|
|||
import {
|
||||
Chip,
|
||||
Tooltip,
|
||||
makeStyles,
|
||||
createStyles,
|
||||
Theme,
|
||||
Avatar,
|
||||
useTheme
|
||||
} from "@material-ui/core";
|
||||
import { purple } from "@material-ui/core/colors";
|
||||
import DefaultIcon from "@material-ui/icons/Memory";
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
import { getDescription, GetComponentIcon } from "pbHelpers/ComponentType";
|
||||
import React from "react";
|
||||
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) =>
|
||||
createStyles({
|
||||
const useStyles = makeStyles((theme: Theme) => {
|
||||
return ({
|
||||
icon: {
|
||||
color: purple["500"]
|
||||
},
|
||||
|
|
@ -23,7 +21,7 @@ const useStyles = makeStyles((theme: Theme) =>
|
|||
width: theme.spacing(2.5)
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
interface Props {
|
||||
component: Component;
|
||||
|
|
@ -36,7 +34,7 @@ export default function ComponentTypeChip(props: Props) {
|
|||
const classes = useStyles();
|
||||
|
||||
const icon = () => {
|
||||
let componentIcon = GetComponentIcon(type, subtype, theme.palette.type);
|
||||
let componentIcon = GetComponentIcon(type, subtype, theme.palette.mode);
|
||||
return componentIcon ? (
|
||||
<Avatar
|
||||
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 moment from "moment";
|
||||
import { getMeasurementSummary, Summary } from "pbHelpers/ComponentType";
|
||||
|
|
@ -16,8 +22,8 @@ interface Props {
|
|||
omitTime?: boolean;
|
||||
}
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
const useStyles = makeStyles((theme: Theme) => {
|
||||
return ({
|
||||
"@keyframes ripple": {
|
||||
from: {
|
||||
transform: "scale(.8)",
|
||||
|
|
@ -39,7 +45,7 @@ const useStyles = makeStyles((theme: Theme) =>
|
|||
backgroundColor: "transparent"
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
//TODO: deprecated as unit measurements use a new measurement summary file
|
||||
export default function MeasurementSummary(props: Props) {
|
||||
|
|
@ -148,7 +154,7 @@ export default function MeasurementSummary(props: Props) {
|
|||
const blacklist: Array<any> = [];
|
||||
const filteredSummaries = summaries.filter((item: any) => !blacklist.includes(item.label));
|
||||
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) => {
|
||||
let sumVal = summary.value;
|
||||
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 GroupPage from "pages/Group";
|
||||
import { ErrorBoundary } from "react-error-boundary";
|
||||
import DeviceComponent from "pages/DeviceComponent";
|
||||
|
||||
const DeviceHistory = lazy(() => import("pages/DeviceHistory"));
|
||||
|
||||
|
|
@ -72,6 +73,10 @@ export default function Router(props: Props) {
|
|||
path="/:deviceID" // "/settings/basic"
|
||||
element={<DevicePage />}
|
||||
/>
|
||||
<Route
|
||||
path="/:deviceID/components/:componentID" // "/settings/basic"
|
||||
element={<DeviceComponent />}
|
||||
/>
|
||||
<Route
|
||||
path="/:deviceID/history" // "/settings/basic"
|
||||
element={<DeviceHistory />}
|
||||
|
|
|
|||
|
|
@ -73,7 +73,6 @@ export default function DevicePage() {
|
|||
resp.data.components.forEach(comp => {
|
||||
newComps.push(Component.create(comp))
|
||||
})
|
||||
console.log(resp.data.components)
|
||||
let available = FindAvailablePositions(
|
||||
newComps,
|
||||
device.settings.product
|
||||
|
|
@ -139,10 +138,6 @@ export default function DevicePage() {
|
|||
.finally(() => setLoading(false));
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
console.log(components)
|
||||
}, [components])
|
||||
|
||||
useEffect(() => {
|
||||
loadDevice()
|
||||
}, [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