799 lines
26 KiB
TypeScript
799 lines
26 KiB
TypeScript
import { Box, Grid2 as Grid, Skeleton, Theme, Tooltip, Typography } from "@mui/material";
|
|
import { makeStyles } from "@mui/styles";
|
|
import { NextMeasurementChip } from "common/NextMeasurementChip";
|
|
import ResponsiveTable, { Column } from "common/ResponsiveTable";
|
|
// import { getTableIcons } from "common/ResponsiveTable";
|
|
import SmartBreadcrumb from "common/SmartBreadcrumb";
|
|
import PendingChangesChip from "common/PendingChangesChip";
|
|
import { GetDefaultDateRange } from "common/time/DateRange";
|
|
import ComponentActions from "component/ComponentActions";
|
|
import ComponentChart from "component/ComponentChart";
|
|
import ComponentTypeChip from "component/ComponentTypeChip";
|
|
import UnitMeasurementSummary from "component/UnitMeasurementSummary";
|
|
// import MeasurementSummary from "component/MeasurementSummary";
|
|
// import UnitMeasurementSummary from "component/UnitMeasurementSummary";
|
|
import {
|
|
useComponentAPI,
|
|
useDeviceAPI,
|
|
useGroupAPI,
|
|
useHTTP,
|
|
useInteractionsAPI,
|
|
usePendingChanges,
|
|
usePrevious,
|
|
useSnackbar,
|
|
useUserAPI
|
|
} from "hooks";
|
|
import { useWebSocket } from "hooks/useWebSocket";
|
|
import InteractionChip from "interactions/InteractionChip";
|
|
import InteractionSettings from "interactions/InteractionSettings";
|
|
import { cloneDeep } from "lodash";
|
|
// 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 } from "providers";
|
|
import { useTeamAPI } from "providers/pond/teamAPI";
|
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
import { useParams } from "react-router-dom";
|
|
// import { useRouteMatch } from "react-router";
|
|
import { isStaleStatusUpdate } from "utils/syncStatus";
|
|
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(0)
|
|
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, showErrors }] = 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 sampleLimit = 200;
|
|
const [deviceComponentPrefs, setDeviceComponentPrefs] = useState<
|
|
pond.DeviceComponentPreferences
|
|
>();
|
|
const { token } = useHTTP();
|
|
const componentPending = usePendingChanges(
|
|
component.status.synced,
|
|
!loadingInitial && component.key() !== ""
|
|
);
|
|
|
|
const liveContextKeys = useMemo(() => getContextKeys(), []);
|
|
const liveContextTypes = useMemo(() => getContextTypes(), []);
|
|
/** Matches the REST calls: omit ?as= when first context type is "team". */
|
|
const liveAs =
|
|
as && !(liveContextTypes.length > 0 && liveContextTypes[0] === "team")
|
|
? as
|
|
: undefined;
|
|
|
|
// --- Real-time component updates ---
|
|
// Streams component changes for this device, including status changes
|
|
// when the device accepts pending component updates.
|
|
useWebSocket<{ key: string; component: Component } | null>({
|
|
path: `/v1/live/devices/${deviceID}/components`,
|
|
parse: (e) => {
|
|
try {
|
|
const raw = JSON.parse(e.data);
|
|
const comp = Component.any(raw);
|
|
if (!comp?.settings) {
|
|
console.warn("[ws] received component without settings:", raw);
|
|
return null;
|
|
}
|
|
return { key: comp.key(), component: comp };
|
|
} catch (err) {
|
|
console.warn("[ws] failed to parse component:", err);
|
|
return null;
|
|
}
|
|
},
|
|
onMessage: (data) => {
|
|
if (!data || data.key !== componentID) return;
|
|
setComponent((prev) => {
|
|
if (prev.key() !== data.key) return prev;
|
|
if (isStaleStatusUpdate(prev.status, data.component.status)) {
|
|
return prev;
|
|
}
|
|
return data.component;
|
|
});
|
|
},
|
|
keys: liveContextKeys,
|
|
types: liveContextTypes,
|
|
as: liveAs,
|
|
token,
|
|
enabled: !!deviceID && componentID !== "",
|
|
});
|
|
|
|
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,
|
|
showErrors,
|
|
as,
|
|
)
|
|
.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, as]);
|
|
|
|
useEffect(() => {
|
|
if (live.current && liveSamples) {
|
|
setSamples(liveSamples.current);
|
|
} else {
|
|
loadUnitMeasurements();
|
|
}
|
|
}, [loadUnitMeasurements]);
|
|
|
|
useEffect(() => {
|
|
if (component.key() !== "") {
|
|
interactionsAPI
|
|
.listInteractionsByComponent(deviceID, component.location(), undefined, as)
|
|
.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), as)
|
|
.then((response: any) => {
|
|
const rDevice = Device.any(response.data);
|
|
setDevice(rDevice);
|
|
let componentsPromise = componentAPI.list(
|
|
deviceID,
|
|
false,
|
|
getContextKeys(),
|
|
getContextTypes(),
|
|
true,
|
|
as
|
|
);
|
|
let userPromise = userAPI.getUser(user.settings.id, deviceScope(deviceID.toString()));
|
|
let prefPromise = deviceAPI.listDeviceComponentPreferences(deviceID, getContextKeys().slice(0, -1), getContextTypes().slice(0, -1), as);
|
|
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?.status.measurement) {
|
|
let measurements = rComponent.status.measurement.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}>
|
|
{(componentPending.pending || componentPending.accepted) && (
|
|
<Grid >
|
|
<PendingChangesChip
|
|
pending={componentPending.pending}
|
|
accepted={componentPending.accepted}
|
|
/>
|
|
</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,
|
|
showErrors,
|
|
as
|
|
)
|
|
.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
|
|
// });
|
|
});
|
|
}
|
|
|
|
useEffect(() => {
|
|
load()
|
|
}, [page, pageSize, as])
|
|
|
|
const columns = (): Column<convertedUnitMeasurement>[] => {
|
|
return [
|
|
{
|
|
title: "Timestamp",
|
|
cellStyle: {width: "50%"},
|
|
render: row => {
|
|
return (
|
|
<Tooltip title={moment(row.timestamp).calendar()}>
|
|
<Box>
|
|
<Typography>{moment(row.timestamp).fromNow()}</Typography>
|
|
</Box>
|
|
</Tooltip>
|
|
)
|
|
}
|
|
},
|
|
{
|
|
title: "Measurement",
|
|
cellStyle: {width: "50%"},
|
|
render: row => {
|
|
return (
|
|
<UnitMeasurementSummary
|
|
component={component}
|
|
reading={row}
|
|
tableCell={true}
|
|
/>
|
|
)
|
|
}
|
|
}
|
|
]
|
|
}
|
|
|
|
// 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={component.settings.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}
|
|
columns={columns()}
|
|
total={total}
|
|
page={page}
|
|
pageSize={pageSize}
|
|
setPage={setPage}
|
|
handleRowsPerPageChange={handleRowsPerPageChange}
|
|
loadMore={() => {
|
|
let currentRows = cloneDeep(rows)
|
|
let newOffset = rows.length
|
|
componentAPI.listUnitMeasurements(
|
|
deviceID,
|
|
componentID,
|
|
startDate,
|
|
endDate,
|
|
pageSize,
|
|
newOffset,
|
|
order,
|
|
undefined,
|
|
getContextKeys(),
|
|
getContextTypes(),
|
|
showErrors,
|
|
showErrors,
|
|
as)
|
|
.then(resp => {
|
|
let newRows = UnitMeasurement.convertMeasurements(
|
|
resp.data.measurements.map(um => UnitMeasurement.any(um, user))
|
|
)
|
|
setRows(currentRows.concat(newRows))
|
|
}).catch(err => {
|
|
|
|
})
|
|
|
|
}}
|
|
/>
|
|
</Grid>
|
|
);
|
|
};
|
|
|
|
return (
|
|
<PageContainer>
|
|
<Grid
|
|
container
|
|
direction="row"
|
|
justifyContent="flex-start"
|
|
alignContent="center"
|
|
rowSpacing={1}
|
|
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 size={{ xs: 12 }}>
|
|
{status()}
|
|
</Grid>
|
|
</Grid>
|
|
|
|
<Grid
|
|
container
|
|
direction="column"
|
|
justifyContent="flex-start"
|
|
alignItems="center"
|
|
className={classes.contentContainer}>
|
|
|
|
<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>
|
|
);
|
|
}
|