diff --git a/indexV2.html b/indexV2.html new file mode 100644 index 0000000..31b4ca1 --- /dev/null +++ b/indexV2.html @@ -0,0 +1,14 @@ + + + + + + + Adaptive Dashboard + + + +
+ + + diff --git a/nginx.conf b/nginx.conf index 8e8fc75..adb8457 100644 --- a/nginx.conf +++ b/nginx.conf @@ -61,7 +61,12 @@ http { expires $cache_expires; root /usr/share/nginx/html; - try_files $uri /index.html; + try_files $uri /indexV2.html; + + location = /index.html { + return 301 /indexV2.html; + } + } # Healthcheck diff --git a/src/cableEstimator/cableEstimator.tsx b/src/cableEstimator/cableEstimator.tsx index 17f909c..86a27b7 100644 --- a/src/cableEstimator/cableEstimator.tsx +++ b/src/cableEstimator/cableEstimator.tsx @@ -140,7 +140,7 @@ export default function CableEstimator() { }, { title: "Peak Height" + (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET ? "(ft)" : "(m)"), - sortKey: "PeakHeight", + sortKey: "Peak", render: (row: jsonBin) => {distanceConversion(row.Peak).toFixed(2)} }, { diff --git a/src/common/ResponsiveTable.tsx b/src/common/ResponsiveTable.tsx index ee1e9dd..f904e0a 100644 --- a/src/common/ResponsiveTable.tsx +++ b/src/common/ResponsiveTable.tsx @@ -1,7 +1,7 @@ import { Box, Button, Card, Checkbox, CircularProgress, Collapse, darken, Divider, Grid2, IconButton, InputAdornment, ListItemButton, ListItemIcon, ListItemText, Menu, MenuItem, Paper, SxProps, Table, TableBody, TableCell, TableContainer, TableHead, TablePagination, TableRow, TextField, Theme, Typography } from "@mui/material"; import { makeStyles } from "@mui/styles"; import { ArrowDownward, ArrowUpward, ChevronRight, Close, Search, ViewColumn } from "@mui/icons-material" -import { useEffect, useState } from "react"; +import { useEffect, useRef, useState } from "react"; import React from "react"; import { useMobile } from "hooks"; import classNames from "classnames"; @@ -99,7 +99,8 @@ interface Props { order?: string; setOrder?: React.Dispatch>; endTitleElement?: string | JSX.Element | (() => string | JSX.Element); - loadMore?: () => void + loadMore?: () => void; + resizeable?: boolean; } export default function ResponsiveTable(props: Props) { @@ -133,14 +134,15 @@ export default function ResponsiveTable(props: Props) { setOrderBy, order, setOrder, - loadMore + loadMore, + // resizeable, } = props const classes = useStyles(); const columns = typeof(props.columns) === "function" ? props.columns() : props.columns const subtitle = typeof(props.subtitle) === "function" ? props.subtitle() : props.subtitle const title = typeof(props.title) === "function" ? props.title() : props.title - // const order = props.order ? props.order : "asc" const endTitleElement = typeof(props.endTitleElement) === "function" ? props.endTitleElement() : props.endTitleElement + const resizeable = props.resizeable === undefined ? false : props.resizeable const isMobile = useMobile() @@ -148,6 +150,19 @@ export default function ResponsiveTable(props: Props) { const [openGutters, setOpenGutters] = useState([]) const [handler, setHandler] = useState(undefined); const [columnAnchor, setColumnAnchor] = useState(null) + + const rowKey = window.location.pathname.split('/').pop() + "-table-row-widths"; + const [rowWidths, setRowWidths] = useState(() => { + const saved = localStorage.getItem(rowKey); + return saved ? JSON.parse(saved) : []; + }) + useEffect(() => { + localStorage.setItem(rowKey, JSON.stringify(rowWidths)); + }, [rowWidths]) + + const dragIndexRef = useRef(undefined); + const dragStartRef = useRef(undefined); + const rowWidthsRef = useRef([]); const filterKey = window.location.pathname.split('/').pop() + "-table-filter-list"; @@ -274,9 +289,7 @@ export default function ResponsiveTable(props: Props) { } const renderMobileRow = (row: T, index: number) => { - if (renderMobile) return ( - renderMobile(row, index) - ) + if (renderMobile) return renderMobile(row, index) if (columns) { return ( @@ -299,8 +312,7 @@ export default function ResponsiveTable(props: Props) { } return ( Object.keys(row).map((key, index) => { - let value = row[key as keyof typeof row] - + const value = row[key as keyof typeof row] return ( @@ -346,7 +358,6 @@ export default function ResponsiveTable(props: Props) { padding: gutterPadding }} onClick={(event) => handleCollapse(index, event)} - // style={{pre}} > (props: Props) { const columnClick = (column: Column) => { const sortKey = column.sortKey ? column.sortKey : column.title.toLowerCase() - // console.log(column.sortKey) if (setOrderBy) { if (orderBy === sortKey && setOrder && order) { if (order === "desc") { @@ -409,13 +419,11 @@ export default function ResponsiveTable(props: Props) { } else { setOrderBy("") } - // setOrder(order === "asc" ? "desc" : "asc") } else { if (setOrder) setOrder("desc") setOrderBy(sortKey) } } - // if (setOrder && order) setOrder(order === "asc" ? "desc" : "asc") } const showOrderIcon = (column: Column) => { @@ -423,14 +431,60 @@ export default function ResponsiveTable(props: Props) { const sortKey = column.sortKey ? column.sortKey : column.title.toLowerCase() if (sortKey === orderBy) { if (order === "asc") { - return + return } else { - return + return } } return null; } + const resizeClick = (event: React.MouseEvent, index: number) => { + + let newWidths: number[] = [] + event.currentTarget.parentElement?.parentElement?.parentElement?.childNodes.forEach(node => { + if (node instanceof HTMLElement) { + newWidths.push(node.clientWidth) + } + }) + + rowWidthsRef.current = newWidths + dragIndexRef.current = index; + dragStartRef.current = event.clientX; + + window.addEventListener('mousemove', resizeDrag); + window.addEventListener('mouseup', resizeUnclick); + } + + const resizeUnclick = () => { + dragIndexRef.current = undefined; + dragStartRef.current = undefined; + window.removeEventListener('mousemove', resizeDrag); + window.removeEventListener('mouseup', resizeUnclick); + } + + const resizeDrag = (e: any) => { + const index = dragIndexRef.current; + const dragStart = dragStartRef.current + const rowWidths = rowWidthsRef.current + if (index === undefined) return + if (rowWidths[index] === undefined) return + if (rowWidths[index+1] === undefined) return + if (!dragStart) return + + let newWidths: number[] = [...rowWidths] + newWidths[index] = newWidths[index] - (dragStart - e.clientX) + newWidths.every((width, j) => { + if (j <= index || width === 0) return true + else if (newWidths[j] !== undefined) { + newWidths[j] = newWidths[j] + (dragStart - e.clientX) + return false + } + }) + setRowWidths(newWidths) + + } + return ( @@ -483,15 +537,28 @@ export default function ResponsiveTable(props: Props) { { renderGutter && } { columns ? columns.map((column, index) => { - if (filterList.includes(column.title) || column.hidden) return null + if (filterList.includes(column.title) || column.hidden) return ( + + ) return ( - !column.disableSort && columnClick(column)} - sx={{ cursor: column.disableSort ? "default" : "pointer" }} - > - - {column.title} {showOrderIcon(column)} + + + !column.disableSort && columnClick(column)} + sx={{ cursor: column.disableSort ? "default" : "pointer" }} + > + {column.title} {showOrderIcon(column)} + + {index < columns.length-1 && resizeable && + resizeClick(event, index)} + > + | + + } ) @@ -547,15 +614,17 @@ export default function ResponsiveTable(props: Props) { } {columns ? columns.map((column, j) => { - if (filterList.includes(column.title) || column.hidden) return null + if (filterList.includes(column.title) || column.hidden) return ( + + ) if (column.render) return ( - + {column.render(row)} ) return ( - {Object.values(row)[j].toString()} + {Object.values(row)[j] && Object.values(row)[j].toString()} ) }) @@ -615,9 +684,8 @@ export default function ResponsiveTable(props: Props) { filter(column.title)} key={"column-list-"+index} - aria-label="Open User Settings" dense - sx={{ marginLeft: -0.75 }} + sx={{ marginLeft: -0.75, paddingRight: 3 }} > (props: Props) { ) })} + setRowWidths([])}> + + Reset Widths + + ) diff --git a/src/pages/Devices.tsx b/src/pages/Devices.tsx index 9e8bb0d..8236074 100644 --- a/src/pages/Devices.tsx +++ b/src/pages/Devices.tsx @@ -27,6 +27,7 @@ import StatusSen5x from "common/StatusSen5x"; import StatusDust from "common/StatusDust"; import StatusGas from "common/StatusGas"; import { cloneDeep } from "lodash"; +import LoadingScreen from "app/LoadingScreen"; const useStyles = makeStyles((theme: Theme) => { return ({ @@ -56,6 +57,11 @@ const useStyles = makeStyles((theme: Theme) => { width: theme.spacing(10) // justifyContent: "center", }, + deviceContainer: { + margin: theme.spacing(1), + display: "flex", + width: theme.spacing(10) + }, descriptionCellContainer: { margin: theme.spacing(1), marginLeft: theme.spacing(2), @@ -67,7 +73,7 @@ const useStyles = makeStyles((theme: Theme) => { margin: theme.spacing(1), marginLeft: theme.spacing(2), display: "flex", - width: theme.spacing(18) + minWidth: theme.spacing(10) // justifyContent: "center", }, green: { @@ -140,9 +146,22 @@ export default function Devices() { // const [totalGroups, setTotalGroups] = useState(0); const [groupPermissions, setGroupPermissions] = useState([]) + const [groupsLoaded, setGroupsLoaded] = useState(false) const groupID = useParams<{ groupID: string }>()?.groupID ?? "all"; - const [tab, setTab] = useState(groupID==="all" ? groupID : groupID); + // const [tab, setTab] = useState(groupID==="all" ? groupID : groupID); + const [tab, setTab] = useState(() => { + console.log("groupID: "+groupID) + if (groupID!=="all") return groupID + const stored = sessionStorage.getItem(location.pathname+"-groups-tab"); + console.log("stored: "+stored) + return stored !== null ? stored : "all"; + }); + + useEffect(() => { + console.log(tab) + sessionStorage.setItem(location.pathname+"-groups-tab", tab); + }, [tab]); const [selectedGroup, setSelectedGroup] = useState(undefined); const [groupSettingsMode, setGroupSettingsMode] = useState< @@ -184,7 +203,7 @@ export default function Devices() { useEffect(() => { setGroupPermissions([]) - if (tab === "all") return + if (tab === "all" || !getGroup()) return groupAPI.getGroupPermissions(getGroup().id()).then(resp => { setGroupPermissions(resp.data.permissions) }) @@ -232,7 +251,7 @@ export default function Devices() { }; const getKeys = () => { - if (tab !== "all") { + if (tab !== "all" && getGroup()) { let keys = getContextKeys() //keys.splice(keys.length - 1, 0, getGroup().id().toString()); keys.push(getGroup().id().toString()); @@ -274,11 +293,13 @@ export default function Devices() { updateGroups(newGroups) }).finally(() => { setGroupsLoading(false) + setGroupsLoaded(true) }) } const loadDevices = () => { setDevicesLoading(true) + if (!groupsLoaded) return deviceAPI.list( limit, page*limit, @@ -316,7 +337,7 @@ export default function Devices() { useEffect(() => { loadDevices() - }, [limit, page, order, orderBy, fieldContains, search, tab, as]) + }, [limit, page, order, orderBy, fieldContains, search, tab, as, groupsLoaded]) useEffect(() => { loadGroups() @@ -371,12 +392,14 @@ export default function Devices() { let columns: Column[] = [ { title: "State", + // cellStyle: {width: 100000}, // sortKey: "state", render: (device: Device) => { const status = device.status ?? pond.DeviceStatus.create() const deviceStateHelper = getDeviceStateHelper(status.state); return ( + {/* */} {deviceStateHelper.icon} ) @@ -391,10 +414,12 @@ export default function Devices() { }, { title: "Device", + cellStyle: { width: 160 }, sortKey: "name", render: (device: Device) => { return ( - + + {/* // */} { const description = device.settings?.description ?? "" let size = (hasCo2 && !groupGas) ? 26 : 36 return ( - + {description.length > size @@ -680,9 +706,7 @@ export default function Devices() { dense > - + @@ -692,10 +716,7 @@ export default function Devices() { dense > - + @@ -703,6 +724,14 @@ export default function Devices() { ) } + useEffect(() => { + console.log('Current tab value:', tab, typeof tab); + }, [tab]); + + if (!groupsLoaded) return ( + + ) + return( }/> : groups.map((group, index) => { if (group.id()) return ( - {group.name()}} key={"group-tab-"+index}/> + {group.name()}} key={"group-tab-"+index}/> ) })} } onClick={() => openGroupSettings(undefined, "add")} /> @@ -753,6 +782,7 @@ export default function Devices() { orderBy={orderBy} setOrderBy={setOrderBy} endTitleElement={preferencesButton} + resizeable loadMore={()=>{ let currentRows = cloneDeep(devices) deviceAPI.list( @@ -784,7 +814,7 @@ export default function Devices() { newDevices.push(Device.create(device)) }) setDevices(currentRows.concat(newDevices)) - }).catch(err => { + }).catch(_err => { }) }}