merged staging and updated master proto
This commit is contained in:
commit
1e34530ef3
51 changed files with 6483 additions and 4024 deletions
|
|
@ -7,6 +7,12 @@
|
||||||
<title id="title-id">Adaptive Dashboard</title>
|
<title id="title-id">Adaptive Dashboard</title>
|
||||||
<link rel="manifest" id="manifest-link" />
|
<link rel="manifest" id="manifest-link" />
|
||||||
</head>
|
</head>
|
||||||
|
<script>
|
||||||
|
window.CRISP_RUNTIME_CONFIG = {
|
||||||
|
session_merge: true
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<script type="text/javascript">window.$crisp=[];window.CRISP_WEBSITE_ID="80170383-b426-43c0-8f66-8e20a05bcdce";(function(){d=document;s=d.createElement("script");s.src="https://client.crisp.chat/l.js";s.async=1;d.getElementsByTagName("head")[0].appendChild(s);})();</script>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
<script type="module" src="/src/app/main.tsx"></script>
|
<script type="module" src="/src/app/main.tsx"></script>
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,12 @@
|
||||||
<title id="title-id">Adaptive Dashboard</title>
|
<title id="title-id">Adaptive Dashboard</title>
|
||||||
<link rel="manifest" id="manifest-link" />
|
<link rel="manifest" id="manifest-link" />
|
||||||
</head>
|
</head>
|
||||||
|
<script>
|
||||||
|
window.CRISP_RUNTIME_CONFIG = {
|
||||||
|
session_merge: true
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<script type="text/javascript">window.$crisp=[];window.CRISP_WEBSITE_ID="80170383-b426-43c0-8f66-8e20a05bcdce";(function(){d=document;s=d.createElement("script");s.src="https://client.crisp.chat/l.js";s.async=1;d.getElementsByTagName("head")[0].appendChild(s);})();</script>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
<script type="module" src="/src/app/main.tsx"></script>
|
<script type="module" src="/src/app/main.tsx"></script>
|
||||||
|
|
|
||||||
10
nginx.conf
10
nginx.conf
|
|
@ -78,18 +78,18 @@ http {
|
||||||
}
|
}
|
||||||
|
|
||||||
# Redirect old /index.html requests to new file
|
# Redirect old /index.html requests to new file
|
||||||
location = /index.html {
|
# location = /index.html {
|
||||||
return 301 /indexV2.html;
|
# return 301 /indexV2.html;
|
||||||
}
|
# }
|
||||||
|
|
||||||
# Serve indexV2.html directly without redirect loop
|
# Serve indexV2.html directly without redirect loop
|
||||||
location = /indexV2.html {
|
location = /index.html {
|
||||||
root /usr/share/nginx/html;
|
root /usr/share/nginx/html;
|
||||||
}
|
}
|
||||||
|
|
||||||
# Fallback for SPA routing — all unmatched routes go to indexV2.html
|
# Fallback for SPA routing — all unmatched routes go to indexV2.html
|
||||||
location / {
|
location / {
|
||||||
try_files $uri /indexV2.html;
|
try_files $uri /index.html;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
8903
package-lock.json
generated
8903
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -12,10 +12,10 @@
|
||||||
"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_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:staging": "VITE_CRISP_WEBSITE_ID={CRISP_WEBSITE_ID} VITE_AUTH0_CLIENT_DOMAIN=adaptivestaging.us.auth0.com VITE_AUTH0_AUDIENCE=stagingapi.brandxtech.ca VITE_AUTH0_CLIENT_ID=3ib460VvLwdeyse5iUSQfxkVdQaUmphP VITE_APP_API_URL=https://stagingapi.brandxtech.ca/v1 VITE_APP_WS_URL=wss://stagingapi.brandxtech.ca/v1/live NODE_OPTIONS=--max_old_space_size=4096 VITE_APP_GOOGLE_API_KEY=${GOOGLE_API_KEY} VITE_APP_STRIPE_PUBLIC_KEY=${STRIPE_PUBLIC_KEY_STAGING} VITE_MAPBOX_ACCESS_TOKEN=${MAPBOX_ACCESS_TOKEN} VITE_CNHI_CLIENT_ID=${CNHI_CLIENT_ID} VITE_CNHI_AUTHORIZE_URL=${CNHI_AUTHORIZE_URL} VITE_CNHI_REDIRECT_URI=${CNHI_REDIRECT_URI} VITE_CNHI_SCOPES=${CNHI_SCOPES} VITE_CNHI_CONNECTION=${CNHI_CONNECTION} VITE_CNHI_AUDIENCE=${CNHI_AUDIENCE} VITE_APP_IMAGE4IO_USERNAME=${IMAGE4IO_USERNAME} VITE_APP_IMAGE4IO_PASSWORD=${IMAGE4IO_PASSWORD} VITE_JD_CLIENT_ID=${JD_CLIENT_ID} VITE_JD_AUTHORIZE_URL=${JD_AUTHORIZE_URL} VITE_JD_REDIRECT_URI=${JD_REDIRECT_URI} VITE_JD_SCOPES=${JD_SCOPES} VITE_JD_STATE=${JD_STATE} VITE_OPEN_WEATHERMAP=${OPEN_WEATHERMAP} vite build",
|
||||||
"build:development": "VITE_AUTH0_CLIENT_ID=dzJTpqIeMA4Rwk4xujtwPbAO3TY32bM1 VITE_AUTH0_AUDIENCE=bxt-dev.api.adaptiveagriculture.ca VITE_APP_API_URL=https://bxt-dev.api.adaptiveagriculture.ca/v1 NODE_OPTIONS=--max_old_space_size=4096 VITE_APP_GOOGLE_API_KEY=${GOOGLE_API_KEY} VITE_APP_STRIPE_PUBLIC_KEY=${STRIPE_PUBLIC_KEY_DEVELOPMENT} VITE_MAPBOX_ACCESS_TOKEN=${MAPBOX_ACCESS_TOKEN} VITE_CNHI_CLIENT_ID=${CNHI_CLIENT_ID} VITE_CNHI_AUTHORIZE_URL=${CNHI_AUTHORIZE_URL} VITE_CNHI_REDIRECT_URI=${CNHI_REDIRECT_URI} VITE_CNHI_SCOPES=${CNHI_SCOPES} VITE_CNHI_CONNECTION=${CNHI_CONNECTION} VITE_CNHI_AUDIENCE=${CNHI_AUDIENCE} VITE_APP_IMAGE4IO_USERNAME=${IMAGE4IO_USERNAME} VITE_APP_IMAGE4IO_PASSWORD=${IMAGE4IO_PASSWORD} VITE_JD_CLIENT_ID=${JD_CLIENT_ID} VITE_JD_AUTHORIZE_URL=${JD_AUTHORIZE_URL} VITE_JD_REDIRECT_URI=${JD_REDIRECT_URI} VITE_JD_SCOPES=${JD_SCOPES} VITE_JD_STATE=${JD_STATE} VITE_OPEN_WEATHERMAP=${OPEN_WEATHERMAP} vite build",
|
"build:development": "VITE_CRISP_WEBSITE_ID={CRISP_WEBSITE_ID} VITE_AUTH0_CLIENT_ID=dzJTpqIeMA4Rwk4xujtwPbAO3TY32bM1 VITE_AUTH0_AUDIENCE=bxt-dev.api.adaptiveagriculture.ca VITE_APP_API_URL=https://bxt-dev.api.adaptiveagriculture.ca/v1 NODE_OPTIONS=--max_old_space_size=4096 VITE_APP_GOOGLE_API_KEY=${GOOGLE_API_KEY} VITE_APP_STRIPE_PUBLIC_KEY=${STRIPE_PUBLIC_KEY_DEVELOPMENT} VITE_MAPBOX_ACCESS_TOKEN=${MAPBOX_ACCESS_TOKEN} VITE_CNHI_CLIENT_ID=${CNHI_CLIENT_ID} VITE_CNHI_AUTHORIZE_URL=${CNHI_AUTHORIZE_URL} VITE_CNHI_REDIRECT_URI=${CNHI_REDIRECT_URI} VITE_CNHI_SCOPES=${CNHI_SCOPES} VITE_CNHI_CONNECTION=${CNHI_CONNECTION} VITE_CNHI_AUDIENCE=${CNHI_AUDIENCE} VITE_APP_IMAGE4IO_USERNAME=${IMAGE4IO_USERNAME} VITE_APP_IMAGE4IO_PASSWORD=${IMAGE4IO_PASSWORD} VITE_JD_CLIENT_ID=${JD_CLIENT_ID} VITE_JD_AUTHORIZE_URL=${JD_AUTHORIZE_URL} VITE_JD_REDIRECT_URI=${JD_REDIRECT_URI} VITE_JD_SCOPES=${JD_SCOPES} VITE_JD_STATE=${JD_STATE} VITE_OPEN_WEATHERMAP=${OPEN_WEATHERMAP} vite build",
|
||||||
"build:production": "VITE_AUTH0_CLIENT_ID=5pUCkl2SfogkWmM244UDcLEUOp8EFdHd VITE_AUTH0_CLIENT_DOMAIN=brandxtech.auth0.com VITE_AUTH0_AUDIENCE=api.brandxtech.ca VITE_APP_API_URL=https://api.brandxtech.ca/v1 NODE_OPTIONS=--max_old_space_size=4096 VITE_APP_GOOGLE_API_KEY=${GOOGLE_API_KEY} VITE_APP_STRIPE_PUBLIC_KEY=${STRIPE_PUBLIC_KEY_PRODUCTION} VITE_MAPBOX_ACCESS_TOKEN=${MAPBOX_ACCESS_TOKEN} VITE_CNHI_CLIENT_ID=${CNHI_CLIENT_ID} VITE_CNHI_AUTHORIZE_URL=${CNHI_AUTHORIZE_URL} VITE_CNHI_REDIRECT_URI=${CNHI_REDIRECT_URI} VITE_CNHI_SCOPES=${CNHI_SCOPES} VITE_CNHI_CONNECTION=${CNHI_CONNECTION} VITE_CNHI_AUDIENCE=${CNHI_AUDIENCE} VITE_APP_IMAGE4IO_USERNAME=${IMAGE4IO_USERNAME} VITE_APP_IMAGE4IO_PASSWORD=${IMAGE4IO_PASSWORD} VITE_JD_CLIENT_ID=${JD_CLIENT_ID} VITE_JD_AUTHORIZE_URL=${JD_AUTHORIZE_URL} VITE_JD_REDIRECT_URI=${JD_REDIRECT_URI} VITE_JD_SCOPES=${JD_SCOPES} VITE_JD_STATE=${JD_STATE} VITE_OPEN_WEATHERMAP=${OPEN_WEATHERMAP} vite build",
|
"build:production": "VITE_CRISP_WEBSITE_ID={CRISP_WEBSITE_ID} 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:streamline": "VITE_AUTH0_CLIENT_ID=HwUV0hHNdVvU96zuMBTAU8i7nFdwwgIX VITE_AUTH0_CLIENT_DOMAIN=brandxtech.auth0.com VITE_AUTH0_AUDIENCE=streamline.api.adaptiveagriculture.ca VITE_APP_API_URL=https://streamline.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_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:streamline": "VITE_CRISP_WEBSITE_ID={CRISP_WEBSITE_ID} VITE_AUTH0_CLIENT_ID=HwUV0hHNdVvU96zuMBTAU8i7nFdwwgIX VITE_AUTH0_CLIENT_DOMAIN=brandxtech.auth0.com VITE_AUTH0_AUDIENCE=streamline.api.adaptiveagriculture.ca VITE_APP_API_URL=https://streamline.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_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"
|
||||||
},
|
},
|
||||||
|
|
@ -42,6 +42,7 @@
|
||||||
"@turf/turf": "^7.2.0",
|
"@turf/turf": "^7.2.0",
|
||||||
"@types/classnames": "^2.3.0",
|
"@types/classnames": "^2.3.0",
|
||||||
"axios": "^1.7.7",
|
"axios": "^1.7.7",
|
||||||
|
"crisp-sdk-web": "^1.0.26",
|
||||||
"dayjs": "^1.11.13",
|
"dayjs": "^1.11.13",
|
||||||
"deep-object-diff": "^1.1.9",
|
"deep-object-diff": "^1.1.9",
|
||||||
"framer-motion": "^12.6.2",
|
"framer-motion": "^12.6.2",
|
||||||
|
|
|
||||||
|
|
@ -45,3 +45,15 @@ html, body, #root {
|
||||||
.MuiPopover-root {
|
.MuiPopover-root {
|
||||||
z-index: 1501 !important;
|
z-index: 1501 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#tidio-chat-iframe {
|
||||||
|
bottom: 100px !important; /* Adjust this value – higher number = moved further up from the bottom */
|
||||||
|
right: 20px !important; /* Optional: tweak horizontal position if needed */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Optional: Different position on mobile (to avoid overlaps there) */
|
||||||
|
@media only screen and (max-width: 980px) {
|
||||||
|
#tidio-chat-iframe {
|
||||||
|
bottom: 80px !important; /* Example for mobile – test and adjust */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -12,7 +12,7 @@ import { useTeamAPI } from "providers";
|
||||||
import { cloneDeep } from "lodash";
|
import { cloneDeep } from "lodash";
|
||||||
|
|
||||||
|
|
||||||
const useStyles = makeStyles((theme: Theme) => ({
|
const useStyles = makeStyles<Theme>((theme: Theme) => ({
|
||||||
button: {
|
button: {
|
||||||
color: getSignatureAccentColour(),
|
color: getSignatureAccentColour(),
|
||||||
width: theme.spacing(6),
|
width: theme.spacing(6),
|
||||||
|
|
@ -70,8 +70,8 @@ export default function HeaderButtons(props: Props) {
|
||||||
setHasNewChats(true);
|
setHasNewChats(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
console.log(team.preferences.chatViewedTimestamp)
|
// console.log(team.preferences.chatViewedTimestamp)
|
||||||
console.log(team.settings.lastChatTimestamp)
|
// console.log(team.settings.lastChatTimestamp)
|
||||||
let viewMoment = moment(team.preferences.chatViewedTimestamp);
|
let viewMoment = moment(team.preferences.chatViewedTimestamp);
|
||||||
let chatMoment = moment(team.settings.lastChatTimestamp);
|
let chatMoment = moment(team.settings.lastChatTimestamp);
|
||||||
if (viewMoment.diff(chatMoment) < 0) {
|
if (viewMoment.diff(chatMoment) < 0) {
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,9 @@ import { makeStyles } from '@mui/styles'
|
||||||
import { CssBaseline, Theme } from '@mui/material'
|
import { CssBaseline, Theme } from '@mui/material'
|
||||||
import { AppThemeProvider } from 'theme/AppThemeProvider'
|
import { AppThemeProvider } from 'theme/AppThemeProvider'
|
||||||
import HTTPProvider from 'providers/http'
|
import HTTPProvider from 'providers/http'
|
||||||
import { useSnackbar } from 'hooks'
|
import { Crisp } from "crisp-sdk-web";
|
||||||
|
import { useMobile, useSnackbar } from 'hooks'
|
||||||
|
import { initCrisp } from '../chat/CrispChat'
|
||||||
// import FirmwareLoader from './FirmwareLoader'
|
// import FirmwareLoader from './FirmwareLoader'
|
||||||
|
|
||||||
const reducer = (state: GlobalState, action: GlobalStateAction): GlobalState => {
|
const reducer = (state: GlobalState, action: GlobalStateAction): GlobalState => {
|
||||||
|
|
@ -66,9 +68,13 @@ export default function UserWrapper(props: Props) {
|
||||||
const hasFetched = useRef(false);
|
const hasFetched = useRef(false);
|
||||||
const [global, setGlobal] = useState<undefined | GlobalState>(undefined)
|
const [global, setGlobal] = useState<undefined | GlobalState>(undefined)
|
||||||
const snackbar = useSnackbar()
|
const snackbar = useSnackbar()
|
||||||
|
const isMobile = useMobile()
|
||||||
|
|
||||||
const user_id = or(useAuth.user?.sub, "")
|
const user_id = or(useAuth.user?.sub, "")
|
||||||
|
|
||||||
|
const crispInitialized = useRef(false);
|
||||||
|
Crisp.configure(import.meta.env.VITE_CRISP_WEBSITE_ID);
|
||||||
|
|
||||||
const loadUser = useCallback(() => {
|
const loadUser = useCallback(() => {
|
||||||
if (!userAPI.getUserWithTeam) return;
|
if (!userAPI.getUserWithTeam) return;
|
||||||
if (hasFetched.current) return;
|
if (hasFetched.current) return;
|
||||||
|
|
@ -107,6 +113,66 @@ export default function UserWrapper(props: Props) {
|
||||||
})
|
})
|
||||||
}, [setGlobal])
|
}, [setGlobal])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (global?.user) {
|
||||||
|
initCrisp({
|
||||||
|
websiteId: import.meta.env.VITE_CRISP_WEBSITE_ID,
|
||||||
|
email: global.user.settings.email,
|
||||||
|
nickname: global.user.settings.name || global.user.settings.email,
|
||||||
|
phone: global.user.settings.phoneNumber,
|
||||||
|
tokenId: global.user.id(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [global]);
|
||||||
|
|
||||||
|
// useEffect(() => {
|
||||||
|
// if (window.$crisp) {
|
||||||
|
// if (isMobile) {
|
||||||
|
// $crisp.push(["config", "position:offset", [20, 120]]);
|
||||||
|
// } else {
|
||||||
|
// $crisp.push(["config", "position:offset", [20, 20]]);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }, [isMobile]);
|
||||||
|
|
||||||
|
// useEffect(() => {
|
||||||
|
// console.log("crisb")
|
||||||
|
// const crispEl = document.getElementById("crisp-chatbox");
|
||||||
|
// console.log(crispEl)
|
||||||
|
// if (crispEl) {
|
||||||
|
// if (isMobile) {
|
||||||
|
// console.log("setting margin")
|
||||||
|
// crispEl.style.setProperty("margin-bottom", "180px", "important");
|
||||||
|
// } else {
|
||||||
|
// crispEl.style.setProperty("margin-bottom", "0px", "important");
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }, [isMobile]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let style = document.getElementById("crisp-offset-override");
|
||||||
|
if (!style) {
|
||||||
|
style = document.createElement("style");
|
||||||
|
style.id = "crisp-offset-override";
|
||||||
|
document.head.appendChild(style);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isMobile) {
|
||||||
|
style.textContent = `
|
||||||
|
#crisp-chatbox [aria-label="Open chat"] {
|
||||||
|
bottom: 6em !important;
|
||||||
|
}
|
||||||
|
// #crisp-chatbox [data-visible][style*="width: 360px"] {
|
||||||
|
// bottom: 180px !important;
|
||||||
|
// }
|
||||||
|
`;
|
||||||
|
} else {
|
||||||
|
style.textContent = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => style?.remove();
|
||||||
|
}, [isMobile]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (loading) return;
|
if (loading) return;
|
||||||
loadUser()
|
loadUser()
|
||||||
|
|
|
||||||
BIN
src/assets/common/robotIconDark.png
Normal file
BIN
src/assets/common/robotIconDark.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.5 KiB |
BIN
src/assets/common/robotIconLight.png
Normal file
BIN
src/assets/common/robotIconLight.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.3 KiB |
|
|
@ -29,7 +29,7 @@ const useStyles = makeStyles((theme: Theme) => {
|
||||||
bottom: theme.spacing(8), //for mobile navigator
|
bottom: theme.spacing(8), //for mobile navigator
|
||||||
right: theme.spacing(2),
|
right: theme.spacing(2),
|
||||||
[theme.breakpoints.up("sm")]: {
|
[theme.breakpoints.up("sm")]: {
|
||||||
bottom: theme.spacing(2)
|
bottom: theme.spacing(1.75)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
pulse: {
|
pulse: {
|
||||||
|
|
|
||||||
|
|
@ -1434,6 +1434,7 @@ export default function BinVisualizer(props: Props) {
|
||||||
|
|
||||||
const controls = () => {
|
const controls = () => {
|
||||||
if (bin.settings.inventory?.empty === true) return null;
|
if (bin.settings.inventory?.empty === true) return null;
|
||||||
|
const canEdit = permissions ? permissions.includes(pond.Permission.PERMISSION_WRITE) : false;
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
height={1}
|
height={1}
|
||||||
|
|
@ -1447,12 +1448,13 @@ export default function BinVisualizer(props: Props) {
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<Box display="flex" height={isMobile ? 35 : 40} marginBottom={1}>
|
<Box display="flex" height={isMobile ? 35 : 40} marginBottom={1}>
|
||||||
<IconButton
|
<IconButton
|
||||||
|
disabled={!canEdit}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setGrainUpdate(true);
|
setGrainUpdate(true);
|
||||||
}}
|
}}
|
||||||
style={{
|
style={{
|
||||||
margin: "auto",
|
margin: "auto",
|
||||||
backgroundColor: "gold",
|
backgroundColor: canEdit ? "gold" : "grey",
|
||||||
color: "black",
|
color: "black",
|
||||||
height: "100%",
|
height: "100%",
|
||||||
width: isMobile ? 35 : 40
|
width: isMobile ? 35 : 40
|
||||||
|
|
@ -1468,6 +1470,7 @@ export default function BinVisualizer(props: Props) {
|
||||||
alignContent="flex-end">
|
alignContent="flex-end">
|
||||||
{fillPercentage !== null && (
|
{fillPercentage !== null && (
|
||||||
<Slider
|
<Slider
|
||||||
|
disabled={!canEdit}
|
||||||
orientation="vertical"
|
orientation="vertical"
|
||||||
value={fillPercentage}
|
value={fillPercentage}
|
||||||
classes={{
|
classes={{
|
||||||
|
|
@ -1477,7 +1480,7 @@ export default function BinVisualizer(props: Props) {
|
||||||
// thumb: isMobile ? classes.mobileSliderThumb : classes.sliderThumb,
|
// thumb: isMobile ? classes.mobileSliderThumb : classes.sliderThumb,
|
||||||
valueLabel: classes.sliderValLabel,
|
valueLabel: classes.sliderValLabel,
|
||||||
}}
|
}}
|
||||||
style={{ height: "100%", color: sliderColour }}
|
style={{ height: "100%", color: canEdit ? sliderColour : "gray" }}
|
||||||
min={0}
|
min={0}
|
||||||
max={100}
|
max={100}
|
||||||
valueLabelDisplay="auto"
|
valueLabelDisplay="auto"
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ export default function BinUtilizationChart(props: Props) {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const grainColour = customColour ? customColour : GrainColour(grain);
|
const grainColour = customColour ? customColour : GrainColour(grain);
|
||||||
return (
|
return (
|
||||||
<Box height={1} width={1}>
|
<Box height={200} width={200}>
|
||||||
<CardActionArea
|
<CardActionArea
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
style={{ height: "100%", borderRadius: theme.shape.borderRadius }}>
|
style={{ height: "100%", borderRadius: theme.shape.borderRadius }}>
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
import { useTheme } from "@mui/material";
|
import { useTheme } from "@mui/material";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useMemo, useState } from "react";
|
||||||
import {
|
import {
|
||||||
Area,
|
Area,
|
||||||
AreaChart,
|
AreaChart,
|
||||||
|
Legend,
|
||||||
ReferenceArea,
|
ReferenceArea,
|
||||||
ReferenceLine,
|
ReferenceLine,
|
||||||
ResponsiveContainer,
|
ResponsiveContainer,
|
||||||
|
|
@ -13,25 +14,38 @@ import {
|
||||||
YAxis
|
YAxis
|
||||||
} from "recharts";
|
} from "recharts";
|
||||||
import MaterialChartTooltip from "./MaterialChartTooltip";
|
import MaterialChartTooltip from "./MaterialChartTooltip";
|
||||||
|
import { blue, orange } from "@mui/material/colors";
|
||||||
|
import { Payload } from "recharts/types/component/DefaultLegendContent";
|
||||||
|
|
||||||
export interface SSAreaDataPoint {
|
export interface SSAreaDataPoint {
|
||||||
timestamp: number;
|
timestamp: number;
|
||||||
value: number;
|
value: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ColourData {
|
||||||
|
colour: string,
|
||||||
|
label: string
|
||||||
|
}
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
data: SSAreaDataPoint[];
|
data: SSAreaDataPoint[];
|
||||||
|
tooltipLabel: string
|
||||||
|
tooltipUnit?: string
|
||||||
|
yAxisLabel: string
|
||||||
maxRef?: number;
|
maxRef?: number;
|
||||||
minRef?: number;
|
minRef?: number;
|
||||||
newXDomain?: number[] | string[];
|
newXDomain?: number[] | string[];
|
||||||
multiGraphZoom?: (domain: number[] | string[]) => void;
|
multiGraphZoom?: (domain: number[] | string[]) => void;
|
||||||
|
colourAboveZero?: ColourData
|
||||||
|
colourBelowZero?: ColourData
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function SingleSetAreaChart(props: Props) {
|
export default function SingleSetAreaChart(props: Props) {
|
||||||
const { data, maxRef, minRef, newXDomain, multiGraphZoom } = props;
|
const { data, maxRef, minRef, newXDomain, multiGraphZoom, yAxisLabel, colourAboveZero, colourBelowZero, tooltipLabel, tooltipUnit } = props;
|
||||||
const [xDomain, setXDomain] = useState<string[] | number[]>(["dataMin", "dataMax"]);
|
const [xDomain, setXDomain] = useState<string[] | number[]>(["dataMin", "dataMax"]);
|
||||||
const [refLeft, setRefLeft] = useState<number | undefined>();
|
const [refLeft, setRefLeft] = useState<number | undefined>();
|
||||||
const [refRight, setRefRight] = useState<number | undefined>();
|
const [refRight, setRefRight] = useState<number | undefined>();
|
||||||
|
const [legendPayload, setLegendPayload] = useState<Payload[]>([])
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const now = moment();
|
const now = moment();
|
||||||
|
|
||||||
|
|
@ -41,6 +55,40 @@ export default function SingleSetAreaChart(props: Props) {
|
||||||
}
|
}
|
||||||
}, [newXDomain]);
|
}, [newXDomain]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let legend: Payload[] = []
|
||||||
|
if(colourAboveZero){
|
||||||
|
legend.push({
|
||||||
|
value: colourAboveZero.label,
|
||||||
|
color: colourAboveZero.colour,
|
||||||
|
id: colourAboveZero.label,
|
||||||
|
type: "square"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if(colourBelowZero){
|
||||||
|
legend.push({
|
||||||
|
value: colourBelowZero.label,
|
||||||
|
color: colourBelowZero.colour,
|
||||||
|
id: colourBelowZero.label,
|
||||||
|
type: "square"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
setLegendPayload(legend)
|
||||||
|
}, [colourAboveZero, colourBelowZero])
|
||||||
|
|
||||||
|
const gradientOffset = useMemo(() => {
|
||||||
|
if (!data.length) return 0;
|
||||||
|
|
||||||
|
const values = data.map(p => p.value);
|
||||||
|
const max = Math.max(...values);
|
||||||
|
const min = Math.min(...values);
|
||||||
|
|
||||||
|
if (max <= 0) return 0;
|
||||||
|
if (min >= 0) return 1;
|
||||||
|
|
||||||
|
return max / (max - min);
|
||||||
|
}, [data]);
|
||||||
|
|
||||||
const zoom = () => {
|
const zoom = () => {
|
||||||
let newDomain: number[] | string[] = ["dataMin", "dataMax"];
|
let newDomain: number[] | string[] = ["dataMin", "dataMax"];
|
||||||
if (refLeft && refRight && refLeft !== refRight) {
|
if (refLeft && refRight && refLeft !== refRight) {
|
||||||
|
|
@ -75,12 +123,16 @@ export default function SingleSetAreaChart(props: Props) {
|
||||||
setRefRight(undefined);
|
setRefRight(undefined);
|
||||||
zoom();
|
zoom();
|
||||||
}}>
|
}}>
|
||||||
|
<Legend
|
||||||
|
verticalAlign="top"
|
||||||
|
payload={legendPayload}
|
||||||
|
/>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
animationEasing="ease-out"
|
animationEasing="ease-out"
|
||||||
cursor={{ fill: theme.palette.text.primary, opacity: "0.15" }}
|
cursor={{ fill: theme.palette.text.primary, opacity: "0.15" }}
|
||||||
labelFormatter={timestamp => moment(timestamp).format("lll")}
|
labelFormatter={timestamp => moment(timestamp).format("lll")}
|
||||||
content={(props: TooltipProps<any, any>) => (
|
content={(props: TooltipProps<any, any>) => (
|
||||||
<MaterialChartTooltip {...props} valueFormatter={value => `${value}`} />
|
<MaterialChartTooltip {...props} valueFormatter={value => `${value}` + (tooltipUnit ? tooltipUnit : "")} />
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<XAxis
|
<XAxis
|
||||||
|
|
@ -102,13 +154,26 @@ export default function SingleSetAreaChart(props: Props) {
|
||||||
type="number"
|
type="number"
|
||||||
domain={["auto", "auto"]}
|
domain={["auto", "auto"]}
|
||||||
label={{
|
label={{
|
||||||
value: "Mass Flow (kg/s)",
|
value: yAxisLabel,
|
||||||
position: "insideLeft",
|
position: "insideLeft",
|
||||||
angle: -90,
|
angle: -90,
|
||||||
fill: theme.palette.text.primary
|
fill: theme.palette.text.primary
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Area dataKey={"value"} strokeWidth={3} />
|
<defs>
|
||||||
|
{colourAboveZero && colourBelowZero &&
|
||||||
|
<linearGradient id="solidColour" x1="0" y1="0" x2="0" y2="1">
|
||||||
|
<stop offset={gradientOffset} stopColor={colourAboveZero.colour} stopOpacity="75%" />
|
||||||
|
<stop offset={gradientOffset} stopColor={colourBelowZero.colour} stopOpacity="75%" />
|
||||||
|
</linearGradient>
|
||||||
|
}
|
||||||
|
</defs>
|
||||||
|
<Area dataKey={"value"} strokeWidth={3}
|
||||||
|
type="monotone"
|
||||||
|
name={tooltipLabel}
|
||||||
|
stroke={(colourAboveZero && colourBelowZero) && "url(#solidColour)"}
|
||||||
|
fill={(colourAboveZero && colourBelowZero) && "url(#solidColour)"}
|
||||||
|
/>
|
||||||
<ReferenceLine
|
<ReferenceLine
|
||||||
ifOverflow="extendDomain"
|
ifOverflow="extendDomain"
|
||||||
y={maxRef}
|
y={maxRef}
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@ export default function Chat(props: Props) {
|
||||||
|
|
||||||
const loadChats = () => {
|
const loadChats = () => {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
|
// console.log("listing chats?")
|
||||||
noteAPI.listChats(10, chats.length, "desc", "timestamp", objectKey).then(resp => {
|
noteAPI.listChats(10, chats.length, "desc", "timestamp", objectKey).then(resp => {
|
||||||
setChats(resp.data.chats ? resp.data.chats.reverse().concat(chats) : [])
|
setChats(resp.data.chats ? resp.data.chats.reverse().concat(chats) : [])
|
||||||
setTotalMessages(resp.data.total)
|
setTotalMessages(resp.data.total)
|
||||||
|
|
@ -76,33 +77,42 @@ export default function Chat(props: Props) {
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadChats()
|
setChats([]);
|
||||||
}, []);
|
setLoaded(false);
|
||||||
|
setLoading(false);
|
||||||
|
setTotalMessages(0);
|
||||||
|
}, [objectKey]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (chats.length === 0 && !loaded) {
|
||||||
|
loadChats();
|
||||||
|
}
|
||||||
|
}, [objectKey, chats, loaded]);
|
||||||
|
|
||||||
const loadMore = () => {
|
const loadMore = () => {
|
||||||
loadChats();
|
loadChats();
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<Box className={classes.chatContainer}>
|
||||||
|
<Box sx={{ flex: 1, overflowY: "hidden", minHeight: 0 }}>
|
||||||
{loading && !loaded ? (
|
{loading && !loaded ? (
|
||||||
<Box width="100%" height="100%" alignContent={"center"} textAlign={"center"} >
|
<Box width="100%" height="100%" alignContent="center" textAlign="center">
|
||||||
<CircularProgress />
|
<CircularProgress />
|
||||||
</Box>
|
</Box>
|
||||||
) : (
|
) : (
|
||||||
<Box className={classes.chatContainer}>
|
|
||||||
<ChatOutput
|
<ChatOutput
|
||||||
totalMessages={totalMessages}
|
totalMessages={totalMessages}
|
||||||
messages={chats}
|
messages={chats}
|
||||||
removeNoteMethod={removeNote}
|
removeNoteMethod={removeNote}
|
||||||
loadMore={loadMore}
|
loadMore={loadMore}
|
||||||
/>
|
/>
|
||||||
<Box style={{bottom: 0}}>
|
)}
|
||||||
|
</Box>
|
||||||
|
<Box style={{ flexShrink: 0 }}>
|
||||||
<Divider />
|
<Divider />
|
||||||
<ChatInput newNoteMethod={showNewNotes} objectKey={objectKey} type={type} />
|
<ChatInput newNoteMethod={showNewNotes} objectKey={objectKey} type={type} />
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
|
||||||
</React.Fragment>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,19 @@
|
||||||
import { ChevronRight } from "@mui/icons-material";
|
import { ChevronRight } from "@mui/icons-material";
|
||||||
import { Box, Drawer, IconButton, Theme, Typography } from "@mui/material";
|
import { Avatar, Box, Drawer, IconButton, Theme, Tooltip, Typography } from "@mui/material";
|
||||||
import { makeStyles } from "@mui/styles";
|
import { makeStyles } from "@mui/styles";
|
||||||
import { useMobile } from "hooks";
|
import { useMobile } from "hooks";
|
||||||
import { Team } from "models";
|
import { Team } from "models";
|
||||||
import Chat from "./Chat";
|
import Chat from "./Chat";
|
||||||
import { pond } from "protobuf-ts/pond";
|
import { pond } from "protobuf-ts/pond";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { useTeamAPI } from "providers";
|
||||||
|
import { closeCrispChat, openCrispChat } from './CrispChat';
|
||||||
|
import RobotIcon from "products/CommonIcons/robotIcon";
|
||||||
|
|
||||||
const useStyles = makeStyles((theme: Theme) => {
|
const useStyles = makeStyles<Theme>((theme: Theme) => ({
|
||||||
return ({
|
|
||||||
chatDrawer: {
|
chatDrawer: {
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "column", // Arrange children vertically
|
flexDirection: "column",
|
||||||
backgroundColor: theme.palette.background.default,
|
backgroundColor: theme.palette.background.default,
|
||||||
height: "100%",
|
height: "100%",
|
||||||
maxHeight: "100%",
|
maxHeight: "100%",
|
||||||
|
|
@ -24,18 +27,19 @@ const useStyles = makeStyles((theme: Theme) => {
|
||||||
},
|
},
|
||||||
chatDrawerMobile: {
|
chatDrawerMobile: {
|
||||||
backgroundColor: theme.palette.background.default,
|
backgroundColor: theme.palette.background.default,
|
||||||
height: "auto",
|
height: "100vh",
|
||||||
width: "100%"
|
width: "100%",
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
},
|
},
|
||||||
drawerHeader: {
|
drawerHeader: {
|
||||||
display: "flex",
|
display: "flex",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
padding: theme.spacing(1),
|
padding: theme.spacing(1),
|
||||||
...theme.mixins.toolbar,
|
...theme.mixins.toolbar,
|
||||||
justifyContent: "flex-start"
|
justifyContent: "flex-start",
|
||||||
}
|
},
|
||||||
});
|
}));
|
||||||
});
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
|
|
@ -46,21 +50,103 @@ interface Props {
|
||||||
export function ChatDrawer(props: Props) {
|
export function ChatDrawer(props: Props) {
|
||||||
const { open, onClose, team } = props;
|
const { open, onClose, team } = props;
|
||||||
const isMobile = useMobile();
|
const isMobile = useMobile();
|
||||||
const classes = useStyles()
|
const classes = useStyles();
|
||||||
|
const teamAPI = useTeamAPI();
|
||||||
|
|
||||||
|
const [selectedTeam, setSelectedTeam] = useState<Team>(team);
|
||||||
|
const [teams, setTeams] = useState<pond.Team[]>([])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
teamAPI.listTeams(10, 0, "desc", "name", undefined, false, undefined).then(resp => {
|
||||||
|
setTeams(resp.data.teams)
|
||||||
|
})
|
||||||
|
}, [teamAPI])
|
||||||
|
|
||||||
|
const openCrisp = () => {
|
||||||
|
openCrispChat()
|
||||||
|
onClose()
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (open) {
|
||||||
|
closeCrispChat()
|
||||||
|
}
|
||||||
|
}, [open])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Drawer open={open} onClose={onClose} anchor="right" >
|
<Drawer
|
||||||
|
open={open}
|
||||||
|
onClose={onClose}
|
||||||
|
anchor="right"
|
||||||
|
slotProps={{
|
||||||
|
paper: {
|
||||||
|
sx: isMobile ? { width: "100%" } : undefined,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box display="flex" flexDirection="row" height="100%">
|
||||||
|
{/* Side icon column */}
|
||||||
|
<Box
|
||||||
|
display="flex"
|
||||||
|
flexDirection="column"
|
||||||
|
alignItems="center"
|
||||||
|
padding={1}
|
||||||
|
gap={1}
|
||||||
|
sx={(theme: Theme) => ({
|
||||||
|
backgroundColor: theme.palette.background.paper,
|
||||||
|
borderRight: `1px solid ${theme.palette.divider}`,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<Tooltip title={team.name()} placement="right">
|
||||||
|
<IconButton
|
||||||
|
onClick={() => setSelectedTeam(team)}
|
||||||
|
sx={{
|
||||||
|
border: selectedTeam.settings?.key === team.settings.key ? "1px solid white" : "1px solid transparent",
|
||||||
|
borderRadius: "50%",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Avatar src={team.settings.avatar} />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip title={"Chat Assistant"} placement="right">
|
||||||
|
<IconButton
|
||||||
|
onClick={openCrisp}
|
||||||
|
>
|
||||||
|
<RobotIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
<Box width={"100%"} borderBottom={"1px solid grey"} />
|
||||||
|
{teams.map((t, i)=> {
|
||||||
|
if (t.settings?.key === team.key()) return null;
|
||||||
|
return (
|
||||||
|
<Tooltip title={t.settings?.name} key={"chat-team-"+i} placement="right">
|
||||||
|
<IconButton
|
||||||
|
onClick={() => setSelectedTeam(Team.create(t))}
|
||||||
|
sx={{
|
||||||
|
border: selectedTeam.key() === t.settings?.key ? "1px solid white" : "1px solid transparent",
|
||||||
|
borderRadius: "50%",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Avatar src={t.settings?.avatar} />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Main chat area */}
|
||||||
<Box className={isMobile ? classes.chatDrawerMobile : classes.chatDrawer}>
|
<Box className={isMobile ? classes.chatDrawerMobile : classes.chatDrawer}>
|
||||||
<Box className={classes.drawerHeader}>
|
<Box className={classes.drawerHeader}>
|
||||||
<IconButton onClick={onClose}>
|
<IconButton onClick={onClose}>
|
||||||
<ChevronRight />
|
<ChevronRight />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<Typography>{team.name()} Chat</Typography>
|
<Typography>{selectedTeam.name()} Chat</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<Box className={classes.chatContainer}>
|
<Box className={classes.chatContainer}>
|
||||||
<Chat objectKey={team.settings.key} type={pond.NoteType.NOTE_TYPE_TEAM}/>
|
<Chat objectKey={selectedTeam.key()} type={pond.NoteType.NOTE_TYPE_TEAM} />
|
||||||
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</Drawer>
|
</Drawer>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -74,10 +74,23 @@ export default function ChatMessage(props: Props) {
|
||||||
const mappedPerm = pond.Permission[perm as keyof typeof pond.Permission];
|
const mappedPerm = pond.Permission[perm as keyof typeof pond.Permission];
|
||||||
return mappedPerm; // Return null for invalid keys
|
return mappedPerm; // Return null for invalid keys
|
||||||
}).filter(Boolean); // Optionally filter out any null values
|
}).filter(Boolean); // Optionally filter out any null values
|
||||||
|
if (user.id() === props.chat.profile?.id) {
|
||||||
|
perms = [
|
||||||
|
pond.Permission.PERMISSION_READ,
|
||||||
|
pond.Permission.PERMISSION_WRITE
|
||||||
|
]
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// console.error("data.permissions is not an array or does not exist");
|
// console.error("data.permissions is not an array or does not exist");
|
||||||
|
if (user.id() === props.chat.profile?.id) {
|
||||||
|
perms = [
|
||||||
|
pond.Permission.PERMISSION_READ,
|
||||||
|
pond.Permission.PERMISSION_WRITE
|
||||||
|
]
|
||||||
|
} else {
|
||||||
perms = [];
|
perms = [];
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return perms
|
return perms
|
||||||
}
|
}
|
||||||
const permissions = generatePermissions()
|
const permissions = generatePermissions()
|
||||||
|
|
|
||||||
|
|
@ -72,6 +72,7 @@ export default function ChatOutput(props: Props) {
|
||||||
<Box ref={containerRef} onScroll={onScroll} style={{
|
<Box ref={containerRef} onScroll={onScroll} style={{
|
||||||
overflowY: "scroll",
|
overflowY: "scroll",
|
||||||
overflowX: "hidden",
|
overflowX: "hidden",
|
||||||
|
height: "100%",
|
||||||
}}>
|
}}>
|
||||||
{props.messages.length < props.totalMessages && (
|
{props.messages.length < props.totalMessages && (
|
||||||
<ListItemButton onClick={seeMore} sx={{ justifyContent: "center"}}>
|
<ListItemButton onClick={seeMore} sx={{ justifyContent: "center"}}>
|
||||||
|
|
|
||||||
111
src/chat/CrispChat.ts
Normal file
111
src/chat/CrispChat.ts
Normal file
|
|
@ -0,0 +1,111 @@
|
||||||
|
import { Crisp } from "crisp-sdk-web";
|
||||||
|
|
||||||
|
const CRISP_HIDDEN_STYLE_ID = "crisp-hide-override";
|
||||||
|
const ANIMATION_DURATION_MS = 300;
|
||||||
|
|
||||||
|
let initialized = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize Crisp and immediately hide the default chat button.
|
||||||
|
* Call this once on app load (e.g., in UserWrapper after user data is ready).
|
||||||
|
*/
|
||||||
|
export function initCrisp(opts: {
|
||||||
|
websiteId: string;
|
||||||
|
email?: string;
|
||||||
|
nickname?: string;
|
||||||
|
phone?: string;
|
||||||
|
tokenId?: string;
|
||||||
|
}) {
|
||||||
|
if (initialized) return;
|
||||||
|
|
||||||
|
Crisp.configure(opts.websiteId);
|
||||||
|
Crisp.session.reset();
|
||||||
|
|
||||||
|
if (opts.email) Crisp.user.setEmail(opts.email);
|
||||||
|
if (opts.nickname) Crisp.user.setNickname(opts.nickname);
|
||||||
|
if (opts.phone) Crisp.user.setPhone(opts.phone);
|
||||||
|
if (opts.tokenId) Crisp.setTokenId(opts.tokenId);
|
||||||
|
|
||||||
|
initialized = true;
|
||||||
|
|
||||||
|
// Hide the chat button off-screen
|
||||||
|
injectCrispStyles();
|
||||||
|
|
||||||
|
// Auto-hide when the user closes the chat
|
||||||
|
Crisp.chat.onChatClosed(() => {
|
||||||
|
hideCrispChatButton();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inject base styles that keep Crisp hidden until we want it.
|
||||||
|
* Uses opacity + visibility so Crisp keeps its default bottom-right positioning.
|
||||||
|
*/
|
||||||
|
function injectCrispStyles() {
|
||||||
|
let style = document.getElementById(CRISP_HIDDEN_STYLE_ID);
|
||||||
|
if (!style) {
|
||||||
|
style = document.createElement("style");
|
||||||
|
style.id = CRISP_HIDDEN_STYLE_ID;
|
||||||
|
document.head.appendChild(style);
|
||||||
|
}
|
||||||
|
|
||||||
|
style.textContent = `
|
||||||
|
#crisp-chatbox {
|
||||||
|
z-index: 99999 !important;
|
||||||
|
transition: opacity ${ANIMATION_DURATION_MS}ms ease-in-out,
|
||||||
|
visibility ${ANIMATION_DURATION_MS}ms ease-in-out !important;
|
||||||
|
opacity: 0 !important;
|
||||||
|
visibility: hidden !important;
|
||||||
|
pointer-events: none !important;
|
||||||
|
}
|
||||||
|
#crisp-chatbox.crisp-visible {
|
||||||
|
opacity: 1 !important;
|
||||||
|
visibility: visible !important;
|
||||||
|
pointer-events: auto !important;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the Crisp widget and open the chat window.
|
||||||
|
* Safe to call from any onClick handler.
|
||||||
|
*/
|
||||||
|
export function openCrispChat() {
|
||||||
|
const chatbox = document.getElementById("crisp-chatbox");
|
||||||
|
if (chatbox) {
|
||||||
|
chatbox.classList.add("crisp-visible");
|
||||||
|
setTimeout(() => {
|
||||||
|
Crisp.chat.open();
|
||||||
|
}, ANIMATION_DURATION_MS);
|
||||||
|
} else {
|
||||||
|
Crisp.chat.open();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close the chat window and hide the widget.
|
||||||
|
*/
|
||||||
|
export function closeCrispChat() {
|
||||||
|
Crisp.chat.close();
|
||||||
|
hideCrispChatButton();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the default Crisp button without opening the chat.
|
||||||
|
*/
|
||||||
|
export function showCrispButton() {
|
||||||
|
const chatbox = document.getElementById("crisp-chatbox");
|
||||||
|
if (chatbox) {
|
||||||
|
chatbox.classList.add("crisp-visible");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hide the default Crisp button.
|
||||||
|
*/
|
||||||
|
export function hideCrispChatButton() {
|
||||||
|
const chatbox = document.getElementById("crisp-chatbox");
|
||||||
|
if (chatbox) {
|
||||||
|
chatbox.classList.remove("crisp-visible");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -9,7 +9,7 @@ import {
|
||||||
Typography
|
Typography
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { useFileControllerAPI, useGlobalState } from "providers";
|
import { useFileControllerAPI, useGlobalState } from "providers";
|
||||||
import { useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import ErrorIcon from "@mui/icons-material/Error";
|
import ErrorIcon from "@mui/icons-material/Error";
|
||||||
import CheckCircleOutlineIcon from "@mui/icons-material/CheckCircleOutline";
|
import CheckCircleOutlineIcon from "@mui/icons-material/CheckCircleOutline";
|
||||||
import { getThemeType } from "theme";
|
import { getThemeType } from "theme";
|
||||||
|
|
@ -25,6 +25,7 @@ interface Props {
|
||||||
uniqueID?: string;
|
uniqueID?: string;
|
||||||
keys?: string[];
|
keys?: string[];
|
||||||
types?: string[];
|
types?: string[];
|
||||||
|
hasFilePermission: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const useStyles = makeStyles((theme: Theme) => {
|
const useStyles = makeStyles((theme: Theme) => {
|
||||||
|
|
@ -53,8 +54,8 @@ const useStyles = makeStyles((theme: Theme) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
export default function FileSelector(props: Props) {
|
export default function FileSelector(props: Props) {
|
||||||
const { uploadEnd, uploadStart, uniqueID, toAttach, keys, types } = props;
|
const { uploadEnd, uploadStart, uniqueID, toAttach, keys, types, hasFilePermission } = props;
|
||||||
const [{ userTeamPermissions }] = useGlobalState();
|
// const [{ userTeamPermissions }] = useGlobalState();
|
||||||
const [fileList, setFileList] = useState<FileList | undefined>();
|
const [fileList, setFileList] = useState<FileList | undefined>();
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
const fileAPI = useFileControllerAPI();
|
const fileAPI = useFileControllerAPI();
|
||||||
|
|
@ -142,7 +143,7 @@ export default function FileSelector(props: Props) {
|
||||||
disabled={
|
disabled={
|
||||||
uploading ||
|
uploading ||
|
||||||
(!uploadFailed && fileID !== "") ||
|
(!uploadFailed && fileID !== "") ||
|
||||||
!userTeamPermissions.includes(pond.Permission.PERMISSION_FILE_MANAGEMENT)
|
!hasFilePermission
|
||||||
}
|
}
|
||||||
multiple={false}
|
multiple={false}
|
||||||
name={uniqueID || "fileInput"}
|
name={uniqueID || "fileInput"}
|
||||||
|
|
@ -163,7 +164,7 @@ export default function FileSelector(props: Props) {
|
||||||
<label
|
<label
|
||||||
htmlFor={uniqueID || "fileInput"}
|
htmlFor={uniqueID || "fileInput"}
|
||||||
className={
|
className={
|
||||||
userTeamPermissions.includes(pond.Permission.PERMISSION_FILE_MANAGEMENT)
|
hasFilePermission
|
||||||
? classes.fileSelect
|
? classes.fileSelect
|
||||||
: classes.fileDisabled
|
: classes.fileDisabled
|
||||||
}>
|
}>
|
||||||
|
|
|
||||||
|
|
@ -10,19 +10,22 @@ interface Props {
|
||||||
uploadEnd: (fileID?: string, fileName?: string) => void;
|
uploadEnd: (fileID?: string, fileName?: string) => void;
|
||||||
keys?: string[];
|
keys?: string[];
|
||||||
types?: string[];
|
types?: string[];
|
||||||
|
hasFilePermission: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function FileUploader(props: Props) {
|
export default function FileUploader(props: Props) {
|
||||||
const { uploadStart, uploadEnd, startingSelectorCount, toAttach, keys, types } = props;
|
const { uploadStart, uploadEnd, startingSelectorCount, toAttach, keys, types, hasFilePermission } = props;
|
||||||
const [numSelectors, setNumSelectors] = useState(startingSelectorCount || 1);
|
const [numSelectors, setNumSelectors] = useState(startingSelectorCount || 1);
|
||||||
const [selectors, setSelectors] = useState<JSX.Element[]>([]);
|
const [selectors, setSelectors] = useState<JSX.Element[]>([]);
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let currentSelectors: JSX.Element[] = [];
|
let currentSelectors: JSX.Element[] = [];
|
||||||
for (let i = 0; i < numSelectors; i++) {
|
for (let i = 0; i < numSelectors; i++) {
|
||||||
currentSelectors.push(
|
currentSelectors.push(
|
||||||
<Box key={i} margin={2}>
|
<Box key={i} margin={2}>
|
||||||
<FileSelector
|
<FileSelector
|
||||||
|
hasFilePermission={hasFilePermission}
|
||||||
toAttach={toAttach}
|
toAttach={toAttach}
|
||||||
keys={keys}
|
keys={keys}
|
||||||
types={types}
|
types={types}
|
||||||
|
|
|
||||||
|
|
@ -402,7 +402,7 @@ export default function ContractSettings(props: Props) {
|
||||||
borderRadius: 5,
|
borderRadius: 5,
|
||||||
margin: 20
|
margin: 20
|
||||||
}}>
|
}}>
|
||||||
Note: When delivering grain on contract from a field or a bin the Grain Types must match
|
Note: When delivering grain on contract from a field or a bin the Grain Types must match, if it is a custom type the names must match.
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<Grid
|
<Grid
|
||||||
|
|
|
||||||
|
|
@ -6,26 +6,17 @@ import {
|
||||||
Button,
|
Button,
|
||||||
Grid2,
|
Grid2,
|
||||||
IconButton,
|
IconButton,
|
||||||
Paper,
|
|
||||||
Stack,
|
Stack,
|
||||||
Tab,
|
Tab,
|
||||||
Table,
|
|
||||||
TableBody,
|
|
||||||
TableCell,
|
|
||||||
TableContainer,
|
|
||||||
TableHead,
|
|
||||||
TableRow,
|
|
||||||
Tabs,
|
Tabs,
|
||||||
Typography
|
Typography
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import React, { useCallback, useEffect, useState } from "react";
|
import React, { useCallback, useEffect, useState } from "react";
|
||||||
import { useMobile, useSnackbar, useUserAPI } from "hooks";
|
import { useMobile, useSnackbar } from "hooks";
|
||||||
import { useGlobalState, useFieldAPI, useJohnDeereProxyAPI, useCNHiProxyAPI } from "providers";
|
import { useGlobalState, useFieldAPI, useJohnDeereProxyAPI, useCNHiProxyAPI } from "providers";
|
||||||
//import HarvestTable from "harvestPlan/HarvestTable";
|
//import HarvestTable from "harvestPlan/HarvestTable";
|
||||||
import { Field, fieldScope, teamScope } from "models";
|
import { Field } from "models";
|
||||||
import GrainDescriber from "grain/GrainDescriber";
|
import GrainDescriber from "grain/GrainDescriber";
|
||||||
import { pond } from "protobuf-ts/pond";
|
|
||||||
import HarvestSettings from "harvestPlan/HarvestSettings";
|
|
||||||
import ResponsiveTable, { Column } from "common/ResponsiveTable";
|
import ResponsiveTable, { Column } from "common/ResponsiveTable";
|
||||||
import FieldMinimap from "./Fieldminimap";
|
import FieldMinimap from "./Fieldminimap";
|
||||||
import FieldActions from "./FieldActions";
|
import FieldActions from "./FieldActions";
|
||||||
|
|
@ -36,12 +27,6 @@ import ShareAllFields from "./ShareAllFields";
|
||||||
import EventBlocker from "common/EventBlocker";
|
import EventBlocker from "common/EventBlocker";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
interface TabPanelProps {
|
|
||||||
children?: React.ReactNode;
|
|
||||||
index: any;
|
|
||||||
value: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function FieldList() {
|
export default function FieldList() {
|
||||||
const [{ as }] = useGlobalState();
|
const [{ as }] = useGlobalState();
|
||||||
const isMobile = useMobile();
|
const isMobile = useMobile();
|
||||||
|
|
@ -50,7 +35,6 @@ export default function FieldList() {
|
||||||
const cnhAPI = useCNHiProxyAPI();
|
const cnhAPI = useCNHiProxyAPI();
|
||||||
const { openSnack } = useSnackbar();
|
const { openSnack } = useSnackbar();
|
||||||
const [tabValue, setTabValue] = React.useState(0);
|
const [tabValue, setTabValue] = React.useState(0);
|
||||||
const [openHarvestSettings, setOpenHarvestSettings] = useState(false);
|
|
||||||
// const [fieldForPlan, setFieldForPlan] = useState(Field.create());
|
// const [fieldForPlan, setFieldForPlan] = useState(Field.create());
|
||||||
const [fields, setFields] = useState<Field[]>([]);
|
const [fields, setFields] = useState<Field[]>([]);
|
||||||
const [total, setTotal] = useState(0)
|
const [total, setTotal] = useState(0)
|
||||||
|
|
@ -235,7 +219,7 @@ const columns: Column<Field>[] = [
|
||||||
<Typography textAlign="center">{row.grainName()}</Typography>
|
<Typography textAlign="center">{row.grainName()}</Typography>
|
||||||
<Typography textAlign="center">{row.acres()} Acres</Typography>
|
<Typography textAlign="center">{row.acres()} Acres</Typography>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Button variant="contained" color="primary">Go</Button>
|
<Button variant="contained" color="primary" fullWidth onClick={() => goTo(row)}>Go</Button>
|
||||||
</Grid2>
|
</Grid2>
|
||||||
</Grid2>
|
</Grid2>
|
||||||
</AccordionDetails>
|
</AccordionDetails>
|
||||||
|
|
|
||||||
226
src/gate/GateDeltaTempGraph.tsx
Normal file
226
src/gate/GateDeltaTempGraph.tsx
Normal file
|
|
@ -0,0 +1,226 @@
|
||||||
|
import { Card, CardHeader, Grid2, Typography } from "@mui/material"
|
||||||
|
import { blue, orange } from "@mui/material/colors"
|
||||||
|
import SingleSetAreaChart, { SSAreaDataPoint } from "charts/SingleSetAreaChart"
|
||||||
|
import { useComponentAPI } from "hooks"
|
||||||
|
import { cloneDeep } from "lodash"
|
||||||
|
import { Component } from "models"
|
||||||
|
import { UnitMeasurement } from "models/UnitMeasurement"
|
||||||
|
import moment from "moment"
|
||||||
|
import { Moment } from "moment"
|
||||||
|
import { pond } from "protobuf-ts/pond"
|
||||||
|
import { quack } from "protobuf-ts/quack"
|
||||||
|
import { useGlobalState } from "providers"
|
||||||
|
import { useEffect, useState } from "react"
|
||||||
|
import { getTemperatureUnit } from "utils"
|
||||||
|
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
//the key for the temperature chain, this may be overhauled to only be a single node in the future for the outlet and the 'inlet' would be the ambient
|
||||||
|
deviceID: number
|
||||||
|
tempComponent: Component
|
||||||
|
ambient?: Component
|
||||||
|
start: Moment;
|
||||||
|
end: Moment;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function GateDeltaTempGraph(props: Props){
|
||||||
|
const {
|
||||||
|
tempComponent,
|
||||||
|
ambient,
|
||||||
|
deviceID,
|
||||||
|
start,
|
||||||
|
end
|
||||||
|
} = props
|
||||||
|
const [data, setData] = useState<SSAreaDataPoint[]>([])
|
||||||
|
const [{user}] = useGlobalState()
|
||||||
|
const componentAPI = useComponentAPI();
|
||||||
|
const [recent, setRecent] = useState<SSAreaDataPoint>()
|
||||||
|
const warmingColour = orange["500"]
|
||||||
|
const coolingColour = blue["500"]
|
||||||
|
|
||||||
|
const getUnitByType = (unitMeasurements: UnitMeasurement[], type: quack.MeasurementType) => {
|
||||||
|
return unitMeasurements.find(u => u.type === type);
|
||||||
|
}
|
||||||
|
|
||||||
|
const buildDeltas = (ambientMeasurements: UnitMeasurement[], tempCompMeasurements: UnitMeasurement[], measurementType: quack.MeasurementType) => {
|
||||||
|
const ambient = getUnitByType(ambientMeasurements, measurementType);
|
||||||
|
const outlet = getUnitByType(tempCompMeasurements, measurementType);
|
||||||
|
|
||||||
|
if (!ambient || !outlet) return [];
|
||||||
|
|
||||||
|
let i = 0;
|
||||||
|
let j = 0;
|
||||||
|
|
||||||
|
let lastAmbient: number | null = null;
|
||||||
|
let lastOutlet: number | null = null;
|
||||||
|
|
||||||
|
const deltas: SSAreaDataPoint[] = [];
|
||||||
|
|
||||||
|
const ambientTimes = ambient.timestamps.map(t => moment(t).valueOf()).reverse();
|
||||||
|
const outletTimes = outlet.timestamps.map(t => moment(t).valueOf()).reverse();
|
||||||
|
const ambientValues = [...ambient.values].reverse();
|
||||||
|
const outletValues = [...outlet.values].reverse();
|
||||||
|
|
||||||
|
while (i < ambientTimes.length || j < outletTimes.length) {
|
||||||
|
|
||||||
|
const aTime = i < ambientTimes.length ? ambientTimes[i] : Infinity;
|
||||||
|
const bTime = j < outletTimes.length ? outletTimes[j] : Infinity;
|
||||||
|
|
||||||
|
if (aTime <= bTime) {
|
||||||
|
const valueArray = ambientValues[i++];
|
||||||
|
if (!valueArray.error && valueArray.values.length > 0) {
|
||||||
|
lastAmbient = valueArray.values[valueArray.values.length -1];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastAmbient !== null && lastOutlet !== null) {
|
||||||
|
deltas.push({
|
||||||
|
timestamp: aTime,
|
||||||
|
value: Math.round((lastOutlet - lastAmbient)*100)/100
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
const valueArray = outletValues[j++];
|
||||||
|
if (!valueArray.error && valueArray.values.length > 0) {
|
||||||
|
lastOutlet = valueArray.values[valueArray.values.length -1];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastAmbient !== null && lastOutlet !== null) {
|
||||||
|
deltas.push({
|
||||||
|
timestamp: bTime,
|
||||||
|
value: Math.round((lastOutlet - lastAmbient)*100)/100
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return deltas
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* the use effect will use the temp component passed in get the measurements, then use the first node and the last node to determine the difference,
|
||||||
|
* if there is only one node, or only one node is reading due to errors, then it will use that as both the inlet and outlet so the delta will be 0,
|
||||||
|
*
|
||||||
|
* if an ambient is passed in it will use the readings from the ambient as the inlet and the temp component as the outlet
|
||||||
|
*/
|
||||||
|
useEffect(()=>{
|
||||||
|
//get the measurements for the temp component
|
||||||
|
componentAPI.listUnitMeasurements(
|
||||||
|
deviceID,
|
||||||
|
tempComponent.key(),
|
||||||
|
start.toISOString(),
|
||||||
|
end.toISOString(),
|
||||||
|
500,
|
||||||
|
0,
|
||||||
|
"timestamp",
|
||||||
|
).then(resp => {
|
||||||
|
if(resp.data.measurements){
|
||||||
|
let tempCompMeasurements = resp.data.measurements.map(um => UnitMeasurement.any(um, user));
|
||||||
|
//if there is an ambient component, then treat the temp component like a single sensor and not a chain, and use the ambient measurements as the inlet
|
||||||
|
if(ambient){
|
||||||
|
//need to then load the measurements for the ambient component
|
||||||
|
componentAPI.listUnitMeasurements(
|
||||||
|
deviceID,
|
||||||
|
ambient.key(),
|
||||||
|
start.toISOString(),
|
||||||
|
end.toISOString(),
|
||||||
|
500,
|
||||||
|
0,
|
||||||
|
"timestamp",
|
||||||
|
).then(resp => {
|
||||||
|
let ambientMeasurements = resp.data.measurements.map(um => UnitMeasurement.any(um, user));
|
||||||
|
//now loop through each of them to 'combine' them into a single array
|
||||||
|
const deltas = buildDeltas(ambientMeasurements, tempCompMeasurements, quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE)
|
||||||
|
if(deltas.length > 0){
|
||||||
|
setData(deltas)
|
||||||
|
setRecent(deltas[deltas.length-1])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
}else{//else no ambient is passed in treat the temp component like a chain
|
||||||
|
let deltas: SSAreaDataPoint[] = []
|
||||||
|
tempCompMeasurements.forEach(um => {
|
||||||
|
if (um.type === quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE){
|
||||||
|
um.values.forEach((nodes, index) => {
|
||||||
|
if (nodes.values && nodes.values.length > 0 && um.timestamps[index]){
|
||||||
|
//use the first node as the inlet
|
||||||
|
let inletNode = nodes.values[0]
|
||||||
|
//use the second node as the outlet, if there is an error or there is only one node then it will be used as both and the delta will be 0
|
||||||
|
let outletNode = nodes.values[nodes.values.length -1]
|
||||||
|
let newDelta: SSAreaDataPoint = {
|
||||||
|
value: Math.round((outletNode - inletNode)*100)/100,
|
||||||
|
timestamp: moment(um.timestamps[index]).valueOf(),
|
||||||
|
}
|
||||||
|
deltas.push(newDelta)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
setData(deltas)
|
||||||
|
//use the components status to set the recent to show in the header of the card
|
||||||
|
let recent: SSAreaDataPoint | undefined
|
||||||
|
if(tempComponent.status.measurement){
|
||||||
|
tempComponent.status.measurement.forEach(um => {
|
||||||
|
if(um.type === quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE){
|
||||||
|
let clone = cloneDeep(um)
|
||||||
|
let m = UnitMeasurement.create(clone, user)
|
||||||
|
if(m.values.length > 0){
|
||||||
|
let lastReading = m.values[m.values.length - 1]
|
||||||
|
if (lastReading.values.length > 0){
|
||||||
|
recent = {
|
||||||
|
value: lastReading.values[lastReading.values.length -1] - lastReading.values[0],
|
||||||
|
timestamp: moment(m.timestamps[m.values.length - 1]).valueOf()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
setRecent(recent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},[tempComponent, ambient, deviceID, start, end])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card raised style={{ padding: 10, marginBottom: 15, marginTop: 15 }}>
|
||||||
|
<CardHeader
|
||||||
|
title={<Typography style={{ fontSize: 25, fontWeight: 650 }}>Delta Temp</Typography>}
|
||||||
|
subheader={
|
||||||
|
recent ?
|
||||||
|
(
|
||||||
|
<Grid2 container>
|
||||||
|
<Grid2 size={12}>
|
||||||
|
<span>
|
||||||
|
{"Delta Temp: "}
|
||||||
|
<span style={{ color: recent.value > 0 ? warmingColour : coolingColour, fontWeight: 500 }}>
|
||||||
|
{recent.value.toFixed(2) + (getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT ? " °F" : " °C")}
|
||||||
|
</span>
|
||||||
|
<br />
|
||||||
|
</span>
|
||||||
|
</Grid2>
|
||||||
|
<Grid2 size={12}>
|
||||||
|
<Typography color="textSecondary" variant={"caption"}>
|
||||||
|
{moment(recent.timestamp).fromNow()}
|
||||||
|
</Typography>
|
||||||
|
</Grid2>
|
||||||
|
</Grid2>
|
||||||
|
)
|
||||||
|
: (
|
||||||
|
<Typography variant="body1" color="textPrimary">
|
||||||
|
No Data
|
||||||
|
</Typography>
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<SingleSetAreaChart
|
||||||
|
data={data}
|
||||||
|
tooltipLabel="Delta Temp"
|
||||||
|
tooltipUnit={getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT ? " °F" : " °C"}
|
||||||
|
yAxisLabel={"Delta T" + (getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT ? " °F" : " °C")}
|
||||||
|
colourAboveZero={{colour: warmingColour, label: "Heating"}}
|
||||||
|
colourBelowZero={{colour: coolingColour, label: "Cooling"}}/>
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -23,6 +23,7 @@ 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";
|
import ButtonGroup from "common/ButtonGroup";
|
||||||
|
import { cloneDeep } from "lodash";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
gate: Gate;
|
gate: Gate;
|
||||||
|
|
@ -44,21 +45,36 @@ export default function GateDevice(props: Props) {
|
||||||
const [tempKey, setTempKey] = useState("");
|
const [tempKey, setTempKey] = useState("");
|
||||||
const [pressureKey, setPressureKey] = useState("");
|
const [pressureKey, setPressureKey] = useState("");
|
||||||
const [ambientKey, setAmbientKey] = useState("");
|
const [ambientKey, setAmbientKey] = useState("");
|
||||||
|
const [greenLightKey, setGreenLightKey] = useState("");
|
||||||
|
const [redLightKey, setRedLightKey] = useState("");
|
||||||
const { openSnack } = useSnackbar();
|
const { openSnack } = useSnackbar();
|
||||||
const [{ user }] = useGlobalState();
|
const [{ user }] = useGlobalState();
|
||||||
const [interactionDialog, setInteractionDialog] = useState(false);
|
const [interactionDialog, setInteractionDialog] = useState(false);
|
||||||
const [densityTemp, setDensityTemp] = useState(0);
|
const [densityTemp, setDensityTemp] = useState(0);
|
||||||
const isMobile = useMobile();
|
const isMobile = useMobile();
|
||||||
const [pcaState, setPCAState] = useState(false);
|
// const [pcaState, setPCAState] = useState(false);
|
||||||
const [pcaFanOn, setPCAFanOn] = useState(false);
|
// const [pcaFanOn, setPCAFanOn] = useState(false);
|
||||||
const [detail, setDetail] = useState<"sensors" | "analytics">("analytics");
|
const [detail, setDetail] = useState<"sensors" | "analytics">("analytics");
|
||||||
const [lastAmbient, setLastAmbient] = useState(0);
|
const [lastAmbient, setLastAmbient] = useState(0);
|
||||||
const [lastTemps, setLastTemps] = useState({ t1: 0, t2: 0 });
|
//const [lastTemps, setLastTemps] = useState({ t1: 0, t2: 0 });
|
||||||
const [lastPressures, setLastPressures] = useState({ p1: 0, p2: 0 });
|
const [ductTemp, setDuctTemp] = useState<number | undefined>()
|
||||||
|
//const [lastPressures, setLastPressures] = useState({ p1: 0, p2: 0 });
|
||||||
|
const [ductPressure, setDuctPressure] = useState<number | undefined>()
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
//addresses of controller LEDs on a V1 device
|
||||||
|
let redAddr = "3-1-512";
|
||||||
|
let greenAddr = "3-1-1024";
|
||||||
if (comprehensiveDevice.device) {
|
if (comprehensiveDevice.device) {
|
||||||
setDevice(Device.any(comprehensiveDevice.device));
|
setDevice(Device.any(comprehensiveDevice.device));
|
||||||
|
|
||||||
|
//check if the device is a MiPCA V2 device since the lEDs will have different addresses
|
||||||
|
let dev = Device.create(comprehensiveDevice.device);
|
||||||
|
if (dev.settings.product === pond.DeviceProduct.DEVICE_PRODUCT_MIPCA_V2) {
|
||||||
|
redAddr = "3-1-256";
|
||||||
|
greenAddr = "3-1-512";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (comprehensiveDevice.components) {
|
if (comprehensiveDevice.components) {
|
||||||
let components: Map<string, Component> = new Map<string, Component>();
|
let components: Map<string, Component> = new Map<string, Component>();
|
||||||
|
|
@ -87,19 +103,24 @@ export default function GateDevice(props: Props) {
|
||||||
break;
|
break;
|
||||||
case pond.GateComponentType.GATE_COMPONENT_TYPE_PRESSURE:
|
case pond.GateComponentType.GATE_COMPONENT_TYPE_PRESSURE:
|
||||||
setPressureKey(c.key());
|
setPressureKey(c.key());
|
||||||
if (
|
// if (
|
||||||
c.status.measurement[0] &&
|
// c.status.measurement[0] &&
|
||||||
c.status.measurement[0].values[0] &&
|
// c.status.measurement[0].values[0] &&
|
||||||
c.status.measurement[0].values[0].values[1]
|
// c.status.measurement[0].values[0].values[1]
|
||||||
) {
|
// ) {
|
||||||
setPCAFanOn(c.status.measurement[0].values[0].values[1] > 996); //apx 4 iwg
|
// setPCAFanOn(c.status.measurement[0].values[0].values[1] > 996); //apx 4 iwg
|
||||||
}
|
// }
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
// type is unknown, do nothing
|
// type is unknown, do nothing
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (c.locationString() === redAddr && c.subType() === 1) {
|
||||||
|
setRedLightKey(c.key());
|
||||||
|
} else if (c.locationString() === greenAddr && c.subType() === 1) {
|
||||||
|
setGreenLightKey(c.key());
|
||||||
|
}
|
||||||
});
|
});
|
||||||
setComponentOptions(components);
|
setComponentOptions(components);
|
||||||
}
|
}
|
||||||
|
|
@ -125,24 +146,20 @@ export default function GateDevice(props: Props) {
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let tempComponent = componentOptions.get(tempKey);
|
let tempComponent = componentOptions.get(tempKey);
|
||||||
let t1 = 0;
|
|
||||||
let t2 = 0;
|
|
||||||
if(tempComponent){
|
if(tempComponent){
|
||||||
tempComponent.status.measurement.forEach(um => {
|
tempComponent.status.measurement.forEach(um => {
|
||||||
let measurement = UnitMeasurement.any(um, user);
|
if(um.type === quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE){
|
||||||
if (
|
let clone = cloneDeep(um)
|
||||||
measurement.type === quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE &&
|
let measurement = UnitMeasurement.any(clone, user)
|
||||||
measurement.values.length > 0
|
if(measurement.values.length > 0){
|
||||||
) {
|
let readings = measurement.values[measurement.values.length -1]
|
||||||
let nodeVals = measurement.values[0].values;
|
if(readings.values.length > 0){
|
||||||
if (nodeVals.length > 1) {
|
setDuctTemp(readings.values[readings.values.length -1])
|
||||||
t1 = nodeVals[0]; //uses the first node
|
|
||||||
t2 = nodeVals[1]; //uses second node node
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
setLastTemps({ t1, t2 });
|
})
|
||||||
|
}
|
||||||
}, [componentOptions, tempKey, user]);
|
}, [componentOptions, tempKey, user]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -163,24 +180,20 @@ export default function GateDevice(props: Props) {
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let pressureComponent = componentOptions.get(pressureKey);
|
let pressureComponent = componentOptions.get(pressureKey);
|
||||||
let p1 = 0;
|
|
||||||
let p2 = 0;
|
|
||||||
if(pressureComponent){
|
if(pressureComponent){
|
||||||
pressureComponent.status.measurement.forEach(um => {
|
pressureComponent.status.measurement.forEach(um => {
|
||||||
let measurement = UnitMeasurement.any(um, user);
|
if(um.type === quack.MeasurementType.MEASUREMENT_TYPE_PRESSURE){
|
||||||
if (
|
let clone = cloneDeep(um)
|
||||||
measurement.type === quack.MeasurementType.MEASUREMENT_TYPE_PRESSURE &&
|
let measurement = UnitMeasurement.any(clone, user)
|
||||||
measurement.values.length > 0
|
if(measurement.values.length > 0){
|
||||||
) {
|
let readings = measurement.values[measurement.values.length -1]
|
||||||
let nodeVals = measurement.values[0].values;
|
if(readings.values.length > 0){
|
||||||
if (nodeVals.length > 1) {
|
setDuctPressure(readings.values[readings.values.length -1])
|
||||||
p1 = nodeVals[0];
|
|
||||||
p2 = nodeVals[1];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
setLastPressures({ p1, p2 });
|
})
|
||||||
|
}
|
||||||
}, [componentOptions, pressureKey, user]);
|
}, [componentOptions, pressureKey, user]);
|
||||||
|
|
||||||
const ambientSelector = () => {
|
const ambientSelector = () => {
|
||||||
|
|
@ -323,13 +336,15 @@ export default function GateDevice(props: Props) {
|
||||||
: 0
|
: 0
|
||||||
}
|
}
|
||||||
ambientTemp={lastAmbient}
|
ambientTemp={lastAmbient}
|
||||||
innerTemps={lastTemps}
|
//innerTemps={lastTemps}
|
||||||
innerPressures={lastPressures}
|
//innerPressures={lastPressures}
|
||||||
|
ductTemp={ductTemp}
|
||||||
|
ductPressure={ductPressure}
|
||||||
ambientSelector={ambientSelector}
|
ambientSelector={ambientSelector}
|
||||||
tempChainSelector={tempSelector}
|
tempChainSelector={tempSelector}
|
||||||
pressureChainSelector={pressureSelector}
|
pressureChainSelector={pressureSelector}
|
||||||
pcaState={pcaState}
|
pcaState={gate.status.pcaState}
|
||||||
pcaFanState={pcaFanOn}
|
// pcaFanState={pcaFanOn}
|
||||||
checkInTime={device.status.lastActive}
|
checkInTime={device.status.lastActive}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
|
|
@ -357,11 +372,11 @@ export default function GateDevice(props: Props) {
|
||||||
gate={gate}
|
gate={gate}
|
||||||
display={detail}
|
display={detail}
|
||||||
compMap={componentOptions}
|
compMap={componentOptions}
|
||||||
setPCAState={(state: boolean) => {
|
|
||||||
setPCAState(state);
|
|
||||||
}}
|
|
||||||
ambient={ambientKey}
|
ambient={ambientKey}
|
||||||
pressure={pressureKey}
|
pressure={pressureKey}
|
||||||
|
tempChain={tempKey}
|
||||||
|
redKey={redLightKey}
|
||||||
|
greenKey={greenLightKey}
|
||||||
device={device.id()}
|
device={device.id()}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ interface Props {
|
||||||
ambient?: string;
|
ambient?: string;
|
||||||
newXDomain?: number[] | string[];
|
newXDomain?: number[] | string[];
|
||||||
pressureComponent?: string;
|
pressureComponent?: string;
|
||||||
setPCAState: (state: boolean) => void;
|
// setPCAState: (state: boolean) => void;
|
||||||
multiGraphZoom?: (domain: number[] | string[]) => void;
|
multiGraphZoom?: (domain: number[] | string[]) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -55,7 +55,7 @@ export default function GateFlowGraph(props: Props) {
|
||||||
device,
|
device,
|
||||||
ambient,
|
ambient,
|
||||||
pressureComponent,
|
pressureComponent,
|
||||||
setPCAState,
|
// setPCAState,
|
||||||
start,
|
start,
|
||||||
end,
|
end,
|
||||||
newXDomain,
|
newXDomain,
|
||||||
|
|
@ -131,22 +131,22 @@ export default function GateFlowGraph(props: Props) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
setRuntime(moment.duration(runtime));
|
setRuntime(moment.duration(runtime));
|
||||||
let state = false;
|
// let state = false;
|
||||||
if (
|
// if (
|
||||||
data.length > 1 &&
|
// data.length > 1 &&
|
||||||
data[data.length - 1].value > gate.lowerFlow() &&
|
// data[data.length - 1].value > gate.lowerFlow() &&
|
||||||
data[data.length - 1].value < gate.upperFlow()
|
// data[data.length - 1].value < gate.upperFlow()
|
||||||
) {
|
// ) {
|
||||||
state = true;
|
// state = true;
|
||||||
}
|
// }
|
||||||
setPCAState(state);
|
// setPCAState(state);
|
||||||
}
|
}
|
||||||
setFlowData(data);
|
setFlowData(data);
|
||||||
setLoadingChartData(false);
|
setLoadingChartData(false);
|
||||||
setRecent(recent);
|
setRecent(recent);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [gateAPI, gate, ambient, pressureComponent, start, end, device, setPCAState, as]); // eslint-disable-line react-hooks/exhaustive-deps
|
}, [gateAPI, gate, ambient, pressureComponent, start, end, device, as]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||||
|
|
||||||
const loadFlowEvents = () => {
|
const loadFlowEvents = () => {
|
||||||
if (ambient && pressureComponent) {
|
if (ambient && pressureComponent) {
|
||||||
|
|
@ -209,6 +209,8 @@ export default function GateFlowGraph(props: Props) {
|
||||||
{flowData.length !== 0 ? (
|
{flowData.length !== 0 ? (
|
||||||
<SingleSetAreaChart
|
<SingleSetAreaChart
|
||||||
data={flowData}
|
data={flowData}
|
||||||
|
tooltipLabel="Flow"
|
||||||
|
yAxisLabel="Mass Flow (kg/s)"
|
||||||
maxRef={gate.upperFlow()}
|
maxRef={gate.upperFlow()}
|
||||||
minRef={gate.lowerFlow()}
|
minRef={gate.lowerFlow()}
|
||||||
newXDomain={newXDomain}
|
newXDomain={newXDomain}
|
||||||
|
|
|
||||||
|
|
@ -28,15 +28,19 @@ import { avg } from "utils";
|
||||||
import GateFlowGraph from "./GateFlowGraph";
|
import GateFlowGraph from "./GateFlowGraph";
|
||||||
import { makeStyles } from "@mui/styles";
|
import { makeStyles } from "@mui/styles";
|
||||||
import { cloneDeep } from "lodash";
|
import { cloneDeep } from "lodash";
|
||||||
|
import GateDeltaTempGraph from "./GateDeltaTempGraph";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
gate: Gate;
|
gate: Gate;
|
||||||
display: "analytics" | "sensors";
|
display: "analytics" | "sensors";
|
||||||
compMap: Map<string, Component>;
|
compMap: Map<string, Component>;
|
||||||
device: string | number;
|
device: number;
|
||||||
pressure: string;
|
pressure: string;
|
||||||
ambient: string;
|
ambient: string;
|
||||||
setPCAState: (state: boolean) => void;
|
tempChain: string;
|
||||||
|
redKey: string;
|
||||||
|
greenKey: string;
|
||||||
|
// setPCAState: (state: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const useStyles = makeStyles((theme: Theme) => ({
|
const useStyles = makeStyles((theme: Theme) => ({
|
||||||
|
|
@ -60,7 +64,7 @@ const useStyles = makeStyles((theme: Theme) => ({
|
||||||
);
|
);
|
||||||
|
|
||||||
export default function GateGraphs(props: Props) {
|
export default function GateGraphs(props: Props) {
|
||||||
const { gate, display, compMap, device, pressure, ambient, setPCAState } = props;
|
const { gate, display, compMap, device, pressure, ambient, tempChain, greenKey, redKey } = props;
|
||||||
const [{ as }] = useGlobalState();
|
const [{ as }] = useGlobalState();
|
||||||
const [xDomain, setXDomain] = useState<string[] | number[]>(["dataMin", "dataMax"]);
|
const [xDomain, setXDomain] = useState<string[] | number[]>(["dataMin", "dataMax"]);
|
||||||
const [zoomed, setZoomed] = useState(false);
|
const [zoomed, setZoomed] = useState(false);
|
||||||
|
|
@ -270,44 +274,78 @@ export default function GateGraphs(props: Props) {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const sensorGraphs = () => {
|
const graphCard = (component: Component, unitMeasurements: UnitMeasurement[]) => {
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Card raised key={component.key()} style={{ padding: 10, marginBottom: 15 }}>
|
||||||
{Array.from(compMeasurements.values()).map((compMeasurement, i) => {
|
{graphHeader(component)}
|
||||||
let c = Component.create();
|
{unitMeasurements.map(um => {
|
||||||
if (compMeasurement[0]) {
|
|
||||||
c = compMap.get(compMeasurement[0].componentId) ?? Component.create();
|
|
||||||
}
|
|
||||||
if (compMap.get(c.key())) {
|
|
||||||
return (
|
|
||||||
<Card raised key={i} style={{ padding: 10, marginBottom: 15 }}>
|
|
||||||
{graphHeader(c)}
|
|
||||||
{compMeasurement.map(um => {
|
|
||||||
if (um.values[0] && um.values[0].values.length > 1) {
|
if (um.values[0] && um.values[0].values.length > 1) {
|
||||||
return areaGraph(
|
return areaGraph(
|
||||||
um,
|
um,
|
||||||
describeMeasurement(um.type, c.type(), c.subType()),
|
describeMeasurement(um.type, component.type(), component.subType()),
|
||||||
c.settings.smoothingAverages
|
component.settings.smoothingAverages
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return lineGraph(
|
return lineGraph(
|
||||||
um,
|
um,
|
||||||
describeMeasurement(um.type, c.type(), c.subType()),
|
describeMeasurement(um.type, component.type(), component.subType()),
|
||||||
c.settings.smoothingAverages
|
component.settings.smoothingAverages
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
})}
|
})}
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
} else {
|
|
||||||
return <Box></Box>;
|
|
||||||
}
|
}
|
||||||
})}
|
|
||||||
</Box>
|
/**
|
||||||
);
|
* This function creates the charts for the gate page using the MAIN components on the gate, and delta time, it DOES NOT show any other components that could be on the device
|
||||||
};
|
* @returns list of cards for the charts
|
||||||
|
*/
|
||||||
|
const sensorGraphs = () => {
|
||||||
|
const tempComp = compMap.get(tempChain)
|
||||||
|
const ambientComp = compMap.get(ambient)
|
||||||
|
let graphs: JSX.Element[] = []
|
||||||
|
|
||||||
|
//pressure
|
||||||
|
let pressureComp = compMap.get(pressure)
|
||||||
|
let pressureReadings = compMeasurements.get(pressure)
|
||||||
|
if(pressureComp && pressureReadings){
|
||||||
|
graphs.push(graphCard(pressureComp, pressureReadings))
|
||||||
|
}
|
||||||
|
|
||||||
|
if(tempComp){
|
||||||
|
graphs.push(
|
||||||
|
<GateDeltaTempGraph deviceID={device} start={startDate} end={endDate} tempComponent={tempComp} ambient={ambientComp}/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
//T/H chain
|
||||||
|
let tempReadings = compMeasurements.get(tempChain)
|
||||||
|
if(tempComp && tempReadings){
|
||||||
|
graphs.push(graphCard(tempComp, tempReadings))
|
||||||
|
}
|
||||||
|
//ambient
|
||||||
|
let ambientReadings = compMeasurements.get(ambient)
|
||||||
|
if (ambientComp && ambientReadings){
|
||||||
|
graphs.push(graphCard(ambientComp, ambientReadings))
|
||||||
|
}
|
||||||
|
//green
|
||||||
|
let greenComp = compMap.get(greenKey)
|
||||||
|
let greenReadings = compMeasurements.get(greenKey)
|
||||||
|
if (greenComp && greenReadings){
|
||||||
|
graphs.push(graphCard(greenComp, greenReadings))
|
||||||
|
}
|
||||||
|
//red
|
||||||
|
let redComp = compMap.get(redKey)
|
||||||
|
let redReadings = compMeasurements.get(redKey)
|
||||||
|
if (redComp && redReadings){
|
||||||
|
graphs.push(graphCard(redComp, redReadings))
|
||||||
|
}
|
||||||
|
return graphs
|
||||||
|
}
|
||||||
|
|
||||||
const analyticGraphs = () => {
|
const analyticGraphs = () => {
|
||||||
|
let tempComp = compMap.get(tempChain)
|
||||||
|
let ambientComp = compMap.get(ambient)
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<GateFlowGraph
|
<GateFlowGraph
|
||||||
|
|
@ -318,14 +356,18 @@ export default function GateGraphs(props: Props) {
|
||||||
gate={gate}
|
gate={gate}
|
||||||
ambient={ambient}
|
ambient={ambient}
|
||||||
pressureComponent={pressure}
|
pressureComponent={pressure}
|
||||||
setPCAState={(state: boolean) => {
|
// setPCAState={(state: boolean) => {
|
||||||
setPCAState(state);
|
// setPCAState(state);
|
||||||
}}
|
// }}
|
||||||
multiGraphZoom={domain => {
|
multiGraphZoom={domain => {
|
||||||
setXDomain(domain);
|
setXDomain(domain);
|
||||||
setZoomed(true);
|
setZoomed(true);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
{/* add a new chart here for the delta temp over time */}
|
||||||
|
{tempComp &&
|
||||||
|
<GateDeltaTempGraph deviceID={device} start={startDate} end={endDate} tempComponent={tempComp} ambient={ambientComp}/>
|
||||||
|
}
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,11 @@ import { CheckCircleOutline, DoNotDisturb, ErrorOutline, HelpOutlineOutlined, Se
|
||||||
import { cloneDeep } from "lodash";
|
import { cloneDeep } from "lodash";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import { pond } from "protobuf-ts/pond";
|
import { pond } from "protobuf-ts/pond";
|
||||||
|
import { UnitMeasurement } from "models/UnitMeasurement";
|
||||||
|
import { quack } from "protobuf-ts/quack";
|
||||||
|
import { getTemperatureUnit } from "utils";
|
||||||
|
import { teal } from "@mui/material/colors";
|
||||||
|
import { react } from "@babel/types";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
//gates: Gate[];
|
//gates: Gate[];
|
||||||
|
|
@ -47,6 +52,7 @@ export default function GateList(props: Props) {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const isMobile = useMobile();
|
const isMobile = useMobile();
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
|
const [{user}] = useGlobalState()
|
||||||
const [gateDialog, setGateDialog] = useState(false);
|
const [gateDialog, setGateDialog] = useState(false);
|
||||||
const [tablePage, setTablePage] = useState(0)
|
const [tablePage, setTablePage] = useState(0)
|
||||||
const [pageSize, setPageSize] = useState(10)
|
const [pageSize, setPageSize] = useState(10)
|
||||||
|
|
@ -104,6 +110,115 @@ export default function GateList(props: Props) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// const lastOutletReading = (reading: pond.UnitMeasurementsForComponent[]) => {
|
||||||
|
// let outletDisplay = "--"
|
||||||
|
// let timeSince = "No Reading Yet"
|
||||||
|
// let color = ""
|
||||||
|
// reading.forEach(um => {
|
||||||
|
// let clone = cloneDeep(um)
|
||||||
|
// let measurement = UnitMeasurement.create(clone, user)
|
||||||
|
// if(measurement.type === quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE || measurement.type === quack.MeasurementType.MEASUREMENT_TYPE_PRESSURE){
|
||||||
|
// let nodes = measurement.values[measurement.values.length-1].values
|
||||||
|
// outletDisplay = nodes[nodes.length-1].toFixed(2) + " " + measurement.unit
|
||||||
|
// timeSince = moment(measurement.timestamps[measurement.timestamps.length-1]).fromNow()
|
||||||
|
// color = measurement.colour
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
|
||||||
|
// if(isMobile){
|
||||||
|
// return (
|
||||||
|
// <React.Fragment>
|
||||||
|
// <Typography color={color}>{outletDisplay}</Typography>
|
||||||
|
// <Typography>{timeSince}</Typography>
|
||||||
|
// </React.Fragment>
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
// return (
|
||||||
|
// <Box padding={2}>
|
||||||
|
// <Box display="flex" justifyContent="space-between">
|
||||||
|
// <Typography>Value:</Typography>
|
||||||
|
// <Typography color={color}>{outletDisplay}</Typography>
|
||||||
|
// </Box>
|
||||||
|
// <Box display="flex" justifyContent="space-between">
|
||||||
|
// <Typography>Time:</Typography>
|
||||||
|
// <Typography>{timeSince}</Typography>
|
||||||
|
// </Box>
|
||||||
|
// </Box>
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
|
||||||
|
// const tempDisplay = (gate: Gate) => {
|
||||||
|
// let display = "--"
|
||||||
|
// if(gate.status.pcaState !== pond.PCAState.PCA_STATE_OFF){
|
||||||
|
// //only display it if the final temp is between -4 and 60 C, is a valid number, and is not exactly 0
|
||||||
|
// //0 is technically a valid number but the likely hood of the calculation being exactly 0 is negligible
|
||||||
|
// if (gate.status.finalTemp < 60 && gate.status.finalTemp > -40 && !isNaN(gate.status.finalTemp) && gate.status.finalTemp !== 0){
|
||||||
|
// display = getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT ? CtoF(gate.status.finalTemp).toFixed(2) + "°F" : gate.status.finalTemp.toFixed(2) + "°C"
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// return (
|
||||||
|
// <Typography>
|
||||||
|
// {display}
|
||||||
|
// </Typography>
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
|
||||||
|
const conditionDisplay = (gate: Gate) => {
|
||||||
|
let display = ""
|
||||||
|
if(gate.status.pcaState === pond.PCAState.PCA_STATE_OFF){
|
||||||
|
display = "Inactive"
|
||||||
|
} else if (gate.status.pcaState === pond.PCAState.PCA_STATE_UNKNOWN){
|
||||||
|
display = "--"
|
||||||
|
} else { //the pca is currently active calulate the delta temp (outlet - ambient)
|
||||||
|
let ambient = cloneDeep(gate.status.lastAmbientReading)
|
||||||
|
let temp = cloneDeep(gate.status.lastTempReading)
|
||||||
|
|
||||||
|
if(ambient.length > 0 && temp.length > 0){
|
||||||
|
let ambientTemp: number | undefined
|
||||||
|
let outletTemp: number | undefined
|
||||||
|
ambient.forEach(um => {
|
||||||
|
if(um.type === quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE){
|
||||||
|
let clone = cloneDeep(um)
|
||||||
|
let measurement = UnitMeasurement.any(clone, user)
|
||||||
|
if(measurement.values.length > 0){
|
||||||
|
let readings = measurement.values[measurement.values.length -1]
|
||||||
|
if(readings.values.length > 0){
|
||||||
|
ambientTemp = readings.values[readings.values.length -1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
temp.forEach(um => {
|
||||||
|
if(um.type === quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE){
|
||||||
|
let clone = cloneDeep(um)
|
||||||
|
let measurement = UnitMeasurement.any(clone, user)
|
||||||
|
if(measurement.values.length > 0){
|
||||||
|
let readings = measurement.values[measurement.values.length -1]
|
||||||
|
if(readings.values.length > 0){
|
||||||
|
outletTemp = readings.values[readings.values.length -1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if(ambientTemp && outletTemp){
|
||||||
|
let deltaTemp = outletTemp - ambientTemp
|
||||||
|
display = deltaTemp.toFixed(2) + (getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT ? "°F" : "°C")
|
||||||
|
}else{
|
||||||
|
display = "Sensor Missing"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Typography>
|
||||||
|
{display}
|
||||||
|
</Typography>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// const CtoF = (celsius: number) => {
|
||||||
|
// return Math.round((celsius * (9 / 5) + 32) * 100) / 100;
|
||||||
|
// };
|
||||||
|
|
||||||
const desktopCols = (): Column<Gate>[] => {
|
const desktopCols = (): Column<Gate>[] => {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
|
|
@ -126,69 +241,61 @@ export default function GateList(props: Props) {
|
||||||
</Box>
|
</Box>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
{
|
|
||||||
title: "Duct Type",
|
|
||||||
render: gate => (
|
|
||||||
<Box padding={2}>
|
|
||||||
<Typography>
|
|
||||||
{gate.settings.ductName}
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Duct Size(mm)",
|
|
||||||
render: gate => (
|
|
||||||
<Box padding={2}>
|
|
||||||
<Typography>
|
|
||||||
{gate.ductDiameter()}
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Duct Length(m)",
|
|
||||||
render: gate => (
|
|
||||||
<Box padding={2}>
|
|
||||||
<Typography>
|
|
||||||
{gate.settings.ductLength}
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "PCA Unit",
|
|
||||||
render: gate => (
|
|
||||||
<Box padding={2}>
|
|
||||||
<Typography>
|
|
||||||
{gate.settings.pcaType}
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
title: "PCA Status",
|
title: "PCA Status",
|
||||||
render: gate => (
|
render: gate => {
|
||||||
<Box padding={2}>
|
return (<Box padding={2}>
|
||||||
{displayPCAStatus(gate.status.pcaState)}
|
{displayPCAStatus(gate.status.pcaState)}
|
||||||
</Box>
|
</Box>)
|
||||||
)
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Last FLow",
|
title: "Delta Temperature",
|
||||||
render: gate => (
|
render: gate => {
|
||||||
|
return (
|
||||||
<Box padding={2}>
|
<Box padding={2}>
|
||||||
<Box display="flex" justifyContent="space-between">
|
{conditionDisplay(gate)}
|
||||||
<Typography>Last Flow:</Typography>
|
|
||||||
<Typography>{gate.status.lastMassAirflow.toFixed(2)}</Typography>
|
|
||||||
</Box>
|
</Box>
|
||||||
<Box display="flex" justifyContent="space-between">
|
)}
|
||||||
<Typography>Last Reading:</Typography>
|
},
|
||||||
<Typography>{gate.status.lastUpdate !== "" ? moment(gate.status.lastUpdate).fromNow() : "No Reading Yet"}</Typography>
|
//taking out these columns for now because were not sure if the customer actually wants them
|
||||||
</Box>
|
// {
|
||||||
</Box>
|
// title: "Estimated Temp",
|
||||||
)
|
// render: gate => {
|
||||||
}
|
// return (
|
||||||
|
// <Box padding={2}>
|
||||||
|
// {tempDisplay(gate)}
|
||||||
|
// </Box>
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// title: "Last FLow",
|
||||||
|
// render: gate => (
|
||||||
|
// <Box padding={2}>
|
||||||
|
// <Box display="flex" justifyContent="space-between">
|
||||||
|
// <Typography>Flow:</Typography>
|
||||||
|
// <Typography color={teal[500]}>{gate.status.lastMassAirflow.toFixed(2)} kg/s</Typography>
|
||||||
|
// </Box>
|
||||||
|
// <Box display="flex" justifyContent="space-between">
|
||||||
|
// <Typography>Read:</Typography>
|
||||||
|
// <Typography>{gate.status.lastUpdate !== "" ? moment(gate.status.lastUpdate).fromNow() : "No Reading Yet"}</Typography>
|
||||||
|
// </Box>
|
||||||
|
// </Box>
|
||||||
|
// )
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// title: "Outlet Temperature",
|
||||||
|
// render: gate => (
|
||||||
|
// <Box>{lastOutletReading(gate.status.lastTempReading)}</Box>
|
||||||
|
// )
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// title: "Outlet Pressure",
|
||||||
|
// render: gate => (
|
||||||
|
// <Box>{lastOutletReading(gate.status.lastPressureReading)}</Box>
|
||||||
|
// )
|
||||||
|
// }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
const mobileCols = (): Column<Gate>[] => {
|
const mobileCols = (): Column<Gate>[] => {
|
||||||
|
|
@ -224,33 +331,28 @@ export default function GateList(props: Props) {
|
||||||
<Typography>{terminalMap.get(gate.terminal()) ?? "None"}</Typography>
|
<Typography>{terminalMap.get(gate.terminal()) ?? "None"}</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<Box padding={2} display="flex" flexDirection="row" justifyContent="space-between">
|
<Box padding={2} display="flex" flexDirection="row" justifyContent="space-between">
|
||||||
<Typography>Duct Type:</Typography>
|
<Typography>Delta Temperature:</Typography>
|
||||||
<Typography>{gate.ductName() !== "" ? gate.ductName() : "None"}</Typography>
|
{conditionDisplay(gate)}
|
||||||
</Box>
|
</Box>
|
||||||
<Box padding={2} display="flex" flexDirection="row" justifyContent="space-between">
|
{/* taking these out for now because we are not sure if the customer wants them */}
|
||||||
<Typography>Duct Size(mm):</Typography>
|
{/* <Box padding={2} display="flex" flexDirection="row" justifyContent="space-between">
|
||||||
<Typography>{gate.ductDiameter()}</Typography>
|
<Typography>Estimated Temp:</Typography>
|
||||||
</Box>
|
{tempDisplay(gate)}
|
||||||
<Box padding={2} display="flex" flexDirection="row" justifyContent="space-between">
|
|
||||||
<Typography>DuctLength(m):</Typography>
|
|
||||||
<Typography>{gate.ductLength()}</Typography>
|
|
||||||
</Box>
|
|
||||||
<Box padding={2} display="flex" flexDirection="row" justifyContent="space-between">
|
|
||||||
<Typography>PCA Unit:</Typography>
|
|
||||||
<Typography>{gate.settings.pcaType !== "" ? gate.settings.pcaType : "None"}</Typography>
|
|
||||||
</Box>
|
</Box>
|
||||||
<Box padding={2} display="flex" flexDirection="row" justifyContent="space-between">
|
<Box padding={2} display="flex" flexDirection="row" justifyContent="space-between">
|
||||||
<Typography>Last Flow:</Typography>
|
<Typography>Last Flow:</Typography>
|
||||||
<Typography>{gate.status.lastMassAirflow}</Typography>
|
<Typography color={teal[500]}>{gate.status.lastMassAirflow.toFixed(2)} kg/s</Typography>
|
||||||
</Box>
|
|
||||||
<Box padding={2} display="flex" flexDirection="row" justifyContent="space-between">
|
|
||||||
<Typography>Last Reading:</Typography>
|
|
||||||
<Typography>{gate.status.lastUpdate !== "" ? moment(gate.status.lastUpdate).fromNow() : "No PCA reading yet"}</Typography>
|
<Typography>{gate.status.lastUpdate !== "" ? moment(gate.status.lastUpdate).fromNow() : "No PCA reading yet"}</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
|
<Box padding={2} display="flex" flexDirection="row" justifyContent="space-between">
|
||||||
|
<Typography>Outlet Temp:</Typography>
|
||||||
|
{lastOutletReading(gate.status.lastTempReading)}
|
||||||
|
</Box>
|
||||||
|
<Box padding={2} display="flex" flexDirection="row" justifyContent="space-between">
|
||||||
|
<Typography>Outlet Pressure:</Typography>
|
||||||
|
{lastOutletReading(gate.status.lastPressureReading)}
|
||||||
|
</Box> */}
|
||||||
</Box>
|
</Box>
|
||||||
// <Typography variant="body2" color="textSecondary" sx={{ padding: 1 }}>
|
|
||||||
|
|
||||||
// </Typography>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -43,13 +43,15 @@ const useStyles = makeStyles(() => ({
|
||||||
interface Props {
|
interface Props {
|
||||||
finalTemp: number;
|
finalTemp: number;
|
||||||
ambientTemp: number;
|
ambientTemp: number;
|
||||||
innerTemps: { t1: number; t2: number };
|
//innerTemps: { t1: number; t2: number };
|
||||||
innerPressures: { p1: number; p2: number };
|
//innerPressures: { p1: number; p2: number };
|
||||||
|
ductTemp?: number;
|
||||||
|
ductPressure?: number;
|
||||||
ambientSelector: () => JSX.Element;
|
ambientSelector: () => JSX.Element;
|
||||||
tempChainSelector: () => JSX.Element;
|
tempChainSelector: () => JSX.Element;
|
||||||
pressureChainSelector: () => JSX.Element;
|
pressureChainSelector: () => JSX.Element;
|
||||||
pcaState: boolean;
|
pcaState: pond.PCAState;
|
||||||
pcaFanState: boolean;
|
// pcaFanState: boolean;
|
||||||
checkInTime: string;
|
checkInTime: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -57,13 +59,15 @@ export default function GateSVG(props: Props) {
|
||||||
const {
|
const {
|
||||||
finalTemp,
|
finalTemp,
|
||||||
ambientTemp,
|
ambientTemp,
|
||||||
innerTemps,
|
//innerTemps,
|
||||||
innerPressures,
|
//innerPressures,
|
||||||
|
ductTemp,
|
||||||
|
ductPressure,
|
||||||
ambientSelector,
|
ambientSelector,
|
||||||
tempChainSelector,
|
tempChainSelector,
|
||||||
pressureChainSelector,
|
pressureChainSelector,
|
||||||
pcaState,
|
pcaState,
|
||||||
pcaFanState,
|
// pcaFanState,
|
||||||
checkInTime
|
checkInTime
|
||||||
} = props;
|
} = props;
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
|
|
@ -242,16 +246,23 @@ export default function GateSVG(props: Props) {
|
||||||
return temp;
|
return temp;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const finalTempDisplay = (temp: number) => {
|
||||||
|
let display = "--"
|
||||||
|
if(pcaState === pond.PCAState.PCA_STATE_IN_BOUNDS || pcaState === pond.PCAState.PCA_STATE_OUT_BOUNDS){
|
||||||
|
if(temp < 60 && temp > -40 && !isNaN(temp) && temp !== 0){
|
||||||
|
display = convertFinalTemp(finalTemp).toFixed(2) + (user.settings.temperatureUnit === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT ? "°F" : "°C")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return display
|
||||||
|
}
|
||||||
|
|
||||||
const renderValues = () => {
|
const renderValues = () => {
|
||||||
let values: JSX.Element[] = [];
|
let values: JSX.Element[] = [];
|
||||||
//add the final temp display to the array
|
//add the final temp display to the array
|
||||||
values.push(
|
values.push(
|
||||||
<g key={"finalTemp"} id={"finalTemp"} data-name={"finalTemp"}>
|
<g key={"finalTemp"} id={"finalTemp"} data-name={"finalTemp"}>
|
||||||
<text fontSize={7} className={classes.fontBase} x={100} y={85}>
|
<text fontSize={7} className={classes.fontBase} x={100} y={85}>
|
||||||
{convertFinalTemp(finalTemp).toFixed(2)}
|
{finalTempDisplay(finalTemp)}
|
||||||
{user.settings.temperatureUnit === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT
|
|
||||||
? "°F"
|
|
||||||
: "°C"}
|
|
||||||
</text>
|
</text>
|
||||||
</g>
|
</g>
|
||||||
);
|
);
|
||||||
|
|
@ -270,12 +281,15 @@ export default function GateSVG(props: Props) {
|
||||||
values.push(
|
values.push(
|
||||||
<g key={"tempChain"} id={"tempChain"} data-name={"tempChain"}>
|
<g key={"tempChain"} id={"tempChain"} data-name={"tempChain"}>
|
||||||
<text fontSize={7} className={classes.fontBase} x={30} y={149}>
|
<text fontSize={7} className={classes.fontBase} x={30} y={149}>
|
||||||
T1: {innerTemps.t1}
|
{/* T1: {innerTemps.t1}
|
||||||
{user.settings.temperatureUnit === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT
|
{user.settings.temperatureUnit === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT
|
||||||
? "°F"
|
? "°F"
|
||||||
: "°C"}
|
: "°C"}
|
||||||
, T2: {innerTemps.t2}
|
, T1: {innerTemps.t2}
|
||||||
{user.settings.temperatureUnit === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT
|
{user.settings.temperatureUnit === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT
|
||||||
|
? "°F"
|
||||||
|
: "°C"} */}
|
||||||
|
Temperature: {ductTemp ?? "N/A"} {user.settings.temperatureUnit === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT
|
||||||
? "°F"
|
? "°F"
|
||||||
: "°C"}
|
: "°C"}
|
||||||
</text>
|
</text>
|
||||||
|
|
@ -285,12 +299,15 @@ export default function GateSVG(props: Props) {
|
||||||
values.push(
|
values.push(
|
||||||
<g key={"pressureChain"} id={"pressureChain"} data-name={"pressureChain"}>
|
<g key={"pressureChain"} id={"pressureChain"} data-name={"pressureChain"}>
|
||||||
<text fontSize={7} className={classes.fontBase} x={30} y={184}>
|
<text fontSize={7} className={classes.fontBase} x={30} y={184}>
|
||||||
P1: {innerPressures.p1}{" "}
|
{/* P1: {innerPressures.p1}{" "}
|
||||||
{user.settings.pressureUnit === pond.PressureUnit.PRESSURE_UNIT_INCHES_OF_WATER
|
{user.settings.pressureUnit === pond.PressureUnit.PRESSURE_UNIT_INCHES_OF_WATER
|
||||||
? "iwg"
|
? "iwg"
|
||||||
: "kPa"}
|
: "kPa"}
|
||||||
, P2: {innerPressures.p2}{" "}
|
, P2: {innerPressures.p2}{" "}
|
||||||
{user.settings.pressureUnit === pond.PressureUnit.PRESSURE_UNIT_INCHES_OF_WATER
|
{user.settings.pressureUnit === pond.PressureUnit.PRESSURE_UNIT_INCHES_OF_WATER
|
||||||
|
? "iwg"
|
||||||
|
: "kPa"} */}
|
||||||
|
Pressure: {ductPressure ?? "N/A"} {user.settings.pressureUnit === pond.PressureUnit.PRESSURE_UNIT_INCHES_OF_WATER
|
||||||
? "iwg"
|
? "iwg"
|
||||||
: "kPa"}
|
: "kPa"}
|
||||||
</text>
|
</text>
|
||||||
|
|
@ -302,7 +319,8 @@ export default function GateSVG(props: Props) {
|
||||||
const pcaRed = () => {
|
const pcaRed = () => {
|
||||||
return (
|
return (
|
||||||
<g key={"pcaRed"} id={"pcaRed"} data-name={"pcaRed"}>
|
<g key={"pcaRed"} id={"pcaRed"} data-name={"pcaRed"}>
|
||||||
<circle cx={175} cy={152} r={8} fill={pcaFanState ? (pcaState ? "grey" : "red") : "grey"} />
|
{/* <circle cx={175} cy={152} r={8} fill={(pcaFanState && pcaState === pond.PCAState.PCA_STATE_OUT_BOUNDS) ? "red" : "grey"} /> */}
|
||||||
|
<circle cx={175} cy={152} r={8} fill={pcaState === pond.PCAState.PCA_STATE_OUT_BOUNDS ? "red" : "grey"} />
|
||||||
</g>
|
</g>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
@ -314,7 +332,8 @@ export default function GateSVG(props: Props) {
|
||||||
cx={175}
|
cx={175}
|
||||||
cy={174}
|
cy={174}
|
||||||
r={8}
|
r={8}
|
||||||
fill={pcaFanState ? (pcaState ? "green" : "grey") : "grey"}
|
//fill={(pcaFanState && pcaState === pond.PCAState.PCA_STATE_IN_BOUNDS) ? "green" : "grey"}
|
||||||
|
fill={pcaState === pond.PCAState.PCA_STATE_IN_BOUNDS ? "green" : "grey"}
|
||||||
/>
|
/>
|
||||||
</g>
|
</g>
|
||||||
);
|
);
|
||||||
|
|
@ -324,7 +343,8 @@ export default function GateSVG(props: Props) {
|
||||||
return (
|
return (
|
||||||
<g key={"fanStatus"}>
|
<g key={"fanStatus"}>
|
||||||
<text x={125} y={180} fontSize={5} className={classes.fontBase}>
|
<text x={125} y={180} fontSize={5} className={classes.fontBase}>
|
||||||
PCA Fan: {pcaFanState ? "ON" : "OFF"}
|
{/* PCA Fan: {pcaFanState ? "ON" : "OFF"} */}
|
||||||
|
PCA Fan: {pcaState === pond.PCAState.PCA_STATE_OFF ? "OFF" : "ON"}
|
||||||
</text>
|
</text>
|
||||||
</g>
|
</g>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,8 @@ import {
|
||||||
import FileUploader from "common/FileUploads/FileUploader";
|
import FileUploader from "common/FileUploads/FileUploader";
|
||||||
import ResponsiveDialog from "common/ResponsiveDialog";
|
import ResponsiveDialog from "common/ResponsiveDialog";
|
||||||
import SearchSelect, { Option } from "common/SearchSelect";
|
import SearchSelect, { Option } from "common/SearchSelect";
|
||||||
import { useMobile } from "hooks";
|
import { useMobile, useUserAPI } from "hooks";
|
||||||
import { Bin, Field, Contract, GrainBag } from "models";
|
import { Bin, Field, Contract, GrainBag, teamScope } from "models";
|
||||||
// import { Contract } from "models/Contract";
|
// import { Contract } from "models/Contract";
|
||||||
// import { GrainBag } from "models/GrainBag";
|
// import { GrainBag } from "models/GrainBag";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
|
|
@ -66,7 +66,7 @@ export default function GrainTransaction(props: Props) {
|
||||||
const binAPI = useBinAPI();
|
const binAPI = useBinAPI();
|
||||||
const grainBagAPI = useGrainBagAPI();
|
const grainBagAPI = useGrainBagAPI();
|
||||||
const fieldAPI = useFieldAPI();
|
const fieldAPI = useFieldAPI();
|
||||||
const [{ as }] = useGlobalState();
|
const [{ as, user }] = useGlobalState();
|
||||||
const { openSnack } = useSnackbar();
|
const { openSnack } = useSnackbar();
|
||||||
const [grainChangeDialog, setGrainChangeDialog] = useState(false);
|
const [grainChangeDialog, setGrainChangeDialog] = useState(false);
|
||||||
const [grainEntry, setGrainEntry] = useState("0");
|
const [grainEntry, setGrainEntry] = useState("0");
|
||||||
|
|
@ -79,6 +79,8 @@ export default function GrainTransaction(props: Props) {
|
||||||
const [fieldsLoading, setFieldsLoading] = useState(false);
|
const [fieldsLoading, setFieldsLoading] = useState(false);
|
||||||
const [uploadingFile, setUploadingFile] = useState(false);
|
const [uploadingFile, setUploadingFile] = useState(false);
|
||||||
const [imageIDs, setImageIDs] = useState<string[]>([]);
|
const [imageIDs, setImageIDs] = useState<string[]>([]);
|
||||||
|
const userAPI = useUserAPI();
|
||||||
|
const [filePermission, setFilePermission] = useState(false);
|
||||||
|
|
||||||
const closeDialogs = (confirmed: boolean, newTransaction?: pond.Transaction) => {
|
const closeDialogs = (confirmed: boolean, newTransaction?: pond.Transaction) => {
|
||||||
close();
|
close();
|
||||||
|
|
@ -91,6 +93,20 @@ export default function GrainTransaction(props: Props) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(()=>{
|
||||||
|
//if they are viewing as a team make sure they have file permission to the team for the transactions
|
||||||
|
if(as){
|
||||||
|
let scope = teamScope(as)
|
||||||
|
userAPI.getUser(user.id(), scope).then(resp => {
|
||||||
|
setFilePermission(resp.permissions.includes(pond.Permission.PERMISSION_FILE_MANAGEMENT))
|
||||||
|
});
|
||||||
|
}else{
|
||||||
|
//if they are not viewing as a team and they were able to open this window, they have edit permission to at least the one object they started the change with
|
||||||
|
//a transaction is a new object that permissions will not exist for yet so just allow them to do it
|
||||||
|
setFilePermission(true)
|
||||||
|
}
|
||||||
|
},[as])
|
||||||
|
|
||||||
//get the possible destinations/options (grainbags and bins)
|
//get the possible destinations/options (grainbags and bins)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (open) {
|
if (open) {
|
||||||
|
|
@ -527,6 +543,7 @@ export default function GrainTransaction(props: Props) {
|
||||||
<Box marginTop={1}>
|
<Box marginTop={1}>
|
||||||
{allowAttachmentUploads && (
|
{allowAttachmentUploads && (
|
||||||
<FileUploader
|
<FileUploader
|
||||||
|
hasFilePermission={filePermission}
|
||||||
uploadEnd={fileID => {
|
uploadEnd={fileID => {
|
||||||
if (fileID) {
|
if (fileID) {
|
||||||
let ids = imageIDs;
|
let ids = imageIDs;
|
||||||
|
|
|
||||||
|
|
@ -161,6 +161,7 @@ export default function HarvestPlanActions(props: Props) {
|
||||||
update={updatePlan}
|
update={updatePlan}
|
||||||
open={openState.settings}
|
open={openState.settings}
|
||||||
plan={plan.key() !== "" ? plan : undefined}
|
plan={plan.key() !== "" ? plan : undefined}
|
||||||
|
permissions={permissions}
|
||||||
close={(refresh, updatedPlan) => {
|
close={(refresh, updatedPlan) => {
|
||||||
setOpenState({ ...openState, settings: false });
|
setOpenState({ ...openState, settings: false });
|
||||||
if (refresh) {
|
if (refresh) {
|
||||||
|
|
|
||||||
|
|
@ -58,25 +58,25 @@ export default function HarvestPlanDisplay(props: Props) {
|
||||||
<Grid container direction="column">
|
<Grid container direction="column">
|
||||||
<Grid className={classes.dark}>
|
<Grid className={classes.dark}>
|
||||||
<Box style={{ display: "flex", justifyContent: "space-between" }}>
|
<Box style={{ display: "flex", justifyContent: "space-between" }}>
|
||||||
<Typography variant="subtitle1">Break Even Yield:</Typography>
|
<Typography variant="subtitle1">Break Even Yield (acre):</Typography>
|
||||||
<Typography variant="subtitle1">
|
<Typography variant="subtitle1">
|
||||||
{(
|
{plan.bushelPrice() !== 0 ? (
|
||||||
(plan.totalEquipmentCost() + plan.totalMaterialCost()) /
|
(plan.totalEquipmentCost() + plan.totalMaterialCost()) /
|
||||||
plan.bushelPrice()
|
plan.bushelPrice()
|
||||||
).toFixed(2)}{" "}
|
).toFixed(2) : 0}{" "}
|
||||||
BU
|
BU
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid className={classes.light}>
|
<Grid className={classes.light}>
|
||||||
<Box style={{ display: "flex", justifyContent: "space-between" }}>
|
<Box style={{ display: "flex", justifyContent: "space-between" }}>
|
||||||
<Typography variant="subtitle1">Break Even Sales Price</Typography>
|
<Typography variant="subtitle1">Break Even Sales Price (BU):</Typography>
|
||||||
<Typography variant="subtitle1">
|
<Typography variant="subtitle1">
|
||||||
$
|
$
|
||||||
{(
|
{plan.yieldTarget() !== 0 ? (
|
||||||
(plan.totalEquipmentCost() + plan.totalMaterialCost()) /
|
(plan.totalEquipmentCost() + plan.totalMaterialCost()) /
|
||||||
plan.yieldTarget()
|
plan.yieldTarget()
|
||||||
).toFixed(2)}
|
).toFixed(2) : 0}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import { getThemeType } from "theme"
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
field: Field
|
field: Field
|
||||||
|
planSelected: (plan: HarvestPlan) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const useStyles = makeStyles((theme: Theme) => ({
|
const useStyles = makeStyles((theme: Theme) => ({
|
||||||
|
|
@ -25,7 +26,7 @@ const useStyles = makeStyles((theme: Theme) => ({
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export default function HarvestPlanTable(props: Props){
|
export default function HarvestPlanTable(props: Props){
|
||||||
const {field} = props
|
const {field, planSelected} = props
|
||||||
const harvestAPI = useHarvestPlanAPI()
|
const harvestAPI = useHarvestPlanAPI()
|
||||||
const [page, setPage] = useState(0)
|
const [page, setPage] = useState(0)
|
||||||
const [pageSize, setPageSize] = useState(10)
|
const [pageSize, setPageSize] = useState(10)
|
||||||
|
|
@ -114,6 +115,9 @@ export default function HarvestPlanTable(props: Props){
|
||||||
title={"Harvest Plans"}
|
title={"Harvest Plans"}
|
||||||
resizeable
|
resizeable
|
||||||
page={page}
|
page={page}
|
||||||
|
onRowClick={(row) => {
|
||||||
|
planSelected(row)
|
||||||
|
}}
|
||||||
pageSize={pageSize}
|
pageSize={pageSize}
|
||||||
rows={loadedPlans}
|
rows={loadedPlans}
|
||||||
total={total}
|
total={total}
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ interface Props {
|
||||||
field: Field;
|
field: Field;
|
||||||
update?: boolean;
|
update?: boolean;
|
||||||
plan?: HarvestPlan;
|
plan?: HarvestPlan;
|
||||||
|
permissions?: pond.Permission[]
|
||||||
}
|
}
|
||||||
|
|
||||||
const cropOptions = [
|
const cropOptions = [
|
||||||
|
|
@ -160,7 +161,7 @@ const useStyles = makeStyles((theme: Theme) => ({
|
||||||
const steps = ["Pre-Seeding", "Seeding", "Post-Seeding", "Harvest", "Fall"];
|
const steps = ["Pre-Seeding", "Seeding", "Post-Seeding", "Harvest", "Fall"];
|
||||||
|
|
||||||
export default function HarvestSettings(props: Props) {
|
export default function HarvestSettings(props: Props) {
|
||||||
const { open, plan, close, field, update } = props;
|
const { open, plan, close, field, update, permissions } = props;
|
||||||
const [{as}] = useGlobalState()
|
const [{as}] = useGlobalState()
|
||||||
const [planKey, setPlanKey] = useState<string>();
|
const [planKey, setPlanKey] = useState<string>();
|
||||||
const [newPlan, setNewPlan] = useState(HarvestPlan.create());
|
const [newPlan, setNewPlan] = useState(HarvestPlan.create());
|
||||||
|
|
@ -177,6 +178,7 @@ export default function HarvestSettings(props: Props) {
|
||||||
const [variety, setVariety] = useState("");
|
const [variety, setVariety] = useState("");
|
||||||
const [harvestYear, setHarvestYear] = useState(new Date().getFullYear());
|
const [harvestYear, setHarvestYear] = useState(new Date().getFullYear());
|
||||||
const [targetYield, setTargetYield] = useState(0);
|
const [targetYield, setTargetYield] = useState(0);
|
||||||
|
const [actualYield, setActualYield] = useState(0);
|
||||||
const [price, setPrice] = useState("");
|
const [price, setPrice] = useState("");
|
||||||
const [newTaskDialog, setNewTaskDialog] = useState(false);
|
const [newTaskDialog, setNewTaskDialog] = useState(false);
|
||||||
const [type, setType] = useState("");
|
const [type, setType] = useState("");
|
||||||
|
|
@ -184,10 +186,16 @@ export default function HarvestSettings(props: Props) {
|
||||||
const { openSnack } = useSnackbar();
|
const { openSnack } = useSnackbar();
|
||||||
const [deleteOpen, setDeleteOpen] = useState(false);
|
const [deleteOpen, setDeleteOpen] = useState(false);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const loadTasks = useCallback(() => {
|
const loadTasks = useCallback(() => {
|
||||||
if (planKey) {
|
if (planKey) {
|
||||||
taskAPI.getMultiTasks([planKey], as).then(resp => {
|
taskAPI.getMultiTasks([planKey], as).then(resp => {
|
||||||
|
if(resp.data.tasks){
|
||||||
setPlanTasks(resp.data.tasks.map(t => Task.any(t)));
|
setPlanTasks(resp.data.tasks.map(t => Task.any(t)));
|
||||||
|
}else{
|
||||||
|
setPlanTasks([])
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [taskAPI, planKey, as]);
|
}, [taskAPI, planKey, as]);
|
||||||
|
|
@ -197,7 +205,7 @@ export default function HarvestSettings(props: Props) {
|
||||||
}, [loadTasks]);
|
}, [loadTasks]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!plan || (plan && plan.permissions.includes(pond.Permission.PERMISSION_WRITE))) {
|
if (!plan || (permissions && permissions.includes(pond.Permission.PERMISSION_WRITE))) {
|
||||||
setActiveStep(0);
|
setActiveStep(0);
|
||||||
} else {
|
} else {
|
||||||
setActiveStep(1);
|
setActiveStep(1);
|
||||||
|
|
@ -358,6 +366,7 @@ export default function HarvestSettings(props: Props) {
|
||||||
tempPlan.settings.grainType = variety;
|
tempPlan.settings.grainType = variety;
|
||||||
tempPlan.settings.harvestYear = harvestYear;
|
tempPlan.settings.harvestYear = harvestYear;
|
||||||
tempPlan.settings.yieldTarget = targetYield;
|
tempPlan.settings.yieldTarget = targetYield;
|
||||||
|
tempPlan.settings.actualYield =
|
||||||
tempPlan.settings.bushelPrice = !isNaN(parseFloat(price))
|
tempPlan.settings.bushelPrice = !isNaN(parseFloat(price))
|
||||||
? Math.round(parseFloat(price) * 100) / 100
|
? Math.round(parseFloat(price) * 100) / 100
|
||||||
: 0;
|
: 0;
|
||||||
|
|
@ -411,7 +420,7 @@ export default function HarvestSettings(props: Props) {
|
||||||
key={0}
|
key={0}
|
||||||
classes={{ root: classes.tab }}
|
classes={{ root: classes.tab }}
|
||||||
disabled={
|
disabled={
|
||||||
!planKey || !(plan && plan.permissions.includes(pond.Permission.PERMISSION_WRITE))
|
!planKey || !(permissions && permissions.includes(pond.Permission.PERMISSION_WRITE))
|
||||||
}
|
}
|
||||||
label={"General"}
|
label={"General"}
|
||||||
aria-label={"general"}
|
aria-label={"general"}
|
||||||
|
|
@ -494,6 +503,16 @@ export default function HarvestSettings(props: Props) {
|
||||||
!isNaN(+e.target.value) && setTargetYield(+e.target.value);
|
!isNaN(+e.target.value) && setTargetYield(+e.target.value);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
margin="normal"
|
||||||
|
type="number"
|
||||||
|
label="Actual Yield per acre"
|
||||||
|
value={actualYield}
|
||||||
|
onChange={e => {
|
||||||
|
!isNaN(+e.target.value) && setActualYield(+e.target.value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
<TextField
|
<TextField
|
||||||
margin="dense"
|
margin="dense"
|
||||||
id="bushPrice"
|
id="bushPrice"
|
||||||
|
|
@ -710,7 +729,7 @@ export default function HarvestSettings(props: Props) {
|
||||||
<TaskSettings
|
<TaskSettings
|
||||||
task={taskToEdit}
|
task={taskToEdit}
|
||||||
hasCost
|
hasCost
|
||||||
costTitle="Material Cost(acres)"
|
costTitle="Material Cost(acre)"
|
||||||
secondaryCostTitle="Equipment Cost(acre)"
|
secondaryCostTitle="Equipment Cost(acre)"
|
||||||
objectKey={planKey}
|
objectKey={planKey}
|
||||||
type={type}
|
type={type}
|
||||||
|
|
|
||||||
|
|
@ -134,7 +134,7 @@ export default function FieldDrawer(props: Props) {
|
||||||
hPlanAPI
|
hPlanAPI
|
||||||
.listHarvestPlans(1, 0, "desc", "createDate", field.key(), as)
|
.listHarvestPlans(1, 0, "desc", "createDate", field.key(), as)
|
||||||
.then(resp => {
|
.then(resp => {
|
||||||
if (resp.data.harvestPlan.length > 0) {
|
if (resp.data.harvestPlan) {
|
||||||
let plan = resp.data.harvestPlan[0];
|
let plan = resp.data.harvestPlan[0];
|
||||||
setHPlan(HarvestPlan.any(plan));
|
setHPlan(HarvestPlan.any(plan));
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -20,10 +20,7 @@ import {
|
||||||
Accordion,
|
Accordion,
|
||||||
AccordionSummary,
|
AccordionSummary,
|
||||||
AccordionDetails,
|
AccordionDetails,
|
||||||
darken,
|
|
||||||
Typography,
|
Typography,
|
||||||
ToggleButton,
|
|
||||||
ToggleButtonGroup
|
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import BinActions from "bin/BinActions";
|
import BinActions from "bin/BinActions";
|
||||||
import BinHistory from "bin/BinHistory";
|
import BinHistory from "bin/BinHistory";
|
||||||
|
|
@ -60,7 +57,7 @@ import { Ambient } from "models/Ambient";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import BinStorageConditions from "bin/BinStorageConditions";
|
import BinStorageConditions from "bin/BinStorageConditions";
|
||||||
import BinConditioningCard from "bin/BinConditioningCard";
|
import BinConditioningCard from "bin/BinConditioningCard";
|
||||||
import { makeStyles, styled } from "@mui/styles";
|
import { makeStyles } 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";
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,7 @@ export default function Contracts() {
|
||||||
.then(resp => {
|
.then(resp => {
|
||||||
let contracts: Contract[] = [];
|
let contracts: Contract[] = [];
|
||||||
let contractPermissions: Map<string, pond.Permission[]> = new Map();
|
let contractPermissions: Map<string, pond.Permission[]> = new Map();
|
||||||
|
if (resp.data.contracts){
|
||||||
resp.data.contracts.forEach(contract => {
|
resp.data.contracts.forEach(contract => {
|
||||||
let c = Contract.create(contract);
|
let c = Contract.create(contract);
|
||||||
contracts.push(c);
|
contracts.push(c);
|
||||||
|
|
@ -50,6 +51,7 @@ export default function Contracts() {
|
||||||
});
|
});
|
||||||
setContracts(contracts);
|
setContracts(contracts);
|
||||||
setContractPermissions(contractPermissions);
|
setContractPermissions(contractPermissions);
|
||||||
|
}
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
})
|
})
|
||||||
.catch(err => {});
|
.catch(err => {});
|
||||||
|
|
|
||||||
|
|
@ -323,21 +323,21 @@ export default function Devices() {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
// useEffect(() => {
|
||||||
console.log("has no2: "+hasNo2)
|
// console.log("has no2: "+hasNo2)
|
||||||
}, [hasNo2])
|
// }, [hasNo2])
|
||||||
|
|
||||||
useEffect(() => {
|
// useEffect(() => {
|
||||||
console.log("has co2: "+hasCo2)
|
// console.log("has co2: "+hasCo2)
|
||||||
}, [hasCo2])
|
// }, [hasCo2])
|
||||||
|
|
||||||
useEffect(() => {
|
// useEffect(() => {
|
||||||
console.log("has co: "+hasNo2)
|
// console.log("has co: "+hasNo2)
|
||||||
}, [hasNo2])
|
// }, [hasNo2])
|
||||||
|
|
||||||
useEffect(() => {
|
// useEffect(() => {
|
||||||
console.log("has o2: "+hasO2)
|
// console.log("has o2: "+hasO2)
|
||||||
}, [hasO2])
|
// }, [hasO2])
|
||||||
|
|
||||||
const openProvisionDialog = () => {
|
const openProvisionDialog = () => {
|
||||||
setIsProvisionDialogOpen(true);
|
setIsProvisionDialogOpen(true);
|
||||||
|
|
|
||||||
|
|
@ -227,7 +227,7 @@ export default function FieldPage() {
|
||||||
</Grid2>
|
</Grid2>
|
||||||
<Grid2 size={8}>
|
<Grid2 size={8}>
|
||||||
<Card raised>
|
<Card raised>
|
||||||
<HarvestPlanTable field={field}/>
|
<HarvestPlanTable field={field} planSelected={(plan) => {setHPlan(plan)}}/>
|
||||||
</Card>
|
</Card>
|
||||||
</Grid2>
|
</Grid2>
|
||||||
</Grid2>
|
</Grid2>
|
||||||
|
|
@ -276,7 +276,7 @@ export default function FieldPage() {
|
||||||
</Card>
|
</Card>
|
||||||
</Grid2>
|
</Grid2>
|
||||||
<Grid2>
|
<Grid2>
|
||||||
<HarvestPlanTable field={field}/>
|
<HarvestPlanTable field={field} planSelected={(plan) => {setHPlan(plan)}}/>
|
||||||
</Grid2>
|
</Grid2>
|
||||||
</Grid2>
|
</Grid2>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
|
|
|
||||||
|
|
@ -134,7 +134,6 @@ export default function Gate(props: Props) {
|
||||||
gateAPI
|
gateAPI
|
||||||
.getGatePageData(id, as)
|
.getGatePageData(id, as)
|
||||||
.then(resp => {
|
.then(resp => {
|
||||||
//console.log(resp.data);
|
|
||||||
let p = new Map<number, pond.GateDeviceType>();
|
let p = new Map<number, pond.GateDeviceType>();
|
||||||
Object.keys(resp.data.preferences).forEach(k => {
|
Object.keys(resp.data.preferences).forEach(k => {
|
||||||
let prefKey = parseInt(k);
|
let prefKey = parseInt(k);
|
||||||
|
|
@ -192,7 +191,9 @@ export default function Gate(props: Props) {
|
||||||
const deviceDrawer = () => {
|
const deviceDrawer = () => {
|
||||||
return (
|
return (
|
||||||
<DeviceLinkDrawer
|
<DeviceLinkDrawer
|
||||||
deviceTags={["omniair", "mipca"]}
|
//the mipca tag was not working for some reason so just taking the tags out
|
||||||
|
//and this way we dont have to worry about tagging them when they are provisioned
|
||||||
|
//deviceTags={["omniair", "mipca", "MiPCA", "Mipca"]}
|
||||||
devicePrefMap={devPrefs}
|
devicePrefMap={devPrefs}
|
||||||
prefOptions={[
|
prefOptions={[
|
||||||
<MenuItem
|
<MenuItem
|
||||||
|
|
@ -341,15 +342,13 @@ export default function Gate(props: Props) {
|
||||||
spacing={2}
|
spacing={2}
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
alignContent="center">
|
alignContent="center">
|
||||||
<Grid>
|
<Grid size={12}>
|
||||||
<Box display="flex" justifyContent="center" alignItems="center">
|
<Box display="flex" justifyContent="center" alignItems="center">
|
||||||
<Link style={{ height: 50, width: 50 }} />
|
<Link style={{ height: 50, width: 50 }} />
|
||||||
</Box>
|
<Typography paddingLeft={2} style={{ fontSize: 25, fontWeight: 650 }}>
|
||||||
</Grid>
|
|
||||||
<Grid>
|
|
||||||
<Typography style={{ fontSize: 25, fontWeight: 650 }}>
|
|
||||||
Connect Device
|
Connect Device
|
||||||
</Typography>
|
</Typography>
|
||||||
|
</Box>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid size={12}>
|
<Grid size={12}>
|
||||||
Click here to add a device to the gate. To add an additional device to a gate
|
Click here to add a device to the gate. To add an additional device to a gate
|
||||||
|
|
|
||||||
|
|
@ -9,9 +9,21 @@ const useStyles = makeStyles((theme: Theme) => ({
|
||||||
width: "100%",
|
width: "100%",
|
||||||
overflowX: "hidden",
|
overflowX: "hidden",
|
||||||
overflowY: "auto",
|
overflowY: "auto",
|
||||||
|
// height: `calc(100vh - 112px)`,
|
||||||
|
// [theme.breakpoints.up("sm")]: {
|
||||||
|
// height: `calc(100vh - 64px)`
|
||||||
|
// }
|
||||||
|
minHeight: 0,
|
||||||
height: `calc(100vh - 112px)`,
|
height: `calc(100vh - 112px)`,
|
||||||
|
"@supports (height: 100dvh)": {
|
||||||
|
height: `calc(100dvh - 112px)`
|
||||||
|
},
|
||||||
[theme.breakpoints.up("sm")]: {
|
[theme.breakpoints.up("sm")]: {
|
||||||
height: `calc(100vh - 64px)`
|
height: `calc(100vh - 64px)`,
|
||||||
|
|
||||||
|
"@supports (height: 100dvh)": {
|
||||||
|
height: `calc(100dvh - 64px)`
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
fullViewportContainer: {
|
fullViewportContainer: {
|
||||||
|
|
@ -19,6 +31,9 @@ const useStyles = makeStyles((theme: Theme) => ({
|
||||||
top: 0,
|
top: 0,
|
||||||
left: 0,
|
left: 0,
|
||||||
height: "100vh",
|
height: "100vh",
|
||||||
|
"@supports (height: 100dvh)": {
|
||||||
|
height: "100dvh"
|
||||||
|
},
|
||||||
width: "100vw",
|
width: "100vw",
|
||||||
backgroundColor: theme.palette.background.default,
|
backgroundColor: theme.palette.background.default,
|
||||||
zIndex: 2000,
|
zIndex: 2000,
|
||||||
|
|
|
||||||
|
|
@ -223,7 +223,7 @@ export default function TeamPage() {
|
||||||
const isStreamline = IsStreamline()
|
const isStreamline = IsStreamline()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageContainer spacing={isMobile ? 0 : 2}>
|
<PageContainer spacing={isMobile ? 1 : 2}>
|
||||||
<Box className={classes.container}>
|
<Box className={classes.container}>
|
||||||
<Box className={classes.leftBox}>
|
<Box className={classes.leftBox}>
|
||||||
{title()}
|
{title()}
|
||||||
|
|
|
||||||
|
|
@ -453,7 +453,7 @@ export class MeasurementDescriber {
|
||||||
case quack.MeasurementType.MEASUREMENT_TYPE_SPEED:
|
case quack.MeasurementType.MEASUREMENT_TYPE_SPEED:
|
||||||
let speedUnit = getDistanceUnit()
|
let speedUnit = getDistanceUnit()
|
||||||
this.details.label = "Speed";
|
this.details.label = "Speed";
|
||||||
this.details.unit = speedUnit === pond.DistanceUnit.DISTANCE_UNIT_METERS ? "m/s" : "ft/m";
|
this.details.unit = speedUnit === pond.DistanceUnit.DISTANCE_UNIT_METERS ? "m/s" : "ft/m"; //yes this is meters per second and feet per minute
|
||||||
this.details.colour = green["500"];
|
this.details.colour = green["500"];
|
||||||
this.details.path = "edgeTriggered.rises";
|
this.details.path = "edgeTriggered.rises";
|
||||||
this.details.max = 500;
|
this.details.max = 500;
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ import MarketWhite from "assets/marketplaceImages/marketplaceIconLight.png";
|
||||||
import MarketBlack from "assets/marketplaceImages/marketplaceIconDark.png";
|
import MarketBlack from "assets/marketplaceImages/marketplaceIconDark.png";
|
||||||
import { ImgIcon } from "common/ImgIcon";
|
import { ImgIcon } from "common/ImgIcon";
|
||||||
import { useThemeType } from "hooks";
|
import { useThemeType } from "hooks";
|
||||||
import React from "react";
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
type?: "light" | "dark";
|
type?: "light" | "dark";
|
||||||
|
|
|
||||||
23
src/products/CommonIcons/robotIcon.tsx
Normal file
23
src/products/CommonIcons/robotIcon.tsx
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
import RobotWhite from "assets/common/robotIconLight.png";
|
||||||
|
import RobotBlack from "assets/common/robotIconDark.png";
|
||||||
|
import { ImgIcon } from "common/ImgIcon";
|
||||||
|
import { useThemeType } from "hooks";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
type?: "light" | "dark";
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function RobotIcon(props: Props) {
|
||||||
|
const themeType = useThemeType();
|
||||||
|
const { type } = props;
|
||||||
|
|
||||||
|
const src = () => {
|
||||||
|
if (type) {
|
||||||
|
return type === "light" ? RobotWhite : RobotBlack;
|
||||||
|
}
|
||||||
|
|
||||||
|
return themeType === "light" ? RobotBlack : RobotWhite;
|
||||||
|
};
|
||||||
|
|
||||||
|
return <ImgIcon alt="marketplace" src={src()} />;
|
||||||
|
}
|
||||||
|
|
@ -138,7 +138,6 @@ export default function GateProvider(props: PropsWithChildren<Props>) {
|
||||||
return new Promise<AxiosResponse<pond.ListGatesResponse>>((resolve, reject) => {
|
return new Promise<AxiosResponse<pond.ListGatesResponse>>((resolve, reject) => {
|
||||||
get<pond.ListGatesResponse>(url).then(resp => {
|
get<pond.ListGatesResponse>(url).then(resp => {
|
||||||
resp.data = pond.ListGatesResponse.fromObject(resp.data)
|
resp.data = pond.ListGatesResponse.fromObject(resp.data)
|
||||||
console.log(resp)
|
|
||||||
return resolve(resp)
|
return resolve(resp)
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
return reject(err)
|
return reject(err)
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { AxiosResponse } from "axios";
|
import { AxiosResponse } from "axios";
|
||||||
import { useHTTP } from "hooks";
|
import { useHTTP } from "hooks";
|
||||||
import { pond } from "protobuf-ts/pond";
|
import { pond } from "protobuf-ts/pond";
|
||||||
import { useGlobalState } from "providers";
|
// import { useGlobalState } from "providers";
|
||||||
import { createContext, PropsWithChildren, useContext } from "react";
|
import { createContext, PropsWithChildren, useContext } from "react";
|
||||||
import { pondURL } from "./pond";
|
import { pondURL } from "./pond";
|
||||||
|
|
||||||
|
|
@ -37,7 +37,7 @@ interface Props {}
|
||||||
export default function NoteProvider(props: PropsWithChildren<Props>) {
|
export default function NoteProvider(props: PropsWithChildren<Props>) {
|
||||||
const { children } = props;
|
const { children } = props;
|
||||||
const { get, del, post, put } = useHTTP();
|
const { get, del, post, put } = useHTTP();
|
||||||
const [{as}] = useGlobalState()
|
// const [{as}] = useGlobalState()
|
||||||
|
|
||||||
const addNote = (note: pond.NoteSettings, attachments?: string[]) => {
|
const addNote = (note: pond.NoteSettings, attachments?: string[]) => {
|
||||||
let url = pondURL("/notes" + (attachments ? "?attachments=" + attachments.toString() : ""))
|
let url = pondURL("/notes" + (attachments ? "?attachments=" + attachments.toString() : ""))
|
||||||
|
|
@ -88,9 +88,9 @@ export default function NoteProvider(props: PropsWithChildren<Props>) {
|
||||||
orderBy?: string,
|
orderBy?: string,
|
||||||
search?: string,
|
search?: string,
|
||||||
asRoot?: boolean,
|
asRoot?: boolean,
|
||||||
otherTeam?: string
|
// otherTeam?: string
|
||||||
) => {
|
) => {
|
||||||
const view = otherTeam ? otherTeam : as
|
// const view = otherTeam ? otherTeam : as
|
||||||
let url = pondURL(
|
let url = pondURL(
|
||||||
"/notes" +
|
"/notes" +
|
||||||
"?limit=" +
|
"?limit=" +
|
||||||
|
|
@ -100,9 +100,10 @@ export default function NoteProvider(props: PropsWithChildren<Props>) {
|
||||||
("&order=" + (order ? order : "asc")) +
|
("&order=" + (order ? order : "asc")) +
|
||||||
("&by=" + (orderBy ? orderBy : "key")) +
|
("&by=" + (orderBy ? orderBy : "key")) +
|
||||||
(search ? "&search=" + search : "") +
|
(search ? "&search=" + search : "") +
|
||||||
(asRoot ? "&asRoot=" + asRoot.toString() : "") +
|
(asRoot ? "&asRoot=" + asRoot.toString() : ""),
|
||||||
(view ? "&as=" + view : "")
|
// (view ? "&as=" + "" : "")
|
||||||
)
|
)
|
||||||
|
// console.log(url)
|
||||||
return new Promise<AxiosResponse<pond.ListNotesResponse>>((resolve, reject) => {
|
return new Promise<AxiosResponse<pond.ListNotesResponse>>((resolve, reject) => {
|
||||||
get<pond.ListNotesResponse>(url).then(resp => {
|
get<pond.ListNotesResponse>(url).then(resp => {
|
||||||
resp.data = pond.ListNotesResponse.fromObject(resp.data)
|
resp.data = pond.ListNotesResponse.fromObject(resp.data)
|
||||||
|
|
@ -119,10 +120,10 @@ export default function NoteProvider(props: PropsWithChildren<Props>) {
|
||||||
order?: "asc" | "desc",
|
order?: "asc" | "desc",
|
||||||
orderBy?: string,
|
orderBy?: string,
|
||||||
search?: string,
|
search?: string,
|
||||||
asRoot?: boolean,
|
// asRoot?: boolean,
|
||||||
otherTeam?: string
|
// otherTeam?: string
|
||||||
) => {
|
) => {
|
||||||
const view = otherTeam ? otherTeam : as
|
// const view = otherTeam ? otherTeam : as
|
||||||
let url = pondURL(
|
let url = pondURL(
|
||||||
"/chats" +
|
"/chats" +
|
||||||
"?limit=" +
|
"?limit=" +
|
||||||
|
|
@ -131,10 +132,11 @@ export default function NoteProvider(props: PropsWithChildren<Props>) {
|
||||||
offset +
|
offset +
|
||||||
("&order=" + (order ? order : "asc")) +
|
("&order=" + (order ? order : "asc")) +
|
||||||
("&by=" + (orderBy ? orderBy : "key")) +
|
("&by=" + (orderBy ? orderBy : "key")) +
|
||||||
(search ? "&search=" + search : "") +
|
(search ? "&search=" + search : ""),
|
||||||
(asRoot ? "&asRoot=" + asRoot.toString() : "") +
|
// (asRoot ? "&asRoot=" + asRoot.toString() : ""),
|
||||||
(view ? "&as=" + view : "")
|
// (view ? "&as=" + view : "")
|
||||||
)
|
)
|
||||||
|
// console.log(asRoot)
|
||||||
return new Promise<AxiosResponse<pond.ListChatsResponse>>((resolve, reject) => {
|
return new Promise<AxiosResponse<pond.ListChatsResponse>>((resolve, reject) => {
|
||||||
get<pond.ListChatsResponse>(url).then(resp => {
|
get<pond.ListChatsResponse>(url).then(resp => {
|
||||||
resp.data = pond.ListChatsResponse.fromObject(resp.data)
|
resp.data = pond.ListChatsResponse.fromObject(resp.data)
|
||||||
|
|
|
||||||
|
|
@ -117,9 +117,11 @@ export default function TeamProvider(props: PropsWithChildren<Props>) {
|
||||||
asRoot?: boolean,
|
asRoot?: boolean,
|
||||||
otherTeam?: string,
|
otherTeam?: string,
|
||||||
prefixSearch?: string,
|
prefixSearch?: string,
|
||||||
|
useImitation?: boolean,
|
||||||
) => {
|
) => {
|
||||||
//let asText = team ? "&as=" + team.key() : "";
|
//let asText = team ? "&as=" + team.key() : "";
|
||||||
const view = otherTeam ? otherTeam : (as.length > 0 ? as : undefined)
|
let view = otherTeam ? otherTeam : (as.length > 0 ? as : undefined)
|
||||||
|
if (useImitation === undefined || useImitation === false) view = ""
|
||||||
let url = pondURL(
|
let url = pondURL(
|
||||||
"/teams" +
|
"/teams" +
|
||||||
"?limit=" +
|
"?limit=" +
|
||||||
|
|
|
||||||
|
|
@ -336,9 +336,9 @@ 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("localhost")) {
|
// if (window.location.origin.includes("localhost")) {
|
||||||
return MIPCA_WHITE_LABEL;
|
// return STAGING_WHITELABEL;
|
||||||
}
|
// }
|
||||||
if (window.location.origin.includes("staging") || import.meta.env.VITE_LOCAL_STAGING=='true') {
|
if (window.location.origin.includes("staging") || import.meta.env.VITE_LOCAL_STAGING=='true') {
|
||||||
return STAGING_WHITELABEL;
|
return STAGING_WHITELABEL;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ import Edit from "@mui/icons-material/Edit";
|
||||||
import DeleteIcon from "@mui/icons-material/Delete";
|
import DeleteIcon from "@mui/icons-material/Delete";
|
||||||
import { red } from "@mui/material/colors";
|
import { red } from "@mui/material/colors";
|
||||||
import { pond } from "protobuf-ts/pond";
|
import { pond } from "protobuf-ts/pond";
|
||||||
import { taskScope } from "models/Scope";
|
import { taskScope, teamScope } from "models/Scope";
|
||||||
import EventBlocker from "common/EventBlocker";
|
import EventBlocker from "common/EventBlocker";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|
@ -58,19 +58,22 @@ export default function TaskCard(props: Props) {
|
||||||
const [day, setDay] = useState(0);
|
const [day, setDay] = useState(0);
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
const [permissions, setPermissions] = useState<pond.Permission[]>([]);
|
const [permissions, setPermissions] = useState<pond.Permission[]>([]);
|
||||||
const [{ user }] = useGlobalState();
|
const [{ user, as }] = useGlobalState();
|
||||||
const userAPI = useUserAPI();
|
const userAPI = useUserAPI();
|
||||||
const [menuAnchorEl, setMenuAnchorEl] = useState<Element | null>(null);
|
const [menuAnchorEl, setMenuAnchorEl] = useState<Element | null>(null);
|
||||||
|
|
||||||
const loadPermissions = useCallback(() => {
|
const loadPermissions = useCallback(() => {
|
||||||
let scope = taskScope(props.task.key);
|
let scope = taskScope(props.task.key);
|
||||||
|
if(as){//if viewing as a team use the permissions to the team, and not the task itself
|
||||||
|
scope = teamScope(as)
|
||||||
|
}
|
||||||
userAPI
|
userAPI
|
||||||
.getUser(user.id(), scope)
|
.getUser(user.id(), scope)
|
||||||
.then(resp => {
|
.then(resp => {
|
||||||
setPermissions(resp.permissions);
|
setPermissions(resp.permissions);
|
||||||
})
|
})
|
||||||
.catch(err => {});
|
.catch(err => {});
|
||||||
}, [props.task, userAPI, user]);
|
}, [props.task, userAPI, user, as]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadPermissions();
|
loadPermissions();
|
||||||
|
|
@ -176,3 +179,4 @@ export default function TaskCard(props: Props) {
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ export default defineConfig({
|
||||||
globPatterns: ['**/*.{js,css,html,png,jpg,svg}'], // Cache all common asset types
|
globPatterns: ['**/*.{js,css,html,png,jpg,svg}'], // Cache all common asset types
|
||||||
maximumFileSizeToCacheInBytes: 20 * 1024 * 1024, // 20 MiB
|
maximumFileSizeToCacheInBytes: 20 * 1024 * 1024, // 20 MiB
|
||||||
cleanupOutdatedCaches: true,
|
cleanupOutdatedCaches: true,
|
||||||
navigateFallback: "/indexV2.html",
|
navigateFallback: "/index.html",
|
||||||
runtimeCaching: [
|
runtimeCaching: [
|
||||||
{
|
{
|
||||||
urlPattern: ({ request }) => request.destination === 'document',
|
urlPattern: ({ request }) => request.destination === 'document',
|
||||||
|
|
@ -53,7 +53,7 @@ export default defineConfig({
|
||||||
target: 'esnext',
|
target: 'esnext',
|
||||||
rollupOptions: {
|
rollupOptions: {
|
||||||
input: {
|
input: {
|
||||||
main: path.resolve(__dirname, 'indexV2.html')
|
main: path.resolve(__dirname, 'index.html')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue