Merge branch 'staging_environment' into hybrid_inventory_control

This commit is contained in:
csawatzky 2025-05-16 09:24:23 -06:00
commit 3b8a47e760
63 changed files with 4307 additions and 983 deletions

13
.env
View file

@ -8,13 +8,12 @@ VITE_AUTH0_AUDIENCE=api.brandxtech.ca
VITE_AUTH0_DEV_CLIENT_ID=dzJTpqIeMA4Rwk4xujtwPbAO3TY32bM1 VITE_AUTH0_DEV_CLIENT_ID=dzJTpqIeMA4Rwk4xujtwPbAO3TY32bM1
VITE_AUTH0_STAGING_CLIENT_ID=3ib460VvLwdeyse5iUSQfxkVdQaUmphP VITE_AUTH0_STAGING_CLIENT_ID=3ib460VvLwdeyse5iUSQfxkVdQaUmphP
# VITE_AUTH0_CLIENT_ID=3ib460VvLwdeyse5iUSQfxkVdQaUmphP VITE_AUTH0_BXT_CLIENT_ID=sLnqOu40uWfQT1lYSDYj2wYmlLEHRB74
# VITE_AUTH0_AUDIENCE=stagingapi.brandxtech.ca VITE_AUTH0_ADAPTIVE_AGRICULTURE_CLIENT_ID=5pUCkl2SfogkWmM244UDcLEUOp8EFdHd
# VITE_AUTH0_CLIENT_DOMAIN=adaptivestaging.us.auth0.com VITE_AUTH0_ADAPTIVE_CONSTRUCTION_CLIENT_ID=32rABabJzXRvJiWivTmeKFgwFiqh4ok7
# VITE_APP_API_URL=https://stagingapi.brandxtech.ca/v1 VITE_AUTH0_AEROGROW_CLIENT_ID=KHl9ooUt1nia1RYw5n224dyggCXdbsSd
# VITE_APP_WS_URL=ws://stagingapi.brandxtech.ca/v1/live VITE_AUTH0_MIVENT_CLIENT_ID=VNALE7RW6l3dY5uYcxgwElZV0lcT25Fg
# VITE_APP_AUTH0_CLIENT_DOMAIN=adaptivestaging.us.auth0.com VITE_AUTH0_OMNIAIR_CLIENT_ID=IblmarD8wFafiD6doxTmOHQ6Bx3L9wWl
# VITE_APP_AUTH0_AUDIENCE=stagingapi.brandxtech.ca
#Branding (Default theme) #Branding (Default theme)
VITE_APP_WEBSITE_TITLE="Adaptive Dashboard" VITE_APP_WEBSITE_TITLE="Adaptive Dashboard"

View file

@ -13,8 +13,8 @@ dependencies:production:
only: only:
refs: refs:
- master - master
before_script: # before_script:
- npm -g config set user root # - npm -g config set user root
script: script:
- npm ci - npm ci
cache: cache:
@ -28,17 +28,17 @@ dependencies:production:
paths: paths:
- node_modules/ - node_modules/
test: # test:
image: node:$NODE_VERSION # image: node:$NODE_VERSION
stage: setup # stage: setup
dependencies: # dependencies:
- dependencies:production # - dependencies:production
only: # only:
- master # - master
before_script: # before_script:
- npm run lint # - npm run lint
script: # script:
- CI=true npm run test # - CI=true npm run test
setup:production: setup:production:
image: node:$NODE_VERSION image: node:$NODE_VERSION
@ -197,6 +197,7 @@ staging:deploy:
DEPLOY_IMAGE: ${CI_REGISTRY}/brandx/bxt-app/production:staging DEPLOY_IMAGE: ${CI_REGISTRY}/brandx/bxt-app/production:staging
DOCKER_HOST: ssh://deploy@staging.brandxtech.ca DOCKER_HOST: ssh://deploy@staging.brandxtech.ca
DEPLOY_TARGET: deploy@staging.brandxtech.ca DEPLOY_TARGET: deploy@staging.brandxtech.ca
VITE_MAPBOX_ACCESS_TOKEN: $MAPBOX_ACCESS_TOKEN
only: only:
- staging_environment - staging_environment
environment: environment:
@ -206,6 +207,7 @@ staging:deploy:
- "apk --update add openssh-client" - "apk --update add openssh-client"
- "eval $(ssh-agent -s)" - "eval $(ssh-agent -s)"
- "echo \"$STAGING_SSH_PRIV_KEY\" | tr -d '\r' | ssh-add - " - "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 -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}" # - "ssh -S deploy-ctrl-socket -O check ${DEPLOY_TARGET}"
- mkdir -p ~/.ssh - mkdir -p ~/.ssh

View file

@ -4,7 +4,7 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/x-icon" id="favicon-link" /> <link rel="icon" type="image/x-icon" id="favicon-link" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <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" /> <link rel="manifest" id="manifest-link" />
</head> </head>
<body> <body>

765
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -4,16 +4,16 @@
"version": "0.0.0", "version": "0.0.0",
"type": "module", "type": "module",
"scripts": { "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-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-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", "build": "tsc -b && vite build",
"lint": "eslint .", "lint": "eslint .",
"preview": "vite preview", "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: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_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_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": "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: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", "build:offline": "npx env-cmd offline,whitelabel npm run build",
"test": "vitest" "test": "vitest"
}, },
@ -39,7 +39,6 @@
"@turf/area": "^7.2.0", "@turf/area": "^7.2.0",
"@turf/turf": "^7.2.0", "@turf/turf": "^7.2.0",
"@types/classnames": "^2.3.0", "@types/classnames": "^2.3.0",
"@types/mapbox-gl": "^3.4.1",
"axios": "^1.7.7", "axios": "^1.7.7",
"dayjs": "^1.11.13", "dayjs": "^1.11.13",
"deep-object-diff": "^1.1.9", "deep-object-diff": "^1.1.9",
@ -80,6 +79,7 @@
"@types/lodash": "^4.17.13", "@types/lodash": "^4.17.13",
"@types/mapbox__mapbox-gl-draw": "^1.4.8", "@types/mapbox__mapbox-gl-draw": "^1.4.8",
"@types/mapbox__mapbox-gl-geocoder": "^5.0.0", "@types/mapbox__mapbox-gl-geocoder": "^5.0.0",
"@types/mapbox-gl": "^3.4.1",
"@types/react": "^18.3.10", "@types/react": "^18.3.10",
"@types/react-beautiful-dnd": "^13.1.8", "@types/react-beautiful-dnd": "^13.1.8",
"@types/react-color": "^3.0.13", "@types/react-color": "^3.0.13",

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

View 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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
public/Staging/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View 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"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

File diff suppressed because it is too large Load diff

After

Width:  |  Height:  |  Size: 144 KiB

View file

