table.jsx 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353
  1. import { useEffect, useState } from 'react';
  2. // material-ui
  3. import {
  4. Avatar,
  5. AvatarGroup,
  6. Box,
  7. Button,
  8. Grid,
  9. List,
  10. ListItemAvatar,
  11. ListItemButton,
  12. ListItemSecondaryAction,
  13. ListItemText,
  14. MenuItem,
  15. Stack,
  16. TextField,
  17. Typography,
  18. Alert,
  19. TableHead,
  20. TableRow,
  21. TableCell,
  22. TableContainer,
  23. Table,
  24. TableBody,
  25. Dialog,
  26. DialogTitle,
  27. DialogContent,
  28. DialogContentText,
  29. DialogActions
  30. } from '@mui/material';
  31. import MainCard from '../../../components/MainCard';
  32. // material-ui
  33. import { useTheme } from '@mui/material/styles';
  34. // third-party
  35. import ReactApexChart from 'react-apexcharts';
  36. import { object } from 'prop-types';
  37. import { FormaterForMetric } from './utils';
  38. import { set } from 'lodash';
  39. import { DownOutlined, UpOutlined } from '@ant-design/icons';
  40. import PlotComponent from './plot';
  41. function formatDate(now, time) {
  42. // use as UTC
  43. // now.setMinutes(now.getMinutes() - now.getTimezoneOffset());
  44. const year = now.getFullYear();
  45. const month = String(now.getMonth() + 1).padStart(2, '0'); // Months are 0-based
  46. const day = String(now.getDate()).padStart(2, '0');
  47. const hours = String(now.getHours()).padStart(2, '0');
  48. const minutes = String(now.getMinutes()).padStart(2, '0');
  49. const seconds = String(now.getSeconds()).padStart(2, '0');
  50. return time ? `${year}-${month}-${day} ${hours}:${minutes}:${seconds}` : `${year}-${month}-${day}`;
  51. }
  52. function descendingComparator(a, b, orderBy) {
  53. let a1 = a[orderBy];
  54. let b1 = b[orderBy];
  55. if(orderBy != 'name') {
  56. a1 = parseFloat(a["__" + orderBy]);
  57. b1 = parseFloat(b["__" + orderBy]);
  58. } else {
  59. a1 = a1.toLowerCase();
  60. b1 = b1.toLowerCase();
  61. }
  62. if (b1 < a1) {
  63. return -1;
  64. }
  65. if (b1 > a1) {
  66. return 1;
  67. }
  68. return 0;
  69. }
  70. function getComparator(order, orderBy) {
  71. return order === 'desc' ? (a, b) => descendingComparator(a, b, orderBy) : (a, b) => -descendingComparator(a, b, orderBy);
  72. }
  73. function stableSort(array, comparator) {
  74. const stabilizedThis = array.map((el, index) => [el, index]);
  75. stabilizedThis.sort((a, b) => {
  76. const order = comparator(a[0], b[0]);
  77. if (order !== 0) {
  78. return order;
  79. }
  80. return a[1] - b[1];
  81. });
  82. return stabilizedThis.map((el) => el[0]);
  83. }
  84. function toUTC(date, time) {
  85. let now = new Date(date);
  86. now.setMinutes(now.getMinutes() + now.getTimezoneOffset());
  87. return formatDate(now, time);
  88. }
  89. function OrderTableHead({ order, orderBy, headCells, setOrderBy, setOrder }) {
  90. return (
  91. <TableHead>
  92. <TableRow>
  93. {headCells.map((headCell) => (
  94. <TableCell
  95. key={headCell.id}
  96. align={headCell.align}
  97. padding={headCell.disablePadding ? 'none' : 'normal'}
  98. sortDirection={orderBy === headCell.id ? order : false}
  99. sx={{
  100. '&:hover': {
  101. cursor: 'pointer',
  102. color: 'primary.main'
  103. }
  104. }}
  105. onClick={() => {
  106. console.log(headCell.id);
  107. if(orderBy === headCell.id) {
  108. setOrder(order === 'asc' ? 'desc' : 'asc');
  109. } else {
  110. setOrder('asc');
  111. setOrderBy(headCell.id);
  112. }
  113. }}
  114. >
  115. {headCell.label} {orderBy === headCell.id ? (
  116. <Box component="span" sx={{ ...(!headCell.disablePadding && { srOnly: true }) }}>
  117. {order === 'desc' ? <DownOutlined /> : <UpOutlined />}
  118. </Box>
  119. ) : null}
  120. </TableCell>
  121. ))}
  122. </TableRow>
  123. </TableHead>
  124. );
  125. }
  126. const TableComponent = ({ title, data, displayMax, render, xAxis, slot, zoom}) => {
  127. const theme = useTheme();
  128. const { primary, secondary } = theme.palette.text;
  129. const line = theme.palette.divider;
  130. const [series, setSeries] = useState([]);
  131. const [order, setOrder] = useState('asc');
  132. const [orderBy, setOrderBy] = useState('name');
  133. const [headCells, setHeadCells] = useState([]);
  134. const [rows, setRows] = useState([]);
  135. const [openModal, setOpenModal] = useState(false);
  136. useEffect(() => {
  137. let heads = {};
  138. let fnrows = [];
  139. data.forEach((item) => {
  140. let k = item.Key.split('.')
  141. let v = item.Values.length ? item.Values[item.Values.length - 1].Value : 0;
  142. let avgIndex = 0;
  143. v = xAxis
  144. .filter((date, index) => {
  145. if (zoom && zoom.xaxis && zoom.xaxis.min && zoom.xaxis.max) {
  146. return index >= zoom.xaxis.min && index <= zoom.xaxis.max;
  147. }
  148. return true;
  149. })
  150. .map((date) => {
  151. if (slot === 'hourly' || slot === 'daily') {
  152. let key = slot === 'hourly' ? "hour_" : "day_";
  153. let k = key + toUTC(date, slot === 'hourly');
  154. if (k in item.ValuesAggl) {
  155. return item.ValuesAggl[k].Value;
  156. } else {
  157. return 0;
  158. }
  159. } else {
  160. let realIndex = item.Values.length - 1 - parseInt(date / item.TimeScale)
  161. return item.Values[realIndex] ? item.Values[realIndex].Value : 0;
  162. }
  163. })
  164. .reduce((a, b) => {
  165. if (typeof b === "undefined" || b === null) {
  166. return a;
  167. }
  168. if (item.AggloType == "min") {
  169. return Math.min(a, b);
  170. } else if (item.AggloType == "max") {
  171. return Math.max(a, b);
  172. } else if (item.AggloType == "avg") {
  173. avgIndex++;
  174. return a + b;
  175. } else if (item.AggloType == "sum") {
  176. return a + b;
  177. } else {
  178. return b;
  179. }
  180. }, 0);
  181. if (item.AggloType == "avg" && avgIndex) {
  182. v = v / avgIndex;
  183. }
  184. let name = k[k.length - 1];
  185. let cat = k[k.length - 2];
  186. heads[cat] = true;
  187. let formatter = FormaterForMetric(item, displayMax);
  188. if(!fnrows.find((row) => row.name === name)) {
  189. fnrows.push({
  190. name,
  191. [cat]: render ? render(item, v, formatter(v)) : formatter(v),
  192. ["__" + cat]: v,
  193. "__key": [item.Key]
  194. });
  195. } else {
  196. fnrows.find((row) => row.name === name)[cat] = render ? render(item, v, formatter(v)) : formatter(v)
  197. fnrows.find((row) => row.name === name)["__" + cat] = v
  198. fnrows.find((row) => row.name === name)["__key"].push(item.Key)
  199. }
  200. });
  201. // remove from fnrows rows where all values are 0
  202. fnrows = fnrows.filter((row) => {
  203. let flag = false;
  204. Object.keys(row).forEach((key) => {
  205. if(key !== 'name' && row["__" + key]) {
  206. flag = true;
  207. }
  208. });
  209. return flag;
  210. }
  211. );
  212. let fnhc = [
  213. {
  214. id: 'name',
  215. align: 'left',
  216. disablePadding: false,
  217. label: 'Name'
  218. },
  219. ];
  220. Object.keys(heads).forEach((head) => {
  221. fnhc.push({
  222. id: head,
  223. align: 'right',
  224. disablePadding: false,
  225. label: head
  226. });
  227. });
  228. setHeadCells(fnhc);
  229. setRows(fnrows);
  230. }, [data, slot, xAxis, zoom]);
  231. return <>
  232. {openModal && <Dialog open={openModal} onClose={() => setOpenModal(false)} maxWidth="md" fullWidth={true}>
  233. <DialogTitle>Detailed History</DialogTitle>
  234. <DialogContent>
  235. <DialogContentText style={{
  236. }}>
  237. <PlotComponent
  238. data={data.filter((item) => {
  239. if (!openModal) {
  240. return false;
  241. }
  242. return openModal.includes(item.Key)
  243. })}
  244. xAxis={xAxis}
  245. slot={slot}
  246. zoom={zoom}
  247. zoomDisabled={true}
  248. />
  249. </DialogContentText>
  250. </DialogContent>
  251. <DialogActions>
  252. <Button onClick={() => {
  253. setOpenModal(false)
  254. }}>Close</Button>
  255. </DialogActions>
  256. </Dialog>}
  257. <Grid item xs={12} md={5} lg={4}>
  258. <Grid container alignItems="center" justifyContent="space-between">
  259. <Grid item>
  260. <Typography variant="h5">{title}</Typography>
  261. </Grid>
  262. </Grid>
  263. <MainCard content={false} sx={{ mt: 1.5 }}>
  264. <Box>
  265. <TableContainer
  266. sx={{
  267. width: '100%',
  268. overflowX: 'auto',
  269. position: 'relative',
  270. display: 'block',
  271. maxWidth: '100%',
  272. '& td, & th': { whiteSpace: 'nowrap' },
  273. maxHeight: '474px'
  274. }}
  275. >
  276. <Table
  277. aria-labelledby="tableTitle"
  278. sx={{
  279. '& .MuiTableCell-root:first-child': {
  280. pl: 2
  281. },
  282. '& .MuiTableCell-root:last-child': {
  283. pr: 3
  284. }
  285. }}
  286. >
  287. <OrderTableHead headCells={headCells} order={order} orderBy={orderBy} setOrderBy={setOrderBy} setOrder={setOrder} />
  288. <TableBody style={{height:'409px', overflow: 'auto'}}>
  289. {stableSort(rows, getComparator(order, orderBy)).map((row, index) => {
  290. const isItemSelected = false // isSelected(row.trackingNo);
  291. const labelId = `enhanced-table-checkbox-${index}`;
  292. return (
  293. <TableRow
  294. hover
  295. role="checkbox"
  296. sx={{ '&:last-child td, &:last-child th': { border: 0 } }}
  297. aria-checked={isItemSelected}
  298. tabIndex={-1}
  299. key={row.trackingNo}
  300. selected={isItemSelected}
  301. onClick={() => {
  302. setOpenModal(row.__key);
  303. }}
  304. >
  305. {headCells.map((headCell) => {
  306. return <TableCell align={
  307. headCell.align
  308. }>{row[headCell.id]}</TableCell>
  309. })}
  310. </TableRow>
  311. );
  312. })}
  313. </TableBody>
  314. </Table>
  315. </TableContainer>
  316. </Box>
  317. </MainCard>
  318. </Grid>
  319. </>
  320. }
  321. export default TableComponent;