merged in messages changes
37
README.md
|
|
@ -7,6 +7,43 @@ Currently, two official plugins are available:
|
||||||
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
|
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
|
||||||
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
|
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
|
||||||
|
|
||||||
|
### White-labeling
|
||||||
|
|
||||||
|
Steps to add a new white label
|
||||||
|
|
||||||
|
1. Client Instructions:
|
||||||
|
|
||||||
|
- supply assets and colours to be used for the white-label (favicon if necessary)
|
||||||
|
- determine a sub-domain to use as mask, dashboard is preferred (ex: dashboard.example.com)
|
||||||
|
- add a `CNAME` record pointing their custom subdomain (ex: dashboard.example.com) to brandxtech.ca
|
||||||
|
- add a login button on their website point to `/login` of their custom subdomain (ex: dashboard.example.com/login)
|
||||||
|
|
||||||
|
<!-- frontend steps -->
|
||||||
|
2. Create a PWA folder in `public` that is named after the client and generate PWA assets using [this](https://realfavicongenerator.net/) website (use the company logo but you might need to use a custom favicon depending on the result)
|
||||||
|
|
||||||
|
- https://maskable.app/ also create a maskable icon for PWA
|
||||||
|
- create a 512x512 png as well for PWA
|
||||||
|
|
||||||
|
3. Create a white-label branding folder in `src/assets/whitelabels` and create a `darkLogo.png` and `lightLogo.png` from the client's logo
|
||||||
|
4. In `src/services/whiteLabel.ts`, add a new `Whitelabel` instance with the required fields and add its hostname mapping to the `whitelabels` map (it is very similar to step one but had some constraints when dealing with the `public` folder)
|
||||||
|
5. If there are any pages specific to the whitelabel be sure to update the side and bottom navigator
|
||||||
|
|
||||||
|
<!-- backend steps -->
|
||||||
|
6. In `dynamic-config.prod.yml`, add a router entry for the new whitelabel
|
||||||
|
7. In `pond/http/server.go`, add the sub-domain to the `allowedOrigins`
|
||||||
|
8. In `pond/whitelabel.go`, add an entry for the new white label
|
||||||
|
|
||||||
|
7. Create a new `Auth0` application for the client
|
||||||
|
- use a image bucket like [postimg](https://postimages.org/) to store their company logo and favicon
|
||||||
|
- add a whitelabel logo
|
||||||
|
- allow necessary `origins`, `callback URLs`, and `CORS domains` to the application settings (see the default application as a guideline)
|
||||||
|
- customize the Universal Login page to handle the new whitelabel
|
||||||
|
- check if the client ID matches this client's Auth0 application
|
||||||
|
- change the Universal Login theme using white-label colours and images stored in a bucket (ex: [postimg](https://postimages.org/))
|
||||||
|
8. Enable the white-label sub-domain for CORS in the backend
|
||||||
|
|
||||||
|
- in [http.go](https://gitlab.com/brandx/backend/blob/master/pond/http.go), add the white-label sub-domain to the `AllowedOrigins` list
|
||||||
|
|
||||||
## Expanding the ESLint configuration
|
## Expanding the ESLint configuration
|
||||||
|
|
||||||
If you are developing a production application, we recommend updating the configuration to enable type aware lint rules:
|
If you are developing a production application, we recommend updating the configuration to enable type aware lint rules:
|
||||||
|
|
|
||||||
8
package-lock.json
generated
|
|
@ -43,7 +43,11 @@
|
||||||
"mui-tel-input": "^7.0.0",
|
"mui-tel-input": "^7.0.0",
|
||||||
"notistack": "^3.0.1",
|
"notistack": "^3.0.1",
|
||||||
"openweathermap-ts": "^1.2.10",
|
"openweathermap-ts": "^1.2.10",
|
||||||
|
<<<<<<< HEAD
|
||||||
"protobuf-ts": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#dev",
|
"protobuf-ts": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#dev",
|
||||||
|
=======
|
||||||
|
"protobuf-ts": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#staging",
|
||||||
|
>>>>>>> messages_overhaul
|
||||||
"query-string": "^9.2.1",
|
"query-string": "^9.2.1",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-beautiful-dnd": "^13.1.1",
|
"react-beautiful-dnd": "^13.1.1",
|
||||||
|
|
@ -11967,7 +11971,11 @@
|
||||||
},
|
},
|
||||||
"node_modules/protobuf-ts": {
|
"node_modules/protobuf-ts": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
|
<<<<<<< HEAD
|
||||||
"resolved": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#bcf085706791036841f7047a5b37f00c18e54574",
|
"resolved": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#bcf085706791036841f7047a5b37f00c18e54574",
|
||||||
|
=======
|
||||||
|
"resolved": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#ab9e8d4fd38f9af7802398403d38fddda5eb01da",
|
||||||
|
>>>>>>> messages_overhaul
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"protobufjs": "^6.8.8"
|
"protobufjs": "^6.8.8"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
BIN
public/MiPCA/android-chrome-192x192.png
Normal file
|
After Width: | Height: | Size: 4 KiB |
BIN
public/MiPCA/android-chrome-512x512.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
public/MiPCA/apple-touch-icon.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
9
public/MiPCA/browserconfig.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<browserconfig>
|
||||||
|
<msapplication>
|
||||||
|
<tile>
|
||||||
|
<square150x150logo src="/mstile-150x150.png"/>
|
||||||
|
<TileColor>#da532c</TileColor>
|
||||||
|
</tile>
|
||||||
|
</msapplication>
|
||||||
|
</browserconfig>
|
||||||
BIN
public/MiPCA/favicon-16x16.png
Normal file
|
After Width: | Height: | Size: 808 B |
BIN
public/MiPCA/favicon-32x32.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
public/MiPCA/favicon.ico
Normal file
|
After Width: | Height: | Size: 15 KiB |
33
public/MiPCA/manifest.json
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
{
|
||||||
|
"name": "MiPCA",
|
||||||
|
"short_name": "MiPCA",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "favicon.ico",
|
||||||
|
"sizes": "48x48",
|
||||||
|
"type": "image/x-icon"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "android-chrome-192x192.png",
|
||||||
|
"sizes": "192x192",
|
||||||
|
"type": "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "android-chrome-512x512.png",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"type": "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "maskable_icon.png",
|
||||||
|
"sizes": "196x196",
|
||||||
|
"type": "image/png",
|
||||||
|
"purpose": "any maskable"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"start_url": "/",
|
||||||
|
"theme_color": "#272727",
|
||||||
|
"background_color": "#303030",
|
||||||
|
"display": "standalone",
|
||||||
|
"orientation": "any"
|
||||||
|
}
|
||||||
|
|
||||||
BIN
public/MiPCA/mstile-150x150.png
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
50
public/MiPCA/safari-pinned-tab.svg
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
<?xml version="1.0" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||||
|
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||||
|
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="1059.000000pt" height="1059.000000pt" viewBox="0 0 1059.000000 1059.000000"
|
||||||
|
preserveAspectRatio="xMidYMid meet">
|
||||||
|
<metadata>
|
||||||
|
Created by potrace 1.14, written by Peter Selinger 2001-2017
|
||||||
|
</metadata>
|
||||||
|
<g transform="translate(0.000000,1059.000000) scale(0.100000,-0.100000)"
|
||||||
|
fill="#000000" stroke="none">
|
||||||
|
<path d="M7029 6666 c-2 -2 -31 -7 -64 -10 -426 -43 -727 -354 -781 -806 -10
|
||||||
|
-89 -11 -984 -1 -1090 27 -277 151 -517 340 -659 92 -69 256 -140 366 -157 18
|
||||||
|
-3 43 -8 55 -10 11 -3 80 -8 151 -11 408 -17 711 161 894 525 39 77 95 242
|
||||||
|
114 335 l14 67 -36 1 c-174 5 -484 0 -490 -7 -4 -5 -15 -36 -25 -69 -40 -134
|
||||||
|
-128 -250 -226 -300 -73 -37 -171 -51 -268 -38 -177 23 -289 121 -347 303 -16
|
||||||
|
49 -18 107 -20 522 -3 508 1 556 51 668 15 33 30 60 34 60 3 0 13 12 20 26 20
|
||||||
|
36 100 92 167 115 116 41 247 36 353 -14 52 -25 157 -127 182 -178 36 -73 56
|
||||||
|
-125 63 -161 4 -21 10 -38 14 -38 9 -1 494 -1 512 -1 15 1 15 7 -2 79 -37 158
|
||||||
|
-60 225 -114 332 -141 281 -357 448 -651 504 -62 11 -296 21 -305 12z"/>
|
||||||
|
<path d="M0 5298 c0 -1071 3 -1348 13 -1349 55 -4 439 -1 444 4 3 3 6 420 6
|
||||||
|
927 0 506 2 919 4 917 2 -3 62 -190 134 -418 72 -228 178 -565 236 -749 58
|
||||||
|
-184 108 -345 110 -357 3 -13 10 -23 17 -24 6 0 107 -1 225 -2 205 -2 214 -1
|
||||||
|
222 18 9 21 232 743 429 1390 29 94 55 173 59 177 3 4 6 -418 5 -938 -1 -861
|
||||||
|
0 -944 15 -945 93 -5 426 -2 434 3 9 5 12 346 12 1348 l0 1340 -35 1 c-46 0
|
||||||
|
-555 0 -601 0 l-35 -1 -28 -97 c-15 -54 -32 -111 -37 -128 -5 -16 -40 -140
|
||||||
|
-78 -275 -38 -135 -101 -360 -141 -500 -40 -140 -81 -284 -90 -320 -10 -36
|
||||||
|
-41 -146 -69 -245 -29 -99 -54 -191 -57 -205 -8 -39 -12 -36 -28 21 -8 30 -47
|
||||||
|
167 -86 304 -40 138 -82 288 -95 335 -33 121 -120 427 -221 780 -47 168 -87
|
||||||
|
311 -87 318 0 6 -7 12 -16 12 -9 1 -161 2 -339 3 l-322 2 0 -1347z"/>
|
||||||
|
<path d="M2890 6400 l0 -245 253 0 252 0 0 245 0 245 -252 0 -253 0 0 -245z"/>
|
||||||
|
<path d="M3948 6642 l-46 -3 0 -244 1 -244 591 -3 c575 -3 592 -4 636 -24 85
|
||||||
|
-39 141 -114 165 -219 43 -186 -50 -367 -212 -411 -38 -10 -170 -12 -610 -10
|
||||||
|
l-563 2 -1 -30 c-4 -176 0 -1499 5 -1503 4 -5 417 -8 486 -4 14 1 15 63 15
|
||||||
|
521 0 343 3 520 10 521 5 0 170 1 365 3 384 3 419 7 558 63 270 108 449 345
|
||||||
|
478 632 10 109 11 132 3 224 -22 244 -131 442 -322 581 -93 68 -273 131 -408
|
||||||
|
142 -69 6 -1070 11 -1151 6z"/>
|
||||||
|
<path d="M9279 6642 c-46 -3 -47 -4 -64 -50 -9 -26 -21 -56 -25 -67 -5 -11
|
||||||
|
-50 -128 -100 -260 -50 -132 -94 -249 -99 -260 -5 -11 -55 -141 -111 -290 -56
|
||||||
|
-148 -105 -279 -110 -290 -11 -24 2 10 -296 -770 -284 -741 -267 -697 -255
|
||||||
|
-704 11 -7 2358 -8 2364 -1 3 4 -59 172 -126 340 -3 8 -144 377 -312 820 -169
|
||||||
|
443 -322 843 -340 890 -18 47 -79 207 -135 355 -57 149 -106 274 -110 278 -7
|
||||||
|
8 -198 14 -281 9z m144 -932 c8 -25 45 -130 82 -235 37 -104 86 -246 110 -315
|
||||||
|
24 -69 71 -204 105 -300 34 -96 76 -218 95 -270 18 -52 38 -107 44 -122 6 -16
|
||||||
|
9 -29 8 -29 -1 -1 -213 -2 -470 -3 l-468 -1 6 23 c5 22 54 162 300 862 51 146
|
||||||
|
108 307 126 358 17 51 35 89 39 85 4 -4 14 -28 23 -53z"/>
|
||||||
|
<path d="M2890 4720 c0 -609 3 -770 13 -771 6 0 120 -1 252 -2 l240 0 3 771 2
|
||||||
|
772 -255 0 -255 0 0 -770z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 3.1 KiB |
|
|
@ -13,7 +13,7 @@ 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 { Crisp } from "crisp-sdk-web";
|
import { Crisp } from "crisp-sdk-web";
|
||||||
import { useSnackbar } from 'hooks'
|
import { useMobile, useSnackbar } from 'hooks'
|
||||||
// import FirmwareLoader from './FirmwareLoader'
|
// import FirmwareLoader from './FirmwareLoader'
|
||||||
|
|
||||||
const reducer = (state: GlobalState, action: GlobalStateAction): GlobalState => {
|
const reducer = (state: GlobalState, action: GlobalStateAction): GlobalState => {
|
||||||
|
|
@ -67,6 +67,7 @@ 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, "")
|
||||||
|
|
||||||
|
|
@ -128,6 +129,54 @@ export default function UserWrapper(props: Props) {
|
||||||
Crisp.setTokenId(global?.user.id()); // e.g., your DB user ID
|
Crisp.setTokenId(global?.user.id()); // e.g., your DB user ID
|
||||||
}
|
}
|
||||||
}, [global]);
|
}, [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;
|
||||||
|
|
|
||||||
BIN
src/assets/whitelabels/MiPCA/MiPCALogo.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
|
|
@ -65,7 +65,7 @@ import { getDistanceUnit, or, getTemperatureUnit, getGrainUnit } from "utils";
|
||||||
import { makeStyles } from "@mui/styles";
|
import { makeStyles } from "@mui/styles";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import BinSelector from "./BinSelector";
|
import BinSelector from "./BinSelector";
|
||||||
import CustomGrainForm from "grain/CustomGrainForm";
|
import CustomGrainSelector from "grain/CustomGrainSelector";
|
||||||
// import BinSelector from "./BinSelector";
|
// import BinSelector from "./BinSelector";
|
||||||
|
|
||||||
const useStyles = makeStyles((theme: Theme) => {
|
const useStyles = makeStyles((theme: Theme) => {
|
||||||
|
|
@ -1054,7 +1054,7 @@ export default function BinSettings(props: Props) {
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
:
|
:
|
||||||
<Box sx={{border: "1px solid white", borderRadius: 2, padding: 2, marginBottom: 2}}>
|
<Box sx={{border: "1px solid white", borderRadius: 2, padding: 2, marginBottom: 2}}>
|
||||||
<CustomGrainForm initialGrain={customGrain} onGrainSettingsChange={(newGrainSettings) => {setCustomGrain(newGrainSettings)}}/>
|
<CustomGrainSelector initialGrain={customGrain} onGrainSettingsChange={(newGrainSettings) => {setCustomGrain(newGrainSettings)}}/>
|
||||||
</Box>
|
</Box>
|
||||||
}
|
}
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
|
|
|
||||||
|
|
@ -70,7 +70,7 @@ import Edit from "@mui/icons-material/Edit";
|
||||||
import { makeStyles } from "@mui/styles";
|
import { makeStyles } from "@mui/styles";
|
||||||
import ButtonGroup from "common/ButtonGroup";
|
import ButtonGroup from "common/ButtonGroup";
|
||||||
import ModeChangeDialog from "./conditioning/modeChangeDialog";
|
import ModeChangeDialog from "./conditioning/modeChangeDialog";
|
||||||
import CustomGrainForm from "grain/CustomGrainForm";
|
import CustomGrainSelector from "grain/CustomGrainSelector";
|
||||||
|
|
||||||
const useStyles = makeStyles((theme: Theme) => {
|
const useStyles = makeStyles((theme: Theme) => {
|
||||||
return ({
|
return ({
|
||||||
|
|
@ -332,14 +332,23 @@ export default function BinVisualizer(props: Props) {
|
||||||
setNewGrainSettings(bin.customGrain());
|
setNewGrainSettings(bin.customGrain());
|
||||||
}
|
}
|
||||||
if (bin.settings) {
|
if (bin.settings) {
|
||||||
let t = bin.settings.outdoorTemp;
|
let t = 0
|
||||||
|
let h = 0
|
||||||
|
if(ambient){
|
||||||
|
t = ambient.temperature
|
||||||
|
h = ambient.humidity
|
||||||
|
}else{
|
||||||
|
t = bin.settings.outdoorTemp;//use what is already set in the bin settings
|
||||||
|
h = bin.settings.outdoorHumidity
|
||||||
|
}
|
||||||
|
|
||||||
if (user.settings.temperatureUnit === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
|
if (user.settings.temperatureUnit === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
|
||||||
t = CtoF(t);
|
t = CtoF(t);
|
||||||
}
|
}
|
||||||
setOutdoorTemp(t);
|
setOutdoorTemp(t);
|
||||||
|
setOutdoorHumidity(h)
|
||||||
}
|
}
|
||||||
if (bin.settings) setOutdoorHumidity(bin.settings.outdoorHumidity);
|
}, [bin, user, ambient]);
|
||||||
}, [bin, user]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
//if(loadingTrend) return
|
//if(loadingTrend) return
|
||||||
|
|
@ -861,7 +870,7 @@ export default function BinVisualizer(props: Props) {
|
||||||
className={classes.bottomSpacing}
|
className={classes.bottomSpacing}
|
||||||
/>
|
/>
|
||||||
)} */}
|
)} */}
|
||||||
<CustomGrainForm initialGrain={bin.customGrain()} onGrainSettingsChange={settings => {setNewGrainSettings(settings)}}/>
|
<CustomGrainSelector initialGrain={bin.customGrain()} onGrainSettingsChange={settings => {setNewGrainSettings(settings)}}/>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
) : (
|
) : (
|
||||||
<TextField
|
<TextField
|
||||||
|
|
@ -1425,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}
|
||||||
|
|
@ -1438,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
|
||||||
|
|
@ -1459,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={{
|
||||||
|
|
@ -1468,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"
|
||||||
|
|
@ -1998,7 +2010,7 @@ export default function BinVisualizer(props: Props) {
|
||||||
}
|
}
|
||||||
b.settings.mode = newBinMode;
|
b.settings.mode = newBinMode;
|
||||||
}
|
}
|
||||||
|
console.log(b.settings)
|
||||||
binAPI.updateBin(bin.key(), b.settings, as).then(resp => {
|
binAPI.updateBin(bin.key(), b.settings, as).then(resp => {
|
||||||
refresh();
|
refresh();
|
||||||
});
|
});
|
||||||
|
|
@ -2025,8 +2037,8 @@ export default function BinVisualizer(props: Props) {
|
||||||
if(refresh) updateBin();
|
if(refresh) updateBin();
|
||||||
}}
|
}}
|
||||||
defaultTargetMoisture={bin.settings.inventory?.initialMoisture}
|
defaultTargetMoisture={bin.settings.inventory?.initialMoisture}
|
||||||
// defaultOutdoorTemp={get this from any ambient sensors}
|
defaultOutdoorTemp={outdoorTemp}
|
||||||
// defaultOutdoorHumidity={get this from any ambient sensors}
|
defaultOutdoorHumidity={outdoorHumidity}
|
||||||
changeTargetMoisture={newMoisture => {
|
changeTargetMoisture={newMoisture => {
|
||||||
setTargetMoisture(newMoisture)
|
setTargetMoisture(newMoisture)
|
||||||
}}
|
}}
|
||||||
|
|
|
||||||
|
|
@ -108,9 +108,9 @@ export default function ModeChangeDialog(props: Props){
|
||||||
const [deviceOption, setDeviceOption] = useState<Option>()
|
const [deviceOption, setDeviceOption] = useState<Option>()
|
||||||
|
|
||||||
useEffect(()=>{
|
useEffect(()=>{
|
||||||
setMoistureInput(defaultTargetMoisture?.toFixed(2) ?? "0")
|
setMoistureInput(defaultTargetMoisture?.toString() ?? "0")
|
||||||
setOutdoorTempInput(defaultOutdoorTemp?.toFixed(2) ?? "0")
|
setOutdoorTempInput(defaultOutdoorTemp?.toString() ?? "0")
|
||||||
setOutdoorHumidityInput(defaultOutdoorHumidity?.toFixed(2) ?? "0")
|
setOutdoorHumidityInput(defaultOutdoorHumidity?.toString() ?? "0")
|
||||||
},[defaultTargetMoisture, defaultOutdoorTemp, defaultOutdoorHumidity])
|
},[defaultTargetMoisture, defaultOutdoorTemp, defaultOutdoorHumidity])
|
||||||
|
|
||||||
//get the interactions and components for the device when it is selected from the dropdown
|
//get the interactions and components for the device when it is selected from the dropdown
|
||||||
|
|
@ -436,7 +436,9 @@ if (!selectedDevice) return;
|
||||||
}
|
}
|
||||||
stage1.steps.push(newStep);
|
stage1.steps.push(newStep);
|
||||||
});
|
});
|
||||||
stages.push(stage1)
|
if(stage1.steps.length > 0){
|
||||||
|
stages.push(stage1)
|
||||||
|
}
|
||||||
//stage two is to add the new interactions
|
//stage two is to add the new interactions
|
||||||
let stage2: Stage = {
|
let stage2: Stage = {
|
||||||
title: "Add New Interactions",
|
title: "Add New Interactions",
|
||||||
|
|
@ -447,7 +449,9 @@ if (!selectedDevice) return;
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
stages.push(stage2)
|
if(stage2.steps.length > 0){
|
||||||
|
stages.push(stage2)
|
||||||
|
}
|
||||||
|
|
||||||
//stage three is to update the controller components
|
//stage three is to update the controller components
|
||||||
let stage3: Stage = {
|
let stage3: Stage = {
|
||||||
|
|
@ -463,7 +467,9 @@ if (!selectedDevice) return;
|
||||||
stage3.steps.push(newStep)
|
stage3.steps.push(newStep)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
stages.push(stage3)
|
if(stage3.steps.length > 0){
|
||||||
|
stages.push(stage3)
|
||||||
|
}
|
||||||
//set those stages to a state variable
|
//set those stages to a state variable
|
||||||
setPromiseStages(stages)
|
setPromiseStages(stages)
|
||||||
}
|
}
|
||||||
|
|
@ -618,11 +624,7 @@ if (!selectedDevice) return;
|
||||||
onChange={event => {
|
onChange={event => {
|
||||||
setOutdoorTempInput(event.target.value)
|
setOutdoorTempInput(event.target.value)
|
||||||
if(changeOutdoorTemp && !isNaN(parseFloat(event.target.value))){
|
if(changeOutdoorTemp && !isNaN(parseFloat(event.target.value))){
|
||||||
//if the users prefs are F will need to convert it to C when sending it back
|
|
||||||
let val = parseFloat(event.target.value)
|
let val = parseFloat(event.target.value)
|
||||||
if(getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT){
|
|
||||||
val = fahrenheitToCelsius(val)
|
|
||||||
}
|
|
||||||
changeOutdoorTemp(val)
|
changeOutdoorTemp(val)
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
|
@ -721,7 +723,7 @@ if (!selectedDevice) return;
|
||||||
<Box>
|
<Box>
|
||||||
<PromiseProgress
|
<PromiseProgress
|
||||||
stages={promiseStages}
|
stages={promiseStages}
|
||||||
automaticStart
|
description="These are the changes that will occur in order to change your bin mode with the given options. Press start to begin."
|
||||||
failFast
|
failFast
|
||||||
onStart={() => {
|
onStart={() => {
|
||||||
startChange()
|
startChange()
|
||||||
|
|
|
||||||
|
|
@ -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}
|
||||||
|
|
|
||||||
|
|
@ -1,41 +1,39 @@
|
||||||
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, 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";
|
||||||
|
|
||||||
const useStyles = makeStyles((theme: Theme) => {
|
const useStyles = makeStyles<Theme>((theme: Theme) => ({
|
||||||
return ({
|
chatDrawer: {
|
||||||
chatDrawer: {
|
display: "flex",
|
||||||
display: "flex",
|
flexDirection: "column",
|
||||||
flexDirection: "column", // Arrange children vertically
|
backgroundColor: theme.palette.background.default,
|
||||||
backgroundColor: theme.palette.background.default,
|
height: "100%",
|
||||||
height: "100%",
|
maxHeight: "100%",
|
||||||
maxHeight: "100%",
|
minHeight: "100%",
|
||||||
minHeight: "100%",
|
minWidth: theme.spacing(54),
|
||||||
minWidth: theme.spacing(54),
|
maxWidth: theme.spacing(54),
|
||||||
maxWidth: theme.spacing(54),
|
},
|
||||||
},
|
chatContainer: {
|
||||||
chatContainer: {
|
flex: 1,
|
||||||
flex: 1,
|
overflow: "hidden",
|
||||||
overflow: "hidden",
|
},
|
||||||
},
|
chatDrawerMobile: {
|
||||||
chatDrawerMobile: {
|
backgroundColor: theme.palette.background.default,
|
||||||
backgroundColor: theme.palette.background.default,
|
height: "auto",
|
||||||
height: "auto",
|
width: "100%",
|
||||||
width: "100%"
|
},
|
||||||
},
|
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;
|
||||||
|
|
@ -48,17 +46,53 @@ export function ChatDrawer(props: Props) {
|
||||||
const isMobile = useMobile();
|
const isMobile = useMobile();
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
|
|
||||||
|
console.log(team.settings.avatar)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Drawer open={open} onClose={onClose} anchor="right" >
|
<Drawer
|
||||||
<Box className={isMobile ? classes.chatDrawerMobile : classes.chatDrawer}>
|
open={open}
|
||||||
<Box className={classes.drawerHeader}>
|
onClose={onClose}
|
||||||
<IconButton onClick={onClose}>
|
anchor="right"
|
||||||
<ChevronRight />
|
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}`,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{/* Add your icons here */}
|
||||||
|
<IconButton >
|
||||||
|
{/* <SomeIcon /> */}
|
||||||
|
<Avatar src={team.settings.avatar} />
|
||||||
|
</IconButton>
|
||||||
|
<IconButton>
|
||||||
|
{/* <AnotherIcon /> */}
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<Typography>{team.name()} Chat</Typography>
|
|
||||||
</Box>
|
</Box>
|
||||||
<Box className={classes.chatContainer}>
|
|
||||||
<Chat objectKey={team.settings.key} type={pond.NoteType.NOTE_TYPE_TEAM}/>
|
{/* Main chat area */}
|
||||||
|
<Box className={isMobile ? classes.chatDrawerMobile : classes.chatDrawer}>
|
||||||
|
<Box className={classes.drawerHeader}>
|
||||||
|
<IconButton onClick={onClose}>
|
||||||
|
<ChevronRight />
|
||||||
|
</IconButton>
|
||||||
|
<Typography>{team.name()} Chat</Typography>
|
||||||
|
</Box>
|
||||||
|
<Box className={classes.chatContainer}>
|
||||||
|
<Chat objectKey={team.settings.key} type={pond.NoteType.NOTE_TYPE_TEAM} />
|
||||||
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</Drawer>
|
</Drawer>
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import { pond } from "protobuf-ts/pond";
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { makeStyles } from "@mui/styles";
|
import { makeStyles } from "@mui/styles";
|
||||||
import { useGlobalState } from "providers";
|
import { useGlobalState } from "providers";
|
||||||
|
import { getContextKeys, getContextTypes } from "pbHelpers/Context";
|
||||||
|
|
||||||
interface TabPanelProps {
|
interface TabPanelProps {
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
|
|
@ -116,13 +117,7 @@ export default function DeviceLinkDrawer(props: Props) {
|
||||||
undefined,
|
undefined,
|
||||||
undefined,
|
undefined,
|
||||||
undefined,
|
undefined,
|
||||||
true,
|
true
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
as
|
|
||||||
)
|
)
|
||||||
.then(resp => {
|
.then(resp => {
|
||||||
let devMap = new Map<string, pond.ComprehensiveDevice>();
|
let devMap = new Map<string, pond.ComprehensiveDevice>();
|
||||||
|
|
|
||||||
|
|
@ -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}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { AxiosResponse } from "axios"
|
import { AxiosResponse } from "axios"
|
||||||
import { Box, Button, CircularProgress, DialogActions, DialogContent, DialogTitle, Typography } from "@mui/material"
|
import { Box, Button, CircularProgress, DialogActions, DialogContent, DialogTitle, Divider, Typography } from "@mui/material"
|
||||||
import { useEffect, useState } from "react"
|
import { useEffect, useState } from "react"
|
||||||
import { CheckCircle, Error, Pending } from "@mui/icons-material"
|
import { CheckCircle, Error, Pending } from "@mui/icons-material"
|
||||||
import React from "react"
|
import React from "react"
|
||||||
|
|
@ -18,7 +18,7 @@ export interface Step {
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
stages: Stage[]
|
stages: Stage[]
|
||||||
automaticStart: boolean
|
description?: string
|
||||||
failFast?: boolean
|
failFast?: boolean
|
||||||
onStart?: () => void
|
onStart?: () => void
|
||||||
onComplete?: () => void
|
onComplete?: () => void
|
||||||
|
|
@ -32,7 +32,7 @@ enum progress {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function PromiseProgress(props: Props){
|
export function PromiseProgress(props: Props){
|
||||||
const {stages, failFast, onStart, onComplete, automaticStart} = props
|
const {stages, failFast, onStart, onComplete, description } = props
|
||||||
const [completion, setCompletion] = useState<Map<string, progress>>(new Map())
|
const [completion, setCompletion] = useState<Map<string, progress>>(new Map())
|
||||||
|
|
||||||
const execute = () => {
|
const execute = () => {
|
||||||
|
|
@ -84,12 +84,6 @@ export function PromiseProgress(props: Props){
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if(automaticStart){
|
|
||||||
execute()
|
|
||||||
}
|
|
||||||
},[automaticStart, execute])
|
|
||||||
|
|
||||||
const getCompletion = (completion?: progress) => {
|
const getCompletion = (completion?: progress) => {
|
||||||
switch(completion){
|
switch(completion){
|
||||||
case progress.InProgress:
|
case progress.InProgress:
|
||||||
|
|
@ -123,16 +117,17 @@ export function PromiseProgress(props: Props){
|
||||||
Progress
|
Progress
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
{stages.map((stage, number) => displayStage(stage, number))}
|
<Typography>{description}</Typography>
|
||||||
|
<Box marginTop={2}>
|
||||||
|
{stages.map((stage, number) => displayStage(stage, number))}
|
||||||
|
</Box>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
{!automaticStart &&
|
<Button variant="contained" color="primary" onClick={() => {
|
||||||
<Button onClick={() => {
|
execute()
|
||||||
execute()
|
}}>
|
||||||
}}>
|
Start
|
||||||
Start
|
</Button>
|
||||||
</Button>
|
|
||||||
}
|
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@ import { getDistanceUnit } from "utils";
|
||||||
import { GrainOptions } from "grain";
|
import { GrainOptions } from "grain";
|
||||||
import CompModes from "component/ComponentMode.json";
|
import CompModes from "component/ComponentMode.json";
|
||||||
import FanPicker from "fans/fanPicker";
|
import FanPicker from "fans/fanPicker";
|
||||||
import CustomGrainForm from "grain/CustomGrainForm";
|
import CustomGrainSelector from "grain/CustomGrainSelector";
|
||||||
|
|
||||||
const useStyles = makeStyles((theme: Theme) => {
|
const useStyles = makeStyles((theme: Theme) => {
|
||||||
return ({
|
return ({
|
||||||
|
|
@ -654,7 +654,7 @@ export default function ComponentForm(props: Props) {
|
||||||
/>
|
/>
|
||||||
{useCustomGrain ?
|
{useCustomGrain ?
|
||||||
<Box sx={{border: "1px solid white", borderRadius: 2, padding: 2, marginBottom: 2}}>
|
<Box sx={{border: "1px solid white", borderRadius: 2, padding: 2, marginBottom: 2}}>
|
||||||
<CustomGrainForm onGrainSettingsChange={updateCustomGrain} initialGrain={form.component.customGrainProps()}/>
|
<CustomGrainSelector onGrainSettingsChange={updateCustomGrain} initialGrain={form.component.customGrainProps()}/>
|
||||||
</Box>
|
</Box>
|
||||||
:
|
:
|
||||||
<SearchSelect
|
<SearchSelect
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
152
src/gate/GateDeltaTempGraph.tsx
Normal file
|
|
@ -0,0 +1,152 @@
|
||||||
|
import { Box, 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"]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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,
|
||||||
|
*
|
||||||
|
* //NOT IMPLEMENTED YET//
|
||||||
|
* 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){
|
||||||
|
//TODO: this is just an idea of how to handle a possible path for the overhaul of gates, if that is the route we take then this needs to be coded
|
||||||
|
|
||||||
|
}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>
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -44,21 +44,33 @@ 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 [lastPressures, setLastPressures] = useState({ p1: 0, p2: 0 });
|
||||||
|
|
||||||
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 +99,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);
|
||||||
}
|
}
|
||||||
|
|
@ -328,8 +345,8 @@ export default function GateDevice(props: Props) {
|
||||||
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 +374,14 @@ export default function GateDevice(props: Props) {
|
||||||
gate={gate}
|
gate={gate}
|
||||||
display={detail}
|
display={detail}
|
||||||
compMap={componentOptions}
|
compMap={componentOptions}
|
||||||
setPCAState={(state: boolean) => {
|
// setPCAState={(state: boolean) => {
|
||||||
setPCAState(state);
|
// setPCAState(state);
|
||||||
}}
|
// }}
|
||||||
ambient={ambientKey}
|
ambient={ambientKey}
|
||||||
pressure={pressureKey}
|
pressure={pressureKey}
|
||||||
|
tempChain={tempKey}
|
||||||
|
redKey={redLightKey}
|
||||||
|
greenKey={greenLightKey}
|
||||||
device={device.id()}
|
device={device.id()}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { Button, DialogActions, DialogContent, DialogTitle, Typography } from "@mui/material";
|
import { CheckBox } from "@mui/icons-material";
|
||||||
|
import { Button, Checkbox, DialogActions, DialogContent, DialogTitle, FormControl, FormControlLabel, FormLabel, InputAdornment, TextField, Typography } from "@mui/material";
|
||||||
import ResponsiveDialog from "common/ResponsiveDialog";
|
import ResponsiveDialog from "common/ResponsiveDialog";
|
||||||
import { Component, Device } from "models";
|
import { Component, Device } from "models";
|
||||||
import { Gate } from "models/Gate";
|
import { Gate } from "models/Gate";
|
||||||
|
|
@ -6,6 +7,7 @@ import moment from "moment";
|
||||||
import { pond, quack } from "protobuf-ts/pond";
|
import { pond, quack } from "protobuf-ts/pond";
|
||||||
import { useGlobalState, useInteractionsAPI, useSnackbar } from "providers";
|
import { useGlobalState, useInteractionsAPI, useSnackbar } from "providers";
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
|
import { getPressureUnit } from "utils";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
|
|
@ -37,6 +39,7 @@ const densityMap = new Map<number, number>([
|
||||||
export default function GateDeviceInteraction(props: Props) {
|
export default function GateDeviceInteraction(props: Props) {
|
||||||
const { open, close, gate, compDevice, densityTemp } = props;
|
const { open, close, gate, compDevice, densityTemp } = props;
|
||||||
const [{ user, as }] = useGlobalState();
|
const [{ user, as }] = useGlobalState();
|
||||||
|
const [device, setDevice] = useState<Device>(Device.create())
|
||||||
const [lowDelta, setLowDelta] = useState(0);
|
const [lowDelta, setLowDelta] = useState(0);
|
||||||
const [highDelta, setHighDelta] = useState(0);
|
const [highDelta, setHighDelta] = useState(0);
|
||||||
const [greenComponent, setGreenComponent] = useState<Component>();
|
const [greenComponent, setGreenComponent] = useState<Component>();
|
||||||
|
|
@ -44,7 +47,12 @@ export default function GateDeviceInteraction(props: Props) {
|
||||||
const [pressureComponent, setPressureComponent] = useState<Component>();
|
const [pressureComponent, setPressureComponent] = useState<Component>();
|
||||||
const interactionsAPI = useInteractionsAPI();
|
const interactionsAPI = useInteractionsAPI();
|
||||||
const [adding, setAdding] = useState(false);
|
const [adding, setAdding] = useState(false);
|
||||||
|
//boolean to determine if a third condition should be put on the interaction for the red light, requires a V2 device running at least fromware version 2.1.9
|
||||||
|
const [useRedOffCondition, setUseRedOffCondition] = useState(false)
|
||||||
|
//if the pressure is under this value then the red light should be off
|
||||||
|
const [redThreshold, setRedThreshold] = useState("0")
|
||||||
const { openSnack } = useSnackbar();
|
const { openSnack } = useSnackbar();
|
||||||
|
const [pressureSource, setPressureSource] = useState<quack.ComponentID>(quack.ComponentID.create())
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
//math to determine what the delta pressures to set will be
|
//math to determine what the delta pressures to set will be
|
||||||
|
|
@ -95,46 +103,64 @@ export default function GateDeviceInteraction(props: Props) {
|
||||||
gate.preferences[component.key()] === pond.GateComponentType.GATE_COMPONENT_TYPE_PRESSURE
|
gate.preferences[component.key()] === pond.GateComponentType.GATE_COMPONENT_TYPE_PRESSURE
|
||||||
) {
|
) {
|
||||||
setPressureComponent(component);
|
setPressureComponent(component);
|
||||||
|
setPressureSource(
|
||||||
|
quack.ComponentID.create({
|
||||||
|
type: component.type(),
|
||||||
|
address: component.settings.address,
|
||||||
|
addressType: component.settings.addressType
|
||||||
|
})
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if(compDevice.device){
|
||||||
|
setDevice(Device.create(compDevice.device))
|
||||||
|
}
|
||||||
}, [gate, densityTemp, user, compDevice]);
|
}, [gate, densityTemp, user, compDevice]);
|
||||||
|
|
||||||
// useEffect(() => {
|
const buttonDisabled = () => {
|
||||||
// //load current interactions for the device
|
if (greenComponent === undefined) return true
|
||||||
// let deviceID = Device.any(compDevice.device).id();
|
if (redComponent === undefined) return true
|
||||||
// interactionsAPI.listInteractionsByDevice(deviceID).then(resp => {
|
if (adding) return true
|
||||||
// setCurrentInteractions(resp);
|
if (useRedOffCondition && isNaN(parseFloat(redThreshold))) return true
|
||||||
// });
|
return false
|
||||||
// }, [compDevice.device, interactionsAPI]);
|
}
|
||||||
|
|
||||||
// const removeCurrentInteractions = () => {
|
|
||||||
// let deviceID = Device.any(compDevice.device).id();
|
|
||||||
// currentInteractions.forEach(interaction => {
|
|
||||||
// interactionsAPI.removeInteraction(deviceID, interaction.key());
|
|
||||||
// });
|
|
||||||
// };
|
|
||||||
|
|
||||||
const createInteractions = async () => {
|
const createInteractions = async () => {
|
||||||
//the interactions to be made
|
//the interactions to be made
|
||||||
|
|
||||||
//TOGGLE green ON when pressure within range of upper and lower
|
|
||||||
if (
|
if (
|
||||||
greenComponent !== undefined &&
|
greenComponent !== undefined &&
|
||||||
redComponent !== undefined &&
|
redComponent !== undefined &&
|
||||||
pressureComponent !== undefined
|
pressureComponent !== undefined
|
||||||
) {
|
) {
|
||||||
let greenToggle: pond.InteractionSettings = pond.InteractionSettings.create({
|
|
||||||
sink: quack.ComponentID.create({
|
let lightInteractions: pond.InteractionSettings[] = []
|
||||||
type: greenComponent.type(),
|
//making variables for the parameters of the interactions
|
||||||
address: greenComponent.settings.address,
|
const redSink = quack.ComponentID.create({
|
||||||
addressType: greenComponent.settings.addressType
|
type: redComponent.type(),
|
||||||
}),
|
address: redComponent.settings.address,
|
||||||
source: quack.ComponentID.create({
|
addressType: redComponent.settings.addressType
|
||||||
type: pressureComponent.type(),
|
})
|
||||||
address: pressureComponent.settings.address,
|
|
||||||
addressType: pressureComponent.settings.addressType
|
const greenSink = quack.ComponentID.create({
|
||||||
}),
|
type: greenComponent.type(),
|
||||||
|
address: greenComponent.settings.address,
|
||||||
|
addressType: greenComponent.settings.addressType
|
||||||
|
})
|
||||||
|
|
||||||
|
const lightSchedule = pond.InteractionSchedule.create({
|
||||||
|
timezone: moment.tz.guess(),
|
||||||
|
weekdays: ["sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday"],
|
||||||
|
timeOfDayStart: "00:00",
|
||||||
|
timeOfDayEnd: "24:00"
|
||||||
|
})
|
||||||
|
|
||||||
|
//TOGGLE green ON when pressure within range of upper and lower, this will always be the same regardless
|
||||||
|
lightInteractions.push(pond.InteractionSettings.create({
|
||||||
|
sink: greenSink,
|
||||||
|
source: pressureSource,
|
||||||
conditions: [
|
conditions: [
|
||||||
pond.InteractionCondition.create({
|
pond.InteractionCondition.create({
|
||||||
comparison: quack.RelationalOperator.RELATIONAL_OPERATOR_GREATER_THAN,
|
comparison: quack.RelationalOperator.RELATIONAL_OPERATOR_GREATER_THAN,
|
||||||
|
|
@ -150,61 +176,146 @@ export default function GateDeviceInteraction(props: Props) {
|
||||||
nodeOne: 2,
|
nodeOne: 2,
|
||||||
nodeTwo: 1,
|
nodeTwo: 1,
|
||||||
subtype: 18,
|
subtype: 18,
|
||||||
schedule: pond.InteractionSchedule.create({
|
schedule: lightSchedule,
|
||||||
timezone: moment.tz.guess(),
|
|
||||||
weekdays: ["sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday"],
|
|
||||||
timeOfDayStart: "00:00",
|
|
||||||
timeOfDayEnd: "24:00"
|
|
||||||
}),
|
|
||||||
result: pond.InteractionResult.create({
|
result: pond.InteractionResult.create({
|
||||||
type: quack.InteractionResultType.INTERACTION_RESULT_TYPE_TOGGLE,
|
type: quack.InteractionResultType.INTERACTION_RESULT_TYPE_TOGGLE,
|
||||||
value: 1
|
value: 1
|
||||||
})
|
})
|
||||||
});
|
}))
|
||||||
|
|
||||||
//TOGGLE red OFF when pressure within range of upper and lower
|
//if they want the red light off when below a certain pressure, it will need 4 seperate SET interactions
|
||||||
let redToggle: pond.InteractionSettings = pond.InteractionSettings.create({
|
if(useRedOffCondition){
|
||||||
sink: quack.ComponentID.create({
|
//need to make sure the redthreshold is in pascals
|
||||||
type: redComponent.type(),
|
let thresholdPascals = 0
|
||||||
address: redComponent.settings.address,
|
if(!isNaN(parseFloat(redThreshold))){
|
||||||
addressType: redComponent.settings.addressType
|
if(getPressureUnit() === pond.PressureUnit.PRESSURE_UNIT_KILOPASCALS){
|
||||||
}),
|
thresholdPascals = parseFloat(redThreshold)*1000
|
||||||
source: quack.ComponentID.create({
|
}else if (getPressureUnit() === pond.PressureUnit.PRESSURE_UNIT_INCHES_OF_WATER){
|
||||||
type: pressureComponent.type(),
|
thresholdPascals = parseFloat(redThreshold)*249
|
||||||
address: pressureComponent.settings.address,
|
}
|
||||||
addressType: pressureComponent.settings.addressType
|
}
|
||||||
}),
|
|
||||||
conditions: [
|
//SET red on when above high
|
||||||
pond.InteractionCondition.create({
|
lightInteractions.push(pond.InteractionSettings.create({
|
||||||
comparison: quack.RelationalOperator.RELATIONAL_OPERATOR_GREATER_THAN,
|
sink: redSink,
|
||||||
value: -highDelta,
|
source: pressureSource,
|
||||||
measurementType: quack.MeasurementType.MEASUREMENT_TYPE_PRESSURE
|
conditions: [
|
||||||
}),
|
pond.InteractionCondition.create({
|
||||||
pond.InteractionCondition.create({
|
comparison: quack.RelationalOperator.RELATIONAL_OPERATOR_LESS_THAN,
|
||||||
comparison: quack.RelationalOperator.RELATIONAL_OPERATOR_LESS_THAN,
|
value: -highDelta,
|
||||||
value: -lowDelta,
|
measurementType: quack.MeasurementType.MEASUREMENT_TYPE_PRESSURE
|
||||||
measurementType: quack.MeasurementType.MEASUREMENT_TYPE_PRESSURE
|
})
|
||||||
|
],
|
||||||
|
nodeOne: 2,
|
||||||
|
nodeTwo: 1,
|
||||||
|
subtype: 18,
|
||||||
|
schedule: lightSchedule,
|
||||||
|
result: pond.InteractionResult.create({
|
||||||
|
type: quack.InteractionResultType.INTERACTION_RESULT_TYPE_SET,
|
||||||
|
value: 1
|
||||||
})
|
})
|
||||||
],
|
}))
|
||||||
nodeOne: 2,
|
//SET red off when below high and above low
|
||||||
nodeTwo: 1,
|
lightInteractions.push(pond.InteractionSettings.create({
|
||||||
subtype: 18,
|
sink: redSink,
|
||||||
schedule: pond.InteractionSchedule.create({
|
source: pressureSource,
|
||||||
timezone: moment.tz.guess(),
|
conditions: [
|
||||||
weekdays: ["sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday"],
|
pond.InteractionCondition.create({
|
||||||
timeOfDayStart: "00:00",
|
comparison: quack.RelationalOperator.RELATIONAL_OPERATOR_GREATER_THAN,
|
||||||
timeOfDayEnd: "24:00"
|
value: -highDelta,
|
||||||
}),
|
measurementType: quack.MeasurementType.MEASUREMENT_TYPE_PRESSURE
|
||||||
result: pond.InteractionResult.create({
|
}),
|
||||||
type: quack.InteractionResultType.INTERACTION_RESULT_TYPE_TOGGLE,
|
pond.InteractionCondition.create({
|
||||||
value: 0
|
comparison: quack.RelationalOperator.RELATIONAL_OPERATOR_LESS_THAN,
|
||||||
})
|
value: -lowDelta,
|
||||||
});
|
measurementType: quack.MeasurementType.MEASUREMENT_TYPE_PRESSURE
|
||||||
|
})
|
||||||
|
],
|
||||||
|
nodeOne: 2,
|
||||||
|
nodeTwo: 1,
|
||||||
|
subtype: 18,
|
||||||
|
schedule: lightSchedule,
|
||||||
|
result: pond.InteractionResult.create({
|
||||||
|
type: quack.InteractionResultType.INTERACTION_RESULT_TYPE_SET,
|
||||||
|
value: 0
|
||||||
|
})
|
||||||
|
}))
|
||||||
|
//SET red on when below low and above threshold
|
||||||
|
lightInteractions.push(pond.InteractionSettings.create({
|
||||||
|
sink: redSink,
|
||||||
|
source: pressureSource,
|
||||||
|
conditions: [
|
||||||
|
pond.InteractionCondition.create({
|
||||||
|
comparison: quack.RelationalOperator.RELATIONAL_OPERATOR_GREATER_THAN,
|
||||||
|
value: -lowDelta,
|
||||||
|
measurementType: quack.MeasurementType.MEASUREMENT_TYPE_PRESSURE
|
||||||
|
}),
|
||||||
|
pond.InteractionCondition.create({
|
||||||
|
comparison: quack.RelationalOperator.RELATIONAL_OPERATOR_LESS_THAN,
|
||||||
|
value: -thresholdPascals,
|
||||||
|
measurementType: quack.MeasurementType.MEASUREMENT_TYPE_PRESSURE
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
nodeOne: 2,
|
||||||
|
nodeTwo: 1,
|
||||||
|
subtype: 18,
|
||||||
|
schedule: lightSchedule,
|
||||||
|
result: pond.InteractionResult.create({
|
||||||
|
type: quack.InteractionResultType.INTERACTION_RESULT_TYPE_SET,
|
||||||
|
value: 1
|
||||||
|
})
|
||||||
|
}))
|
||||||
|
//SET red off when below threshold
|
||||||
|
lightInteractions.push(pond.InteractionSettings.create({
|
||||||
|
sink: redSink,
|
||||||
|
source: pressureSource,
|
||||||
|
conditions: [
|
||||||
|
pond.InteractionCondition.create({
|
||||||
|
comparison: quack.RelationalOperator.RELATIONAL_OPERATOR_LESS_THAN,
|
||||||
|
value: thresholdPascals,
|
||||||
|
measurementType: quack.MeasurementType.MEASUREMENT_TYPE_PRESSURE
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
subtype: 1,
|
||||||
|
schedule: lightSchedule,
|
||||||
|
result: pond.InteractionResult.create({
|
||||||
|
type: quack.InteractionResultType.INTERACTION_RESULT_TYPE_SET,
|
||||||
|
value: 0
|
||||||
|
})
|
||||||
|
}))
|
||||||
|
}else{
|
||||||
|
//otherwise use the regular single TOGGLE interaction
|
||||||
|
//TOGGLE red OFF when pressure within range of upper and lower
|
||||||
|
lightInteractions.push(pond.InteractionSettings.create({
|
||||||
|
sink: redSink,
|
||||||
|
source: pressureSource,
|
||||||
|
conditions: [
|
||||||
|
pond.InteractionCondition.create({
|
||||||
|
comparison: quack.RelationalOperator.RELATIONAL_OPERATOR_GREATER_THAN,
|
||||||
|
value: -highDelta,
|
||||||
|
measurementType: quack.MeasurementType.MEASUREMENT_TYPE_PRESSURE
|
||||||
|
}),
|
||||||
|
pond.InteractionCondition.create({
|
||||||
|
comparison: quack.RelationalOperator.RELATIONAL_OPERATOR_LESS_THAN,
|
||||||
|
value: -lowDelta,
|
||||||
|
measurementType: quack.MeasurementType.MEASUREMENT_TYPE_PRESSURE
|
||||||
|
})
|
||||||
|
],
|
||||||
|
nodeOne: 2,
|
||||||
|
nodeTwo: 1,
|
||||||
|
subtype: 18,
|
||||||
|
schedule: lightSchedule,
|
||||||
|
result: pond.InteractionResult.create({
|
||||||
|
type: quack.InteractionResultType.INTERACTION_RESULT_TYPE_TOGGLE,
|
||||||
|
value: 0
|
||||||
|
})
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
let deviceID = Device.any(compDevice.device).id();
|
let deviceID = Device.any(compDevice.device).id();
|
||||||
if (deviceID !== undefined) {
|
if (deviceID !== undefined) {
|
||||||
let multi = pond.MultiInteractionSettings.create({
|
let multi = pond.MultiInteractionSettings.create({
|
||||||
interactions: [greenToggle, redToggle]
|
interactions: lightInteractions
|
||||||
});
|
});
|
||||||
interactionsAPI
|
interactionsAPI
|
||||||
.addMultiInteractions(deviceID, multi, as)
|
.addMultiInteractions(deviceID, multi, as)
|
||||||
|
|
@ -230,11 +341,51 @@ export default function GateDeviceInteraction(props: Props) {
|
||||||
}}>
|
}}>
|
||||||
<DialogTitle>Set Interaction For Light Toggle</DialogTitle>
|
<DialogTitle>Set Interaction For Light Toggle</DialogTitle>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
Your Delta Pressures, in pascals, will be:
|
{/* Your Delta Pressures, in pascals, will be:
|
||||||
<Typography>low = {lowDelta}</Typography>
|
<Typography>low = {lowDelta}</Typography>
|
||||||
<Typography>high = {highDelta}</Typography>
|
<Typography>high = {highDelta}</Typography>
|
||||||
<Typography>{greenComponent ? "" : "Green LED Component not found"}</Typography>
|
<Typography>{greenComponent ? "" : "Green LED Component not found"}</Typography>
|
||||||
<Typography>{redComponent ? "" : "Red LED Component not found"}</Typography>
|
<Typography>{redComponent ? "" : "Red LED Component not found"}</Typography> */}
|
||||||
|
<Typography>
|
||||||
|
This will clear existing interactions on the pressure chain and set new ones to toggle the green light on and the red light off
|
||||||
|
when the difference between the pressures falls within this range and vice versa:
|
||||||
|
</Typography>
|
||||||
|
<br />
|
||||||
|
<Typography>Upper Limit: {getPressureUnit() === pond.PressureUnit.PRESSURE_UNIT_INCHES_OF_WATER ? (highDelta/249.089).toFixed(2) + " iwg" : highDelta/1000 + " kPa"}</Typography>
|
||||||
|
<Typography>Lower Limit: {getPressureUnit() === pond.PressureUnit.PRESSURE_UNIT_INCHES_OF_WATER ? (lowDelta/249.089).toFixed(2) + " iwg" : lowDelta/1000 + " kPa"}</Typography>
|
||||||
|
<br />
|
||||||
|
|
||||||
|
<Typography>If you would like to have the interactions set in such a way that if the average pressure falls below a given value set the use off condition and set the value to use</Typography>
|
||||||
|
<FormControlLabel
|
||||||
|
control={
|
||||||
|
<Checkbox
|
||||||
|
checked={useRedOffCondition}
|
||||||
|
onChange={(e, checked) => {
|
||||||
|
setUseRedOffCondition(checked)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label={<Typography>Use Off Condition</Typography>}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
type="number"
|
||||||
|
fullWidth
|
||||||
|
disabled={!useRedOffCondition}
|
||||||
|
value={redThreshold}
|
||||||
|
error={isNaN(parseFloat(redThreshold))}
|
||||||
|
helperText={isNaN(parseFloat(redThreshold)) ? "Must be a valid number" : ""}
|
||||||
|
InputProps={{
|
||||||
|
endAdornment: (
|
||||||
|
<InputAdornment position="end">
|
||||||
|
{getPressureUnit() === pond.PressureUnit.PRESSURE_UNIT_INCHES_OF_WATER ? "iwg" : "kPa"}
|
||||||
|
</InputAdornment>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
onChange={e => {
|
||||||
|
setRedThreshold(e.target.value)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button
|
<Button
|
||||||
|
|
@ -244,9 +395,13 @@ export default function GateDeviceInteraction(props: Props) {
|
||||||
//TODO: Once we figure out how to fix the backend to handle rapid addition/removal of interactions
|
//TODO: Once we figure out how to fix the backend to handle rapid addition/removal of interactions
|
||||||
//removeCurrentInteractions();
|
//removeCurrentInteractions();
|
||||||
setAdding(true);
|
setAdding(true);
|
||||||
createInteractions();
|
interactionsAPI.clearInteractions(device.id(), [pressureSource]).then(resp => {
|
||||||
|
createInteractions();
|
||||||
|
}).catch(err => {
|
||||||
|
console.error(err)
|
||||||
|
})
|
||||||
}}
|
}}
|
||||||
disabled={greenComponent === undefined || redComponent === undefined || adding}>
|
disabled={buttonDisabled()}>
|
||||||
{adding ? "Adding Interaction" : "Create Interaction"}
|
{adding ? "Adding Interaction" : "Create Interaction"}
|
||||||
</Button>
|
</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
@ -70,13 +70,19 @@ export default function GateFlowGraph(props: Props) {
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
const [flowEvents, setFlowEvents] = useState<pond.AirFlowEvent[]>([]);
|
const [flowEvents, setFlowEvents] = useState<pond.AirFlowEvent[]>([]);
|
||||||
const [eventsLoading, setEventsLoading] = useState(false);
|
const [eventsLoading, setEventsLoading] = useState(false);
|
||||||
//these two constants could be entered by the user at time of retrieval
|
//these two constants could be entered by the user at time of retrieval or set in the gates settings
|
||||||
const eventThreshold = 5; //the threshold that if crossed from one measurement to the next during a flow event will end the current flow event and start a new one
|
const eventThreshold = 5; //the threshold that if crossed from one measurement to the next during a flow event will end the current flow event and start a new one
|
||||||
const idleFlow = 3.5; //the mass air flow that must be crossed in order to start a flow event, anything below this will not start an event but must go below this to end an event
|
//const idleFlow = 3.5; //the mass air flow that must be crossed in order to start a flow event, anything below this will not start an event but must go below this to end an event
|
||||||
|
const [idleFlow, setIdleFlow] = useState(3.5)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (loadingChartData) return;
|
if (loadingChartData) return;
|
||||||
if (ambient && pressureComponent) {
|
if (ambient && pressureComponent) {
|
||||||
|
let gateIdle = idleFlow
|
||||||
|
if(gate.settings.idleFlow){
|
||||||
|
gateIdle = gate.settings.idleFlow
|
||||||
|
}
|
||||||
|
|
||||||
let recent: SSAreaDataPoint | undefined;
|
let recent: SSAreaDataPoint | undefined;
|
||||||
setLoadingChartData(true);
|
setLoadingChartData(true);
|
||||||
gateAPI
|
gateAPI
|
||||||
|
|
@ -110,11 +116,11 @@ export default function GateFlowGraph(props: Props) {
|
||||||
|
|
||||||
/** determine runtime */
|
/** determine runtime */
|
||||||
// set the start time if the values is greater than the idleFlow and start is not already set
|
// set the start time if the values is greater than the idleFlow and start is not already set
|
||||||
if (val.airFlow >= idleFlow && !start) {
|
if (val.airFlow >= gateIdle && !start) {
|
||||||
start = time;
|
start = time;
|
||||||
}
|
}
|
||||||
// set the stop time when off or at the last measurements and the start time is set
|
// set the stop time when off or at the last measurements and the start time is set
|
||||||
if ((val.airFlow < idleFlow || i === resp.data.values.length - idleFlow) && start) {
|
if ((val.airFlow < gateIdle || i === resp.data.values.length - gateIdle) && start) {
|
||||||
stop = time;
|
stop = time;
|
||||||
}
|
}
|
||||||
// if both start and stop are set calculate add the timeframe to the total runtime
|
// if both start and stop are set calculate add the timeframe to the total runtime
|
||||||
|
|
@ -125,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) {
|
||||||
|
|
@ -203,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,77 @@ 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]) {
|
if (um.values[0] && um.values[0].values.length > 1) {
|
||||||
c = compMap.get(compMeasurement[0].componentId) ?? Component.create();
|
return areaGraph(
|
||||||
}
|
um,
|
||||||
if (compMap.get(c.key())) {
|
describeMeasurement(um.type, component.type(), component.subType()),
|
||||||
return (
|
component.settings.smoothingAverages
|
||||||
<Card raised key={i} style={{ padding: 10, marginBottom: 15 }}>
|
|
||||||
{graphHeader(c)}
|
|
||||||
{compMeasurement.map(um => {
|
|
||||||
if (um.values[0] && um.values[0].values.length > 1) {
|
|
||||||
return areaGraph(
|
|
||||||
um,
|
|
||||||
describeMeasurement(um.type, c.type(), c.subType()),
|
|
||||||
c.settings.smoothingAverages
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return lineGraph(
|
|
||||||
um,
|
|
||||||
describeMeasurement(um.type, c.type(), c.subType()),
|
|
||||||
c.settings.smoothingAverages
|
|
||||||
);
|
|
||||||
}
|
|
||||||
})}
|
|
||||||
</Card>
|
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return <Box></Box>;
|
return lineGraph(
|
||||||
|
um,
|
||||||
|
describeMeasurement(um.type, component.type(), component.subType()),
|
||||||
|
component.settings.smoothingAverages
|
||||||
|
);
|
||||||
}
|
}
|
||||||
})}
|
})}
|
||||||
</Box>
|
</Card>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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)
|
||||||
|
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}/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
//T/H chain
|
||||||
|
let tempReadings = compMeasurements.get(tempChain)
|
||||||
|
if(tempComp && tempReadings){
|
||||||
|
graphs.push(graphCard(tempComp, tempReadings))
|
||||||
|
}
|
||||||
|
//ambient
|
||||||
|
let ambientComp = compMap.get(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)
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<GateFlowGraph
|
<GateFlowGraph
|
||||||
|
|
@ -318,14 +355,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}/>
|
||||||
|
}
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,15 @@ import ResponsiveTable, { Column } from "common/ResponsiveTable";
|
||||||
import AddGateFab from "./AddGateFab";
|
import AddGateFab from "./AddGateFab";
|
||||||
import GateSettings from "./GateSettings";
|
import GateSettings from "./GateSettings";
|
||||||
import { useGateAPI, useGlobalState } from "providers";
|
import { useGateAPI, useGlobalState } from "providers";
|
||||||
import { Settings } from "@mui/icons-material";
|
import { CheckCircleOutline, DoNotDisturb, ErrorOutline, HelpOutlineOutlined, Settings } from "@mui/icons-material";
|
||||||
import { cloneDeep } from "lodash";
|
import { cloneDeep } from "lodash";
|
||||||
|
import moment from "moment";
|
||||||
|
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[];
|
||||||
|
|
@ -45,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)
|
||||||
|
|
@ -89,6 +97,108 @@ export default function GateList(props: Props) {
|
||||||
setPageSize(event.target.value);
|
setPageSize(event.target.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const displayPCAStatus = (state: pond.PCAState) => {
|
||||||
|
switch(state){
|
||||||
|
case pond.PCAState.PCA_STATE_IN_BOUNDS:
|
||||||
|
return <CheckCircleOutline sx={{color: "green"}}/>
|
||||||
|
case pond.PCAState.PCA_STATE_OUT_BOUNDS:
|
||||||
|
return <ErrorOutline sx={{color: "red"}} />
|
||||||
|
case pond.PCAState.PCA_STATE_OFF:
|
||||||
|
return <DoNotDisturb />
|
||||||
|
default:
|
||||||
|
return <HelpOutlineOutlined />
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) => {
|
||||||
|
console.log(gate)
|
||||||
|
let display = ""
|
||||||
|
let deltaTemp = 0
|
||||||
|
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 (T2 - T1) provided there are two temps
|
||||||
|
//loop to find the temp readings
|
||||||
|
let clone = cloneDeep(gate.status.lastTempReading)
|
||||||
|
clone.forEach(unitMeasurement => {
|
||||||
|
let um = UnitMeasurement.create(unitMeasurement, user)
|
||||||
|
if(um.type === quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE){
|
||||||
|
if(um.values.length > 0){ //as long as there is at least on thing in the measurements
|
||||||
|
let lastReading = um.values[um.values.length-1] //there should only be one measurement in here but just make sure to get the end of the array
|
||||||
|
if(lastReading.values.length > 1){ //make sure there are at least two values in the array
|
||||||
|
console.log(lastReading.values)
|
||||||
|
deltaTemp = lastReading.values[lastReading.values.length -1] - lastReading.values[0] //subtract the first value from the last value to get the delta
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
display = deltaTemp.toFixed(2) + (getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT ? "°F" : "°C")
|
||||||
|
}
|
||||||
|
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 [
|
||||||
{
|
{
|
||||||
|
|
@ -112,45 +222,60 @@ export default function GateList(props: Props) {
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Duct Type",
|
title: "PCA Status",
|
||||||
render: gate => (
|
render: gate => {
|
||||||
<Box padding={2}>
|
return (<Box padding={2}>
|
||||||
<Typography>
|
{displayPCAStatus(gate.status.pcaState)}
|
||||||
{gate.settings.ductName}
|
</Box>)
|
||||||
</Typography>
|
}
|
||||||
</Box>
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Duct Size(mm)",
|
title: "Delta Temperature",
|
||||||
render: gate => (
|
render: gate => {
|
||||||
|
return (
|
||||||
<Box padding={2}>
|
<Box padding={2}>
|
||||||
<Typography>
|
{conditionDisplay(gate)}
|
||||||
{gate.ductDiameter()}
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
</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>
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
|
//taking out these columns for now because were not sure if the customer actually wants them
|
||||||
|
// {
|
||||||
|
// 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>[] => {
|
||||||
|
|
@ -162,13 +287,16 @@ export default function GateList(props: Props) {
|
||||||
<Typography margin="auto" marginLeft={0} fontWeight={650} fontSize={20}>
|
<Typography margin="auto" marginLeft={0} fontWeight={650} fontSize={20}>
|
||||||
{gate.name}
|
{gate.name}
|
||||||
</Typography>
|
</Typography>
|
||||||
<IconButton onClick={(event) => {
|
<Box display="flex" alignItems="center" gap={1}>
|
||||||
event.stopPropagation()
|
{displayPCAStatus(gate.status.pcaState)}
|
||||||
setSelectedGate(gate)
|
<IconButton onClick={(event) => {
|
||||||
setGateDialog(true)
|
event.stopPropagation()
|
||||||
}}>
|
setSelectedGate(gate)
|
||||||
<Settings />
|
setGateDialog(true)
|
||||||
</IconButton>
|
}}>
|
||||||
|
<Settings />
|
||||||
|
</IconButton>
|
||||||
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -183,25 +311,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>
|
||||||
|
{/* taking these out for now because we are not sure if the customer wants them */}
|
||||||
|
{/* <Box padding={2} display="flex" flexDirection="row" justifyContent="space-between">
|
||||||
|
<Typography>Estimated Temp:</Typography>
|
||||||
|
{tempDisplay(gate)}
|
||||||
</Box>
|
</Box>
|
||||||
<Box padding={2} display="flex" flexDirection="row" justifyContent="space-between">
|
<Box padding={2} display="flex" flexDirection="row" justifyContent="space-between">
|
||||||
<Typography>Duct Size(mm):</Typography>
|
<Typography>Last Flow:</Typography>
|
||||||
<Typography>{gate.ductDiameter()}</Typography>
|
<Typography color={teal[500]}>{gate.status.lastMassAirflow.toFixed(2)} kg/s</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">
|
<Box padding={2} display="flex" flexDirection="row" justifyContent="space-between">
|
||||||
<Typography>DuctLength(m):</Typography>
|
<Typography>Outlet Temp:</Typography>
|
||||||
<Typography>{gate.ductLength()}</Typography>
|
{lastOutletReading(gate.status.lastTempReading)}
|
||||||
</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">
|
||||||
|
<Typography>Outlet Pressure:</Typography>
|
||||||
|
{lastOutletReading(gate.status.lastPressureReading)}
|
||||||
|
</Box> */}
|
||||||
</Box>
|
</Box>
|
||||||
// <Typography variant="body2" color="textSecondary" sx={{ padding: 1 }}>
|
|
||||||
|
|
||||||
// </Typography>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -48,8 +48,8 @@ interface Props {
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -63,7 +63,7 @@ export default function GateSVG(props: Props) {
|
||||||
tempChainSelector,
|
tempChainSelector,
|
||||||
pressureChainSelector,
|
pressureChainSelector,
|
||||||
pcaState,
|
pcaState,
|
||||||
pcaFanState,
|
// pcaFanState,
|
||||||
checkInTime
|
checkInTime
|
||||||
} = props;
|
} = props;
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
|
|
@ -242,16 +242,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>
|
||||||
);
|
);
|
||||||
|
|
@ -302,7 +309,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 +322,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 +333,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>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -76,6 +76,7 @@ export default function GateSettings(props: Props) {
|
||||||
//user set identifier to be shown on the marker on the map
|
//user set identifier to be shown on the marker on the map
|
||||||
const [terminalIdentifier, setTerminalIdentifier] = useState("");
|
const [terminalIdentifier, setTerminalIdentifier] = useState("");
|
||||||
const [gateIdentifier, setGateIdentifier] = useState("");
|
const [gateIdentifier, setGateIdentifier] = useState("");
|
||||||
|
const [idleLimit, setIdleLimit] = useState<number>(0);
|
||||||
const [hourlyPCA, setHourlyPCA] = useState<number>(0);
|
const [hourlyPCA, setHourlyPCA] = useState<number>(0);
|
||||||
const [hourlyAPU, setHourlyAPU] = useState<number>(0);
|
const [hourlyAPU, setHourlyAPU] = useState<number>(0);
|
||||||
|
|
||||||
|
|
@ -140,6 +141,7 @@ export default function GateSettings(props: Props) {
|
||||||
settings.thermalResistance = ductProps.thermalResistance;
|
settings.thermalResistance = ductProps.thermalResistance;
|
||||||
settings.lowerFlow = lowerFlowBound;
|
settings.lowerFlow = lowerFlowBound;
|
||||||
settings.upperFlow = upperFlowBound;
|
settings.upperFlow = upperFlowBound;
|
||||||
|
settings.idleFlow = idleLimit;
|
||||||
settings.ductName = ductName;
|
settings.ductName = ductName;
|
||||||
settings.pcaType = pcaUnit;
|
settings.pcaType = pcaUnit;
|
||||||
settings.letterIdentifier = terminalIdentifier;
|
settings.letterIdentifier = terminalIdentifier;
|
||||||
|
|
@ -167,6 +169,7 @@ export default function GateSettings(props: Props) {
|
||||||
thermalResistance: ductProps.thermalResistance,
|
thermalResistance: ductProps.thermalResistance,
|
||||||
lowerFlow: lowerFlowBound,
|
lowerFlow: lowerFlowBound,
|
||||||
upperFlow: upperFlowBound,
|
upperFlow: upperFlowBound,
|
||||||
|
idleFlow: idleLimit,
|
||||||
ductName: ductName,
|
ductName: ductName,
|
||||||
pcaType: pcaUnit,
|
pcaType: pcaUnit,
|
||||||
letterIdentifier: terminalIdentifier,
|
letterIdentifier: terminalIdentifier,
|
||||||
|
|
@ -283,16 +286,18 @@ export default function GateSettings(props: Props) {
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<TextField
|
<TextField
|
||||||
|
margin="dense"
|
||||||
label="Gate Name"
|
label="Gate Name"
|
||||||
fullWidth
|
fullWidth
|
||||||
value={gateName}
|
value={gateName}
|
||||||
onChange={e => setGateName(e.target.value)}
|
onChange={e => setGateName(e.target.value)}
|
||||||
/>
|
/>
|
||||||
<Select
|
<TextField
|
||||||
|
margin="dense"
|
||||||
id="terminal"
|
id="terminal"
|
||||||
label="Terminal"
|
label="Terminal"
|
||||||
fullWidth
|
fullWidth
|
||||||
displayEmpty
|
select
|
||||||
value={terminalKey}
|
value={terminalKey}
|
||||||
onChange={e => {
|
onChange={e => {
|
||||||
setTerminalKey(e.target.value as string);
|
setTerminalKey(e.target.value as string);
|
||||||
|
|
@ -305,8 +310,9 @@ export default function GateSettings(props: Props) {
|
||||||
{terminal.name}
|
{terminal.name}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
))}
|
))}
|
||||||
</Select>
|
</TextField>
|
||||||
<TextField
|
<TextField
|
||||||
|
margin="dense"
|
||||||
label="Low Mass Air Flow"
|
label="Low Mass Air Flow"
|
||||||
fullWidth
|
fullWidth
|
||||||
type="number"
|
type="number"
|
||||||
|
|
@ -314,19 +320,30 @@ export default function GateSettings(props: Props) {
|
||||||
onChange={e => setLowerFlowBound(+e.target.value)}
|
onChange={e => setLowerFlowBound(+e.target.value)}
|
||||||
/>
|
/>
|
||||||
<TextField
|
<TextField
|
||||||
|
margin="dense"
|
||||||
label="High Mass Air Flow"
|
label="High Mass Air Flow"
|
||||||
fullWidth
|
fullWidth
|
||||||
type="number"
|
type="dense"
|
||||||
value={upperFlowBound}
|
value={upperFlowBound}
|
||||||
onChange={e => setUpperFlowBound(+e.target.value)}
|
onChange={e => setUpperFlowBound(+e.target.value)}
|
||||||
/>
|
/>
|
||||||
<TextField
|
<TextField
|
||||||
|
margin="dense"
|
||||||
|
label="Idle Mass Air Flow"
|
||||||
|
fullWidth
|
||||||
|
type="number"
|
||||||
|
value={idleLimit}
|
||||||
|
onChange={e => setIdleLimit(+e.target.value)}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
margin="dense"
|
||||||
label="PCA Unit"
|
label="PCA Unit"
|
||||||
fullWidth
|
fullWidth
|
||||||
value={pcaUnit}
|
value={pcaUnit}
|
||||||
onChange={e => setPcaUnit(e.target.value)}
|
onChange={e => setPcaUnit(e.target.value)}
|
||||||
/>
|
/>
|
||||||
<TextField
|
<TextField
|
||||||
|
margin="dense"
|
||||||
style={{ width: "45%" }}
|
style={{ width: "45%" }}
|
||||||
select
|
select
|
||||||
label="Terminal Identifier"
|
label="Terminal Identifier"
|
||||||
|
|
@ -340,6 +357,7 @@ export default function GateSettings(props: Props) {
|
||||||
{tiOptions}
|
{tiOptions}
|
||||||
</TextField>
|
</TextField>
|
||||||
<TextField
|
<TextField
|
||||||
|
margin="dense"
|
||||||
style={{ width: "45%", marginLeft: "10%" }}
|
style={{ width: "45%", marginLeft: "10%" }}
|
||||||
label="Gate Identifier"
|
label="Gate Identifier"
|
||||||
select
|
select
|
||||||
|
|
@ -351,6 +369,7 @@ export default function GateSettings(props: Props) {
|
||||||
{numberOptions}
|
{numberOptions}
|
||||||
</TextField>
|
</TextField>
|
||||||
<TextField
|
<TextField
|
||||||
|
margin="dense"
|
||||||
label="Hourly PCA Cost"
|
label="Hourly PCA Cost"
|
||||||
fullWidth
|
fullWidth
|
||||||
type="number"
|
type="number"
|
||||||
|
|
@ -361,6 +380,7 @@ export default function GateSettings(props: Props) {
|
||||||
onChange={e => setHourlyPCA(+e.target.value)}
|
onChange={e => setHourlyPCA(+e.target.value)}
|
||||||
/>
|
/>
|
||||||
<TextField
|
<TextField
|
||||||
|
margin="dense"
|
||||||
label="Hourly APU Cost"
|
label="Hourly APU Cost"
|
||||||
fullWidth
|
fullWidth
|
||||||
type="number"
|
type="number"
|
||||||
|
|
|
||||||
|
|
@ -1,101 +1,57 @@
|
||||||
import { ExpandMore } from "@mui/icons-material"
|
import {MenuItem, TextField } from "@mui/material"
|
||||||
import { Accordion, AccordionDetails, AccordionSummary, Box, Button, MenuItem, TextField, Typography } from "@mui/material"
|
|
||||||
import SearchSelect, { Option } from "common/SearchSelect"
|
|
||||||
import { cloneDeep } from "lodash"
|
import { cloneDeep } from "lodash"
|
||||||
import { pond } from "protobuf-ts/pond"
|
import { pond } from "protobuf-ts/pond"
|
||||||
import { useGlobalState } from "providers"
|
import React, { useEffect, useRef, useState } from "react"
|
||||||
import { useGrainAPI } from "providers/pond/grainAPI"
|
|
||||||
import React, { useEffect, useState } from "react"
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
initialGrain?: pond.GrainSettings
|
grainSettings?: pond.GrainSettings
|
||||||
onGrainSettingsChange: (settings: pond.GrainSettings) => void
|
onGrainSettingsChange: (settings: pond.GrainSettings, formValid: boolean) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function CustomGrainForm(props: Props) {
|
export default function CustomGrainForm(props: Props) {
|
||||||
const {initialGrain, onGrainSettingsChange} = props
|
const {grainSettings, onGrainSettingsChange} = props
|
||||||
const grainAPI = useGrainAPI()
|
const [newGrainSettings, setNewGrainSettings] = useState(pond.GrainSettings.create())
|
||||||
const [{as}] = useGlobalState()
|
const [name, setName] = useState("")
|
||||||
const [name, setName] = useState(initialGrain ? initialGrain.name : "")
|
const [group, setGroup] = useState("")
|
||||||
const [group, setGroup] = useState(initialGrain ? initialGrain.group : "")
|
const [equation, setEquation] = useState<pond.MoistureEquation>(pond.MoistureEquation.MOISTURE_EQUATION_NONE)
|
||||||
const [equation, setEquation] = useState<pond.MoistureEquation>(initialGrain ? initialGrain.equation : pond.MoistureEquation.MOISTURE_EQUATION_NONE)
|
const [constantA, setConstantA] = useState("0")
|
||||||
const [constantA, setConstantA] = useState(initialGrain ? initialGrain.a.toString() : "0")
|
const [constantB, setConstantB] = useState("0")
|
||||||
const [constantB, setConstantB] = useState(initialGrain ? initialGrain.b.toString() : "0")
|
const [constantC, setConstantC] = useState("0")
|
||||||
const [constantC, setConstantC] = useState(initialGrain ? initialGrain.c.toString() : "0")
|
const [kgPerBushel, setKgPerBushel] = useState("0")
|
||||||
const [kgPerBushel, setKgPerBushel] = useState(initialGrain ? initialGrain.kgPerBushel.toString() : "0")
|
const [bushelsPerTonne, setBushelsPerTonne] = useState("0")
|
||||||
const [bushelsPerTonne, setBushelsPerTonne] = useState(initialGrain ? initialGrain.bushelsPerTonne.toString() : "0")
|
const valid = useRef(false)
|
||||||
const [newGrainSettings, setNewGrainSettings] = useState(initialGrain ?? pond.GrainSettings.create())
|
|
||||||
const [grainMap, setGrainMap] = useState<Map<string, pond.GrainObject>>(
|
|
||||||
initialGrain ?
|
|
||||||
new Map([
|
|
||||||
["Active", pond.GrainObject.create({key: "Active", name: initialGrain.name, settings: initialGrain})]
|
|
||||||
])
|
|
||||||
:
|
|
||||||
new Map())
|
|
||||||
const [grainOptions, setGrainOptions] = useState<Option[]>(
|
|
||||||
initialGrain ?
|
|
||||||
[
|
|
||||||
{
|
|
||||||
label: initialGrain.name,
|
|
||||||
value: "Active",
|
|
||||||
group: "Active"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
:
|
|
||||||
[]
|
|
||||||
)
|
|
||||||
const [isNew, setIsNew] = useState(false)
|
|
||||||
const [selectedOption, setSelectedOption] = useState<Option | null>(initialGrain ? {
|
|
||||||
label: initialGrain.name,
|
|
||||||
value: "Active",
|
|
||||||
group: "Active"
|
|
||||||
} : null)
|
|
||||||
|
|
||||||
useEffect(()=>{
|
useEffect(()=>{
|
||||||
grainAPI.listGrains(0, 0, "asc", "name", undefined, undefined, undefined, as).then(resp => {
|
if(grainSettings){
|
||||||
let options: Option[] = grainOptions
|
setName(grainSettings.name)
|
||||||
let map: Map<string, pond.GrainObject> = grainMap
|
setGroup(grainSettings.group)
|
||||||
if (resp.data.grains){
|
setEquation(grainSettings.equation)
|
||||||
resp.data.grains.forEach(grain => {
|
setConstantA(grainSettings.a.toString())
|
||||||
map.set(grain.key, grain)
|
setConstantB(grainSettings.b.toString())
|
||||||
options.push({
|
setConstantC(grainSettings.c.toString())
|
||||||
label: grain.name,
|
setKgPerBushel(grainSettings.kgPerBushel.toString())
|
||||||
value: grain.key,
|
setBushelsPerTonne(grainSettings.bushelsPerTonne.toString())
|
||||||
group: grain.settings?.group,
|
setNewGrainSettings(grainSettings)
|
||||||
})
|
}
|
||||||
})
|
},[grainSettings])
|
||||||
}
|
|
||||||
setGrainOptions([...options])
|
|
||||||
setGrainMap(map)
|
|
||||||
})
|
|
||||||
},[grainAPI])
|
|
||||||
|
|
||||||
const invalid = () => {
|
const validate = (name: string, group: string, constA: string, constB: string, constC: string, kgPerBushel: string, bushelsPerTonne: string) => {
|
||||||
if (name === "") return true
|
if (name === "") return false
|
||||||
if (group === "") return true
|
if (group === "") return false
|
||||||
if (isNaN(parseFloat(constantA))) return true
|
if (isNaN(parseFloat(constA))) return false
|
||||||
if (isNaN(parseFloat(constantB))) return true
|
if (isNaN(parseFloat(constB))) return false
|
||||||
if (isNaN(parseFloat(constantA))) return true
|
if (isNaN(parseFloat(constC))) return false
|
||||||
if (isNaN(parseFloat(kgPerBushel))) return true
|
if (isNaN(parseFloat(kgPerBushel))) return false
|
||||||
if (isNaN(parseFloat(bushelsPerTonne))) return true
|
if (isNaN(parseFloat(bushelsPerTonne))) return false
|
||||||
return false
|
return true
|
||||||
}
|
|
||||||
|
|
||||||
const saveGrain = () => {
|
|
||||||
// console.log(newGrainSettings)
|
|
||||||
grainAPI.addGrain(newGrainSettings, as).then(resp => {
|
|
||||||
console.log("grain added")
|
|
||||||
}).catch(err => {
|
|
||||||
console.log("err")
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const settingsChanged = (newSettings: pond.GrainSettings) => {
|
const settingsChanged = (newSettings: pond.GrainSettings) => {
|
||||||
setNewGrainSettings(newSettings)
|
setNewGrainSettings(newSettings)
|
||||||
onGrainSettingsChange(newSettings)
|
onGrainSettingsChange(newSettings, valid.current)
|
||||||
}
|
}
|
||||||
|
|
||||||
const newGrain = () => {
|
const grainForm = () => {
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<TextField
|
<TextField
|
||||||
|
|
@ -106,9 +62,9 @@ export default function CustomGrainForm(props: Props) {
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
let name = e.target.value
|
let name = e.target.value
|
||||||
setName(name)
|
setName(name)
|
||||||
setIsNew(true)
|
|
||||||
let settings = cloneDeep(newGrainSettings)
|
let settings = cloneDeep(newGrainSettings)
|
||||||
settings.name = name
|
settings.name = name
|
||||||
|
valid.current = validate(name, group, constantA, constantB, constantC, kgPerBushel, bushelsPerTonne)
|
||||||
settingsChanged(settings)
|
settingsChanged(settings)
|
||||||
}}/>
|
}}/>
|
||||||
<TextField
|
<TextField
|
||||||
|
|
@ -119,9 +75,9 @@ export default function CustomGrainForm(props: Props) {
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
let group = e.target.value
|
let group = e.target.value
|
||||||
setGroup(group)
|
setGroup(group)
|
||||||
setIsNew(true)
|
|
||||||
let settings = cloneDeep(newGrainSettings)
|
let settings = cloneDeep(newGrainSettings)
|
||||||
settings.group = group
|
settings.group = group
|
||||||
|
valid.current = validate(name, group, constantA, constantB, constantC, kgPerBushel, bushelsPerTonne)
|
||||||
settingsChanged(settings)
|
settingsChanged(settings)
|
||||||
}}/>
|
}}/>
|
||||||
<TextField
|
<TextField
|
||||||
|
|
@ -133,9 +89,9 @@ export default function CustomGrainForm(props: Props) {
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
let enumVal = parseFloat(e.target.value)
|
let enumVal = parseFloat(e.target.value)
|
||||||
setEquation(enumVal)
|
setEquation(enumVal)
|
||||||
setIsNew(true)
|
|
||||||
let settings = cloneDeep(newGrainSettings)
|
let settings = cloneDeep(newGrainSettings)
|
||||||
settings.equation = enumVal
|
settings.equation = enumVal
|
||||||
|
valid.current = validate(name, group, constantA, constantB, constantC, kgPerBushel, bushelsPerTonne)
|
||||||
settingsChanged(settings)
|
settingsChanged(settings)
|
||||||
}}
|
}}
|
||||||
select>
|
select>
|
||||||
|
|
@ -165,12 +121,12 @@ export default function CustomGrainForm(props: Props) {
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
let val = e.target.value
|
let val = e.target.value
|
||||||
setConstantA(val)
|
setConstantA(val)
|
||||||
setIsNew(true)
|
valid.current = validate(name, group, val, constantB, constantC, kgPerBushel, bushelsPerTonne)
|
||||||
|
let settings = cloneDeep(newGrainSettings)
|
||||||
if(!isNaN(parseFloat(val))){
|
if(!isNaN(parseFloat(val))){
|
||||||
let settings = cloneDeep(newGrainSettings)
|
|
||||||
settings.a = parseFloat(val)
|
settings.a = parseFloat(val)
|
||||||
settingsChanged(settings)
|
|
||||||
}
|
}
|
||||||
|
settingsChanged(settings)
|
||||||
}}
|
}}
|
||||||
label="Constant A"/>
|
label="Constant A"/>
|
||||||
<TextField
|
<TextField
|
||||||
|
|
@ -183,12 +139,12 @@ export default function CustomGrainForm(props: Props) {
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
let val = e.target.value
|
let val = e.target.value
|
||||||
setConstantB(val)
|
setConstantB(val)
|
||||||
setIsNew(true)
|
valid.current = validate(name, group, constantA, val, constantC, kgPerBushel, bushelsPerTonne)
|
||||||
|
let settings = cloneDeep(newGrainSettings)
|
||||||
if(!isNaN(parseFloat(val))){
|
if(!isNaN(parseFloat(val))){
|
||||||
let settings = cloneDeep(newGrainSettings)
|
|
||||||
settings.b = parseFloat(val)
|
settings.b = parseFloat(val)
|
||||||
settingsChanged(settings)
|
|
||||||
}
|
}
|
||||||
|
settingsChanged(settings)
|
||||||
}}
|
}}
|
||||||
label="Constant B"/>
|
label="Constant B"/>
|
||||||
<TextField
|
<TextField
|
||||||
|
|
@ -201,12 +157,12 @@ export default function CustomGrainForm(props: Props) {
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
let val = e.target.value
|
let val = e.target.value
|
||||||
setConstantC(val)
|
setConstantC(val)
|
||||||
setIsNew(true)
|
valid.current = validate(name, group, constantA, constantB, val, kgPerBushel, bushelsPerTonne)
|
||||||
|
let settings = cloneDeep(newGrainSettings)
|
||||||
if(!isNaN(parseFloat(val))){
|
if(!isNaN(parseFloat(val))){
|
||||||
let settings = cloneDeep(newGrainSettings)
|
|
||||||
settings.c = parseFloat(val)
|
settings.c = parseFloat(val)
|
||||||
settingsChanged(settings)
|
|
||||||
}
|
}
|
||||||
|
settingsChanged(settings)
|
||||||
}}
|
}}
|
||||||
label="Constant C"/>
|
label="Constant C"/>
|
||||||
<TextField
|
<TextField
|
||||||
|
|
@ -219,12 +175,12 @@ export default function CustomGrainForm(props: Props) {
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
let val = e.target.value
|
let val = e.target.value
|
||||||
setKgPerBushel(val)
|
setKgPerBushel(val)
|
||||||
setIsNew(true)
|
valid.current = validate(name, group, constantA, constantB, constantC, val, bushelsPerTonne)
|
||||||
|
let settings = cloneDeep(newGrainSettings)
|
||||||
if(!isNaN(parseFloat(val))){
|
if(!isNaN(parseFloat(val))){
|
||||||
let settings = cloneDeep(newGrainSettings)
|
|
||||||
settings.kgPerBushel = parseFloat(val)
|
settings.kgPerBushel = parseFloat(val)
|
||||||
settingsChanged(settings)
|
|
||||||
}
|
}
|
||||||
|
settingsChanged(settings)
|
||||||
}}
|
}}
|
||||||
label="kg per Bushel"/>
|
label="kg per Bushel"/>
|
||||||
<TextField
|
<TextField
|
||||||
|
|
@ -237,85 +193,21 @@ export default function CustomGrainForm(props: Props) {
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
let val = e.target.value
|
let val = e.target.value
|
||||||
setBushelsPerTonne(val)
|
setBushelsPerTonne(val)
|
||||||
setIsNew(true)
|
valid.current = validate(name, group, constantA, constantB, constantC, kgPerBushel, val)
|
||||||
|
let settings = cloneDeep(newGrainSettings)
|
||||||
if(!isNaN(parseFloat(val))){
|
if(!isNaN(parseFloat(val))){
|
||||||
let settings = cloneDeep(newGrainSettings)
|
|
||||||
settings.bushelsPerTonne = parseFloat(val)
|
settings.bushelsPerTonne = parseFloat(val)
|
||||||
settingsChanged(settings)
|
|
||||||
}
|
}
|
||||||
|
settingsChanged(settings)
|
||||||
}}
|
}}
|
||||||
label="Bushels per Tonne"/>
|
label="Bushels per Tonne"/>
|
||||||
{isNew &&
|
|
||||||
<Box display='flex' justifyContent="flex-end">
|
|
||||||
<Button
|
|
||||||
variant="contained"
|
|
||||||
disabled={invalid()}
|
|
||||||
onClick={() => {
|
|
||||||
saveGrain()
|
|
||||||
setIsNew(false)
|
|
||||||
}}
|
|
||||||
color="primary">
|
|
||||||
Save Custom Grain
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
}
|
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const grainAccordion = () => {
|
|
||||||
return (
|
|
||||||
<Accordion>
|
|
||||||
<AccordionSummary expandIcon={<ExpandMore />}>Custom Grain Properties</AccordionSummary>
|
|
||||||
<AccordionDetails>
|
|
||||||
{newGrain()}
|
|
||||||
</AccordionDetails>
|
|
||||||
</Accordion>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
{grainOptions.length === 0 ?
|
{grainForm()}
|
||||||
<Box>
|
|
||||||
<Typography sx={{marginBottom: 1}}>Custom Grain Properties</Typography>
|
|
||||||
{newGrain()}
|
|
||||||
</Box>
|
|
||||||
:
|
|
||||||
<Box>
|
|
||||||
<Box sx={{marginBottom: 2}}>
|
|
||||||
<SearchSelect
|
|
||||||
label="My Grains"
|
|
||||||
selected={selectedOption}
|
|
||||||
changeSelection={option => {
|
|
||||||
setIsNew(false)
|
|
||||||
setSelectedOption(option)
|
|
||||||
//when an option is selected set all of the state variables controlling the form entries
|
|
||||||
let grain = grainMap.get(option?.value)
|
|
||||||
if(grain && grain.settings){
|
|
||||||
//set the form values
|
|
||||||
setName(grain.name)
|
|
||||||
setGroup(grain.settings.group)
|
|
||||||
setEquation(grain.settings.equation)
|
|
||||||
setConstantA(grain.settings.a.toString())
|
|
||||||
setConstantB(grain.settings.b.toString())
|
|
||||||
setConstantC(grain.settings.c.toString())
|
|
||||||
setKgPerBushel(grain.settings.kgPerBushel.toString())
|
|
||||||
setBushelsPerTonne(grain.settings.bushelsPerTonne.toString())
|
|
||||||
//the the new grain settings object
|
|
||||||
setNewGrainSettings(grain.settings)
|
|
||||||
//pass that settings object back up
|
|
||||||
settingsChanged(grain.settings)
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
group
|
|
||||||
options={grainOptions}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
{grainAccordion()}
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
}
|
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
155
src/grain/CustomGrainSelector.tsx
Normal file
|
|
@ -0,0 +1,155 @@
|
||||||
|
import { ExpandMore } from "@mui/icons-material"
|
||||||
|
import { Accordion, AccordionDetails, AccordionSummary, Box, Button, Typography } from "@mui/material"
|
||||||
|
import SearchSelect, { Option } from "common/SearchSelect"
|
||||||
|
import { pond } from "protobuf-ts/pond"
|
||||||
|
import { useGlobalState } from "providers"
|
||||||
|
import { useGrainAPI } from "providers/pond/grainAPI"
|
||||||
|
import React, { useEffect, useState } from "react"
|
||||||
|
import CustomGrainForm from "./CustomGrainForm"
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
initialGrain?: pond.GrainSettings
|
||||||
|
onGrainSettingsChange: (settings: pond.GrainSettings) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function CustomGrainSelector(props: Props) {
|
||||||
|
const {initialGrain, onGrainSettingsChange} = props
|
||||||
|
const grainAPI = useGrainAPI()
|
||||||
|
const [{as}] = useGlobalState()
|
||||||
|
const [newGrainSettings, setNewGrainSettings] = useState(initialGrain ?? pond.GrainSettings.create())
|
||||||
|
const [formValid, setFormValid] = useState(true)
|
||||||
|
const [grainMap, setGrainMap] = useState<Map<string, pond.GrainObject>>(
|
||||||
|
initialGrain ?
|
||||||
|
new Map([
|
||||||
|
["Active", pond.GrainObject.create({key: "Active", name: initialGrain.name, settings: initialGrain})]
|
||||||
|
])
|
||||||
|
:
|
||||||
|
new Map())
|
||||||
|
const [grainOptions, setGrainOptions] = useState<Option[]>(
|
||||||
|
initialGrain ?
|
||||||
|
[
|
||||||
|
{
|
||||||
|
label: initialGrain.name,
|
||||||
|
value: "Active",
|
||||||
|
group: "Active"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
:
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
const [isNew, setIsNew] = useState(false)
|
||||||
|
const [selectedOption, setSelectedOption] = useState<Option | null>(initialGrain ? {
|
||||||
|
label: initialGrain.name,
|
||||||
|
value: "Active",
|
||||||
|
group: "Active"
|
||||||
|
} : null)
|
||||||
|
|
||||||
|
useEffect(()=>{
|
||||||
|
grainAPI.listGrains(0, 0, "asc", "name", undefined, undefined, undefined, as).then(resp => {
|
||||||
|
let options: Option[] = grainOptions
|
||||||
|
let map: Map<string, pond.GrainObject> = grainMap
|
||||||
|
if (resp.data.grains){
|
||||||
|
resp.data.grains.forEach(grain => {
|
||||||
|
map.set(grain.key, grain)
|
||||||
|
options.push({
|
||||||
|
label: grain.name,
|
||||||
|
value: grain.key,
|
||||||
|
group: grain.settings?.group,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
setGrainOptions([...options])
|
||||||
|
setGrainMap(map)
|
||||||
|
})
|
||||||
|
},[grainAPI])
|
||||||
|
|
||||||
|
const saveGrain = () => {
|
||||||
|
// console.log(newGrainSettings)
|
||||||
|
grainAPI.addGrain(newGrainSettings, as).then(resp => {
|
||||||
|
console.log("grain added")
|
||||||
|
}).catch(err => {
|
||||||
|
console.log("err")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const settingsChanged = (newSettings: pond.GrainSettings, formValid: boolean) => {
|
||||||
|
setNewGrainSettings(newSettings)
|
||||||
|
setIsNew(true)
|
||||||
|
setFormValid(formValid)
|
||||||
|
onGrainSettingsChange(newSettings)
|
||||||
|
}
|
||||||
|
|
||||||
|
const grainAccordion = () => {
|
||||||
|
return (
|
||||||
|
<Accordion>
|
||||||
|
<AccordionSummary expandIcon={<ExpandMore />}>Custom Grain Properties</AccordionSummary>
|
||||||
|
<AccordionDetails>
|
||||||
|
<CustomGrainForm grainSettings={newGrainSettings} onGrainSettingsChange={settingsChanged}/>
|
||||||
|
{isNew &&
|
||||||
|
<Box display='flex' justifyContent="flex-end">
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
disabled={!formValid}
|
||||||
|
onClick={() => {
|
||||||
|
setIsNew(false)
|
||||||
|
saveGrain()
|
||||||
|
}}
|
||||||
|
color="primary">
|
||||||
|
Save Custom Grain
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
}
|
||||||
|
</AccordionDetails>
|
||||||
|
</Accordion>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
{grainOptions.length === 0 ?
|
||||||
|
<Box>
|
||||||
|
<Typography sx={{marginBottom: 1}}>Custom Grain Properties</Typography>
|
||||||
|
<CustomGrainForm grainSettings={newGrainSettings} onGrainSettingsChange={settingsChanged}/>
|
||||||
|
{isNew &&
|
||||||
|
<Box display='flex' justifyContent="flex-end">
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
disabled={!formValid}
|
||||||
|
onClick={() => {
|
||||||
|
setIsNew(false)
|
||||||
|
}}
|
||||||
|
color="primary">
|
||||||
|
Save Custom Grain
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
}
|
||||||
|
</Box>
|
||||||
|
:
|
||||||
|
<Box>
|
||||||
|
<Box sx={{marginBottom: 2}}>
|
||||||
|
<SearchSelect
|
||||||
|
label="My Grains"
|
||||||
|
selected={selectedOption}
|
||||||
|
changeSelection={option => {
|
||||||
|
setIsNew(false)
|
||||||
|
setSelectedOption(option)
|
||||||
|
//when an option is selected set all of the state variables controlling the form entries
|
||||||
|
let grain = grainMap.get(option?.value)
|
||||||
|
if(grain && grain.settings){
|
||||||
|
//the the new grain settings object
|
||||||
|
setNewGrainSettings(grain.settings)
|
||||||
|
//pass that settings object back up
|
||||||
|
settingsChanged(grain.settings, true)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
group
|
||||||
|
options={grainOptions}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
{grainAccordion()}
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
}
|
||||||
|
</React.Fragment>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -14,11 +14,12 @@ import WheatImg from "assets/grain/wheat.jpg";
|
||||||
import { Option } from "common/SearchSelect";
|
import { Option } from "common/SearchSelect";
|
||||||
import { cloneDeep } from "lodash";
|
import { cloneDeep } from "lodash";
|
||||||
import { pond } from "protobuf-ts/pond";
|
import { pond } from "protobuf-ts/pond";
|
||||||
|
import { Equation } from "./GrainMoisture";
|
||||||
|
|
||||||
export interface GrainExtension {
|
export interface GrainExtension {
|
||||||
name: string;
|
name: string;
|
||||||
group: string;
|
group: string;
|
||||||
equation: pond.MoistureEquation;
|
equation: Equation;
|
||||||
a: number;
|
a: number;
|
||||||
b: number;
|
b: number;
|
||||||
c: number;
|
c: number;
|
||||||
|
|
@ -36,7 +37,7 @@ const defaultSetTemp = 30.0;
|
||||||
const defaultGrain: GrainExtension = {
|
const defaultGrain: GrainExtension = {
|
||||||
name: "None",
|
name: "None",
|
||||||
group: "",
|
group: "",
|
||||||
equation: pond.MoistureEquation.MOISTURE_EQUATION_NONE,
|
equation: Equation.none,
|
||||||
a: 0,
|
a: 0,
|
||||||
b: 0,
|
b: 0,
|
||||||
c: 0,
|
c: 0,
|
||||||
|
|
@ -55,7 +56,7 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
||||||
{
|
{
|
||||||
name: "Custom Type",
|
name: "Custom Type",
|
||||||
group: "",
|
group: "",
|
||||||
equation: pond.MoistureEquation.MOISTURE_EQUATION_NONE,
|
equation: Equation.none,
|
||||||
a: 0,
|
a: 0,
|
||||||
b: 0,
|
b: 0,
|
||||||
c: 0,
|
c: 0,
|
||||||
|
|
@ -71,7 +72,7 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
||||||
{
|
{
|
||||||
name: "Barley",
|
name: "Barley",
|
||||||
group: "Barley",
|
group: "Barley",
|
||||||
equation: pond.MoistureEquation.MOISTURE_EQUATION_CHUNG_PFOST,
|
equation: Equation.chungPfost,
|
||||||
a: 475.12,
|
a: 475.12,
|
||||||
b: 0.14843,
|
b: 0.14843,
|
||||||
c: 71.996,
|
c: 71.996,
|
||||||
|
|
@ -88,7 +89,7 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
||||||
{
|
{
|
||||||
name: "Buckwheat",
|
name: "Buckwheat",
|
||||||
group: "Buckwheat",
|
group: "Buckwheat",
|
||||||
equation: pond.MoistureEquation.MOISTURE_EQUATION_CHUNG_PFOST,
|
equation: Equation.chungPfost,
|
||||||
a: 103540000,
|
a: 103540000,
|
||||||
b: 0.1646,
|
b: 0.1646,
|
||||||
c: 15853000,
|
c: 15853000,
|
||||||
|
|
@ -105,7 +106,7 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
||||||
{
|
{
|
||||||
name: "Canola",
|
name: "Canola",
|
||||||
group: "Canola",
|
group: "Canola",
|
||||||
equation: pond.MoistureEquation.MOISTURE_EQUATION_HALSEY,
|
equation: Equation.halsey,
|
||||||
a: 3.489,
|
a: 3.489,
|
||||||
b: -0.010553,
|
b: -0.010553,
|
||||||
c: 1.86,
|
c: 1.86,
|
||||||
|
|
@ -123,7 +124,7 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
||||||
{
|
{
|
||||||
name: "Rapeseed",
|
name: "Rapeseed",
|
||||||
group: "Canola",
|
group: "Canola",
|
||||||
equation: pond.MoistureEquation.MOISTURE_EQUATION_HALSEY,
|
equation: Equation.halsey,
|
||||||
a: 3.0026,
|
a: 3.0026,
|
||||||
b: -0.0048967,
|
b: -0.0048967,
|
||||||
c: 1.7607,
|
c: 1.7607,
|
||||||
|
|
@ -140,7 +141,7 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
||||||
{
|
{
|
||||||
name: "Corn (Henderson)",
|
name: "Corn (Henderson)",
|
||||||
group: "Corn",
|
group: "Corn",
|
||||||
equation: pond.MoistureEquation.MOISTURE_EQUATION_HENDERSON,
|
equation: Equation.henderson,
|
||||||
a: 0.000066612,
|
a: 0.000066612,
|
||||||
b: 1.9677,
|
b: 1.9677,
|
||||||
c: 42.143,
|
c: 42.143,
|
||||||
|
|
@ -157,7 +158,7 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
||||||
{
|
{
|
||||||
name: "Corn (Chung-Pfost)",
|
name: "Corn (Chung-Pfost)",
|
||||||
group: "Corn",
|
group: "Corn",
|
||||||
equation: pond.MoistureEquation.MOISTURE_EQUATION_CHUNG_PFOST,
|
equation: Equation.chungPfost,
|
||||||
a: 374.34,
|
a: 374.34,
|
||||||
b: 0.18662,
|
b: 0.18662,
|
||||||
c: 31.696,
|
c: 31.696,
|
||||||
|
|
@ -174,7 +175,7 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
||||||
{
|
{
|
||||||
name: "Corn (Oswin)",
|
name: "Corn (Oswin)",
|
||||||
group: "Corn",
|
group: "Corn",
|
||||||
equation: pond.MoistureEquation.MOISTURE_EQUATION_OSWIN,
|
equation: Equation.oswin,
|
||||||
a: 15.303,
|
a: 15.303,
|
||||||
b: -0.10164,
|
b: -0.10164,
|
||||||
c: 3.0358,
|
c: 3.0358,
|
||||||
|
|
@ -191,7 +192,7 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
||||||
{
|
{
|
||||||
name: "Maize White",
|
name: "Maize White",
|
||||||
group: "Corn",
|
group: "Corn",
|
||||||
equation: pond.MoistureEquation.MOISTURE_EQUATION_HENDERSON,
|
equation: Equation.henderson,
|
||||||
a: 0.000066612,
|
a: 0.000066612,
|
||||||
b: 1.9677,
|
b: 1.9677,
|
||||||
c: 70.143,
|
c: 70.143,
|
||||||
|
|
@ -208,7 +209,7 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
||||||
{
|
{
|
||||||
name: "Maize Yellow",
|
name: "Maize Yellow",
|
||||||
group: "Corn",
|
group: "Corn",
|
||||||
equation: pond.MoistureEquation.MOISTURE_EQUATION_HENDERSON,
|
equation: Equation.henderson,
|
||||||
a: 0.000066612,
|
a: 0.000066612,
|
||||||
b: 1.9677,
|
b: 1.9677,
|
||||||
c: 65.143,
|
c: 65.143,
|
||||||
|
|
@ -225,7 +226,7 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
||||||
{
|
{
|
||||||
name: "Oats (Henderson)",
|
name: "Oats (Henderson)",
|
||||||
group: "Oats",
|
group: "Oats",
|
||||||
equation: pond.MoistureEquation.MOISTURE_EQUATION_HENDERSON,
|
equation: Equation.henderson,
|
||||||
a: 0.000085511,
|
a: 0.000085511,
|
||||||
b: 2.0087,
|
b: 2.0087,
|
||||||
c: 37.811,
|
c: 37.811,
|
||||||
|
|
@ -242,7 +243,7 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
||||||
{
|
{
|
||||||
name: "Oats (Chung-Pfost)",
|
name: "Oats (Chung-Pfost)",
|
||||||
group: "Oats",
|
group: "Oats",
|
||||||
equation: pond.MoistureEquation.MOISTURE_EQUATION_CHUNG_PFOST,
|
equation: Equation.chungPfost,
|
||||||
a: 442.85,
|
a: 442.85,
|
||||||
b: 0.21228,
|
b: 0.21228,
|
||||||
c: 35.803,
|
c: 35.803,
|
||||||
|
|
@ -259,7 +260,7 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
||||||
{
|
{
|
||||||
name: "Oats (Oswin)",
|
name: "Oats (Oswin)",
|
||||||
group: "Oats",
|
group: "Oats",
|
||||||
equation: pond.MoistureEquation.MOISTURE_EQUATION_OSWIN,
|
equation: Equation.oswin,
|
||||||
a: 12.412,
|
a: 12.412,
|
||||||
b: -0.060707,
|
b: -0.060707,
|
||||||
c: 2.9397,
|
c: 2.9397,
|
||||||
|
|
@ -276,7 +277,7 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
||||||
{
|
{
|
||||||
name: "Peanuts",
|
name: "Peanuts",
|
||||||
group: "Peanuts",
|
group: "Peanuts",
|
||||||
equation: pond.MoistureEquation.MOISTURE_EQUATION_OSWIN,
|
equation: Equation.oswin,
|
||||||
a: 8.6588,
|
a: 8.6588,
|
||||||
b: -0.057904,
|
b: -0.057904,
|
||||||
c: 2.6204,
|
c: 2.6204,
|
||||||
|
|
@ -293,7 +294,7 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
||||||
{
|
{
|
||||||
name: "Long Grain Rice",
|
name: "Long Grain Rice",
|
||||||
group: "Rice",
|
group: "Rice",
|
||||||
equation: pond.MoistureEquation.MOISTURE_EQUATION_HENDERSON,
|
equation: Equation.henderson,
|
||||||
a: 0.000041276,
|
a: 0.000041276,
|
||||||
b: 2.1191,
|
b: 2.1191,
|
||||||
c: 49.828,
|
c: 49.828,
|
||||||
|
|
@ -310,7 +311,7 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
||||||
{
|
{
|
||||||
name: "Medium Grain Rice",
|
name: "Medium Grain Rice",
|
||||||
group: "Rice",
|
group: "Rice",
|
||||||
equation: pond.MoistureEquation.MOISTURE_EQUATION_HENDERSON,
|
equation: Equation.henderson,
|
||||||
a: 0.000035502,
|
a: 0.000035502,
|
||||||
b: 2.31,
|
b: 2.31,
|
||||||
c: 27.396,
|
c: 27.396,
|
||||||
|
|
@ -327,7 +328,7 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
||||||
{
|
{
|
||||||
name: "Short Grain Rice",
|
name: "Short Grain Rice",
|
||||||
group: "Rice",
|
group: "Rice",
|
||||||
equation: pond.MoistureEquation.MOISTURE_EQUATION_HENDERSON,
|
equation: Equation.henderson,
|
||||||
a: 0.000048524,
|
a: 0.000048524,
|
||||||
b: 2.0794,
|
b: 2.0794,
|
||||||
c: 45.646,
|
c: 45.646,
|
||||||
|
|
@ -344,7 +345,7 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
||||||
{
|
{
|
||||||
name: "Sorghum",
|
name: "Sorghum",
|
||||||
group: "Sorghum",
|
group: "Sorghum",
|
||||||
equation: pond.MoistureEquation.MOISTURE_EQUATION_CHUNG_PFOST,
|
equation: Equation.chungPfost,
|
||||||
a: 797.33,
|
a: 797.33,
|
||||||
b: 0.18159,
|
b: 0.18159,
|
||||||
c: 52.238,
|
c: 52.238,
|
||||||
|
|
@ -361,8 +362,7 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
||||||
{
|
{
|
||||||
name: "Soybeans",
|
name: "Soybeans",
|
||||||
group: "Soybeans",
|
group: "Soybeans",
|
||||||
//equation: Equation.chungPfost,
|
equation: Equation.chungPfost,
|
||||||
equation: pond.MoistureEquation.MOISTURE_EQUATION_CHUNG_PFOST,
|
|
||||||
a: 228.2,
|
a: 228.2,
|
||||||
b: 0.2072,
|
b: 0.2072,
|
||||||
c: 30,
|
c: 30,
|
||||||
|
|
@ -379,7 +379,7 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
||||||
{
|
{
|
||||||
name: "Sunflower",
|
name: "Sunflower",
|
||||||
group: "Sunflower",
|
group: "Sunflower",
|
||||||
equation: pond.MoistureEquation.MOISTURE_EQUATION_HENDERSON,
|
equation: Equation.henderson,
|
||||||
a: 0.00031,
|
a: 0.00031,
|
||||||
b: 1.7459,
|
b: 1.7459,
|
||||||
c: 66.603,
|
c: 66.603,
|
||||||
|
|
@ -396,7 +396,7 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
||||||
{
|
{
|
||||||
name: "Durum Wheat",
|
name: "Durum Wheat",
|
||||||
group: "Wheat",
|
group: "Wheat",
|
||||||
equation: pond.MoistureEquation.MOISTURE_EQUATION_OSWIN,
|
equation: Equation.oswin,
|
||||||
a: 13.101,
|
a: 13.101,
|
||||||
b: -0.052626,
|
b: -0.052626,
|
||||||
c: 2.9987,
|
c: 2.9987,
|
||||||
|
|
@ -413,8 +413,7 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
||||||
{
|
{
|
||||||
name: "Hard Red Wheat",
|
name: "Hard Red Wheat",
|
||||||
group: "Wheat",
|
group: "Wheat",
|
||||||
//equation: Equation.chungPfost,
|
equation: Equation.chungPfost,
|
||||||
equation: pond.MoistureEquation.MOISTURE_EQUATION_CHUNG_PFOST,
|
|
||||||
a: 610.34,
|
a: 610.34,
|
||||||
b: 0.15526,
|
b: 0.15526,
|
||||||
c: 93.213,
|
c: 93.213,
|
||||||
|
|
@ -431,8 +430,7 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
||||||
{
|
{
|
||||||
name: "Wheat SAWOS",
|
name: "Wheat SAWOS",
|
||||||
group: "Wheat",
|
group: "Wheat",
|
||||||
//equation: Equation.chungPfost,
|
equation: Equation.chungPfost,
|
||||||
equation: pond.MoistureEquation.MOISTURE_EQUATION_CHUNG_PFOST,
|
|
||||||
a: 610.34,
|
a: 610.34,
|
||||||
b: 0.15526,
|
b: 0.15526,
|
||||||
c: 93.213,
|
c: 93.213,
|
||||||
|
|
@ -449,7 +447,7 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
||||||
{
|
{
|
||||||
name: "Un-retted Flax",
|
name: "Un-retted Flax",
|
||||||
group: "Flax",
|
group: "Flax",
|
||||||
equation: pond.MoistureEquation.MOISTURE_EQUATION_HALSEY,
|
equation: Equation.halsey,
|
||||||
a: 5.11,
|
a: 5.11,
|
||||||
b: Math.pow(-8.46 * 10, -3),
|
b: Math.pow(-8.46 * 10, -3),
|
||||||
c: 2.26,
|
c: 2.26,
|
||||||
|
|
@ -466,7 +464,7 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
||||||
{
|
{
|
||||||
name: "Dew-retted Flax",
|
name: "Dew-retted Flax",
|
||||||
group: "Flax",
|
group: "Flax",
|
||||||
equation: pond.MoistureEquation.MOISTURE_EQUATION_OSWIN,
|
equation: Equation.oswin,
|
||||||
a: 6.5,
|
a: 6.5,
|
||||||
b: Math.pow(-1.68 * 10, -2),
|
b: Math.pow(-1.68 * 10, -2),
|
||||||
c: 3.2,
|
c: 3.2,
|
||||||
|
|
@ -483,7 +481,7 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
||||||
{
|
{
|
||||||
name: "Yellow Peas",
|
name: "Yellow Peas",
|
||||||
group: "Peas",
|
group: "Peas",
|
||||||
equation: pond.MoistureEquation.MOISTURE_EQUATION_OSWIN,
|
equation: Equation.oswin,
|
||||||
a: 14.81,
|
a: 14.81,
|
||||||
b: -0.109,
|
b: -0.109,
|
||||||
c: 3.019,
|
c: 3.019,
|
||||||
|
|
@ -500,7 +498,7 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
||||||
{
|
{
|
||||||
name: "Blaze Lentils",
|
name: "Blaze Lentils",
|
||||||
group: "Lentils",
|
group: "Lentils",
|
||||||
equation: pond.MoistureEquation.MOISTURE_EQUATION_HALSEY,
|
equation: Equation.halsey,
|
||||||
a: 5.39,
|
a: 5.39,
|
||||||
b: -0.015,
|
b: -0.015,
|
||||||
c: 2.273,
|
c: 2.273,
|
||||||
|
|
@ -517,7 +515,7 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
||||||
{
|
{
|
||||||
name: "Redberry Lentils",
|
name: "Redberry Lentils",
|
||||||
group: "Lentils",
|
group: "Lentils",
|
||||||
equation: pond.MoistureEquation.MOISTURE_EQUATION_HALSEY,
|
equation: Equation.halsey,
|
||||||
a: 4.749,
|
a: 4.749,
|
||||||
b: -0.0116,
|
b: -0.0116,
|
||||||
c: 2.066,
|
c: 2.066,
|
||||||
|
|
@ -534,7 +532,7 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
||||||
{
|
{
|
||||||
name: "Robin Lentils",
|
name: "Robin Lentils",
|
||||||
group: "Lentils",
|
group: "Lentils",
|
||||||
equation: pond.MoistureEquation.MOISTURE_EQUATION_HALSEY,
|
equation: Equation.halsey,
|
||||||
a: 5.176,
|
a: 5.176,
|
||||||
b: -0.0065,
|
b: -0.0065,
|
||||||
c: 2.337,
|
c: 2.337,
|
||||||
|
|
@ -551,7 +549,7 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
||||||
{
|
{
|
||||||
name: "Dry Beans Red",
|
name: "Dry Beans Red",
|
||||||
group: "Dry Beans",
|
group: "Dry Beans",
|
||||||
equation: pond.MoistureEquation.MOISTURE_EQUATION_HALSEY,
|
equation: Equation.halsey,
|
||||||
a: 4.2669,
|
a: 4.2669,
|
||||||
b: -0.013382,
|
b: -0.013382,
|
||||||
c: 1.6933,
|
c: 1.6933,
|
||||||
|
|
@ -566,7 +564,7 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
||||||
{
|
{
|
||||||
name: "Dry Beans Black",
|
name: "Dry Beans Black",
|
||||||
group: "Dry Beans",
|
group: "Dry Beans",
|
||||||
equation: pond.MoistureEquation.MOISTURE_EQUATION_HALSEY,
|
equation: Equation.halsey,
|
||||||
a: 5.2003,
|
a: 5.2003,
|
||||||
b: -0.022685,
|
b: -0.022685,
|
||||||
c: 1.9656,
|
c: 1.9656,
|
||||||
|
|
@ -581,7 +579,7 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
||||||
{
|
{
|
||||||
name: "Rye (Henderson)",
|
name: "Rye (Henderson)",
|
||||||
group: "Rye",
|
group: "Rye",
|
||||||
equation: pond.MoistureEquation.MOISTURE_EQUATION_HENDERSON,
|
equation: Equation.henderson,
|
||||||
a: 0.00006343,
|
a: 0.00006343,
|
||||||
b: 2.2060,
|
b: 2.2060,
|
||||||
c: 13.1810,
|
c: 13.1810,
|
||||||
|
|
@ -596,8 +594,7 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
||||||
{
|
{
|
||||||
name: "Rye (Chung-Pfost)",
|
name: "Rye (Chung-Pfost)",
|
||||||
group: "Rye",
|
group: "Rye",
|
||||||
//equation: Equation.chungPfost,
|
equation: Equation.chungPfost,
|
||||||
equation: pond.MoistureEquation.MOISTURE_EQUATION_CHUNG_PFOST,
|
|
||||||
a: 461.0230,
|
a: 461.0230,
|
||||||
b: 0.1840,
|
b: 0.1840,
|
||||||
c: 36.7410,
|
c: 36.7410,
|
||||||
|
|
@ -612,7 +609,7 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
||||||
{
|
{
|
||||||
name: "Rye (Halsey)",
|
name: "Rye (Halsey)",
|
||||||
group: "Rye",
|
group: "Rye",
|
||||||
equation: pond.MoistureEquation.MOISTURE_EQUATION_HALSEY,
|
equation: Equation.halsey,
|
||||||
a: 4.2970,
|
a: 4.2970,
|
||||||
b: 0.380,
|
b: 0.380,
|
||||||
c: 2.2710,
|
c: 2.2710,
|
||||||
|
|
@ -627,7 +624,7 @@ export const GrainExtensions: Map<pond.Grain, GrainExtension> = new Map([
|
||||||
{
|
{
|
||||||
name: "Rye (Oswin)",
|
name: "Rye (Oswin)",
|
||||||
group: "Rye",
|
group: "Rye",
|
||||||
equation: pond.MoistureEquation.MOISTURE_EQUATION_OSWIN,
|
equation: Equation.oswin,
|
||||||
a: 11.8870,
|
a: 11.8870,
|
||||||
b: 0.0210,
|
b: 0.0210,
|
||||||
c: 3.2620,
|
c: 3.2620,
|
||||||
|
|
@ -644,6 +641,16 @@ export function GetGrainExtensionMap(): Map<pond.Grain, GrainExtension> {
|
||||||
return cloneDeep(GrainExtensions);
|
return cloneDeep(GrainExtensions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function GetValidGrainExtensions(): GrainExtension[] {
|
||||||
|
let validGrains: GrainExtension[] = []
|
||||||
|
GrainExtensions.forEach((ext, key) => {
|
||||||
|
if(key !== pond.Grain.GRAIN_NONE && key !== pond.Grain.GRAIN_INVALID && key !== pond.Grain.GRAIN_CUSTOM){
|
||||||
|
validGrains.push(ext)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return validGrains
|
||||||
|
}
|
||||||
|
|
||||||
export default function GrainDescriber(type: pond.Grain): GrainExtension {
|
export default function GrainDescriber(type: pond.Grain): GrainExtension {
|
||||||
let describer = GrainExtensions.get(type);
|
let describer = GrainExtensions.get(type);
|
||||||
//console.log(describer)
|
//console.log(describer)
|
||||||
|
|
|
||||||
|
|
@ -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 => {
|
||||||
setPlanTasks(resp.data.tasks.map(t => Task.any(t)));
|
if(resp.data.tasks){
|
||||||
|
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 {
|
||||||
|
|
|
||||||
|
|
@ -176,12 +176,23 @@ export class Device {
|
||||||
case pond.DevicePlatform.DEVICE_PLATFORM_V2_WIFI_BLUE:
|
case pond.DevicePlatform.DEVICE_PLATFORM_V2_WIFI_BLUE:
|
||||||
case pond.DevicePlatform.DEVICE_PLATFORM_V2_CELLULAR_BLUE:
|
case pond.DevicePlatform.DEVICE_PLATFORM_V2_CELLULAR_BLUE:
|
||||||
case pond.DevicePlatform.DEVICE_PLATFORM_V2_ETHERNET_BLUE:
|
case pond.DevicePlatform.DEVICE_PLATFORM_V2_ETHERNET_BLUE:
|
||||||
max = this.status.firmwareVersion >= "2.1.9" ? 4 : 2;
|
max = this.versionComparison(this.status.firmwareVersion, "2.1.9") ? 4 : 2;
|
||||||
}
|
}
|
||||||
return max
|
return max
|
||||||
}
|
}
|
||||||
|
|
||||||
private versionComparison(deviceVersion: string, requiredVersion: string): boolean {
|
private versionComparison(deviceVersion: string, requiredVersion: string): boolean {
|
||||||
|
// Feature explicitly not supported on this platform
|
||||||
|
if (!requiredVersion || requiredVersion === "N/A") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Device firmware missing / unset
|
||||||
|
if (!deviceVersion || deviceVersion.trim() === "") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
//compare the versions
|
||||||
const parse = (v: string) => {
|
const parse = (v: string) => {
|
||||||
const [core, pre] = v.split("-");
|
const [core, pre] = v.split("-");
|
||||||
const parts = core.split(".").map(n => parseInt(n, 10));
|
const parts = core.split(".").map(n => parseInt(n, 10));
|
||||||
|
|
|
||||||
|
|
@ -5,22 +5,24 @@ import { or } from "utils/types";
|
||||||
|
|
||||||
export class Gate {
|
export class Gate {
|
||||||
public settings: pond.GateSettings = pond.GateSettings.create();
|
public settings: pond.GateSettings = pond.GateSettings.create();
|
||||||
|
public status: pond.GateStatus = pond.GateStatus.create();
|
||||||
public name: string = "Gate";
|
public name: string = "Gate";
|
||||||
public key: string = "";
|
public key: string = "";
|
||||||
public preferences: any = {};
|
public preferences: any = {};
|
||||||
public gateMutations: any = {};
|
public gateMutations: any = {};
|
||||||
public pcaState: pond.PCAState = pond.PCAState.PCA_STATE_UNKNOWN;
|
// public pcaState: pond.PCAState = pond.PCAState.PCA_STATE_UNKNOWN;
|
||||||
|
|
||||||
public static create(pb?: pond.Gate): Gate {
|
public static create(pb?: pond.Gate): Gate {
|
||||||
let my = new Gate();
|
let my = new Gate();
|
||||||
if (pb) {
|
if (pb) {
|
||||||
let g = pond.Gate.fromObject(pb);
|
let g = pond.Gate.fromObject(pb);
|
||||||
my.settings = pond.GateSettings.fromObject(cloneDeep(or(g.settings, {})));
|
my.settings = pond.GateSettings.fromObject(cloneDeep(or(g.settings, {})));
|
||||||
|
my.status = pond.GateStatus.fromObject(cloneDeep(or(g.status, {})));
|
||||||
my.name = g.name;
|
my.name = g.name;
|
||||||
my.key = g.key;
|
my.key = g.key;
|
||||||
my.preferences = g.componentPreferences;
|
my.preferences = g.componentPreferences;
|
||||||
my.gateMutations = g.gateMutations;
|
my.gateMutations = g.gateMutations;
|
||||||
my.pcaState = g.pcaState;
|
// my.pcaState = g.pcaState;
|
||||||
}
|
}
|
||||||
return my;
|
return my;
|
||||||
}
|
}
|
||||||
|
|
@ -77,7 +79,7 @@ export class Gate {
|
||||||
}
|
}
|
||||||
|
|
||||||
public gateMarkerColour(): string {
|
public gateMarkerColour(): string {
|
||||||
switch (this.pcaState) {
|
switch (this.status.pcaState) {
|
||||||
case pond.PCAState.PCA_STATE_OFF:
|
case pond.PCAState.PCA_STATE_OFF:
|
||||||
return "grey";
|
return "grey";
|
||||||
case pond.PCAState.PCA_STATE_IN_BOUNDS:
|
case pond.PCAState.PCA_STATE_IN_BOUNDS:
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ import BinsIcon from "products/Bindapt/BinsIcon";
|
||||||
import { useGlobalState } from "providers";
|
import { useGlobalState } from "providers";
|
||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
import { useNavigate, useLocation } from "react-router-dom";
|
import { useNavigate, useLocation } from "react-router-dom";
|
||||||
import { IsAdaptiveAgriculture, isBXT, IsMiVent, IsAdCon, IsOmniAir } from "services/whiteLabel";
|
import { IsAdaptiveAgriculture, isBXT, IsMiVent, IsAdCon, IsOmniAir, IsMiPCA } from "services/whiteLabel";
|
||||||
import FieldsIcon from "products/AgIcons/FieldsIcon";
|
import FieldsIcon from "products/AgIcons/FieldsIcon";
|
||||||
import NexusSTIcon from "products/Construction/NexusSTIcon";
|
import NexusSTIcon from "products/Construction/NexusSTIcon";
|
||||||
import OmniAirDeviceIcon from "products/AviationIcons/OmniAirDeviceIcon";
|
import OmniAirDeviceIcon from "products/AviationIcons/OmniAirDeviceIcon";
|
||||||
|
|
@ -40,6 +40,7 @@ export default function BottomNavigator(props: Props) {
|
||||||
const isMiVent = IsMiVent();
|
const isMiVent = IsMiVent();
|
||||||
const isAdCon = IsAdCon();
|
const isAdCon = IsAdCon();
|
||||||
const isOmni = IsOmniAir();
|
const isOmni = IsOmniAir();
|
||||||
|
const isMiPCA = IsMiPCA();
|
||||||
|
|
||||||
const reRoute = useCallback(
|
const reRoute = useCallback(
|
||||||
(path: string) => {
|
(path: string) => {
|
||||||
|
|
@ -117,14 +118,14 @@ export default function BottomNavigator(props: Props) {
|
||||||
value="constructionMap"
|
value="constructionMap"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{isOmni && (
|
{(isOmni || isMiPCA) && (
|
||||||
<BottomNavigationAction
|
<BottomNavigationAction
|
||||||
label="Map"
|
label="Map"
|
||||||
icon={<AirportMapIcon type={getType()} />}
|
icon={<AirportMapIcon type={getType()} />}
|
||||||
value="aviationMap"
|
value="aviationMap"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{isOmni && (
|
{(isOmni || isMiPCA) && (
|
||||||
<BottomNavigationAction
|
<BottomNavigationAction
|
||||||
label="Terminals"
|
label="Terminals"
|
||||||
icon={<PlaneIcon type={getType()} />}
|
icon={<PlaneIcon type={getType()} />}
|
||||||
|
|
@ -139,7 +140,7 @@ export default function BottomNavigator(props: Props) {
|
||||||
<BindaptIcon type={getType()} />
|
<BindaptIcon type={getType()} />
|
||||||
) : isAdCon ? (
|
) : isAdCon ? (
|
||||||
<NexusSTIcon type={getType()} />
|
<NexusSTIcon type={getType()} />
|
||||||
) : isOmni ? (
|
) : (isOmni || isMiPCA) ? (
|
||||||
<OmniAirDeviceIcon type={getType()} />
|
<OmniAirDeviceIcon type={getType()} />
|
||||||
) : (
|
) : (
|
||||||
<DevicesIcon />
|
<DevicesIcon />
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,7 @@ const JohnDeere = lazy(() => import("pages/JohnDeere"));
|
||||||
const CNHi = lazy(() => import("pages/CNHi"));
|
const CNHi = lazy(() => import("pages/CNHi"));
|
||||||
const LibraCart = lazy(() => import("pages/LibraCart"));
|
const LibraCart = lazy(() => import("pages/LibraCart"));
|
||||||
const FieldPage = lazy(()=>import("pages/Field"));
|
const FieldPage = lazy(()=>import("pages/Field"));
|
||||||
|
const GrainsPage = lazy(()=>import("pages/Grains"));
|
||||||
|
|
||||||
export const appendToUrl = (appendage: number | string) => {
|
export const appendToUrl = (appendage: number | string) => {
|
||||||
const basePath = location.pathname.replace(/\/$/, "");
|
const basePath = location.pathname.replace(/\/$/, "");
|
||||||
|
|
@ -360,6 +361,7 @@ export default function Router() {
|
||||||
{user.hasFeature("libra-cart") &&
|
{user.hasFeature("libra-cart") &&
|
||||||
<Route path="libracart" element={<LibraCart />} />
|
<Route path="libracart" element={<LibraCart />} />
|
||||||
}
|
}
|
||||||
|
<Route path="grains" element={<GrainsPage />} />
|
||||||
{/* Map routes */}
|
{/* Map routes */}
|
||||||
<Route path="visualFarm" element={<FieldMap />} />
|
<Route path="visualFarm" element={<FieldMap />} />
|
||||||
<Route path="aviationMap" element={<AviationMap />} />
|
<Route path="aviationMap" element={<AviationMap />} />
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { ChevronRight, Code, Memory, People, Person, SyncAlt, TapAndPlay } from "@mui/icons-material";
|
import { ChevronRight, Code, Grain, Memory, People, Person, SyncAlt, TapAndPlay } from "@mui/icons-material";
|
||||||
import ChevronLeft from "@mui/icons-material/ChevronLeft";
|
import ChevronLeft from "@mui/icons-material/ChevronLeft";
|
||||||
import {
|
import {
|
||||||
darken,
|
darken,
|
||||||
|
|
@ -27,6 +27,7 @@ import {
|
||||||
IsAdaptiveAgriculture,
|
IsAdaptiveAgriculture,
|
||||||
// hasTutorialPlaylist,
|
// hasTutorialPlaylist,
|
||||||
IsAdCon,
|
IsAdCon,
|
||||||
|
IsMiPCA,
|
||||||
// isBXT,
|
// isBXT,
|
||||||
IsMiVent,
|
IsMiVent,
|
||||||
IsOmniAir,
|
IsOmniAir,
|
||||||
|
|
@ -166,7 +167,8 @@ export default function SideNavigator(props: Props) {
|
||||||
const isMiVent = IsMiVent();
|
const isMiVent = IsMiVent();
|
||||||
const isAg = IsAdaptiveAgriculture()
|
const isAg = IsAdaptiveAgriculture()
|
||||||
const isStreamline = IsStreamline()
|
const isStreamline = IsStreamline()
|
||||||
const isMiPCA = IsOmniAir()
|
const isOmni = IsOmniAir()
|
||||||
|
const isMiPCA = IsMiPCA()
|
||||||
const isAdCon = IsAdCon()
|
const isAdCon = IsAdCon()
|
||||||
return (
|
return (
|
||||||
<List className={classes.list} component="nav">
|
<List className={classes.list} component="nav">
|
||||||
|
|
@ -184,7 +186,7 @@ export default function SideNavigator(props: Props) {
|
||||||
</ListItemButton>
|
</ListItemButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
{(isMiPCA || user.hasFeature("admin")) && (
|
{((isOmni || isMiPCA) || user.hasFeature("admin")) && (
|
||||||
<Tooltip title="Aviation Map" placement="right">
|
<Tooltip title="Aviation Map" placement="right">
|
||||||
<ListItemButton
|
<ListItemButton
|
||||||
id="tour-aviation-map"
|
id="tour-aviation-map"
|
||||||
|
|
@ -240,7 +242,7 @@ export default function SideNavigator(props: Props) {
|
||||||
</ListItemButton>
|
</ListItemButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
{(isMiPCA || user.hasFeature("admin")) && (
|
{((isOmni || isMiPCA) || user.hasFeature("admin")) && (
|
||||||
<Tooltip title="Terminals" placement="right">
|
<Tooltip title="Terminals" placement="right">
|
||||||
<ListItemButton
|
<ListItemButton
|
||||||
id="tour-terminals"
|
id="tour-terminals"
|
||||||
|
|
@ -374,6 +376,20 @@ export default function SideNavigator(props: Props) {
|
||||||
</ListItemButton>
|
</ListItemButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
|
{(isAg || isStreamline || user.hasFeature("admin")) && (
|
||||||
|
<Tooltip title="Grain Types" placement="right">
|
||||||
|
<ListItemButton
|
||||||
|
id="tour-grain-types"
|
||||||
|
onClick={() => goTo("/grains")}
|
||||||
|
classes={getClasses("/grains")}
|
||||||
|
>
|
||||||
|
<ListItemIcon>
|
||||||
|
<Grain />
|
||||||
|
</ListItemIcon>
|
||||||
|
{open && <ListItemText primary="Grain Types" />}
|
||||||
|
</ListItemButton>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
|
||||||
{(isAdCon || user.hasFeature("admin")) && (
|
{(isAdCon || user.hasFeature("admin")) && (
|
||||||
<Tooltip title="Jobsites" placement="right">
|
<Tooltip title="Jobsites" placement="right">
|
||||||
|
|
|
||||||
|
|
@ -109,7 +109,7 @@ export default function ObjectHeaterCard(props: Props) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (heaterData.devices[0]) {
|
if (heaterData.devices && heaterData.devices[0]) {
|
||||||
setConnectedDevice(Device.any(heaterData.devices[0].device));
|
setConnectedDevice(Device.any(heaterData.devices[0].device));
|
||||||
heaterData.devices[0].components.forEach(c => {
|
heaterData.devices[0].components.forEach(c => {
|
||||||
//loop through the components and assign them accordingly
|
//loop through the components and assign them accordingly
|
||||||
|
|
@ -171,7 +171,7 @@ export default function ObjectHeaterCard(props: Props) {
|
||||||
|
|
||||||
const goToHeater = () => {
|
const goToHeater = () => {
|
||||||
//history.push("/objectHeaters/" + heater.key);
|
//history.push("/objectHeaters/" + heater.key);
|
||||||
let path = "/objectHeaters/" + heater.key;
|
let path = "/heaters/" + heater.key;
|
||||||
navigate(path, { state: {heater: heater} });
|
navigate(path, { state: {heater: heater} });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -179,7 +179,6 @@ export default function NewObjectInteraction(props: Props){
|
||||||
setNewAlertComponentType(quack.ComponentType.COMPONENT_TYPE_INVALID);
|
setNewAlertComponentType(quack.ComponentType.COMPONENT_TYPE_INVALID);
|
||||||
setConditions([]);
|
setConditions([]);
|
||||||
setSelectedAlertComponents([]);
|
setSelectedAlertComponents([]);
|
||||||
setValidOptions([])
|
|
||||||
setValidComponents([])
|
setValidComponents([])
|
||||||
setValStrings(["0", "0"]);
|
setValStrings(["0", "0"]);
|
||||||
setNumConditions(0)
|
setNumConditions(0)
|
||||||
|
|
|
||||||
|
|
@ -40,16 +40,18 @@ 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();
|
||||||
resp.data.contracts.forEach(contract => {
|
if (resp.data.contracts){
|
||||||
let c = Contract.create(contract);
|
resp.data.contracts.forEach(contract => {
|
||||||
contracts.push(c);
|
let c = Contract.create(contract);
|
||||||
let p = pond.EvaluatePermissionsResponse.fromObject(
|
contracts.push(c);
|
||||||
resp.data.contractPermissions[c.key()]
|
let p = pond.EvaluatePermissionsResponse.fromObject(
|
||||||
);
|
resp.data.contractPermissions[c.key()]
|
||||||
contractPermissions.set(c.key(), p.permissions);
|
);
|
||||||
});
|
contractPermissions.set(c.key(), p.permissions);
|
||||||
setContracts(contracts);
|
});
|
||||||
setContractPermissions(contractPermissions);
|
setContracts(contracts);
|
||||||
|
setContractPermissions(contractPermissions);
|
||||||
|
}
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
})
|
})
|
||||||
.catch(err => {});
|
.catch(err => {});
|
||||||
|
|
|
||||||
|
|
@ -128,10 +128,9 @@ export default function DevicePage() {
|
||||||
interaction.settings.nodeOne > interaction.settings.nodeTwo &&
|
interaction.settings.nodeOne > interaction.settings.nodeTwo &&
|
||||||
interaction.settings.nodeTwo !== 0
|
interaction.settings.nodeTwo !== 0
|
||||||
) {
|
) {
|
||||||
//flip operator and send negative comparitor to save
|
//flip operator to display what is actually happening
|
||||||
interaction.settings.conditions.forEach(condition => {
|
interaction.settings.conditions.forEach(condition => {
|
||||||
//coming back from the backend as a string for some reason
|
if (condition.comparison === quack.RelationalOperator.RELATIONAL_OPERATOR_GREATER_THAN) {
|
||||||
if (condition.comparison.toString() === "RELATIONAL_OPERATOR_GREATER_THAN") {
|
|
||||||
condition.comparison = quack.RelationalOperator.RELATIONAL_OPERATOR_LESS_THAN;
|
condition.comparison = quack.RelationalOperator.RELATIONAL_OPERATOR_LESS_THAN;
|
||||||
} else {
|
} else {
|
||||||
condition.comparison = quack.RelationalOperator.RELATIONAL_OPERATOR_GREATER_THAN;
|
condition.comparison = quack.RelationalOperator.RELATIONAL_OPERATOR_GREATER_THAN;
|
||||||
|
|
|
||||||
|
|
@ -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"]}
|
//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,16 +342,14 @@ 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 }} />
|
||||||
|
<Typography paddingLeft={2} style={{ fontSize: 25, fontWeight: 650 }}>
|
||||||
|
Connect Device
|
||||||
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid>
|
|
||||||
<Typography style={{ fontSize: 25, fontWeight: 650 }}>
|
|
||||||
Connect Device
|
|
||||||
</Typography>
|
|
||||||
</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
|
||||||
use the "Link Device" icon on the top right side of this page
|
use the "Link Device" icon on the top right side of this page
|
||||||
|
|
|
||||||
474
src/pages/Grains.tsx
Normal file
|
|
@ -0,0 +1,474 @@
|
||||||
|
import { useCallback, useEffect, useState } from "react";
|
||||||
|
import PageContainer from "./PageContainer";
|
||||||
|
import CustomGrainForm from "grain/CustomGrainForm";
|
||||||
|
import { Accordion, AccordionDetails, AccordionSummary, Box, Button, DialogActions, DialogContent, DialogTitle, Divider, Grid2, IconButton, Typography } from "@mui/material";
|
||||||
|
import { pond } from "protobuf-ts/pond";
|
||||||
|
import ButtonGroup from "common/ButtonGroup";
|
||||||
|
import { useMobile, useSnackbar, useUserAPI } from "hooks";
|
||||||
|
import { useGrainAPI } from "providers/pond/grainAPI";
|
||||||
|
import ResponsiveTable from "common/ResponsiveTable";
|
||||||
|
import { GetGrainExtensionMap, GetValidGrainExtensions, GrainExtension } from "grain";
|
||||||
|
import ResponsiveDialog from "common/ResponsiveDialog";
|
||||||
|
import { useGlobalState } from "providers";
|
||||||
|
import { Scope } from "models";
|
||||||
|
import React from "react";
|
||||||
|
import { AddCircle, ExpandMore } from "@mui/icons-material";
|
||||||
|
|
||||||
|
export default function Grains() {
|
||||||
|
const [currentCustomGrain, setCurrentCustomGrain] = useState<pond.GrainSettings>()
|
||||||
|
const [formValid, setFormValid] = useState(false)
|
||||||
|
const [selectedGrain, setSelectedGrain] = useState<pond.GrainObject | undefined>()
|
||||||
|
const [displayCustom, setDisplayCustom] = useState(false)
|
||||||
|
const { openSnack } = useSnackbar()
|
||||||
|
const [tableSize, setTableSize] = useState(10)
|
||||||
|
const [tablePage, setTablePage] = useState(0)
|
||||||
|
const [tableTotal, setTableTotal] = useState(0)
|
||||||
|
const grainAPI = useGrainAPI()
|
||||||
|
const userAPI = useUserAPI()
|
||||||
|
const [{as, user}] = useGlobalState();
|
||||||
|
const [permissions, setPermissions] = useState<pond.Permission[]>([])
|
||||||
|
const [openRemoveConfirmation, setOpenRemoveConfirmation] = useState(false)
|
||||||
|
const [openUpdateConfirmation, setOpenUpdateConfirmation] = useState(false)
|
||||||
|
const [grainObjectTableData, setGrainObjectTableData] = useState<pond.GrainObject[]>([])
|
||||||
|
const [supportedGrainTableData, setSupportedGrainTableData] = useState<GrainExtension[]>([])
|
||||||
|
// const [selectedGrainExtension, setSelectedGrainExtension] = useState<GrainExtension | undefined>()
|
||||||
|
const [accordionKey, setAccordionKey] = useState("allClosed")
|
||||||
|
const [openNewGrainDialog, setOpenNewGrainDialog] = useState(false)
|
||||||
|
const isMobile = useMobile()
|
||||||
|
const extraLoad = 10 //the amount of more to load on the mobile view when load more is clicked
|
||||||
|
|
||||||
|
//function to load the first set of custom grains
|
||||||
|
const loadCustomGrain = useCallback(() => {
|
||||||
|
grainAPI.listGrains(tableSize, tablePage * tableSize, "asc", "name").then(resp => {
|
||||||
|
setGrainObjectTableData(resp.data.grains)
|
||||||
|
setTableTotal(resp.data.total)
|
||||||
|
}).catch(err => {
|
||||||
|
openSnack("There was an issue loading your custom grains")
|
||||||
|
})
|
||||||
|
},[tableSize, tablePage])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
//load the custom grains
|
||||||
|
if(displayCustom){
|
||||||
|
loadCustomGrain()
|
||||||
|
}else{
|
||||||
|
let displayedGrains: GrainExtension[] = []
|
||||||
|
let offset = tablePage * tableSize
|
||||||
|
let limit = tableSize
|
||||||
|
let grainList = GetValidGrainExtensions()
|
||||||
|
let sortedGrain = grainList.sort((a, b) => (a.name.toLowerCase() > b.name.toLowerCase() ? 1 : -1))
|
||||||
|
sortedGrain.forEach((ext, i) => {
|
||||||
|
if(i >= offset && displayedGrains.length < limit){
|
||||||
|
displayedGrains.push(ext)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
setSupportedGrainTableData(displayedGrains)
|
||||||
|
setTableTotal(sortedGrain.length)
|
||||||
|
}
|
||||||
|
},[loadCustomGrain, tableSize, tablePage, displayCustom, as])
|
||||||
|
|
||||||
|
useEffect(()=>{
|
||||||
|
//if the user is viewing as a team get their permissions to the team, if they are not then we will get their permission to the grain type individually when a type is selected
|
||||||
|
if(as){
|
||||||
|
userAPI.getUser(user.id(), { key: as, kind: "team" } as Scope).then(resp => {
|
||||||
|
setPermissions(resp.permissions);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},[as, userAPI])
|
||||||
|
|
||||||
|
const equationName = (enumVal: number) => {
|
||||||
|
switch(enumVal){
|
||||||
|
case pond.MoistureEquation.MOISTURE_EQUATION_CHUNG_PFOST:
|
||||||
|
return "Chung-Pfost"
|
||||||
|
case pond.MoistureEquation.MOISTURE_EQUATION_HALSEY:
|
||||||
|
return "Halsey"
|
||||||
|
case pond.MoistureEquation.MOISTURE_EQUATION_HENDERSON:
|
||||||
|
return "Henderson"
|
||||||
|
case pond.MoistureEquation.MOISTURE_EQUATION_OSWIN:
|
||||||
|
return "Oswin"
|
||||||
|
default:
|
||||||
|
return "None"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//function to update
|
||||||
|
|
||||||
|
//table that will display the custom grains created by the user
|
||||||
|
const grainObjectTable = () => {
|
||||||
|
return (
|
||||||
|
<ResponsiveTable<pond.GrainObject>
|
||||||
|
rows={grainObjectTableData}
|
||||||
|
noDataMessage="No Grain Types Found. Add a new custom grain to see them here"
|
||||||
|
loadMore={() => {
|
||||||
|
setTableSize(tableSize + extraLoad)
|
||||||
|
}}
|
||||||
|
renderMobile={(row) => <Accordion expanded={accordionKey === row.key} onChange={() => {
|
||||||
|
//close it if it is already open
|
||||||
|
if(accordionKey === row.key){
|
||||||
|
setAccordionKey("allClosed")
|
||||||
|
}else{
|
||||||
|
setAccordionKey(row.key)
|
||||||
|
}
|
||||||
|
setCurrentCustomGrain(row.settings ?? pond.GrainSettings.create())
|
||||||
|
}}>
|
||||||
|
<AccordionSummary expandIcon={<ExpandMore />}>{row.name}</AccordionSummary>
|
||||||
|
<AccordionDetails>
|
||||||
|
{customGrainDisplay()}
|
||||||
|
</AccordionDetails>
|
||||||
|
</Accordion>}
|
||||||
|
onRowClick={(grainObj) => {
|
||||||
|
setSelectedGrain(grainObj)
|
||||||
|
setFormValid(true)
|
||||||
|
/** if they are viewin as a team leave the permissions alone, if not then use the users permissions to the grain type,
|
||||||
|
* for the moment grain types are not shareable so this will be unused, but this will cover our bases if we want to implement sharing in the future
|
||||||
|
* */
|
||||||
|
if (!as) {
|
||||||
|
userAPI.getUser(user.id(), { key: grainObj.key, kind: "grain" } as Scope).then(resp => {
|
||||||
|
setPermissions(resp.permissions);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
columns={[
|
||||||
|
{
|
||||||
|
title: "Name",
|
||||||
|
render: row => <Box padding={2}>
|
||||||
|
<Typography>
|
||||||
|
{row.name}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Group",
|
||||||
|
render: row => <Box padding={2}>
|
||||||
|
<Typography>
|
||||||
|
{row.settings?.group}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Equation",
|
||||||
|
render: row => <Box padding={2}>
|
||||||
|
<Typography>
|
||||||
|
{equationName(row.settings?.equation ?? 0)}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
page={tablePage}
|
||||||
|
pageSize={tableSize}
|
||||||
|
handleRowsPerPageChange={(e) => {
|
||||||
|
setTableSize(e.target.value)
|
||||||
|
}}
|
||||||
|
setPage={(page) => {
|
||||||
|
setTablePage(page)
|
||||||
|
}}
|
||||||
|
total={tableTotal}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
//table that will display the grain extensions provided by us
|
||||||
|
const supportedGrainTable = () => {
|
||||||
|
return (
|
||||||
|
<ResponsiveTable<GrainExtension>
|
||||||
|
rows={supportedGrainTableData}
|
||||||
|
page={tablePage}
|
||||||
|
// onRowClick={(ext) => {setSelectedGrainExtension(ext)}}
|
||||||
|
loadMore={() => {
|
||||||
|
setTableSize(tableSize + extraLoad)
|
||||||
|
}}
|
||||||
|
renderMobile={(row) => <Accordion>
|
||||||
|
<AccordionSummary expandIcon={<ExpandMore />}>{row.name}</AccordionSummary>
|
||||||
|
<AccordionDetails>
|
||||||
|
{supportedDisplay(row)}
|
||||||
|
</AccordionDetails>
|
||||||
|
</Accordion>}
|
||||||
|
pageSize={tableSize}
|
||||||
|
columns={[
|
||||||
|
{
|
||||||
|
title: "Name",
|
||||||
|
render: row => <Box padding={2}>
|
||||||
|
<Typography>
|
||||||
|
{row.name}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Group",
|
||||||
|
render: row => <Box padding={2}>
|
||||||
|
<Typography>
|
||||||
|
{row.group}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Equation",
|
||||||
|
render: row => <Box padding={2}>
|
||||||
|
<Typography>
|
||||||
|
{equationName(row.equation ?? 0)}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Kg Per Bushel",
|
||||||
|
render: row => <Box padding={2}>
|
||||||
|
<Typography>
|
||||||
|
{row.weightConversionKg.toFixed(2)}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Bushels Per Tonne",
|
||||||
|
render: row => <Box padding={2}>
|
||||||
|
<Typography>
|
||||||
|
{row.bushelsPerTonne.toFixed(2)}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
handleRowsPerPageChange={(e) => {
|
||||||
|
setTableSize(e.target.value)
|
||||||
|
}}
|
||||||
|
setPage={(page) => {
|
||||||
|
setTablePage(page)
|
||||||
|
}}
|
||||||
|
total={tableTotal}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
//the grain display for supported grain type to show the rest of the data such as what constants are used in the formula
|
||||||
|
const supportedDisplay = (grain?: GrainExtension) => {
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
{grain ?
|
||||||
|
<React.Fragment>
|
||||||
|
<Typography>
|
||||||
|
Info
|
||||||
|
</Typography>
|
||||||
|
<Divider />
|
||||||
|
<Box padding={2}>
|
||||||
|
<Typography>Name: {grain.name}</Typography>
|
||||||
|
<Typography>Group: {grain.group}</Typography>
|
||||||
|
</Box>
|
||||||
|
<Typography>
|
||||||
|
Formula
|
||||||
|
</Typography>
|
||||||
|
<Divider />
|
||||||
|
<Box padding={2}>
|
||||||
|
<Typography>Equation: {equationName(grain.equation)}</Typography>
|
||||||
|
{/* <Typography>Constant A: {grain.a}</Typography>
|
||||||
|
<Typography>Constant B: {grain.b}</Typography>
|
||||||
|
<Typography>Constant C: {grain.c}</Typography> */}
|
||||||
|
</Box>
|
||||||
|
<Typography>
|
||||||
|
Conversions
|
||||||
|
</Typography>
|
||||||
|
<Divider />
|
||||||
|
<Box padding={2}>
|
||||||
|
<Typography>Kg per Bushel: {grain.weightConversionKg.toFixed(2)}</Typography>
|
||||||
|
<Typography>Bushels per Tonne: {grain.bushelsPerTonne.toFixed(2)}</Typography>
|
||||||
|
</Box>
|
||||||
|
</React.Fragment>
|
||||||
|
:
|
||||||
|
<Typography>Select a Grain from the table to view its details</Typography>
|
||||||
|
}
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateGrain = () => {
|
||||||
|
if(selectedGrain && currentCustomGrain){
|
||||||
|
grainAPI.updateGrain(selectedGrain.key, currentCustomGrain).then(resp => {
|
||||||
|
openSnack("Grain type updated")
|
||||||
|
}).catch(err => {
|
||||||
|
openSnack("There was an issue updating the selected grain type")
|
||||||
|
}).finally(() => {
|
||||||
|
setOpenUpdateConfirmation(false)
|
||||||
|
loadCustomGrain()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const updateGrainConfirmation = () => {
|
||||||
|
return (
|
||||||
|
<ResponsiveDialog open={openUpdateConfirmation} onClose={() => {setOpenUpdateConfirmation(false)}}>
|
||||||
|
<DialogTitle>Update Custom Grain</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
Update existing custom grain type?
|
||||||
|
<br/>
|
||||||
|
Note that updating the grain here will not change any bins or components this type was used for, those will retain the old settings.
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button variant="contained" color="primary" onClick={()=>{setOpenUpdateConfirmation(false)}}>Cancel</Button>
|
||||||
|
<Button variant="contained" color="primary" onClick={()=>{updateGrain()}}>Confirm</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</ResponsiveDialog>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
//because the form is embedded on desktop this dialog is specifically for mobile view
|
||||||
|
const newGrainDialog = () => {
|
||||||
|
return (
|
||||||
|
<ResponsiveDialog open={openNewGrainDialog} onClose={() => setOpenNewGrainDialog(false)}>
|
||||||
|
<DialogTitle>Add New Grain</DialogTitle>
|
||||||
|
<DialogActions>
|
||||||
|
{customGrainDisplay()}
|
||||||
|
|
||||||
|
</DialogActions>
|
||||||
|
</ResponsiveDialog>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const addNewGrain = () => {
|
||||||
|
if(currentCustomGrain){
|
||||||
|
grainAPI.addGrain(currentCustomGrain).then(resp => {
|
||||||
|
//new grain added
|
||||||
|
openSnack("New grain type added")
|
||||||
|
}).catch(err => {
|
||||||
|
openSnack("There was a problem creating the new grain type")
|
||||||
|
}).finally(() => {
|
||||||
|
loadCustomGrain()
|
||||||
|
setOpenNewGrainDialog(false)//only for the mobile view since the desktop has it embedded
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeGrain = () => {
|
||||||
|
if(selectedGrain){
|
||||||
|
grainAPI.removeGrain(selectedGrain.key).then(resp => {
|
||||||
|
openSnack("Grain type removed")
|
||||||
|
}).catch(err => {
|
||||||
|
openSnack("There was a problem removing the selected grain type")
|
||||||
|
}).finally(() => {
|
||||||
|
setOpenRemoveConfirmation(false)
|
||||||
|
loadCustomGrain()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const removeConfirmation = () => {
|
||||||
|
return (
|
||||||
|
<ResponsiveDialog open={openRemoveConfirmation} onClose={() => {setOpenRemoveConfirmation(false)}}>
|
||||||
|
<DialogTitle>Remove Custom Grain</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
Remove custom grain type?
|
||||||
|
<br />
|
||||||
|
Note that removing the grain here will not change any bins or components this type was used for, those will retain the old settings.
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button variant="contained" color="primary" onClick={()=>{setOpenRemoveConfirmation(false)}}>Cancel</Button>
|
||||||
|
<Button variant="contained" color="primary" onClick={()=>{removeGrain()}}>Confirm</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</ResponsiveDialog>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const customGrainActions = () => {
|
||||||
|
return (
|
||||||
|
<Box display="flex" justifyContent="space-between">
|
||||||
|
<Box>
|
||||||
|
{selectedGrain &&
|
||||||
|
<Button disabled={!permissions.includes(pond.Permission.PERMISSION_WRITE)} variant="contained" color="error" onClick={() => {setOpenRemoveConfirmation(true)}}>Remove Grain</Button>
|
||||||
|
}
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
{selectedGrain &&
|
||||||
|
<Button sx={{marginRight: 1}} onClick={() => setOpenUpdateConfirmation(true)} disabled={!formValid || !permissions.includes(pond.Permission.PERMISSION_WRITE)} variant="contained" color="primary">Update</Button>
|
||||||
|
}
|
||||||
|
{isMobile && !selectedGrain && <Button onClick={() => {setOpenNewGrainDialog(false)}}>Cancel</Button>}
|
||||||
|
<Button onClick={() => addNewGrain()} disabled={!formValid || (as !== "" && !permissions.includes(pond.Permission.PERMISSION_WRITE))} variant="contained" color="primary">Save New</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
//the grain display for custom grain the user has defined
|
||||||
|
const customGrainDisplay = () => {
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<CustomGrainForm
|
||||||
|
grainSettings={selectedGrain?.settings || undefined}
|
||||||
|
onGrainSettingsChange={(newSettings, formValid)=>{
|
||||||
|
setCurrentCustomGrain(newSettings)
|
||||||
|
setFormValid(formValid)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{customGrainActions()}
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const desktopView = () => {
|
||||||
|
if(displayCustom){
|
||||||
|
return (
|
||||||
|
<Grid2 container>
|
||||||
|
<Grid2 size={7} padding={2}>
|
||||||
|
{grainObjectTable()}
|
||||||
|
</Grid2>
|
||||||
|
<Grid2 size={5} padding={2}>
|
||||||
|
{customGrainDisplay()}
|
||||||
|
</Grid2>
|
||||||
|
</Grid2>
|
||||||
|
)
|
||||||
|
}else{
|
||||||
|
return (
|
||||||
|
<Box marginTop={2}>
|
||||||
|
{supportedGrainTable()}
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mobileView = () => {
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
{displayCustom ? grainObjectTable() : supportedGrainTable()}
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PageContainer>
|
||||||
|
<Box padding={2}>
|
||||||
|
<Typography variant="h4">
|
||||||
|
Adaptive Grains
|
||||||
|
</Typography>
|
||||||
|
<Box display="flex" marginTop={2} justifyContent="space-between">
|
||||||
|
<ButtonGroup
|
||||||
|
toggle
|
||||||
|
buttons={[
|
||||||
|
{
|
||||||
|
title: "Supported",
|
||||||
|
function: () => {
|
||||||
|
setDisplayCustom(false)
|
||||||
|
setTablePage(0)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Custom",
|
||||||
|
function: () => {
|
||||||
|
setDisplayCustom(true)
|
||||||
|
setTablePage(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
{isMobile && displayCustom &&
|
||||||
|
<IconButton sx={{padding: 0}} color="primary" onClick={()=>{
|
||||||
|
setSelectedGrain(undefined)
|
||||||
|
setCurrentCustomGrain(undefined)
|
||||||
|
setOpenNewGrainDialog(true)
|
||||||
|
}}>
|
||||||
|
<AddCircle />
|
||||||
|
</IconButton>
|
||||||
|
}
|
||||||
|
</Box>
|
||||||
|
{!isMobile ? desktopView() : mobileView()}
|
||||||
|
</Box>
|
||||||
|
{updateGrainConfirmation()}
|
||||||
|
{removeConfirmation()}
|
||||||
|
{newGrainDialog()}
|
||||||
|
</PageContainer>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -56,8 +56,8 @@ export default function Site() {
|
||||||
siteAPI
|
siteAPI
|
||||||
.getSitePage(siteKey, as)
|
.getSitePage(siteKey, as)
|
||||||
.then(resp => {
|
.then(resp => {
|
||||||
setSite(ISite.any(resp.data.site));
|
if(resp.data.site) setSite(ISite.any(resp.data.site));
|
||||||
setHeaters(resp.data.heaterData);
|
if(resp.data.heaterData) setHeaters(resp.data.heaterData);
|
||||||
setLoadingSite(false);
|
setLoadingSite(false);
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
|
|
|
||||||
|
|
@ -182,7 +182,6 @@ export default function Terminals(props: Props) {
|
||||||
if(loading) return
|
if(loading) return
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
terminalAPI.listTerminals(200, 0, "asc", "name", undefined, as).then(resp => {
|
terminalAPI.listTerminals(200, 0, "asc", "name", undefined, as).then(resp => {
|
||||||
console.log(resp.data)
|
|
||||||
if(resp.data.terminals){
|
if(resp.data.terminals){
|
||||||
setTerminals(resp.data.terminals.map(a => Terminal.any(a)));
|
setTerminals(resp.data.terminals.map(a => Terminal.any(a)));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,13 +33,13 @@ export function Airflow(subtype: number = 0): ComponentTypeExtension {
|
||||||
|
|
||||||
let measurementTypes = [
|
let measurementTypes = [
|
||||||
{
|
{
|
||||||
measurementType: quack.MeasurementType.MEASUREMENT_TYPE_PPM,
|
measurementType: quack.MeasurementType.MEASUREMENT_TYPE_CFM,
|
||||||
label: cfm.label(),
|
label: cfm.label(),
|
||||||
colour: cfm.colour(),
|
colour: cfm.colour(),
|
||||||
graphType: cfm.graph()
|
graphType: cfm.graph()
|
||||||
} as ComponentMeasurement,
|
} as ComponentMeasurement,
|
||||||
{
|
{
|
||||||
measurementType: quack.MeasurementType.MEASUREMENT_TYPE_VOLTAGE,
|
measurementType: quack.MeasurementType.MEASUREMENT_TYPE_SPEED,
|
||||||
label: velocity.label(),
|
label: velocity.label(),
|
||||||
colour: velocity.colour(),
|
colour: velocity.colour(),
|
||||||
graphType: velocity.graph()
|
graphType: velocity.graph()
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -138,6 +138,7 @@ 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)
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ export interface IGrainInterface {
|
||||||
types?: string[],
|
types?: string[],
|
||||||
otherTeam?: string,
|
otherTeam?: string,
|
||||||
) => Promise<AxiosResponse<pond.ListGrainsResponse>>;
|
) => Promise<AxiosResponse<pond.ListGrainsResponse>>;
|
||||||
|
updateGrain: (key: string, settings: pond.GrainSettings, otherTeam?: string) => Promise<AxiosResponse<pond.UpdateGrainResponse>>
|
||||||
removeGrain: (key: string, otherTeam?: string) => Promise<AxiosResponse<pond.RemoveGrainResponse>>;
|
removeGrain: (key: string, otherTeam?: string) => Promise<AxiosResponse<pond.RemoveGrainResponse>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -31,7 +32,7 @@ interface Props {}
|
||||||
|
|
||||||
export default function GrainProvider(props: PropsWithChildren<Props>) {
|
export default function GrainProvider(props: PropsWithChildren<Props>) {
|
||||||
const { children } = props;
|
const { children } = props;
|
||||||
const { get, del, post } = useHTTP();
|
const { get, del, post, put } = useHTTP();
|
||||||
const [{ as }] = useGlobalState();
|
const [{ as }] = useGlobalState();
|
||||||
|
|
||||||
//add
|
//add
|
||||||
|
|
@ -79,7 +80,6 @@ export default function GrainProvider(props: PropsWithChildren<Props>) {
|
||||||
get<pond.ListGrainsResponse>(
|
get<pond.ListGrainsResponse>(
|
||||||
pondURL(
|
pondURL(
|
||||||
"/grains?limit=" +
|
"/grains?limit=" +
|
||||||
"?limit=" +
|
|
||||||
limit +
|
limit +
|
||||||
"&offset=" +
|
"&offset=" +
|
||||||
offset +
|
offset +
|
||||||
|
|
@ -98,6 +98,19 @@ export default function GrainProvider(props: PropsWithChildren<Props>) {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
|
//update
|
||||||
|
const updateGrain = (key: string, settings: pond.GrainSettings, otherTeam?: string) => {
|
||||||
|
const view = otherTeam ? otherTeam : as
|
||||||
|
if (view) {
|
||||||
|
return put<pond.UpdateGrainResponse>(
|
||||||
|
pondURL("/grains/"+ key + "?&as=" + view),
|
||||||
|
settings
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return put<pond.UpdateGrainResponse>(pondURL("/grains/" + key), settings);
|
||||||
|
}
|
||||||
|
|
||||||
//remove
|
//remove
|
||||||
const removeGrain = (key: string, otherTeam?: string) => {
|
const removeGrain = (key: string, otherTeam?: string) => {
|
||||||
const view = otherTeam ? otherTeam : as
|
const view = otherTeam ? otherTeam : as
|
||||||
|
|
@ -113,6 +126,7 @@ export default function GrainProvider(props: PropsWithChildren<Props>) {
|
||||||
addGrain,
|
addGrain,
|
||||||
getGrain,
|
getGrain,
|
||||||
listGrains,
|
listGrains,
|
||||||
|
updateGrain,
|
||||||
removeGrain
|
removeGrain
|
||||||
}}>
|
}}>
|
||||||
{children}
|
{children}
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@ export interface IInteractionsAPIContext {
|
||||||
alerts: pond.AlertData[],
|
alerts: pond.AlertData[],
|
||||||
otherTeam?: string
|
otherTeam?: string
|
||||||
) => Promise<AxiosResponse<pond.SetAlertInteractionsResponse>>;
|
) => Promise<AxiosResponse<pond.SetAlertInteractionsResponse>>;
|
||||||
|
clearInteractions: (device: number, sources?: quack.ComponentID[], otherTeam?: string) => Promise<AxiosResponse<pond.ClearInteractionsResponse>>
|
||||||
}
|
}
|
||||||
|
|
||||||
export const InteractionsAPIContext = createContext<IInteractionsAPIContext>(
|
export const InteractionsAPIContext = createContext<IInteractionsAPIContext>(
|
||||||
|
|
@ -285,6 +286,25 @@ export default function InteractionProvider(props: PropsWithChildren<Props>) {
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const clearInteractions = (device: number, sources?: quack.ComponentID[], otherTeam?: string): Promise<AxiosResponse<pond.ClearInteractionsResponse>> => {
|
||||||
|
const view = otherTeam ? otherTeam : as
|
||||||
|
let sourceArray: string[] = []
|
||||||
|
if (sources){
|
||||||
|
sources.forEach(source => {
|
||||||
|
sourceArray.push(componentIDToString(source))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
let url = pondURL("/devices/"+ device + "/interactions/clear" + (view ? "?as=" + view : "") + (sources ? "&sources=" + sourceArray.toString() : ""))
|
||||||
|
return new Promise<AxiosResponse<pond.ClearInteractionsResponse>>((resolve, reject) => {
|
||||||
|
post<pond.ClearInteractionsResponse>(url).then(resp => {
|
||||||
|
resp.data = pond.ClearInteractionsResponse.fromObject(resp.data)
|
||||||
|
return resolve(resp)
|
||||||
|
}).catch(err => {
|
||||||
|
return reject(err)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<InteractionsAPIContext.Provider
|
<InteractionsAPIContext.Provider
|
||||||
value={{
|
value={{
|
||||||
|
|
@ -296,7 +316,8 @@ export default function InteractionProvider(props: PropsWithChildren<Props>) {
|
||||||
updateInteractionPondSettings,
|
updateInteractionPondSettings,
|
||||||
removeInteraction,
|
removeInteraction,
|
||||||
listInteractionsByDevice,
|
listInteractionsByDevice,
|
||||||
listInteractionsByComponent
|
listInteractionsByComponent,
|
||||||
|
clearInteractions
|
||||||
}}>
|
}}>
|
||||||
{children}
|
{children}
|
||||||
</InteractionsAPIContext.Provider>
|
</InteractionsAPIContext.Provider>
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ import AeroGrowDarkLogo from "../assets/whitelabels/AeroGrow/darkLogo.png";
|
||||||
import AeroGrowLightLogo from "../assets/whitelabels/AeroGrow/lightLogo.png";
|
import AeroGrowLightLogo from "../assets/whitelabels/AeroGrow/lightLogo.png";
|
||||||
import MiVentLightLogo from "../assets/whitelabels/MiVent/lightLogo.png";
|
import MiVentLightLogo from "../assets/whitelabels/MiVent/lightLogo.png";
|
||||||
// import OmniAirLogo from "../assets/whitelabels/OmniAir/OmniAirLogo.png";
|
// import OmniAirLogo from "../assets/whitelabels/OmniAir/OmniAirLogo.png";
|
||||||
import MiPCALogo from "../assets/whitelabels/OmniAir/MiPCALogo.png";
|
import MiPCALogo from "../assets/whitelabels/MiPCA/MiPCALogo.png";
|
||||||
import StreamlineLogo from "../assets/whitelabels/Streamline/stream-logo.png"
|
import StreamlineLogo from "../assets/whitelabels/Streamline/stream-logo.png"
|
||||||
// import { green, yellow } from "@mui/material/colors";
|
// import { green, yellow } from "@mui/material/colors";
|
||||||
|
|
||||||
|
|
@ -284,6 +284,32 @@ export function IsOmniAir(): boolean {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const MIPCA_WHITE_LABEL: WhiteLabel = {
|
||||||
|
name: "MiPCA",
|
||||||
|
primaryColour: "#004f9b",
|
||||||
|
secondaryColour: "yellow",
|
||||||
|
signatureColour: "#272727",
|
||||||
|
signatureAccentColour: "#fff",
|
||||||
|
//omni air and MiPCA are the same client ID in Auth0, it is to replace it, once omniair gets removed we can re-name this
|
||||||
|
auth0ClientId: import.meta.env.VITE_AUTH0_OMNIAIR_CLIENT_ID,
|
||||||
|
redirectOnLogout: true,
|
||||||
|
logoutRedirectTarget: "https://mionetech.com",
|
||||||
|
darkLogo: MiPCALogo,
|
||||||
|
lightLogo: MiPCALogo,
|
||||||
|
transparentLogoBG: true,
|
||||||
|
blacklist: ["cost"],
|
||||||
|
docs: "MiPCA",
|
||||||
|
protips: protips.concat([])
|
||||||
|
};
|
||||||
|
|
||||||
|
export function IsMiPCA(): boolean {
|
||||||
|
return (
|
||||||
|
getName() === "MiPCA" ||
|
||||||
|
window.location.origin.includes("staging") ||
|
||||||
|
window.location.origin.includes("localhost")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const whitelabels = new Map<string, WhiteLabel>([
|
const whitelabels = new Map<string, WhiteLabel>([
|
||||||
["streamline", STREAMLINE_WHITE_LABEL],
|
["streamline", STREAMLINE_WHITE_LABEL],
|
||||||
["adaptiveag", ADAPTIVE_AGRICULTURE_WHITE_LABEL],
|
["adaptiveag", ADAPTIVE_AGRICULTURE_WHITE_LABEL],
|
||||||
|
|
@ -296,7 +322,9 @@ const whitelabels = new Map<string, WhiteLabel>([
|
||||||
["10.0", ADAPTIVE_CONSTRUCTION_WHITE_LABEL],
|
["10.0", ADAPTIVE_CONSTRUCTION_WHITE_LABEL],
|
||||||
["mivent", MIVENT_WHITE_LABEL],
|
["mivent", MIVENT_WHITE_LABEL],
|
||||||
["adaptiveconstruction", ADAPTIVE_CONSTRUCTION_WHITE_LABEL],
|
["adaptiveconstruction", ADAPTIVE_CONSTRUCTION_WHITE_LABEL],
|
||||||
["omniair", OMNIAIR_WHITE_LABEL]
|
["omniair", OMNIAIR_WHITE_LABEL],
|
||||||
|
["mipca", MIPCA_WHITE_LABEL],
|
||||||
|
["mionetech", MIPCA_WHITE_LABEL]
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export function getWhitelabel(): WhiteLabel {
|
export function getWhitelabel(): WhiteLabel {
|
||||||
|
|
@ -309,7 +337,7 @@ export function getWhitelabel(): WhiteLabel {
|
||||||
return BXT_WHITE_LABEL;
|
return BXT_WHITE_LABEL;
|
||||||
}
|
}
|
||||||
if (window.location.origin.includes("localhost")) {
|
if (window.location.origin.includes("localhost")) {
|
||||||
return STREAMLINE_WHITE_LABEL;
|
return MIPCA_WHITE_LABEL;
|
||||||
}
|
}
|
||||||
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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||