@ -37,19 +37,16 @@ function App() {
faviconDocument.href = "BrandXTechnologies/favicon.ico" 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 domain: string | undefined = import.meta.env.VITE_AUTH0_CLIENT_DOMAIN;
let audience: string | undefined = import.meta.env.VITE_AUTH0_AUDIENCE; let audience: string | undefined = import.meta.env.VITE_AUTH0_AUDIENCE;
// let client_id = whiteLabel.auth0ClientId 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;
// 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 || "")
// }
const skipCallbacks = [ const skipCallbacks = [
"/johndeere", "/johndeere",

View file

@ -1,5 +1,6 @@
import { Box, Theme, ToggleButton, ToggleButtonGroup, darken } from "@mui/material"; import { Box, Theme, ToggleButton, ToggleButtonGroup, darken } from "@mui/material";
import { makeStyles, withStyles } from "@mui/styles"; import { makeStyles, withStyles } from "@mui/styles";
import ButtonGroup from "common/ButtonGroup";
import { ExtractMoisture } from "grain"; import { ExtractMoisture } from "grain";
import { GrainCable } from "models/GrainCable"; import { GrainCable } from "models/GrainCable";
import { describeMeasurement } from "pbHelpers/MeasurementDescriber"; import { describeMeasurement } from "pbHelpers/MeasurementDescriber";
@ -212,54 +213,54 @@ export default function BinSVGV2(props: Props) {
const [extraDetails, setExtraDetails] = useState<"temps" | "hums">("temps"); const [extraDetails, setExtraDetails] = useState<"temps" | "hums">("temps");
const [nodeWarning, setNodeWarning] = useState(false); const [nodeWarning, setNodeWarning] = useState(false);
const StyledToggleButtonGroup = withStyles(theme => ({ // const StyledToggleButtonGroup = withStyles(theme => ({
grouped: { // grouped: {
margin: theme.spacing(-0.5), // margin: theme.spacing(-0.5),
border: "none", // border: "none",
padding: theme.spacing(1), // padding: theme.spacing(1),
"&:not(:first-child):not(:last-child)": { // "&:not(:first-child):not(:last-child)": {
borderRadius: 24, // borderRadius: 24,
marginRight: theme.spacing(0.5), // marginRight: theme.spacing(0.5),
marginLeft: theme.spacing(0.5) // marginLeft: theme.spacing(0.5)
}, // },
"&:first-child": { // "&:first-child": {
borderRadius: 24, // borderRadius: 24,
marginLeft: theme.spacing(0.25) // marginLeft: theme.spacing(0.25)
}, // },
"&:last-child": { // "&:last-child": {
borderRadius: 24, // borderRadius: 24,
marginRight: theme.spacing(0.25) // marginRight: theme.spacing(0.25)
} // }
}, // },
root: { // root: {
backgroundColor: darken( // backgroundColor: darken(
theme.palette.background.paper, // theme.palette.background.paper,
getThemeType() === "light" ? 0.05 : 0.25 // getThemeType() === "light" ? 0.05 : 0.25
), // ),
borderRadius: 24, // borderRadius: 24,
content: "border-box" // content: "border-box"
} // }
}))(ToggleButtonGroup); // }))(ToggleButtonGroup);
const StyledToggle = withStyles({ // const StyledToggle = withStyles({
root: { // root: {
backgroundColor: "transparent", // backgroundColor: "transparent",
overflow: "visible", // overflow: "visible",
content: "content-box", // content: "content-box",
"&$selected": { // "&$selected": {
backgroundColor: "gold", // backgroundColor: "gold",
color: "black", // color: "black",
borderRadius: 24, // borderRadius: 24,
fontWeight: "bold" // fontWeight: "bold"
}, // },
"&$selected:hover": { // "&$selected:hover": {
backgroundColor: "rgb(255, 255, 0)", // backgroundColor: "rgb(255, 255, 0)",
color: "black", // color: "black",
borderRadius: 24 // borderRadius: 24
} // }
}, // },
selected: {} // selected: {}
})(ToggleButton); // })(ToggleButton);
const gradients = () => { const gradients = () => {
const burning = "#8a1f0d"; const burning = "#8a1f0d";
@ -832,7 +833,6 @@ export default function BinSVGV2(props: Props) {
cableGrainEndY = cableGrainStartY; cableGrainEndY = cableGrainStartY;
} }
if (cableEstimate && cables.length > 0 && cableGrainPathPoints.length === cables.length) { if (cableEstimate && cables.length > 0 && cableGrainPathPoints.length === cables.length) {
console.log("calc cable path");
let cablePath = cableGrainEstimate( let cablePath = cableGrainEstimate(
cableGrainPathPoints, cableGrainPathPoints,
spacingX, spacingX,
@ -1048,10 +1048,26 @@ export default function BinSVGV2(props: Props) {
<React.Fragment> <React.Fragment>
{showTempHum && ( {showTempHum && (
<Box <Box
display="flex"
alignContent="center"
justifyContent="center"
textAlign="center" textAlign="center"
//visibility={showTempHum ? "visible" : "hidden"} //visibility={showTempHum ? "visible" : "hidden"}
className={!isFullScreen ? classes.smallToggle : classes.fullToggle}> 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} value={extraDetails}
exclusive exclusive
size="small" size="small"
@ -1068,7 +1084,7 @@ export default function BinSVGV2(props: Props) {
aria-label="Node Humidity"> aria-label="Node Humidity">
{grainType ? (isFullScreen ? "Moisture" : "EMC") : isFullScreen ? "Humidity" : "Hum"} {grainType ? (isFullScreen ? "Moisture" : "EMC") : isFullScreen ? "Humidity" : "Hum"}
</StyledToggle> </StyledToggle>
</StyledToggleButtonGroup> </StyledToggleButtonGroup> */}
</Box> </Box>
)} )}
<Box <Box

View file

@ -401,7 +401,7 @@ export default function BinStorageConditions(props: Props) {
alignContent="center" alignContent="center"
alignItems="center" alignItems="center"
justifyContent="space-between"> justifyContent="space-between">
<Grid size={{ xs: 6 }}> <Grid size={{ xs: 8 }}>
<Box display="flex" alignContent="center" alignItems="center"> <Box display="flex" alignContent="center" alignItems="center">
<TemperatureIcon /> <TemperatureIcon />
<Typography noWrap style={{ fontWeight: 650 }}> <Typography noWrap style={{ fontWeight: 650 }}>

View file

@ -71,6 +71,7 @@ import { ExtractMoisture } from "grain";
import SearchSelect, { Option } from "common/SearchSelect"; import SearchSelect, { Option } from "common/SearchSelect";
import Edit from "@mui/icons-material/Edit"; import Edit from "@mui/icons-material/Edit";
import { makeStyles, styled } from "@mui/styles"; import { makeStyles, styled } from "@mui/styles";
import ButtonGroup from "common/ButtonGroup";
const useStyles = makeStyles((theme: Theme) => { const useStyles = makeStyles((theme: Theme) => {
return ({ return ({
@ -280,54 +281,54 @@ export default function BinVisualizer(props: Props) {
const [storageDate, setStorageDate] = useState(moment().format("YYYY-MM-DD")); const [storageDate, setStorageDate] = useState(moment().format("YYYY-MM-DD"));
const [storageTime, setStorageTime] = useState(moment().format("HH:mm")); const [storageTime, setStorageTime] = useState(moment().format("HH:mm"));
const StyledToggle = styled(ToggleButton)(({ theme }: { theme: Theme }) => ({ // const StyledToggle = styled(ToggleButton)(({ theme }: { theme: Theme }) => ({
root: { // root: {
backgroundColor: "transparent", // backgroundColor: "transparent",
overflow: "visible", // overflow: "visible",
content: "content-box", // content: "content-box",
"&$selected": { // "&$selected": {
backgroundColor: "gold", // backgroundColor: "gold",
color: "black", // color: "black",
borderRadius: 24, // borderRadius: 24,
fontWeight: "bold" // fontWeight: "bold"
}, // },
"&$selected:hover": { // "&$selected:hover": {
backgroundColor: "rgb(255, 255, 0)", // backgroundColor: "rgb(255, 255, 0)",
color: "black", // color: "black",
borderRadius: 24 // borderRadius: 24
} // }
}, // },
selected: {} // selected: {}
})); // }));
const StyledToggleButtonGroup = styled(ToggleButtonGroup)(({ theme }: { theme: Theme }) => ({ // const StyledToggleButtonGroup = styled(ToggleButtonGroup)(({ theme }: { theme: Theme }) => ({
grouped: { // grouped: {
margin: theme.spacing(-0.5), // margin: theme.spacing(-0.5),
border: "none", // border: "none",
padding: theme.spacing(1), // padding: theme.spacing(1),
"&:not(:first-child):not(:last-child)": { // "&:not(:first-child):not(:last-child)": {
borderRadius: 24, // borderRadius: 24,
marginRight: theme.spacing(0.5), // marginRight: theme.spacing(0.5),
marginLeft: theme.spacing(0.5) // marginLeft: theme.spacing(0.5)
}, // },
"&:first-child": { // "&:first-child": {
borderRadius: 24, // borderRadius: 24,
marginLeft: theme.spacing(0.25) // marginLeft: theme.spacing(0.25)
}, // },
"&:last-child": { // "&:last-child": {
borderRadius: 24, // borderRadius: 24,
marginRight: theme.spacing(0.25) // marginRight: theme.spacing(0.25)
} // }
}, // },
root: { // root: {
backgroundColor: darken( // backgroundColor: darken(
theme.palette.background.paper, // theme.palette.background.paper,
getThemeType() === "light" ? 0.05 : 0.25 // getThemeType() === "light" ? 0.05 : 0.25
), // ),
borderRadius: 24, // borderRadius: 24,
content: "border-box" // content: "border-box"
} // }
})); // }));
useEffect(() => { useEffect(() => {
setModeTime(moment(bin.status.lastModeChange)); 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 binMode = () => {
const mode = bin.settings.mode; const mode = bin.settings.mode;
return ( return (
@ -1720,7 +1733,31 @@ export default function BinVisualizer(props: Props) {
<Typography variant="subtitle1" style={{ fontWeight: 800 }}> <Typography variant="subtitle1" style={{ fontWeight: 800 }}>
Bin Mode Bin Mode
</Typography> </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" id="tour-bin-mode"
value={mode} value={mode}
exclusive exclusive
@ -1754,7 +1791,7 @@ export default function BinVisualizer(props: Props) {
Drying Drying
</StyledToggle> </StyledToggle>
)} )}
</StyledToggleButtonGroup> </StyledToggleButtonGroup> */}
</Box> </Box>
); );
}; };
@ -2067,6 +2104,7 @@ export default function BinVisualizer(props: Props) {
devices={devices} devices={devices}
refreshCallback={success => { refreshCallback={success => {
setShowInputMoisture(false); setShowInputMoisture(false);
setNewPreset(0)
if (success) updateBin(); if (success) updateBin();
}} }}
parentPreset={newPreset} parentPreset={newPreset}

View file

@ -130,6 +130,7 @@ const useStyles = makeStyles((theme: Theme) => {
borderTopLeftRadius: "6px", borderTopLeftRadius: "6px",
borderTopRightRadius: "6px", borderTopRightRadius: "6px",
background: theme.palette.background.default, background: theme.palette.background.default,
fontWeight: 1000,
transform: "0.3s ease-in-out", transform: "0.3s ease-in-out",
// "linear-gradient(rgba(150, 150, 150, 0.3)," // "linear-gradient(rgba(150, 150, 150, 0.3),"
// + theme.palette.background.default + " 80%)" // + theme.palette.background.default + " 80%)"

View file

@ -123,10 +123,11 @@ export default function BinyardDisplay(props: Props) {
}); });
const loadBins = useCallback(() => { const loadBins = useCallback(() => {
setBinsLoading(true); if (yardKey === "") return
//load the bins based on the yard key //load the bins based on the yard key
setBinsLoading(true);
binAPI binAPI
.listBinsAndData(maxBins, 0, "asc", "name", undefined, as, undefined, [yardKey], ["binyard"]) .listBinsAndData(maxBins, 0, "asc", "name", undefined, undefined, undefined, [yardKey], ["binyard"])
.then(resp => { .then(resp => {
let yardBins: Bin[] = []; let yardBins: Bin[] = [];
let grainBins: Bin[] = []; let grainBins: Bin[] = [];
@ -315,17 +316,10 @@ export default function BinyardDisplay(props: Props) {
<Grid size={12}> <Grid size={12}>
<BinsList <BinsList
gridView gridView
valDisplay="average"
bins={grainBins} bins={grainBins}
duplicateBin={duplicateBin} duplicateBin={duplicateBin}
title={"Grain Bins"} 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> </Grid>
</React.Fragment> </React.Fragment>
@ -338,16 +332,10 @@ export default function BinyardDisplay(props: Props) {
<Grid size={12}> <Grid size={12}>
<BinsList <BinsList
gridView gridView
valDisplay="average"
bins={fertBins} bins={fertBins}
duplicateBin={duplicateBin} duplicateBin={duplicateBin}
title={"Fertilizer Bins"} 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> </Grid>
</React.Fragment> </React.Fragment>
@ -360,6 +348,7 @@ export default function BinyardDisplay(props: Props) {
<Grid size={12}> <Grid size={12}>
<BinsList <BinsList
gridView gridView
valDisplay="average"
bins={emptyBins} bins={emptyBins}
duplicateBin={duplicateBin} duplicateBin={duplicateBin}
title={"Empty Bins"} title={"Empty Bins"}

161
src/common/ButtonGroup.tsx Normal file
View 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>
)
}

View file

@ -133,7 +133,7 @@ export default function DisplayDrawer(props: Props) {
</Grid> </Grid>
<Grid> <Grid>
<Box> <Box>
<Typography align="center" variant="h5"> <Typography sx={{ color: "black", fontWeight: 650}} align="center" variant="h5">
{title} {title}
</Typography> </Typography>
</Box> </Box>

View file

@ -3,7 +3,7 @@ import { Beenhere } from "@mui/icons-material";
import { useThemeType } from "hooks"; import { useThemeType } from "hooks";
import moment from "moment"; import moment from "moment";
import React, { useEffect, useState } from "react"; 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; const MAPBOX_TOKEN = import.meta.env.VITE_MAPBOX_ACCESS_TOKEN;

View file

@ -7,11 +7,12 @@ import {
LinkProps, LinkProps,
Skeleton, Skeleton,
Theme, Theme,
Typography,
useTheme useTheme
} from "@mui/material"; } from "@mui/material";
import { Replay } from "@mui/icons-material"; import { Replay } from "@mui/icons-material";
import { useMobile } from "hooks"; import { useMobile } from "hooks";
import { ReactNode, useEffect, useRef } from "react"; import { ReactNode } from "react";
import { useLocation } from "react-router"; import { useLocation } from "react-router";
import { Link as RouterLink } from "react-router-dom"; import { Link as RouterLink } from "react-router-dom";
import { or } from "utils/types"; import { or } from "utils/types";
@ -57,11 +58,13 @@ interface Props {
paddingTop?: number; paddingTop?: number;
paddingBottom?: number; paddingBottom?: number;
prependPaths?: string[]; prependPaths?: string[];
state?: any;
} }
interface LinkRouterProps extends LinkProps { interface LinkRouterProps extends LinkProps {
to: string; to: string;
replace?: boolean; replace?: boolean;
state?: any;
} }
const LinkRouter = (props: LinkRouterProps) => <Link {...props} component={RouterLink as 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 location = useLocation();
const isMobile = useMobile(); const isMobile = useMobile();
const classes = useStyles(); const classes = useStyles();
const { loading, reload } = props; const { loading, reload, state } = props;
const groupID = (): string => { const groupID = (): string => {
let index = prependPaths?.indexOf("groups") let index = prependPaths?.indexOf("groups")
@ -289,7 +292,7 @@ export default function SmartBreadcrumb(props: Props) {
if (prependPaths) pathnames.splice(pathnames.length - 1, 0, ...prependPaths); 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 lastPath = index === pathnames.length - 1;
const to = `/${pathnames.slice(0, index + 1).join("/")}`; const to = `/${pathnames.slice(0, index + 1).join("/")}`;
@ -310,6 +313,21 @@ export default function SmartBreadcrumb(props: Props) {
links.push(linkComponent(to, label, lastPath)); 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; return links;
}; };
@ -324,6 +342,7 @@ export default function SmartBreadcrumb(props: Props) {
color={lastPath ? "textPrimary" : "textSecondary"} color={lastPath ? "textPrimary" : "textSecondary"}
variant="subtitle1" variant="subtitle1"
to={to} to={to}
state={state}
sx={{ '&:hover': { color: getThemeType() === "dark" ? 'white' : "black" }}} sx={{ '&:hover': { color: getThemeType() === "dark" ? 'white' : "black" }}}
className={classes.textOverflow}> className={classes.textOverflow}>
{label} {label}

View file

@ -176,12 +176,12 @@ export default function StatusPlenum(props: Props) {
> >
<Grid2> <Grid2>
<Typography variant="body2" style={{ color: blue[300]}}> <Typography variant="body2" style={{ color: blue[300]}}>
{device.status.sen5x?.voc.toFixed(1)}% V: {device.status.sen5x?.voc.toFixed(1)}%
</Typography> </Typography>
</Grid2> </Grid2>
<Grid2> <Grid2>
<Typography variant="body2" style={{ color: blue[400]}}> <Typography variant="body2" style={{ color: blue[400]}}>
{device.status.sen5x?.nox.toFixed(1)}% N: {device.status.sen5x?.nox.toFixed(1)}%
</Typography> </Typography>
</Grid2> </Grid2>
</Grid2> </Grid2>

View file

@ -2,7 +2,8 @@ import {
Button, Button,
DialogActions, DialogActions,
DialogContent, DialogContent,
Grid, Grid2 as Grid,
TextField,
Theme, Theme,
Typography Typography
} from "@mui/material"; } from "@mui/material";
@ -13,6 +14,8 @@ import ResponsiveDialog from "common/ResponsiveDialog";
import { DateRangePreset, GetDefaultDateRange, SetDefaultPreset } from "./DateRange"; import { DateRangePreset, GetDefaultDateRange, SetDefaultPreset } from "./DateRange";
import { useThemeType } from "hooks"; import { useThemeType } from "hooks";
import { makeStyles } from "@mui/styles"; import { makeStyles } from "@mui/styles";
import { cloneDeep } from "lodash";
import { color } from "@mui/system";
interface Props { interface Props {
startDate: Moment; startDate: Moment;
@ -26,10 +29,12 @@ type DateRange<T> = [T | null, T | null];
const useStyles = makeStyles((_theme: Theme) => { const useStyles = makeStyles((_theme: Theme) => {
return ({ return ({
activeButtonLight: { activeButtonLight: {
border: "1px solid black" border: "1px solid black",
color: "black"
}, },
acitveButtonDark: { acitveButtonDark: {
border: "1px solid white" border: "1px solid white",
color: "white"
} }
}) })
}); });
@ -49,7 +54,7 @@ export default function TimeBar(props: Props) {
const submitDateRange = () => { const submitDateRange = () => {
const startDate = dateRange[0] ? dateRange[0].clone() : moment(); 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); updateDateRange(startDate, endDate, false);
setDateRangeDialog(false); setDateRangeDialog(false);
setActiveView("selectRange"); setActiveView("selectRange");
@ -85,19 +90,30 @@ export default function TimeBar(props: Props) {
onClose={() => setDateRangeDialog(false)} onClose={() => setDateRangeDialog(false)}
aria-labelledby="date-range-dialog"> aria-labelledby="date-range-dialog">
<DialogContent> <DialogContent>
{/* <StaticDateRangePicker <TextField
disableFuture fullWidth
value={dateRange} margin="normal"
onChange={date => setDateRange(date)} type="date"
renderInput={(startProps, endProps) => ( label="Start Time"
<React.Fragment> value={dateRange[0]?.format("YYYY-MM-DD")}
<TextField {...startProps} /> onChange={e => {
<DateRangeDelimiter> to </DateRangeDelimiter> let range = cloneDeep(dateRange)
<TextField {...endProps} /> range[0] = moment(e.target.value)
</React.Fragment> setDateRange(range)
)} }}
/> */} />
DATE PICKER GOES HERE <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> </DialogContent>
<DialogActions> <DialogActions>
<Button onClick={() => setDateRangeDialog(false)} color="primary"> <Button onClick={() => setDateRangeDialog(false)} color="primary">
@ -115,7 +131,7 @@ export default function TimeBar(props: Props) {
<React.Fragment> <React.Fragment>
{datePickerDialog()} {datePickerDialog()}
<Grid container direction="row" spacing={1} wrap="nowrap"> <Grid container direction="row" spacing={1} wrap="nowrap">
<Grid item> <Grid>
<Button <Button
style={{ borderRadius: "25px" }} style={{ borderRadius: "25px" }}
className={ className={
@ -132,7 +148,7 @@ export default function TimeBar(props: Props) {
<Typography style={{ fontWeight: 650 }}>1H</Typography> <Typography style={{ fontWeight: 650 }}>1H</Typography>
</Button> </Button>
</Grid> </Grid>
<Grid item> <Grid>
<Button <Button
style={{ borderRadius: "25px" }} style={{ borderRadius: "25px" }}
className={ className={
@ -149,7 +165,7 @@ export default function TimeBar(props: Props) {
<Typography style={{ fontWeight: 650 }}>1D</Typography> <Typography style={{ fontWeight: 650 }}>1D</Typography>
</Button> </Button>
</Grid> </Grid>
<Grid item> <Grid>
<Button <Button
style={{ borderRadius: "25px" }} style={{ borderRadius: "25px" }}
className={ className={
@ -166,7 +182,7 @@ export default function TimeBar(props: Props) {
<Typography style={{ fontWeight: 650 }}>1W</Typography> <Typography style={{ fontWeight: 650 }}>1W</Typography>
</Button> </Button>
</Grid> </Grid>
<Grid item> <Grid>
<Button <Button
style={{ borderRadius: "25px" }} style={{ borderRadius: "25px" }}
className={ className={
@ -183,7 +199,7 @@ export default function TimeBar(props: Props) {
<Typography style={{ fontWeight: 650 }}>1M</Typography> <Typography style={{ fontWeight: 650 }}>1M</Typography>
</Button> </Button>
</Grid> </Grid>
<Grid item> <Grid>
<Button <Button
style={{ borderRadius: "25px" }} style={{ borderRadius: "25px" }}
className={ className={

View file

@ -114,7 +114,10 @@ export default function ComponentActions(props: Props) {
onClose={closeMoreMenu} onClose={closeMoreMenu}
disableAutoFocusItem> disableAutoFocusItem>
{canAddInteraction && ( {canAddInteraction && (
<MenuItem dense onClick={() => setIsAddInteractionOpen(true)} divider> <MenuItem dense onClick={() => {
setIsAddInteractionOpen(true)
closeMoreMenu()
}} divider>
<ListItemIcon> <ListItemIcon>
<AddCircle className={classes.addIcon} /> <AddCircle className={classes.addIcon} />
</ListItemIcon> </ListItemIcon>
@ -126,6 +129,7 @@ export default function ComponentActions(props: Props) {
onClick={() => { onClick={() => {
setIsJSON(false); setIsJSON(false);
setIsExportDataOpen(true); setIsExportDataOpen(true);
closeMoreMenu()
}}> }}>
<ListItemIcon> <ListItemIcon>
<SaveAlt className={classes.exportDataIcon} /> <SaveAlt className={classes.exportDataIcon} />
@ -134,7 +138,10 @@ export default function ComponentActions(props: Props) {
</MenuItem> </MenuItem>
{canEdit && <Divider />} {canEdit && <Divider />}
{canEdit && ( {canEdit && (
<MenuItem dense onClick={() => openComponentSettingsDialog("remove")}> <MenuItem dense onClick={() => {
openComponentSettingsDialog("remove")
setAnchorEl(null);
}}>
<ListItemIcon> <ListItemIcon>
<DeleteOutline className={classes.red} /> <DeleteOutline className={classes.red} />
</ListItemIcon> </ListItemIcon>

View file

@ -254,15 +254,15 @@ export default function ComponentCard(props: Props) {
size="small" size="small"
onTouchStart={event => { onTouchStart={event => {
event.stopPropagation(); event.stopPropagation();
event.preventDefault(); //event.preventDefault();
}} }}
onMouseDown={event => { onMouseDown={event => {
event.stopPropagation(); event.stopPropagation();
event.preventDefault(); //event.preventDefault();
}} }}
onClick={event => { onClick={event => {
event.stopPropagation(); event.stopPropagation();
event.preventDefault(); //event.preventDefault();
}} }}
disabled={!canEdit} disabled={!canEdit}
onChange={(_, checked) => updateControllerState(checked)} onChange={(_, checked) => updateControllerState(checked)}

View file

@ -237,8 +237,7 @@ export default function ComponentForm(props: Props) {
}; };
useEffect(() => { useEffect(() => {
let comp = tidyComponent(form.component); componentChanged(form.component, isFormValid());
componentChanged(comp, isFormValid());
}, [form.component, componentChanged]); // eslint-disable-line react-hooks/exhaustive-deps }, [form.component, componentChanged]); // eslint-disable-line react-hooks/exhaustive-deps
const isNameValid = (name: string): boolean => { const isNameValid = (name: string): boolean => {

View file

@ -24,7 +24,8 @@ import {
AccountCircle as ObjectUsersIcon, AccountCircle as ObjectUsersIcon,
SupervisedUserCircle as ObjectTeamsIcon, SupervisedUserCircle as ObjectTeamsIcon,
Sync as SyncDeviceIcon, Sync as SyncDeviceIcon,
Wifi as WifiIcon Wifi as WifiIcon,
Visibility
} from "@mui/icons-material"; } from "@mui/icons-material";
import { Skeleton } from "@mui/material"; import { Skeleton } from "@mui/material";
// import Datadog from "assets/external/datadog.png"; // import Datadog from "assets/external/datadog.png";
@ -47,7 +48,7 @@ import { Component, Device, deviceScope, Interaction } from "models";
// import { isShareableLink } from "pbHelpers/Device"; // import { isShareableLink } from "pbHelpers/Device";
import { pond } from "protobuf-ts/pond"; import { pond } from "protobuf-ts/pond";
import { useGlobalState } from "providers"; import { useGlobalState } from "providers";
import React, { useState } from "react"; import React, { useEffect, useState } from "react";
// import { useHistory, useRouteMatch } from "react-router"; // import { useHistory, useRouteMatch } from "react-router";
import { isOffline } from "utils/environment"; import { isOffline } from "utils/environment";
import { or } from "utils/types"; import { or } from "utils/types";
@ -70,6 +71,8 @@ import SaveDeviceProfile from "./SaveDeviceProfile";
import Connection from "./Connection"; import Connection from "./Connection";
import ComponentOrder from "component/ComponentOrder"; import ComponentOrder from "component/ComponentOrder";
import ArcGISDeviceData from "./ArcGISDeviceData"; import ArcGISDeviceData from "./ArcGISDeviceData";
import UpgradeDevice from "./UpgradeDevice";
import { DevicePageData } from "pages/Device";
const useStyles = makeStyles((_theme: Theme) => { const useStyles = makeStyles((_theme: Theme) => {
// const isMobile = useMobile() // const isMobile = useMobile()
@ -168,6 +171,19 @@ export default function DeviceActions(props: Props) {
isJsonDataDialogOpen: false 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) => { const openDialog = (target: keyof DialogState) => {
let updatedDialogState = cloneDeep(dialogState); let updatedDialogState = cloneDeep(dialogState);
updatedDialogState[target] = true; 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 = () => { const buttons = () => {
return ( return (
<React.Fragment> <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) && ( */} {/* {!isShareableLink(match.params.deviceID) && ( */}
<NotificationButton <NotificationButton
notify={preferences.notify} notify={preferences.notify}

View 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>
);
}

View file

@ -22,6 +22,7 @@ import GateSVG from "./GateSVG";
import GateGraphs from "./GateGraphs"; import GateGraphs from "./GateGraphs";
//import { ToggleButton, ToggleButtonGroup } from "@material-ui/lab"; //import { ToggleButton, ToggleButtonGroup } from "@material-ui/lab";
import { getThemeType } from "theme"; import { getThemeType } from "theme";
import ButtonGroup from "common/ButtonGroup";
interface Props { interface Props {
gate: Gate; gate: Gate;
@ -30,6 +31,8 @@ interface Props {
drawerView?: boolean; drawerView?: boolean;
} }
export default function GateDevice(props: Props) { export default function GateDevice(props: Props) {
const { gate, comprehensiveDevice, linkedCompList, drawerView } = props; const { gate, comprehensiveDevice, linkedCompList, drawerView } = props;
const [{as}] = useGlobalState(); const [{as}] = useGlobalState();
@ -233,6 +236,7 @@ export default function GateDevice(props: Props) {
return ( return (
<Select <Select
id="ambientSelect" id="ambientSelect"
variant="standard"
value={ambientKey} value={ambientKey}
style={{ fontSize: 8 }} style={{ fontSize: 8 }}
onChange={e => { onChange={e => {
@ -265,6 +269,7 @@ export default function GateDevice(props: Props) {
return ( return (
<Select <Select
id="tempSelect" id="tempSelect"
variant="standard"
value={tempKey} value={tempKey}
style={{ fontSize: 8 }} style={{ fontSize: 8 }}
onChange={e => { onChange={e => {
@ -299,6 +304,7 @@ export default function GateDevice(props: Props) {
return ( return (
<Select <Select
id="pressureSelect" id="pressureSelect"
variant="standard"
value={pressureKey} value={pressureKey}
style={{ fontSize: 8 }} style={{ fontSize: 8 }}
onChange={e => { onChange={e => {
@ -334,9 +340,10 @@ export default function GateDevice(props: Props) {
container container
direction="row" direction="row"
alignContent="center" alignContent="center"
width="100%"
//alignItems="center" //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 <Box
paddingLeft={1} paddingLeft={1}
display="flex" display="flex"
@ -344,7 +351,20 @@ export default function GateDevice(props: Props) {
alignItems="center" alignItems="center"
marginBottom={2}> marginBottom={2}>
<Typography style={{ fontWeight: 650, fontSize: 20 }}>{device.name()}</Typography> <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 <ToggleButton
onClick={() => setDetail("analytics")} onClick={() => setDetail("analytics")}
value={"analytics"} value={"analytics"}
@ -357,7 +377,7 @@ export default function GateDevice(props: Props) {
aria-label="Sensor Graphs"> aria-label="Sensor Graphs">
Sensors Sensors
</ToggleButton> </ToggleButton>
</ToggleButtonGroup> </ToggleButtonGroup> */}
</Box> </Box>
<Card raised> <Card raised>
<GateSVG <GateSVG

View file

@ -355,13 +355,13 @@ export default function GateSVG(props: Props) {
{renderValues()} {renderValues()}
</g> </g>
<foreignObject height={20} width={120} x={10} y={83}> <foreignObject height={20} width={120} x={10} y={83}>
<Box style={{ position: "fixed" }}>{ambientSelector()}</Box> <Box sx={{ position: "fixed" }}>{ambientSelector()}</Box>
</foreignObject> </foreignObject>
<foreignObject height={20} width={120} x={10} y={118}> <foreignObject height={20} width={120} x={10} y={118}>
<Box style={{ position: "fixed" }}>{tempChainSelector()}</Box> <Box sx={{ position: "fixed" }}>{tempChainSelector()}</Box>
</foreignObject> </foreignObject>
<foreignObject height={20} width={120} x={10} y={153}> <foreignObject height={20} width={120} x={10} y={153}>
<Box style={{ position: "fixed" }}>{pressureChainSelector()}</Box> <Box sx={{ position: "fixed" }}>{pressureChainSelector()}</Box>
</foreignObject> </foreignObject>
{pcaRed()} {pcaRed()}
{pcaGreen()} {pcaGreen()}

View file

@ -33,7 +33,7 @@ import { GeometryMapping, shapeFromCoords } from "models/GeometryMapping";
import { FeatureCollection, Feature } from "geojson"; import { FeatureCollection, Feature } from "geojson";
import DrawController from "./mapControllers/drawController"; import DrawController from "./mapControllers/drawController";
//import { Geometry } from "geojson"; //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 { getDistanceUnit } from "utils";
import { MapMouseEvent } from "mapbox-gl"; import { MapMouseEvent } from "mapbox-gl";
import { makeStyles } from "@mui/styles"; import { makeStyles } from "@mui/styles";

View file

@ -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 MapboxGeocoder, { Result } from "@mapbox/mapbox-gl-geocoder";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";

View file

@ -1,5 +1,5 @@
import { useEffect, useRef } from "react"; 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 { FeatureCollection } from "geojson";
import MapboxDraw from "@mapbox/mapbox-gl-draw"; import MapboxDraw from "@mapbox/mapbox-gl-draw";

View file

@ -1,5 +1,5 @@
import React, { useEffect } from "react"; 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"; import { FeatureCollection } from "geojson";
interface Props { interface Props {

View file

@ -5,7 +5,7 @@ import { useMobile } from "hooks";
import { clone } from "lodash"; import { clone } from "lodash";
import { pond } from "protobuf-ts/pond"; import { pond } from "protobuf-ts/pond";
import React, { useCallback, useEffect, useState } from "react"; 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 //interface for markers
export interface MarkerData { export interface MarkerData {

View file

@ -703,7 +703,12 @@ import { Result } from "@mapbox/mapbox-gl-geocoder";
</Grid> </Grid>
</Grid> </Grid>
<Grid size={3}> <Grid size={3}>
<Button style={{ margin: "auto" }} onClick={() => setOpenFMSettings(true)}> <Button
style={{ margin: "auto" }}
onClick={() => {
setOpenFMSettings(true)
setAnchorFieldMarker(null)
}}>
<Edit /> <Edit />
</Button> </Button>
</Grid> </Grid>

32
src/models/Upgrade.ts Normal file
View 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;
}
}

View file

@ -11,7 +11,7 @@ export * from "./ShareableLink";
export * from "./Site"; export * from "./Site";
export * from "./Tag"; export * from "./Tag";
export * from "./Task"; export * from "./Task";
// export * from "./Upgrade"; export * from "./Upgrade";
// export * from "./User"; // export * from "./User";
export * from "./user"; export * from "./user";
export * from "./Field"; export * from "./Field";

View file

@ -105,7 +105,7 @@ export default function BottomNavigator(props: Props) {
return ( return (
<BottomNavigation value={route} onChange={(_, newValue) => handleRouteChange(newValue)}> <BottomNavigation value={route} onChange={(_, newValue) => handleRouteChange(newValue)}>
{isAdaptive && ( {isAdaptive && (
<BottomNavigationAction label="Farm" icon={<FieldsIcon type={getType()} />} value="fields" /> <BottomNavigationAction label="Farm" icon={<FieldsIcon type={getType()} />} value="visualFarm" />
)} )}
{isAdaptive && ( {isAdaptive && (
<BottomNavigationAction label="Bins" icon={<BinsIcon type={getType()} />} value="bins" /> <BottomNavigationAction label="Bins" icon={<BinsIcon type={getType()} />} value="bins" />
@ -114,7 +114,7 @@ export default function BottomNavigator(props: Props) {
<BottomNavigationAction <BottomNavigationAction
label="Site Map" label="Site Map"
icon={<FieldsIcon type={getType()} />} icon={<FieldsIcon type={getType()} />}
value="constructionsiteMap" value="constructionMap"
/> />
)} )}
{isOmni && ( {isOmni && (

View file

@ -8,6 +8,7 @@ import { ErrorBoundary } from "react-error-boundary";
import { getWhitelabel } from "services/whiteLabel"; import { getWhitelabel } from "services/whiteLabel";
import Ventilation from "pages/VentEditor"; import Ventilation from "pages/VentEditor";
import { useGlobalState } from "providers"; import { useGlobalState } from "providers";
import DeviceSupport from "pages/DeviceSupport";
const DeviceHistory = lazy(() => import("pages/DeviceHistory")); const DeviceHistory = lazy(() => import("pages/DeviceHistory"));
const DevicePage = lazy(() => import("pages/Device")); const DevicePage = lazy(() => import("pages/Device"));
@ -103,6 +104,10 @@ export default function Router() {
path="/:deviceID" // "/settings/basic" path="/:deviceID" // "/settings/basic"
element={<DevicePage />} element={<DevicePage />}
/> />
<Route
path="/:deviceID/support" // "/settings/basic"
element={<DeviceSupport />}
/>
<Route <Route
path="/:deviceID/components/:componentID" // "/settings/basic" path="/:deviceID/components/:componentID" // "/settings/basic"
element={<DeviceComponent />} element={<DeviceComponent />}

View file

@ -160,84 +160,6 @@ export default function SideNavigator(props: Props) {
const isAdCon = IsAdCon() const isAdCon = IsAdCon()
return ( return (
<List className={classes.list} component="nav"> <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")) && ( {(isAg || user.hasFeature("admin")) && (
<Tooltip title="Visual Farm" placement="right"> <Tooltip title="Visual Farm" placement="right">
<ListItemButton <ListItemButton
@ -252,20 +174,6 @@ export default function SideNavigator(props: Props) {
</ListItemButton> </ListItemButton>
</Tooltip> </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")) && ( {(isMiPCA || user.hasFeature("admin")) && (
<Tooltip title="Aviation Map" placement="right"> <Tooltip title="Aviation Map" placement="right">
<ListItemButton <ListItemButton
@ -294,6 +202,169 @@ export default function SideNavigator(props: Props) {
</ListItemButton> </ListItemButton>
</Tooltip> </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")) && ( {(isAdCon || user.hasFeature("admin")) && (
<Tooltip title="Jobsites" placement="right"> <Tooltip title="Jobsites" placement="right">
<ListItemButton <ListItemButton
@ -334,48 +405,7 @@ export default function SideNavigator(props: Props) {
{open && <ListItemText primary="Tasks" />} {open && <ListItemText primary="Tasks" />}
</ListItemButton> </ListItemButton>
</Tooltip> </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") && {user.hasFeature("developer") &&
<Tooltip title="API documentation" placement="right"> <Tooltip title="API documentation" placement="right">
<ListItemButton <ListItemButton
@ -390,34 +420,6 @@ export default function SideNavigator(props: Props) {
</ListItemButton> </ListItemButton>
</Tooltip> </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 && {user.hasFeature("admin") && window.NDEFReader &&
<Tooltip title="NFC" placement="right"> <Tooltip title="NFC" placement="right">
<ListItemButton <ListItemButton

View file

@ -66,6 +66,7 @@ import { makeStyles, styled } from "@mui/styles";
import { useNavigate, useParams } from "react-router-dom"; import { useNavigate, useParams } from "react-router-dom";
import { Controller } from "models/Controller"; import { Controller } from "models/Controller";
import TaskViewer from "tasks/TaskViewer"; import TaskViewer from "tasks/TaskViewer";
import ButtonGroup from "common/ButtonGroup";
interface TabPanelProps { interface TabPanelProps {
children?: React.ReactNode; children?: React.ReactNode;
@ -214,54 +215,54 @@ export default function Bin(props: Props) {
setValue(newValue); setValue(newValue);
}; };
const StyledToggle = styled(ToggleButton)(() => ({ // const StyledToggle = styled(ToggleButton)(() => ({
root: { // root: {
backgroundColor: "transparent", // backgroundColor: "transparent",
overflow: "visible", // overflow: "visible",
content: "content-box", // content: "content-box",
"&$selected": { // "&$selected": {
backgroundColor: "gold", // backgroundColor: "gold",
color: "black", // color: "black",
borderRadius: 24, // borderRadius: 24,
fontWeight: "bold" // fontWeight: "bold"
}, // },
"&$selected:hover": { // "&$selected:hover": {
backgroundColor: "rgb(255, 255, 0)", // backgroundColor: "rgb(255, 255, 0)",
color: "black", // color: "black",
borderRadius: 24 // borderRadius: 24
} // }
}, // },
selected: {} // selected: {}
})) // }))
const StyledToggleButtonGroup = styled(ToggleButtonGroup)(({ theme }: { theme: Theme }) => ({ // const StyledToggleButtonGroup = styled(ToggleButtonGroup)(({ theme }: { theme: Theme }) => ({
grouped: { // grouped: {
margin: theme.spacing(-0.5), // margin: theme.spacing(-0.5),
border: "none", // border: "none",
padding: theme.spacing(1), // padding: theme.spacing(1),
"&:not(:first-child):not(:last-child)": { // "&:not(:first-child):not(:last-child)": {
borderRadius: 24, // borderRadius: 24,
marginRight: theme.spacing(0.5), // marginRight: theme.spacing(0.5),
marginLeft: theme.spacing(0.5) // marginLeft: theme.spacing(0.5)
}, // },
"&:first-child": { // "&:first-child": {
borderRadius: 24, // borderRadius: 24,
marginLeft: theme.spacing(0.25) // marginLeft: theme.spacing(0.25)
}, // },
"&:last-child": { // "&:last-child": {
borderRadius: 24, // borderRadius: 24,
marginRight: theme.spacing(0.25) // marginRight: theme.spacing(0.25)
} // }
}, // },
root: { // root: {
backgroundColor: darken( // backgroundColor: darken(
theme.palette.background.paper, // theme.palette.background.paper,
getThemeType() === "light" ? 0.05 : 0.25 // getThemeType() === "light" ? 0.05 : 0.25
), // ),
borderRadius: 24, // borderRadius: 24,
content: "border-box" // content: "border-box"
} // }
})) // }))
const load = useCallback(() => { const load = useCallback(() => {
if (loadRef.current || user.id() === "") return; if (loadRef.current || user.id() === "") return;
@ -547,7 +548,6 @@ export default function Bin(props: Props) {
}; };
const tasks = () => { const tasks = () => {
console.log(showTasks())
if (showTasks()) { if (showTasks()) {
return ( return (
<React.Fragment> <React.Fragment>
@ -816,9 +816,9 @@ export default function Bin(props: Props) {
const desktopView = () => { const desktopView = () => {
return ( return (
<Card style={{ padding: 15 }}> <Card sx={{ padding: 1 }}>
<Grid container spacing={2} direction={"row"} justifyContent="center" alignItems="stretch"> <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 <Box
paddingX={1} paddingX={1}
marginBottom={1} marginBottom={1}
@ -844,7 +844,7 @@ export default function Bin(props: Props) {
{overview()} {overview()}
{preferences && binComponents(preferences)} {preferences && binComponents(preferences)}
</Grid> </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_DRYING ||
bin.settings.mode === pond.BinMode.BIN_MODE_HYDRATING) && ( bin.settings.mode === pond.BinMode.BIN_MODE_HYDRATING) && (
<BinConditioningCard <BinConditioningCard
@ -862,9 +862,34 @@ export default function Bin(props: Props) {
<BinStorageConditions bin={bin} headspaceCO2={headspaceCO2} cables={grainCables} /> <BinStorageConditions bin={bin} headspaceCO2={headspaceCO2} cables={grainCables} />
</Box> </Box>
</Grid> </Grid>
<Grid id="tour-graphs" size={{ xs: 12, sm: 12, md: 12, lg: 6 }}> <Grid id="tour-graphs" size={{ xs: 12, sm: 12, md: 12, lg: 5.3 }}>
<Box paddingBottom={2}> <Box paddingBottom={2} display="flex">
<StyledToggleButtonGroup <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" id="tour-graph-tabs"
value={detail} value={detail}
exclusive exclusive
@ -900,7 +925,7 @@ export default function Bin(props: Props) {
aria-label="Bin Mode Presets"> aria-label="Bin Mode Presets">
Presets Presets
</StyledToggle> </StyledToggle>
</StyledToggleButtonGroup> </StyledToggleButtonGroup> */}
</Box> </Box>
{detail === "alerts" && ( {detail === "alerts" && (
<Box> <Box>
@ -1089,11 +1114,12 @@ export default function Bin(props: Props) {
}; };
return ( return (
<PageContainer> <PageContainer padding={1}>
{showPlenumError()} {showPlenumError()}
<Box <Box
paddingBottom={isMobile || displayMobile ? 1 : 2} paddingBottom={isMobile || displayMobile ? 1 : 2}
paddingX={isMobile || displayMobile ? 1 : 2}> paddingX={1}
>
{tabs()} {tabs()}
{isMobile && ( {isMobile && (
<Box <Box

View file

@ -81,6 +81,7 @@ import { getThemeType } from "theme/themeType";
import GrainBagSettings from "grainBag/grainBagSettings"; import GrainBagSettings from "grainBag/grainBagSettings";
import GrainBagList from "grainBag/grainBagList"; import GrainBagList from "grainBag/grainBagList";
import BinYards from "bin/BinYards"; import BinYards from "bin/BinYards";
import ButtonGroup from "common/ButtonGroup";
// import { useHistory } from "react-router"; // import { useHistory } from "react-router";
const useStyles = makeStyles((theme: Theme) => { const useStyles = makeStyles((theme: Theme) => {
@ -200,56 +201,56 @@ export default function Bins(props: Props) {
// fertilizer: 0 // fertilizer: 0
// }); // });
const StyledToggleButtonGroup = styled(ToggleButtonGroup)(({ theme }: { theme: Theme }) => ({ // const StyledToggleButtonGroup = styled(ToggleButtonGroup)(({ theme }: { theme: Theme }) => ({
'& .MuiToggleButtonGroup-grouped': { // '& .MuiToggleButtonGroup-grouped': {
// border: 'none' replaces the old 'grouped' border: "none" // // border: 'none' replaces the old 'grouped' border: "none"
border: 'none', // border: 'none',
padding: theme.spacing(1), // padding: theme.spacing(1),
// Middle buttons // // Middle buttons
'&:not(:first-child):not(:last-child)': { // '&:not(:first-child):not(:last-child)': {
borderRadius: 24, // borderRadius: 24,
marginRight: theme.spacing(0.5), // marginRight: theme.spacing(0.5),
marginLeft: theme.spacing(0.5), // marginLeft: theme.spacing(0.5),
}, // },
// First button // // First button
'&:first-child': { // '&:first-child': {
borderRadius: 24, // borderRadius: 24,
marginLeft: theme.spacing(0.25), // marginLeft: theme.spacing(0.25),
}, // },
// Last button // // Last button
'&:last-child': { // '&:last-child': {
borderRadius: 24, // borderRadius: 24,
marginRight: theme.spacing(0.25), // marginRight: theme.spacing(0.25),
}, // },
}, // },
// Root styles // // Root styles
backgroundColor: darken( // backgroundColor: darken(
theme.palette.background.paper, // theme.palette.background.paper,
getThemeType() === 'light' ? 0.05 : 0.25 // getThemeType() === 'light' ? 0.05 : 0.25
), // ),
borderRadius: 24, // borderRadius: 24,
boxSizing: 'content-box', // Fixed 'content: "border-box"' typo from original // boxSizing: 'content-box', // Fixed 'content: "border-box"' typo from original
})); // }));
const StyledToggle = styled(ToggleButton)(({ theme }: { theme: Theme }) => ({ // const StyledToggle = styled(ToggleButton)(({ theme }: { theme: Theme }) => ({
root: { // root: {
backgroundColor: "transparent", // backgroundColor: "transparent",
overflow: "visible", // overflow: "visible",
content: "content-box", // content: "content-box",
"&$selected": { // "&$selected": {
backgroundColor: "gold", // backgroundColor: "gold",
color: "black", // color: "black",
borderRadius: 24, // borderRadius: 24,
fontWeight: "bold" // fontWeight: "bold"
}, // },
"&$selected:hover": { // "&$selected:hover": {
backgroundColor: "rgb(255, 255, 0)", // backgroundColor: "rgb(255, 255, 0)",
color: "black", // color: "black",
borderRadius: 24 // borderRadius: 24
} // }
}, // },
selected: {} // selected: {}
})); // }));
useEffect(() => { useEffect(() => {
let ebt = sessionStorage.getItem("expandBinTotal"); let ebt = sessionStorage.getItem("expandBinTotal");
@ -502,6 +503,17 @@ export default function Bins(props: Props) {
} }
}, [loadBins, props.insert, contentFilter]); }, [loadBins, props.insert, contentFilter]);
const determineNodeToggle = () => {
switch(cardValDisplay) {
case "low":
return [0]
case "high":
return [2]
default:
return [1]
}
}
const duplicateBin = (bin: Bin) => { const duplicateBin = (bin: Bin) => {
binAPI.addBin(bin.settings, as).then(resp => { binAPI.addBin(bin.settings, as).then(resp => {
loadBins(); loadBins();
@ -1154,7 +1166,25 @@ export default function Bins(props: Props) {
Load All Load All
</Button> </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" id="cardValueDisplay"
value={cardValDisplay} value={cardValDisplay}
exclusive exclusive
@ -1184,12 +1214,33 @@ export default function Bins(props: Props) {
}}> }}>
High High
</StyledToggle> </StyledToggle>
</StyledToggleButtonGroup> </StyledToggleButtonGroup> */}
</Box> </Box>
</Grid> </Grid>
<Grid > <Grid >
<Box display="flex" paddingY={1} alignContent="center" alignItems="center"> <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" id="tour-graph-tabs"
value={binView} value={binView}
exclusive exclusive
@ -1203,7 +1254,7 @@ export default function Bins(props: Props) {
sessionStorage.setItem("binsView", "grid"); sessionStorage.setItem("binsView", "grid");
}}> }}>
<ViewComfy /> <ViewComfy />
</StyledToggle> </StyledToggle> */}
{/* hidden at dustins request so that grid view and list are the only two */} {/* hidden at dustins request so that grid view and list are the only two */}
{/* <StyledToggle {/* <StyledToggle
value={"scroll"} value={"scroll"}
@ -1214,7 +1265,7 @@ export default function Bins(props: Props) {
}}> }}>
<ViewColumn /> <ViewColumn />
</StyledToggle> */} </StyledToggle> */}
<StyledToggle {/* <StyledToggle
value={"list"} value={"list"}
aria-label="list view" aria-label="list view"
onClick={() => { onClick={() => {
@ -1223,7 +1274,7 @@ export default function Bins(props: Props) {
}}> }}>
<ViewList /> <ViewList />
</StyledToggle> </StyledToggle>
</StyledToggleButtonGroup> </StyledToggleButtonGroup> */}
<MoreVert <MoreVert
className={classes.icon} className={classes.icon}
onClick={event => { onClick={event => {
@ -1261,7 +1312,28 @@ export default function Bins(props: Props) {
</Grid> </Grid>
<Grid > <Grid >
<Box display="flex" paddingY={1} alignContent="center" alignItems="center"> <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" id="tour-graph-tabs"
value={bagView} value={bagView}
exclusive exclusive
@ -1275,7 +1347,7 @@ export default function Bins(props: Props) {
sessionStorage.setItem("bagsView", "grid"); sessionStorage.setItem("bagsView", "grid");
}}> }}>
<ViewComfy /> <ViewComfy />
</StyledToggle> </StyledToggle> */}
{/* hidden at dustins request so that grid view and list are the only two */} {/* hidden at dustins request so that grid view and list are the only two */}
{/* <StyledToggle {/* <StyledToggle
value={"scroll"} value={"scroll"}
@ -1286,7 +1358,7 @@ export default function Bins(props: Props) {
}}> }}>
<ViewColumn /> <ViewColumn />
</StyledToggle> */} </StyledToggle> */}
<StyledToggle {/* <StyledToggle
value={"list"} value={"list"}
aria-label="list view" aria-label="list view"
onClick={() => { onClick={() => {
@ -1295,7 +1367,7 @@ export default function Bins(props: Props) {
}}> }}>
<ViewList /> <ViewList />
</StyledToggle> </StyledToggle>
</StyledToggleButtonGroup> </StyledToggleButtonGroup> */}
<IconButton <IconButton
className={classes.icon} className={classes.icon}
onClick={() => { onClick={() => {

View file

@ -22,6 +22,15 @@ import { or } from "utils";
import ComponentDiagnostics from "component/ComponentDiagnostics"; import ComponentDiagnostics from "component/ComponentDiagnostics";
import DeviceWizard from "device/DeviceWizard"; 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() { export default function DevicePage() {
const deviceAPI = useDeviceAPI() const deviceAPI = useDeviceAPI()
const snackbar = useSnackbar() const snackbar = useSnackbar()
@ -30,6 +39,7 @@ export default function DevicePage() {
const groupID = useParams<{ groupID: string }>()?.groupID ?? ""; const groupID = useParams<{ groupID: string }>()?.groupID ?? "";
const { state } = useLocation(); const { state } = useLocation();
const [{ as, team, user }] = useGlobalState() const [{ as, team, user }] = useGlobalState()
// console.log(state)
const [device, setDevice] = useState<Device>(state?.device ? Device.create(state.device) : Device.create()) const [device, setDevice] = useState<Device>(state?.device ? Device.create(state.device) : Device.create())
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
const [permissions, setPermissions] = useState<pond.Permission[]>([]) const [permissions, setPermissions] = useState<pond.Permission[]>([])
@ -51,11 +61,21 @@ export default function DevicePage() {
const [addComponentManualDialogOpen, setAddComponentManualDialogOpen] = useState(false) const [addComponentManualDialogOpen, setAddComponentManualDialogOpen] = useState(false)
// const [devicePageData, setDevicePageData] = useState(pond.GetDevicePageDataResponse.create())
const loadDevice = () => { const loadDevice = () => {
// console.log("load device page data")
if (loading) return 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) setLoading(true)
deviceAPI.getPageData(deviceID, getContextKeys(), getContextTypes(), as).then(resp => { deviceAPI.getPageData(deviceID, getContextKeys(), getContextTypes(), as).then(resp => {
// setDevicePageData(resp.data)
let device = Device.any(resp.data.device) let device = Device.any(resp.data.device)
// console.log(resp.data.device) // console.log(resp.data.device)
setDevice(device) setDevice(device)
@ -418,19 +438,9 @@ export default function DevicePage() {
<PageContainer padding={isMobile ? 0 : 2}> <PageContainer padding={isMobile ? 0 : 2}>
<Grid container justifyContent={"space-between"}> <Grid container justifyContent={"space-between"}>
<Grid> <Grid>
<SmartBreadcrumb /> <SmartBreadcrumb deviceName={device.name()} />
</Grid> </Grid>
<Grid> <Grid>
<AddComponentManualDialog
device={device.id()}
open={addComponentManualDialogOpen}
onClose={() => setAddComponentManualDialogOpen(false)}
/>
{user.hasFeature("developer") === true &&
<Button onClick={() => setAddComponentManualDialogOpen(true)}>
Manual Comp
</Button>
}
<DeviceActions <DeviceActions
device={device} device={device}
isPaused={false} isPaused={false}
@ -449,8 +459,6 @@ export default function DevicePage() {
<DeviceOverview <DeviceOverview
device={device} device={device}
components={[...components.values()]} components={[...components.values()]}
// components={[]}
// components={components}
usage={getUsage()} usage={getUsage()}
loading={loading} loading={loading}
groupID={parseInt(groupID)} groupID={parseInt(groupID)}

393
src/pages/DeviceSupport.tsx Normal file
View 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>
)
}

View file

@ -57,7 +57,14 @@ const useStyles = makeStyles((theme: Theme) => {
margin: theme.spacing(1), margin: theme.spacing(1),
marginLeft: theme.spacing(2), marginLeft: theme.spacing(2),
display: "flex", 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", // justifyContent: "center",
}, },
green: { green: {
@ -328,8 +335,8 @@ export default function Devices() {
<Box className={classes.descriptionCellContainer}> <Box className={classes.descriptionCellContainer}>
<Tooltip title={device.settings?.description}> <Tooltip title={device.settings?.description}>
<span> <span>
{description.length > 32 {description.length > 36
? description.substring(0, 32) + "..." ? description.substring(0, 36) + "..."
: description} : description}
</span> </span>
</Tooltip> </Tooltip>
@ -341,7 +348,7 @@ export default function Devices() {
title: "Tags", title: "Tags",
render: (device: Device) => { render: (device: Device) => {
return ( return (
<Grid2 container spacing={1} className={classes.descriptionCellContainer}> <Grid2 container spacing={1} className={classes.tagsCellContainer}>
{device.status.tags.map((tag: pond.TagSettings) => ( {device.status.tags.map((tag: pond.TagSettings) => (
<Grid2 key={"device-"+device.id()+"-tag-"+tag.key}> <Grid2 key={"device-"+device.id()+"-tag-"+tag.key}>
<TagUI tag={Tag.any({ settings: tag })} /> <TagUI tag={Tag.any({ settings: tag })} />

View file

@ -1,4 +1,3 @@
import React from "react";
import PageContainer from "./PageContainer"; import PageContainer from "./PageContainer";
import FieldList from "field/FieldList"; import FieldList from "field/FieldList";
import { Box } from "@mui/material"; import { Box } from "@mui/material";

View file

@ -206,7 +206,7 @@ export default function Firmware() {
// }; // };
const load = useCallback(() => { const load = useCallback(() => {
firmwareAPI.listFirmware(pageSize, pageSize * tablePage, "asc", "version", searchText) firmwareAPI.listFirmware(pageSize, pageSize * tablePage, "desc", "version", searchText)
.then((response) => { .then((response) => {
setTotal(response.data.total ? response.data.total : 0) setTotal(response.data.total ? response.data.total : 0)
let rows: FirmwareModel[] = or(response.data.firmware, []).map( let rows: FirmwareModel[] = or(response.data.firmware, []).map(

View file

@ -339,6 +339,7 @@ export default function Gate(props: Props) {
}} }}
style={{ height: 150, width: "100%", margin: -15 }}> style={{ height: 150, width: "100%", margin: -15 }}>
<Grid <Grid
width="100%"
container container
direction="row" direction="row"
spacing={2} spacing={2}

View file

@ -34,7 +34,7 @@ export const HTTPContext = createContext<IHTTPContext>({} as IHTTPContext);
export default function HTTPProvider(props: Props) { export default function HTTPProvider(props: Props) {
const { children, token } = props; const { children, token } = props;
const { isAuthenticated, /*loginWithRedirect,*/ loginWithPopup } = useAuth0(); const { isAuthenticated, loginWithRedirect } = useAuth0();
const defaultOptions = (demo: boolean = false) => { const defaultOptions = (demo: boolean = false) => {
if (demo || !isAuthenticated || !token) { if (demo || !isAuthenticated || !token) {
@ -79,7 +79,7 @@ export default function HTTPProvider(props: Props) {
function get<T>(url: string, spreadOptions?: AxiosRequestConfig): Promise<AxiosResponse<T>> { function get<T>(url: string, spreadOptions?: AxiosRequestConfig): Promise<AxiosResponse<T>> {
if (isTokenExpired(token)) { if (isTokenExpired(token)) {
loginWithPopup() loginWithRedirect()
} }
return axios.get(url, {...defaultOptions(), ...spreadOptions}); return axios.get(url, {...defaultOptions(), ...spreadOptions});
} }

View file

@ -1,6 +1,5 @@
import { useHTTP, usePermissionAPI } from "hooks"; import { useHTTP, usePermissionAPI } from "hooks";
// import { useWebsocket } from "websocket"; import { Component, deviceScope, User } from "models";
import { /*Component,*/ Component, deviceScope, Team, User } from "models";
import { pond } from "protobuf-ts/pond"; import { pond } from "protobuf-ts/pond";
import { createContext, PropsWithChildren, useContext } from "react"; import { createContext, PropsWithChildren, useContext } from "react";
import { pondURL } from "./pond"; import { pondURL } from "./pond";
@ -9,7 +8,6 @@ import { useGlobalState } from "providers";
import moment from "moment"; import moment from "moment";
import { or } from "utils/types"; import { or } from "utils/types";
import { dateRange } from "providers/http"; import { dateRange } from "providers/http";
// import { reject, result } from "lodash";
export interface IDeviceAPIContext { export interface IDeviceAPIContext {
add: (name: string, description: string, backpack: pond.BackpackSettings, otherTeam?: string) => Promise<AxiosResponse<pond.AddDeviceResponse>>; 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[] types?: string[]
) => { ) => {
let url = pondURL("/deviceStatistics"); let url = pondURL("/deviceStatistics");
if (types && types.length > 0) if (types && types.length > 0) {
url = pondURL( url = pondURL(
"/deviceStatistics/" + "/deviceStatistics/" +
(keys ? "?keys=" + keys.toString() : "") + (keys ? "?keys=" + keys.toString() : "") +
(types ? "&types=" + types.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) => { return new Promise<AxiosResponse<pond.GetDeviceStatisticsResponse>>((resolve, reject) => {
get<pond.GetDeviceStatisticsResponse>(url).then(resp => { get<pond.GetDeviceStatisticsResponse>(url).then(resp => {
resp.data = pond.GetDeviceStatisticsResponse.fromObject(resp.data) resp.data = pond.GetDeviceStatisticsResponse.fromObject(resp.data)

View file

@ -70,7 +70,7 @@ interface WhiteLabel {
// }; // };
const STAGING_WHITELABEL: WhiteLabel = { const STAGING_WHITELABEL: WhiteLabel = {
name: import.meta.env.VITE_APP_WEBSITE_TITLE, name: "Staging",
primaryColour: import.meta.env.VITE_APP_PRIMARY_COLOUR, primaryColour: import.meta.env.VITE_APP_PRIMARY_COLOUR,
secondaryColour: import.meta.env.VITE_APP_SECONDARY_COLOUR, secondaryColour: import.meta.env.VITE_APP_SECONDARY_COLOUR,
signatureColour: import.meta.env.VITE_APP_SIGNATURE_COLOUR, signatureColour: import.meta.env.VITE_APP_SIGNATURE_COLOUR,
@ -123,7 +123,7 @@ const ADAPTIVE_AGRICULTURE_WHITE_LABEL: WhiteLabel = {
// secondaryColour: "#FFFF00", // secondaryColour: "#FFFF00",
signatureColour: "#272727", signatureColour: "#272727",
signatureAccentColour: "#fff", 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, redirectOnLogout: true,
logoutRedirectTarget: "https://adaptiveagriculture.ca", logoutRedirectTarget: "https://adaptiveagriculture.ca",
darkLogo: AdapativeAgLogo, darkLogo: AdapativeAgLogo,
@ -163,7 +163,7 @@ const AEROGROW_WHITE_LABEL: WhiteLabel = {
secondaryColour: "cyan", secondaryColour: "cyan",
signatureColour: "#fff", signatureColour: "#fff",
signatureAccentColour: "#000", signatureAccentColour: "#000",
auth0ClientId: import.meta.env.REACT_APP_AUTH0_AEROGROW_CLIENT_ID, auth0ClientId: import.meta.env.VITE_AUTH0_AEROGROW_CLIENT_ID,
redirectOnLogout: true, redirectOnLogout: true,
logoutRedirectTarget: "https://www.aerogrowmanufacturing.com", logoutRedirectTarget: "https://www.aerogrowmanufacturing.com",
darkLogo: AeroGrowDarkLogo, darkLogo: AeroGrowDarkLogo,
@ -189,7 +189,7 @@ const MIVENT_WHITE_LABEL: WhiteLabel = {
secondaryColour: "yellow", secondaryColour: "yellow",
signatureColour: "#272727", signatureColour: "#272727",
signatureAccentColour: "#fff", signatureAccentColour: "#fff",
auth0ClientId: import.meta.env.REACT_APP_AUTH0_MIVENT_CLIENT_ID, auth0ClientId: import.meta.env.VITE_AUTH0_MIVENT_CLIENT_ID,
redirectOnLogout: true, redirectOnLogout: true,
logoutRedirectTarget: "https://mivent.ca", logoutRedirectTarget: "https://mivent.ca",
darkLogo: MiVentLightLogo, darkLogo: MiVentLightLogo,
@ -207,7 +207,7 @@ const ADAPTIVE_CONSTRUCTION_WHITE_LABEL: WhiteLabel = {
secondaryColour: "blue", secondaryColour: "blue",
signatureColour: "#272727", signatureColour: "#272727",
signatureAccentColour: "#fff", 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, redirectOnLogout: true,
logoutRedirectTarget: "https://adaptiveconstruction.ca", logoutRedirectTarget: "https://adaptiveconstruction.ca",
darkLogo: AdConLogo, darkLogo: AdConLogo,
@ -233,7 +233,7 @@ const OMNIAIR_WHITE_LABEL: WhiteLabel = {
secondaryColour: "yellow", secondaryColour: "yellow",
signatureColour: "#272727", signatureColour: "#272727",
signatureAccentColour: "#fff", signatureAccentColour: "#fff",
auth0ClientId: import.meta.env.REACT_APP_AUTH0_OMNIAIR_CLIENT_ID, auth0ClientId: import.meta.env.VITE_AUTH0_OMNIAIR_CLIENT_ID,
redirectOnLogout: true, redirectOnLogout: true,
logoutRedirectTarget: "https://omniairsystems.com", logoutRedirectTarget: "https://omniairsystems.com",
darkLogo: MiPCALogo, darkLogo: MiPCALogo,
@ -259,7 +259,7 @@ const whitelabels = new Map<string, WhiteLabel>([
["brandxducks", BXT_WHITE_LABEL], ["brandxducks", BXT_WHITE_LABEL],
["brandxtech", BXT_WHITE_LABEL], ["brandxtech", BXT_WHITE_LABEL],
["aerogrowmanufacturing", AEROGROW_WHITE_LABEL], ["aerogrowmanufacturing", AEROGROW_WHITE_LABEL],
["localhost", STAGING_WHITELABEL], ["localhost", BXT_WHITE_LABEL],
["staging", STAGING_WHITELABEL], ["staging", STAGING_WHITELABEL],
["10.0", ADAPTIVE_CONSTRUCTION_WHITE_LABEL], ["10.0", ADAPTIVE_CONSTRUCTION_WHITE_LABEL],
["mivent", MIVENT_WHITE_LABEL], ["mivent", MIVENT_WHITE_LABEL],
@ -276,7 +276,7 @@ export function getWhitelabel(): WhiteLabel {
if (window.location.origin.includes("bxt-dev")) { if (window.location.origin.includes("bxt-dev")) {
return BXT_WHITE_LABEL; 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; return STAGING_WHITELABEL;
} }
const whiteLabelKeys = Array.from(whitelabels.keys()); const whiteLabelKeys = Array.from(whitelabels.keys());

View file

@ -8,6 +8,7 @@ import { Task } from "models";
//import { ToggleButton, ToggleButtonGroup } from "@material-ui/lab"; //import { ToggleButton, ToggleButtonGroup } from "@material-ui/lab";
import TasksIcon from "products/Construction/TasksIcon"; import TasksIcon from "products/Construction/TasksIcon";
import moment from "moment"; import moment from "moment";
import ButtonGroup from "common/ButtonGroup";
interface Props { interface Props {
tasks: Task[]; tasks: Task[];
@ -147,8 +148,21 @@ export default function TaskList(props: Props) {
return ( return (
<Box padding="5px" textAlign="center"> <Box padding="5px" textAlign="center">
<Typography variant="h5">{label ? label : ""}</Typography> <Typography variant="h5">{label ? label : ""}</Typography>
<Box textAlign="center"> <Box textAlign="center" display="flex" justifyContent="center">
<ToggleButtonGroup value={viewing} exclusive size="small" aria-label="Bin Mode"> <ButtonGroup
toggle
buttons={[
{
title: "Upcoming",
function: () => setViewing("upcoming")
},
{
title: "Complete",
function: () => setViewing("complete")
}
]}
/>
{/* <ToggleButtonGroup value={viewing} exclusive size="small" aria-label="Bin Mode">
<ToggleButton <ToggleButton
value={"upcoming"} value={"upcoming"}
aria-label="upcoming" aria-label="upcoming"
@ -161,7 +175,7 @@ export default function TaskList(props: Props) {
aria-label="complete"> aria-label="complete">
Complete Complete
</ToggleButton> </ToggleButton>
</ToggleButtonGroup> </ToggleButtonGroup> */}
{location !== "/tasks" && ( {location !== "/tasks" && (
<IconButton id="tasks" component={Link} to="/tasks"> <IconButton id="tasks" component={Link} to="/tasks">
<TasksIcon /> <TasksIcon />

View file

@ -80,7 +80,7 @@ export default function TeamList() {
viewAs = undefined viewAs = undefined
} }
teamAPI teamAPI
.listTeams(limit, limit * page, undefined, "name", undefined, undefined, viewAs, search) .listTeams(limit, limit * page, undefined, "name", undefined, undefined, " ", search)
.then(resp => { .then(resp => {
setTotal(resp.data.total ? resp.data.total : 0); setTotal(resp.data.total ? resp.data.total : 0);
let newTeams: Team[] = [] let newTeams: Team[] = []

View file

@ -27,7 +27,7 @@ export default function TeamSearch(props: Props) {
if (!setSearch) return; if (!setSearch) return;
if (searchText === undefined) return; if (searchText === undefined) return;
setHandler(setTimeout(() => { setHandler(setTimeout(() => {
setSearchText(searchText); setSearch(searchText);
}, 750)); // Delay of 750ms }, 750)); // Delay of 750ms
return () => clearTimeout(handler); // Cleanup on change return () => clearTimeout(handler); // Cleanup on change
@ -41,7 +41,7 @@ export default function TeamSearch(props: Props) {
let options: Option[] = []; let options: Option[] = [];
setLoading(true); setLoading(true);
teamAPI teamAPI
.listTeams(10, 0, "desc", undefined, undefined, undefined, undefined, search) .listTeams(10, 0, "desc", undefined, undefined, undefined, " ", search)
.then(resp => { .then(resp => {
if (loadUsers) if (loadUsers)
options.push({ options.push({

View file

@ -29,7 +29,7 @@ export const AppThemeProvider: React.FC<{ children: React.ReactNode }> = ({ chil
const savedTheme = localStorage.getItem("theme"); const savedTheme = localStorage.getItem("theme");
const isValidTheme = (theme: string | null): theme is ThemeMode => const isValidTheme = (theme: string | null): theme is ThemeMode =>
theme === "light" || theme === "dark" || theme === "system"; 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 prefersDark = useMediaQuery("(prefers-color-scheme: dark)");
const resolvedMode: PaletteMode = mode === "system" ? (prefersDark ? "dark" : "light") : mode; const resolvedMode: PaletteMode = mode === "system" ? (prefersDark ? "dark" : "light") : mode;

View file

@ -17,6 +17,20 @@ const baseTheme: ThemeOptions = {
paper: '#f5f5f5', 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 // Add other options if needed
}; };

View file

@ -73,14 +73,19 @@ export default function AccessObject(props: Props) {
const [teamsTotal, setTeamsTotal] = useState<number>(0); const [teamsTotal, setTeamsTotal] = useState<number>(0);
const [devicesOffset, setDevicesOffset] = useState<number>(0); const [devicesOffset, setDevicesOffset] = useState<number>(0);
const [devicesTotal, setDevicesTotal] = 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(); closeDialogCallback();
}; };
const getAccess = (key: number | string, name: string, url: string, useImitation = false) => { const getAccess = (key: number | string, name: string, url: string, useImitation = false) => {
user.settings.useTeam = false; user.settings.useTeam = false;
dispatch({ key: "as", value: "" }); //dispatch({ key: "as", value: "" });
userAPI.updateUser(user.id(), user.protobuf()).then(() => { userAPI.updateUser(user.id(), user.protobuf()).then(() => {
info("Will no longer view as team by default"); info("Will no longer view as team by default");
permissionAPI permissionAPI
@ -258,6 +263,12 @@ export default function AccessObject(props: Props) {
useEffect(() => { useEffect(() => {
if (isOpen && (prevSearchValue !== searchValue || prevKind !== kind || isOpen !== prevOpen)) { 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(); load();
} }
}, [ }, [
@ -350,12 +361,12 @@ export default function AccessObject(props: Props) {
return ( return (
<ResponsiveDialog <ResponsiveDialog
open={isOpen} open={isOpen}
onClose={close} onClose={() => close(true)}
aria-labelledby="access-object-dialog" aria-labelledby="access-object-dialog"
fullScreen> fullScreen>
<AppBar position="sticky"> <AppBar position="sticky">
<Toolbar> <Toolbar>
<IconButton edge="start" color="inherit" onClick={close} aria-label="close"> <IconButton edge="start" color="inherit" onClick={() => close(true)} aria-label="close">
<CloseIcon /> <CloseIcon />
</IconButton> </IconButton>
<Typography variant="h6" noWrap> <Typography variant="h6" noWrap>

View file

@ -22,8 +22,10 @@ export default defineConfig({
outDir: './build', outDir: './build',
sourcemap: true, sourcemap: true,
minify: false, minify: false,
target: 'esnext'
}, },
esbuild: { esbuild: {
keepNames: true, // Prevent function name mangling keepNames: true, // Prevent function name mangling
} },
}) })