Merge branch 'staging_environment' into hybrid_inventory_control
13
.env
|
|
@ -8,13 +8,12 @@ VITE_AUTH0_AUDIENCE=api.brandxtech.ca
|
|||
VITE_AUTH0_DEV_CLIENT_ID=dzJTpqIeMA4Rwk4xujtwPbAO3TY32bM1
|
||||
VITE_AUTH0_STAGING_CLIENT_ID=3ib460VvLwdeyse5iUSQfxkVdQaUmphP
|
||||
|
||||
# VITE_AUTH0_CLIENT_ID=3ib460VvLwdeyse5iUSQfxkVdQaUmphP
|
||||
# VITE_AUTH0_AUDIENCE=stagingapi.brandxtech.ca
|
||||
# VITE_AUTH0_CLIENT_DOMAIN=adaptivestaging.us.auth0.com
|
||||
# VITE_APP_API_URL=https://stagingapi.brandxtech.ca/v1
|
||||
# VITE_APP_WS_URL=ws://stagingapi.brandxtech.ca/v1/live
|
||||
# VITE_APP_AUTH0_CLIENT_DOMAIN=adaptivestaging.us.auth0.com
|
||||
# VITE_APP_AUTH0_AUDIENCE=stagingapi.brandxtech.ca
|
||||
VITE_AUTH0_BXT_CLIENT_ID=sLnqOu40uWfQT1lYSDYj2wYmlLEHRB74
|
||||
VITE_AUTH0_ADAPTIVE_AGRICULTURE_CLIENT_ID=5pUCkl2SfogkWmM244UDcLEUOp8EFdHd
|
||||
VITE_AUTH0_ADAPTIVE_CONSTRUCTION_CLIENT_ID=32rABabJzXRvJiWivTmeKFgwFiqh4ok7
|
||||
VITE_AUTH0_AEROGROW_CLIENT_ID=KHl9ooUt1nia1RYw5n224dyggCXdbsSd
|
||||
VITE_AUTH0_MIVENT_CLIENT_ID=VNALE7RW6l3dY5uYcxgwElZV0lcT25Fg
|
||||
VITE_AUTH0_OMNIAIR_CLIENT_ID=IblmarD8wFafiD6doxTmOHQ6Bx3L9wWl
|
||||
|
||||
#Branding (Default theme)
|
||||
VITE_APP_WEBSITE_TITLE="Adaptive Dashboard"
|
||||
|
|
|
|||
|
|
@ -13,8 +13,8 @@ dependencies:production:
|
|||
only:
|
||||
refs:
|
||||
- master
|
||||
before_script:
|
||||
- npm -g config set user root
|
||||
# before_script:
|
||||
# - npm -g config set user root
|
||||
script:
|
||||
- npm ci
|
||||
cache:
|
||||
|
|
@ -28,17 +28,17 @@ dependencies:production:
|
|||
paths:
|
||||
- node_modules/
|
||||
|
||||
test:
|
||||
image: node:$NODE_VERSION
|
||||
stage: setup
|
||||
dependencies:
|
||||
- dependencies:production
|
||||
only:
|
||||
- master
|
||||
before_script:
|
||||
- npm run lint
|
||||
script:
|
||||
- CI=true npm run test
|
||||
# test:
|
||||
# image: node:$NODE_VERSION
|
||||
# stage: setup
|
||||
# dependencies:
|
||||
# - dependencies:production
|
||||
# only:
|
||||
# - master
|
||||
# before_script:
|
||||
# - npm run lint
|
||||
# script:
|
||||
# - CI=true npm run test
|
||||
|
||||
setup:production:
|
||||
image: node:$NODE_VERSION
|
||||
|
|
@ -197,6 +197,7 @@ staging:deploy:
|
|||
DEPLOY_IMAGE: ${CI_REGISTRY}/brandx/bxt-app/production:staging
|
||||
DOCKER_HOST: ssh://deploy@staging.brandxtech.ca
|
||||
DEPLOY_TARGET: deploy@staging.brandxtech.ca
|
||||
VITE_MAPBOX_ACCESS_TOKEN: $MAPBOX_ACCESS_TOKEN
|
||||
only:
|
||||
- staging_environment
|
||||
environment:
|
||||
|
|
@ -206,6 +207,7 @@ staging:deploy:
|
|||
- "apk --update add openssh-client"
|
||||
- "eval $(ssh-agent -s)"
|
||||
- "echo \"$STAGING_SSH_PRIV_KEY\" | tr -d '\r' | ssh-add - "
|
||||
- "echo \"$MAPBOX_ACCESS_TOKEN\""
|
||||
# - "ssh -Mo StrictHostKeyChecking=no -S deploy-ctrl-socket -fnNTL /var/run/docker.sock:/var/run/docker.sock ${DEPLOY_TARGET}"
|
||||
# - "ssh -S deploy-ctrl-socket -O check ${DEPLOY_TARGET}"
|
||||
- mkdir -p ~/.ssh
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/x-icon" id="favicon-link" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Adaptive Dashboard</title>
|
||||
<title id="title-id">Adaptive Dashboard</title>
|
||||
<link rel="manifest" id="manifest-link" />
|
||||
</head>
|
||||
<body>
|
||||
|
|
|
|||
765
package-lock.json
generated
12
package.json
|
|
@ -4,16 +4,16 @@
|
|||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"start": "vite",
|
||||
"start": "VITE_AUTH0_CLIENT_ID=5pUCkl2SfogkWmM244UDcLEUOp8EFdHd VITE_AUTH0_AUDIENCE=api.brandxtech.ca VITE_APP_API_URL=https://api.brandxtech.ca/v1 VITE_APP_WS_URL=ws://api.brandxtech.ca/v1/live vite",
|
||||
"start-local": "VITE_AUTH0_CLIENT_ID=dzJTpqIeMA4Rwk4xujtwPbAO3TY32bM1 VITE_AUTH0_AUDIENCE=bxt-dev.api.adaptiveagriculture.ca VITE_APP_API_URL=http://localhost:50052/v1 VITE_APP_WS_URL=ws://localhost:50052/v1/live VITE_APP_BILLING_URL=http://localhost:50053/v1 VITE_APP_RECLUSE_URL=http://localhost:50054/v1 VITE_APP_GITLAB_URL=http://localhost:50055/v1 vite",
|
||||
"start-dev": "VITE_AUTH0_CLIENT_ID=dzJTpqIeMA4Rwk4xujtwPbAO3TY32bM1 VITE_APP_API_URL=https://bxt-dev.api.adaptiveagriculture.ca/v1 VITE_AUTH0_CLIENT_DOMAIN=brandxtech.auth0.com VITE_AUTH0_AUDIENCE=bxt-dev.api.adaptiveagriculture.ca VITE_AUTH0_DEV_CLIENT_ID=dzJTpqIeMA4Rwk4xujtwPbAO3TY32bM1 vite",
|
||||
"start-staging": "VITE_AUTH0_CLIENT_ID=3ib460VvLwdeyse5iUSQfxkVdQaUmphP VITE_AUTH0_AUDIENCE=stagingapi.brandxtech.ca VITE_AUTH0_CLIENT_DOMAIN=adaptivestaging.us.auth0.com VITE_APP_API_URL=https://stagingapi.brandxtech.ca/v1 VITE_APP_WS_URL=ws://stagingapi.brandxtech.ca/v1/live VITE_APP_AUTH0_CLIENT_DOMAIN=adaptivestaging.us.auth0.com VITE_APP_AUTH0_AUDIENCE=stagingapi.brandxtech.ca vite",
|
||||
"start-staging": "VITE_LOCAL_STAGING=true VITE_AUTH0_CLIENT_ID=3ib460VvLwdeyse5iUSQfxkVdQaUmphP VITE_AUTH0_AUDIENCE=stagingapi.brandxtech.ca VITE_AUTH0_CLIENT_DOMAIN=adaptivestaging.us.auth0.com VITE_APP_API_URL=https://stagingapi.brandxtech.ca/v1 VITE_APP_WS_URL=ws://stagingapi.brandxtech.ca/v1/live VITE_APP_AUTH0_CLIENT_DOMAIN=adaptivestaging.us.auth0.com VITE_APP_AUTH0_AUDIENCE=stagingapi.brandxtech.ca vite",
|
||||
"build": "tsc -b && vite build",
|
||||
"lint": "eslint .",
|
||||
"preview": "vite preview",
|
||||
"build:staging": "VITE_AUTH0_CLIENT_DOMAIN=adaptivestaging.us.auth0.com VITE_AUTH0_AUDIENCE=stagingapi.brandxtech.ca VITE_AUTH0_CLIENT_ID=3ib460VvLwdeyse5iUSQfxkVdQaUmphP VITE_APP_API_URL=https://stagingapi.brandxtech.ca/v1 VITE_APP_WS_URL=wss://stagingapi.brandxtech.ca/v1/live NODE_OPTIONS=--max_old_space_size=4096 VITE_APP_GOOGLE_API_KEY=${GOOGLE_API_KEY} VITE_APP_STRIPE_PUBLIC_KEY=${STRIPE_PUBLIC_KEY_STAGING} VITE_APP_MAPBOX_ACCESS_TOKEN=${MAPBOX_ACCESS_TOKEN} VITE_APP_CNHI_CLIENT_ID=${CNHI_CLIENT_ID} VITE_APP_CNHI_AUTHORIZE_URL=${CNHI_AUTHORIZE_URL} VITE_APP_CNHI_REDIRECT_URI=${CNHI_REDIRECT_URI} VITE_APP_CNHI_SCOPES=${CNHI_SCOPES} VITE_APP_CNHI_CONNECTION=${CNHI_CONNECTION} VITE_APP_CNHI_AUDIENCE=${CNHI_AUDIENCE} VITE_APP_IMAGE4IO_USERNAME=${IMAGE4IO_USERNAME} VITE_APP_IMAGE4IO_PASSWORD=${IMAGE4IO_PASSWORD} VITE_APP_JD_CLIENT_ID=${JD_CLIENT_ID} VITE_APP_JD_AUTHORIZE_URL=${JD_AUTHORIZE_URL} VITE_APP_JD_REDIRECT_URI=${JD_REDIRECT_URI} VITE_APP_JD_SCOPES=${JD_SCOPES} VITE_APP_JD_STATE=${JD_STATE} vite build",
|
||||
"build:development": "VITE_AUTH0_CLIENT_ID=dzJTpqIeMA4Rwk4xujtwPbAO3TY32bM1 VITE_AUTH0_AUDIENCE=bxt-dev.api.adaptiveagriculture.ca VITE_APP_API_URL=https://bxt-dev.api.adaptiveagriculture.ca/v1 NODE_OPTIONS=--max_old_space_size=4096 VITE_APP_GOOGLE_API_KEY=${GOOGLE_API_KEY} VITE_APP_STRIPE_PUBLIC_KEY=${STRIPE_PUBLIC_KEY_DEVELOPMENT} VITE_APP_MAPBOX_ACCESS_TOKEN=${MAPBOX_ACCESS_TOKEN} VITE_APP_CNHI_CLIENT_ID=${CNHI_CLIENT_ID} VITE_APP_CNHI_AUTHORIZE_URL=${CNHI_AUTHORIZE_URL} VITE_APP_CNHI_REDIRECT_URI=${CNHI_REDIRECT_URI} VITE_APP_CNHI_SCOPES=${CNHI_SCOPES} VITE_APP_CNHI_CONNECTION=${CNHI_CONNECTION} VITE_APP_CNHI_AUDIENCE=${CNHI_AUDIENCE} VITE_APP_IMAGE4IO_USERNAME=${IMAGE4IO_USERNAME} VITE_APP_IMAGE4IO_PASSWORD=${IMAGE4IO_PASSWORD} VITE_APP_JD_CLIENT_ID=${JD_CLIENT_ID} VITE_APP_JD_AUTHORIZE_URL=${JD_AUTHORIZE_URL} VITE_APP_JD_REDIRECT_URI=${JD_REDIRECT_URI} VITE_APP_JD_SCOPES=${JD_SCOPES} VITE_APP_JD_STATE=${JD_STATE} vite build",
|
||||
"build:production": "NODE_OPTIONS=--max_old_space_size=4096 VITE_APP_GOOGLE_API_KEY=${GOOGLE_API_KEY} VITE_APP_STRIPE_PUBLIC_KEY=${STRIPE_PUBLIC_KEY_PRODUCTION} VITE_APP_MAPBOX_ACCESS_TOKEN=${MAPBOX_ACCESS_TOKEN} VITE_APP_CNHI_CLIENT_ID=${CNHI_CLIENT_ID} VITE_APP_CNHI_AUTHORIZE_URL=${CNHI_AUTHORIZE_URL} VITE_APP_CNHI_REDIRECT_URI=${CNHI_REDIRECT_URI} VITE_APP_CNHI_SCOPES=${CNHI_SCOPES} VITE_APP_CNHI_CONNECTION=${CNHI_CONNECTION} VITE_APP_CNHI_AUDIENCE=${CNHI_AUDIENCE} VITE_APP_IMAGE4IO_USERNAME=${IMAGE4IO_USERNAME} VITE_APP_IMAGE4IO_PASSWORD=${IMAGE4IO_PASSWORD} VITE_APP_JD_CLIENT_ID=${JD_CLIENT_ID} VITE_APP_JD_AUTHORIZE_URL=${JD_AUTHORIZE_URL} VITE_APP_JD_REDIRECT_URI=${JD_REDIRECT_URI} VITE_APP_JD_SCOPES=${JD_SCOPES} VITE_APP_JD_STATE=${JD_STATE} vite build",
|
||||
"build:staging": "VITE_AUTH0_CLIENT_DOMAIN=adaptivestaging.us.auth0.com VITE_AUTH0_AUDIENCE=stagingapi.brandxtech.ca VITE_AUTH0_CLIENT_ID=3ib460VvLwdeyse5iUSQfxkVdQaUmphP VITE_APP_API_URL=https://stagingapi.brandxtech.ca/v1 VITE_APP_WS_URL=wss://stagingapi.brandxtech.ca/v1/live NODE_OPTIONS=--max_old_space_size=4096 VITE_APP_GOOGLE_API_KEY=${GOOGLE_API_KEY} VITE_APP_STRIPE_PUBLIC_KEY=${STRIPE_PUBLIC_KEY_STAGING} VITE_MAPBOX_ACCESS_TOKEN=${MAPBOX_ACCESS_TOKEN} VITE_CNHI_CLIENT_ID=${CNHI_CLIENT_ID} VITE_CNHI_AUTHORIZE_URL=${CNHI_AUTHORIZE_URL} VITE_CNHI_REDIRECT_URI=${CNHI_REDIRECT_URI} VITE_CNHI_SCOPES=${CNHI_SCOPES} VITE_CNHI_CONNECTION=${CNHI_CONNECTION} VITE_CNHI_AUDIENCE=${CNHI_AUDIENCE} VITE_APP_IMAGE4IO_USERNAME=${IMAGE4IO_USERNAME} VITE_APP_IMAGE4IO_PASSWORD=${IMAGE4IO_PASSWORD} VITE_JD_CLIENT_ID=${JD_CLIENT_ID} VITE_JD_AUTHORIZE_URL=${JD_AUTHORIZE_URL} VITE_JD_REDIRECT_URI=${JD_REDIRECT_URI} VITE_JD_SCOPES=${JD_SCOPES} VITE_JD_STATE=${JD_STATE} VITE_OPEN_WEATHERMAP={OPEN_WEATHERMAP} vite build",
|
||||
"build:development": "VITE_AUTH0_CLIENT_ID=dzJTpqIeMA4Rwk4xujtwPbAO3TY32bM1 VITE_AUTH0_AUDIENCE=bxt-dev.api.adaptiveagriculture.ca VITE_APP_API_URL=https://bxt-dev.api.adaptiveagriculture.ca/v1 NODE_OPTIONS=--max_old_space_size=4096 VITE_APP_GOOGLE_API_KEY=${GOOGLE_API_KEY} VITE_APP_STRIPE_PUBLIC_KEY=${STRIPE_PUBLIC_KEY_DEVELOPMENT} VITE_MAPBOX_ACCESS_TOKEN=${MAPBOX_ACCESS_TOKEN} VITE_CNHI_CLIENT_ID=${CNHI_CLIENT_ID} VITE_CNHI_AUTHORIZE_URL=${CNHI_AUTHORIZE_URL} VITE_CNHI_REDIRECT_URI=${CNHI_REDIRECT_URI} VITE_CNHI_SCOPES=${CNHI_SCOPES} VITE_CNHI_CONNECTION=${CNHI_CONNECTION} VITE_CNHI_AUDIENCE=${CNHI_AUDIENCE} VITE_APP_IMAGE4IO_USERNAME=${IMAGE4IO_USERNAME} VITE_APP_IMAGE4IO_PASSWORD=${IMAGE4IO_PASSWORD} VITE_JD_CLIENT_ID=${JD_CLIENT_ID} VITE_JD_AUTHORIZE_URL=${JD_AUTHORIZE_URL} VITE_JD_REDIRECT_URI=${JD_REDIRECT_URI} VITE_JD_SCOPES=${JD_SCOPES} VITE_JD_STATE=${JD_STATE} VITE_OPEN_WEATHERMAP={OPEN_WEATHERMAP} vite build",
|
||||
"build:production": "VITE_AUTH0_CLIENT_ID=5pUCkl2SfogkWmM244UDcLEUOp8EFdHd VITE_AUTH0_CLIENT_DOMAIN=brandxtech.auth0.com VITE_AUTH0_AUDIENCE=api.brandxtech.ca VITE_APP_API_URL=https://api.brandxtech.ca/v1 NODE_OPTIONS=--max_old_space_size=4096 VITE_APP_GOOGLE_API_KEY=${GOOGLE_API_KEY} VITE_APP_STRIPE_PUBLIC_KEY=${STRIPE_PUBLIC_KEY_PRODUCTION} VITE_MAPBOX_ACCESS_TOKEN=${MAPBOX_ACCESS_TOKEN} VITE_CNHI_CLIENT_ID=${CNHI_CLIENT_ID} VITE_CNHI_AUTHORIZE_URL=${CNHI_AUTHORIZE_URL} VITE_CNHI_REDIRECT_URI=${CNHI_REDIRECT_URI} VITE_CNHI_SCOPES=${CNHI_SCOPES} VITE_CNHI_CONNECTION=${CNHI_CONNECTION} VITE_CNHI_AUDIENCE=${CNHI_AUDIENCE} VITE_APP_IMAGE4IO_USERNAME=${IMAGE4IO_USERNAME} VITE_APP_IMAGE4IO_PASSWORD=${IMAGE4IO_PASSWORD} VITE_JD_CLIENT_ID=${JD_CLIENT_ID} VITE_JD_AUTHORIZE_URL=${JD_AUTHORIZE_URL} VITE_JD_REDIRECT_URI=${JD_REDIRECT_URI} VITE_JD_SCOPES=${JD_SCOPES} VITE_JD_STATE=${JD_STATE} VITE_OPEN_WEATHERMAP={OPEN_WEATHERMAP} vite build",
|
||||
"build:offline": "npx env-cmd offline,whitelabel npm run build",
|
||||
"test": "vitest"
|
||||
},
|
||||
|
|
@ -39,7 +39,6 @@
|
|||
"@turf/area": "^7.2.0",
|
||||
"@turf/turf": "^7.2.0",
|
||||
"@types/classnames": "^2.3.0",
|
||||
"@types/mapbox-gl": "^3.4.1",
|
||||
"axios": "^1.7.7",
|
||||
"dayjs": "^1.11.13",
|
||||
"deep-object-diff": "^1.1.9",
|
||||
|
|
@ -80,6 +79,7 @@
|
|||
"@types/lodash": "^4.17.13",
|
||||
"@types/mapbox__mapbox-gl-draw": "^1.4.8",
|
||||
"@types/mapbox__mapbox-gl-geocoder": "^5.0.0",
|
||||
"@types/mapbox-gl": "^3.4.1",
|
||||
"@types/react": "^18.3.10",
|
||||
"@types/react-beautiful-dnd": "^13.1.8",
|
||||
"@types/react-color": "^3.0.13",
|
||||
|
|
|
|||
BIN
public/Staging/android-chrome-192x192.png
Normal file
|
After Width: | Height: | Size: 6.1 KiB |
BIN
public/Staging/android-chrome-512x512.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
public/Staging/apple-touch-icon.png
Normal file
|
After Width: | Height: | Size: 5.7 KiB |
9
public/Staging/browserconfig.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<browserconfig>
|
||||
<msapplication>
|
||||
<tile>
|
||||
<square150x150logo src="/mstile-150x150.png"/>
|
||||
<TileColor>#ffffff</TileColor>
|
||||
</tile>
|
||||
</msapplication>
|
||||
</browserconfig>
|
||||
BIN
public/Staging/favicon-16x16.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
public/Staging/favicon-32x32.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
public/Staging/favicon.ico
Normal file
|
After Width: | Height: | Size: 15 KiB |
26
public/Staging/manifest.json
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"name": "Brand X Technologies",
|
||||
"short_name": "BXT",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"sizes": "48x48",
|
||||
"type": "image/x-icon"
|
||||
},
|
||||
{
|
||||
"src": "android-chrome-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "android-chrome-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
}
|
||||
],
|
||||
"start_url": "/",
|
||||
"theme_color": "#272727",
|
||||
"background_color": "#424242",
|
||||
"display": "standalone",
|
||||
"orientation": "any"
|
||||
}
|
||||
BIN
public/Staging/mstile-150x150.png
Normal file
|
After Width: | Height: | Size: 4.9 KiB |
2198
public/Staging/safari-pinned-tab.svg
Normal file
|
After Width: | Height: | Size: 144 KiB |
|
|
@ -37,19 +37,16 @@ function App() {
|
|||
faviconDocument.href = "BrandXTechnologies/favicon.ico"
|
||||
})
|
||||
|
||||
// Set title
|
||||
const titleElement = document.getElementById("title-id") as HTMLElement;
|
||||
if (titleElement) titleElement.textContent = whiteLabel.name;
|
||||
|
||||
|
||||
let domain: string | undefined = import.meta.env.VITE_AUTH0_CLIENT_DOMAIN;
|
||||
let audience: string | undefined = import.meta.env.VITE_AUTH0_AUDIENCE;
|
||||
|
||||
// let client_id = whiteLabel.auth0ClientId
|
||||
let client_id = import.meta.env.VITE_AUTH0_CLIENT_ID;
|
||||
// if (!client_id) client_id = import.meta.env.VITE_AUTH0_CLIENT_ID;
|
||||
|
||||
//check the url for a code before auth0 causes a login re-direct
|
||||
// if (window.location.pathname !== "/callback") {
|
||||
// //set the code into local storage
|
||||
// let code = new URLSearchParams(window.location.search).get("code");
|
||||
// sessionStorage.setItem("code", code || "")
|
||||
// }
|
||||
let client_id = whiteLabel.auth0ClientId
|
||||
if (!client_id) client_id = import.meta.env.VITE_AUTH0_CLIENT_ID;
|
||||
|
||||
const skipCallbacks = [
|
||||
"/johndeere",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { Box, Theme, ToggleButton, ToggleButtonGroup, darken } from "@mui/material";
|
||||
import { makeStyles, withStyles } from "@mui/styles";
|
||||
import ButtonGroup from "common/ButtonGroup";
|
||||
import { ExtractMoisture } from "grain";
|
||||
import { GrainCable } from "models/GrainCable";
|
||||
import { describeMeasurement } from "pbHelpers/MeasurementDescriber";
|
||||
|
|
@ -212,54 +213,54 @@ export default function BinSVGV2(props: Props) {
|
|||
const [extraDetails, setExtraDetails] = useState<"temps" | "hums">("temps");
|
||||
const [nodeWarning, setNodeWarning] = useState(false);
|
||||
|
||||
const StyledToggleButtonGroup = withStyles(theme => ({
|
||||
grouped: {
|
||||
margin: theme.spacing(-0.5),
|
||||
border: "none",
|
||||
padding: theme.spacing(1),
|
||||
"&:not(:first-child):not(:last-child)": {
|
||||
borderRadius: 24,
|
||||
marginRight: theme.spacing(0.5),
|
||||
marginLeft: theme.spacing(0.5)
|
||||
},
|
||||
"&:first-child": {
|
||||
borderRadius: 24,
|
||||
marginLeft: theme.spacing(0.25)
|
||||
},
|
||||
"&:last-child": {
|
||||
borderRadius: 24,
|
||||
marginRight: theme.spacing(0.25)
|
||||
}
|
||||
},
|
||||
root: {
|
||||
backgroundColor: darken(
|
||||
theme.palette.background.paper,
|
||||
getThemeType() === "light" ? 0.05 : 0.25
|
||||
),
|
||||
borderRadius: 24,
|
||||
content: "border-box"
|
||||
}
|
||||
}))(ToggleButtonGroup);
|
||||
// const StyledToggleButtonGroup = withStyles(theme => ({
|
||||
// grouped: {
|
||||
// margin: theme.spacing(-0.5),
|
||||
// border: "none",
|
||||
// padding: theme.spacing(1),
|
||||
// "&:not(:first-child):not(:last-child)": {
|
||||
// borderRadius: 24,
|
||||
// marginRight: theme.spacing(0.5),
|
||||
// marginLeft: theme.spacing(0.5)
|
||||
// },
|
||||
// "&:first-child": {
|
||||
// borderRadius: 24,
|
||||
// marginLeft: theme.spacing(0.25)
|
||||
// },
|
||||
// "&:last-child": {
|
||||
// borderRadius: 24,
|
||||
// marginRight: theme.spacing(0.25)
|
||||
// }
|
||||
// },
|
||||
// root: {
|
||||
// backgroundColor: darken(
|
||||
// theme.palette.background.paper,
|
||||
// getThemeType() === "light" ? 0.05 : 0.25
|
||||
// ),
|
||||
// borderRadius: 24,
|
||||
// content: "border-box"
|
||||
// }
|
||||
// }))(ToggleButtonGroup);
|
||||
|
||||
const StyledToggle = withStyles({
|
||||
root: {
|
||||
backgroundColor: "transparent",
|
||||
overflow: "visible",
|
||||
content: "content-box",
|
||||
"&$selected": {
|
||||
backgroundColor: "gold",
|
||||
color: "black",
|
||||
borderRadius: 24,
|
||||
fontWeight: "bold"
|
||||
},
|
||||
"&$selected:hover": {
|
||||
backgroundColor: "rgb(255, 255, 0)",
|
||||
color: "black",
|
||||
borderRadius: 24
|
||||
}
|
||||
},
|
||||
selected: {}
|
||||
})(ToggleButton);
|
||||
// const StyledToggle = withStyles({
|
||||
// root: {
|
||||
// backgroundColor: "transparent",
|
||||
// overflow: "visible",
|
||||
// content: "content-box",
|
||||
// "&$selected": {
|
||||
// backgroundColor: "gold",
|
||||
// color: "black",
|
||||
// borderRadius: 24,
|
||||
// fontWeight: "bold"
|
||||
// },
|
||||
// "&$selected:hover": {
|
||||
// backgroundColor: "rgb(255, 255, 0)",
|
||||
// color: "black",
|
||||
// borderRadius: 24
|
||||
// }
|
||||
// },
|
||||
// selected: {}
|
||||
// })(ToggleButton);
|
||||
|
||||
const gradients = () => {
|
||||
const burning = "#8a1f0d";
|
||||
|
|
@ -832,7 +833,6 @@ export default function BinSVGV2(props: Props) {
|
|||
cableGrainEndY = cableGrainStartY;
|
||||
}
|
||||
if (cableEstimate && cables.length > 0 && cableGrainPathPoints.length === cables.length) {
|
||||
console.log("calc cable path");
|
||||
let cablePath = cableGrainEstimate(
|
||||
cableGrainPathPoints,
|
||||
spacingX,
|
||||
|
|
@ -1048,10 +1048,26 @@ export default function BinSVGV2(props: Props) {
|
|||
<React.Fragment>
|
||||
{showTempHum && (
|
||||
<Box
|
||||
display="flex"
|
||||
alignContent="center"
|
||||
justifyContent="center"
|
||||
textAlign="center"
|
||||
//visibility={showTempHum ? "visible" : "hidden"}
|
||||
className={!isFullScreen ? classes.smallToggle : classes.fullToggle}>
|
||||
<StyledToggleButtonGroup
|
||||
<ButtonGroup
|
||||
buttons={[
|
||||
{
|
||||
title: isFullScreen ? "Temperature" : "Temp",
|
||||
function: () => setExtraDetails("temps")
|
||||
},
|
||||
{
|
||||
title: grainType ? (isFullScreen ? "Moisture" : "EMC") : isFullScreen ? "Humidity" : "Hum",
|
||||
function: () => setExtraDetails("hums")
|
||||
}
|
||||
]}
|
||||
toggle
|
||||
/>
|
||||
{/* <StyledToggleButtonGroup
|
||||
value={extraDetails}
|
||||
exclusive
|
||||
size="small"
|
||||
|
|
@ -1068,7 +1084,7 @@ export default function BinSVGV2(props: Props) {
|
|||
aria-label="Node Humidity">
|
||||
{grainType ? (isFullScreen ? "Moisture" : "EMC") : isFullScreen ? "Humidity" : "Hum"}
|
||||
</StyledToggle>
|
||||
</StyledToggleButtonGroup>
|
||||
</StyledToggleButtonGroup> */}
|
||||
</Box>
|
||||
)}
|
||||
<Box
|
||||
|
|
|
|||
|
|
@ -401,7 +401,7 @@ export default function BinStorageConditions(props: Props) {
|
|||
alignContent="center"
|
||||
alignItems="center"
|
||||
justifyContent="space-between">
|
||||
<Grid size={{ xs: 6 }}>
|
||||
<Grid size={{ xs: 8 }}>
|
||||
<Box display="flex" alignContent="center" alignItems="center">
|
||||
<TemperatureIcon />
|
||||
<Typography noWrap style={{ fontWeight: 650 }}>
|
||||
|
|
|
|||
|
|
@ -71,6 +71,7 @@ import { ExtractMoisture } from "grain";
|
|||
import SearchSelect, { Option } from "common/SearchSelect";
|
||||
import Edit from "@mui/icons-material/Edit";
|
||||
import { makeStyles, styled } from "@mui/styles";
|
||||
import ButtonGroup from "common/ButtonGroup";
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) => {
|
||||
return ({
|
||||
|
|
@ -280,54 +281,54 @@ export default function BinVisualizer(props: Props) {
|
|||
const [storageDate, setStorageDate] = useState(moment().format("YYYY-MM-DD"));
|
||||
const [storageTime, setStorageTime] = useState(moment().format("HH:mm"));
|
||||
|
||||
const StyledToggle = styled(ToggleButton)(({ theme }: { theme: Theme }) => ({
|
||||
root: {
|
||||
backgroundColor: "transparent",
|
||||
overflow: "visible",
|
||||
content: "content-box",
|
||||
"&$selected": {
|
||||
backgroundColor: "gold",
|
||||
color: "black",
|
||||
borderRadius: 24,
|
||||
fontWeight: "bold"
|
||||
},
|
||||
"&$selected:hover": {
|
||||
backgroundColor: "rgb(255, 255, 0)",
|
||||
color: "black",
|
||||
borderRadius: 24
|
||||
}
|
||||
},
|
||||
selected: {}
|
||||
}));
|
||||
// const StyledToggle = styled(ToggleButton)(({ theme }: { theme: Theme }) => ({
|
||||
// root: {
|
||||
// backgroundColor: "transparent",
|
||||
// overflow: "visible",
|
||||
// content: "content-box",
|
||||
// "&$selected": {
|
||||
// backgroundColor: "gold",
|
||||
// color: "black",
|
||||
// borderRadius: 24,
|
||||
// fontWeight: "bold"
|
||||
// },
|
||||
// "&$selected:hover": {
|
||||
// backgroundColor: "rgb(255, 255, 0)",
|
||||
// color: "black",
|
||||
// borderRadius: 24
|
||||
// }
|
||||
// },
|
||||
// selected: {}
|
||||
// }));
|
||||
|
||||
const StyledToggleButtonGroup = styled(ToggleButtonGroup)(({ theme }: { theme: Theme }) => ({
|
||||
grouped: {
|
||||
margin: theme.spacing(-0.5),
|
||||
border: "none",
|
||||
padding: theme.spacing(1),
|
||||
"&:not(:first-child):not(:last-child)": {
|
||||
borderRadius: 24,
|
||||
marginRight: theme.spacing(0.5),
|
||||
marginLeft: theme.spacing(0.5)
|
||||
},
|
||||
"&:first-child": {
|
||||
borderRadius: 24,
|
||||
marginLeft: theme.spacing(0.25)
|
||||
},
|
||||
"&:last-child": {
|
||||
borderRadius: 24,
|
||||
marginRight: theme.spacing(0.25)
|
||||
}
|
||||
},
|
||||
root: {
|
||||
backgroundColor: darken(
|
||||
theme.palette.background.paper,
|
||||
getThemeType() === "light" ? 0.05 : 0.25
|
||||
),
|
||||
borderRadius: 24,
|
||||
content: "border-box"
|
||||
}
|
||||
}));
|
||||
// const StyledToggleButtonGroup = styled(ToggleButtonGroup)(({ theme }: { theme: Theme }) => ({
|
||||
// grouped: {
|
||||
// margin: theme.spacing(-0.5),
|
||||
// border: "none",
|
||||
// padding: theme.spacing(1),
|
||||
// "&:not(:first-child):not(:last-child)": {
|
||||
// borderRadius: 24,
|
||||
// marginRight: theme.spacing(0.5),
|
||||
// marginLeft: theme.spacing(0.5)
|
||||
// },
|
||||
// "&:first-child": {
|
||||
// borderRadius: 24,
|
||||
// marginLeft: theme.spacing(0.25)
|
||||
// },
|
||||
// "&:last-child": {
|
||||
// borderRadius: 24,
|
||||
// marginRight: theme.spacing(0.25)
|
||||
// }
|
||||
// },
|
||||
// root: {
|
||||
// backgroundColor: darken(
|
||||
// theme.palette.background.paper,
|
||||
// getThemeType() === "light" ? 0.05 : 0.25
|
||||
// ),
|
||||
// borderRadius: 24,
|
||||
// content: "border-box"
|
||||
// }
|
||||
// }));
|
||||
|
||||
useEffect(() => {
|
||||
setModeTime(moment(bin.status.lastModeChange));
|
||||
|
|
@ -1705,6 +1706,18 @@ export default function BinVisualizer(props: Props) {
|
|||
);
|
||||
};
|
||||
|
||||
const determineToggle = (binMode: pond.BinMode) => {
|
||||
switch (binMode) {
|
||||
case pond.BinMode.BIN_MODE_STORAGE:
|
||||
return [0]
|
||||
case pond.BinMode.BIN_MODE_COOLDOWN:
|
||||
return [1]
|
||||
case pond.BinMode.BIN_MODE_DRYING:
|
||||
case pond.BinMode.BIN_MODE_HYDRATING:
|
||||
return [2]
|
||||
}
|
||||
}
|
||||
|
||||
const binMode = () => {
|
||||
const mode = bin.settings.mode;
|
||||
return (
|
||||
|
|
@ -1720,7 +1733,31 @@ export default function BinVisualizer(props: Props) {
|
|||
<Typography variant="subtitle1" style={{ fontWeight: 800 }}>
|
||||
Bin Mode
|
||||
</Typography>
|
||||
<StyledToggleButtonGroup
|
||||
<ButtonGroup
|
||||
buttons={[
|
||||
{
|
||||
title: "Storage",
|
||||
function: () => {
|
||||
setModeStorage()
|
||||
}
|
||||
},
|
||||
{
|
||||
title: "Cooldown",
|
||||
function: () => {
|
||||
setModeCooldown()
|
||||
}
|
||||
},
|
||||
{
|
||||
title: bin.settings.inventory && bin.settings.inventory?.initialMoisture < bin.settings.inventory?.targetMoisture ? "Hydrating" : "Drying",
|
||||
function: () => {
|
||||
setShowInputMoisture(true)
|
||||
}
|
||||
}
|
||||
]}
|
||||
toggledButtons={determineToggle(mode)}
|
||||
toggle
|
||||
/>
|
||||
{/* <StyledToggleButtonGroup
|
||||
id="tour-bin-mode"
|
||||
value={mode}
|
||||
exclusive
|
||||
|
|
@ -1754,7 +1791,7 @@ export default function BinVisualizer(props: Props) {
|
|||
Drying
|
||||
</StyledToggle>
|
||||
)}
|
||||
</StyledToggleButtonGroup>
|
||||
</StyledToggleButtonGroup> */}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
|
@ -2067,6 +2104,7 @@ export default function BinVisualizer(props: Props) {
|
|||
devices={devices}
|
||||
refreshCallback={success => {
|
||||
setShowInputMoisture(false);
|
||||
setNewPreset(0)
|
||||
if (success) updateBin();
|
||||
}}
|
||||
parentPreset={newPreset}
|
||||
|
|
|
|||
|
|
@ -130,6 +130,7 @@ const useStyles = makeStyles((theme: Theme) => {
|
|||
borderTopLeftRadius: "6px",
|
||||
borderTopRightRadius: "6px",
|
||||
background: theme.palette.background.default,
|
||||
fontWeight: 1000,
|
||||
transform: "0.3s ease-in-out",
|
||||
// "linear-gradient(rgba(150, 150, 150, 0.3),"
|
||||
// + theme.palette.background.default + " 80%)"
|
||||
|
|
|
|||
|
|
@ -123,10 +123,11 @@ export default function BinyardDisplay(props: Props) {
|
|||
});
|
||||
|
||||
const loadBins = useCallback(() => {
|
||||
setBinsLoading(true);
|
||||
if (yardKey === "") return
|
||||
//load the bins based on the yard key
|
||||
setBinsLoading(true);
|
||||
binAPI
|
||||
.listBinsAndData(maxBins, 0, "asc", "name", undefined, as, undefined, [yardKey], ["binyard"])
|
||||
.listBinsAndData(maxBins, 0, "asc", "name", undefined, undefined, undefined, [yardKey], ["binyard"])
|
||||
.then(resp => {
|
||||
let yardBins: Bin[] = [];
|
||||
let grainBins: Bin[] = [];
|
||||
|
|
@ -315,17 +316,10 @@ export default function BinyardDisplay(props: Props) {
|
|||
<Grid size={12}>
|
||||
<BinsList
|
||||
gridView
|
||||
valDisplay="average"
|
||||
bins={grainBins}
|
||||
duplicateBin={duplicateBin}
|
||||
title={"Grain Bins"}
|
||||
// because this only triggers in scroll view it is no longer necessary as we dont do the scroll here
|
||||
// loadMore={newTranslation => {
|
||||
// //only triggered in the scroll view so this will never trigger in grid view
|
||||
// if (yardBins.length < binTotal) {
|
||||
// loadMoreBins(contentFilter);
|
||||
// setScrollTranslations({ ...scrollTranslations, bins: newTranslation });
|
||||
// }
|
||||
// }}
|
||||
/>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
|
|
@ -338,16 +332,10 @@ export default function BinyardDisplay(props: Props) {
|
|||
<Grid size={12}>
|
||||
<BinsList
|
||||
gridView
|
||||
valDisplay="average"
|
||||
bins={fertBins}
|
||||
duplicateBin={duplicateBin}
|
||||
title={"Fertilizer Bins"}
|
||||
// loadMore={newTranslation => {
|
||||
// //only triggered in the scroll view so this will never trigger in grid view
|
||||
// if (yardBins.length < binTotal) {
|
||||
// loadMoreBins(contentFilter);
|
||||
// setScrollTranslations({ ...scrollTranslations, fertilizer: newTranslation });
|
||||
// }
|
||||
// }}
|
||||
/>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
|
|
@ -360,6 +348,7 @@ export default function BinyardDisplay(props: Props) {
|
|||
<Grid size={12}>
|
||||
<BinsList
|
||||
gridView
|
||||
valDisplay="average"
|
||||
bins={emptyBins}
|
||||
duplicateBin={duplicateBin}
|
||||
title={"Empty Bins"}
|
||||
|
|
|
|||
161
src/common/ButtonGroup.tsx
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
import { Box, Button, darken, Theme, Typography } from "@mui/material";
|
||||
import { makeStyles } from "@mui/styles";
|
||||
import { cloneDeep } from "lodash";
|
||||
import { useEffect } from "react";
|
||||
import { useState } from "react";
|
||||
import { getSecondaryColour } from "services/whiteLabel";
|
||||
import { getThemeType } from "theme/themeType";
|
||||
|
||||
export interface ButtonData {
|
||||
/**
|
||||
* The title and description of the button
|
||||
*/
|
||||
title: string
|
||||
/**
|
||||
* An icon that would replace the text of the button
|
||||
*/
|
||||
icon?: JSX.Element
|
||||
/**
|
||||
* The function to run when the button is clicked or toggled on when toggle is true
|
||||
*/
|
||||
function: () => void
|
||||
}
|
||||
|
||||
interface Props {
|
||||
/**
|
||||
* The array of button data to be rendered by the group
|
||||
*/
|
||||
buttons: ButtonData[]
|
||||
/**
|
||||
* When true will track the toggle state of the buttons, false will just be basic buttons
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
toggle?: boolean
|
||||
/**
|
||||
* When true will allow multiple buttons to be toggled, false will untoggle the previous button when another is selected
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
multiToggle?: boolean
|
||||
/**
|
||||
* Can be used to control which buttons are toggled on from the parent by passing the indexes of the buttons to toggle on.
|
||||
* If not passed in will default to toggle the first button and then be controlled internally
|
||||
*
|
||||
* @default undefined
|
||||
*/
|
||||
toggledButtons?: number[]
|
||||
/**
|
||||
* Numerical value for the size of the text for buttons that dont use the icon
|
||||
*/
|
||||
textSize?: number
|
||||
}
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) => {
|
||||
return ({
|
||||
container: {
|
||||
backgroundColor: darken(
|
||||
theme.palette.background.paper,
|
||||
getThemeType() === "light" ? 0.10 : 0.05
|
||||
),
|
||||
borderRadius: 24,
|
||||
content: "border-box",
|
||||
border: "none",
|
||||
//padding: theme.spacing(1),
|
||||
},
|
||||
button: {
|
||||
backgroundColor: "transparent",
|
||||
color: theme.palette.text.primary,
|
||||
overflow: "visible",
|
||||
content: "content-box",
|
||||
borderRadius: 24,
|
||||
marginLeft: theme.spacing(0.05),
|
||||
marginRight: theme.spacing(0.05),
|
||||
"&:hover": {
|
||||
//backgroundColor: getSecondaryColour(), //use the colour of the whitelabel
|
||||
backgroundColor: "gold", //for now Dustin just wants it to be gold
|
||||
color: "black",
|
||||
}
|
||||
},
|
||||
buttonToggled: {
|
||||
//backgroundColor: getSecondaryColour(), //use the colour of the whitelabel
|
||||
backgroundColor: "gold", //for now Dustin just wants it to be gold
|
||||
color: "black",
|
||||
borderRadius: 24,
|
||||
fontWeight: "bold"
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
export default function ButtonGroup(props: Props){
|
||||
const { buttons, toggle, toggledButtons, multiToggle, textSize } = props
|
||||
const classes = useStyles()
|
||||
const [currentToggle, setCurrentToggle] = useState<number[]>([])
|
||||
|
||||
useEffect(()=>{
|
||||
//determine the default state of the button toggles if toggle is true
|
||||
if(toggle){
|
||||
let toggled: number [] = [0]
|
||||
if(toggledButtons){
|
||||
toggled = toggledButtons
|
||||
}
|
||||
setCurrentToggle(toggled)
|
||||
}
|
||||
},[toggle, toggledButtons])
|
||||
|
||||
const checkToggled = (index: number) => {
|
||||
return currentToggle.includes(index)
|
||||
}
|
||||
|
||||
const toggleControl = (i: number) => {
|
||||
let toggled = cloneDeep(currentToggle)
|
||||
//is the button currently toggled
|
||||
if(checkToggled(i)){
|
||||
//if the button is toggled
|
||||
if(multiToggle){
|
||||
//un-toggle the button (remove it from the array)
|
||||
console.log(toggled)
|
||||
toggled.splice(currentToggle.indexOf(i), 1)
|
||||
}
|
||||
//and if it is single toggle do nothing
|
||||
} else {
|
||||
//if multiToggle is false
|
||||
if(!multiToggle){
|
||||
//replace the currentToggle array
|
||||
toggled = [i]
|
||||
}else{
|
||||
//add it to the currentToggle array
|
||||
toggled.push(i)
|
||||
}
|
||||
}
|
||||
setCurrentToggle(toggled)
|
||||
}
|
||||
|
||||
return (
|
||||
<Box className={classes.container} display="flex" flexDirection="row">
|
||||
{buttons.map((b, i) => (
|
||||
<Button
|
||||
key={i}
|
||||
className={checkToggled(i) ? classes.buttonToggled : classes.button}
|
||||
onClick={() => {
|
||||
if(toggle){
|
||||
//control the toggle internally if the array was not passed in
|
||||
if(!toggledButtons){
|
||||
toggleControl(i)
|
||||
}
|
||||
}
|
||||
b.function()
|
||||
}}>
|
||||
{b.icon
|
||||
?
|
||||
b.icon
|
||||
:
|
||||
<Typography sx={{fontSize: textSize ?? 13, fontWeight: checkToggled(i) ? 650 : 500}}>
|
||||
{b.title}
|
||||
</Typography>
|
||||
}
|
||||
</Button>
|
||||
))}
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
|
@ -133,7 +133,7 @@ export default function DisplayDrawer(props: Props) {
|
|||
</Grid>
|
||||
<Grid>
|
||||
<Box>
|
||||
<Typography align="center" variant="h5">
|
||||
<Typography sx={{ color: "black", fontWeight: 650}} align="center" variant="h5">
|
||||
{title}
|
||||
</Typography>
|
||||
</Box>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { Beenhere } from "@mui/icons-material";
|
|||
import { useThemeType } from "hooks";
|
||||
import moment from "moment";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import Map, { Marker } from "react-map-gl/mapbox-legacy";
|
||||
import Map, { Marker } from "react-map-gl/mapbox";
|
||||
|
||||
const MAPBOX_TOKEN = import.meta.env.VITE_MAPBOX_ACCESS_TOKEN;
|
||||
|
||||
|
|
|
|||
|
|
@ -7,11 +7,12 @@ import {
|
|||
LinkProps,
|
||||
Skeleton,
|
||||
Theme,
|
||||
Typography,
|
||||
useTheme
|
||||
} from "@mui/material";
|
||||
import { Replay } from "@mui/icons-material";
|
||||
import { useMobile } from "hooks";
|
||||
import { ReactNode, useEffect, useRef } from "react";
|
||||
import { ReactNode } from "react";
|
||||
import { useLocation } from "react-router";
|
||||
import { Link as RouterLink } from "react-router-dom";
|
||||
import { or } from "utils/types";
|
||||
|
|
@ -57,11 +58,13 @@ interface Props {
|
|||
paddingTop?: number;
|
||||
paddingBottom?: number;
|
||||
prependPaths?: string[];
|
||||
state?: any;
|
||||
}
|
||||
|
||||
interface LinkRouterProps extends LinkProps {
|
||||
to: string;
|
||||
replace?: boolean;
|
||||
state?: any;
|
||||
}
|
||||
|
||||
const LinkRouter = (props: LinkRouterProps) => <Link {...props} component={RouterLink as any} />;
|
||||
|
|
@ -72,7 +75,7 @@ export default function SmartBreadcrumb(props: Props) {
|
|||
const location = useLocation();
|
||||
const isMobile = useMobile();
|
||||
const classes = useStyles();
|
||||
const { loading, reload } = props;
|
||||
const { loading, reload, state } = props;
|
||||
|
||||
const groupID = (): string => {
|
||||
let index = prependPaths?.indexOf("groups")
|
||||
|
|
@ -289,7 +292,7 @@ export default function SmartBreadcrumb(props: Props) {
|
|||
|
||||
if (prependPaths) pathnames.splice(pathnames.length - 1, 0, ...prependPaths);
|
||||
|
||||
pathnames.forEach((_value: any, index: any) => {
|
||||
pathnames.forEach((value: any, index: any) => {
|
||||
|
||||
const lastPath = index === pathnames.length - 1;
|
||||
const to = `/${pathnames.slice(0, index + 1).join("/")}`;
|
||||
|
|
@ -310,6 +313,21 @@ export default function SmartBreadcrumb(props: Props) {
|
|||
links.push(linkComponent(to, label, lastPath));
|
||||
}
|
||||
|
||||
if (value === "support") {
|
||||
links.push(
|
||||
<Chip
|
||||
key={"support-chip"}
|
||||
variant={"filled"}
|
||||
label={
|
||||
<Typography variant="subtitle1" color="textPrimary">
|
||||
Support
|
||||
</Typography>
|
||||
}
|
||||
className={classes.chip}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
});
|
||||
return links;
|
||||
};
|
||||
|
|
@ -324,6 +342,7 @@ export default function SmartBreadcrumb(props: Props) {
|
|||
color={lastPath ? "textPrimary" : "textSecondary"}
|
||||
variant="subtitle1"
|
||||
to={to}
|
||||
state={state}
|
||||
sx={{ '&:hover': { color: getThemeType() === "dark" ? 'white' : "black" }}}
|
||||
className={classes.textOverflow}>
|
||||
{label}
|
||||
|
|
|
|||
|
|
@ -176,12 +176,12 @@ export default function StatusPlenum(props: Props) {
|
|||
>
|
||||
<Grid2>
|
||||
<Typography variant="body2" style={{ color: blue[300]}}>
|
||||
{device.status.sen5x?.voc.toFixed(1)}%
|
||||
V: {device.status.sen5x?.voc.toFixed(1)}%
|
||||
</Typography>
|
||||
</Grid2>
|
||||
<Grid2>
|
||||
<Typography variant="body2" style={{ color: blue[400]}}>
|
||||
{device.status.sen5x?.nox.toFixed(1)}%
|
||||
N: {device.status.sen5x?.nox.toFixed(1)}%
|
||||
</Typography>
|
||||
</Grid2>
|
||||
</Grid2>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,8 @@ import {
|
|||
Button,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
Grid,
|
||||
Grid2 as Grid,
|
||||
TextField,
|
||||
Theme,
|
||||
Typography
|
||||
} from "@mui/material";
|
||||
|
|
@ -13,6 +14,8 @@ import ResponsiveDialog from "common/ResponsiveDialog";
|
|||
import { DateRangePreset, GetDefaultDateRange, SetDefaultPreset } from "./DateRange";
|
||||
import { useThemeType } from "hooks";
|
||||
import { makeStyles } from "@mui/styles";
|
||||
import { cloneDeep } from "lodash";
|
||||
import { color } from "@mui/system";
|
||||
|
||||
interface Props {
|
||||
startDate: Moment;
|
||||
|
|
@ -26,10 +29,12 @@ type DateRange<T> = [T | null, T | null];
|
|||
const useStyles = makeStyles((_theme: Theme) => {
|
||||
return ({
|
||||
activeButtonLight: {
|
||||
border: "1px solid black"
|
||||
border: "1px solid black",
|
||||
color: "black"
|
||||
},
|
||||
acitveButtonDark: {
|
||||
border: "1px solid white"
|
||||
border: "1px solid white",
|
||||
color: "white"
|
||||
}
|
||||
})
|
||||
});
|
||||
|
|
@ -49,7 +54,7 @@ export default function TimeBar(props: Props) {
|
|||
|
||||
const submitDateRange = () => {
|
||||
const startDate = dateRange[0] ? dateRange[0].clone() : moment();
|
||||
const endDate = dateRange[1] ? dateRange[1].clone() : moment();
|
||||
const endDate = dateRange[1] ? dateRange[1].add(1, "day").clone() : moment(); //add 1 day to the end time to include that day rather than have it be the cutoff
|
||||
updateDateRange(startDate, endDate, false);
|
||||
setDateRangeDialog(false);
|
||||
setActiveView("selectRange");
|
||||
|
|
@ -85,19 +90,30 @@ export default function TimeBar(props: Props) {
|
|||
onClose={() => setDateRangeDialog(false)}
|
||||
aria-labelledby="date-range-dialog">
|
||||
<DialogContent>
|
||||
{/* <StaticDateRangePicker
|
||||
disableFuture
|
||||
value={dateRange}
|
||||
onChange={date => setDateRange(date)}
|
||||
renderInput={(startProps, endProps) => (
|
||||
<React.Fragment>
|
||||
<TextField {...startProps} />
|
||||
<DateRangeDelimiter> to </DateRangeDelimiter>
|
||||
<TextField {...endProps} />
|
||||
</React.Fragment>
|
||||
)}
|
||||
/> */}
|
||||
DATE PICKER GOES HERE
|
||||
<TextField
|
||||
fullWidth
|
||||
margin="normal"
|
||||
type="date"
|
||||
label="Start Time"
|
||||
value={dateRange[0]?.format("YYYY-MM-DD")}
|
||||
onChange={e => {
|
||||
let range = cloneDeep(dateRange)
|
||||
range[0] = moment(e.target.value)
|
||||
setDateRange(range)
|
||||
}}
|
||||
/>
|
||||
<TextField
|
||||
fullWidth
|
||||
margin="normal"
|
||||
type="date"
|
||||
label="End Time"
|
||||
value={dateRange[1]?.format("YYYY-MM-DD")}
|
||||
onChange={e => {
|
||||
let range = cloneDeep(dateRange)
|
||||
range[1] = moment(e.target.value)
|
||||
setDateRange(range)
|
||||
}}
|
||||
/>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => setDateRangeDialog(false)} color="primary">
|
||||
|
|
@ -115,7 +131,7 @@ export default function TimeBar(props: Props) {
|
|||
<React.Fragment>
|
||||
{datePickerDialog()}
|
||||
<Grid container direction="row" spacing={1} wrap="nowrap">
|
||||
<Grid item>
|
||||
<Grid>
|
||||
<Button
|
||||
style={{ borderRadius: "25px" }}
|
||||
className={
|
||||
|
|
@ -132,7 +148,7 @@ export default function TimeBar(props: Props) {
|
|||
<Typography style={{ fontWeight: 650 }}>1H</Typography>
|
||||
</Button>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Grid>
|
||||
<Button
|
||||
style={{ borderRadius: "25px" }}
|
||||
className={
|
||||
|
|
@ -149,7 +165,7 @@ export default function TimeBar(props: Props) {
|
|||
<Typography style={{ fontWeight: 650 }}>1D</Typography>
|
||||
</Button>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Grid>
|
||||
<Button
|
||||
style={{ borderRadius: "25px" }}
|
||||
className={
|
||||
|
|
@ -166,7 +182,7 @@ export default function TimeBar(props: Props) {
|
|||
<Typography style={{ fontWeight: 650 }}>1W</Typography>
|
||||
</Button>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Grid>
|
||||
<Button
|
||||
style={{ borderRadius: "25px" }}
|
||||
className={
|
||||
|
|
@ -183,7 +199,7 @@ export default function TimeBar(props: Props) {
|
|||
<Typography style={{ fontWeight: 650 }}>1M</Typography>
|
||||
</Button>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Grid>
|
||||
<Button
|
||||
style={{ borderRadius: "25px" }}
|
||||
className={
|
||||
|
|
|
|||
|
|
@ -114,7 +114,10 @@ export default function ComponentActions(props: Props) {
|
|||
onClose={closeMoreMenu}
|
||||
disableAutoFocusItem>
|
||||
{canAddInteraction && (
|
||||
<MenuItem dense onClick={() => setIsAddInteractionOpen(true)} divider>
|
||||
<MenuItem dense onClick={() => {
|
||||
setIsAddInteractionOpen(true)
|
||||
closeMoreMenu()
|
||||
}} divider>
|
||||
<ListItemIcon>
|
||||
<AddCircle className={classes.addIcon} />
|
||||
</ListItemIcon>
|
||||
|
|
@ -126,6 +129,7 @@ export default function ComponentActions(props: Props) {
|
|||
onClick={() => {
|
||||
setIsJSON(false);
|
||||
setIsExportDataOpen(true);
|
||||
closeMoreMenu()
|
||||
}}>
|
||||
<ListItemIcon>
|
||||
<SaveAlt className={classes.exportDataIcon} />
|
||||
|
|
@ -134,7 +138,10 @@ export default function ComponentActions(props: Props) {
|
|||
</MenuItem>
|
||||
{canEdit && <Divider />}
|
||||
{canEdit && (
|
||||
<MenuItem dense onClick={() => openComponentSettingsDialog("remove")}>
|
||||
<MenuItem dense onClick={() => {
|
||||
openComponentSettingsDialog("remove")
|
||||
setAnchorEl(null);
|
||||
}}>
|
||||
<ListItemIcon>
|
||||
<DeleteOutline className={classes.red} />
|
||||
</ListItemIcon>
|
||||
|
|
|
|||
|
|
@ -254,15 +254,15 @@ export default function ComponentCard(props: Props) {
|
|||
size="small"
|
||||
onTouchStart={event => {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
//event.preventDefault();
|
||||
}}
|
||||
onMouseDown={event => {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
//event.preventDefault();
|
||||
}}
|
||||
onClick={event => {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
//event.preventDefault();
|
||||
}}
|
||||
disabled={!canEdit}
|
||||
onChange={(_, checked) => updateControllerState(checked)}
|
||||
|
|
|
|||
|
|
@ -237,8 +237,7 @@ export default function ComponentForm(props: Props) {
|
|||
};
|
||||
|
||||
useEffect(() => {
|
||||
let comp = tidyComponent(form.component);
|
||||
componentChanged(comp, isFormValid());
|
||||
componentChanged(form.component, isFormValid());
|
||||
}, [form.component, componentChanged]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
const isNameValid = (name: string): boolean => {
|
||||
|
|
|
|||
|
|
@ -24,7 +24,8 @@ import {
|
|||
AccountCircle as ObjectUsersIcon,
|
||||
SupervisedUserCircle as ObjectTeamsIcon,
|
||||
Sync as SyncDeviceIcon,
|
||||
Wifi as WifiIcon
|
||||
Wifi as WifiIcon,
|
||||
Visibility
|
||||
} from "@mui/icons-material";
|
||||
import { Skeleton } from "@mui/material";
|
||||
// import Datadog from "assets/external/datadog.png";
|
||||
|
|
@ -47,7 +48,7 @@ import { Component, Device, deviceScope, Interaction } from "models";
|
|||
// import { isShareableLink } from "pbHelpers/Device";
|
||||
import { pond } from "protobuf-ts/pond";
|
||||
import { useGlobalState } from "providers";
|
||||
import React, { useState } from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
// import { useHistory, useRouteMatch } from "react-router";
|
||||
import { isOffline } from "utils/environment";
|
||||
import { or } from "utils/types";
|
||||
|
|
@ -70,6 +71,8 @@ import SaveDeviceProfile from "./SaveDeviceProfile";
|
|||
import Connection from "./Connection";
|
||||
import ComponentOrder from "component/ComponentOrder";
|
||||
import ArcGISDeviceData from "./ArcGISDeviceData";
|
||||
import UpgradeDevice from "./UpgradeDevice";
|
||||
import { DevicePageData } from "pages/Device";
|
||||
|
||||
const useStyles = makeStyles((_theme: Theme) => {
|
||||
// const isMobile = useMobile()
|
||||
|
|
@ -168,6 +171,19 @@ export default function DeviceActions(props: Props) {
|
|||
isJsonDataDialogOpen: false
|
||||
});
|
||||
|
||||
const [devicePageData, setDevicePageData] = useState<DevicePageData | undefined>(undefined);
|
||||
|
||||
useEffect(() => {
|
||||
let newPageData: DevicePageData = {
|
||||
device: device,
|
||||
components: components,
|
||||
interactions: interactions,
|
||||
preferences: preferences,
|
||||
permissions: permissions,
|
||||
}
|
||||
setDevicePageData(newPageData)
|
||||
}, [device, components, interactions, preferences, permissions])
|
||||
|
||||
const openDialog = (target: keyof DialogState) => {
|
||||
let updatedDialogState = cloneDeep(dialogState);
|
||||
updatedDialogState[target] = true;
|
||||
|
|
@ -494,10 +510,19 @@ export default function DeviceActions(props: Props) {
|
|||
);
|
||||
};
|
||||
|
||||
const showSupport = () => {
|
||||
return user.allowedTo("provision") && !location.pathname.includes("support")
|
||||
}
|
||||
|
||||
const buttons = () => {
|
||||
return (
|
||||
<React.Fragment>
|
||||
{/* {canWrite && user.allowedTo("provision") && <UpgradeDevice device={device} />} */}
|
||||
{showSupport() && <Tooltip title="Support">
|
||||
<IconButton onClick={() => navigate("support", { state: {device: device, devicePageData: devicePageData }})}>
|
||||
<Visibility />
|
||||
</IconButton>
|
||||
</Tooltip>}
|
||||
{canWrite && user.allowedTo("provision") && <UpgradeDevice device={device} />}
|
||||
{/* {!isShareableLink(match.params.deviceID) && ( */}
|
||||
<NotificationButton
|
||||
notify={preferences.notify}
|
||||
|
|
|
|||
321
src/device/UpgradeDevice.tsx
Normal file
|
|
@ -0,0 +1,321 @@
|
|||
import {
|
||||
Button,
|
||||
CircularProgress,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogContentText,
|
||||
DialogTitle,
|
||||
Divider,
|
||||
IconButton,
|
||||
LinearProgress,
|
||||
Link,
|
||||
Theme,
|
||||
Tooltip,
|
||||
Typography
|
||||
} from "@mui/material";
|
||||
// import { createStyles, makeStyles, Theme } from "@material-ui/core/styles";
|
||||
import UpdateIcon from "@mui/icons-material/Update";
|
||||
import { useDeviceAPI, useFirmwareAPI, usePrevious, useSnackbar } from "hooks";
|
||||
import { Device, latestFirmwareVersion, Upgrade } from "models";
|
||||
import moment from "moment";
|
||||
import { getContextKeys, getContextTypes } from "pbHelpers/Context";
|
||||
import { useGlobalState } from "providers";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { or } from "utils/types";
|
||||
import { makeStyles } from "@mui/styles";
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) => {
|
||||
return ({
|
||||
dialogContent: {
|
||||
marginTop: theme.spacing(2)
|
||||
},
|
||||
buttonProgress: {
|
||||
color: theme.palette.primary.main,
|
||||
position: "absolute",
|
||||
zIndex: 1
|
||||
},
|
||||
failed: {
|
||||
color: theme.palette.error.main
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
interface Props {
|
||||
device: Device;
|
||||
}
|
||||
|
||||
export default function UpgradeDevice(props: Props) {
|
||||
const classes = useStyles();
|
||||
const deviceAPI = useDeviceAPI();
|
||||
const { success, error } = useSnackbar();
|
||||
const firmwareAPI = useFirmwareAPI();
|
||||
const [{ firmware }] = useGlobalState();
|
||||
const prevFirmware = usePrevious(firmware);
|
||||
const { device } = props;
|
||||
const prevDevice = usePrevious(device);
|
||||
const [open, setOpen] = useState<boolean>(false);
|
||||
const prevOpen = usePrevious(open);
|
||||
const [upgrade, setUpgrade] = useState<Upgrade>(Upgrade.create());
|
||||
const [gettingStatus, setGettingStatus] = useState(0);
|
||||
|
||||
const progress = useCallback(() => {
|
||||
return upgrade.status.progress;
|
||||
}, [upgrade.status.progress]);
|
||||
|
||||
const currentFirmware = () => {
|
||||
return device.status.firmwareVersion;
|
||||
};
|
||||
|
||||
const latestFirmware = () => {
|
||||
return latestFirmwareVersion(
|
||||
firmware,
|
||||
device.settings.platform,
|
||||
device.settings.upgradeChannel
|
||||
);
|
||||
};
|
||||
|
||||
const upgradeVersion = () => {
|
||||
return upgrade.settings.version;
|
||||
};
|
||||
|
||||
const available = () => {
|
||||
return currentFirmware() !== latestFirmware();
|
||||
};
|
||||
|
||||
const upgrading = useCallback(() => {
|
||||
return progress() === "upgrading";
|
||||
}, [progress]);
|
||||
|
||||
const completed = () => {
|
||||
return progress() === "completed" && currentFirmware() === latestFirmware();
|
||||
};
|
||||
const failed = () => {
|
||||
return progress() === "failed" && upgradeVersion() === latestFirmware();
|
||||
};
|
||||
|
||||
const percent = () => {
|
||||
return Math.round((upgrade.status.offset / upgrade.status.size) * 100);
|
||||
};
|
||||
|
||||
const tooltip = () => {
|
||||
return upgrading()
|
||||
? "Upgrade in progress: " + percent() + "%"
|
||||
: failed()
|
||||
? "Upgrade failed"
|
||||
: "Upgrade Device";
|
||||
};
|
||||
|
||||
const upgradeTimingDesc = () => {
|
||||
let desc = "requested " + moment(or(upgrade.status.started, 0)).fromNow();
|
||||
if (completed()) {
|
||||
desc = "completed " + moment(or(upgrade.status.stopped, 0)).fromNow();
|
||||
} else if (failed()) {
|
||||
desc = "failed " + moment(or(upgrade.status.changed, 0)).fromNow();
|
||||
}
|
||||
|
||||
return desc;
|
||||
};
|
||||
|
||||
const mode = () => {
|
||||
let mode = "prompt";
|
||||
if (upgrading() || failed() || completed()) {
|
||||
mode = "progress";
|
||||
}
|
||||
return mode;
|
||||
};
|
||||
|
||||
const getStatus = useCallback(() => {
|
||||
if (gettingStatus === device.id() && !upgrading()) {
|
||||
return;
|
||||
}
|
||||
setGettingStatus(device.id());
|
||||
deviceAPI
|
||||
.getUpgradeStatus(device.id(), getContextKeys(), getContextTypes())
|
||||
.then((res: any) => {
|
||||
if (res && res.data && res.data.upgrade) {
|
||||
setUpgrade(Upgrade.any(res.data.upgrade));
|
||||
}
|
||||
})
|
||||
.catch((err: any) => error(err));
|
||||
}, [device, deviceAPI, error, gettingStatus, upgrading]);
|
||||
|
||||
useEffect(() => {
|
||||
const deviceIdUnchanged = !prevDevice || prevDevice.id() === device.id();
|
||||
if (
|
||||
(open && !prevOpen) ||
|
||||
firmware !== prevFirmware ||
|
||||
(device !== prevDevice && deviceIdUnchanged)
|
||||
) {
|
||||
getStatus();
|
||||
}
|
||||
}, [open, prevOpen, getStatus, device, prevDevice, firmware, prevFirmware]);
|
||||
|
||||
useEffect(() => {
|
||||
function getStatusAgain() {
|
||||
if (upgrading()) {
|
||||
getStatus();
|
||||
}
|
||||
}
|
||||
const interval = setInterval(() => getStatusAgain(), 5000);
|
||||
return () => {
|
||||
clearInterval(interval);
|
||||
};
|
||||
}, [open, getStatus, upgrading]);
|
||||
|
||||
const close = () => {
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
const handleUpgrade = () => {
|
||||
firmwareAPI
|
||||
.upgradeFirmware(device.id())
|
||||
.then(() => {
|
||||
success(device.name() + " will upgrade to " + latestFirmware() + " next time it checks in");
|
||||
getStatus();
|
||||
})
|
||||
.catch((err: any) => {
|
||||
console.error(err);
|
||||
error(err);
|
||||
});
|
||||
};
|
||||
|
||||
const restart = () => {
|
||||
firmwareAPI
|
||||
.upgradeFirmware(device.id())
|
||||
.then(() => {
|
||||
success(
|
||||
"Upgrade successfully restarted, " +
|
||||
device.name() +
|
||||
" will upgrade next time it checks in"
|
||||
);
|
||||
getStatus();
|
||||
})
|
||||
.catch((err: any) => {
|
||||
console.error(err);
|
||||
error(err);
|
||||
});
|
||||
};
|
||||
|
||||
const cancel = () => {
|
||||
firmwareAPI
|
||||
.cancelUpgrade(device.id())
|
||||
.then(() => {
|
||||
success("Upgrade cancelled");
|
||||
})
|
||||
.catch((err: any) => {
|
||||
console.error(err);
|
||||
error(err);
|
||||
});
|
||||
};
|
||||
|
||||
const button = () => {
|
||||
return (
|
||||
<Tooltip title={tooltip()}>
|
||||
<IconButton
|
||||
color="default"
|
||||
className={failed() ? classes.failed : undefined}
|
||||
onClick={() => setOpen(true)}
|
||||
aria-label={tooltip()}>
|
||||
<UpdateIcon />
|
||||
{upgrading() && (
|
||||
<CircularProgress
|
||||
// variant="static"
|
||||
value={percent()}
|
||||
size={"2rem"}
|
||||
className={classes.buttonProgress}
|
||||
/>
|
||||
)}
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{(available() || upgrading() || failed()) && button()}
|
||||
<Dialog fullWidth open={open} onClose={close} aria-labelledby="form-dialog-title">
|
||||
{mode() === "prompt" && (
|
||||
<React.Fragment>
|
||||
<DialogTitle id="form-dialog-title">
|
||||
Upgrade Device
|
||||
<Typography variant="body2" color="textSecondary">
|
||||
{device.name()}
|
||||
</Typography>
|
||||
</DialogTitle>
|
||||
<Divider />
|
||||
<DialogContent className={classes.dialogContent}>
|
||||
<DialogContentText id="alert-dialog-slide-description">
|
||||
Upgrading from {currentFirmware()} to {latestFirmware()}
|
||||
<br />
|
||||
<br />
|
||||
It is recommended that the device be plugged in during the upgrade as it will take
|
||||
several minutes to complete
|
||||
<br />
|
||||
<br />
|
||||
While the upgrade is in progress you can return to this dialog to view its status
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={close} color="primary">
|
||||
Close
|
||||
</Button>
|
||||
<Button onClick={handleUpgrade} color="primary">
|
||||
Upgrade
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</React.Fragment>
|
||||
)}
|
||||
{mode() === "progress" && (
|
||||
<React.Fragment>
|
||||
<DialogTitle id="form-dialog-title">
|
||||
Upgrading Device
|
||||
<Typography variant="body2" color="textSecondary">
|
||||
{device.name()}
|
||||
</Typography>
|
||||
</DialogTitle>
|
||||
<Divider />
|
||||
<DialogContent className={classes.dialogContent}>
|
||||
<DialogContentText id="alert-dialog-slide-description">
|
||||
Upgrade to {latestFirmware()} {upgradeTimingDesc()}
|
||||
</DialogContentText>
|
||||
<br />
|
||||
<LinearProgress variant="determinate" value={percent()} />
|
||||
<DialogContentText>
|
||||
{or(upgrade.status.offset, 0)} / {upgrade.status.size} bytes ({percent()}%)
|
||||
</DialogContentText>
|
||||
{failed() && (
|
||||
<React.Fragment>
|
||||
<DialogContentText>
|
||||
<br />
|
||||
Uh oh – something went wrong. You can try to{" "}
|
||||
<Link variant="body2" onClick={restart} style={{ cursor: "pointer" }}>
|
||||
restart
|
||||
</Link>{" "}
|
||||
the upgrade, if the problem persists we'll be alerted and follow up as soon as
|
||||
possible.
|
||||
</DialogContentText>
|
||||
</React.Fragment>
|
||||
)}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={close} color="primary">
|
||||
Close
|
||||
</Button>
|
||||
{upgrading() && (
|
||||
<Button onClick={cancel} style={{ color: "red" }}>
|
||||
Cancel
|
||||
</Button>
|
||||
)}
|
||||
{failed() && (
|
||||
<Button onClick={restart} color="primary">
|
||||
Restart
|
||||
</Button>
|
||||
)}
|
||||
</DialogActions>
|
||||
</React.Fragment>
|
||||
)}
|
||||
</Dialog>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
|
@ -22,6 +22,7 @@ import GateSVG from "./GateSVG";
|
|||
import GateGraphs from "./GateGraphs";
|
||||
//import { ToggleButton, ToggleButtonGroup } from "@material-ui/lab";
|
||||
import { getThemeType } from "theme";
|
||||
import ButtonGroup from "common/ButtonGroup";
|
||||
|
||||
interface Props {
|
||||
gate: Gate;
|
||||
|
|
@ -30,6 +31,8 @@ interface Props {
|
|||
drawerView?: boolean;
|
||||
}
|
||||
|
||||
|
||||
|
||||
export default function GateDevice(props: Props) {
|
||||
const { gate, comprehensiveDevice, linkedCompList, drawerView } = props;
|
||||
const [{as}] = useGlobalState();
|
||||
|
|
@ -233,6 +236,7 @@ export default function GateDevice(props: Props) {
|
|||
return (
|
||||
<Select
|
||||
id="ambientSelect"
|
||||
variant="standard"
|
||||
value={ambientKey}
|
||||
style={{ fontSize: 8 }}
|
||||
onChange={e => {
|
||||
|
|
@ -265,6 +269,7 @@ export default function GateDevice(props: Props) {
|
|||
return (
|
||||
<Select
|
||||
id="tempSelect"
|
||||
variant="standard"
|
||||
value={tempKey}
|
||||
style={{ fontSize: 8 }}
|
||||
onChange={e => {
|
||||
|
|
@ -299,6 +304,7 @@ export default function GateDevice(props: Props) {
|
|||
return (
|
||||
<Select
|
||||
id="pressureSelect"
|
||||
variant="standard"
|
||||
value={pressureKey}
|
||||
style={{ fontSize: 8 }}
|
||||
onChange={e => {
|
||||
|
|
@ -334,9 +340,10 @@ export default function GateDevice(props: Props) {
|
|||
container
|
||||
direction="row"
|
||||
alignContent="center"
|
||||
width="100%"
|
||||
//alignItems="center"
|
||||
>
|
||||
<Grid size={{ sm: 12, lg: isMobile || drawerView ? 12 : 8}} style={{ padding: 10 }}>
|
||||
<Grid size={{ sm: 12, lg: isMobile || drawerView ? 12 : 4}} style={{ padding: 10 }}>
|
||||
<Box
|
||||
paddingLeft={1}
|
||||
display="flex"
|
||||
|
|
@ -344,7 +351,20 @@ export default function GateDevice(props: Props) {
|
|||
alignItems="center"
|
||||
marginBottom={2}>
|
||||
<Typography style={{ fontWeight: 650, fontSize: 20 }}>{device.name()}</Typography>
|
||||
<ToggleButtonGroup value={detail} exclusive size="small" aria-label="detail">
|
||||
<ButtonGroup
|
||||
toggle
|
||||
buttons={[
|
||||
{
|
||||
title: "Analysis",
|
||||
function: () => setDetail("analytics")
|
||||
},
|
||||
{
|
||||
title: "Sensors",
|
||||
function: () => setDetail("sensors")
|
||||
}
|
||||
]}
|
||||
/>
|
||||
{/* <ToggleButtonGroup value={detail} exclusive size="small" aria-label="detail">
|
||||
<ToggleButton
|
||||
onClick={() => setDetail("analytics")}
|
||||
value={"analytics"}
|
||||
|
|
@ -357,7 +377,7 @@ export default function GateDevice(props: Props) {
|
|||
aria-label="Sensor Graphs">
|
||||
Sensors
|
||||
</ToggleButton>
|
||||
</ToggleButtonGroup>
|
||||
</ToggleButtonGroup> */}
|
||||
</Box>
|
||||
<Card raised>
|
||||
<GateSVG
|
||||
|
|
|
|||
|
|
@ -355,13 +355,13 @@ export default function GateSVG(props: Props) {
|
|||
{renderValues()}
|
||||
</g>
|
||||
<foreignObject height={20} width={120} x={10} y={83}>
|
||||
<Box style={{ position: "fixed" }}>{ambientSelector()}</Box>
|
||||
<Box sx={{ position: "fixed" }}>{ambientSelector()}</Box>
|
||||
</foreignObject>
|
||||
<foreignObject height={20} width={120} x={10} y={118}>
|
||||
<Box style={{ position: "fixed" }}>{tempChainSelector()}</Box>
|
||||
<Box sx={{ position: "fixed" }}>{tempChainSelector()}</Box>
|
||||
</foreignObject>
|
||||
<foreignObject height={20} width={120} x={10} y={153}>
|
||||
<Box style={{ position: "fixed" }}>{pressureChainSelector()}</Box>
|
||||
<Box sx={{ position: "fixed" }}>{pressureChainSelector()}</Box>
|
||||
</foreignObject>
|
||||
{pcaRed()}
|
||||
{pcaGreen()}
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ import { GeometryMapping, shapeFromCoords } from "models/GeometryMapping";
|
|||
import { FeatureCollection, Feature } from "geojson";
|
||||
import DrawController from "./mapControllers/drawController";
|
||||
//import { Geometry } from "geojson";
|
||||
import Map, { MapRef, Marker, MarkerDragEvent } from "react-map-gl/mapbox-legacy";
|
||||
import Map, { MapRef, Marker, MarkerDragEvent } from "react-map-gl/mapbox";
|
||||
import { getDistanceUnit } from "utils";
|
||||
import { MapMouseEvent } from "mapbox-gl";
|
||||
import { makeStyles } from "@mui/styles";
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { ControlPosition, useMap } from "react-map-gl/mapbox-legacy";
|
||||
import { ControlPosition, useMap } from "react-map-gl/mapbox";
|
||||
import MapboxGeocoder, { Result } from "@mapbox/mapbox-gl-geocoder";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { useEffect, useRef } from "react";
|
||||
import { useMap } from "react-map-gl/mapbox-legacy";
|
||||
import { useMap } from "react-map-gl/mapbox";
|
||||
import { FeatureCollection } from "geojson";
|
||||
import MapboxDraw from "@mapbox/mapbox-gl-draw";
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import React, { useEffect } from "react";
|
||||
import { Layer, Source } from "react-map-gl/mapbox-legacy";
|
||||
import { Layer, Source } from "react-map-gl/mapbox";
|
||||
import { FeatureCollection } from "geojson";
|
||||
|
||||
interface Props {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { useMobile } from "hooks";
|
|||
import { clone } from "lodash";
|
||||
import { pond } from "protobuf-ts/pond";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { Marker } from "react-map-gl/mapbox-legacy";
|
||||
import { Marker } from "react-map-gl/mapbox";
|
||||
|
||||
//interface for markers
|
||||
export interface MarkerData {
|
||||
|
|
|
|||
|
|
@ -703,7 +703,12 @@ import { Result } from "@mapbox/mapbox-gl-geocoder";
|
|||
</Grid>
|
||||
</Grid>
|
||||
<Grid size={3}>
|
||||
<Button style={{ margin: "auto" }} onClick={() => setOpenFMSettings(true)}>
|
||||
<Button
|
||||
style={{ margin: "auto" }}
|
||||
onClick={() => {
|
||||
setOpenFMSettings(true)
|
||||
setAnchorFieldMarker(null)
|
||||
}}>
|
||||
<Edit />
|
||||
</Button>
|
||||
</Grid>
|
||||
|
|
|
|||
32
src/models/Upgrade.ts
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
import { pond } from "protobuf-ts/pond";
|
||||
import { or } from "utils/types";
|
||||
import { cloneDeep } from "lodash";
|
||||
|
||||
export class Upgrade {
|
||||
public settings: pond.UpgradeSettings = pond.UpgradeSettings.create();
|
||||
public status: pond.UpgradeStatus = pond.UpgradeStatus.create();
|
||||
|
||||
public static create(pb?: pond.Upgrade): Upgrade {
|
||||
let my = new Upgrade();
|
||||
if (pb) {
|
||||
my.settings = pond.UpgradeSettings.fromObject(or(pb.settings, {}));
|
||||
my.status = pond.UpgradeStatus.fromObject(or(pb.status, {}));
|
||||
}
|
||||
return my;
|
||||
}
|
||||
|
||||
public static any(data: any): Upgrade {
|
||||
return Upgrade.create(pond.Upgrade.fromObject(cloneDeep(data)));
|
||||
}
|
||||
|
||||
public static clone(other: Upgrade): Upgrade {
|
||||
let my = new Upgrade();
|
||||
my.settings = pond.UpgradeSettings.fromObject(cloneDeep(other.settings));
|
||||
my.status = pond.UpgradeStatus.fromObject(cloneDeep(other.status));
|
||||
return my;
|
||||
}
|
||||
|
||||
public key(): string {
|
||||
return this.settings.key;
|
||||
}
|
||||
}
|
||||
|
|
@ -11,7 +11,7 @@ export * from "./ShareableLink";
|
|||
export * from "./Site";
|
||||
export * from "./Tag";
|
||||
export * from "./Task";
|
||||
// export * from "./Upgrade";
|
||||
export * from "./Upgrade";
|
||||
// export * from "./User";
|
||||
export * from "./user";
|
||||
export * from "./Field";
|
||||
|
|
|
|||
|
|
@ -105,7 +105,7 @@ export default function BottomNavigator(props: Props) {
|
|||
return (
|
||||
<BottomNavigation value={route} onChange={(_, newValue) => handleRouteChange(newValue)}>
|
||||
{isAdaptive && (
|
||||
<BottomNavigationAction label="Farm" icon={<FieldsIcon type={getType()} />} value="fields" />
|
||||
<BottomNavigationAction label="Farm" icon={<FieldsIcon type={getType()} />} value="visualFarm" />
|
||||
)}
|
||||
{isAdaptive && (
|
||||
<BottomNavigationAction label="Bins" icon={<BinsIcon type={getType()} />} value="bins" />
|
||||
|
|
@ -114,7 +114,7 @@ export default function BottomNavigator(props: Props) {
|
|||
<BottomNavigationAction
|
||||
label="Site Map"
|
||||
icon={<FieldsIcon type={getType()} />}
|
||||
value="constructionsiteMap"
|
||||
value="constructionMap"
|
||||
/>
|
||||
)}
|
||||
{isOmni && (
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import { ErrorBoundary } from "react-error-boundary";
|
|||
import { getWhitelabel } from "services/whiteLabel";
|
||||
import Ventilation from "pages/VentEditor";
|
||||
import { useGlobalState } from "providers";
|
||||
import DeviceSupport from "pages/DeviceSupport";
|
||||
|
||||
const DeviceHistory = lazy(() => import("pages/DeviceHistory"));
|
||||
const DevicePage = lazy(() => import("pages/Device"));
|
||||
|
|
@ -103,6 +104,10 @@ export default function Router() {
|
|||
path="/:deviceID" // "/settings/basic"
|
||||
element={<DevicePage />}
|
||||
/>
|
||||
<Route
|
||||
path="/:deviceID/support" // "/settings/basic"
|
||||
element={<DeviceSupport />}
|
||||
/>
|
||||
<Route
|
||||
path="/:deviceID/components/:componentID" // "/settings/basic"
|
||||
element={<DeviceComponent />}
|
||||
|
|
|
|||
|
|
@ -160,84 +160,6 @@ export default function SideNavigator(props: Props) {
|
|||
const isAdCon = IsAdCon()
|
||||
return (
|
||||
<List className={classes.list} component="nav">
|
||||
{(isAg || user.hasFeature("admin")) && (
|
||||
<Tooltip title="Bins" placement="right">
|
||||
<ListItemButton
|
||||
id="tour-dashboard"
|
||||
onClick={() => goTo("/bins")}
|
||||
classes={getClasses("/bin")}
|
||||
>
|
||||
<ListItemIcon>
|
||||
<BinsIcon height={"26px"}/>
|
||||
</ListItemIcon>
|
||||
{open && <ListItemText primary="Bins" />}
|
||||
</ListItemButton>
|
||||
</Tooltip>
|
||||
)}
|
||||
{(isMiPCA || user.hasFeature("admin")) && (
|
||||
<Tooltip title="Terminals" placement="right">
|
||||
<ListItemButton
|
||||
id="tour-terminals"
|
||||
onClick={() => goTo("/terminals")}
|
||||
classes={getClasses("/terminal")}
|
||||
>
|
||||
<ListItemIcon>
|
||||
<PlaneIcon />
|
||||
</ListItemIcon>
|
||||
{open && <ListItemText primary="Terminals" />}
|
||||
</ListItemButton>
|
||||
</Tooltip>
|
||||
)}
|
||||
<Tooltip title="Devices" placement="right">
|
||||
<ListItemButton
|
||||
id="tour-dashboard"
|
||||
onClick={() => goTo("/devices")}
|
||||
classes={getClasses("/device")}
|
||||
>
|
||||
<ListItemIcon>
|
||||
<BindaptIcon />
|
||||
</ListItemIcon>
|
||||
{open && <ListItemText primary="Devices" />}
|
||||
</ListItemButton>
|
||||
</Tooltip>
|
||||
{(isMiVent || user.hasFeature("admin")) && (
|
||||
<Tooltip title="Mines" placement="right">
|
||||
<ListItemButton
|
||||
id="tour-dashboard"
|
||||
onClick={() => goTo("/mines")}
|
||||
classes={getClasses("/mine")}
|
||||
>
|
||||
<ListItemIcon>
|
||||
<MiningIcon height={"26px"} width={"26px"} />
|
||||
</ListItemIcon>
|
||||
{open && <ListItemText primary="Mines" />}
|
||||
</ListItemButton>
|
||||
</Tooltip>
|
||||
)}
|
||||
<Tooltip title="Teams" placement="right">
|
||||
<ListItemButton
|
||||
id="tour-dashboard"
|
||||
onClick={() => goTo("/teams")}
|
||||
classes={getClasses("/team")}
|
||||
>
|
||||
<ListItemIcon>
|
||||
<People style={{ color: getThemeType() === "light" ? "black" : undefined }}/>
|
||||
</ListItemIcon>
|
||||
{open && <ListItemText primary="Teams" />}
|
||||
</ListItemButton>
|
||||
</Tooltip>
|
||||
<Tooltip title="Users" placement="right">
|
||||
<ListItemButton
|
||||
id="tour-dashboard"
|
||||
onClick={() => goTo("/users")}
|
||||
classes={getClasses("/user")}
|
||||
>
|
||||
<ListItemIcon>
|
||||
<Person style={{ color: getThemeType() === "light" ? "black" : undefined }}/>
|
||||
</ListItemIcon>
|
||||
{open && <ListItemText primary="Users" />}
|
||||
</ListItemButton>
|
||||
</Tooltip>
|
||||
{(isAg || user.hasFeature("admin")) && (
|
||||
<Tooltip title="Visual Farm" placement="right">
|
||||
<ListItemButton
|
||||
|
|
@ -252,20 +174,6 @@ export default function SideNavigator(props: Props) {
|
|||
</ListItemButton>
|
||||
</Tooltip>
|
||||
)}
|
||||
{(isAg || user.hasFeature("admin")) && (
|
||||
<Tooltip title="My Fields" placement="right">
|
||||
<ListItemButton
|
||||
id="tour-field-list"
|
||||
onClick={() => goTo("/fields")}
|
||||
classes={getClasses("/fields")}
|
||||
>
|
||||
<ListItemIcon>
|
||||
<FieldListIcon />
|
||||
</ListItemIcon>
|
||||
{open && <ListItemText primary="My Fields" />}
|
||||
</ListItemButton>
|
||||
</Tooltip>
|
||||
)}
|
||||
{(isMiPCA || user.hasFeature("admin")) && (
|
||||
<Tooltip title="Aviation Map" placement="right">
|
||||
<ListItemButton
|
||||
|
|
@ -294,6 +202,169 @@ export default function SideNavigator(props: Props) {
|
|||
</ListItemButton>
|
||||
</Tooltip>
|
||||
)}
|
||||
{(isAg || user.hasFeature("admin")) && (
|
||||
<Tooltip title="Contracts" placement="right">
|
||||
<ListItemButton
|
||||
id="tour-contracts"
|
||||
onClick={() => goTo("/contracts")}
|
||||
classes={getClasses("/contracts")}
|
||||
>
|
||||
<ListItemIcon>
|
||||
<ContractsIcon />
|
||||
</ListItemIcon>
|
||||
{open && <ListItemText primary="Contracts" />}
|
||||
</ListItemButton>
|
||||
</Tooltip>
|
||||
)}
|
||||
{(isAg || user.hasFeature("admin")) && (
|
||||
<Tooltip title="Bins" placement="right">
|
||||
<ListItemButton
|
||||
id="tour-dashboard"
|
||||
onClick={() => goTo("/bins")}
|
||||
classes={getClasses("/bin")}
|
||||
>
|
||||
<ListItemIcon>
|
||||
<BinsIcon height={"26px"}/>
|
||||
</ListItemIcon>
|
||||
{open && <ListItemText primary="Bins" />}
|
||||
</ListItemButton>
|
||||
</Tooltip>
|
||||
)}
|
||||
{(isMiPCA || user.hasFeature("admin")) && (
|
||||
<Tooltip title="Terminals" placement="right">
|
||||
<ListItemButton
|
||||
id="tour-terminals"
|
||||
onClick={() => goTo("/terminals")}
|
||||
classes={getClasses("/terminal")}
|
||||
>
|
||||
<ListItemIcon>
|
||||
<PlaneIcon />
|
||||
</ListItemIcon>
|
||||
{open && <ListItemText primary="Terminals" />}
|
||||
</ListItemButton>
|
||||
</Tooltip>
|
||||
)}
|
||||
{(user.hasFeature("installer") && isAg || user.hasFeature("admin")) &&
|
||||
<Tooltip title="Cable Estimator" placement="right">
|
||||
<ListItemButton
|
||||
id="tour-cable-estimator"
|
||||
onClick={() => goTo("/cableEstimate")}
|
||||
classes={getClasses("/cableEstimate")}
|
||||
>
|
||||
<ListItemIcon>
|
||||
<CableIcon />
|
||||
</ListItemIcon>
|
||||
{open && <ListItemText primary="Cable Estimator" />}
|
||||
</ListItemButton>
|
||||
</Tooltip>
|
||||
}
|
||||
{(isAg || user.hasFeature("admin")) && (
|
||||
<Tooltip title="Transactions" placement="right">
|
||||
<ListItemButton
|
||||
id="tour-transactions"
|
||||
onClick={() => goTo("/transactions")}
|
||||
classes={getClasses("/transactions")}
|
||||
>
|
||||
<ListItemIcon>
|
||||
<SyncAlt />
|
||||
</ListItemIcon>
|
||||
{open && <ListItemText primary="Transactions" />}
|
||||
</ListItemButton>
|
||||
</Tooltip>
|
||||
)}
|
||||
{(isMiVent || user.hasFeature("admin")) && (
|
||||
<Tooltip title="Mines" placement="right">
|
||||
<ListItemButton
|
||||
id="tour-dashboard"
|
||||
onClick={() => goTo("/mines")}
|
||||
classes={getClasses("/mine")}
|
||||
>
|
||||
<ListItemIcon>
|
||||
<MiningIcon height={"26px"} width={"26px"} />
|
||||
</ListItemIcon>
|
||||
{open && <ListItemText primary="Mines" />}
|
||||
</ListItemButton>
|
||||
</Tooltip>
|
||||
)}
|
||||
<Tooltip title="Devices" placement="right">
|
||||
<ListItemButton
|
||||
id="tour-dashboard"
|
||||
onClick={() => goTo("/devices")}
|
||||
classes={getClasses("/device")}
|
||||
>
|
||||
<ListItemIcon>
|
||||
<BindaptIcon />
|
||||
</ListItemIcon>
|
||||
{open && <ListItemText primary="Devices" />}
|
||||
</ListItemButton>
|
||||
</Tooltip>
|
||||
{user.hasFeature("admin") &&
|
||||
<Tooltip title="Firmware" placement="right">
|
||||
<ListItemButton
|
||||
id="tour-firmware"
|
||||
onClick={() => goTo("/firmware")}
|
||||
classes={getClasses("/firmware")}
|
||||
>
|
||||
<ListItemIcon>
|
||||
<Memory />
|
||||
</ListItemIcon>
|
||||
{open && <ListItemText primary="Logs" />}
|
||||
</ListItemButton>
|
||||
</Tooltip>
|
||||
}
|
||||
{user.hasFeature("admin") &&
|
||||
<Tooltip title="Logs" placement="right">
|
||||
<ListItemButton
|
||||
id="tour-logs"
|
||||
onClick={() => goTo("/logs")}
|
||||
classes={getClasses("/logs")}
|
||||
>
|
||||
<ListItemIcon>
|
||||
<DataDuckIcon />
|
||||
</ListItemIcon>
|
||||
{open && <ListItemText primary="Logs" />}
|
||||
</ListItemButton>
|
||||
</Tooltip>
|
||||
}
|
||||
<Tooltip title="Teams" placement="right">
|
||||
<ListItemButton
|
||||
id="tour-dashboard"
|
||||
onClick={() => goTo("/teams")}
|
||||
classes={getClasses("/team")}
|
||||
>
|
||||
<ListItemIcon>
|
||||
<People style={{ color: getThemeType() === "light" ? "black" : undefined }}/>
|
||||
</ListItemIcon>
|
||||
{open && <ListItemText primary="Teams" />}
|
||||
</ListItemButton>
|
||||
</Tooltip>
|
||||
<Tooltip title="Users" placement="right">
|
||||
<ListItemButton
|
||||
id="tour-dashboard"
|
||||
onClick={() => goTo("/users")}
|
||||
classes={getClasses("/user")}
|
||||
>
|
||||
<ListItemIcon>
|
||||
<Person style={{ color: getThemeType() === "light" ? "black" : undefined }}/>
|
||||
</ListItemIcon>
|
||||
{open && <ListItemText primary="Users" />}
|
||||
</ListItemButton>
|
||||
</Tooltip>
|
||||
{(isAg || user.hasFeature("admin")) && (
|
||||
<Tooltip title="My Fields" placement="right">
|
||||
<ListItemButton
|
||||
id="tour-field-list"
|
||||
onClick={() => goTo("/fields")}
|
||||
classes={getClasses("/fields")}
|
||||
>
|
||||
<ListItemIcon>
|
||||
<FieldListIcon />
|
||||
</ListItemIcon>
|
||||
{open && <ListItemText primary="My Fields" />}
|
||||
</ListItemButton>
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
{(isAdCon || user.hasFeature("admin")) && (
|
||||
<Tooltip title="Jobsites" placement="right">
|
||||
<ListItemButton
|
||||
|
|
@ -334,48 +405,7 @@ export default function SideNavigator(props: Props) {
|
|||
{open && <ListItemText primary="Tasks" />}
|
||||
</ListItemButton>
|
||||
</Tooltip>
|
||||
{(isAg || user.hasFeature("admin")) && (
|
||||
<Tooltip title="Transactions" placement="right">
|
||||
<ListItemButton
|
||||
id="tour-transactions"
|
||||
onClick={() => goTo("/transactions")}
|
||||
classes={getClasses("/transactions")}
|
||||
>
|
||||
<ListItemIcon>
|
||||
<SyncAlt />
|
||||
</ListItemIcon>
|
||||
{open && <ListItemText primary="Transactions" />}
|
||||
</ListItemButton>
|
||||
</Tooltip>
|
||||
)}
|
||||
{(isAg || user.hasFeature("admin")) && (
|
||||
<Tooltip title="Contracts" placement="right">
|
||||
<ListItemButton
|
||||
id="tour-contracts"
|
||||
onClick={() => goTo("/contracts")}
|
||||
classes={getClasses("/contracts")}
|
||||
>
|
||||
<ListItemIcon>
|
||||
<ContractsIcon />
|
||||
</ListItemIcon>
|
||||
{open && <ListItemText primary="Contracts" />}
|
||||
</ListItemButton>
|
||||
</Tooltip>
|
||||
)}
|
||||
{(user.hasFeature("installer") && isAg || user.hasFeature("admin")) &&
|
||||
<Tooltip title="Cable Estimator" placement="right">
|
||||
<ListItemButton
|
||||
id="tour-cable-estimator"
|
||||
onClick={() => goTo("/cableEstimate")}
|
||||
classes={getClasses("/cableEstimate")}
|
||||
>
|
||||
<ListItemIcon>
|
||||
<CableIcon />
|
||||
</ListItemIcon>
|
||||
{open && <ListItemText primary="Cable Estimator" />}
|
||||
</ListItemButton>
|
||||
</Tooltip>
|
||||
}
|
||||
|
||||
{user.hasFeature("developer") &&
|
||||
<Tooltip title="API documentation" placement="right">
|
||||
<ListItemButton
|
||||
|
|
@ -390,34 +420,6 @@ export default function SideNavigator(props: Props) {
|
|||
</ListItemButton>
|
||||
</Tooltip>
|
||||
}
|
||||
{user.hasFeature("admin") &&
|
||||
<Tooltip title="Firmware" placement="right">
|
||||
<ListItemButton
|
||||
id="tour-firmware"
|
||||
onClick={() => goTo("/firmware")}
|
||||
classes={getClasses("/firmware")}
|
||||
>
|
||||
<ListItemIcon>
|
||||
<Memory />
|
||||
</ListItemIcon>
|
||||
{open && <ListItemText primary="Logs" />}
|
||||
</ListItemButton>
|
||||
</Tooltip>
|
||||
}
|
||||
{user.hasFeature("admin") &&
|
||||
<Tooltip title="Logs" placement="right">
|
||||
<ListItemButton
|
||||
id="tour-logs"
|
||||
onClick={() => goTo("/logs")}
|
||||
classes={getClasses("/logs")}
|
||||
>
|
||||
<ListItemIcon>
|
||||
<DataDuckIcon />
|
||||
</ListItemIcon>
|
||||
{open && <ListItemText primary="Logs" />}
|
||||
</ListItemButton>
|
||||
</Tooltip>
|
||||
}
|
||||
{user.hasFeature("admin") && window.NDEFReader &&
|
||||
<Tooltip title="NFC" placement="right">
|
||||
<ListItemButton
|
||||
|
|
|
|||
|
|
@ -66,6 +66,7 @@ import { makeStyles, styled } from "@mui/styles";
|
|||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { Controller } from "models/Controller";
|
||||
import TaskViewer from "tasks/TaskViewer";
|
||||
import ButtonGroup from "common/ButtonGroup";
|
||||
|
||||
interface TabPanelProps {
|
||||
children?: React.ReactNode;
|
||||
|
|
@ -214,54 +215,54 @@ export default function Bin(props: Props) {
|
|||
setValue(newValue);
|
||||
};
|
||||
|
||||
const StyledToggle = styled(ToggleButton)(() => ({
|
||||
root: {
|
||||
backgroundColor: "transparent",
|
||||
overflow: "visible",
|
||||
content: "content-box",
|
||||
"&$selected": {
|
||||
backgroundColor: "gold",
|
||||
color: "black",
|
||||
borderRadius: 24,
|
||||
fontWeight: "bold"
|
||||
},
|
||||
"&$selected:hover": {
|
||||
backgroundColor: "rgb(255, 255, 0)",
|
||||
color: "black",
|
||||
borderRadius: 24
|
||||
}
|
||||
},
|
||||
selected: {}
|
||||
}))
|
||||
// const StyledToggle = styled(ToggleButton)(() => ({
|
||||
// root: {
|
||||
// backgroundColor: "transparent",
|
||||
// overflow: "visible",
|
||||
// content: "content-box",
|
||||
// "&$selected": {
|
||||
// backgroundColor: "gold",
|
||||
// color: "black",
|
||||
// borderRadius: 24,
|
||||
// fontWeight: "bold"
|
||||
// },
|
||||
// "&$selected:hover": {
|
||||
// backgroundColor: "rgb(255, 255, 0)",
|
||||
// color: "black",
|
||||
// borderRadius: 24
|
||||
// }
|
||||
// },
|
||||
// selected: {}
|
||||
// }))
|
||||
|
||||
const StyledToggleButtonGroup = styled(ToggleButtonGroup)(({ theme }: { theme: Theme }) => ({
|
||||
grouped: {
|
||||
margin: theme.spacing(-0.5),
|
||||
border: "none",
|
||||
padding: theme.spacing(1),
|
||||
"&:not(:first-child):not(:last-child)": {
|
||||
borderRadius: 24,
|
||||
marginRight: theme.spacing(0.5),
|
||||
marginLeft: theme.spacing(0.5)
|
||||
},
|
||||
"&:first-child": {
|
||||
borderRadius: 24,
|
||||
marginLeft: theme.spacing(0.25)
|
||||
},
|
||||
"&:last-child": {
|
||||
borderRadius: 24,
|
||||
marginRight: theme.spacing(0.25)
|
||||
}
|
||||
},
|
||||
root: {
|
||||
backgroundColor: darken(
|
||||
theme.palette.background.paper,
|
||||
getThemeType() === "light" ? 0.05 : 0.25
|
||||
),
|
||||
borderRadius: 24,
|
||||
content: "border-box"
|
||||
}
|
||||
}))
|
||||
// const StyledToggleButtonGroup = styled(ToggleButtonGroup)(({ theme }: { theme: Theme }) => ({
|
||||
// grouped: {
|
||||
// margin: theme.spacing(-0.5),
|
||||
// border: "none",
|
||||
// padding: theme.spacing(1),
|
||||
// "&:not(:first-child):not(:last-child)": {
|
||||
// borderRadius: 24,
|
||||
// marginRight: theme.spacing(0.5),
|
||||
// marginLeft: theme.spacing(0.5)
|
||||
// },
|
||||
// "&:first-child": {
|
||||
// borderRadius: 24,
|
||||
// marginLeft: theme.spacing(0.25)
|
||||
// },
|
||||
// "&:last-child": {
|
||||
// borderRadius: 24,
|
||||
// marginRight: theme.spacing(0.25)
|
||||
// }
|
||||
// },
|
||||
// root: {
|
||||
// backgroundColor: darken(
|
||||
// theme.palette.background.paper,
|
||||
// getThemeType() === "light" ? 0.05 : 0.25
|
||||
// ),
|
||||
// borderRadius: 24,
|
||||
// content: "border-box"
|
||||
// }
|
||||
// }))
|
||||
|
||||
const load = useCallback(() => {
|
||||
if (loadRef.current || user.id() === "") return;
|
||||
|
|
@ -547,7 +548,6 @@ export default function Bin(props: Props) {
|
|||
};
|
||||
|
||||
const tasks = () => {
|
||||
console.log(showTasks())
|
||||
if (showTasks()) {
|
||||
return (
|
||||
<React.Fragment>
|
||||
|
|
@ -816,9 +816,9 @@ export default function Bin(props: Props) {
|
|||
|
||||
const desktopView = () => {
|
||||
return (
|
||||
<Card style={{ padding: 15 }}>
|
||||
<Card sx={{ padding: 1 }}>
|
||||
<Grid container spacing={2} direction={"row"} justifyContent="center" alignItems="stretch">
|
||||
<Grid size={{ xs: 12, sm: 12, md: 6, lg: 3 }}>
|
||||
<Grid size={{ xs: 12, sm: 12, md: 12, lg: 4 }}>
|
||||
<Box
|
||||
paddingX={1}
|
||||
marginBottom={1}
|
||||
|
|
@ -844,7 +844,7 @@ export default function Bin(props: Props) {
|
|||
{overview()}
|
||||
{preferences && binComponents(preferences)}
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12, sm: 12, md: 6, lg: 3 }}>
|
||||
<Grid size={{ xs: 12, sm: 12, md: 12, lg: 2.6 }}>
|
||||
{(bin.settings.mode === pond.BinMode.BIN_MODE_DRYING ||
|
||||
bin.settings.mode === pond.BinMode.BIN_MODE_HYDRATING) && (
|
||||
<BinConditioningCard
|
||||
|
|
@ -862,9 +862,34 @@ export default function Bin(props: Props) {
|
|||
<BinStorageConditions bin={bin} headspaceCO2={headspaceCO2} cables={grainCables} />
|
||||
</Box>
|
||||
</Grid>
|
||||
<Grid id="tour-graphs" size={{ xs: 12, sm: 12, md: 12, lg: 6 }}>
|
||||
<Box paddingBottom={2}>
|
||||
<StyledToggleButtonGroup
|
||||
<Grid id="tour-graphs" size={{ xs: 12, sm: 12, md: 12, lg: 5.3 }}>
|
||||
<Box paddingBottom={2} display="flex">
|
||||
<ButtonGroup
|
||||
toggle
|
||||
buttons={[
|
||||
{
|
||||
title: "Inventory",
|
||||
function: () => setDetail("inventory")
|
||||
},
|
||||
{
|
||||
title: "Sensors",
|
||||
function: () => setDetail("sensors")
|
||||
},
|
||||
{
|
||||
title: "Analysis",
|
||||
function: () => setDetail("analytics")
|
||||
},
|
||||
{
|
||||
title: "Alerts",
|
||||
function: () => setDetail("alerts")
|
||||
},
|
||||
{
|
||||
title: "Presets",
|
||||
function: () => setDetail("presets")
|
||||
}
|
||||
]}
|
||||
/>
|
||||
{/* <StyledToggleButtonGroup
|
||||
id="tour-graph-tabs"
|
||||
value={detail}
|
||||
exclusive
|
||||
|
|
@ -900,7 +925,7 @@ export default function Bin(props: Props) {
|
|||
aria-label="Bin Mode Presets">
|
||||
Presets
|
||||
</StyledToggle>
|
||||
</StyledToggleButtonGroup>
|
||||
</StyledToggleButtonGroup> */}
|
||||
</Box>
|
||||
{detail === "alerts" && (
|
||||
<Box>
|
||||
|
|
@ -1089,11 +1114,12 @@ export default function Bin(props: Props) {
|
|||
};
|
||||
|
||||
return (
|
||||
<PageContainer>
|
||||
<PageContainer padding={1}>
|
||||
{showPlenumError()}
|
||||
<Box
|
||||
paddingBottom={isMobile || displayMobile ? 1 : 2}
|
||||
paddingX={isMobile || displayMobile ? 1 : 2}>
|
||||
paddingX={1}
|
||||
>
|
||||
{tabs()}
|
||||
{isMobile && (
|
||||
<Box
|
||||
|
|
|
|||
|
|
@ -81,6 +81,7 @@ import { getThemeType } from "theme/themeType";
|
|||
import GrainBagSettings from "grainBag/grainBagSettings";
|
||||
import GrainBagList from "grainBag/grainBagList";
|
||||
import BinYards from "bin/BinYards";
|
||||
import ButtonGroup from "common/ButtonGroup";
|
||||
// import { useHistory } from "react-router";
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) => {
|
||||
|
|
@ -200,56 +201,56 @@ export default function Bins(props: Props) {
|
|||
// fertilizer: 0
|
||||
// });
|
||||
|
||||
const StyledToggleButtonGroup = styled(ToggleButtonGroup)(({ theme }: { theme: Theme }) => ({
|
||||
'& .MuiToggleButtonGroup-grouped': {
|
||||
// border: 'none' replaces the old 'grouped' border: "none"
|
||||
border: 'none',
|
||||
padding: theme.spacing(1),
|
||||
// Middle buttons
|
||||
'&:not(:first-child):not(:last-child)': {
|
||||
borderRadius: 24,
|
||||
marginRight: theme.spacing(0.5),
|
||||
marginLeft: theme.spacing(0.5),
|
||||
},
|
||||
// First button
|
||||
'&:first-child': {
|
||||
borderRadius: 24,
|
||||
marginLeft: theme.spacing(0.25),
|
||||
},
|
||||
// Last button
|
||||
'&:last-child': {
|
||||
borderRadius: 24,
|
||||
marginRight: theme.spacing(0.25),
|
||||
},
|
||||
},
|
||||
// Root styles
|
||||
backgroundColor: darken(
|
||||
theme.palette.background.paper,
|
||||
getThemeType() === 'light' ? 0.05 : 0.25
|
||||
),
|
||||
borderRadius: 24,
|
||||
boxSizing: 'content-box', // Fixed 'content: "border-box"' typo from original
|
||||
}));
|
||||
// const StyledToggleButtonGroup = styled(ToggleButtonGroup)(({ theme }: { theme: Theme }) => ({
|
||||
// '& .MuiToggleButtonGroup-grouped': {
|
||||
// // border: 'none' replaces the old 'grouped' border: "none"
|
||||
// border: 'none',
|
||||
// padding: theme.spacing(1),
|
||||
// // Middle buttons
|
||||
// '&:not(:first-child):not(:last-child)': {
|
||||
// borderRadius: 24,
|
||||
// marginRight: theme.spacing(0.5),
|
||||
// marginLeft: theme.spacing(0.5),
|
||||
// },
|
||||
// // First button
|
||||
// '&:first-child': {
|
||||
// borderRadius: 24,
|
||||
// marginLeft: theme.spacing(0.25),
|
||||
// },
|
||||
// // Last button
|
||||
// '&:last-child': {
|
||||
// borderRadius: 24,
|
||||
// marginRight: theme.spacing(0.25),
|
||||
// },
|
||||
// },
|
||||
// // Root styles
|
||||
// backgroundColor: darken(
|
||||
// theme.palette.background.paper,
|
||||
// getThemeType() === 'light' ? 0.05 : 0.25
|
||||
// ),
|
||||
// borderRadius: 24,
|
||||
// boxSizing: 'content-box', // Fixed 'content: "border-box"' typo from original
|
||||
// }));
|
||||
|
||||
const StyledToggle = styled(ToggleButton)(({ theme }: { theme: Theme }) => ({
|
||||
root: {
|
||||
backgroundColor: "transparent",
|
||||
overflow: "visible",
|
||||
content: "content-box",
|
||||
"&$selected": {
|
||||
backgroundColor: "gold",
|
||||
color: "black",
|
||||
borderRadius: 24,
|
||||
fontWeight: "bold"
|
||||
},
|
||||
"&$selected:hover": {
|
||||
backgroundColor: "rgb(255, 255, 0)",
|
||||
color: "black",
|
||||
borderRadius: 24
|
||||
}
|
||||
},
|
||||
selected: {}
|
||||
}));
|
||||
// const StyledToggle = styled(ToggleButton)(({ theme }: { theme: Theme }) => ({
|
||||
// root: {
|
||||
// backgroundColor: "transparent",
|
||||
// overflow: "visible",
|
||||
// content: "content-box",
|
||||
// "&$selected": {
|
||||
// backgroundColor: "gold",
|
||||
// color: "black",
|
||||
// borderRadius: 24,
|
||||
// fontWeight: "bold"
|
||||
// },
|
||||
// "&$selected:hover": {
|
||||
// backgroundColor: "rgb(255, 255, 0)",
|
||||
// color: "black",
|
||||
// borderRadius: 24
|
||||
// }
|
||||
// },
|
||||
// selected: {}
|
||||
// }));
|
||||
|
||||
useEffect(() => {
|
||||
let ebt = sessionStorage.getItem("expandBinTotal");
|
||||
|
|
@ -502,6 +503,17 @@ export default function Bins(props: Props) {
|
|||
}
|
||||
}, [loadBins, props.insert, contentFilter]);
|
||||
|
||||
const determineNodeToggle = () => {
|
||||
switch(cardValDisplay) {
|
||||
case "low":
|
||||
return [0]
|
||||
case "high":
|
||||
return [2]
|
||||
default:
|
||||
return [1]
|
||||
}
|
||||
}
|
||||
|
||||
const duplicateBin = (bin: Bin) => {
|
||||
binAPI.addBin(bin.settings, as).then(resp => {
|
||||
loadBins();
|
||||
|
|
@ -1154,7 +1166,25 @@ export default function Bins(props: Props) {
|
|||
Load All
|
||||
</Button>
|
||||
)}
|
||||
<StyledToggleButtonGroup
|
||||
<ButtonGroup
|
||||
toggle
|
||||
toggledButtons={determineNodeToggle()}
|
||||
buttons={[
|
||||
{
|
||||
title: "Low",
|
||||
function: () => {setCardValDisplay("low")}
|
||||
},
|
||||
{
|
||||
title: "Average",
|
||||
function: () => {setCardValDisplay("average")}
|
||||
},
|
||||
{
|
||||
title: "High",
|
||||
function: () => {setCardValDisplay("high")}
|
||||
}
|
||||
]}
|
||||
/>
|
||||
{/* <StyledToggleButtonGroup
|
||||
id="cardValueDisplay"
|
||||
value={cardValDisplay}
|
||||
exclusive
|
||||
|
|
@ -1184,12 +1214,33 @@ export default function Bins(props: Props) {
|
|||
}}>
|
||||
High
|
||||
</StyledToggle>
|
||||
</StyledToggleButtonGroup>
|
||||
</StyledToggleButtonGroup> */}
|
||||
</Box>
|
||||
</Grid>
|
||||
<Grid >
|
||||
<Box display="flex" paddingY={1} alignContent="center" alignItems="center">
|
||||
<StyledToggleButtonGroup
|
||||
<ButtonGroup
|
||||
toggle
|
||||
buttons={[
|
||||
{
|
||||
title: "Grid",
|
||||
icon: <ViewComfy />,
|
||||
function: () => {
|
||||
setBinView("grid");
|
||||
sessionStorage.setItem("binsView", "grid");
|
||||
}
|
||||
},
|
||||
{
|
||||
title: "List",
|
||||
icon: <ViewList />,
|
||||
function: () => {
|
||||
setBinView("list");
|
||||
sessionStorage.setItem("binsView", "list");
|
||||
}
|
||||
}
|
||||
]}
|
||||
/>
|
||||
{/* <StyledToggleButtonGroup
|
||||
id="tour-graph-tabs"
|
||||
value={binView}
|
||||
exclusive
|
||||
|
|
@ -1203,7 +1254,7 @@ export default function Bins(props: Props) {
|
|||
sessionStorage.setItem("binsView", "grid");
|
||||
}}>
|
||||
<ViewComfy />
|
||||
</StyledToggle>
|
||||
</StyledToggle> */}
|
||||
{/* hidden at dustins request so that grid view and list are the only two */}
|
||||
{/* <StyledToggle
|
||||
value={"scroll"}
|
||||
|
|
@ -1214,7 +1265,7 @@ export default function Bins(props: Props) {
|
|||
}}>
|
||||
<ViewColumn />
|
||||
</StyledToggle> */}
|
||||
<StyledToggle
|
||||
{/* <StyledToggle
|
||||
value={"list"}
|
||||
aria-label="list view"
|
||||
onClick={() => {
|
||||
|
|
@ -1223,7 +1274,7 @@ export default function Bins(props: Props) {
|
|||
}}>
|
||||
<ViewList />
|
||||
</StyledToggle>
|
||||
</StyledToggleButtonGroup>
|
||||
</StyledToggleButtonGroup> */}
|
||||
<MoreVert
|
||||
className={classes.icon}
|
||||
onClick={event => {
|
||||
|
|
@ -1261,7 +1312,28 @@ export default function Bins(props: Props) {
|
|||
</Grid>
|
||||
<Grid >
|
||||
<Box display="flex" paddingY={1} alignContent="center" alignItems="center">
|
||||
<StyledToggleButtonGroup
|
||||
<ButtonGroup
|
||||
toggle
|
||||
buttons={[
|
||||
{
|
||||
title: "Grid",
|
||||
icon: <ViewComfy />,
|
||||
function: () => {
|
||||
setBagView("grid");
|
||||
sessionStorage.setItem("bagsView", "grid");
|
||||
}
|
||||
},
|
||||
{
|
||||
title: "List",
|
||||
icon: <ViewList />,
|
||||
function: () => {
|
||||
setBagView("list");
|
||||
sessionStorage.setItem("bagsView", "list");
|
||||
}
|
||||
}
|
||||
]}
|
||||
/>
|
||||
{/* <StyledToggleButtonGroup
|
||||
id="tour-graph-tabs"
|
||||
value={bagView}
|
||||
exclusive
|
||||
|
|
@ -1275,7 +1347,7 @@ export default function Bins(props: Props) {
|
|||
sessionStorage.setItem("bagsView", "grid");
|
||||
}}>
|
||||
<ViewComfy />
|
||||
</StyledToggle>
|
||||
</StyledToggle> */}
|
||||
{/* hidden at dustins request so that grid view and list are the only two */}
|
||||
{/* <StyledToggle
|
||||
value={"scroll"}
|
||||
|
|
@ -1286,7 +1358,7 @@ export default function Bins(props: Props) {
|
|||
}}>
|
||||
<ViewColumn />
|
||||
</StyledToggle> */}
|
||||
<StyledToggle
|
||||
{/* <StyledToggle
|
||||
value={"list"}
|
||||
aria-label="list view"
|
||||
onClick={() => {
|
||||
|
|
@ -1295,7 +1367,7 @@ export default function Bins(props: Props) {
|
|||
}}>
|
||||
<ViewList />
|
||||
</StyledToggle>
|
||||
</StyledToggleButtonGroup>
|
||||
</StyledToggleButtonGroup> */}
|
||||
<IconButton
|
||||
className={classes.icon}
|
||||
onClick={() => {
|
||||
|
|
|
|||
|
|
@ -22,6 +22,15 @@ import { or } from "utils";
|
|||
import ComponentDiagnostics from "component/ComponentDiagnostics";
|
||||
import DeviceWizard from "device/DeviceWizard";
|
||||
|
||||
export interface DevicePageData {
|
||||
device: Device;
|
||||
components: Component[];
|
||||
interactions: Interaction[];
|
||||
permissions: pond.Permission[];
|
||||
preferences: pond.DevicePreferences;
|
||||
// componentPreferences: Map<string, pond.DeviceComponentPreferences>
|
||||
}
|
||||
|
||||
export default function DevicePage() {
|
||||
const deviceAPI = useDeviceAPI()
|
||||
const snackbar = useSnackbar()
|
||||
|
|
@ -30,6 +39,7 @@ export default function DevicePage() {
|
|||
const groupID = useParams<{ groupID: string }>()?.groupID ?? "";
|
||||
const { state } = useLocation();
|
||||
const [{ as, team, user }] = useGlobalState()
|
||||
// console.log(state)
|
||||
const [device, setDevice] = useState<Device>(state?.device ? Device.create(state.device) : Device.create())
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [permissions, setPermissions] = useState<pond.Permission[]>([])
|
||||
|
|
@ -51,11 +61,21 @@ export default function DevicePage() {
|
|||
|
||||
const [addComponentManualDialogOpen, setAddComponentManualDialogOpen] = useState(false)
|
||||
|
||||
// const [devicePageData, setDevicePageData] = useState(pond.GetDevicePageDataResponse.create())
|
||||
|
||||
const loadDevice = () => {
|
||||
// console.log("load device page data")
|
||||
if (loading) return
|
||||
if (state?.devicePageData) {
|
||||
setDevice(Device.create(state.devicePageData.device))
|
||||
setComponents(state.devicePageData.components.map((comp: pond.Component) => Component.create(comp)))
|
||||
setInteractions(state.devicePageData.interactions.map((inter: pond.Interaction) => Interaction.create(inter)))
|
||||
setPreferences(state.devicePageData.preferences)
|
||||
setPermissions(state.devicePageData.permissions)
|
||||
return
|
||||
}
|
||||
setLoading(true)
|
||||
deviceAPI.getPageData(deviceID, getContextKeys(), getContextTypes(), as).then(resp => {
|
||||
// setDevicePageData(resp.data)
|
||||
let device = Device.any(resp.data.device)
|
||||
// console.log(resp.data.device)
|
||||
setDevice(device)
|
||||
|
|
@ -418,19 +438,9 @@ export default function DevicePage() {
|
|||
<PageContainer padding={isMobile ? 0 : 2}>
|
||||
<Grid container justifyContent={"space-between"}>
|
||||
<Grid>
|
||||
<SmartBreadcrumb />
|
||||
<SmartBreadcrumb deviceName={device.name()} />
|
||||
</Grid>
|
||||
<Grid>
|
||||
<AddComponentManualDialog
|
||||
device={device.id()}
|
||||
open={addComponentManualDialogOpen}
|
||||
onClose={() => setAddComponentManualDialogOpen(false)}
|
||||
/>
|
||||
{user.hasFeature("developer") === true &&
|
||||
<Button onClick={() => setAddComponentManualDialogOpen(true)}>
|
||||
Manual Comp
|
||||
</Button>
|
||||
}
|
||||
<DeviceActions
|
||||
device={device}
|
||||
isPaused={false}
|
||||
|
|
@ -449,8 +459,6 @@ export default function DevicePage() {
|
|||
<DeviceOverview
|
||||
device={device}
|
||||
components={[...components.values()]}
|
||||
// components={[]}
|
||||
// components={components}
|
||||
usage={getUsage()}
|
||||
loading={loading}
|
||||
groupID={parseInt(groupID)}
|
||||
|
|
|
|||
393
src/pages/DeviceSupport.tsx
Normal file
|
|
@ -0,0 +1,393 @@
|
|||
import { Box, Button, FormControl, Grid2 as Grid, MenuItem, Select, Tab, Tabs, TextField, Typography, useTheme } from "@mui/material";
|
||||
import PageContainer from "./PageContainer";
|
||||
import { pond } from "protobuf-ts/pond";
|
||||
import LogsDisplay from "common/LogsDisplay";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useDeviceAPI, useSnackbar } from "hooks";
|
||||
import { useLocation, useParams } from "react-router-dom";
|
||||
import { Component, Device, Interaction } from "models";
|
||||
import { getContextKeys, getContextTypes } from "pbHelpers/Context";
|
||||
import SmartBreadcrumb from "common/SmartBreadcrumb";
|
||||
import { DevicePageData } from "./Device";
|
||||
import AddComponentManualDialog from "component/AddComponentManualDialog";
|
||||
import { useGlobalState } from "providers";
|
||||
import DeviceActions from "device/DeviceActions";
|
||||
import { cloneDeep } from "lodash";
|
||||
import { FindAvailablePositions } from "pbHelpers/DeviceAvailability";
|
||||
import LoadingScreen from "app/LoadingScreen";
|
||||
import DeviceOverview from "device/DeviceOverview";
|
||||
|
||||
interface TabPanelProps {
|
||||
children?: React.ReactNode;
|
||||
index: any;
|
||||
value: any;
|
||||
}
|
||||
|
||||
function TabPanelMine(props: TabPanelProps) {
|
||||
const { children, value, index, ...other } = props;
|
||||
|
||||
return (
|
||||
<div
|
||||
role="tabpanel"
|
||||
hidden={value !== index}
|
||||
aria-labelledby={`simple-tab-${index}`}
|
||||
{...other}>
|
||||
{value === index && <>{children}</>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function DeviceSupport() {
|
||||
const deviceID = +(useParams<{ deviceID: string }>()?.deviceID ?? "0");
|
||||
|
||||
const deviceAPI = useDeviceAPI()
|
||||
const snackbar = useSnackbar()
|
||||
const theme = useTheme()
|
||||
|
||||
const { state } = useLocation();
|
||||
const [{ user, as }] = useGlobalState()
|
||||
const [device, setDevice] = useState<Device>(Device.create())
|
||||
const [devicePageData, setDevicePageData] = useState<DevicePageData>(state?.devicePageData ? state.devicePageData : undefined)
|
||||
const [tabNumber, setTabNumber] = useState(0)
|
||||
const [newCap, setNewCap] = useState(0);
|
||||
const [datacap, setDatacap] = useState<number>();
|
||||
const [isOver, setIsOver] = useState(undefined);
|
||||
const [pauseCheck, setPauseCheck] = useState(undefined);
|
||||
const [inputFirmware, setInputFirmware] = useState<string>("");
|
||||
const [platform, setPlatform] = useState<pond.DevicePlatform>(
|
||||
pond.DevicePlatform.DEVICE_PLATFORM_INVALID
|
||||
);
|
||||
const [addComponentManualDialogOpen, setAddComponentManualDialogOpen] = useState(false)
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
// useEffect(() => {
|
||||
// if (state?.device) return
|
||||
// deviceAPI.get(deviceID, undefined, getContextKeys(), getContextTypes()).then(resp => {
|
||||
// setDevice(Device.create(resp.data))
|
||||
// })
|
||||
// }, [deviceID, state?.device])
|
||||
|
||||
useEffect(() => {
|
||||
if (state?.devicePageData) {
|
||||
setDevice(Device.create(state?.devicePageData.device))
|
||||
return
|
||||
}
|
||||
setLoading(true)
|
||||
deviceAPI.getPageData(deviceID, getContextKeys(), getContextTypes()).then(resp => {
|
||||
setDevice(resp.data?.device ? Device.create(resp.data?.device) : Device.create())
|
||||
setDevicePageData({
|
||||
device: resp.data.device ? Device.create(resp.data.device) : Device.create(),
|
||||
components: resp.data.components.map(comp => Component.create(comp)),
|
||||
interactions: resp.data.interactions.map(interaction => Interaction.create(interaction)),
|
||||
permissions: resp.data.permissions,
|
||||
preferences: resp.data.user?.preferences as pond.DevicePreferences
|
||||
})
|
||||
}).finally(() => {
|
||||
setLoading(false)
|
||||
})
|
||||
}, [deviceID, state?.devicePageData])
|
||||
|
||||
const handleChange = (_event: React.ChangeEvent<{}>, newValue: number) => {
|
||||
setTabNumber(newValue);
|
||||
};
|
||||
|
||||
const resetQuackCount = () => {
|
||||
deviceAPI.resetQuackCount(device.id()).then(resp => console.log(resp));
|
||||
};
|
||||
|
||||
const resetQuackCountTx = () => {
|
||||
deviceAPI.resetQuackCountTx(device.id()).then(resp => console.log(resp));
|
||||
};
|
||||
|
||||
const resetQuackCountTx1000 = () => {
|
||||
deviceAPI.resetQuackCountTx1000(device.id()).then(resp => console.log(resp));
|
||||
};
|
||||
|
||||
const clearPending = () => {
|
||||
deviceAPI.clearPending(device.id()).then(resp => {
|
||||
console.log(resp);
|
||||
});
|
||||
};
|
||||
|
||||
const setFirmwareVersion = (firmwareVersion: string) => {
|
||||
let status = device.status;
|
||||
status.firmwareVersion = firmwareVersion;
|
||||
deviceAPI.updateStatus(device.id(), status).then(() => {
|
||||
snackbar.info("Firmware version set to " + firmwareVersion);
|
||||
});
|
||||
};
|
||||
|
||||
const updateDevicePlatform = (firmwareVersion: pond.DevicePlatform) => {
|
||||
let settings = device.settings;
|
||||
settings.platform = firmwareVersion;
|
||||
deviceAPI.update(device.id(), settings).then(() => {
|
||||
snackbar.info("Platform set to " + firmwareVersion.toString());
|
||||
});
|
||||
};
|
||||
|
||||
const getDatacap = () => {
|
||||
deviceAPI.getDatacap(device.id()).then(resp => {
|
||||
//console.log(resp);
|
||||
setDatacap(resp.data.overlimit);
|
||||
});
|
||||
};
|
||||
|
||||
const updateDatacap = () => {
|
||||
deviceAPI.setDatacap(device.id(), newCap).then(resp => {
|
||||
console.log(resp);
|
||||
});
|
||||
};
|
||||
|
||||
const checkLimit = () => {
|
||||
deviceAPI.isOverLimit(device.id()).then(resp => {
|
||||
//console.log(resp);
|
||||
setIsOver(resp.data);
|
||||
});
|
||||
};
|
||||
|
||||
const checkIfPaused = () => {
|
||||
deviceAPI.isPaused(device.id()).then(resp => {
|
||||
//console.log(resp);
|
||||
setPauseCheck(resp.data);
|
||||
});
|
||||
};
|
||||
|
||||
const pauseDevice = () => {
|
||||
deviceAPI.pause(device.id()).then(resp => {
|
||||
console.log(resp);
|
||||
});
|
||||
};
|
||||
|
||||
const resumeDevice = () => {
|
||||
deviceAPI.resume(device.id()).then(resp => {
|
||||
console.log(resp);
|
||||
});
|
||||
};
|
||||
|
||||
const toggleNotificationPreference = () => {
|
||||
let updatedPreferences = cloneDeep(devicePageData.preferences);
|
||||
updatedPreferences.notify = !devicePageData.preferences.notify;
|
||||
let updatedDevicePageData = cloneDeep(devicePageData)
|
||||
updatedDevicePageData.preferences = updatedPreferences
|
||||
deviceAPI
|
||||
.updatePreferences(deviceID, updatedPreferences, getContextKeys(), getContextTypes(), as)
|
||||
.then(() => setDevicePageData(updatedDevicePageData))
|
||||
.catch(() => {
|
||||
snackbar.error(
|
||||
"Error occured while " +
|
||||
(devicePageData.preferences.notify ? "enabling" : "disabling") +
|
||||
" notifications"
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
if (!devicePageData) {
|
||||
return (
|
||||
<LoadingScreen message="Getting device data"/>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<PageContainer>
|
||||
<Grid container justifyContent={"space-between"}>
|
||||
<Grid>
|
||||
<SmartBreadcrumb deviceName={device.name()} state={{devicePageData: devicePageData}} />
|
||||
</Grid>
|
||||
<Grid>
|
||||
<DeviceActions
|
||||
device={device}
|
||||
isPaused={false}
|
||||
isLoading={loading}
|
||||
permissions={devicePageData.permissions}
|
||||
preferences={devicePageData.preferences}
|
||||
toggleNotificationPreference={toggleNotificationPreference}
|
||||
refreshCallback={() => {}}
|
||||
availablePositions={FindAvailablePositions(devicePageData.components, device.settings.product).availability}
|
||||
availableOffsets={new Map()}
|
||||
components={[...devicePageData.components.map(comp => Component.any(comp))]}
|
||||
interactions={devicePageData.interactions.map(inter => Interaction.any(inter))}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
{/* <SmartBreadcrumb deviceName={device.name()} state={{devicePageData: devicePageData}} /> */}
|
||||
<DeviceOverview device={device} components={devicePageData.components}/>
|
||||
<br/>
|
||||
<Tabs
|
||||
style={{ position: "sticky" }}
|
||||
value={tabNumber}
|
||||
onChange={handleChange}
|
||||
centered
|
||||
TabIndicatorProps={{ style: { background: "rgba(255,255,0,255)" } }}>
|
||||
<Tab label="Control Center" />
|
||||
<Tab label="Hologram" />
|
||||
<Tab label="DataDog" />
|
||||
<Tab label="DuckData" />
|
||||
</Tabs>
|
||||
<TabPanelMine value={tabNumber} index={0}>
|
||||
<Box>
|
||||
<Typography gutterBottom variant="h5">
|
||||
General Options:
|
||||
</Typography>
|
||||
<Grid container direction="row">
|
||||
<Grid>
|
||||
<Button color="primary" onClick={resetQuackCount}>
|
||||
Reset Count Rx (safe, use when quack count errors)
|
||||
</Button>
|
||||
<Button color="primary" onClick={resetQuackCountTx}>
|
||||
Reset Count Tx, Are you sure (ONLY use when device not responding)
|
||||
</Button>
|
||||
<Button color="primary" onClick={resetQuackCountTx1000}>
|
||||
Reset Count Tx - 1000, Are you sure (ONLY use when device not responding)
|
||||
</Button>
|
||||
</Grid>
|
||||
|
||||
<Grid>
|
||||
<Button color="primary" onClick={clearPending}>
|
||||
Clear Pending
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid>
|
||||
<AddComponentManualDialog
|
||||
device={device.id()}
|
||||
open={addComponentManualDialogOpen}
|
||||
onClose={() => setAddComponentManualDialogOpen(false)}
|
||||
/>
|
||||
{user.hasFeature("developer") === true &&
|
||||
<Button onClick={() => setAddComponentManualDialogOpen(true)}>
|
||||
Manual Comp
|
||||
</Button>
|
||||
}
|
||||
</Grid>
|
||||
<Grid container direction="row">
|
||||
<Grid>
|
||||
<TextField
|
||||
value={inputFirmware}
|
||||
onChange={e => setInputFirmware(e.currentTarget.value)}
|
||||
/>
|
||||
<Button color="primary" onClick={() => setFirmwareVersion(inputFirmware)}>
|
||||
Set Firmware
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid container direction="row">
|
||||
<Grid>
|
||||
<FormControl>
|
||||
<Select
|
||||
labelId="demo-simple-select-label"
|
||||
id="demo-simple-select"
|
||||
value={platform}
|
||||
label="Platform"
|
||||
onChange={(event: any) => setPlatform(event.target.value)}>
|
||||
<MenuItem value={pond.DevicePlatform.DEVICE_PLATFORM_PHOTON}>Photon</MenuItem>
|
||||
<MenuItem value={pond.DevicePlatform.DEVICE_PLATFORM_ELECTRON}>
|
||||
Electron
|
||||
</MenuItem>
|
||||
<MenuItem value={pond.DevicePlatform.DEVICE_PLATFORM_V2_CELLULAR}>
|
||||
V2 Cellular
|
||||
</MenuItem>
|
||||
<MenuItem value={pond.DevicePlatform.DEVICE_PLATFORM_V2_WIFI_S3}>
|
||||
V2 Wifi S3
|
||||
</MenuItem>
|
||||
<MenuItem value={pond.DevicePlatform.DEVICE_PLATFORM_V2_CELLULAR_BLACK}>
|
||||
V2 Cellular Black
|
||||
</MenuItem>
|
||||
<MenuItem value={pond.DevicePlatform.DEVICE_PLATFORM_V2_CELLULAR_GREEN}>
|
||||
V2 Cellular Green
|
||||
</MenuItem>
|
||||
<MenuItem value={pond.DevicePlatform.DEVICE_PLATFORM_V2_WIFI_BLUE}>
|
||||
V2 Wifi Blue
|
||||
</MenuItem>
|
||||
<MenuItem value={pond.DevicePlatform.DEVICE_PLATFORM_V2_CELLULAR_BLUE}>
|
||||
V2 Cellular Blue
|
||||
</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
<Button color="primary" onClick={() => updateDevicePlatform(platform)}>
|
||||
Set Platform
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Box>
|
||||
<Box marginTop={theme.spacing(0.5)}>
|
||||
<Typography gutterBottom variant="h5">
|
||||
OPI Cable Functions:
|
||||
</Typography>
|
||||
<Grid container direction="row">
|
||||
<Grid>
|
||||
<Button color="primary">Cable Reprogrammer</Button>
|
||||
</Grid>
|
||||
<Grid>
|
||||
<Button color="primary">Cable Diagnostic</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Box>
|
||||
</TabPanelMine>
|
||||
<TabPanelMine value={tabNumber} index={1}>
|
||||
<Grid container direction="column">
|
||||
<Grid container direction="row">
|
||||
<Grid>
|
||||
<TextField
|
||||
type="number"
|
||||
value={newCap}
|
||||
onChange={e => setNewCap(+e.target.value)}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid>
|
||||
<Button onClick={updateDatacap} color="primary">
|
||||
Set New Datacap
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid container direction="row" alignItems="center">
|
||||
<Grid>
|
||||
<Button onClick={getDatacap} color="primary">
|
||||
DataCap
|
||||
</Button>
|
||||
</Grid>
|
||||
<Grid>
|
||||
<Box margin={3}>{datacap && datacap / 1000 / 1000}Mb</Box>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid container direction="row" alignItems="center">
|
||||
<Grid>
|
||||
<Button onClick={checkLimit} color="primary">
|
||||
OverLimit
|
||||
</Button>
|
||||
</Grid>
|
||||
<Grid>
|
||||
<Box margin={3}>
|
||||
{isOver === undefined ? "Click to Check" : isOver ? "Over Limit" : "Under Limit"}
|
||||
</Box>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid container direction="row" alignItems="center">
|
||||
<Grid>
|
||||
<Button onClick={checkIfPaused} color="primary">
|
||||
Am I Paused
|
||||
</Button>
|
||||
</Grid>
|
||||
<Grid>
|
||||
<Box margin={3}>
|
||||
{pauseCheck === undefined ? "Click to Check" : pauseCheck ? "Paused" : "Running"}
|
||||
</Box>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Button onClick={pauseDevice} color="primary">
|
||||
Pause Me
|
||||
</Button>
|
||||
<Button onClick={resumeDevice} color="primary">
|
||||
Get Me Goin
|
||||
</Button>
|
||||
</TabPanelMine>
|
||||
<TabPanelMine value={tabNumber} index={2}>
|
||||
<LogsDisplay deviceID={deviceID} />
|
||||
</TabPanelMine>
|
||||
<TabPanelMine value={tabNumber} index={3}>
|
||||
History Not imported yet
|
||||
{/* <CompleteHistory device={device} /> */}
|
||||
</TabPanelMine>
|
||||
</PageContainer>
|
||||
)
|
||||
}
|
||||
|
|
@ -57,7 +57,14 @@ const useStyles = makeStyles((theme: Theme) => {
|
|||
margin: theme.spacing(1),
|
||||
marginLeft: theme.spacing(2),
|
||||
display: "flex",
|
||||
width: theme.spacing(26)
|
||||
width: theme.spacing(32)
|
||||
// justifyContent: "center",
|
||||
},
|
||||
tagsCellContainer: {
|
||||
margin: theme.spacing(1),
|
||||
marginLeft: theme.spacing(2),
|
||||
display: "flex",
|
||||
width: theme.spacing(24)
|
||||
// justifyContent: "center",
|
||||
},
|
||||
green: {
|
||||
|
|
@ -328,8 +335,8 @@ export default function Devices() {
|
|||
<Box className={classes.descriptionCellContainer}>
|
||||
<Tooltip title={device.settings?.description}>
|
||||
<span>
|
||||
{description.length > 32
|
||||
? description.substring(0, 32) + "..."
|
||||
{description.length > 36
|
||||
? description.substring(0, 36) + "..."
|
||||
: description}
|
||||
</span>
|
||||
</Tooltip>
|
||||
|
|
@ -341,7 +348,7 @@ export default function Devices() {
|
|||
title: "Tags",
|
||||
render: (device: Device) => {
|
||||
return (
|
||||
<Grid2 container spacing={1} className={classes.descriptionCellContainer}>
|
||||
<Grid2 container spacing={1} className={classes.tagsCellContainer}>
|
||||
{device.status.tags.map((tag: pond.TagSettings) => (
|
||||
<Grid2 key={"device-"+device.id()+"-tag-"+tag.key}>
|
||||
<TagUI tag={Tag.any({ settings: tag })} />
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import React from "react";
|
||||
import PageContainer from "./PageContainer";
|
||||
import FieldList from "field/FieldList";
|
||||
import { Box } from "@mui/material";
|
||||
|
|
|
|||
|
|
@ -206,7 +206,7 @@ export default function Firmware() {
|
|||
// };
|
||||
|
||||
const load = useCallback(() => {
|
||||
firmwareAPI.listFirmware(pageSize, pageSize * tablePage, "asc", "version", searchText)
|
||||
firmwareAPI.listFirmware(pageSize, pageSize * tablePage, "desc", "version", searchText)
|
||||
.then((response) => {
|
||||
setTotal(response.data.total ? response.data.total : 0)
|
||||
let rows: FirmwareModel[] = or(response.data.firmware, []).map(
|
||||
|
|
|
|||
|
|
@ -339,6 +339,7 @@ export default function Gate(props: Props) {
|
|||
}}
|
||||
style={{ height: 150, width: "100%", margin: -15 }}>
|
||||
<Grid
|
||||
width="100%"
|
||||
container
|
||||
direction="row"
|
||||
spacing={2}
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ export const HTTPContext = createContext<IHTTPContext>({} as IHTTPContext);
|
|||
|
||||
export default function HTTPProvider(props: Props) {
|
||||
const { children, token } = props;
|
||||
const { isAuthenticated, /*loginWithRedirect,*/ loginWithPopup } = useAuth0();
|
||||
const { isAuthenticated, loginWithRedirect } = useAuth0();
|
||||
|
||||
const defaultOptions = (demo: boolean = false) => {
|
||||
if (demo || !isAuthenticated || !token) {
|
||||
|
|
@ -79,7 +79,7 @@ export default function HTTPProvider(props: Props) {
|
|||
|
||||
function get<T>(url: string, spreadOptions?: AxiosRequestConfig): Promise<AxiosResponse<T>> {
|
||||
if (isTokenExpired(token)) {
|
||||
loginWithPopup()
|
||||
loginWithRedirect()
|
||||
}
|
||||
return axios.get(url, {...defaultOptions(), ...spreadOptions});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import { useHTTP, usePermissionAPI } from "hooks";
|
||||
// import { useWebsocket } from "websocket";
|
||||
import { /*Component,*/ Component, deviceScope, Team, User } from "models";
|
||||
import { Component, deviceScope, User } from "models";
|
||||
import { pond } from "protobuf-ts/pond";
|
||||
import { createContext, PropsWithChildren, useContext } from "react";
|
||||
import { pondURL } from "./pond";
|
||||
|
|
@ -9,7 +8,6 @@ import { useGlobalState } from "providers";
|
|||
import moment from "moment";
|
||||
import { or } from "utils/types";
|
||||
import { dateRange } from "providers/http";
|
||||
// import { reject, result } from "lodash";
|
||||
|
||||
export interface IDeviceAPIContext {
|
||||
add: (name: string, description: string, backpack: pond.BackpackSettings, otherTeam?: string) => Promise<AxiosResponse<pond.AddDeviceResponse>>;
|
||||
|
|
@ -213,12 +211,27 @@ export default function DeviceProvider(props: PropsWithChildren<Props>) {
|
|||
types?: string[]
|
||||
) => {
|
||||
let url = pondURL("/deviceStatistics");
|
||||
if (types && types.length > 0)
|
||||
if (types && types.length > 0) {
|
||||
url = pondURL(
|
||||
"/deviceStatistics/" +
|
||||
(keys ? "?keys=" + keys.toString() : "") +
|
||||
(types ? "&types=" + types.toString() : "")
|
||||
)
|
||||
if (!types.includes("team") && as.length > 0) {
|
||||
url = pondURL(
|
||||
"/deviceStatistics/" +
|
||||
(keys ? "?keys=" + keys.toString() : "") +
|
||||
(types ? "&types=" + types.toString() : "") +
|
||||
("&as=" + as)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
if (as.length > 0) {
|
||||
url = pondURL(
|
||||
"/deviceStatistics/?as=" + as
|
||||
)
|
||||
}
|
||||
}
|
||||
return new Promise<AxiosResponse<pond.GetDeviceStatisticsResponse>>((resolve, reject) => {
|
||||
get<pond.GetDeviceStatisticsResponse>(url).then(resp => {
|
||||
resp.data = pond.GetDeviceStatisticsResponse.fromObject(resp.data)
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ interface WhiteLabel {
|
|||
// };
|
||||
|
||||
const STAGING_WHITELABEL: WhiteLabel = {
|
||||
name: import.meta.env.VITE_APP_WEBSITE_TITLE,
|
||||
name: "Staging",
|
||||
primaryColour: import.meta.env.VITE_APP_PRIMARY_COLOUR,
|
||||
secondaryColour: import.meta.env.VITE_APP_SECONDARY_COLOUR,
|
||||
signatureColour: import.meta.env.VITE_APP_SIGNATURE_COLOUR,
|
||||
|
|
@ -123,7 +123,7 @@ const ADAPTIVE_AGRICULTURE_WHITE_LABEL: WhiteLabel = {
|
|||
// secondaryColour: "#FFFF00",
|
||||
signatureColour: "#272727",
|
||||
signatureAccentColour: "#fff",
|
||||
auth0ClientId: import.meta.env.REACT_APP_AUTH0_ADAPTIVE_AGRICULTURE_CLIENT_ID,
|
||||
auth0ClientId: import.meta.env.VITE_AUTH0_ADAPTIVE_AGRICULTURE_CLIENT_ID,
|
||||
redirectOnLogout: true,
|
||||
logoutRedirectTarget: "https://adaptiveagriculture.ca",
|
||||
darkLogo: AdapativeAgLogo,
|
||||
|
|
@ -163,7 +163,7 @@ const AEROGROW_WHITE_LABEL: WhiteLabel = {
|
|||
secondaryColour: "cyan",
|
||||
signatureColour: "#fff",
|
||||
signatureAccentColour: "#000",
|
||||
auth0ClientId: import.meta.env.REACT_APP_AUTH0_AEROGROW_CLIENT_ID,
|
||||
auth0ClientId: import.meta.env.VITE_AUTH0_AEROGROW_CLIENT_ID,
|
||||
redirectOnLogout: true,
|
||||
logoutRedirectTarget: "https://www.aerogrowmanufacturing.com",
|
||||
darkLogo: AeroGrowDarkLogo,
|
||||
|
|
@ -189,7 +189,7 @@ const MIVENT_WHITE_LABEL: WhiteLabel = {
|
|||
secondaryColour: "yellow",
|
||||
signatureColour: "#272727",
|
||||
signatureAccentColour: "#fff",
|
||||
auth0ClientId: import.meta.env.REACT_APP_AUTH0_MIVENT_CLIENT_ID,
|
||||
auth0ClientId: import.meta.env.VITE_AUTH0_MIVENT_CLIENT_ID,
|
||||
redirectOnLogout: true,
|
||||
logoutRedirectTarget: "https://mivent.ca",
|
||||
darkLogo: MiVentLightLogo,
|
||||
|
|
@ -207,7 +207,7 @@ const ADAPTIVE_CONSTRUCTION_WHITE_LABEL: WhiteLabel = {
|
|||
secondaryColour: "blue",
|
||||
signatureColour: "#272727",
|
||||
signatureAccentColour: "#fff",
|
||||
auth0ClientId: import.meta.env.REACT_APP_AUTH0_ADAPTIVE_CONSTRUCTION_CLIENT_ID,
|
||||
auth0ClientId: import.meta.env.VITE_AUTH0_ADAPTIVE_CONSTRUCTION_CLIENT_ID,
|
||||
redirectOnLogout: true,
|
||||
logoutRedirectTarget: "https://adaptiveconstruction.ca",
|
||||
darkLogo: AdConLogo,
|
||||
|
|
@ -233,7 +233,7 @@ const OMNIAIR_WHITE_LABEL: WhiteLabel = {
|
|||
secondaryColour: "yellow",
|
||||
signatureColour: "#272727",
|
||||
signatureAccentColour: "#fff",
|
||||
auth0ClientId: import.meta.env.REACT_APP_AUTH0_OMNIAIR_CLIENT_ID,
|
||||
auth0ClientId: import.meta.env.VITE_AUTH0_OMNIAIR_CLIENT_ID,
|
||||
redirectOnLogout: true,
|
||||
logoutRedirectTarget: "https://omniairsystems.com",
|
||||
darkLogo: MiPCALogo,
|
||||
|
|
@ -259,7 +259,7 @@ const whitelabels = new Map<string, WhiteLabel>([
|
|||
["brandxducks", BXT_WHITE_LABEL],
|
||||
["brandxtech", BXT_WHITE_LABEL],
|
||||
["aerogrowmanufacturing", AEROGROW_WHITE_LABEL],
|
||||
["localhost", STAGING_WHITELABEL],
|
||||
["localhost", BXT_WHITE_LABEL],
|
||||
["staging", STAGING_WHITELABEL],
|
||||
["10.0", ADAPTIVE_CONSTRUCTION_WHITE_LABEL],
|
||||
["mivent", MIVENT_WHITE_LABEL],
|
||||
|
|
@ -276,7 +276,7 @@ export function getWhitelabel(): WhiteLabel {
|
|||
if (window.location.origin.includes("bxt-dev")) {
|
||||
return BXT_WHITE_LABEL;
|
||||
}
|
||||
if (window.location.origin.includes("staging")) {
|
||||
if (window.location.origin.includes("staging") || import.meta.env.VITE_LOCAL_STAGING=='true') {
|
||||
return STAGING_WHITELABEL;
|
||||
}
|
||||
const whiteLabelKeys = Array.from(whitelabels.keys());
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import { Task } from "models";
|
|||
//import { ToggleButton, ToggleButtonGroup } from "@material-ui/lab";
|
||||
import TasksIcon from "products/Construction/TasksIcon";
|
||||
import moment from "moment";
|
||||
import ButtonGroup from "common/ButtonGroup";
|
||||
|
||||
interface Props {
|
||||
tasks: Task[];
|
||||
|
|
@ -147,8 +148,21 @@ export default function TaskList(props: Props) {
|
|||
return (
|
||||
<Box padding="5px" textAlign="center">
|
||||
<Typography variant="h5">{label ? label : ""}</Typography>
|
||||
<Box textAlign="center">
|
||||
<ToggleButtonGroup value={viewing} exclusive size="small" aria-label="Bin Mode">
|
||||
<Box textAlign="center" display="flex" justifyContent="center">
|
||||
<ButtonGroup
|
||||
toggle
|
||||
buttons={[
|
||||
{
|
||||
title: "Upcoming",
|
||||
function: () => setViewing("upcoming")
|
||||
},
|
||||
{
|
||||
title: "Complete",
|
||||
function: () => setViewing("complete")
|
||||
}
|
||||
]}
|
||||
/>
|
||||
{/* <ToggleButtonGroup value={viewing} exclusive size="small" aria-label="Bin Mode">
|
||||
<ToggleButton
|
||||
value={"upcoming"}
|
||||
aria-label="upcoming"
|
||||
|
|
@ -161,7 +175,7 @@ export default function TaskList(props: Props) {
|
|||
aria-label="complete">
|
||||
Complete
|
||||
</ToggleButton>
|
||||
</ToggleButtonGroup>
|
||||
</ToggleButtonGroup> */}
|
||||
{location !== "/tasks" && (
|
||||
<IconButton id="tasks" component={Link} to="/tasks">
|
||||
<TasksIcon />
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ export default function TeamList() {
|
|||
viewAs = undefined
|
||||
}
|
||||
teamAPI
|
||||
.listTeams(limit, limit * page, undefined, "name", undefined, undefined, viewAs, search)
|
||||
.listTeams(limit, limit * page, undefined, "name", undefined, undefined, " ", search)
|
||||
.then(resp => {
|
||||
setTotal(resp.data.total ? resp.data.total : 0);
|
||||
let newTeams: Team[] = []
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ export default function TeamSearch(props: Props) {
|
|||
if (!setSearch) return;
|
||||
if (searchText === undefined) return;
|
||||
setHandler(setTimeout(() => {
|
||||
setSearchText(searchText);
|
||||
setSearch(searchText);
|
||||
}, 750)); // Delay of 750ms
|
||||
|
||||
return () => clearTimeout(handler); // Cleanup on change
|
||||
|
|
@ -41,7 +41,7 @@ export default function TeamSearch(props: Props) {
|
|||
let options: Option[] = [];
|
||||
setLoading(true);
|
||||
teamAPI
|
||||
.listTeams(10, 0, "desc", undefined, undefined, undefined, undefined, search)
|
||||
.listTeams(10, 0, "desc", undefined, undefined, undefined, " ", search)
|
||||
.then(resp => {
|
||||
if (loadUsers)
|
||||
options.push({
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ export const AppThemeProvider: React.FC<{ children: React.ReactNode }> = ({ chil
|
|||
const savedTheme = localStorage.getItem("theme");
|
||||
const isValidTheme = (theme: string | null): theme is ThemeMode =>
|
||||
theme === "light" || theme === "dark" || theme === "system";
|
||||
const [mode, setMode] = useState<ThemeMode>(isValidTheme(savedTheme) ? savedTheme : "system");
|
||||
const [mode, setMode] = useState<ThemeMode>(isValidTheme(savedTheme) ? savedTheme : "dark");
|
||||
|
||||
const prefersDark = useMediaQuery("(prefers-color-scheme: dark)");
|
||||
const resolvedMode: PaletteMode = mode === "system" ? (prefersDark ? "dark" : "light") : mode;
|
||||
|
|
|
|||
|
|
@ -17,6 +17,20 @@ const baseTheme: ThemeOptions = {
|
|||
paper: '#f5f5f5',
|
||||
},
|
||||
},
|
||||
typography: {
|
||||
fontFamily: [
|
||||
"Open Sans",
|
||||
"-apple-system",
|
||||
"BlinkMacSystemFont",
|
||||
'"Segoe UI"',
|
||||
'"Helvetica Neue"',
|
||||
"Arial",
|
||||
"sans-serif",
|
||||
'"Apple Color Emoji"',
|
||||
'"Segoe UI Emoji"',
|
||||
'"Segoe UI Symbol"'
|
||||
].join(",")
|
||||
}
|
||||
// Add other options if needed
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -73,14 +73,19 @@ export default function AccessObject(props: Props) {
|
|||
const [teamsTotal, setTeamsTotal] = useState<number>(0);
|
||||
const [devicesOffset, setDevicesOffset] = useState<number>(0);
|
||||
const [devicesTotal, setDevicesTotal] = useState<number>(0);
|
||||
const [prevAs, setPrevAs] = useState("")
|
||||
|
||||
const close = () => {
|
||||
|
||||
const close = (restoreAs?: boolean) => {
|
||||
if(restoreAs){
|
||||
dispatch({ key: "as", value: prevAs });
|
||||
}
|
||||
closeDialogCallback();
|
||||
};
|
||||
|
||||
const getAccess = (key: number | string, name: string, url: string, useImitation = false) => {
|
||||
user.settings.useTeam = false;
|
||||
dispatch({ key: "as", value: "" });
|
||||
//dispatch({ key: "as", value: "" });
|
||||
userAPI.updateUser(user.id(), user.protobuf()).then(() => {
|
||||
info("Will no longer view as team by default");
|
||||
permissionAPI
|
||||
|
|
@ -258,6 +263,12 @@ export default function AccessObject(props: Props) {
|
|||
|
||||
useEffect(() => {
|
||||
if (isOpen && (prevSearchValue !== searchValue || prevKind !== kind || isOpen !== prevOpen)) {
|
||||
//if the user is viewing as a team record who it was
|
||||
if(as){
|
||||
setPrevAs(as)
|
||||
}
|
||||
//change as so the user is viewing as themself
|
||||
dispatch({ key: "as", value: "" });
|
||||
load();
|
||||
}
|
||||
}, [
|
||||
|
|
@ -350,12 +361,12 @@ export default function AccessObject(props: Props) {
|
|||
return (
|
||||
<ResponsiveDialog
|
||||
open={isOpen}
|
||||
onClose={close}
|
||||
onClose={() => close(true)}
|
||||
aria-labelledby="access-object-dialog"
|
||||
fullScreen>
|
||||
<AppBar position="sticky">
|
||||
<Toolbar>
|
||||
<IconButton edge="start" color="inherit" onClick={close} aria-label="close">
|
||||
<IconButton edge="start" color="inherit" onClick={() => close(true)} aria-label="close">
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
<Typography variant="h6" noWrap>
|
||||
|
|
|
|||
|
|
@ -22,8 +22,10 @@ export default defineConfig({
|
|||
outDir: './build',
|
||||
sourcemap: true,
|
||||
minify: false,
|
||||
target: 'esnext'
|
||||
},
|
||||
esbuild: {
|
||||
keepNames: true, // Prevent function name mangling
|
||||
}
|
||||
},
|
||||
|
||||
})
|
||||
|
|
|
|||