Merge branch 'staging_environment' into get_permissions_fix

This commit is contained in:
csawatzky 2026-03-26 14:30:46 -06:00
commit 777089e8c4
65 changed files with 1144 additions and 1773 deletions

3
.env
View file

@ -20,3 +20,6 @@ VITE_APP_WEBSITE_TITLE="Adaptive Dashboard"
VITE_APP_PRIMARY_COLOUR=blue VITE_APP_PRIMARY_COLOUR=blue
VITE_APP_SECONDARY_COLOUR=blueGrey VITE_APP_SECONDARY_COLOUR=blueGrey
VITE_APP_SIGNATURE_COLOUR="#323232" VITE_APP_SIGNATURE_COLOUR="#323232"
# Live device/component WebSocket streams (must match string "true" in code)
VITE_ENABLE_WEBSOCKETS=true

762
package-lock.json generated
View file

@ -1922,278 +1922,6 @@
"integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==", "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/@esbuild/aix-ppc64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz",
"integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"aix"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-arm": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz",
"integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-arm64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz",
"integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-x64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz",
"integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/darwin-arm64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz",
"integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/darwin-x64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz",
"integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/freebsd-arm64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz",
"integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/freebsd-x64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz",
"integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-arm": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz",
"integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-arm64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz",
"integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-ia32": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz",
"integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-loong64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz",
"integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==",
"cpu": [
"loong64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-mips64el": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz",
"integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==",
"cpu": [
"mips64el"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-ppc64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz",
"integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-riscv64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz",
"integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==",
"cpu": [
"riscv64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-s390x": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz",
"integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==",
"cpu": [
"s390x"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-x64": { "node_modules/@esbuild/linux-x64": {
"version": "0.25.12", "version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz",
@ -2211,159 +1939,6 @@
"node": ">=18" "node": ">=18"
} }
}, },
"node_modules/@esbuild/netbsd-arm64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz",
"integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/netbsd-x64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz",
"integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/openbsd-arm64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz",
"integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/openbsd-x64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz",
"integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/openharmony-arm64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz",
"integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openharmony"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/sunos-x64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz",
"integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"sunos"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-arm64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz",
"integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-ia32": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz",
"integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-x64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz",
"integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@eslint-community/eslint-utils": { "node_modules/@eslint-community/eslint-utils": {
"version": "4.9.1", "version": "4.9.1",
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz",
@ -3742,244 +3317,6 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz",
"integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
]
},
"node_modules/@rollup/rollup-android-arm64": {
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz",
"integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
]
},
"node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz",
"integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
]
},
"node_modules/@rollup/rollup-darwin-x64": {
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz",
"integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
]
},
"node_modules/@rollup/rollup-freebsd-arm64": {
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz",
"integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
]
},
"node_modules/@rollup/rollup-freebsd-x64": {
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz",
"integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
]
},
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz",
"integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz",
"integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-arm64-gnu": {
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz",
"integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-arm64-musl": {
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz",
"integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-loong64-gnu": {
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz",
"integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==",
"cpu": [
"loong64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-loong64-musl": {
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz",
"integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==",
"cpu": [
"loong64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz",
"integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-ppc64-musl": {
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz",
"integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz",
"integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==",
"cpu": [
"riscv64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-riscv64-musl": {
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz",
"integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==",
"cpu": [
"riscv64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-s390x-gnu": {
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz",
"integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==",
"cpu": [
"s390x"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-x64-gnu": { "node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.59.0", "version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz",
@ -4008,90 +3345,6 @@
"linux" "linux"
] ]
}, },
"node_modules/@rollup/rollup-openbsd-x64": {
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz",
"integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openbsd"
]
},
"node_modules/@rollup/rollup-openharmony-arm64": {
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz",
"integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openharmony"
]
},
"node_modules/@rollup/rollup-win32-arm64-msvc": {
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz",
"integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
]
},
"node_modules/@rollup/rollup-win32-ia32-msvc": {
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz",
"integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
]
},
"node_modules/@rollup/rollup-win32-x64-gnu": {
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz",
"integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
]
},
"node_modules/@rollup/rollup-win32-x64-msvc": {
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz",
"integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
]
},
"node_modules/@sentry-internal/browser-utils": { "node_modules/@sentry-internal/browser-utils": {
"version": "8.55.0", "version": "8.55.0",
"resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-8.55.0.tgz", "resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-8.55.0.tgz",
@ -9370,21 +8623,6 @@
"integrity": "sha512-hlq4+BU0hlPmwsFjwGGzZ+OZ9N/wq9Ljg/sq3pX+2CD7hrJsX9tJgWWK/wiNTFM212CLHWhicOoqwXyZGGetJg==", "integrity": "sha512-hlq4+BU0hlPmwsFjwGGzZ+OZ9N/wq9Ljg/sq3pX+2CD7hrJsX9tJgWWK/wiNTFM212CLHWhicOoqwXyZGGetJg==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/function-bind": { "node_modules/function-bind": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",

View file

@ -17,7 +17,7 @@ import moment from "moment";
import { pond } from "protobuf-ts/pond"; import { pond } from "protobuf-ts/pond";
import { useGlobalState } from "providers"; import { useGlobalState } from "providers";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { celsiusToFahrenheit, getGrainUnit, or } from "utils"; import { celsiusToFahrenheit, or } from "utils";
//import { useHistory } from "react-router"; //import { useHistory } from "react-router";
//import BinModeDot from "./BinModeDot"; //import BinModeDot from "./BinModeDot";
import BinSVGV2 from "./BinSVGV2"; import BinSVGV2 from "./BinSVGV2";
@ -444,16 +444,16 @@ export default function BinCard(props: Props) {
); );
} }
if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && bin.bushelsPerTonne() > 1) { if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && bin.bushelsPerTonne() > 1) {
return ( return (
bin.grainInventory().toLocaleString() + bin.grainInventory(user).toLocaleString() +
" mT " + " mT " +
bin.fillPercent() + bin.fillPercent() +
"%" "%"
); );
} else if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && bin.bushelsPerTonne() > 1) { } else if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && bin.bushelsPerTonne() > 1) {
return ( return (
bin.grainInventory().toLocaleString() + bin.grainInventory(user).toLocaleString() +
" t " + " t " +
bin.fillPercent() + bin.fillPercent() +
"%" "%"

View file

@ -21,7 +21,7 @@ import { describeMeasurement } from "pbHelpers/MeasurementDescriber";
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 { avg, fahrenheitToCelsius, getTemperatureUnit } from "utils"; import { avg, fahrenheitToCelsius } from "utils";
import { makeStyles } from "@mui/styles"; import { makeStyles } from "@mui/styles";
import { Mark } from "@mui/material/Slider/useSlider.types"; import { Mark } from "@mui/material/Slider/useSlider.types";
@ -131,7 +131,7 @@ export default function BinConditioningInteraction(props: Props) {
temp && temp &&
hum hum
) { ) {
if (getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) { if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
//the emc calc needs the temp to be in celsius //the emc calc needs the temp to be in celsius
temp = fahrenheitToCelsius(temp); temp = fahrenheitToCelsius(temp);
} }
@ -139,7 +139,7 @@ export default function BinConditioningInteraction(props: Props) {
setBaseEMC(emc === hum ? undefined : emc); setBaseEMC(emc === hum ? undefined : emc);
} }
}, [sliderVals, grain]); }, [sliderVals, grain, user]);
const updateInteraction = () => { const updateInteraction = () => {
interactionAPI interactionAPI
@ -178,7 +178,7 @@ export default function BinConditioningInteraction(props: Props) {
let marks: Mark[] = []; let marks: Mark[] = [];
if (condition.measurementType === quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE) { if (condition.measurementType === quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE) {
if (getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) { if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
labelTail = "°F"; labelTail = "°F";
} else { } else {
labelTail = "°C"; labelTail = "°C";
@ -220,7 +220,7 @@ export default function BinConditioningInteraction(props: Props) {
quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE
) { ) {
if ( if (
getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT
) { ) {
return value.toFixed(1) + "°F"; return value.toFixed(1) + "°F";
} else { } else {

View file

@ -4,7 +4,7 @@ import SearchSelect, { Option } from "common/SearchSelect";
import { useMobile } from "hooks"; import { useMobile } from "hooks";
import { pond } from "protobuf-ts/pond"; import { pond } from "protobuf-ts/pond";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { getDistanceUnit } from "utils"; import { useGlobalState } from "providers";
interface Props { interface Props {
optionsChanged: (binOptions: jsonBin[]) => void; optionsChanged: (binOptions: jsonBin[]) => void;
@ -13,6 +13,7 @@ interface Props {
export default function BinSelector(props: Props) { export default function BinSelector(props: Props) {
const { optionsChanged, vertical } = props; const { optionsChanged, vertical } = props;
const [{user}] = useGlobalState();
const isMobile = useMobile(); const isMobile = useMobile();
const [manufacturerOptions, SetManufacturerOptions] = useState<Option[]>([]); const [manufacturerOptions, SetManufacturerOptions] = useState<Option[]>([]);
const [manufacturer, SetManufacturer] = useState<Option | null>(null); const [manufacturer, SetManufacturer] = useState<Option | null>(null);
@ -126,7 +127,7 @@ export default function BinSelector(props: Props) {
}} }}
label={ label={
"Minimum Diameter " + "Minimum Diameter " +
(getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET ? "(ft)" : "(m)") (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET ? "(ft)" : "(m)")
} }
/> />
</Grid> </Grid>
@ -143,7 +144,7 @@ export default function BinSelector(props: Props) {
}} }}
label={ label={
"Maximum Diameter " + "Maximum Diameter " +
(getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET ? "(ft)" : "(m)") (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET ? "(ft)" : "(m)")
} }
/> />
</Grid> </Grid>

View file

@ -61,7 +61,7 @@ import { pond } from "protobuf-ts/pond";
import { useBinAPI, useBinYardAPI, useGlobalState, useLibraCartProxyAPI } from "providers"; import { useBinAPI, useBinYardAPI, useGlobalState, useLibraCartProxyAPI } from "providers";
import React, { useCallback, useEffect, useState } from "react"; import React, { useCallback, useEffect, useState } from "react";
// import { useHistory } from "react-router"; // import { useHistory } from "react-router";
import { getDistanceUnit, or, getTemperatureUnit, getGrainUnit } from "utils"; import { or } 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";
@ -240,16 +240,16 @@ export default function BinSettings(props: Props) {
initForm.inventory.bushelsPerTonne = grain.bushelsPerTonne; initForm.inventory.bushelsPerTonne = grain.bushelsPerTonne;
} }
if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE){ if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE){
setBushelConversion(initForm.inventory.bushelsPerTonne) setBushelConversion(initForm.inventory.bushelsPerTonne)
}else if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON){ }else if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON){
setBushelConversion(initForm.inventory.bushelsPerTonne * 0.907) setBushelConversion(initForm.inventory.bushelsPerTonne * 0.907)
} }
if (initForm.inventory.customGrain){ if (initForm.inventory.customGrain){
setCustomGrain(initForm.inventory.customGrain) setCustomGrain(initForm.inventory.customGrain)
if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE){ if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE){
setBushelConversion(initForm.inventory.customGrain.bushelsPerTonne) setBushelConversion(initForm.inventory.customGrain.bushelsPerTonne)
}else if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON){ }else if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON){
setBushelConversion(initForm.inventory.customGrain.bushelsPerTonne * 0.907) setBushelConversion(initForm.inventory.customGrain.bushelsPerTonne * 0.907)
} }
} }
@ -269,7 +269,7 @@ export default function BinSettings(props: Props) {
let sidewallHeight = ""; let sidewallHeight = "";
let hopperHeight = ""; let hopperHeight = "";
if (initForm.specs) { if (initForm.specs) {
if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET) { if (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET) {
h = (initForm.specs.heightCm * 0.0328).toFixed(2); h = (initForm.specs.heightCm * 0.0328).toFixed(2);
d = (initForm.specs.diameterCm * 0.0328).toFixed(2); d = (initForm.specs.diameterCm * 0.0328).toFixed(2);
if (initForm.specs.advancedDimensions) { if (initForm.specs.advancedDimensions) {
@ -290,7 +290,7 @@ export default function BinSettings(props: Props) {
let high = initForm.highTemp ?? 20; let high = initForm.highTemp ?? 20;
let low = initForm.lowTemp ?? 10; let low = initForm.lowTemp ?? 10;
let target = initForm.inventory?.targetTemperature ?? 15; let target = initForm.inventory?.targetTemperature ?? 15;
if (getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) { if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
high = parseFloat((high * 1.8 + 32).toFixed(2)); high = parseFloat((high * 1.8 + 32).toFixed(2));
low = parseFloat((low * 1.8 + 32).toFixed(2)); low = parseFloat((low * 1.8 + 32).toFixed(2));
target = parseFloat((target * 1.8 + 32).toFixed(2)); target = parseFloat((target * 1.8 + 32).toFixed(2));
@ -304,15 +304,15 @@ export default function BinSettings(props: Props) {
: initForm.inventory?.grainBushels ?? 0; : initForm.inventory?.grainBushels ?? 0;
let weight = "" let weight = ""
if(initForm.inventory){ if(initForm.inventory){
if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE){ if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE){
weight = (gb/initForm.inventory.bushelsPerTonne).toFixed(2) weight = (gb/initForm.inventory.bushelsPerTonne).toFixed(2)
}else if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON){ }else if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON){
weight = (gb/(initForm.inventory.bushelsPerTonne * 0.907)).toFixed(2) weight = (gb/(initForm.inventory.bushelsPerTonne * 0.907)).toFixed(2)
} }
if(initForm.inventory.customGrain){ if(initForm.inventory.customGrain){
if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE){ if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE){
weight = (gb/initForm.inventory.customGrain.bushelsPerTonne).toFixed(2) weight = (gb/initForm.inventory.customGrain.bushelsPerTonne).toFixed(2)
}else if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON){ }else if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON){
weight = (gb/(initForm.inventory.customGrain.bushelsPerTonne * 0.907)).toFixed(2) weight = (gb/(initForm.inventory.customGrain.bushelsPerTonne * 0.907)).toFixed(2)
} }
} }
@ -350,7 +350,7 @@ export default function BinSettings(props: Props) {
setMoistureTargetDeviation(initForm.inventory?.moistureTargetDeviation.toString() ?? "0"); setMoistureTargetDeviation(initForm.inventory?.moistureTargetDeviation.toString() ?? "0");
setAutoFillThreshold(initForm.inventory?.autoThreshold ?? 5) setAutoFillThreshold(initForm.inventory?.autoThreshold ?? 5)
let dropDistance = initForm.inventory?.lidarDropCm ?? 0 let dropDistance = initForm.inventory?.lidarDropCm ?? 0
if(getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET){ if(user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET){
dropDistance = Math.round((dropDistance/30.48)*100)/100 dropDistance = Math.round((dropDistance/30.48)*100)/100
} }
setLidarDropDistance(dropDistance) setLidarDropDistance(dropDistance)
@ -364,7 +364,7 @@ export default function BinSettings(props: Props) {
} else { } else {
setInitialized(false); setInitialized(false);
} }
}, [bin, open, mode, openedBinYard]); }, [bin, open, mode, openedBinYard, user]);
useEffect(() => { useEffect(() => {
if (mode === "remove") { if (mode === "remove") {
@ -475,7 +475,7 @@ export default function BinSettings(props: Props) {
if (form.inventory) { if (form.inventory) {
form.inventory.inventoryControl = inventoryControl form.inventory.inventoryControl = inventoryControl
form.inventory.autoThreshold = autoFillThreshold form.inventory.autoThreshold = autoFillThreshold
form.inventory.lidarDropCm = getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET ? lidarDropDistance * 30.48 : lidarDropDistance form.inventory.lidarDropCm = user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET ? lidarDropDistance * 30.48 : lidarDropDistance
if(form.inventory.grainType !== pond.Grain.GRAIN_CUSTOM){ if(form.inventory.grainType !== pond.Grain.GRAIN_CUSTOM){
form.inventory.customGrain = undefined form.inventory.customGrain = undefined
}else{ }else{
@ -529,7 +529,7 @@ export default function BinSettings(props: Props) {
form.inventory.inventoryControl = inventoryControl form.inventory.inventoryControl = inventoryControl
form.inventory.autoThreshold = autoFillThreshold form.inventory.autoThreshold = autoFillThreshold
//if the users preferences are in feet convert the distance to cm otherwise it was entered as cm so use that //if the users preferences are in feet convert the distance to cm otherwise it was entered as cm so use that
form.inventory.lidarDropCm = getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET ? lidarDropDistance * 30.48 : lidarDropDistance form.inventory.lidarDropCm = user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET ? lidarDropDistance * 30.48 : lidarDropDistance
if(form.inventory.grainType !== pond.Grain.GRAIN_CUSTOM){ if(form.inventory.grainType !== pond.Grain.GRAIN_CUSTOM){
form.inventory.customGrain = undefined form.inventory.customGrain = undefined
}else{ }else{
@ -804,7 +804,7 @@ export default function BinSettings(props: Props) {
const binQuantity = formExtension.grainBushels; const binQuantity = formExtension.grainBushels;
//as long as the storage type is not fertilizer it is some sort of grain, whether it is supported or custom is not important in this instance so we do not need to check for unknown //as long as the storage type is not fertilizer it is some sort of grain, whether it is supported or custom is not important in this instance so we do not need to check for unknown
if(storageType !== pond.BinStorage.BIN_STORAGE_FERTILIZER && ((getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE || getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON) && bushelConversion !== 0 && grainWeight !== "")){ if(storageType !== pond.BinStorage.BIN_STORAGE_FERTILIZER && ((user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE || user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON) && bushelConversion !== 0 && grainWeight !== "")){
return ( return (
<TextField <TextField
label="Amount" label="Amount"
@ -830,7 +830,7 @@ export default function BinSettings(props: Props) {
variant="outlined" variant="outlined"
disabled={!canEdit} disabled={!canEdit}
InputProps={{ InputProps={{
endAdornment: <InputAdornment position="end">{getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE ? "mT" : "t"}</InputAdornment> endAdornment: <InputAdornment position="end">{user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE ? "mT" : "t"}</InputAdornment>
}} }}
className={classes.bottomSpacing} className={classes.bottomSpacing}
/> />
@ -1099,7 +1099,7 @@ export default function BinSettings(props: Props) {
InputProps={{ InputProps={{
endAdornment: ( endAdornment: (
<InputAdornment position="end"> <InputAdornment position="end">
{getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET ? "ft" : "cm"} {user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET ? "ft" : "cm"}
</InputAdornment> </InputAdornment>
) )
}} }}
@ -1179,9 +1179,9 @@ export default function BinSettings(props: Props) {
setCustomGrain(newGrainSettings) setCustomGrain(newGrainSettings)
let conversion = 0 let conversion = 0
if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE){ if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE){
conversion = newGrainSettings.bushelsPerTonne conversion = newGrainSettings.bushelsPerTonne
}else if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON){ }else if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON){
conversion = newGrainSettings.bushelsPerTonne * 0.907 conversion = newGrainSettings.bushelsPerTonne * 0.907
} }
updateForm( updateForm(
@ -1218,9 +1218,9 @@ export default function BinSettings(props: Props) {
}) })
); );
let conversion = 0 let conversion = 0
if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE){ if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE){
conversion = GrainDescriber(newGrainType).bushelsPerTonne conversion = GrainDescriber(newGrainType).bushelsPerTonne
}else if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON){ }else if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON){
conversion = GrainDescriber(newGrainType).bushelsPerTon conversion = GrainDescriber(newGrainType).bushelsPerTon
} }
setBushelConversion(conversion) setBushelConversion(conversion)
@ -1435,7 +1435,7 @@ export default function BinSettings(props: Props) {
let sH = sidewallHeight; let sH = sidewallHeight;
let hH = hopperHeight ?? 0; let hH = hopperHeight ?? 0;
let d = diameter; let d = diameter;
if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) { if (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) {
tcH = tcH * 3.281; tcH = tcH * 3.281;
sH = sH * 3.281; sH = sH * 3.281;
hH = hH * 3.281; hH = hH * 3.281;
@ -1463,7 +1463,7 @@ export default function BinSettings(props: Props) {
let hopperHeight = formExtension.hopperHeight; let hopperHeight = formExtension.hopperHeight;
let diameter = formExtension.diameter; let diameter = formExtension.diameter;
// if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET) { // if (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET) {
// diameterM = (Number(diameterM) * 3.28084).toFixed(2); // diameterM = (Number(diameterM) * 3.28084).toFixed(2);
// heightM = (Number(heightM) * 3.28084).toFixed(2); // heightM = (Number(heightM) * 3.28084).toFixed(2);
// } // }
@ -1552,7 +1552,7 @@ export default function BinSettings(props: Props) {
let d = diameter; let d = diameter;
let valueM = value; let valueM = value;
let dM = diameter; let dM = diameter;
if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET) { if (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET) {
valueM = (Number(value) * 0.3048).toFixed(2); valueM = (Number(value) * 0.3048).toFixed(2);
dM = (Number(d) * 0.3048).toFixed(2); dM = (Number(d) * 0.3048).toFixed(2);
} }
@ -1577,7 +1577,7 @@ export default function BinSettings(props: Props) {
InputProps={{ InputProps={{
endAdornment: ( endAdornment: (
<InputAdornment position="end"> <InputAdornment position="end">
{getDistanceUnit() !== pond.DistanceUnit.DISTANCE_UNIT_FEET ? "m" : "ft"} {user.distanceUnit() !== pond.DistanceUnit.DISTANCE_UNIT_FEET ? "m" : "ft"}
</InputAdornment> </InputAdornment>
) )
}} }}
@ -1637,7 +1637,7 @@ export default function BinSettings(props: Props) {
let value = event?.target.value; let value = event?.target.value;
let valueM = value; let valueM = value;
setModelID(0); setModelID(0);
if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET) { if (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET) {
valueM = (Number(value) * 0.3048).toFixed(2); valueM = (Number(value) * 0.3048).toFixed(2);
} }
let coneCM = let coneCM =
@ -1674,7 +1674,7 @@ export default function BinSettings(props: Props) {
//need to get the total height in cm //need to get the total height in cm
let totalHeightCM = s + t + h; //the total height in the users units let totalHeightCM = s + t + h; //the total height in the users units
if(getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET){ if(user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET){
totalHeightCM = totalHeightCM * 30.48 //convert from feet to cm totalHeightCM = totalHeightCM * 30.48 //convert from feet to cm
}else{ }else{
totalHeightCM = totalHeightCM * 100 //convert from m to cm totalHeightCM = totalHeightCM * 100 //convert from m to cm
@ -1698,7 +1698,7 @@ export default function BinSettings(props: Props) {
InputProps={{ InputProps={{
endAdornment: ( endAdornment: (
<InputAdornment position="end"> <InputAdornment position="end">
{getDistanceUnit() !== pond.DistanceUnit.DISTANCE_UNIT_FEET {user.distanceUnit() !== pond.DistanceUnit.DISTANCE_UNIT_FEET
? "m" ? "m"
: "ft"} : "ft"}
</InputAdornment> </InputAdornment>
@ -1725,7 +1725,7 @@ export default function BinSettings(props: Props) {
//calculate the new cone height for the roof angle //calculate the new cone height for the roof angle
let coneCM = TriangleOppositeLength(d / 2, angle ?? 0); let coneCM = TriangleOppositeLength(d / 2, angle ?? 0);
let newConeHeight = let newConeHeight =
getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET
? (coneCM / 30.48).toFixed(2) ? (coneCM / 30.48).toFixed(2)
: (coneCM / 100).toFixed(2); : (coneCM / 100).toFixed(2);
@ -1749,7 +1749,7 @@ export default function BinSettings(props: Props) {
//need to get the total height in cm //need to get the total height in cm
let totalHeightCM = s + t + h; //the total height in the users units let totalHeightCM = s + t + h; //the total height in the users units
if(getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET){ if(user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET){
totalHeightCM = totalHeightCM * 30.48 //convert from feet to cm totalHeightCM = totalHeightCM * 30.48 //convert from feet to cm
}else{ }else{
totalHeightCM = totalHeightCM * 100 //convert from m to cm totalHeightCM = totalHeightCM * 100 //convert from m to cm
@ -1786,7 +1786,7 @@ export default function BinSettings(props: Props) {
onChange={event => { onChange={event => {
let value = event?.target.value; let value = event?.target.value;
let valueM = value; let valueM = value;
if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET) { if (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET) {
valueM = (Number(value) * 0.3048).toFixed(2); valueM = (Number(value) * 0.3048).toFixed(2);
} }
let ext = cloneDeep(formExtension); let ext = cloneDeep(formExtension);
@ -1811,7 +1811,7 @@ export default function BinSettings(props: Props) {
//need to get the total height in cm //need to get the total height in cm
let totalHeightCM = s + t + h; //the total height in the users units let totalHeightCM = s + t + h; //the total height in the users units
if(getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET){ if(user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET){
totalHeightCM = totalHeightCM * 30.48 //convert from feet to cm totalHeightCM = totalHeightCM * 30.48 //convert from feet to cm
}else{ }else{
totalHeightCM = totalHeightCM * 100 //convert from m to cm totalHeightCM = totalHeightCM * 100 //convert from m to cm
@ -1838,7 +1838,7 @@ export default function BinSettings(props: Props) {
InputProps={{ InputProps={{
endAdornment: ( endAdornment: (
<InputAdornment position="end"> <InputAdornment position="end">
{getDistanceUnit() !== pond.DistanceUnit.DISTANCE_UNIT_FEET {user.distanceUnit() !== pond.DistanceUnit.DISTANCE_UNIT_FEET
? "m" ? "m"
: "ft"} : "ft"}
</InputAdornment> </InputAdornment>
@ -1868,7 +1868,7 @@ export default function BinSettings(props: Props) {
let coneCM = TriangleOppositeLength(d / 2, angle ?? 0); let coneCM = TriangleOppositeLength(d / 2, angle ?? 0);
let newHopperHeight = let newHopperHeight =
getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET
? (coneCM / 30.48).toFixed(2) ? (coneCM / 30.48).toFixed(2)
: (coneCM / 100).toFixed(2); : (coneCM / 100).toFixed(2);
@ -1926,7 +1926,7 @@ export default function BinSettings(props: Props) {
onChange={event => { onChange={event => {
let value = event?.target.value; let value = event?.target.value;
let valueM = value; let valueM = value;
if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET) { if (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET) {
valueM = (Number(value) * 0.3048).toFixed(2); valueM = (Number(value) * 0.3048).toFixed(2);
} }
@ -1982,7 +1982,7 @@ export default function BinSettings(props: Props) {
InputProps={{ InputProps={{
endAdornment: ( endAdornment: (
<InputAdornment position="end"> <InputAdornment position="end">
{getDistanceUnit() !== pond.DistanceUnit.DISTANCE_UNIT_FEET {user.distanceUnit() !== pond.DistanceUnit.DISTANCE_UNIT_FEET
? "m" ? "m"
: "ft"} : "ft"}
</InputAdornment> </InputAdornment>
@ -2009,7 +2009,7 @@ export default function BinSettings(props: Props) {
let h = height; let h = height;
let valueM = value; let valueM = value;
let hM = height; let hM = height;
if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET) { if (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET) {
valueM = (Number(value) * 0.3048).toFixed(2); valueM = (Number(value) * 0.3048).toFixed(2);
hM = (Number(h) * 0.3048).toFixed(2); hM = (Number(h) * 0.3048).toFixed(2);
} }
@ -2063,7 +2063,7 @@ export default function BinSettings(props: Props) {
InputProps={{ InputProps={{
endAdornment: ( endAdornment: (
<InputAdornment position="end"> <InputAdornment position="end">
{getDistanceUnit() !== pond.DistanceUnit.DISTANCE_UNIT_FEET ? "m" : "ft"} {user.distanceUnit() !== pond.DistanceUnit.DISTANCE_UNIT_FEET ? "m" : "ft"}
</InputAdornment> </InputAdornment>
) )
}} }}
@ -2253,7 +2253,7 @@ export default function BinSettings(props: Props) {
let userHopperHeight = hopperHeight; let userHopperHeight = hopperHeight;
let userDiameter = jsonBin.Diameter; let userDiameter = jsonBin.Diameter;
//since the jsone data is in feet just check if we need to make it in meters //since the jsone data is in feet just check if we need to make it in meters
if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) { if (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) {
userHeight = userHeight / 3.281; userHeight = userHeight / 3.281;
userDiameter = userDiameter / 3.281; userDiameter = userDiameter / 3.281;
userTopCone = userTopCone / 3.281; userTopCone = userTopCone / 3.281;
@ -2308,7 +2308,7 @@ export default function BinSettings(props: Props) {
onChange={event => { onChange={event => {
let value = event?.target.value; let value = event?.target.value;
let valueC = value; let valueC = value;
if (getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) { if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
valueC = ((Number(value) - 32) * (5 / 9)).toFixed(2); valueC = ((Number(value) - 32) * (5 / 9)).toFixed(2);
} }
@ -2320,7 +2320,7 @@ export default function BinSettings(props: Props) {
InputProps={{ InputProps={{
endAdornment: ( endAdornment: (
<InputAdornment position="end"> <InputAdornment position="end">
{getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT {user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT
? "F" ? "F"
: "C"} : "C"}
</InputAdornment> </InputAdornment>
@ -2348,7 +2348,7 @@ export default function BinSettings(props: Props) {
onChange={event => { onChange={event => {
let value = event?.target.value; let value = event?.target.value;
let valueC = value; let valueC = value;
if (getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) { if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
valueC = ((Number(value) - 32) * (5 / 9)).toFixed(2); valueC = ((Number(value) - 32) * (5 / 9)).toFixed(2);
} }
setLowTempC(+valueC); setLowTempC(+valueC);
@ -2357,7 +2357,7 @@ export default function BinSettings(props: Props) {
InputProps={{ InputProps={{
endAdornment: ( endAdornment: (
<InputAdornment position="end"> <InputAdornment position="end">
{getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT {user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT
? "F" ? "F"
: "C"} : "C"}
</InputAdornment> </InputAdornment>
@ -2383,7 +2383,7 @@ export default function BinSettings(props: Props) {
onChange={event => { onChange={event => {
let value = event?.target.value; let value = event?.target.value;
let valueC = value; let valueC = value;
if (getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) { if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
valueC = ((Number(value) - 32) * (5 / 9)).toFixed(2); valueC = ((Number(value) - 32) * (5 / 9)).toFixed(2);
} }
setHighTempC(+valueC); setHighTempC(+valueC);
@ -2392,7 +2392,7 @@ export default function BinSettings(props: Props) {
InputProps={{ InputProps={{
endAdornment: ( endAdornment: (
<InputAdornment position="end"> <InputAdornment position="end">
{getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT {user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT
? "F" ? "F"
: "C"} : "C"}
</InputAdornment> </InputAdornment>

View file

@ -24,7 +24,7 @@ import Co2Icon from "products/CommonIcons/co2Icon";
import { pond, quack } from "protobuf-ts/pond"; import { pond, quack } from "protobuf-ts/pond";
import { useBinAPI, useGlobalState, useSnackbar } from "providers"; import { useBinAPI, useGlobalState, useSnackbar } from "providers";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { avg, getTemperatureUnit } from "utils"; import { avg } from "utils";
import { makeStyles } from "@mui/styles"; import { makeStyles } from "@mui/styles";
import { Mark } from "@mui/material/Slider/useSlider.types"; import { Mark } from "@mui/material/Slider/useSlider.types";
import { CO2 } from "models/CO2"; import { CO2 } from "models/CO2";
@ -134,7 +134,7 @@ export default function BinStorageConditions(props: Props) {
const binAPI = useBinAPI(); const binAPI = useBinAPI();
const { openSnack } = useSnackbar(); const { openSnack } = useSnackbar();
const { bin, headspaceCO2, cables } = props; const { bin, headspaceCO2, cables } = props;
const [{as}] = useGlobalState() const [{as, user}] = useGlobalState()
const [sliderTemps, setSliderTemps] = useState<number[]>([-40, 40]); const [sliderTemps, setSliderTemps] = useState<number[]>([-40, 40]);
//boolean that if a cable is missing its filled to display an icon or something to let the user know not all of the cables have their fill set //boolean that if a cable is missing its filled to display an icon or something to let the user know not all of the cables have their fill set
const [missingTopNodeWarning, setMissingTopNodeWarning] = useState(false); const [missingTopNodeWarning, setMissingTopNodeWarning] = useState(false);
@ -233,7 +233,7 @@ export default function BinStorageConditions(props: Props) {
} }
setTempTargets(tempCounts); setTempTargets(tempCounts);
let tempVal = bin.settings.inventory?.targetTemperature ?? 0; let tempVal = bin.settings.inventory?.targetTemperature ?? 0;
if (getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) { if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
tempVal = tempVal * 1.8 + 32; tempVal = tempVal * 1.8 + 32;
} }
setTargetTemp(tempVal.toFixed(1)); setTargetTemp(tempVal.toFixed(1));
@ -244,7 +244,7 @@ export default function BinStorageConditions(props: Props) {
setEmcTargets(emcCounts); setEmcTargets(emcCounts);
setTargetEMC(bin.settings.inventory?.targetMoisture.toFixed(1) ?? ""); setTargetEMC(bin.settings.inventory?.targetMoisture.toFixed(1) ?? "");
setEmcDeviation(bin.settings.inventory?.moistureTargetDeviation.toFixed(1) ?? ""); setEmcDeviation(bin.settings.inventory?.moistureTargetDeviation.toFixed(1) ?? "");
}, [bin, cables]); }, [bin, cables, user]);
//useEffect that watches for changes in the target emc and deviation to update the targets //useEffect that watches for changes in the target emc and deviation to update the targets
useEffect(() => { useEffect(() => {
@ -301,7 +301,7 @@ export default function BinStorageConditions(props: Props) {
settings.highTemp = sliderTemps[1]; settings.highTemp = sliderTemps[1];
if (settings.inventory) { if (settings.inventory) {
let tempVal = parseFloat(targetTemp); let tempVal = parseFloat(targetTemp);
if (getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) { if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
tempVal = Math.fround(((tempVal - 32) * 5) / 9); tempVal = Math.fround(((tempVal - 32) * 5) / 9);
} }
settings.inventory.targetTemperature = tempVal; settings.inventory.targetTemperature = tempVal;
@ -376,14 +376,14 @@ export default function BinStorageConditions(props: Props) {
let mark: Mark[] = []; let mark: Mark[] = [];
if (averageTemp) { if (averageTemp) {
let temp = averageTemp; let temp = averageTemp;
if (getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) { if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
temp = temp * 1.8 + 32; temp = temp * 1.8 + 32;
} }
mark = [ mark = [
{ {
label: customMark( label: customMark(
temp.toFixed(1) + temp.toFixed(1) +
(getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT
? "°F" ? "°F"
: "°C"), : "°C"),
"AVG" "AVG"
@ -422,7 +422,7 @@ export default function BinStorageConditions(props: Props) {
InputProps={{ InputProps={{
endAdornment: ( endAdornment: (
<InputAdornment position="end"> <InputAdornment position="end">
{getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT {user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT
? "°F" ? "°F"
: "°C"} : "°C"}
</InputAdornment> </InputAdornment>
@ -451,7 +451,7 @@ export default function BinStorageConditions(props: Props) {
max={sliderEdge} max={sliderEdge}
valueLabelDisplay="on" valueLabelDisplay="on"
valueLabelFormat={value => { valueLabelFormat={value => {
if (getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) { if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
return ((value * 9) / 5 + 32).toFixed(1) + "°F"; return ((value * 9) / 5 + 32).toFixed(1) + "°F";
} }
return value.toFixed(1) + "°C"; return value.toFixed(1) + "°C";

View file

@ -10,11 +10,10 @@ import { Transaction } from "models/Transaction";
import moment from "moment"; import moment from "moment";
import ObjectDescriber from "objects/ObjectDescriber"; import ObjectDescriber from "objects/ObjectDescriber";
import { pond } from "protobuf-ts/pond"; import { pond } from "protobuf-ts/pond";
import { useBinAPI, useContractAPI, useFieldAPI, useGrainBagAPI, useSnackbar, useTransactionAPI } from "providers"; import { useBinAPI, useContractAPI, useFieldAPI, useGlobalState, useGrainBagAPI, useSnackbar, useTransactionAPI } from "providers";
import React from "react"; import React from "react";
import { useCallback, useEffect, useState } from "react"; import { useCallback, useEffect, useState } from "react";
import TransactionDataDisplay from "transactions/transactionDataDisplay"; import TransactionDataDisplay from "transactions/transactionDataDisplay";
import { getGrainUnit } from "utils";
interface Props { interface Props {
bin: Bin bin: Bin
@ -26,6 +25,7 @@ export default function BinTransactions(props: Props){
const { bin, permissions, refresh } = props const { bin, permissions, refresh } = props
const [state, setState] = useState<pond.TransactionState>(pond.TransactionState.TRANSACTION_STATE_APPROVAL_REQUIRED) const [state, setState] = useState<pond.TransactionState>(pond.TransactionState.TRANSACTION_STATE_APPROVAL_REQUIRED)
const transactionAPI = useTransactionAPI() const transactionAPI = useTransactionAPI()
const [{user}] = useGlobalState()
const binAPI = useBinAPI() const binAPI = useBinAPI()
const grainBagAPI = useGrainBagAPI() const grainBagAPI = useGrainBagAPI()
const fieldAPI = useFieldAPI() const fieldAPI = useFieldAPI()
@ -103,7 +103,7 @@ export default function BinTransactions(props: Props){
const loadContracts = useCallback(()=>{ const loadContracts = useCallback(()=>{
contractAPI.listContracts(0,0, "asc", "name").then(resp => { contractAPI.listContracts(0,0, "asc", "name").then(resp => {
setContractOptions(resp.data.contracts.map(c => { setContractOptions(resp.data.contracts.map(c => {
let contract = Contract.create(c) let contract = Contract.create(c, user)
return {label: contract.name(), value: contract.key(), group: contract.grainName()} return {label: contract.name(), value: contract.key(), group: contract.grainName()}
})) }))
}) })
@ -198,11 +198,11 @@ export default function BinTransactions(props: Props){
const grainQuantityDisplay = (bushels: number) => { const grainQuantityDisplay = (bushels: number) => {
const grainTransaction = selectedTransaction?.transaction.transaction?.grainTransaction ?? pond.GrainTransaction.create() const grainTransaction = selectedTransaction?.transaction.transaction?.grainTransaction ?? pond.GrainTransaction.create()
if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && grainTransaction.bushelsPerTonne > 1){ if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && grainTransaction.bushelsPerTonne > 1){
let tonneWeight = bushels / grainTransaction.bushelsPerTonne let tonneWeight = bushels / grainTransaction.bushelsPerTonne
return tonneWeight + " mT" return tonneWeight + " mT"
} }
if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && grainTransaction.bushelsPerTonne > 1){ if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && grainTransaction.bushelsPerTonne > 1){
let tonneWeight = bushels / (grainTransaction.bushelsPerTonne * 0.907) let tonneWeight = bushels / (grainTransaction.bushelsPerTonne * 0.907)
return tonneWeight + " t" return tonneWeight + " t"
} }

View file

@ -40,7 +40,7 @@ import { FullScreen, useFullScreenHandle } from "react-full-screen";
import moment, { Moment } from "moment"; import moment, { Moment } from "moment";
import ResponsiveDialog from "common/ResponsiveDialog"; import ResponsiveDialog from "common/ResponsiveDialog";
import { getThemeType } from "theme"; import { getThemeType } from "theme";
import { getGrainUnit, getTemperatureUnit, or } from "utils"; import { or } from "utils";
import { useBinAPI } from "providers/pond/binAPI"; import { useBinAPI } from "providers/pond/binAPI";
import BindaptIcon from "products/Bindapt/BindaptIcon"; import BindaptIcon from "products/Bindapt/BindaptIcon";
import { import {
@ -498,9 +498,9 @@ export default function BinVisualizer(props: Props) {
if (bin.storage() === pond.BinStorage.BIN_STORAGE_FERTILIZER) { if (bin.storage() === pond.BinStorage.BIN_STORAGE_FERTILIZER) {
return Math.round(val * 35.239 * 100) / 100; return Math.round(val * 35.239 * 100) / 100;
} else { } else {
if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && bin.bushelsPerTonne() > 1) { if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && bin.bushelsPerTonne() > 1) {
return Math.round((val / bin.bushelsPerTonne()) * 100) / 100; return Math.round((val / bin.bushelsPerTonne()) * 100) / 100;
} else if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && bin.bushelsPerTonne() > 1) { } else if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && bin.bushelsPerTonne() > 1) {
return Math.round((val / (bin.bushelsPerTonne()*0.907)) * 100) / 100; return Math.round((val / (bin.bushelsPerTonne()*0.907)) * 100) / 100;
} }
} }
@ -516,9 +516,9 @@ export default function BinVisualizer(props: Props) {
if (bin.storage() === pond.BinStorage.BIN_STORAGE_FERTILIZER) { if (bin.storage() === pond.BinStorage.BIN_STORAGE_FERTILIZER) {
return " / " + capacity.toLocaleString() + " L"; return " / " + capacity.toLocaleString() + " L";
} }
if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && bin.bushelsPerTonne() > 1) { if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && bin.bushelsPerTonne() > 1) {
return "mT (" + bin.fillPercent() + "%)"; return "mT (" + bin.fillPercent() + "%)";
} else if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && bin.bushelsPerTonne() > 1) { } else if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && bin.bushelsPerTonne() > 1) {
return "t (" + bin.fillPercent() + "%)"; return "t (" + bin.fillPercent() + "%)";
} else { } else {
return " / " + capacity.toLocaleString() + " bu"; return " / " + capacity.toLocaleString() + " bu";
@ -605,7 +605,7 @@ export default function BinVisualizer(props: Props) {
break; break;
} }
if (valC) { if (valC) {
if (getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) { if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
temp = CtoF(valC).toFixed(1) + "°F"; temp = CtoF(valC).toFixed(1) + "°F";
} else { } else {
temp = valC.toFixed(1) + "°C"; temp = valC.toFixed(1) + "°C";
@ -705,7 +705,7 @@ export default function BinVisualizer(props: Props) {
break; break;
} }
if (valC) { if (valC) {
if (getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) { if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
temp = Math.abs(valC * 1.8).toFixed(1) + "°F"; //since this is a measurement of change a change in 1 degrre celsius is equivalent to 1.8 fahrenheit temp = Math.abs(valC * 1.8).toFixed(1) + "°F"; //since this is a measurement of change a change in 1 degrre celsius is equivalent to 1.8 fahrenheit
} else { } else {
temp = Math.abs(valC).toFixed(1) + "°C"; temp = Math.abs(valC).toFixed(1) + "°C";
@ -1328,14 +1328,14 @@ export default function BinVisualizer(props: Props) {
} }
} }
if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && bin.bushelsPerTonne() > 1) { if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && bin.bushelsPerTonne() > 1) {
diffDisplay = diffDisplay / bin.bushelsPerTonne(); diffDisplay = diffDisplay / bin.bushelsPerTonne();
if (pendingDisplay) { if (pendingDisplay) {
pendingDisplay = pendingDisplay / bin.bushelsPerTonne(); pendingDisplay = pendingDisplay / bin.bushelsPerTonne();
} }
} }
if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && bin.bushelsPerTonne() > 1) { if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && bin.bushelsPerTonne() > 1) {
diffDisplay = diffDisplay / (bin.bushelsPerTonne()*0.907); diffDisplay = diffDisplay / (bin.bushelsPerTonne()*0.907);
if (pendingDisplay) { if (pendingDisplay) {
pendingDisplay = pendingDisplay / (bin.bushelsPerTonne()*0.907); pendingDisplay = pendingDisplay / (bin.bushelsPerTonne()*0.907);
@ -1676,7 +1676,7 @@ export default function BinVisualizer(props: Props) {
fontWeight: 650, fontWeight: 650,
color: tempColour color: tempColour
}}> }}>
{ambient?.getTempString(getTemperatureUnit())} {ambient?.getTempString(user.tempUnit())}
</Typography> </Typography>
</Box> </Box>
<Box display="flex" marginY={1}> <Box display="flex" marginY={1}>

View file

@ -35,7 +35,7 @@ import { Bin } from "models";
import { pond } from "protobuf-ts/pond"; import { pond } from "protobuf-ts/pond";
import { useBinAPI, useGlobalState } from "providers"; import { useBinAPI, useGlobalState } from "providers";
import React, { useEffect, useState, useCallback, SetStateAction } from "react"; import React, { useEffect, useState, useCallback, SetStateAction } from "react";
import { getGrainUnit, stringToMaterialColour } from "utils"; import { stringToMaterialColour } from "utils";
//import BinsFansStatusTable from "./BinFansStatusTable"; //import BinsFansStatusTable from "./BinFansStatusTable";
import BinsList from "./BinsList"; import BinsList from "./BinsList";
@ -95,7 +95,7 @@ export default function BinyardDisplay(props: Props) {
const isMobile = useMobile(); const isMobile = useMobile();
const [carouselIndex, setCarouselIndex] = useState(0); const [carouselIndex, setCarouselIndex] = useState(0);
const [binMenuAnchorEl, setBinMenuAnchorEl] = useState<Element | null>(null); const [binMenuAnchorEl, setBinMenuAnchorEl] = useState<Element | null>(null);
const [{ as }] = useGlobalState(); const [{ as, user }] = useGlobalState();
const maxBins = 40; const maxBins = 40;
const [binsLoading, setBinsLoading] = useState(false); const [binsLoading, setBinsLoading] = useState(false);
const [expandTotal, setExpandTotal] = useState(false); const [expandTotal, setExpandTotal] = useState(false);
@ -658,7 +658,7 @@ export default function BinyardDisplay(props: Props) {
return " bu"; return " bu";
} }
switch (getGrainUnit()) { switch (user.grainUnit()) {
case pond.GrainUnit.GRAIN_UNIT_TONNE: case pond.GrainUnit.GRAIN_UNIT_TONNE:
return " mT"; return " mT";
case pond.GrainUnit.GRAIN_UNIT_TON: case pond.GrainUnit.GRAIN_UNIT_TON:
@ -671,9 +671,9 @@ export default function BinyardDisplay(props: Props) {
const customQuantityDisplay = (customInventory: pond.CustomInventory, bushels: number) => { const customQuantityDisplay = (customInventory: pond.CustomInventory, bushels: number) => {
let amount = bushels let amount = bushels
if(customInventory.bushelsPerTonne > 1){ if(customInventory.bushelsPerTonne > 1){
if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE){ if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE){
amount = bushels/customInventory.bushelsPerTonne amount = bushels/customInventory.bushelsPerTonne
}else if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON){ }else if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON){
amount = bushels/(customInventory.bushelsPerTonne*0.907) amount = bushels/(customInventory.bushelsPerTonne*0.907)
} }
} }
@ -683,9 +683,9 @@ export default function BinyardDisplay(props: Props) {
const supportedGrainDisplay = (grain: pond.Grain, bushels: number) => { const supportedGrainDisplay = (grain: pond.Grain, bushels: number) => {
const describer = GrainDescriber(grain) const describer = GrainDescriber(grain)
let amount = bushels let amount = bushels
if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE){ if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE){
amount = bushels/describer.bushelsPerTonne amount = bushels/describer.bushelsPerTonne
}else if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON){ }else if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON){
amount = bushels/describer.bushelsPerTon amount = bushels/describer.bushelsPerTon
} }
return Math.round(amount*100)/100 return Math.round(amount*100)/100

View file

@ -19,7 +19,6 @@ import { sameComponentID } from "pbHelpers/Component";
import moment from "moment"; import moment from "moment";
import { lowerCase } from "lodash"; import { lowerCase } from "lodash";
import { describeMeasurement } from "pbHelpers/MeasurementDescriber"; import { describeMeasurement } from "pbHelpers/MeasurementDescriber";
import { fahrenheitToCelsius, getTemperatureUnit } from "utils";
import { GetGrainExtensionMap } from "grain"; import { GetGrainExtensionMap } from "grain";
import { PromiseProgress, Stage, Step as PromiseStep } from "common/PromiseProgress"; import { PromiseProgress, Stage, Step as PromiseStep } from "common/PromiseProgress";
@ -76,7 +75,7 @@ export default function ModeChangeDialog(props: Props){
changeOutdoorHumidity, changeOutdoorHumidity,
presets presets
} = props } = props
const [{as}] = useGlobalState() const [{as, user}] = useGlobalState()
const [componentSets, setComponentSets] = useState<ComponentSet[]>([]) const [componentSets, setComponentSets] = useState<ComponentSet[]>([])
const interactionAPI = useInteractionsAPI() const interactionAPI = useInteractionsAPI()
const componentAPI = useComponentAPI(); const componentAPI = useComponentAPI();
@ -296,7 +295,7 @@ if (!selectedDevice) return;
conditions.push(humidityCondition); conditions.push(humidityCondition);
//since the measurement describers function to stored does a conversion into celsius if the users pref is fahrenheit for temperature we need to convert the preset value into fahrenheit //since the measurement describers function to stored does a conversion into celsius if the users pref is fahrenheit for temperature we need to convert the preset value into fahrenheit
if (getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) { if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
temp = Math.round((temp * (9 / 5) + 32) * 100) / 100; temp = Math.round((temp * (9 / 5) + 32) * 100) / 100;
} }
let tempVal = describeMeasurement( let tempVal = describeMeasurement(
@ -392,7 +391,7 @@ if (!selectedDevice) return;
conditions.push(fanConditionOne); conditions.push(fanConditionOne);
//since the measurement describers function toStored does a conversion into celsius for temperature if the users pref is fahrenheit we need to convert the preset value into fahrenheit //since the measurement describers function toStored does a conversion into celsius for temperature if the users pref is fahrenheit we need to convert the preset value into fahrenheit
if (getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) { if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
tempPreset = Math.round((tempPreset * (9 / 5) + 32) * 100) / 100; tempPreset = Math.round((tempPreset * (9 / 5) + 32) * 100) / 100;
} }
let tempVal = describeMeasurement( let tempVal = describeMeasurement(
@ -631,7 +630,7 @@ if (!selectedDevice) return;
InputProps={{ InputProps={{
endAdornment: ( endAdornment: (
<InputAdornment position="end"> <InputAdornment position="end">
{getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT {user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT
? "°F" ? "°F"
: "℃"} : "℃"}
</InputAdornment> </InputAdornment>

View file

@ -11,7 +11,6 @@ import { pond } from "protobuf-ts/pond";
import { useBinAPI, useGlobalState } from "providers"; import { useBinAPI, useGlobalState } from "providers";
import { useCallback, useEffect, useState } from "react"; import { useCallback, useEffect, useState } from "react";
import { Legend } from "recharts"; import { Legend } from "recharts";
import { getGrainUnit } from "utils";
import BinLevelAreaGraph from "./BinLevelAreaGraph"; import BinLevelAreaGraph from "./BinLevelAreaGraph";
interface Props { interface Props {
@ -32,7 +31,7 @@ interface InventoryAt {
export default function BinLevelOverTime(props: Props) { export default function BinLevelOverTime(props: Props) {
const { bin, fertilizerBin, colour, startDate, endDate, customHeight } = props; const { bin, fertilizerBin, colour, startDate, endDate, customHeight } = props;
const binAPI = useBinAPI(); const binAPI = useBinAPI();
const [{as}] = useGlobalState(); const [{as, user}] = useGlobalState();
const [inventoryData, setInventoryData] = useState<InventoryAt[]>([]); const [inventoryData, setInventoryData] = useState<InventoryAt[]>([]);
const [dataLoading, setDataLoading] = useState(false); const [dataLoading, setDataLoading] = useState(false);
const [capacity, setCapacity] = useState<number | undefined>(); const [capacity, setCapacity] = useState<number | undefined>();
@ -69,10 +68,10 @@ export default function BinLevelOverTime(props: Props) {
if (fertilizerBin && cap) { if (fertilizerBin && cap) {
cap = cap * 35.239; cap = cap * 35.239;
} }
if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && cap) { if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && cap) {
cap = cap / bin.bushelsPerTonne(); cap = cap / bin.bushelsPerTonne();
} }
if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && cap) { if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && cap) {
cap = cap / (bin.bushelsPerTonne()*0.907); cap = cap / (bin.bushelsPerTonne()*0.907);
} }
setCapacity(cap); setCapacity(cap);
@ -89,10 +88,10 @@ export default function BinLevelOverTime(props: Props) {
let bushels = hist.settings.inventory.grainBushels ?? 0; let bushels = hist.settings.inventory.grainBushels ?? 0;
if (bushels !== lastBushels) { if (bushels !== lastBushels) {
let grainDisplay = bushels let grainDisplay = bushels
if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && bin.bushelsPerTonne() > 1){ if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && bin.bushelsPerTonne() > 1){
grainDisplay = Math.round((bushels / bin.bushelsPerTonne()) * 100) / 100; grainDisplay = Math.round((bushels / bin.bushelsPerTonne()) * 100) / 100;
} }
if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && bin.bushelsPerTonne() > 1){ if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && bin.bushelsPerTonne() > 1){
grainDisplay = Math.round((bushels / (bin.bushelsPerTonne()*0.907)) * 100) / 100; grainDisplay = Math.round((bushels / (bin.bushelsPerTonne()*0.907)) * 100) / 100;
} }
let newData: InventoryAt = { let newData: InventoryAt = {
@ -121,10 +120,10 @@ export default function BinLevelOverTime(props: Props) {
if (data.length === 0) { if (data.length === 0) {
let bushels = bin.bushels(); let bushels = bin.bushels();
let grainDisplay = bushels let grainDisplay = bushels
if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && bin.bushelsPerTonne() > 1){ if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && bin.bushelsPerTonne() > 1){
grainDisplay = Math.round((bushels / bin.bushelsPerTonne()) * 100) / 100; grainDisplay = Math.round((bushels / bin.bushelsPerTonne()) * 100) / 100;
} }
if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && bin.bushelsPerTonne() > 1){ if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && bin.bushelsPerTonne() > 1){
grainDisplay = Math.round((bushels / (bin.bushelsPerTonne()*0.907)) * 100) / 100; grainDisplay = Math.round((bushels / (bin.bushelsPerTonne()*0.907)) * 100) / 100;
} }
data.push({ data.push({
@ -156,10 +155,10 @@ export default function BinLevelOverTime(props: Props) {
if (fertilizerBin && cap) { if (fertilizerBin && cap) {
cap = cap * 35.239; cap = cap * 35.239;
} }
if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && cap) { if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && cap) {
cap = cap / bin.bushelsPerTonne(); cap = cap / bin.bushelsPerTonne();
} }
if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && cap) { if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && cap) {
cap = cap / (bin.bushelsPerTonne()*0.907); cap = cap / (bin.bushelsPerTonne()*0.907);
} }
setCapacity(cap); setCapacity(cap);
@ -180,10 +179,10 @@ export default function BinLevelOverTime(props: Props) {
if (val.values[0] && m.timestamps[i]) { if (val.values[0] && m.timestamps[i]) {
if (lastBushels !== val.values[0]) { if (lastBushels !== val.values[0]) {
let grainDisplay = val.values[0] let grainDisplay = val.values[0]
if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && bin.bushelsPerTonne() > 1){ if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && bin.bushelsPerTonne() > 1){
grainDisplay = Math.round((grainDisplay / bin.bushelsPerTonne()) * 100) / 100; grainDisplay = Math.round((grainDisplay / bin.bushelsPerTonne()) * 100) / 100;
} }
if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && bin.bushelsPerTonne() > 1){ if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && bin.bushelsPerTonne() > 1){
grainDisplay = Math.round((grainDisplay / (bin.bushelsPerTonne()*0.907)) * 100) / 100; grainDisplay = Math.round((grainDisplay / (bin.bushelsPerTonne()*0.907)) * 100) / 100;
} }
autoBarData.push({ autoBarData.push({
@ -199,10 +198,10 @@ export default function BinLevelOverTime(props: Props) {
let bushels = bin.bushels(); let bushels = bin.bushels();
let currentTime = moment().valueOf(); let currentTime = moment().valueOf();
let grainDisplay = bushels let grainDisplay = bushels
if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && bin.bushelsPerTonne() > 1){ if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && bin.bushelsPerTonne() > 1){
grainDisplay = Math.round((bushels / bin.bushelsPerTonne()) * 100) / 100; grainDisplay = Math.round((bushels / bin.bushelsPerTonne()) * 100) / 100;
} }
if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && bin.bushelsPerTonne() > 1){ if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && bin.bushelsPerTonne() > 1){
grainDisplay = Math.round((bushels / (bin.bushelsPerTonne()*0.907)) * 100) / 100; grainDisplay = Math.round((bushels / (bin.bushelsPerTonne()*0.907)) * 100) / 100;
} }
autoBarData.push({ autoBarData.push({

View file

@ -2,6 +2,7 @@ import { colors } from "@mui/material";
import { jsonBin } from "common/DataImports/BinCables/BinCableImporter"; import { jsonBin } from "common/DataImports/BinCables/BinCableImporter";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { distanceConversion } from "utils"; import { distanceConversion } from "utils";
import { useGlobalState } from "providers";
interface Props { interface Props {
bin: jsonBin; bin: jsonBin;
@ -22,6 +23,8 @@ export default function SideView(props: Props) {
const dimensionFontSize = 7; const dimensionFontSize = 7;
const gapSize = svgViewBoxSize * 0.05; const gapSize = svgViewBoxSize * 0.05;
const { bin } = props; const { bin } = props;
const [{ user }] = useGlobalState();
const distanceUnit = user.distanceUnit();
const [scale, setScale] = useState(0); //the scale to multiply the bin dimension by in order to determine how long to draw the line const [scale, setScale] = useState(0); //the scale to multiply the bin dimension by in order to determine how long to draw the line
const [hopperHeight, setHopperHeight] = useState(0); const [hopperHeight, setHopperHeight] = useState(0);
const [roofHeight, setRoofHeight] = useState(0); const [roofHeight, setRoofHeight] = useState(0);
@ -194,7 +197,7 @@ export default function SideView(props: Props) {
"bottomText", "bottomText",
svgViewBoxSize / 2, svgViewBoxSize / 2,
y2, y2,
distanceConversion(bin.Diameter).toFixed(1) distanceConversion(bin.Diameter, distanceUnit).toFixed(1)
) )
); );
@ -231,7 +234,7 @@ export default function SideView(props: Props) {
let y2 = (roofHeight + bin.Sidewall) * scale; let y2 = (roofHeight + bin.Sidewall) * scale;
d.push( d.push(
dimensionText("sidewallText", x2, midpoint, distanceConversion(bin.Sidewall).toFixed(1)) dimensionText("sidewallText", x2, midpoint, distanceConversion(bin.Sidewall, distanceUnit).toFixed(1))
); );
d.push( d.push(
dimensionPath( dimensionPath(
@ -270,7 +273,7 @@ export default function SideView(props: Props) {
"peakText", "peakText",
xText, xText,
midpoint, midpoint,
distanceConversion(bin.Sidewall + roofHeight).toFixed(1) distanceConversion(bin.Sidewall + roofHeight, distanceUnit).toFixed(1)
) )
); );
d.push( d.push(
@ -313,7 +316,7 @@ export default function SideView(props: Props) {
"totalText", "totalText",
x2, x2,
midpoint, midpoint,
distanceConversion(roofHeight + bin.Sidewall + hopperHeight).toFixed(1) distanceConversion(roofHeight + bin.Sidewall + hopperHeight, distanceUnit).toFixed(1)
) )
); );
d.push( d.push(

View file

@ -18,7 +18,7 @@ import {
//import MaterialTable from "material-table"; //import MaterialTable from "material-table";
//import { getTableIcons } from "common/ResponsiveTable"; //import { getTableIcons } from "common/ResponsiveTable";
import CableDisplay from "./cableDisplay"; import CableDisplay from "./cableDisplay";
import { distanceConversion, getDistanceUnit } from "utils"; import { distanceConversion } from "utils";
import { pond } from "protobuf-ts/pond"; import { pond } from "protobuf-ts/pond";
import CableQuote from "./cableQuote"; import CableQuote from "./cableQuote";
import { useMobile } from "hooks"; import { useMobile } from "hooks";
@ -33,6 +33,7 @@ import {
} from "common/TrigFunctions"; } from "common/TrigFunctions";
import ResponsiveTable, { Column } from "common/ResponsiveTable"; import ResponsiveTable, { Column } from "common/ResponsiveTable";
import { makeStyles } from "@mui/styles"; import { makeStyles } from "@mui/styles";
import { useGlobalState } from "providers";
const useStyles = makeStyles(() => ({ const useStyles = makeStyles(() => ({
sliderThumb: { sliderThumb: {
@ -56,6 +57,7 @@ export default function CableEstimator() {
//the conversion constant to convert cubic feet to bushels //the conversion constant to convert cubic feet to bushels
const conversionConstantFT = 0.7786; const conversionConstantFT = 0.7786;
const classes = useStyles(); const classes = useStyles();
const [{ user }] = useGlobalState();
const [binOptions, setBinOptions] = useState<jsonBin[]>([]); const [binOptions, setBinOptions] = useState<jsonBin[]>([]);
const [displayedRows, setDisplayedRows] = useState<jsonBin[]>([]); const [displayedRows, setDisplayedRows] = useState<jsonBin[]>([]);
const [tablePage, setTablePage] = useState(0) const [tablePage, setTablePage] = useState(0)
@ -67,21 +69,21 @@ export default function CableEstimator() {
const [quoteOpen, setQuoteOpen] = useState(false); const [quoteOpen, setQuoteOpen] = useState(false);
const [useCustomBin, setUseCustomBin] = useState(false); const [useCustomBin, setUseCustomBin] = useState(false);
const [diameterFormEntry, setDiameterFormEntry] = useState( const [diameterFormEntry, setDiameterFormEntry] = useState(
getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS ? "9.1" : "30" user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS ? "9.1" : "30"
); );
//const [capacityFormEntry, setCapacityFormEntry] = useState("0"); //const [capacityFormEntry, setCapacityFormEntry] = useState("0");
const [ringsFormEntry, setRingsFormEntry] = useState("0"); const [ringsFormEntry, setRingsFormEntry] = useState("0");
const [ringHeightFormEntry, setRingHeightFormEntry] = useState("0"); const [ringHeightFormEntry, setRingHeightFormEntry] = useState("0");
const [ringHeightFt, setRingHeightFt] = useState(0); const [ringHeightFt, setRingHeightFt] = useState(0);
const [sidewallFormEntry, setSidewallFormEntry] = useState( const [sidewallFormEntry, setSidewallFormEntry] = useState(
getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS ? "7" : "23" user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS ? "7" : "23"
); );
const [peakFormEntry, setPeakFormEntry] = useState( const [peakFormEntry, setPeakFormEntry] = useState(
getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS ? "9.1" : "30" user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS ? "9.1" : "30"
); );
//const [eaveToPeakFormEntry, setEaveToPeakFormEntry] = useState("0"); //const [eaveToPeakFormEntry, setEaveToPeakFormEntry] = useState("0");
const [roofAngleFormEntry, setRoofAngleFormEntry] = useState( const [roofAngleFormEntry, setRoofAngleFormEntry] = useState(
getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS ? "7.6" : "25" user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS ? "7.6" : "25"
); );
const [hopperAngleFormEntry, setHopperAngleFormEntry] = useState("0"); const [hopperAngleFormEntry, setHopperAngleFormEntry] = useState("0");
const nodeSpacing = 4; const nodeSpacing = 4;
@ -129,24 +131,40 @@ export default function CableEstimator() {
render: (row: jsonBin) => <Box padding={2}>{row.HopperAngle}</Box> render: (row: jsonBin) => <Box padding={2}>{row.HopperAngle}</Box>
}, },
{ {
title: "Diameter " + (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET ? "(ft)" : "(m)"), title: "Diameter " + (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET ? "(ft)" : "(m)"),
sortKey: "Diameter", sortKey: "Diameter",
render: (row: jsonBin) => <Box padding={2}>{distanceConversion(row.Diameter).toFixed(2)}</Box> render: (row: jsonBin) => (
<Box padding={2}>
{distanceConversion(row.Diameter, user.distanceUnit()).toFixed(2)}
</Box>
)
}, },
{ {
title: "Sidewall" + (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET ? "(ft)" : "(m)"), title: "Sidewall" + (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET ? "(ft)" : "(m)"),
sortKey: "Sidewall", sortKey: "Sidewall",
render: (row: jsonBin) => <Box padding={2}>{distanceConversion(row.Sidewall).toFixed(2)}</Box> render: (row: jsonBin) => (
<Box padding={2}>
{distanceConversion(row.Sidewall, user.distanceUnit()).toFixed(2)}
</Box>
)
}, },
{ {
title: "Peak Height" + (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET ? "(ft)" : "(m)"), title: "Peak Height" + (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET ? "(ft)" : "(m)"),
sortKey: "Peak", sortKey: "Peak",
render: (row: jsonBin) => <Box padding={2}>{distanceConversion(row.Peak).toFixed(2)}</Box> render: (row: jsonBin) => (
<Box padding={2}>
{distanceConversion(row.Peak, user.distanceUnit()).toFixed(2)}
</Box>
)
}, },
{ {
title: "Eave To Peak" + (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET ? "(ft)" : "(m)"), title: "Eave To Peak" + (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET ? "(ft)" : "(m)"),
sortKey: "EaveToPeak", sortKey: "EaveToPeak",
render: (row: jsonBin) => <Box padding={2}>{distanceConversion(row.EaveToPeak).toFixed(2)}</Box> render: (row: jsonBin) => (
<Box padding={2}>
{distanceConversion(row.EaveToPeak, user.distanceUnit()).toFixed(2)}
</Box>
)
}, },
{ {
title: "Roof Angle", title: "Roof Angle",
@ -545,7 +563,7 @@ export default function CableEstimator() {
InputProps={{ InputProps={{
endAdornment: ( endAdornment: (
<InputAdornment position="end"> <InputAdornment position="end">
{getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS ? "m" : "ft"} {user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS ? "m" : "ft"}
</InputAdornment> </InputAdornment>
) )
}} }}
@ -554,7 +572,7 @@ export default function CableEstimator() {
onChange={e => { onChange={e => {
setDiameterFormEntry(e.target.value); setDiameterFormEntry(e.target.value);
let val = isNaN(parseFloat(e.target.value)) ? 0 : parseFloat(e.target.value); let val = isNaN(parseFloat(e.target.value)) ? 0 : parseFloat(e.target.value);
if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) { if (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) {
val = val * 3.281; val = val * 3.281;
} }
//when the diameter is changed use the current peak height to adjust the roof angle //when the diameter is changed use the current peak height to adjust the roof angle
@ -577,7 +595,7 @@ export default function CableEstimator() {
InputProps={{ InputProps={{
endAdornment: ( endAdornment: (
<InputAdornment position="end"> <InputAdornment position="end">
{getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS ? "m" : "ft"} {user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS ? "m" : "ft"}
</InputAdornment> </InputAdornment>
) )
}} }}
@ -589,7 +607,7 @@ export default function CableEstimator() {
onChange={e => { onChange={e => {
setPeakFormEntry(e.target.value); setPeakFormEntry(e.target.value);
let val = isNaN(parseFloat(e.target.value)) ? 0 : parseFloat(e.target.value); let val = isNaN(parseFloat(e.target.value)) ? 0 : parseFloat(e.target.value);
if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) { if (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) {
val = val * 3.281; val = val * 3.281;
} }
//when the peak height is changed use the diameter to calculate the roof angle //when the peak height is changed use the diameter to calculate the roof angle
@ -621,7 +639,7 @@ export default function CableEstimator() {
//use the new number of rings and ring height to get the sidewall height //use the new number of rings and ring height to get the sidewall height
let sidewallFt = val * ringHeightFt; let sidewallFt = val * ringHeightFt;
bin.Sidewall = sidewallFt; bin.Sidewall = sidewallFt;
if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) { if (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) {
setSidewallFormEntry((sidewallFt / 3.281).toString()); setSidewallFormEntry((sidewallFt / 3.281).toString());
} else { } else {
setSidewallFormEntry(sidewallFt.toString()); setSidewallFormEntry(sidewallFt.toString());
@ -650,7 +668,7 @@ export default function CableEstimator() {
onChange={e => { onChange={e => {
setRingHeightFormEntry(e.target.value); setRingHeightFormEntry(e.target.value);
let inVal = isNaN(parseFloat(e.target.value)) ? 0 : parseFloat(e.target.value); let inVal = isNaN(parseFloat(e.target.value)) ? 0 : parseFloat(e.target.value);
// if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) { // if (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) {
// val = val * 3.281; // val = val * 3.281;
// } // }
let ringFt = inVal / 12; let ringFt = inVal / 12;
@ -659,7 +677,7 @@ export default function CableEstimator() {
let bin = cloneDeep(customBin); let bin = cloneDeep(customBin);
let sidewallFt = bin.Rings * ringFt; let sidewallFt = bin.Rings * ringFt;
bin.Sidewall = sidewallFt; bin.Sidewall = sidewallFt;
if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) { if (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) {
setSidewallFormEntry((sidewallFt / 3.281).toString()); setSidewallFormEntry((sidewallFt / 3.281).toString());
} else { } else {
setSidewallFormEntry(sidewallFt.toString()); setSidewallFormEntry(sidewallFt.toString());
@ -683,7 +701,7 @@ export default function CableEstimator() {
InputProps={{ InputProps={{
endAdornment: ( endAdornment: (
<InputAdornment position="end"> <InputAdornment position="end">
{getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS ? "m" : "ft"} {user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS ? "m" : "ft"}
</InputAdornment> </InputAdornment>
) )
}} }}
@ -694,7 +712,7 @@ export default function CableEstimator() {
setSidewallFormEntry(e.target.value); setSidewallFormEntry(e.target.value);
let val = isNaN(parseFloat(e.target.value)) ? 0 : parseFloat(e.target.value); let val = isNaN(parseFloat(e.target.value)) ? 0 : parseFloat(e.target.value);
//if the value entered is meters convert it to feet for the bin //if the value entered is meters convert it to feet for the bin
if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) { if (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) {
val = val * 3.281; val = val * 3.281;
} }
let bin = cloneDeep(customBin); let bin = cloneDeep(customBin);
@ -733,7 +751,7 @@ export default function CableEstimator() {
let coneHeight = calcConeHeight(bin.Diameter / 2, bin.RoofAngle); let coneHeight = calcConeHeight(bin.Diameter / 2, bin.RoofAngle);
bin.Peak = bin.Sidewall + coneHeight; //assign it as feet bin.Peak = bin.Sidewall + coneHeight; //assign it as feet
let peakHeightDisplay = bin.Sidewall + coneHeight; let peakHeightDisplay = bin.Sidewall + coneHeight;
if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) { if (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) {
peakHeightDisplay = peakHeightDisplay / 3.281; peakHeightDisplay = peakHeightDisplay / 3.281;
} }
setPeakFormEntry(peakHeightDisplay.toFixed(1)); setPeakFormEntry(peakHeightDisplay.toFixed(1));
@ -762,7 +780,7 @@ export default function CableEstimator() {
let coneHeight = calcConeHeight(bin.Diameter / 2, bin.RoofAngle); let coneHeight = calcConeHeight(bin.Diameter / 2, bin.RoofAngle);
bin.Peak = bin.Sidewall + coneHeight; bin.Peak = bin.Sidewall + coneHeight;
let peakHeightDisplay = bin.Sidewall + coneHeight; let peakHeightDisplay = bin.Sidewall + coneHeight;
if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) { if (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) {
peakHeightDisplay = peakHeightDisplay / 3.281; peakHeightDisplay = peakHeightDisplay / 3.281;
} }
setPeakFormEntry(peakHeightDisplay.toFixed(1)); setPeakFormEntry(peakHeightDisplay.toFixed(1));
@ -887,7 +905,7 @@ export default function CableEstimator() {
setEaveToPeakFormEntry(e.target.value); setEaveToPeakFormEntry(e.target.value);
let val = isNaN(parseFloat(e.target.value)) ? 0 : parseFloat(e.target.value); let val = isNaN(parseFloat(e.target.value)) ? 0 : parseFloat(e.target.value);
//if the value entered is meters convert it to feet for the bin //if the value entered is meters convert it to feet for the bin
if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) { if (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) {
val = val * 3.281; val = val * 3.281;
} }
let bin = cloneDeep(customBin); let bin = cloneDeep(customBin);

View file

@ -4,8 +4,8 @@ import ResponsiveTable, { Column } from "common/ResponsiveTable";
//import { getTableIcons } from "common/ResponsiveTable"; //import { getTableIcons } from "common/ResponsiveTable";
//import MaterialTable from "material-table"; //import MaterialTable from "material-table";
import { pond } from "protobuf-ts/pond"; import { pond } from "protobuf-ts/pond";
import { useEffect } from "react"; import { distanceConversion } from "utils";
import { distanceConversion, getDistanceUnit } from "utils"; import { useGlobalState } from "providers";
interface Props { interface Props {
bin: jsonBin; bin: jsonBin;
@ -13,6 +13,7 @@ interface Props {
export default function CableMounting(props: Props) { export default function CableMounting(props: Props) {
const { bin } = props; const { bin } = props;
const [{ user }] = useGlobalState();
// const [tablePage, setTablePage] = useState(0) // const [tablePage, setTablePage] = useState(0)
// const [pageSize, setPageSize] = useState(10) // const [pageSize, setPageSize] = useState(10)
@ -26,15 +27,19 @@ export default function CableMounting(props: Props) {
render: (row: CableSum) => <Box padding={2}>{row.Orbit === 0 ? "Center" : row.Orbit}</Box> render: (row: CableSum) => <Box padding={2}>{row.Orbit === 0 ? "Center" : row.Orbit}</Box>
}, },
{ {
title: "From Center " + (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET ? "(ft)" : "(m)"), title: "From Center " + (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET ? "(ft)" : "(m)"),
render: (row: CableSum) => ( render: (row: CableSum) => (
<Box padding={2}>{distanceConversion(row.DistanceFromCenter).toFixed(2)}</Box> <Box padding={2}>
{distanceConversion(row.DistanceFromCenter, user.distanceUnit()).toFixed(2)}
</Box>
) )
}, },
{ {
title: "Along Roof" + (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET ? "(ft)" : "(m)"), title: "Along Roof" + (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET ? "(ft)" : "(m)"),
render: (row: CableSum) => ( render: (row: CableSum) => (
<Box padding={2}>{distanceConversion(roofDist(row.DistanceFromCenter)).toFixed(2)}</Box> <Box padding={2}>
{distanceConversion(roofDist(row.DistanceFromCenter), user.distanceUnit()).toFixed(2)}
</Box>
) )
}, },
{ {

View file

@ -5,6 +5,7 @@ import ResponsiveDialog from "common/ResponsiveDialog";
import { useMobile } from "hooks"; import { useMobile } from "hooks";
import { jsonBin } from "common/DataImports/BinCables/BinCableImporter"; import { jsonBin } from "common/DataImports/BinCables/BinCableImporter";
import PdfContent from "./pdfComponents/pdfContent"; import PdfContent from "./pdfComponents/pdfContent";
import { useGlobalState } from "providers";
interface Props { interface Props {
bin: jsonBin; bin: jsonBin;
@ -14,6 +15,7 @@ interface Props {
export default function CableQuote(props: Props) { export default function CableQuote(props: Props) {
const { open, close, bin } = props; const { open, close, bin } = props;
const [{user}] = useGlobalState();
const isMobil = useMobile(); const isMobil = useMobile();
const pdfStyle = StyleSheet.create({ const pdfStyle = StyleSheet.create({
viewerDesktop: { viewerDesktop: {
@ -27,7 +29,11 @@ export default function CableQuote(props: Props) {
}); });
const pdfDoc = () => { const pdfDoc = () => {
return <AgPDFTemplate content={<PdfContent bin={bin} />} />; return (
<AgPDFTemplate
content={<PdfContent bin={bin} distanceUnit={user.distanceUnit()} />}
/>
);
}; };
return ( return (

View file

@ -3,7 +3,8 @@ import { Box, Typography } from "@mui/material";
//import MaterialTable from "material-table"; //import MaterialTable from "material-table";
import { pond } from "protobuf-ts/pond"; import { pond } from "protobuf-ts/pond";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { distanceConversion, getDistanceUnit } from "utils"; import { distanceConversion } from "utils";
import { useGlobalState } from "providers";
import { Cable, jsonBin } from "common/DataImports/BinCables/BinCableImporter"; import { Cable, jsonBin } from "common/DataImports/BinCables/BinCableImporter";
import { cloneDeep } from "lodash"; import { cloneDeep } from "lodash";
import ResponsiveTable, { Column } from "common/ResponsiveTable"; import ResponsiveTable, { Column } from "common/ResponsiveTable";
@ -15,17 +16,19 @@ interface Props {
export default function CableTable(props: Props) { export default function CableTable(props: Props) {
const { bin, /*cablesChanged*/ } = props; const { bin, /*cablesChanged*/ } = props;
const [{ user }] = useGlobalState();
//const nodeSpacing = 4; //space between the nodes in feet //const nodeSpacing = 4; //space between the nodes in feet
const [cableData, setCableData] = useState(bin.Cables); const [cableData, setCableData] = useState(bin.Cables);
useEffect(() => { useEffect(() => {
let convertedCables: Cable[] = cloneDeep(bin.Cables); let convertedCables: Cable[] = cloneDeep(bin.Cables);
const du = user.distanceUnit();
convertedCables.forEach(cable => { convertedCables.forEach(cable => {
cable.Length = Math.round(distanceConversion(cable.Length) * 100) / 100; cable.Length = Math.round(distanceConversion(cable.Length, du) * 100) / 100;
}); });
setCableData(convertedCables); setCableData(convertedCables);
}, [bin]); }, [bin, user]);
// const table = () => { // const table = () => {
// return ( // return (
@ -116,7 +119,7 @@ export default function CableTable(props: Props) {
render: (row) => <Box padding={2}>{row.Count}</Box> render: (row) => <Box padding={2}>{row.Count}</Box>
}, },
{ {
title: "Length" + (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET ? "(ft)" : "(m)"), title: "Length" + (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET ? "(ft)" : "(m)"),
render: (row) => <Box padding={2}>{row.Length}</Box> render: (row) => <Box padding={2}>{row.Length}</Box>
}, },
{ {

View file

@ -1,9 +1,9 @@
import { StyleSheet, Text, View } from "@react-pdf/renderer"; import { StyleSheet, Text, View } from "@react-pdf/renderer";
import { pond } from "protobuf-ts/pond"; import { pond } from "protobuf-ts/pond";
import { getDistanceUnit } from "utils";
interface Props { interface Props {
borderColour: string; borderColour: string;
distanceUnit: pond.DistanceUnit;
} }
export default function CableTableHeader(props: Props) { export default function CableTableHeader(props: Props) {
@ -35,7 +35,8 @@ export default function CableTableHeader(props: Props) {
<Text style={styles.cell}>Type</Text> <Text style={styles.cell}>Type</Text>
<Text style={styles.cell}>Qty</Text> <Text style={styles.cell}>Qty</Text>
<Text style={styles.cell}> <Text style={styles.cell}>
Length {getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET ? "(ft)" : "(m)"} Length{" "}
{props.distanceUnit === pond.DistanceUnit.DISTANCE_UNIT_FEET ? "(ft)" : "(m)"}
</Text> </Text>
<Text style={styles.endCell}>Nodes</Text> <Text style={styles.endCell}>Nodes</Text>
</View> </View>

View file

@ -2,10 +2,12 @@ import { StyleSheet, Text, View } from "@react-pdf/renderer";
import { jsonBin } from "common/DataImports/BinCables/BinCableImporter"; import { jsonBin } from "common/DataImports/BinCables/BinCableImporter";
import React from "react"; import React from "react";
import { distanceConversion } from "utils"; import { distanceConversion } from "utils";
import { pond } from "protobuf-ts/pond";
interface Props { interface Props {
bin: jsonBin; bin: jsonBin;
borderColour: string; borderColour: string;
distanceUnit: pond.DistanceUnit;
} }
export default function CableTableRow(props: Props) { export default function CableTableRow(props: Props) {
@ -48,7 +50,9 @@ export default function CableTableRow(props: Props) {
<Text style={styles.cell}>{cable.Orbit === 0 ? "Center" : cable.Orbit}</Text> <Text style={styles.cell}>{cable.Orbit === 0 ? "Center" : cable.Orbit}</Text>
<Text style={styles.cell}>{cable.Type === 2 ? "Moisture" : "Temperature"}</Text> <Text style={styles.cell}>{cable.Type === 2 ? "Moisture" : "Temperature"}</Text>
<Text style={styles.cell}>{cable.Count}</Text> <Text style={styles.cell}>{cable.Count}</Text>
<Text style={styles.cell}>{distanceConversion(cable.Length).toFixed(2)}</Text> <Text style={styles.cell}>
{distanceConversion(cable.Length, props.distanceUnit).toFixed(2)}
</Text>
<Text style={styles.endCell}>{cable.Nodes}</Text> <Text style={styles.endCell}>{cable.Nodes}</Text>
</View> </View>
); );

View file

@ -1,9 +1,9 @@
import { StyleSheet, Text, View } from "@react-pdf/renderer"; import { StyleSheet, Text, View } from "@react-pdf/renderer";
import { pond } from "protobuf-ts/pond"; import { pond } from "protobuf-ts/pond";
import { getDistanceUnit } from "utils";
interface Props { interface Props {
borderColour: string; borderColour: string;
distanceUnit: pond.DistanceUnit;
} }
export default function MountingTableHeader(props: Props) { export default function MountingTableHeader(props: Props) {
@ -43,10 +43,12 @@ export default function MountingTableHeader(props: Props) {
<View style={styles.view}> <View style={styles.view}>
<Text style={styles.orbit}>Orbit</Text> <Text style={styles.orbit}>Orbit</Text>
<Text style={styles.centerDist}> <Text style={styles.centerDist}>
Radius {getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET ? "(ft)" : "(m)"} Radius{" "}
{props.distanceUnit === pond.DistanceUnit.DISTANCE_UNIT_FEET ? "(ft)" : "(m)"}
</Text> </Text>
<Text style={styles.roofDist}> <Text style={styles.roofDist}>
Along Roof {getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET ? "(ft)" : "(m)"} Along Roof{" "}
{props.distanceUnit === pond.DistanceUnit.DISTANCE_UNIT_FEET ? "(ft)" : "(m)"}
</Text> </Text>
<Text style={styles.bracket}>Bracket</Text> <Text style={styles.bracket}>Bracket</Text>
</View> </View>

View file

@ -2,10 +2,12 @@ import { StyleSheet, Text, View } from "@react-pdf/renderer";
import { getBracket, jsonBin } from "common/DataImports/BinCables/BinCableImporter"; import { getBracket, jsonBin } from "common/DataImports/BinCables/BinCableImporter";
import React from "react"; import React from "react";
import { distanceConversion } from "utils"; import { distanceConversion } from "utils";
import { pond } from "protobuf-ts/pond";
interface Props { interface Props {
bin: jsonBin; bin: jsonBin;
borderColour: string; borderColour: string;
distanceUnit: pond.DistanceUnit;
} }
export default function MountingTableRow(props: Props) { export default function MountingTableRow(props: Props) {
@ -65,10 +67,10 @@ export default function MountingTableRow(props: Props) {
key={i}> key={i}>
<Text style={styles.orbit}>{orbit.Orbit === 0 ? "C" : orbit.Orbit}</Text> <Text style={styles.orbit}>{orbit.Orbit === 0 ? "C" : orbit.Orbit}</Text>
<Text style={styles.centerDist}> <Text style={styles.centerDist}>
{distanceConversion(orbit.DistanceFromCenter).toFixed(2)} {distanceConversion(orbit.DistanceFromCenter, props.distanceUnit).toFixed(2)}
</Text> </Text>
<Text style={styles.roofDist}> <Text style={styles.roofDist}>
{distanceConversion(roofDist(orbit.DistanceFromCenter)).toFixed(2)} {distanceConversion(roofDist(orbit.DistanceFromCenter), props.distanceUnit).toFixed(2)}
</Text> </Text>
<Text style={styles.bracket}> <Text style={styles.bracket}>
{bracketType} {bracketType}

View file

@ -1,6 +1,7 @@
import { StyleSheet, Text, View } from "@react-pdf/renderer"; import { StyleSheet, Text, View } from "@react-pdf/renderer";
import { colors } from "@mui/material"; import { colors } from "@mui/material";
import { jsonBin } from "common/DataImports/BinCables/BinCableImporter"; import { jsonBin } from "common/DataImports/BinCables/BinCableImporter";
import { pond } from "protobuf-ts/pond";
import CableTableHeader from "./cableTable/cableTableHeader"; import CableTableHeader from "./cableTable/cableTableHeader";
import CableTableRow from "./cableTable/cableTableRow"; import CableTableRow from "./cableTable/cableTableRow";
import PDFSideView from "./pdfSVG/pdfSideView"; import PDFSideView from "./pdfSVG/pdfSideView";
@ -10,9 +11,11 @@ import MountingTableRow from "./mountingTable/mountingTableRows";
interface Props { interface Props {
bin: jsonBin; bin: jsonBin;
distanceUnit: pond.DistanceUnit;
} }
export default function PdfContent(props: Props) { export default function PdfContent(props: Props) {
const { distanceUnit } = props;
const tableBorderColour = colors.grey[800]; const tableBorderColour = colors.grey[800];
const styles = StyleSheet.create({ const styles = StyleSheet.create({
@ -53,7 +56,7 @@ export default function PdfContent(props: Props) {
</View> </View>
<View style={{ height: 280, flexDirection: "row" }}> <View style={{ height: 280, flexDirection: "row" }}>
<View style={{ width: 260 }}> <View style={{ width: 260 }}>
<PDFSideView bin={props.bin} /> <PDFSideView bin={props.bin} distanceUnit={distanceUnit} />
</View> </View>
<View style={{ width: 260 }}> <View style={{ width: 260 }}>
<View style={{ height: 195 }}> <View style={{ height: 195 }}>
@ -63,8 +66,12 @@ export default function PdfContent(props: Props) {
<Text style={{ fontFamily: "Helvetica-Bold", marginBottom: 5 }}>Install Guide</Text> <Text style={{ fontFamily: "Helvetica-Bold", marginBottom: 5 }}>Install Guide</Text>
<View <View
style={{ borderColor: tableBorderColour, borderWidth: 1, flexDirection: "column" }}> style={{ borderColor: tableBorderColour, borderWidth: 1, flexDirection: "column" }}>
<MountingTableHeader borderColour={tableBorderColour} /> <MountingTableHeader borderColour={tableBorderColour} distanceUnit={distanceUnit} />
<MountingTableRow borderColour={tableBorderColour} bin={props.bin} /> <MountingTableRow
borderColour={tableBorderColour}
bin={props.bin}
distanceUnit={distanceUnit}
/>
</View> </View>
</View> </View>
</View> </View>
@ -73,8 +80,12 @@ export default function PdfContent(props: Props) {
Recommended Cables Recommended Cables
</Text> </Text>
<View style={styles.cableTable}> <View style={styles.cableTable}>
<CableTableHeader borderColour={tableBorderColour} /> <CableTableHeader borderColour={tableBorderColour} distanceUnit={distanceUnit} />
<CableTableRow bin={props.bin} borderColour={tableBorderColour} /> <CableTableRow
bin={props.bin}
borderColour={tableBorderColour}
distanceUnit={distanceUnit}
/>
</View> </View>
</View> </View>
); );

View file

@ -1,11 +1,13 @@
import { colors } from "@mui/material"; import { colors } from "@mui/material";
import { Circle, Line, Path, Svg, Text } from "@react-pdf/renderer"; import { Circle, Line, Path, Svg, Text } from "@react-pdf/renderer";
import { jsonBin } from "common/DataImports/BinCables/BinCableImporter"; import { jsonBin } from "common/DataImports/BinCables/BinCableImporter";
import { pond } from "protobuf-ts/pond";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { distanceConversion } from "utils"; import { distanceConversion } from "utils";
interface Props { interface Props {
bin: jsonBin; bin: jsonBin;
distanceUnit: pond.DistanceUnit;
} }
interface svgPoint { interface svgPoint {
@ -22,7 +24,7 @@ export default function PDFSideView(props: Props) {
const nodeSpacing = 4; //space in feet between the nodes const nodeSpacing = 4; //space in feet between the nodes
const dimensionFontSize = 18; const dimensionFontSize = 18;
const gapSize = svgViewBoxSize * 0.05; const gapSize = svgViewBoxSize * 0.05;
const { bin } = props; const { bin, distanceUnit } = props;
const [scale, setScale] = useState(0); //the scale to multiply the bin dimension by in order to determine how long to draw the line const [scale, setScale] = useState(0); //the scale to multiply the bin dimension by in order to determine how long to draw the line
const [hopperHeight, setHopperHeight] = useState(0); const [hopperHeight, setHopperHeight] = useState(0);
const [roofHeight, setRoofHeight] = useState(0); const [roofHeight, setRoofHeight] = useState(0);
@ -194,7 +196,7 @@ export default function PDFSideView(props: Props) {
"bottomText", "bottomText",
svgViewBoxSize / 2, svgViewBoxSize / 2,
y2, y2,
distanceConversion(bin.Diameter).toFixed(1) distanceConversion(bin.Diameter, distanceUnit).toFixed(1)
) )
); );
@ -231,7 +233,12 @@ export default function PDFSideView(props: Props) {
let y2 = (roofHeight + bin.Sidewall) * scale; let y2 = (roofHeight + bin.Sidewall) * scale;
d.push( d.push(
dimensionText("sidewallText", x2, midpoint, distanceConversion(bin.Sidewall).toFixed(1)) dimensionText(
"sidewallText",
x2,
midpoint,
distanceConversion(bin.Sidewall, distanceUnit).toFixed(1)
)
); );
d.push( d.push(
dimensionPath( dimensionPath(
@ -270,7 +277,7 @@ export default function PDFSideView(props: Props) {
"peakText", "peakText",
xText, xText,
midpoint, midpoint,
distanceConversion(bin.Sidewall + roofHeight).toFixed(1) distanceConversion(bin.Sidewall + roofHeight, distanceUnit).toFixed(1)
) )
); );
d.push( d.push(
@ -313,7 +320,7 @@ export default function PDFSideView(props: Props) {
"totalText", "totalText",
x2, x2,
midpoint, midpoint,
distanceConversion(roofHeight + bin.Sidewall + hopperHeight).toFixed(1) distanceConversion(roofHeight + bin.Sidewall + hopperHeight, distanceUnit).toFixed(1)
) )
); );
d.push( d.push(

View file

@ -1,57 +1,79 @@
import { Box, BoxProps, Theme } from "@mui/material"; import { Box, BoxProps, Theme } from "@mui/material";
import { makeStyles } from "@mui/styles"; import { makeStyles } from "@mui/styles";
import classNames from "classnames"; import classNames from "classnames";
import { forwardRef, ReactNode } from "react"; import { forwardRef, ReactNode, useCallback, useState } from "react";
const useStyles = makeStyles((_theme: Theme) => ({ const useStyles = makeStyles((_theme: Theme) => ({
pulseWrapper: { pulseWrapper: {
position: 'relative', position: 'relative',
display: 'block', // Changed to block to respect Box margins display: 'block',
width: 'fit-content', // Shrinks to child width width: 'fit-content',
'&::after': { isolation: 'isolate',
content: '""', overflow: 'visible',
position: 'absolute', },
top: 0, pulseRing: {
left: 0, position: 'absolute',
width: '100%', top: 0,
height: '100%', left: 0,
border: '2px solid var(--pulse-color)', width: '100%',
borderRadius: 'inherit', // Should inherit from Box height: '100%',
animation: '$pulse 1.75s infinite ease-in-out', borderRadius: 'inherit',
pointerEvents: 'none', pointerEvents: 'none',
boxSizing: 'border-box', boxSizing: 'border-box',
transition: "border-color 0.35s ease-in-out", zIndex: -1,
}, animation: '$pulse 4.4s infinite ease-in-out',
}, },
'@keyframes pulse': { '@keyframes pulse': {
from: { /* Start behind box, then expand out and fade */
transform: "scale(1)", /* pulse, pause, pulse, pause - CONSISTENT: both pauses equal, both pulses equal */
opacity: 1 /* Pulse 1 (0-42%) - gradual fade in to soften the start */
}, '0%': { boxShadow: '0 0 0 -2px var(--pulse-color)', opacity: 1 },
to: { '40%': { boxShadow: '0 0 0 14px var(--pulse-color)', opacity: 0 },
transform: "scale(1.25)", '42%': { boxShadow: '0 0 0 -2px var(--pulse-color)', opacity: 0 },
opacity: 0 /* Pause 1 (42-50%) */
} '50%': { boxShadow: '0 0 0 -2px var(--pulse-color)', opacity: 0 },
/* Pulse 2 (50-92%) */
'52%': { boxShadow: '0 0 0 -2px var(--pulse-color)', opacity: 1 },
'90%': { boxShadow: '0 0 0 14px var(--pulse-color)', opacity: 0 },
'92%': { boxShadow: '0 0 0 -2px var(--pulse-color)', opacity: 0 },
/* Pause 2 (92-100%) - same length as Pause 1, color change at iteration */
'100%': { boxShadow: '0 0 0 -2px var(--pulse-color)', opacity: 0 },
}, },
})); }));
interface Props extends BoxProps { interface Props extends BoxProps {
color: string; color: string;
colors?: string[];
children: ReactNode; children: ReactNode;
className?: string; className?: string;
} }
const PulseBox = forwardRef<HTMLDivElement, Props>((props, ref) => { const PulseBox = forwardRef<HTMLDivElement, Props>((props, ref) => {
const { color, children, className, ...boxProps } = props; const { color, colors: colorsProp, children, className, ...boxProps } = props;
const classes = useStyles() const classes = useStyles()
const colors = colorsProp && colorsProp.length > 0 ? colorsProp : [color];
const [colorIndex, setColorIndex] = useState(0);
const displayColor = colors[colorIndex % colors.length] ?? color;
const onAnimationIteration = useCallback(() => {
if (colors.length > 1) {
setColorIndex((i) => (i + 1) % colors.length);
}
}, [colors.length]);
return ( return (
<Box <Box
ref={ref} ref={ref}
{...boxProps} {...boxProps}
className={classNames([classes.pulseWrapper, className])} className={classNames([classes.pulseWrapper, className])}
style={{ '--pulse-color': color } as React.CSSProperties} style={{ '--pulse-color': displayColor } as React.CSSProperties}
> >
<span
className={classes.pulseRing}
aria-hidden
onAnimationIteration={onAnimationIteration}
/>
{children} {children}
</Box> </Box>
); );

View file

@ -16,8 +16,6 @@ const useStyles = makeStyles((theme: Theme) => {
}, },
tableContainer: { tableContainer: {
width: "100%", width: "100%",
maxWidth: "100%",
overflow: "hidden",
minWidth: 0, minWidth: 0,
}, },
title: { title: {
@ -28,13 +26,12 @@ const useStyles = makeStyles((theme: Theme) => {
marginTop: theme.spacing(-2), marginTop: theme.spacing(-2),
}, },
titleComp: { titleComp: {
padding: theme.spacing(1), padding: theme.spacing(1, 1, 0, 1.5),
marginLeft: theme.spacing(-1)
}, },
subtitleComp: { subtitleComp: {
padding: theme.spacing(1), padding: theme.spacing(0, 1, 0.5, 1.5),
marginTop: theme.spacing(-2), display: "flex",
marginLeft: theme.spacing(-1), alignItems: "center",
}, },
titleContainer: { titleContainer: {
border: "none", border: "none",
@ -42,6 +39,8 @@ const useStyles = makeStyles((theme: Theme) => {
searchContainer: { searchContainer: {
border: "none", border: "none",
textAlign: "right", textAlign: "right",
display: "flex",
alignItems: "center",
}, },
mobileTitleBox: { mobileTitleBox: {
margin: theme.spacing(1), margin: theme.spacing(1),
@ -118,8 +117,9 @@ function ResizableHeaderTitle<T>(props: {
: {}), : {}),
}} }}
> >
<Box component="span" sx={{ whiteSpace: "nowrap" }}> <Box component="span" sx={{ whiteSpace: "nowrap", display: "inline-flex", alignItems: "center" }}>
{column.title} {showOrderIcon(column)} <Box component="span">{column.title}</Box>
{showOrderIcon(column)}
</Box> </Box>
</Grid2> </Grid2>
); );
@ -235,6 +235,11 @@ export default function ResponsiveTable<T extends Object>(props: Props<T>) {
localStorage.setItem(filterKey, JSON.stringify(filterList)); localStorage.setItem(filterKey, JSON.stringify(filterList));
}, [filterList]) }, [filterList])
const headerColSpan =
(columns?.length ?? ((rows?.[0] && Object.keys(rows[0]).length) || 0)) +
(rowSelect ? 1 : 0) +
(renderGutter ? 1 : 0);
const openColumnMenu = (event: any) => { const openColumnMenu = (event: any) => {
setColumnAnchor(event.currentTarget); setColumnAnchor(event.currentTarget);
// setUserMenuIsOpen(true); // setUserMenuIsOpen(true);
@ -491,9 +496,17 @@ export default function ResponsiveTable<T extends Object>(props: Props<T>) {
const sortKey = column.sortKey ? column.sortKey : column.title.toLowerCase() const sortKey = column.sortKey ? column.sortKey : column.title.toLowerCase()
if (sortKey === orderBy) { if (sortKey === orderBy) {
if (order === "asc") { if (order === "asc") {
return <ArrowDownward fontSize="small" sx={{ marginLeft: 1 }} /> return (
<Box component="span" sx={{ ml: 0.5, display: "inline-flex", alignItems: "center" }}>
<ArrowDownward fontSize="small" />
</Box>
)
} else { } else {
return <ArrowUpward fontSize="small" sx={{ marginLeft: 1 }} /> return (
<Box component="span" sx={{ ml: 0.5, display: "inline-flex", alignItems: "center" }}>
<ArrowUpward fontSize="small" />
</Box>
)
} }
} }
return null; return null;
@ -536,15 +549,13 @@ export default function ResponsiveTable<T extends Object>(props: Props<T>) {
const clampedWidth = Math.max(MIN_COLUMN_WIDTH, newWidthForIndex) const clampedWidth = Math.max(MIN_COLUMN_WIDTH, newWidthForIndex)
const actualDelta = newWidths[index] - clampedWidth const actualDelta = newWidths[index] - clampedWidth
newWidths[index] = clampedWidth newWidths[index] = clampedWidth
newWidths.every((width, j) => {
if (j <= index || width === 0) return true if (actualDelta > 0) {
else if (newWidths[j] !== undefined) { // Shrinking current column give space to next column
newWidths[j] = newWidths[j] + actualDelta newWidths[index + 1] = (newWidths[index + 1] ?? 0) + actualDelta
return false }
} // When growing (actualDelta < 0): never shrink adjacent columns table grows to accommodate
})
setRowWidths(newWidths) setRowWidths(newWidths)
} }
const MIN_COLUMN_WIDTH = 24; const MIN_COLUMN_WIDTH = 24;
@ -553,14 +564,17 @@ export default function ResponsiveTable<T extends Object>(props: Props<T>) {
<Box className={classes.tableContainer} component={Paper}> <Box className={classes.tableContainer} component={Paper}>
{(title || setSearchText) && ( {(title || setSearchText) && (
<Box> <Box>
<Grid2 container justifyContent="space-between" alignItems="center" sx={{ padding: 1 }}> <Grid2
container
justifyContent="space-between"
alignItems="center"
sx={{ px: 1.5, pt: 1, pb: 0.5 }}
>
<Grid2> <Grid2>
<Box sx={{ marginTop: -1 }}> {renderTitle()}
{renderTitle()} {renderSubtitle()}
{renderSubtitle()}
</Box>
</Grid2> </Grid2>
<Grid2 size={{ xs: "grow" }} sx={{ minWidth: 0, display: "flex", justifyContent: "flex-end" }}> <Grid2 size={{ xs: "grow" }} sx={{ minWidth: 0, display: "flex", justifyContent: "flex-end", mt: -1 }}>
<Grid2 container alignItems="center" spacing={1} wrap="nowrap"> <Grid2 container alignItems="center" spacing={1} wrap="nowrap">
<Grid2> <Grid2>
{actions && actions} {actions && actions}
@ -588,11 +602,24 @@ export default function ResponsiveTable<T extends Object>(props: Props<T>) {
</Box> </Box>
)} )}
<TableContainer sx={{ overflowX: "auto", paddingBottom: 1 }}> <TableContainer sx={{ overflowX: "auto", paddingBottom: 1 }}>
<Table> <Table
sx={{
tableLayout: "fixed",
width: columns && rowWidths.length > 0
? (() => {
const colsWidth = columns.reduce(
(sum, c, i) => sum + (filterList.includes(c.title) || c.hidden ? 0 : (rowWidths[i] ?? 0)),
0
) + (rowSelect ? 48 : 0) + (renderGutter ? 48 : 0)
return colsWidth > 0 ? `max(${colsWidth}px, 100%)` : "100%"
})()
: "100%",
}}
>
<TableHead className={stickyHeader ? classes.stickyHeader : undefined}> <TableHead className={stickyHeader ? classes.stickyHeader : undefined}>
{customElement && {customElement &&
<TableRow> <TableRow>
<TableCell colSpan={10000} sx={{ border: "none" }}> <TableCell colSpan={headerColSpan} sx={{ border: "none" }}>
{customElement} {customElement}
</TableCell> </TableCell>
</TableRow> </TableRow>

View file

@ -5,7 +5,6 @@ import { Device } from "models";
import PulseBox from "./PulseBox"; import PulseBox from "./PulseBox";
import RelativeTimestamp from "./RelativeTimestamp"; import RelativeTimestamp from "./RelativeTimestamp";
import { or } from "utils"; import { or } from "utils";
import { useEffect, useState } from "react";
interface Props { interface Props {
device: Device; device: Device;
@ -23,71 +22,47 @@ const useStyles = makeStyles((theme: Theme) => {
marginRight: "auto", marginRight: "auto",
margin: theme.spacing(1), margin: theme.spacing(1),
width: theme.spacing(16), width: theme.spacing(16),
height: theme.spacing(11), minHeight: theme.spacing(11),
},
readingsWrapper: {
position: "relative",
width: "100%",
minHeight: theme.spacing(10),
}, },
carouselContainer: { carouselContainer: {
position: 'relative', position: "relative",
height: '40px', height: "40px",
overflow: 'hidden', overflow: "hidden",
textAlign: "center", textAlign: "center",
}, },
carouselItem: { carouselItem: {
opacity: 0, opacity: 0,
transform: 'translateX(100%)', transform: "translateX(100%)",
transition: 'opacity 0.5s ease, transform 0.5s ease', transition: "opacity 0.5s ease, transform 0.5s ease",
position: 'absolute', position: "absolute",
width: '100%', width: "100%",
overflow: "hidden", overflow: "hidden",
// height: '100%',
}, },
active: { active: {
opacity: 1, opacity: 1,
transform: 'translateX(0)', transform: "translateX(0)",
}, },
inactive: { inactive: {
transform: 'translateX(-100%)', transform: "translateX(-100%)",
}, },
}) });
}) });
export default function StatusDust(props: Props) { export default function StatusDust(props: Props) {
const { device, showTimestamp } = props; const { device, showTimestamp } = props;
const classes = useStyles() const classes = useStyles();
const colors = or(device.status.sen5x?.overlays.map(overlay => overlay.color), []); const colors = or(device.status.sen5x?.overlays.map((overlay) => overlay.color), []);
const messages = or(device.status.sen5x?.overlays.map(overlay => overlay.message), []); const messages = or(device.status.sen5x?.overlays.map((overlay) => overlay.message), []);
const [color, setColor] = useState(colors.length > 0 ? colors[0] : "");
const [, setColorIndex] = useState(0)
useEffect(() => {
const interval = setInterval(() => {
setColorIndex(prevIndex => {
const newIndex = prevIndex >= colors.length - 1 ? 0 : prevIndex + 1;
setColor(colors[newIndex]); // Use the new index here
return newIndex;
});
}, 7500);
return () => clearInterval(interval);
}, []);
// const [currentIndex, setCurrentIndex] = useState(0);
// Auto-cycle through measurements every 3 seconds
// useEffect(() => {
// const interval = setInterval(() => {
// setCurrentIndex((prevIndex) => (prevIndex + 1) % 2);
// }, 3000); // Adjust timing as needed (3000ms = 3s)
// return () => clearInterval(interval); // Cleanup on unmount
// }, []);
const tooltip = () => { const tooltip = () => {
if (messages.length === 0) return null if (messages.length === 0) return null;
if (messages.length < 2) return ( if (messages.length < 2) return messages[0];
messages[0]
)
return ( return (
<Grid2 container direction={"column"}> <Grid2 container direction={"column"}>
<Grid2 container direction="row"> <Grid2 container direction="row">
@ -100,10 +75,10 @@ export default function StatusDust(props: Props) {
<Grid2 key={"tooltip-overly-message-" + message} container direction="row" alignItems={"center"}> <Grid2 key={"tooltip-overly-message-" + message} container direction="row" alignItems={"center"}>
<Box <Box
sx={{ sx={{
width: 10, // Size of the square width: 10,
height: 10, height: 10,
backgroundColor: colors[index], // Passed color prop backgroundColor: colors[index],
borderRadius: 1, // Optional: slight rounding, remove for sharp square borderRadius: 1,
border: "1px solid black", border: "1px solid black",
}} }}
/> />
@ -111,22 +86,20 @@ export default function StatusDust(props: Props) {
{message} {message}
</Typography> </Typography>
</Grid2> </Grid2>
) );
})} })}
</Grid2> </Grid2>
) );
} };
function compareTimestamps(timestamp1: string, timestamp2: string): number { function compareTimestamps(timestamp1: string, timestamp2: string): number {
const date1 = new Date(timestamp1); const date1 = new Date(timestamp1);
const date2 = new Date(timestamp2); const date2 = new Date(timestamp2);
// Check if dates are valid
if (isNaN(date1.getTime()) || isNaN(date2.getTime())) { if (isNaN(date1.getTime()) || isNaN(date2.getTime())) {
throw new Error("Invalid RFC3339 timestamp format"); throw new Error("Invalid RFC3339 timestamp format");
} }
// Compare using getTime() for millisecond precision
return date1.getTime() - date2.getTime(); return date1.getTime() - date2.getTime();
} }
@ -134,47 +107,49 @@ export default function StatusDust(props: Props) {
const now = new Date(); const now = new Date();
const oneDayAgo = new Date(now.getTime() - 24 * 60 * 60 * 1000); const oneDayAgo = new Date(now.getTime() - 24 * 60 * 60 * 1000);
if (compareTimestamps(device.status.sen5x?.timestamp, oneDayAgo.toISOString()) < 0) { if (compareTimestamps(device.status.sen5x?.timestamp, oneDayAgo.toISOString()) < 0) {
return null return null;
} }
} else { } else {
return null return null;
} }
return ( return (
<Tooltip title={tooltip()}> <Tooltip title={tooltip()}>
<PulseBox color={color} className={classes.box} > <PulseBox color={colors[0] ?? ""} colors={colors} className={classes.box}>
<Grid2 container direction="column" > <Grid2 container direction="column" spacing={0.25}>
<Grid2 container direction="column" <Grid2 className={classes.readingsWrapper}>
className={`${classes.carouselItem} ${classes.active }`} <Grid2 container direction="column" className={`${classes.carouselItem} ${classes.active}`}>
> <Grid2>
<Grid2> <Typography variant="body2" style={{ color: green[300] }}>
<Typography variant="body2" style={{ color: green[300]}}> {"<1um:"} {device.status.sen5x?.dust1ug.toFixed(1)}ug/m3
{"<1um:"} {device.status.sen5x?.dust1ug.toFixed(1)}ug/m3 </Typography>
</Typography> </Grid2>
</Grid2> <Grid2>
<Grid2> <Typography variant="body2" style={{ color: green[400] }}>
<Typography variant="body2" style={{ color: green[400]}}> {"<2.5um:"} {device.status.sen5x?.dust2ug.toFixed(1)}ug/m3
{"<2.5um:"} {device.status.sen5x?.dust2ug.toFixed(1)}ug/m3 </Typography>
</Typography> </Grid2>
</Grid2> <Grid2>
<Grid2> <Typography variant="body2" style={{ color: green[500] }}>
<Typography variant="body2" style={{ color: green[500]}}> {"<4um: "}
{"<4um: "}{device.status.sen5x?.dust4ug.toFixed(1)}ug/m3 {device.status.sen5x?.dust4ug.toFixed(1)}ug/m3
</Typography> </Typography>
</Grid2> </Grid2>
<Grid2> <Grid2>
<Typography variant="body2" style={{ color: green[600]}}> <Typography variant="body2" style={{ color: green[600] }}>
{"<10um: "}{device.status.sen5x?.dust10ug.toFixed(1)}ug/m3 {"<10um: "}
</Typography> {device.status.sen5x?.dust10ug.toFixed(1)}ug/m3
</Typography>
</Grid2>
</Grid2> </Grid2>
</Grid2> </Grid2>
{showTimestamp && device.status.sen5x?.timestamp && ( {showTimestamp && device.status.sen5x?.timestamp && (
<Grid2> <Grid2 sx={{ pt: 0.25 }}>
<RelativeTimestamp timestamp={device.status.sen5x.timestamp} /> <RelativeTimestamp timestamp={device.status.sen5x.timestamp} />
</Grid2> </Grid2>
)} )}
</Grid2> </Grid2>
</PulseBox> </PulseBox>
</Tooltip> </Tooltip>
) );
} }

View file

@ -85,23 +85,8 @@ export default function StatusGas(props: Props) {
messages = or(device.status[gasType]?.overlays?.map(overlay => overlay.message), []); messages = or(device.status[gasType]?.overlays?.map(overlay => overlay.message), []);
} }
const [color, setColor] = useState(colors.length > 0 ? colors[0] : "");
const [, setColorIndex] = useState(0)
const gasTypes: ("o2" | "no2" | "co2" | "co" | "lel" | "h2s")[] = ["o2", "no2", "co2", "co", "lel", "h2s"] const gasTypes: ("o2" | "no2" | "co2" | "co" | "lel" | "h2s")[] = ["o2", "no2", "co2", "co", "lel", "h2s"]
useEffect(() => {
const interval = setInterval(() => {
setColorIndex(prevIndex => {
const newIndex = prevIndex >= colors.length - 1 ? 0 : prevIndex + 1;
setColor(colors[newIndex]); // Use the new index here
return newIndex;
});
}, 7500);
return () => clearInterval(interval);
}, []);
const [currentIndex, setCurrentIndex] = useState(0); const [currentIndex, setCurrentIndex] = useState(0);
// Auto-cycle through measurements every 5 seconds // Auto-cycle through measurements every 5 seconds
@ -183,7 +168,7 @@ export default function StatusGas(props: Props) {
} }
return ( return (
<Tooltip title={tooltip()}> <Tooltip title={tooltip()}>
<PulseBox color={color} className={gasType === "all" ? classes.boxTall : classes.box}> <PulseBox color={colors[0] ?? ""} colors={colors} className={gasType === "all" ? classes.boxTall : classes.box}>
<Grid2 container direction="column" > <Grid2 container direction="column" >
{ gasType === "all" && { gasType === "all" &&
<Typography variant="body2"> <Typography variant="body2">

View file

@ -5,7 +5,6 @@ import { Device } from "models";
import PulseBox from "./PulseBox"; import PulseBox from "./PulseBox";
import RelativeTimestamp from "./RelativeTimestamp"; import RelativeTimestamp from "./RelativeTimestamp";
import { or } from "utils"; import { or } from "utils";
import { useEffect, useState } from "react";
interface Props { interface Props {
device: Device; device: Device;
@ -33,21 +32,6 @@ export default function StatusPlenum(props: Props) {
const colors = or(device.status.plenum?.overlays.map(overlay => overlay.color), []); const colors = or(device.status.plenum?.overlays.map(overlay => overlay.color), []);
const messages = or(device.status.plenum?.overlays.map(overlay => overlay.message), []); const messages = or(device.status.plenum?.overlays.map(overlay => overlay.message), []);
const [color, setColor] = useState(colors.length > 0 ? colors[0] : "");
const [, setColorIndex] = useState(0)
useEffect(() => {
const interval = setInterval(() => {
setColorIndex(prevIndex => {
const newIndex = prevIndex >= colors.length - 1 ? 0 : prevIndex + 1;
setColor(colors[newIndex]); // Use the new index here
return newIndex;
});
}, 5000);
return () => clearInterval(interval);
}, []);
const tooltip = () => { const tooltip = () => {
if (messages.length === 0) return null if (messages.length === 0) return null
if (messages.length < 2) return ( if (messages.length < 2) return (
@ -107,7 +91,7 @@ export default function StatusPlenum(props: Props) {
return ( return (
<Tooltip title={tooltip()}> <Tooltip title={tooltip()}>
<PulseBox color={color} className={classes.box} > <PulseBox color={colors[0] ?? ""} colors={colors} className={classes.box} >
<Grid2 container direction="column" > <Grid2 container direction="column" >
<Grid2> <Grid2>
<Typography variant="body2" style={{ color: orange[700]}}> <Typography variant="body2" style={{ color: orange[700]}}>

View file

@ -24,75 +24,61 @@ const useStyles = makeStyles((theme: Theme) => {
marginRight: "auto", marginRight: "auto",
margin: theme.spacing(1), margin: theme.spacing(1),
width: theme.spacing(16), width: theme.spacing(16),
height: theme.spacing(11), minHeight: theme.spacing(11),
},
/** Reserves vertical space so absolute carousel slides do not overlap the timestamp below. */
readingsWrapper: {
position: "relative",
width: "100%",
minHeight: theme.spacing(10),
}, },
carouselContainer: { carouselContainer: {
position: 'relative', position: "relative",
height: '40px', height: "40px",
overflow: 'hidden', overflow: "hidden",
textAlign: "center", textAlign: "center",
}, },
carouselItem: { carouselItem: {
opacity: 0, opacity: 0,
transform: 'translateX(100%)', transform: "translateX(100%)",
transition: 'opacity 0.5s ease, transform 0.5s ease', transition: "opacity 0.5s ease, transform 0.5s ease",
position: 'absolute', position: "absolute",
width: '100%', width: "100%",
overflow: "hidden", overflow: "hidden",
// height: '100%',
}, },
active: { active: {
opacity: 1, opacity: 1,
transform: 'translateX(0)', transform: "translateX(0)",
}, },
inactive: { inactive: {
transform: 'translateX(-100%)', transform: "translateX(-100%)",
}, },
}) });
}) });
export default function StatusSen5x(props: Props) { export default function StatusSen5x(props: Props) {
const { device, noDust, showTimestamp } = props; const { device, noDust, showTimestamp } = props;
const classes = useStyles() const classes = useStyles();
// console.log(noDust) const colors = or(device.status.sen5x?.overlays.map((overlay) => overlay.color), []);
const colors = or(device.status.sen5x?.overlays.map(overlay => overlay.color), []); const messages = or(device.status.sen5x?.overlays.map((overlay) => overlay.message), []);
const messages = or(device.status.sen5x?.overlays.map(overlay => overlay.message), []);
const [color, setColor] = useState(colors.length > 0 ? colors[0] : "");
const [, setColorIndex] = useState(0)
useEffect(() => {
const interval = setInterval(() => {
setColorIndex(prevIndex => {
const newIndex = prevIndex >= colors.length - 1 ? 0 : prevIndex + 1;
setColor(colors[newIndex]); // Use the new index here
return newIndex;
});
}, 7500);
return () => clearInterval(interval);
}, []);
const [currentIndex, setCurrentIndex] = useState(0); const [currentIndex, setCurrentIndex] = useState(0);
// Auto-cycle through measurements every 5 seconds
useEffect(() => { useEffect(() => {
if (noDust) { if (noDust) {
setCurrentIndex(0) setCurrentIndex(0);
return return;
} }
const interval = setInterval(() => { const interval = setInterval(() => {
if (!noDust) setCurrentIndex((prevIndex) => (prevIndex + 1) % 2); if (!noDust) setCurrentIndex((prevIndex) => (prevIndex + 1) % 2);
}, 5000); }, 5000);
return () => clearInterval(interval); // Cleanup on unmount return () => clearInterval(interval);
}, [noDust]); }, [noDust]);
const tooltip = () => { const tooltip = () => {
if (messages.length === 0) return null if (messages.length === 0) return null;
if (messages.length < 2) return ( if (messages.length < 2) return messages[0];
messages[0]
)
return ( return (
<Grid2 container direction={"column"}> <Grid2 container direction={"column"}>
<Grid2 container direction="row"> <Grid2 container direction="row">
@ -105,10 +91,10 @@ export default function StatusSen5x(props: Props) {
<Grid2 key={"tooltip-overly-message-" + message} container direction="row" alignItems={"center"}> <Grid2 key={"tooltip-overly-message-" + message} container direction="row" alignItems={"center"}>
<Box <Box
sx={{ sx={{
width: 10, // Size of the square width: 10,
height: 10, height: 10,
backgroundColor: colors[index], // Passed color prop backgroundColor: colors[index],
borderRadius: 1, // Optional: slight rounding, remove for sharp square borderRadius: 1,
border: "1px solid black", border: "1px solid black",
}} }}
/> />
@ -116,22 +102,20 @@ export default function StatusSen5x(props: Props) {
{message} {message}
</Typography> </Typography>
</Grid2> </Grid2>
) );
})} })}
</Grid2> </Grid2>
) );
} };
function compareTimestamps(timestamp1: string, timestamp2: string): number { function compareTimestamps(timestamp1: string, timestamp2: string): number {
const date1 = new Date(timestamp1); const date1 = new Date(timestamp1);
const date2 = new Date(timestamp2); const date2 = new Date(timestamp2);
// Check if dates are valid
if (isNaN(date1.getTime()) || isNaN(date2.getTime())) { if (isNaN(date1.getTime()) || isNaN(date2.getTime())) {
throw new Error("Invalid RFC3339 timestamp format"); throw new Error("Invalid RFC3339 timestamp format");
} }
// Compare using getTime() for millisecond precision
return date1.getTime() - date2.getTime(); return date1.getTime() - date2.getTime();
} }
@ -139,76 +123,82 @@ export default function StatusSen5x(props: Props) {
const now = new Date(); const now = new Date();
const oneDayAgo = new Date(now.getTime() - 24 * 60 * 60 * 1000); const oneDayAgo = new Date(now.getTime() - 24 * 60 * 60 * 1000);
if (compareTimestamps(device.status.sen5x?.timestamp, oneDayAgo.toISOString()) < 0) { if (compareTimestamps(device.status.sen5x?.timestamp, oneDayAgo.toISOString()) < 0) {
return null return null;
} }
} else { } else {
return null return null;
} }
return ( return (
<Tooltip title={tooltip()}> <Tooltip title={tooltip()}>
<PulseBox color={color} className={classes.box} > <PulseBox color={colors[0] ?? ""} colors={colors} className={classes.box}>
<Grid2 container direction="column" > <Grid2 container direction="column" spacing={0.25}>
<Grid2 container direction="column" <Grid2 className={classes.readingsWrapper}>
className={`${classes.carouselItem} ${ <Grid2
0 === currentIndex ? classes.active : classes.inactive container
}`} direction="column"
> className={`${classes.carouselItem} ${0 === currentIndex ? classes.active : classes.inactive}`}
<Grid2> >
<Typography variant="body2" style={{ color: orange[700]}}> <Grid2>
Temp: {device.status.sen5x?.temperature.toFixed(1)}°C <Typography variant="body2" style={{ color: orange[700] }}>
</Typography> Temp: {device.status.sen5x?.temperature.toFixed(1)}°C
</Grid2> </Typography>
<Grid2> </Grid2>
<Typography variant="body2" style={{ color: blue[700]}}> <Grid2>
Humidity: {device.status.sen5x?.humidity.toFixed(1)}% <Typography variant="body2" style={{ color: blue[700] }}>
</Typography> Humidity: {device.status.sen5x?.humidity.toFixed(1)}%
</Grid2> </Typography>
<Grid2> </Grid2>
<Typography variant="body2" style={{ color: blue[300]}}> <Grid2>
Voc: {device.status.sen5x?.voc.toFixed(1)}% <Typography variant="body2" style={{ color: blue[300] }}>
</Typography> Voc: {device.status.sen5x?.voc.toFixed(1)}%
</Grid2> </Typography>
<Grid2> </Grid2>
<Typography variant="body2" style={{ color: blue[400]}}> <Grid2>
Nox: {device.status.sen5x?.nox.toFixed(1)}% <Typography variant="body2" style={{ color: blue[400] }}>
</Typography> Nox: {device.status.sen5x?.nox.toFixed(1)}%
</Typography>
</Grid2>
</Grid2> </Grid2>
{!noDust && (
<Grid2
container
direction="column"
className={`${classes.carouselItem} ${1 === currentIndex ? classes.active : classes.inactive}`}
>
<Grid2>
<Typography variant="body2" style={{ color: green[300] }}>
{"<1um:"} {device.status.sen5x?.dust1ug.toFixed(1)}ug/m3
</Typography>
</Grid2>
<Grid2>
<Typography variant="body2" style={{ color: green[400] }}>
{"<2.5um:"} {device.status.sen5x?.dust2ug.toFixed(1)}ug/m3
</Typography>
</Grid2>
<Grid2>
<Typography variant="body2" style={{ color: green[500] }}>
{"<4um: "}
{device.status.sen5x?.dust4ug.toFixed(1)}ug/m3
</Typography>
</Grid2>
<Grid2>
<Typography variant="body2" style={{ color: green[600] }}>
{"<10um: "}
{device.status.sen5x?.dust10ug.toFixed(1)}ug/m3
</Typography>
</Grid2>
</Grid2>
)}
</Grid2> </Grid2>
{!noDust && <Grid2 container direction="column"
className={`${classes.carouselItem} ${
1 === currentIndex ? classes.active : classes.inactive
}`}
>
<Grid2>
<Typography variant="body2" style={{ color: green[300]}}>
{"<1um:"} {device.status.sen5x?.dust1ug.toFixed(1)}ug/m3
</Typography>
</Grid2>
<Grid2>
<Typography variant="body2" style={{ color: green[400]}}>
{"<2.5um:"} {device.status.sen5x?.dust2ug.toFixed(1)}ug/m3
</Typography>
</Grid2>
<Grid2>
<Typography variant="body2" style={{ color: green[500]}}>
{"<4um: "}{device.status.sen5x?.dust4ug.toFixed(1)}ug/m3
</Typography>
</Grid2>
<Grid2>
<Typography variant="body2" style={{ color: green[600]}}>
{"<10um: "}{device.status.sen5x?.dust10ug.toFixed(1)}ug/m3
</Typography>
</Grid2>
</Grid2>}
{showTimestamp && device.status.sen5x?.timestamp && ( {showTimestamp && device.status.sen5x?.timestamp && (
<Grid2> <Grid2 sx={{ pt: 0.25 }}>
<RelativeTimestamp timestamp={device.status.sen5x.timestamp} /> <RelativeTimestamp timestamp={device.status.sen5x.timestamp} />
</Grid2> </Grid2>
)} )}
</Grid2> </Grid2>
</PulseBox> </PulseBox>
</Tooltip> </Tooltip>
) );
} }

View file

@ -44,7 +44,6 @@ import { bestUnit, milliToX } from "common/time/duration";
import { useGlobalState } from "providers"; import { useGlobalState } from "providers";
import { green, red } from "@mui/material/colors"; import { green, red } from "@mui/material/colors";
import { makeStyles } from "@mui/styles"; import { makeStyles } from "@mui/styles";
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";
@ -177,11 +176,11 @@ export default function ComponentForm(props: Props) {
let width = formComponent.settings.containerDimensions?.widthCm ?? 0; let width = formComponent.settings.containerDimensions?.widthCm ?? 0;
let height = formComponent.settings.containerDimensions?.heightCm ?? 0; let height = formComponent.settings.containerDimensions?.heightCm ?? 0;
if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) { if (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) {
length = length / 100; length = length / 100;
width = width / 100; width = width / 100;
height = height / 100; height = height / 100;
} else if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET) { } else if (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET) {
length = length / 30.48; length = length / 30.48;
width = width / 30.48; width = width / 30.48;
height = height / 30.48; height = height / 30.48;
@ -189,9 +188,9 @@ export default function ComponentForm(props: Props) {
let sensorDistance = formComponent.settings.sensorDistanceCm ?? 0; let sensorDistance = formComponent.settings.sensorDistanceCm ?? 0;
if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) { if (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) {
sensorDistance = sensorDistance / 100; sensorDistance = sensorDistance / 100;
} else if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET) { } else if (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET) {
sensorDistance = sensorDistance / 30.48; sensorDistance = sensorDistance / 30.48;
} }
@ -520,9 +519,9 @@ export default function ComponentForm(props: Props) {
if (!isNaN(Number(event.target.value))) { if (!isNaN(Number(event.target.value))) {
let dimensions = f.component.settings.containerDimensions; let dimensions = f.component.settings.containerDimensions;
let value = Number(event.target.value); let value = Number(event.target.value);
if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) { if (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) {
value = value * 100; value = value * 100;
} else if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET) { } else if (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET) {
value = value * 30.48; value = value * 30.48;
} }
if (dimensions !== undefined && dimensions !== null) { if (dimensions !== undefined && dimensions !== null) {
@ -549,9 +548,9 @@ export default function ComponentForm(props: Props) {
if (!isNaN(Number(event.target.value))) { if (!isNaN(Number(event.target.value))) {
let dimensions = f.component.settings.containerDimensions; let dimensions = f.component.settings.containerDimensions;
let value = Number(event.target.value); let value = Number(event.target.value);
if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) { if (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) {
value = value * 100; value = value * 100;
} else if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET) { } else if (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET) {
value = value * 30.48; value = value * 30.48;
} }
if (dimensions !== undefined && dimensions !== null) { if (dimensions !== undefined && dimensions !== null) {
@ -578,9 +577,9 @@ export default function ComponentForm(props: Props) {
if (!isNaN(Number(event.target.value))) { if (!isNaN(Number(event.target.value))) {
let dimensions = f.component.settings.containerDimensions; let dimensions = f.component.settings.containerDimensions;
let value = Number(event.target.value); let value = Number(event.target.value);
if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) { if (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) {
value = value * 100; value = value * 100;
} else if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET) { } else if (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET) {
value = value * 30.48; value = value * 30.48;
} }
if (dimensions !== undefined && dimensions !== null) { if (dimensions !== undefined && dimensions !== null) {
@ -606,9 +605,9 @@ export default function ComponentForm(props: Props) {
f.sensorDistance = event.target.value; f.sensorDistance = event.target.value;
if (!isNaN(Number(event.target.value))) { if (!isNaN(Number(event.target.value))) {
let value = Number(event.target.value); let value = Number(event.target.value);
if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) { if (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) {
value = value * 100; value = value * 100;
} else if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET) { } else if (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET) {
value = value * 30.48; value = value * 30.48;
} }
f.component.settings.sensorDistanceCm = value; f.component.settings.sensorDistanceCm = value;
@ -1252,7 +1251,7 @@ export default function ComponentForm(props: Props) {
InputProps={{ InputProps={{
endAdornment: ( endAdornment: (
<InputAdornment position="end"> <InputAdornment position="end">
{getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS {user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS
? "m" ? "m"
: "ft"} : "ft"}
</InputAdornment> </InputAdornment>
@ -1276,7 +1275,7 @@ export default function ComponentForm(props: Props) {
InputProps={{ InputProps={{
endAdornment: ( endAdornment: (
<InputAdornment position="end"> <InputAdornment position="end">
{getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS {user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS
? "m" ? "m"
: "ft"} : "ft"}
</InputAdornment> </InputAdornment>
@ -1300,7 +1299,7 @@ export default function ComponentForm(props: Props) {
InputProps={{ InputProps={{
endAdornment: ( endAdornment: (
<InputAdornment position="end"> <InputAdornment position="end">
{getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS {user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS
? "m" ? "m"
: "ft"} : "ft"}
</InputAdornment> </InputAdornment>
@ -1328,7 +1327,7 @@ export default function ComponentForm(props: Props) {
InputProps={{ InputProps={{
endAdornment: ( endAdornment: (
<InputAdornment position="end"> <InputAdornment position="end">
{getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS ? "m" : "ft"} {user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS ? "m" : "ft"}
</InputAdornment> </InputAdornment>
) )
}} }}

View file

@ -1,107 +0,0 @@
import { Box, Card, Typography, useTheme } from "@mui/material";
import GrainDescriber from "grain/GrainDescriber";
import { Contract } from "models/Contract";
import { Label, Pie, PieChart, ResponsiveContainer, Text } from "recharts";
interface Props {
contract: Contract;
}
export default function ContractVisualizer(props: Props) {
const { contract } = props;
const theme = useTheme();
return (
<Card style={{ height: "100%", padding: 10 }}>
<Box height={"15%"}>
<Typography style={{ fontWeight: 650, fontSize: 20 }}>
{GrainDescriber(contract.grain()).name}
</Typography>
<Typography style={{ fontWeight: 650, fontSize: 15 }}>
{contract.deliveredInPreferredUnit().toFixed(2)}/
{contract.sizeInPreferredUnit().toFixed(2)} {contract.unit}
</Typography>
</Box>
<ResponsiveContainer height={"85%"}>
<PieChart>
{/* <Legend
verticalAlign="bottom"
content={() => (
<Box textAlign="center" marginY={0.5}>
<Chip label={contract.commodityLabel()} />
</Box>
)}
/> */}
<Pie
outerRadius={"100%"}
innerRadius="70%"
stroke="transparent"
data={[
{
name: "",
value: contract.settings.delivered,
fill: contract.colour
},
{
name: "",
value: contract.settings.size - contract.settings.delivered
}
]}
dataKey="value"
cx="50%"
cy="50%">
<Label
position="center"
fill={theme.palette.text.secondary}
fontSize={"2rem"}
fontWeight={650}
content={props => {
const { cx, cy } = props.viewBox as any;
return (
<g>
<Text
x={cx}
y={cy}
dy={-15}
textAnchor="middle"
verticalAnchor="middle"
fill={props.fill}
fontWeight={props.fontWeight}
fontSize={props.fontSize}>
{((contract.settings.delivered / contract.settings.size) * 100).toFixed(1) +
"%"}
</Text>
</g>
);
}}
/>
<Label
position="center"
fill={theme.palette.text.secondary}
fontSize={"1.5rem"}
fontWeight={650}
content={props => {
const { cx, cy } = props.viewBox as any;
return (
<g>
<Text
x={cx}
y={cy}
dy={15}
textAnchor="middle"
verticalAnchor="middle"
fill={props.fill}
fontWeight={props.fontWeight}
fontSize={props.fontSize}>
Delivered
</Text>
</g>
);
}}
/>
</Pie>
</PieChart>
</ResponsiveContainer>
</Card>
);
}

View file

@ -60,7 +60,7 @@ const useStyles = makeStyles((theme: Theme) => ({
export default function ContractsList(props: Props) { export default function ContractsList(props: Props) {
const { contracts, contractPermissions, updateList } = props; const { contracts, contractPermissions, updateList } = props;
const theme = useTheme(); const theme = useTheme();
const [{as}] = useGlobalState(); const [{as, user}] = useGlobalState();
//will need map for contracts that have supported grain types //will need map for contracts that have supported grain types
const [supportedTypes, setSupportedTypes] = useState<Map<pond.Grain, Contract[]>>(new Map()); const [supportedTypes, setSupportedTypes] = useState<Map<pond.Grain, Contract[]>>(new Map());
//will need need map for custom contracts //will need need map for custom contracts
@ -173,16 +173,16 @@ export default function ContractsList(props: Props) {
<TableCell>{contract.name()}</TableCell> <TableCell>{contract.name()}</TableCell>
<TableCell>{contract.settings.contractDate}</TableCell> <TableCell>{contract.settings.contractDate}</TableCell>
<TableCell>{contract.settings.deliveryWindow?.endDate}</TableCell> <TableCell>{contract.settings.deliveryWindow?.endDate}</TableCell>
<TableCell>{contract.sizeInPreferredUnit() + " " + contract.unit}</TableCell> <TableCell>{contract.sizeInPreferredUnit(user) + " " + contract.unit}</TableCell>
<TableCell>{contract.deliveredInPreferredUnit() + " " + contract.unit}</TableCell> <TableCell>{contract.deliveredInPreferredUnit(user) + " " + contract.unit}</TableCell>
<TableCell> <TableCell>
{/* <progress value={contract.settings.delivered} max={contract.settings.size} style={{height: 10}}/> */} {/* <progress value={contract.settings.delivered} max={contract.settings.size} style={{height: 10}}/> */}
<CustomProgressBar colour={colors.blue[500]} completed={fillPercent} /> <CustomProgressBar colour={colors.blue[500]} completed={fillPercent} />
{/* {((contract.settings.delivered/contract.settings.size)*100).toFixed(1) + "%"} */} {/* {((contract.settings.delivered/contract.settings.size)*100).toFixed(1) + "%"} */}
</TableCell> </TableCell>
<TableCell> <TableCell>
{contract.sizeInPreferredUnit() - {contract.sizeInPreferredUnit(user) -
contract.deliveredInPreferredUnit() + contract.deliveredInPreferredUnit(user) +
" " + " " +
contract.unit} contract.unit}
</TableCell> </TableCell>

View file

@ -24,7 +24,6 @@ import moment from "moment";
import { pond } from "protobuf-ts/pond"; import { pond } from "protobuf-ts/pond";
import { useGlobalState, useTaskAPI, useContractAPI, useSnackbar } from "providers"; import { useGlobalState, useTaskAPI, useContractAPI, useSnackbar } from "providers";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { getGrainUnit } from "utils";
interface Props { interface Props {
open: boolean; open: boolean;
@ -45,7 +44,7 @@ export default function ContractSettings(props: Props) {
const { open, close, contract } = props; const { open, close, contract } = props;
const contractAPI = useContractAPI(); const contractAPI = useContractAPI();
const [name, setName] = useState(""); const [name, setName] = useState("");
const [{as}] = useGlobalState(); const [{as, user}] = useGlobalState();
const [contractID, setContractID] = useState(""); //id const [contractID, setContractID] = useState(""); //id
const [holder, setHolder] = useState(""); //holder const [holder, setHolder] = useState(""); //holder
const [deliveryStart, setDeliveryStart] = useState(moment().format("YYYY-MM-DD")); //deliverystart const [deliveryStart, setDeliveryStart] = useState(moment().format("YYYY-MM-DD")); //deliverystart
@ -72,7 +71,6 @@ export default function ContractSettings(props: Props) {
adornment: "" adornment: ""
}); });
const taskAPI = useTaskAPI(); const taskAPI = useTaskAPI();
const [{ user }] = useGlobalState();
const closeSettings = () => { const closeSettings = () => {
close(); close();
@ -87,32 +85,32 @@ export default function ContractSettings(props: Props) {
setDeliveryEnd(contract.settings.deliveryWindow?.endDate ?? ""); setDeliveryEnd(contract.settings.deliveryWindow?.endDate ?? "");
setContractType(contract.settings.type); setContractType(contract.settings.type);
setCustomCommodity(contract.settings.customCommodity); setCustomCommodity(contract.settings.customCommodity);
setContractSize(contract.sizeInPreferredUnit().toFixed(2)); setContractSize(contract.sizeInPreferredUnit(user).toFixed(2));
setDeliveredAmount(contract.deliveredInPreferredUnit().toFixed(2)); setDeliveredAmount(contract.deliveredInPreferredUnit(user).toFixed(2));
setContractValue(contract.settings.totalValue.toString()); setContractValue(contract.settings.totalValue.toString());
setCommodity(contract.settings.commodity); setCommodity(contract.settings.commodity);
setContractDate(contract.settings.contractDate); setContractDate(contract.settings.contractDate);
let conv = 1 let conv = 1
if(contract.settings.conversionValue > 1){ if(contract.settings.conversionValue > 1){
conv = contract.settings.conversionValue conv = contract.settings.conversionValue
if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON){ if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON){
conv = conv * 0.907 conv = conv * 0.907
} }
} }
setConversionValue(conv.toFixed(2)); setConversionValue(conv.toFixed(2));
setUseCustom(contract.isCustom()); setUseCustom(contract.isCustom());
} }
}, [contract]); }, [contract, user]);
useEffect(() => { useEffect(() => {
switch (contractType) { switch (contractType) {
case pond.ContractType.CONTRACT_TYPE_GRAIN: case pond.ContractType.CONTRACT_TYPE_GRAIN:
let conversionLabel = "Bushels Per Tonne" let conversionLabel = "Bushels Per Tonne"
let adornment = "bu" let adornment = "bu"
if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE){ if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE){
adornment = "mT" adornment = "mT"
} }
if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON){ if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON){
conversionLabel = "Bushels Per Ton" conversionLabel = "Bushels Per Ton"
adornment = "t" adornment = "t"
} }
@ -167,14 +165,14 @@ export default function ContractSettings(props: Props) {
//we will assume that both of those entries are using the same unit ie both US ton or both metric tonne //we will assume that both of those entries are using the same unit ie both US ton or both metric tonne
const deliveredVal = isNaN(parseFloat(deliveredAmount)) const deliveredVal = isNaN(parseFloat(deliveredAmount))
? 0 ? 0
: Math.round(Contract.toStoredUnit(parseFloat(deliveredAmount), contractType, conVal)); : Math.round(Contract.toStoredUnit(parseFloat(deliveredAmount), contractType, conVal, user));
const sizeVal = isNaN(parseFloat(contractSize)) const sizeVal = isNaN(parseFloat(contractSize))
? 0 ? 0
: Math.round(Contract.toStoredUnit(parseFloat(contractSize), contractType, conVal)); : Math.round(Contract.toStoredUnit(parseFloat(contractSize), contractType, conVal, user));
//then after the conversion to bushels change the conversion value to metric to be stored in the contract //then after the conversion to bushels change the conversion value to metric to be stored in the contract
if(contractType === pond.ContractType.CONTRACT_TYPE_GRAIN){ if(contractType === pond.ContractType.CONTRACT_TYPE_GRAIN){
if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON){ if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON){
conVal = conVal / 0.907 //convert the ton they entered to mT to be stored in the contracts conversion value conVal = conVal / 0.907 //convert the ton they entered to mT to be stored in the contracts conversion value
} }
} }

View file

@ -4,8 +4,8 @@ import { Transaction } from "models/Transaction";
import BarGraph, { BarData } from "charts/BarGraph"; import BarGraph, { BarData } from "charts/BarGraph";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import moment, { Moment } from "moment"; import moment, { Moment } from "moment";
import { getGrainUnit } from "utils";
import { pond } from "protobuf-ts/pond"; import { pond } from "protobuf-ts/pond";
import { useGlobalState } from "providers";
interface Props { interface Props {
contract: Contract; contract: Contract;
@ -14,6 +14,7 @@ interface Props {
export default function ContractTransactionGraph(props: Props) { export default function ContractTransactionGraph(props: Props) {
const { contract, transactions } = props; const { contract, transactions } = props;
const [{ user }] = useGlobalState();
const [data, setData] = useState<BarData[]>([]); const [data, setData] = useState<BarData[]>([]);
useEffect(() => { useEffect(() => {
@ -27,13 +28,13 @@ export default function ContractTransactionGraph(props: Props) {
if (transaction?.grainTransaction) { if (transaction?.grainTransaction) {
value = transaction.grainTransaction.bushels; value = transaction.grainTransaction.bushels;
if ( if (
getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE &&
transaction.grainTransaction.bushelsPerTonne > 1 transaction.grainTransaction.bushelsPerTonne > 1
) { ) {
value = value / transaction.grainTransaction.bushelsPerTonne; value = value / transaction.grainTransaction.bushelsPerTonne;
} }
if ( if (
getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON &&
transaction.grainTransaction.bushelsPerTonne > 1 transaction.grainTransaction.bushelsPerTonne > 1
) { ) {
value = value / (transaction.grainTransaction.bushelsPerTonne * 0.907); value = value / (transaction.grainTransaction.bushelsPerTonne * 0.907);

View file

@ -232,6 +232,7 @@ export default function ContractTransactionTable(props: Props) {
<DialogTitle>Select Files to Upload</DialogTitle> <DialogTitle>Select Files to Upload</DialogTitle>
<DialogContent> <DialogContent>
<FileUploader <FileUploader
hasFilePermission={permissions.includes(pond.Permission.PERMISSION_FILE_MANAGEMENT)}
keys={[contract.key(), selectedTransaction.transaction.key]} keys={[contract.key(), selectedTransaction.transaction.key]}
types={["contract", "transaction"]} types={["contract", "transaction"]}
toAttach={{ toAttach={{

View file

@ -1,6 +1,7 @@
import { Box, Card, Typography, useTheme } from "@mui/material"; import { Box, Card, Typography, useTheme } from "@mui/material";
import GrainDescriber from "grain/GrainDescriber"; import GrainDescriber from "grain/GrainDescriber";
import { Contract } from "models/Contract"; import { Contract } from "models/Contract";
import { useGlobalState } from "providers";
import { Label, Pie, PieChart, ResponsiveContainer, Text } from "recharts"; import { Label, Pie, PieChart, ResponsiveContainer, Text } from "recharts";
interface Props { interface Props {
@ -9,6 +10,7 @@ interface Props {
export default function ContractVisualizer(props: Props) { export default function ContractVisualizer(props: Props) {
const { contract } = props; const { contract } = props;
const [{ user }] = useGlobalState();
const theme = useTheme(); const theme = useTheme();
return ( return (
@ -18,8 +20,8 @@ export default function ContractVisualizer(props: Props) {
{GrainDescriber(contract.grain()).name} {GrainDescriber(contract.grain()).name}
</Typography> </Typography>
<Typography style={{ fontWeight: 650, fontSize: 15 }}> <Typography style={{ fontWeight: 650, fontSize: 15 }}>
{contract.deliveredInPreferredUnit().toFixed(2)}/ {contract.deliveredInPreferredUnit(user).toFixed(2)}/
{contract.sizeInPreferredUnit().toFixed(2)} {contract.unit} {contract.sizeInPreferredUnit(user).toFixed(2)} {contract.unit}
</Typography> </Typography>
</Box> </Box>
<ResponsiveContainer height={"85%"}> <ResponsiveContainer height={"85%"}>

View file

@ -33,7 +33,6 @@ import { describeMeasurement } from "pbHelpers/MeasurementDescriber";
import { pond, quack } from "protobuf-ts/pond"; import { pond, quack } from "protobuf-ts/pond";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import CableTopNodeSummary from "bin/CableTopNodeSummary"; import CableTopNodeSummary from "bin/CableTopNodeSummary";
import { getTemperatureUnit } from "utils";
import { DevicePreset } from "models/DevicePreset"; import { DevicePreset } from "models/DevicePreset";
import { Ambient } from "models/Ambient"; import { Ambient } from "models/Ambient";
import { useGlobalState } from "providers"; import { useGlobalState } from "providers";
@ -182,7 +181,7 @@ export default function DevicePresets(props: DevicePresetsProps) {
const [heaters, setHeaters] = useState<Controller[]>([]); const [heaters, setHeaters] = useState<Controller[]>([]);
const [fans, setFans] = useState<Component[]>([]); const [fans, setFans] = useState<Component[]>([]);
//const [subscribeBinToAutoTopNode, setSubscribeBinToAutoTopNode] = useState(false); //const [subscribeBinToAutoTopNode, setSubscribeBinToAutoTopNode] = useState(false);
const [{as}] = useGlobalState(); const [{as, user}] = useGlobalState();
useEffect(() => { useEffect(() => {
if (deviceComponents.get(deviceIndex.toString())) return; if (deviceComponents.get(deviceIndex.toString())) return;
@ -445,7 +444,7 @@ export default function DevicePresets(props: DevicePresetsProps) {
conditions.push(fanConditionOne); conditions.push(fanConditionOne);
//since the measurement describers function toStored does a conversion into celsius for temperature if the users pref is fahrenheit we need to convert the preset value into fahrenheit //since the measurement describers function toStored does a conversion into celsius for temperature if the users pref is fahrenheit we need to convert the preset value into fahrenheit
if (getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) { if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
tempPreset = Math.round((tempPreset * (9 / 5) + 32) * 100) / 100; tempPreset = Math.round((tempPreset * (9 / 5) + 32) * 100) / 100;
} }
let tempVal = describeMeasurement( let tempVal = describeMeasurement(
@ -541,7 +540,7 @@ export default function DevicePresets(props: DevicePresetsProps) {
} }
//since the measurement describers function to stored does a conversion into celsius if the users pref is fahrenheit for temperature we need to convert the preset value into fahrenheit //since the measurement describers function to stored does a conversion into celsius if the users pref is fahrenheit for temperature we need to convert the preset value into fahrenheit
if (getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) { if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
temp = Math.round((temp * (9 / 5) + 32) * 100) / 100; temp = Math.round((temp * (9 / 5) + 32) * 100) / 100;
} }
let tempVal = describeMeasurement( let tempVal = describeMeasurement(

View file

@ -18,10 +18,9 @@ import { cloneDeep } from "lodash";
import { DevicePreset } from "models/DevicePreset"; import { DevicePreset } from "models/DevicePreset";
import { ObjectTypeString } from "objects/ObjectDescriber"; import { ObjectTypeString } from "objects/ObjectDescriber";
import { pond } from "protobuf-ts/pond"; import { pond } from "protobuf-ts/pond";
import { useSnackbar } from "providers"; import { useGlobalState, useSnackbar } from "providers";
import { useDevicePresetAPI } from "providers/pond/devicePresetAPI"; import { useDevicePresetAPI } from "providers/pond/devicePresetAPI";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { getTemperatureUnit } from "utils";
interface Props { interface Props {
preset: DevicePreset; preset: DevicePreset;
@ -54,6 +53,7 @@ export default function DevicePresetCard(props: Props) {
const [humString, setHumString] = useState("0"); const [humString, setHumString] = useState("0");
const [hum, setHum] = useState(0); const [hum, setHum] = useState(0);
const isMobile = useMobile(); const isMobile = useMobile();
const [{user}] = useGlobalState();
useEffect(() => { useEffect(() => {
setPresetName(preset.name); setPresetName(preset.name);
@ -63,11 +63,11 @@ export default function DevicePresetCard(props: Props) {
setHum(preset.settings.humidity); setHum(preset.settings.humidity);
setHumString(preset.settings.humidity.toString()); setHumString(preset.settings.humidity.toString());
let displayTemp = preset.settings.temperature.toString(); let displayTemp = preset.settings.temperature.toString();
if (getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) { if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
displayTemp = ((preset.settings.temperature * 9) / 5 + 32).toFixed(); displayTemp = ((preset.settings.temperature * 9) / 5 + 32).toFixed();
} }
setTempString(displayTemp); setTempString(displayTemp);
}, [preset]); }, [preset, user]);
const saveNewPreset = () => { const saveNewPreset = () => {
let typestring; let typestring;
@ -296,7 +296,7 @@ export default function DevicePresetCard(props: Props) {
InputProps={{ InputProps={{
endAdornment: ( endAdornment: (
<InputAdornment position="end"> <InputAdornment position="end">
{getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT {user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT
? "°F" ? "°F"
: "°C"} : "°C"}
</InputAdornment> </InputAdornment>
@ -307,7 +307,7 @@ export default function DevicePresetCard(props: Props) {
onChange={e => { onChange={e => {
if (!isNaN(+e.target.value)) { if (!isNaN(+e.target.value)) {
let temp = +e.target.value; let temp = +e.target.value;
if (getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) { if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
temp = Math.round((((temp - 32) * 5) / 9) * 10) / 10; temp = Math.round((((temp - 32) * 5) / 9) * 10) / 10;
} }
setTempC(temp); setTempC(temp);
@ -324,7 +324,7 @@ export default function DevicePresetCard(props: Props) {
max={40} max={40}
valueLabelDisplay="auto" valueLabelDisplay="auto"
valueLabelFormat={value => { valueLabelFormat={value => {
if (getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) { if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
return ((value * 9) / 5 + 32).toFixed() + "°F"; return ((value * 9) / 5 + 32).toFixed() + "°F";
} }
return value.toFixed() + "°C"; return value.toFixed() + "°C";
@ -350,7 +350,7 @@ export default function DevicePresetCard(props: Props) {
max={40} max={40}
valueLabelDisplay="auto" valueLabelDisplay="auto"
valueLabelFormat={value => { valueLabelFormat={value => {
if (getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) { if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
return ((value * 9) / 5 + 32).toFixed() + "°F"; return ((value * 9) / 5 + 32).toFixed() + "°F";
} }
return value.toFixed() + "°C"; return value.toFixed() + "°C";
@ -371,7 +371,7 @@ export default function DevicePresetCard(props: Props) {
InputProps={{ InputProps={{
endAdornment: ( endAdornment: (
<InputAdornment position="end"> <InputAdornment position="end">
{getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT {user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT
? "°F" ? "°F"
: "°C"} : "°C"}
</InputAdornment> </InputAdornment>
@ -382,7 +382,7 @@ export default function DevicePresetCard(props: Props) {
onChange={e => { onChange={e => {
if (!isNaN(+e.target.value)) { if (!isNaN(+e.target.value)) {
let temp = +e.target.value; let temp = +e.target.value;
if (getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) { if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
temp = Math.round((((temp - 32) * 5) / 9) * 10) / 10; temp = Math.round((((temp - 32) * 5) / 9) * 10) / 10;
} }
setTempC(temp); setTempC(temp);

View file

@ -11,7 +11,6 @@ import { pond } from "protobuf-ts/pond"
import { quack } from "protobuf-ts/quack" import { quack } from "protobuf-ts/quack"
import { useGlobalState } from "providers" import { useGlobalState } from "providers"
import { useEffect, useState } from "react" import { useEffect, useState } from "react"
import { getTemperatureUnit } from "utils"
interface Props { interface Props {
@ -104,28 +103,25 @@ export default function GateDeltaTempGraph(props: Props){
*/ */
useEffect(()=>{ useEffect(()=>{
//get the measurements for the temp component //get the measurements for the temp component
componentAPI.listUnitMeasurements( componentAPI.sampleUnitMeasurements(
deviceID, deviceID,
tempComponent.key(), tempComponent.key(),
start.toISOString(), start.toISOString(),
end.toISOString(), end.toISOString(),
300, 500
0,
"timestamp",
).then(resp => { ).then(resp => {
console.log(resp)
if(resp.data.measurements){ if(resp.data.measurements){
let tempCompMeasurements = resp.data.measurements.map(um => UnitMeasurement.any(um, user)); 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 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){ if(ambient){
//need to then load the measurements for the ambient component //need to then load the measurements for the ambient component
componentAPI.listUnitMeasurements( componentAPI.sampleUnitMeasurements(
deviceID, deviceID,
ambient.key(), ambient.key(),
start.toISOString(), start.toISOString(),
end.toISOString(), end.toISOString(),
300, 500
0,
"timestamp",
).then(resp => { ).then(resp => {
let ambientMeasurements = resp.data.measurements.map(um => UnitMeasurement.any(um, user)); let ambientMeasurements = resp.data.measurements.map(um => UnitMeasurement.any(um, user));
//now loop through each of them to 'combine' them into a single array //now loop through each of them to 'combine' them into a single array
@ -193,7 +189,7 @@ export default function GateDeltaTempGraph(props: Props){
<span> <span>
{"Delta Temp: "} {"Delta Temp: "}
<span style={{ color: recent.value > 0 ? warmingColour : coolingColour, fontWeight: 500 }}> <span style={{ color: recent.value > 0 ? warmingColour : coolingColour, fontWeight: 500 }}>
{recent.value.toFixed(2) + (getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT ? " °F" : " °C")} {recent.value.toFixed(2) + (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT ? " °F" : " °C")}
</span> </span>
<br /> <br />
</span> </span>
@ -216,8 +212,8 @@ export default function GateDeltaTempGraph(props: Props){
<SingleSetAreaChart <SingleSetAreaChart
data={data} data={data}
tooltipLabel="Delta Temp" tooltipLabel="Delta Temp"
tooltipUnit={getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT ? " °F" : " °C"} tooltipUnit={user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT ? " °F" : " °C"}
yAxisLabel={"Delta T" + (getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT ? " °F" : " °C")} yAxisLabel={"Delta T" + (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT ? " °F" : " °C")}
colourAboveZero={{colour: warmingColour, label: "Heating"}} colourAboveZero={{colour: warmingColour, label: "Heating"}}
colourBelowZero={{colour: coolingColour, label: "Cooling"}}/> colourBelowZero={{colour: coolingColour, label: "Cooling"}}/>
</Card> </Card>

View file

@ -7,8 +7,6 @@ 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;
close: (canceled: boolean) => void; close: (canceled: boolean) => void;
@ -185,9 +183,9 @@ export default function GateDeviceInteraction(props: Props) {
//need to make sure the redthreshold is in pascals //need to make sure the redthreshold is in pascals
let thresholdPascals = 0 let thresholdPascals = 0
if(!isNaN(parseFloat(redThreshold))){ if(!isNaN(parseFloat(redThreshold))){
if(getPressureUnit() === pond.PressureUnit.PRESSURE_UNIT_KILOPASCALS){ if(user.pressureUnit() === pond.PressureUnit.PRESSURE_UNIT_KILOPASCALS){
thresholdPascals = parseFloat(redThreshold)*1000 thresholdPascals = parseFloat(redThreshold)*1000
}else if (getPressureUnit() === pond.PressureUnit.PRESSURE_UNIT_INCHES_OF_WATER){ }else if (user.pressureUnit() === pond.PressureUnit.PRESSURE_UNIT_INCHES_OF_WATER){
thresholdPascals = parseFloat(redThreshold)*249 thresholdPascals = parseFloat(redThreshold)*249
} }
} }
@ -345,8 +343,8 @@ export default function GateDeviceInteraction(props: Props) {
when the difference between the pressures falls within this range and vice versa: when the difference between the pressures falls within this range and vice versa:
</Typography> </Typography>
<br /> <br />
<Typography>Upper Limit: {getPressureUnit() === pond.PressureUnit.PRESSURE_UNIT_INCHES_OF_WATER ? (highDelta/249.089).toFixed(2) + " iwg" : highDelta/1000 + " kPa"}</Typography> <Typography>Upper Limit: {user.pressureUnit() === 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> <Typography>Lower Limit: {user.pressureUnit() === pond.PressureUnit.PRESSURE_UNIT_INCHES_OF_WATER ? (lowDelta/249.089).toFixed(2) + " iwg" : lowDelta/1000 + " kPa"}</Typography>
<br /> <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> <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>
@ -371,7 +369,7 @@ export default function GateDeviceInteraction(props: Props) {
InputProps={{ InputProps={{
endAdornment: ( endAdornment: (
<InputAdornment position="end"> <InputAdornment position="end">
{getPressureUnit() === pond.PressureUnit.PRESSURE_UNIT_INCHES_OF_WATER ? "iwg" : "kPa"} {user.pressureUnit() === pond.PressureUnit.PRESSURE_UNIT_INCHES_OF_WATER ? "iwg" : "kPa"}
</InputAdornment> </InputAdornment>
) )
}} }}

View file

@ -17,7 +17,6 @@ import moment from "moment";
import { pond } from "protobuf-ts/pond"; import { pond } from "protobuf-ts/pond";
import { UnitMeasurement } from "models/UnitMeasurement"; import { UnitMeasurement } from "models/UnitMeasurement";
import { quack } from "protobuf-ts/quack"; import { quack } from "protobuf-ts/quack";
import { getTemperatureUnit } from "utils";
import { teal } from "@mui/material/colors"; import { teal } from "@mui/material/colors";
import { react } from "@babel/types"; import { react } from "@babel/types";
import { getDeviceStateHelper } from "pbHelpers/DeviceState"; import { getDeviceStateHelper } from "pbHelpers/DeviceState";
@ -229,7 +228,7 @@ export default function GateList(props: Props) {
}) })
if(ambientTemp && outletTemp){ if(ambientTemp && outletTemp){
let deltaTemp = outletTemp - ambientTemp let deltaTemp = outletTemp - ambientTemp
display = deltaTemp.toFixed(2) + (getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT ? "°F" : "°C") display = deltaTemp.toFixed(2) + (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT ? "°F" : "°C")
}else{ }else{
display = "Sensor Missing" display = "Sensor Missing"
} }

View file

@ -2,8 +2,7 @@ import {MenuItem, TextField } from "@mui/material"
import { cloneDeep } from "lodash" import { cloneDeep } from "lodash"
import { pond } from "protobuf-ts/pond" import { pond } from "protobuf-ts/pond"
import React, { useEffect, useRef, useState } from "react" import React, { useEffect, useRef, useState } from "react"
import { getGrainUnit } from "utils" import { useGlobalState } from "providers";
interface Props { interface Props {
grainSettings?: pond.GrainSettings grainSettings?: pond.GrainSettings
onGrainSettingsChange: (settings: pond.GrainSettings, formValid: boolean) => void onGrainSettingsChange: (settings: pond.GrainSettings, formValid: boolean) => void
@ -11,6 +10,7 @@ interface Props {
export default function CustomGrainForm(props: Props) { export default function CustomGrainForm(props: Props) {
const {grainSettings, onGrainSettingsChange} = props const {grainSettings, onGrainSettingsChange} = props
const [{ user }] = useGlobalState();
const [newGrainSettings, setNewGrainSettings] = useState(pond.GrainSettings.create()) const [newGrainSettings, setNewGrainSettings] = useState(pond.GrainSettings.create())
const [name, setName] = useState("") const [name, setName] = useState("")
const [group, setGroup] = useState("") const [group, setGroup] = useState("")
@ -204,7 +204,7 @@ export default function CustomGrainForm(props: Props) {
valid.current = validate(name, group, constantA, constantB, constantC, kgPerBushel, val) valid.current = validate(name, group, constantA, constantB, constantC, kgPerBushel, val)
let settings = cloneDeep(newGrainSettings) let settings = cloneDeep(newGrainSettings)
if(!isNaN(parseFloat(val))){ if(!isNaN(parseFloat(val))){
if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON){ if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON){
settings.bushelsPerTonne = parseFloat(val)/0.907 settings.bushelsPerTonne = parseFloat(val)/0.907
}else{ }else{
settings.bushelsPerTonne = parseFloat(val) settings.bushelsPerTonne = parseFloat(val)
@ -212,7 +212,7 @@ export default function CustomGrainForm(props: Props) {
} }
settingsChanged(settings) settingsChanged(settings)
}} }}
label={"Bushels per " + (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE ? " Tonne" : "Ton")}/> label={"Bushels per " + (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE ? " Tonne" : "Ton")}/>
</React.Fragment> </React.Fragment>
) )
} }

View file

@ -29,7 +29,6 @@ import {
useTransactionAPI useTransactionAPI
} from "providers"; } from "providers";
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import { getGrainUnit } from "utils";
interface Props { interface Props {
mainObject: Bin | GrainBag | Field | Contract; mainObject: Bin | GrainBag | Field | Contract;
@ -451,7 +450,7 @@ export default function GrainTransaction(props: Props) {
}; };
const grainUnitDisplay = () => { const grainUnitDisplay = () => {
switch (getGrainUnit()){ switch (user.grainUnit()){
case pond.GrainUnit.GRAIN_UNIT_TONNE: case pond.GrainUnit.GRAIN_UNIT_TONNE:
return "mT" return "mT"
case pond.GrainUnit.GRAIN_UNIT_TON: case pond.GrainUnit.GRAIN_UNIT_TON:
@ -525,7 +524,7 @@ export default function GrainTransaction(props: Props) {
onChange={e => { onChange={e => {
//if the user is viewing the grain in weight //if the user is viewing the grain in weight
let grainVal = e.target.value; let grainVal = e.target.value;
if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE) { if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE) {
//need to convert what the user input (weight) into what is actually stored (bushels) //need to convert what the user input (weight) into what is actually stored (bushels)
//use source of the conversion value if it was selected, otherwise use the destination //use source of the conversion value if it was selected, otherwise use the destination
if (!isNaN(parseFloat(e.target.value))) { if (!isNaN(parseFloat(e.target.value))) {
@ -535,7 +534,7 @@ export default function GrainTransaction(props: Props) {
grainVal = (+grainVal * selectedDestination.value.bushelsPerTonne()).toFixed(2); grainVal = (+grainVal * selectedDestination.value.bushelsPerTonne()).toFixed(2);
} }
} }
}else if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON) { }else if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON) {
if (!isNaN(parseFloat(e.target.value))) { if (!isNaN(parseFloat(e.target.value))) {
if (selectedSource) { if (selectedSource) {
if(selectedSource.value.bushelsPerTonne){ if(selectedSource.value.bushelsPerTonne){

View file

@ -6,7 +6,6 @@ import moment, { Moment } from "moment";
import { pond } from "protobuf-ts/pond"; import { pond } from "protobuf-ts/pond";
import { useGlobalState, useGrainBagAPI, useSnackbar } from "providers"; import { useGlobalState, useGrainBagAPI, useSnackbar } from "providers";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { getGrainUnit } from "utils";
interface Props { interface Props {
grainBag: GrainBag; grainBag: GrainBag;
@ -21,7 +20,7 @@ export default function GrainBagInventoryGraph(props: Props) {
const [data, setData] = useState<BarData[]>([]); const [data, setData] = useState<BarData[]>([]);
const [loadingData, setLoadingData] = useState<boolean>(false); const [loadingData, setLoadingData] = useState<boolean>(false);
const { openSnack } = useSnackbar(); const { openSnack } = useSnackbar();
const [{as}] = useGlobalState(); const [{as, user}] = useGlobalState();
useEffect(() => { useEffect(() => {
if (grainBag.key() && !loadingData) { if (grainBag.key() && !loadingData) {
@ -36,12 +35,12 @@ export default function GrainBagInventoryGraph(props: Props) {
//let time = hist.timestamp; //let time = hist.timestamp;
let val = hist.object.grainBagSettings.currentBushels ?? 0; let val = hist.object.grainBagSettings.currentBushels ?? 0;
if ( if (
getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE &&
grainBag.bushelsPerTonne() > 1 grainBag.bushelsPerTonne() > 1
) { ) {
val = Math.round((val / grainBag.bushelsPerTonne()) * 100) / 100; val = Math.round((val / grainBag.bushelsPerTonne()) * 100) / 100;
} }
if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && grainBag.bushelsPerTonne() > 1){ if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && grainBag.bushelsPerTonne() > 1){
val = Math.round((val / (grainBag.bushelsPerTonne() * 0.907)) * 100) / 100; val = Math.round((val / (grainBag.bushelsPerTonne() * 0.907)) * 100) / 100;
} }
if (val !== lastBushels) { if (val !== lastBushels) {
@ -55,10 +54,10 @@ export default function GrainBagInventoryGraph(props: Props) {
}); });
if (barData.length === 0) { if (barData.length === 0) {
let val = grainBag.bushels() let val = grainBag.bushels()
if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && grainBag.bushelsPerTonne() > 1){ if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && grainBag.bushelsPerTonne() > 1){
val = grainBag.bushels() / grainBag.bushelsPerTonne() val = grainBag.bushels() / grainBag.bushelsPerTonne()
} }
if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && grainBag.bushelsPerTonne() > 1){ if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && grainBag.bushelsPerTonne() > 1){
val = grainBag.bushels() / (grainBag.bushelsPerTonne() * 0.907) val = grainBag.bushels() / (grainBag.bushelsPerTonne() * 0.907)
} }
barData.push({ barData.push({
@ -79,10 +78,10 @@ export default function GrainBagInventoryGraph(props: Props) {
const maxYAxis = () => { const maxYAxis = () => {
let val = grainBag.capacity() let val = grainBag.capacity()
if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE){ if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE){
val = Math.round((val / grainBag.bushelsPerTonne()) * 100) / 100; val = Math.round((val / grainBag.bushelsPerTonne()) * 100) / 100;
} }
if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && grainBag.bushelsPerTonne() > 1){ if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && grainBag.bushelsPerTonne() > 1){
val = Math.round((val / (grainBag.bushelsPerTonne() * 0.907)) * 100) / 100; val = Math.round((val / (grainBag.bushelsPerTonne() * 0.907)) * 100) / 100;
} }
return val return val

View file

@ -22,7 +22,6 @@ import { pond } from "protobuf-ts/pond";
import { useGlobalState, useGrainBagAPI, useSnackbar } from "providers"; import { useGlobalState, useGrainBagAPI, useSnackbar } from "providers";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { getGrainUnit } from "utils";
const useStyles = makeStyles((theme: Theme) => { const useStyles = makeStyles((theme: Theme) => {
return ({ return ({
@ -84,9 +83,9 @@ export default function GrainBagSettings(props: Props) {
: grainBag.settings.length : grainBag.settings.length
); );
let grainVal = grainBag.bushels(); let grainVal = grainBag.bushels();
if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE) { if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE) {
grainVal = grainBag.bushels() / grainBag.bushelsPerTonne(); grainVal = grainBag.bushels() / grainBag.bushelsPerTonne();
}else if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON) { }else if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON) {
grainVal = grainBag.bushels() / (grainBag.bushelsPerTonne() * 0.907) grainVal = grainBag.bushels() / (grainBag.bushelsPerTonne() * 0.907)
} }
setGrainFill(grainVal.toFixed(2)); setGrainFill(grainVal.toFixed(2));
@ -120,7 +119,7 @@ export default function GrainBagSettings(props: Props) {
const submit = () => { const submit = () => {
//if a bag was passed in do an update //if a bag was passed in do an update
let tonneConversion = bushelConversion let tonneConversion = bushelConversion
if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON){ if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON){
tonneConversion = bushelConversion / 0.907 tonneConversion = bushelConversion / 0.907
} }
if (grainBag) { if (grainBag) {
@ -255,9 +254,9 @@ export default function GrainBagSettings(props: Props) {
const grainUnitDisplay = () => { const grainUnitDisplay = () => {
if(bushelConversion > 1){ if(bushelConversion > 1){
if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE){ if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE){
return "mT" return "mT"
}else if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON){ }else if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON){
return "t" return "t"
} }
}else{ }else{
@ -422,7 +421,7 @@ export default function GrainBagSettings(props: Props) {
value={grainFill} value={grainFill}
onChange={e => { onChange={e => {
let bushelVal = +e.target.value; let bushelVal = +e.target.value;
if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE || getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON) { if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE || user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON) {
//convert the number as a weight into the bushel value, no changing or conversions necessary //convert the number as a weight into the bushel value, no changing or conversions necessary
// since these are both user entered fields and should be the same unit (ton or tonne) // since these are both user entered fields and should be the same unit (ton or tonne)
bushelVal = +e.target.value * (bushelConversion > 0 ? bushelConversion : 1); bushelVal = +e.target.value * (bushelConversion > 0 ? bushelConversion : 1);

View file

@ -19,9 +19,9 @@ import moment from "moment";
import { pond } from "protobuf-ts/pond"; import { pond } from "protobuf-ts/pond";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { getThemeType } from "theme"; import { getThemeType } from "theme";
import { getGrainUnit } from "utils";
import GrainBagSVG from "./grainBagSVG"; import GrainBagSVG from "./grainBagSVG";
import { makeStyles } from "@mui/styles"; import { makeStyles } from "@mui/styles";
import { useGlobalState } from "providers";
interface Props { interface Props {
grainBag: GrainBag; grainBag: GrainBag;
@ -68,6 +68,7 @@ export default function GrainBagVisualizer(props: Props) {
const [grainDiff, setGrainDiff] = useState<number | undefined>(); const [grainDiff, setGrainDiff] = useState<number | undefined>();
const theme = useTheme(); const theme = useTheme();
const [openTransaction, setOpenTransaction] = useState(false); const [openTransaction, setOpenTransaction] = useState(false);
const [{ user }] = useGlobalState();
useEffect(() => { useEffect(() => {
setFillLevel((grainBag.bushels() / grainBag.capacity()) * 100); setFillLevel((grainBag.bushels() / grainBag.capacity()) * 100);
@ -83,13 +84,13 @@ export default function GrainBagVisualizer(props: Props) {
const grainOverlay = () => { const grainOverlay = () => {
let displayPending = pendingGrainAmount; let displayPending = pendingGrainAmount;
let displayDiff = grainDiff; let displayDiff = grainDiff;
if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && grainBag.bushelsPerTonne() > 1) { if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && grainBag.bushelsPerTonne() > 1) {
if (displayPending && displayDiff) { if (displayPending && displayDiff) {
displayPending = Math.round((displayPending / grainBag.bushelsPerTonne()) * 100) / 100; displayPending = Math.round((displayPending / grainBag.bushelsPerTonne()) * 100) / 100;
displayDiff = Math.round((displayDiff / grainBag.bushelsPerTonne()) * 100) / 100; displayDiff = Math.round((displayDiff / grainBag.bushelsPerTonne()) * 100) / 100;
} }
} }
if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && grainBag.bushelsPerTonne() > 1) { if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && grainBag.bushelsPerTonne() > 1) {
if (displayPending && displayDiff) { if (displayPending && displayDiff) {
displayPending = Math.round((displayPending / (grainBag.bushelsPerTonne() * 0.907)) * 100) / 100; displayPending = Math.round((displayPending / (grainBag.bushelsPerTonne() * 0.907)) * 100) / 100;
displayDiff = Math.round((displayDiff / (grainBag.bushelsPerTonne()* 0.907)) * 100) / 100; displayDiff = Math.round((displayDiff / (grainBag.bushelsPerTonne()* 0.907)) * 100) / 100;
@ -148,7 +149,7 @@ export default function GrainBagVisualizer(props: Props) {
}; };
const grainAmountDisplay = () => { const grainAmountDisplay = () => {
if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && grainBag.bushelsPerTonne() > 1) { if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && grainBag.bushelsPerTonne() > 1) {
return ( return (
<div style={{ display: "flex", flexDirection: "row" }}> <div style={{ display: "flex", flexDirection: "row" }}>
<Typography variant="body2" style={{ fontWeight: "bold", marginRight: "4px" }}> <Typography variant="body2" style={{ fontWeight: "bold", marginRight: "4px" }}>
@ -157,7 +158,7 @@ export default function GrainBagVisualizer(props: Props) {
<Typography variant="body2">({grainBag.fillPercent()}%)</Typography> <Typography variant="body2">({grainBag.fillPercent()}%)</Typography>
</div> </div>
); );
} else if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && grainBag.bushelsPerTonne() > 1) { } else if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && grainBag.bushelsPerTonne() > 1) {
return ( return (
<div style={{ display: "flex", flexDirection: "row" }}> <div style={{ display: "flex", flexDirection: "row" }}>
<Typography variant="body2" style={{ fontWeight: "bold", marginRight: "4px" }}> <Typography variant="body2" style={{ fontWeight: "bold", marginRight: "4px" }}>

View file

@ -1,18 +1,9 @@
import { pond } from "protobuf-ts/pond"; import { pond } from "protobuf-ts/pond";
import { useEffect, useRef, useCallback, useMemo } from "react"; import { useEffect, useRef, useMemo } from "react";
import { getWsBaseUrl } from "utils/getWsBaseUrl";
/** /** Off by default to reduce server load; set VITE_ENABLE_WEBSOCKETS=true to enable live streams. */
* Derives the WebSocket base URL from VITE_APP_API_URL. const WEBSOCKETS_ENABLED = import.meta.env.VITE_ENABLE_WEBSOCKETS === "true";
* Matches the logic in useWebSocket.ts.
*/
function getWsBaseUrl(): string {
const apiUrl = import.meta.env.VITE_APP_API_URL;
if (apiUrl) {
return apiUrl.replace(/^http/, "ws");
}
const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
return `${protocol}//${window.location.host}`;
}
export interface UseDeviceStatusStreamsOptions { export interface UseDeviceStatusStreamsOptions {
/** Device IDs to stream status for (e.g. from the visible devices list) */ /** Device IDs to stream status for (e.g. from the visible devices list) */
@ -21,6 +12,12 @@ export interface UseDeviceStatusStreamsOptions {
onStatusUpdate: (deviceId: string, status: pond.DeviceStatus) => void; onStatusUpdate: (deviceId: string, status: pond.DeviceStatus) => void;
/** Auth token passed as ?token= query param */ /** Auth token passed as ?token= query param */
token?: string; token?: string;
/** Same as REST/listDevices (?keys=) — required when listing under a context path or group tab */
keys?: string[];
/** Same as REST (?types=) */
types?: string[];
/** View-as / impersonation (?as=), same as device list when not already team-first in URL */
as?: string;
/** Whether the connections should be active. Default true. */ /** Whether the connections should be active. Default true. */
enabled?: boolean; enabled?: boolean;
} }
@ -31,7 +28,7 @@ export interface UseDeviceStatusStreamsOptions {
* Readings (plenum, sen5x, co, co2, no2, o2, etc.) stream in real time. * Readings (plenum, sen5x, co, co2, no2, o2, etc.) stream in real time.
*/ */
export function useDeviceStatusStreams(options: UseDeviceStatusStreamsOptions) { export function useDeviceStatusStreams(options: UseDeviceStatusStreamsOptions) {
const { deviceIds, onStatusUpdate, token, enabled = true } = options; const { deviceIds, onStatusUpdate, token, keys, types, as, enabled = true } = options;
const onStatusUpdateRef = useRef(onStatusUpdate); const onStatusUpdateRef = useRef(onStatusUpdate);
onStatusUpdateRef.current = onStatusUpdate; onStatusUpdateRef.current = onStatusUpdate;
@ -39,25 +36,67 @@ export function useDeviceStatusStreams(options: UseDeviceStatusStreamsOptions) {
const wsMapRef = useRef<Map<string, WebSocket>>(new Map()); const wsMapRef = useRef<Map<string, WebSocket>>(new Map());
const retriesRef = useRef<Map<string, number>>(new Map()); const retriesRef = useRef<Map<string, number>>(new Map());
const reconnectTimeoutsRef = useRef<Map<string, ReturnType<typeof setTimeout>>>(new Map()); const reconnectTimeoutsRef = useRef<Map<string, ReturnType<typeof setTimeout>>>(new Map());
/** Bumps only in effect cleanup so late onclose / reconnect timers never run after unmount. */
const generationRef = useRef(0);
/** Latest ID set so a device dropped from the list does not reconnect on abnormal close. */
const allowedIdsRef = useRef<Set<string>>(new Set());
allowedIdsRef.current = new Set(deviceIds);
const connect = useCallback( // Normalize for stable comparison: same set of IDs = same string regardless of order
(deviceId: string) => { const deviceIdsKey = useMemo(
() => [...new Set(deviceIds)].sort().join(","),
[deviceIds.join(",")]
);
useEffect(() => {
const clearReconnect = (deviceId: string) => {
const t = reconnectTimeoutsRef.current.get(deviceId);
if (t) {
clearTimeout(t);
reconnectTimeoutsRef.current.delete(deviceId);
}
};
const shutdownAll = () => {
reconnectTimeoutsRef.current.forEach((t) => clearTimeout(t));
reconnectTimeoutsRef.current.clear();
wsMapRef.current.forEach((ws) => ws.close(1000, "cleanup"));
wsMapRef.current.clear();
retriesRef.current.clear();
};
if (!WEBSOCKETS_ENABLED || !enabled || !token || deviceIds.length === 0) {
shutdownAll();
return () => {
generationRef.current += 1;
};
}
const gen = generationRef.current;
const connect = (deviceId: string) => {
if (!token || !deviceId) return; if (!token || !deviceId) return;
if (gen !== generationRef.current) return;
const params = new URLSearchParams(); const params = new URLSearchParams();
params.set("token", token); params.set("token", token);
if (keys?.length) params.set("keys", keys.toString());
if (types?.length) params.set("types", types.toString());
if (as) params.set("as", as);
const path = `/live/devices/${deviceId}/status`; const path = `/v1/live/devices/${deviceId}/status`;
const url = `${getWsBaseUrl()}${path}?${params.toString()}`; const url = `${getWsBaseUrl()}${path}?${params.toString()}`;
const ws = new WebSocket(url); const ws = new WebSocket(url);
wsMapRef.current.set(deviceId, ws); wsMapRef.current.set(deviceId, ws);
ws.onopen = () => { ws.onopen = () => {
if (gen !== generationRef.current) return;
console.debug(`[ws] connected: ${path}`); console.debug(`[ws] connected: ${path}`);
retriesRef.current.set(deviceId, 0); retriesRef.current.set(deviceId, 0);
}; };
ws.onmessage = (event) => { ws.onmessage = (event) => {
if (gen !== generationRef.current) return;
try { try {
const raw = JSON.parse(event.data); const raw = JSON.parse(event.data);
const status = pond.DeviceStatus.fromObject(raw ?? {}); const status = pond.DeviceStatus.fromObject(raw ?? {});
@ -74,7 +113,15 @@ export function useDeviceStatusStreams(options: UseDeviceStatusStreamsOptions) {
ws.onclose = (event) => { ws.onclose = (event) => {
wsMapRef.current.delete(deviceId); wsMapRef.current.delete(deviceId);
// Don't reconnect on clean close or auth rejection if (gen !== generationRef.current) {
return;
}
if (!allowedIdsRef.current.has(deviceId)) {
retriesRef.current.delete(deviceId);
return;
}
if (event.code === 1000 || event.code === 4001 || event.code === 4003) { if (event.code === 1000 || event.code === 4001 || event.code === 4003) {
return; return;
} }
@ -84,55 +131,36 @@ export function useDeviceStatusStreams(options: UseDeviceStatusStreamsOptions) {
retriesRef.current.set(deviceId, retries + 1); retriesRef.current.set(deviceId, retries + 1);
console.debug(`[ws] reconnecting ${path} in ${delay}ms...`); console.debug(`[ws] reconnecting ${path} in ${delay}ms...`);
const timeout = setTimeout(() => connect(deviceId), delay); const timeout = setTimeout(() => {
if (gen !== generationRef.current) return;
if (!allowedIdsRef.current.has(deviceId)) return;
reconnectTimeoutsRef.current.delete(deviceId);
connect(deviceId);
}, delay);
reconnectTimeoutsRef.current.set(deviceId, timeout); reconnectTimeoutsRef.current.set(deviceId, timeout);
}; };
}, };
[token]
);
// Normalize for stable comparison: same set of IDs = same string regardless of order
const deviceIdsKey = useMemo(
() => [...new Set(deviceIds)].sort().join(","),
[deviceIds.join(",")]
);
useEffect(() => {
if (!enabled || !token || deviceIds.length === 0) {
wsMapRef.current.forEach((ws) => ws.close(1000, "disabled"));
wsMapRef.current.clear();
reconnectTimeoutsRef.current.forEach((t) => clearTimeout(t));
reconnectTimeoutsRef.current.clear();
return;
}
const currentIds = new Set(deviceIds); const currentIds = new Set(deviceIds);
// Connect to new devices
deviceIds.forEach((id) => { deviceIds.forEach((id) => {
if (!wsMapRef.current.has(id)) { if (!wsMapRef.current.has(id)) {
connect(id); connect(id);
} }
}); });
// Disconnect from devices no longer in the list
wsMapRef.current.forEach((ws, id) => { wsMapRef.current.forEach((ws, id) => {
if (!currentIds.has(id)) { if (!currentIds.has(id)) {
clearReconnect(id);
ws.close(1000, "device removed"); ws.close(1000, "device removed");
wsMapRef.current.delete(id); wsMapRef.current.delete(id);
const t = reconnectTimeoutsRef.current.get(id); retriesRef.current.delete(id);
if (t) {
clearTimeout(t);
reconnectTimeoutsRef.current.delete(id);
}
} }
}); });
return () => { return () => {
wsMapRef.current.forEach((ws) => ws.close(1000, "cleanup")); generationRef.current += 1;
wsMapRef.current.clear(); shutdownAll();
reconnectTimeoutsRef.current.forEach((t) => clearTimeout(t));
reconnectTimeoutsRef.current.clear();
}; };
}, [enabled, token, deviceIdsKey, deviceIds.length, connect]); }, [enabled, token, deviceIdsKey, deviceIds.length, keys?.join(), types?.join(), as]);
} }

View file

@ -1,21 +1,11 @@
import { useEffect, useRef, useCallback } from "react"; import { useEffect, useRef, useCallback } from "react";
import { getWsBaseUrl } from "utils/getWsBaseUrl";
/** /** Off by default to reduce server load; set VITE_ENABLE_WEBSOCKETS=true to enable live streams. */
* Derives the WebSocket base URL from VITE_APP_API_URL. const WEBSOCKETS_ENABLED = import.meta.env.VITE_ENABLE_WEBSOCKETS === "true";
* e.g. "https://api.brandxtech.ca" -> "wss://api.brandxtech.ca"
* Falls back to current page host for local dev.
*/
function getWsBaseUrl(): string {
const apiUrl = import.meta.env.VITE_APP_API_URL;
if (apiUrl) {
return apiUrl.replace(/^http/, "ws");
}
const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
return `${protocol}//${window.location.host}`;
}
interface UseWebSocketOptions<T> { interface UseWebSocketOptions<T> {
/** The API path, e.g. "/v1/live/devices/123/components" */ /** The API path including /v1, e.g. "/v1/live/devices/123/components" */
path: string; path: string;
/** Transform the raw MessageEvent into your domain type */ /** Transform the raw MessageEvent into your domain type */
parse: (event: MessageEvent) => T; parse: (event: MessageEvent) => T;
@ -25,6 +15,12 @@ interface UseWebSocketOptions<T> {
token?: string; token?: string;
/** Minimum seconds between updates, passed as ?rate= to the backend */ /** Minimum seconds between updates, passed as ?rate= to the backend */
rate?: number; rate?: number;
/** Context keys, same as REST device APIs (?keys=) */
keys?: string[];
/** Context types, same as REST device APIs (?types=) */
types?: string[];
/** Impersonation / team view key, same as REST (?as=) */
as?: string;
/** Whether the connection should be active. Default true. */ /** Whether the connection should be active. Default true. */
enabled?: boolean; enabled?: boolean;
} }
@ -41,7 +37,7 @@ interface UseWebSocketOptions<T> {
* }); * });
*/ */
export function useWebSocket<T>(options: UseWebSocketOptions<T>) { export function useWebSocket<T>(options: UseWebSocketOptions<T>) {
const { path, parse, onMessage, token, rate = 0, enabled = true } = options; const { path, parse, onMessage, token, rate = 0, keys, types, as, enabled = true } = options;
// Keep latest callbacks in refs so reconnects don't use stale closures // Keep latest callbacks in refs so reconnects don't use stale closures
const onMessageRef = useRef(onMessage); const onMessageRef = useRef(onMessage);
@ -52,13 +48,20 @@ export function useWebSocket<T>(options: UseWebSocketOptions<T>) {
const wsRef = useRef<WebSocket | null>(null); const wsRef = useRef<WebSocket | null>(null);
const reconnectTimeoutRef = useRef<ReturnType<typeof setTimeout>>(); const reconnectTimeoutRef = useRef<ReturnType<typeof setTimeout>>();
const retriesRef = useRef(0); const retriesRef = useRef(0);
/** Bumps on each effect cleanup so late onclose / reconnect timers never run after unmount or dependency change. */
const generationRef = useRef(0);
const connect = useCallback(() => { const connect = useCallback(() => {
if (!token) return; if (!token) return;
const gen = generationRef.current;
const params = new URLSearchParams(); const params = new URLSearchParams();
params.set("token", token); params.set("token", token);
if (rate > 0) params.set("rate", rate.toString()); if (rate > 0) params.set("rate", rate.toString());
if (keys?.length) params.set("keys", keys.toString());
if (types?.length) params.set("types", types.toString());
if (as) params.set("as", as);
const url = `${getWsBaseUrl()}${path}?${params.toString()}`; const url = `${getWsBaseUrl()}${path}?${params.toString()}`;
const ws = new WebSocket(url); const ws = new WebSocket(url);
@ -86,6 +89,10 @@ export function useWebSocket<T>(options: UseWebSocketOptions<T>) {
console.debug(`[ws] closed: ${path} (code: ${event.code})`); console.debug(`[ws] closed: ${path} (code: ${event.code})`);
wsRef.current = null; wsRef.current = null;
if (gen !== generationRef.current) {
return;
}
// Don't reconnect on clean close or auth rejection // Don't reconnect on clean close or auth rejection
if (event.code === 1000 || event.code === 4001 || event.code === 4003) { if (event.code === 1000 || event.code === 4001 || event.code === 4003) {
return; return;
@ -95,22 +102,33 @@ export function useWebSocket<T>(options: UseWebSocketOptions<T>) {
const delay = Math.min(1000 * Math.pow(2, retriesRef.current), 30000); const delay = Math.min(1000 * Math.pow(2, retriesRef.current), 30000);
retriesRef.current += 1; retriesRef.current += 1;
console.debug(`[ws] reconnecting in ${delay}ms...`); console.debug(`[ws] reconnecting in ${delay}ms...`);
reconnectTimeoutRef.current = setTimeout(connect, delay); reconnectTimeoutRef.current = setTimeout(() => {
if (gen !== generationRef.current) return;
connect();
}, delay);
}; };
}, [path, token, rate]); }, [path, token, rate, keys?.join(), types?.join(), as]);
useEffect(() => { useEffect(() => {
if (!enabled || !token) { if (!WEBSOCKETS_ENABLED || !enabled || !token) {
if (reconnectTimeoutRef.current) {
clearTimeout(reconnectTimeoutRef.current);
}
if (wsRef.current) { if (wsRef.current) {
wsRef.current.close(1000, "disabled"); wsRef.current.close(1000, "disabled");
wsRef.current = null; wsRef.current = null;
} }
return; retriesRef.current = 0;
return () => {
generationRef.current += 1;
};
} }
retriesRef.current = 0;
connect(); connect();
return () => { return () => {
generationRef.current += 1;
if (reconnectTimeoutRef.current) { if (reconnectTimeoutRef.current) {
clearTimeout(reconnectTimeoutRef.current); clearTimeout(reconnectTimeoutRef.current);
} }

View file

@ -34,7 +34,6 @@ import { FeatureCollection, Feature } from "geojson";
import DrawController from "./mapControllers/drawController"; import DrawController from "./mapControllers/drawController";
//import { Geometry } from "geojson"; //import { Geometry } from "geojson";
import Map, { MapRef, Marker, MarkerDragEvent } from "react-map-gl/mapbox"; import Map, { MapRef, Marker, MarkerDragEvent } from "react-map-gl/mapbox";
import { getDistanceUnit } from "utils";
import { MapMouseEvent } from "mapbox-gl"; import { MapMouseEvent } from "mapbox-gl";
import { makeStyles } from "@mui/styles"; import { makeStyles } from "@mui/styles";
import { Result } from "@mapbox/mapbox-gl-geocoder"; import { Result } from "@mapbox/mapbox-gl-geocoder";
@ -499,7 +498,7 @@ export default function MapBase(props: Props) {
lineWidth: 5, lineWidth: 5,
origin: pond.DataOrigin.DATA_ORIGIN_ADAPTIVE, origin: pond.DataOrigin.DATA_ORIGIN_ADAPTIVE,
totalDist: totalDist:
getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS
? (data.totalDistanceKm * 1000).toFixed(2) ? (data.totalDistanceKm * 1000).toFixed(2)
: (data.totalDistanceKm * 3280.8398950131).toFixed(2) : (data.totalDistanceKm * 3280.8398950131).toFixed(2)
}; };

View file

@ -1,8 +1,8 @@
import GrainDescriber from "grain/GrainDescriber"; import GrainDescriber from "grain/GrainDescriber";
import { cloneDeep } from "lodash"; import { cloneDeep } from "lodash";
import { MarkerData } from "maps/mapMarkers/Markers"; import { MarkerData } from "maps/mapMarkers/Markers";
import { User } from "models";
import { pond } from "protobuf-ts/pond"; import { pond } from "protobuf-ts/pond";
import { getGrainUnit } from "utils";
import { stringToMaterialColour } from "utils/strings"; import { stringToMaterialColour } from "utils/strings";
import { or } from "utils/types"; import { or } from "utils/types";
@ -236,11 +236,11 @@ export class Bin {
return bpt; return bpt;
} }
public grainInventory(): number { public grainInventory(user: User): number {
let grain = this.bushels() let grain = this.bushels()
if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && this.bushelsPerTonne() > 1){ if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && this.bushelsPerTonne() > 1){
grain = this.bushels() / this.bushelsPerTonne() grain = this.bushels() / this.bushelsPerTonne()
}else if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && this.bushelsPerTonne() > 1){ }else if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && this.bushelsPerTonne() > 1){
grain = this.bushels() / (this.bushelsPerTonne() * 0.907) grain = this.bushels() / (this.bushelsPerTonne() * 0.907)
} }
return Math.round(grain*100)/100 return Math.round(grain*100)/100

View file

@ -2,8 +2,9 @@ import { Option } from "common/SearchSelect";
import { GrainOptions, ToGrainOption } from "grain"; import { GrainOptions, ToGrainOption } from "grain";
import GrainDescriber from "grain/GrainDescriber"; import GrainDescriber from "grain/GrainDescriber";
import { cloneDeep } from "lodash"; import { cloneDeep } from "lodash";
import { User } from "models";
import { pond } from "protobuf-ts/pond"; import { pond } from "protobuf-ts/pond";
import { getGrainUnit, stringToMaterialColour } from "utils"; import { stringToMaterialColour } from "utils";
import { or } from "utils/types"; import { or } from "utils/types";
export class Contract { export class Contract {
@ -14,26 +15,28 @@ export class Contract {
public label: string = ""; public label: string = "";
private objKey: string = ""; private objKey: string = "";
public static create(pb?: pond.Contract): Contract { public static create(pb?: pond.Contract, user?: User): Contract {
let my = new Contract(); let my = new Contract();
if (pb) { if (pb) {
my.settings = pond.ContractSettings.fromObject(cloneDeep(or(pb.settings, {}))); my.settings = pond.ContractSettings.fromObject(cloneDeep(or(pb.settings, {})));
my.title = pb.name; my.title = pb.name;
my.objKey = pb.key; my.objKey = pb.key;
my.unit = my.measurementUnit(); my.unit = my.measurementUnit(user);
my.colour = my.commodityColour(); my.colour = my.commodityColour();
my.label = my.commodityLabel(); my.label = my.commodityLabel();
} }
return my; return my;
} }
private measurementUnit(): string { private measurementUnit(user?: User): string {
if (this.settings.type === pond.ContractType.CONTRACT_TYPE_GRAIN) { if (this.settings.type === pond.ContractType.CONTRACT_TYPE_GRAIN) {
if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && this.conversionValue() > 1){ if( user ){
return "mT" if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && this.conversionValue() > 1){
} return "mT"
if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && this.conversionValue() > 1){ }
return "t" if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && this.conversionValue() > 1){
return "t"
}
} }
return "bu"; return "bu";
} }
@ -63,21 +66,22 @@ export class Contract {
return label; return label;
} }
public static clone(other?: Contract): Contract { public static clone(other?: Contract, user?: User): Contract {
if (other) { if (other) {
return Contract.create( return Contract.create(
pond.Contract.fromObject({ pond.Contract.fromObject({
title: other.title, title: other.title,
key: other.objKey, key: other.objKey,
settings: cloneDeep(other.settings) settings: cloneDeep(other.settings)
}) }),
user
); );
} }
return Contract.create(); return Contract.create(undefined, user);
} }
public static any(data: any): Contract { public static any(data: any, user?: User): Contract {
return Contract.create(pond.Contract.fromObject(cloneDeep(data))); return Contract.create(pond.Contract.fromObject(cloneDeep(data)), user);
} }
public key(): string { public key(): string {
@ -204,28 +208,28 @@ export class Contract {
return this.conversionValue(); return this.conversionValue();
} }
public sizeInPreferredUnit(): number { public sizeInPreferredUnit(user: User): number {
let size = this.settings.size; let size = this.settings.size;
switch (this.settings.type) { switch (this.settings.type) {
case pond.ContractType.CONTRACT_TYPE_GRAIN: case pond.ContractType.CONTRACT_TYPE_GRAIN:
if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && this.conversionValue() > 1) { if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && this.conversionValue() > 1) {
size = size / this.conversionValue(); size = size / this.conversionValue();
} }
if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && this.conversionValue() > 1){ if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && this.conversionValue() > 1){
size = size / (this.conversionValue() * 0.907); //the conversion for grain will be stored in metric tonnes, this will convert it to US tons size = size / (this.conversionValue() * 0.907); //the conversion for grain will be stored in metric tonnes, this will convert it to US tons
} }
} }
return Math.round(size * 100) / 100; return Math.round(size * 100) / 100;
} }
public deliveredInPreferredUnit(): number { public deliveredInPreferredUnit(user: User): number {
let del = this.settings.delivered; let del = this.settings.delivered;
switch (this.settings.type) { switch (this.settings.type) {
case pond.ContractType.CONTRACT_TYPE_GRAIN: case pond.ContractType.CONTRACT_TYPE_GRAIN:
if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && this.conversionValue() > 1) { if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && this.conversionValue() > 1) {
del = del / this.conversionValue(); del = del / this.conversionValue();
} }
if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && this.conversionValue() > 1){ if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && this.conversionValue() > 1){
del = del / (this.conversionValue() * 0.907); //the conversion for grain will be stored in metric tonnes, this will convert it to US tons del = del / (this.conversionValue() * 0.907); //the conversion for grain will be stored in metric tonnes, this will convert it to US tons
} }
} }
@ -237,14 +241,15 @@ export class Contract {
public static toStoredUnit( public static toStoredUnit(
secondaryUnitVal: number, secondaryUnitVal: number,
contractType: pond.ContractType, contractType: pond.ContractType,
conversionValue: number conversionValue: number,
user: User
): number { ): number {
let storedUnitVal = secondaryUnitVal; let storedUnitVal = secondaryUnitVal;
switch (contractType) { switch (contractType) {
//use the value and conversion they entered directly, it is safe to assume they are both the same unit (ton vs tonne) so dont need to convert anything //use the value and conversion they entered directly, it is safe to assume they are both the same unit (ton vs tonne) so dont need to convert anything
//before converting to bushels //before converting to bushels
case pond.ContractType.CONTRACT_TYPE_GRAIN: case pond.ContractType.CONTRACT_TYPE_GRAIN:
if ((getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE || getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON) && conversionValue > 1) { if ((user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE || user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON) && conversionValue > 1) {
storedUnitVal = secondaryUnitVal * conversionValue; storedUnitVal = secondaryUnitVal * conversionValue;
} }
} }

View file

@ -101,4 +101,20 @@ export class User {
} }
return this.settings.features.includes(flag); return this.settings.features.includes(flag);
} }
public tempUnit(): pond.TemperatureUnit {
return this.settings.temperatureUnit
}
public pressureUnit(): pond.PressureUnit {
return this.settings.pressureUnit
}
public distanceUnit(): pond.DistanceUnit {
return this.settings.distanceUnit
}
public grainUnit(): pond.GrainUnit {
return this.settings.grainUnit
}
} }

View file

@ -2,13 +2,11 @@ import { Column } from "common/ResponsiveTable";
import { Option } from "common/SearchSelect"; import { Option } from "common/SearchSelect";
import GrainDescriber from "grain/GrainDescriber"; import GrainDescriber from "grain/GrainDescriber";
//import { Column } from "material-table"; //import { Column } from "material-table";
import { Bin, BinYard, Field } from "models"; import { Bin, BinYard, Field, User } from "models";
//import { Gate } from "models/Gate"; //import { Gate } from "models/Gate";
import { GrainBag } from "models/GrainBag"; import { GrainBag } from "models/GrainBag";
//import { ObjectHeater } from "models/ObjectHeater"; //import { ObjectHeater } from "models/ObjectHeater";
import { pond } from "protobuf-ts/pond"; import { pond } from "protobuf-ts/pond";
import { getDistanceUnit, getTemperatureUnit } from "utils";
interface Sort { interface Sort {
order: string; //what to send to the backend list to sort by order: string; //what to send to the backend list to sort by
numerical: boolean; //whether to use a numerical or text sort numerical: boolean; //whether to use a numerical or text sort
@ -20,6 +18,8 @@ export interface ObjectExtension {
isTransactionObject: boolean; isTransactionObject: boolean;
//this will define the columns to be used for the object in a material table //this will define the columns to be used for the object in a material table
tableColumns: Column<any>[]; tableColumns: Column<any>[];
// Optional factory so columns can depend on the active user settings (units, formatting, etc.)
getTableColumns?: (user?: User) => Column<any>[];
//this map will match the title of the column to what the backend needs to perform the ordering //this map will match the title of the column to what the backend needs to perform the ordering
tableSort: Map<string, Sort>; tableSort: Map<string, Sort>;
} }
@ -32,6 +32,202 @@ const defaultObject: ObjectExtension = {
tableSort: new Map() tableSort: new Map()
}; };
const binColumns = (user?: User): Column<any>[] => {
const temperatureUnit =
user?.tempUnit() ?? pond.TemperatureUnit.TEMPERATURE_UNIT_CELSIUS;
return [
{
title: "Name",
cellStyle: {padding: "16px"},
render: row => {
return (<div>{row.settings.name}</div>);
}
},
{
title: "Grain",
cellStyle: {padding: "16px"},
render: row => {
let grain = row.settings.inventory?.grainType;
if (grain) {
return GrainDescriber(grain).name;
}
}
},
{
title: "Bin Height",
cellStyle: {padding: "16px"},
render: row => {
let d = row.settings.specs?.heightCm;
if (d) {
if (
(user?.distanceUnit() ?? pond.DistanceUnit.DISTANCE_UNIT_FEET) ===
pond.DistanceUnit.DISTANCE_UNIT_METERS
) {
return (d / 100).toFixed(2) + " m";
} else {
return (d / 30.48).toFixed(2) + " ft";
}
}
}
},
{
title: "Bin Diameter",
cellStyle: {padding: "16px"},
render: row => {
let d = row.settings.specs?.diameterCm;
if (d) {
if (
(user?.distanceUnit() ?? pond.DistanceUnit.DISTANCE_UNIT_FEET) ===
pond.DistanceUnit.DISTANCE_UNIT_METERS
) {
return (d / 100).toFixed(2) + " m";
} else {
return (d / 30.48).toFixed(2) + " ft";
}
}
}
},
{
title: "Custom Grain Name",
cellStyle: {padding: "16px"},
render: row => {
return row.settings.inventory?.customTypeName;
}
},
{
title: "Grain Variant",
cellStyle: {padding: "16px"},
render: row => {
return row.settings.inventory?.grainSubtype;
}
},
{
title: "Grain Bushels",
cellStyle: {padding: "16px"},
render: row => {
return (<div>{row.settings.inventory ? isNaN(row.settings.inventory.grainBushels) ? 0 : row.settings.inventory.grainBushels : 0}</div>);
}
},
{
title: "Grain Capacity",
cellStyle: {padding: "16px"},
render: row => {
return row.settings.specs?.bushelCapacity;
}
},
{
title: "High Temp Warning",
cellStyle: {padding: "16px"},
render: row => {
let t = row.settings.highTemp;
if (t) {
if (temperatureUnit === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
return (t * 1.8 + 32).toFixed(2) + " °F";
}
return t.toFixed(2) + " °C";
}
}
},
{
title: "Low Temp Warning",
cellStyle: {padding: "16px"},
render: row => {
let t = row.settings.lowTemp;
if (t) {
if (temperatureUnit === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
return (t * 1.8 + 32).toFixed(2) + " °F";
}
return t.toFixed(2) + " °C";
}
}
}
] as Column<Bin>[];
};
const grainBagColumns = (user?: User): Column<GrainBag>[] => {
const distanceUnit =
user?.distanceUnit() ?? pond.DistanceUnit.DISTANCE_UNIT_FEET;
return [
{
title: "Name",
render: row => {
if (row.title) {
return (<div>{row.title.toString()}</div>);
}
}
},
{
title: "Length",
render: row => {
if (row.settings.length) {
if (distanceUnit === pond.DistanceUnit.DISTANCE_UNIT_METERS) {
return row.settings.length.toFixed(2) + " m";
} else {
return (row.settings.length * 3.281).toFixed(2) + " ft";
}
}
}
},
{
title: "Diameter",
render: row => {
if (row.settings.diameter) {
if (distanceUnit === pond.DistanceUnit.DISTANCE_UNIT_METERS) {
return row.settings.diameter.toFixed(2) + " m";
} else {
return (row.settings.diameter * 3.281).toFixed(2) + " ft";
}
}
}
},
{
title: "Grain",
render: row => {
if (row.settings.supportedGrain) {
return GrainDescriber(row.settings.supportedGrain).name;
}
}
},
{
title: "Custom Grain",
render: row => {
return row.settings.customGrain;
}
},
{
title: "Grain Variant",
render: row => {
return row.settings.grainSubtype;
}
},
{
title: "Capacity",
render: row => {
return row.settings.bushelCapacity + " bu";
}
},
{
title: "Bushels",
render: row => {
return row.settings.currentBushels + " bu";
}
},
{
title: "Fill Date",
render: row => {
return row.settings.fillDate;
}
},
{
title: "Initial Moisture",
render: row => {
return row.settings.initialMoisture + "%";
}
}
] as Column<GrainBag>[];
};
export const ObjectExtensions: Map<pond.ObjectType, ObjectExtension> = new Map([ export const ObjectExtensions: Map<pond.ObjectType, ObjectExtension> = new Map([
[pond.ObjectType.OBJECT_TYPE_UNKNOWN, defaultObject], [pond.ObjectType.OBJECT_TYPE_UNKNOWN, defaultObject],
[ [
@ -50,109 +246,8 @@ export const ObjectExtensions: Map<pond.ObjectType, ObjectExtension> = new Map([
name: "Bin", name: "Bin",
inventoryGroup: "grain", inventoryGroup: "grain",
isTransactionObject: true, isTransactionObject: true,
tableColumns: [ tableColumns: binColumns(),
{ getTableColumns: (user?: User) => binColumns(user),
title: "Name",
cellStyle: {padding: "16px"},
render: row => {
return (<div>{row.settings.name}</div>);
}
},
{
title: "Grain",
cellStyle: {padding: "16px"},
render: row => {
let grain = row.settings.inventory?.grainType;
if (grain) {
return GrainDescriber(grain).name;
}
}
},
{
title: "Bin Height",
cellStyle: {padding: "16px"},
render: row => {
let d = row.settings.specs?.heightCm;
if (d) {
if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) {
return (d / 100).toFixed(2) + " m";
} else {
return (d / 30.48).toFixed(2) + " ft";
}
}
}
},
{
title: "Bin Diameter",
cellStyle: {padding: "16px"},
render: row => {
let d = row.settings.specs?.diameterCm;
if (d) {
if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) {
return (d / 100).toFixed(2) + " m";
} else {
return (d / 30.48).toFixed(2) + " ft";
}
}
}
},
{
title: "Custom Grain Name",
cellStyle: {padding: "16px"},
render: row => {
return row.settings.inventory?.customTypeName;
}
},
{
title: "Grain Variant",
cellStyle: {padding: "16px"},
render: row => {
return row.settings.inventory?.grainSubtype;
}
},
{
title: "Grain Bushels",
cellStyle: {padding: "16px"},
render: row => {
return (<div>{row.settings.inventory ? isNaN(row.settings.inventory.grainBushels) ? 0 : row.settings.inventory.grainBushels : 0}</div>);
}
},
{
title: "Grain Capacity",
cellStyle: {padding: "16px"},
render: row => {
return row.settings.specs?.bushelCapacity;
}
},
{
title: "High Temp Warning",
cellStyle: {padding: "16px"},
render: row => {
let t = row.settings.highTemp;
if (t) {
if (getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
return (t * 1.8 + 32).toFixed(2) + " °F";
} else {
return t.toFixed(2) + " °C";
}
}
}
},
{
title: "Low Temp Warning",
cellStyle: {padding: "16px"},
render: row => {
let t = row.settings.lowTemp;
if (t) {
if (getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
return (t * 1.8 + 32).toFixed(2) + " °F";
} else {
return t.toFixed(2) + " °C";
}
}
}
}
] as Column<Bin>[],
tableSort: new Map([ tableSort: new Map([
["Name", { order: "name", numerical: false }], ["Name", { order: "name", numerical: false }],
["Grain", { order: "inventory.grainType", numerical: false }], ["Grain", { order: "inventory.grainType", numerical: false }],
@ -395,84 +490,8 @@ export const ObjectExtensions: Map<pond.ObjectType, ObjectExtension> = new Map([
name: "Grainbag", name: "Grainbag",
inventoryGroup: "grain", inventoryGroup: "grain",
isTransactionObject: true, isTransactionObject: true,
tableColumns: [ tableColumns: grainBagColumns(),
{ getTableColumns: (user?: User) => grainBagColumns(user),
title: "Name",
render: row => {
if (row.title) {
return (<div>{row.title.toString()}</div>);
}
}
},
{
title: "Length",
render: row => {
if (row.settings.length) {
if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) {
return row.settings.length.toFixed(2) + " m";
} else {
return (row.settings.length * 3.281).toFixed(2) + " ft";
}
}
}
},
{
title: "Diameter",
render: row => {
if (row.settings.diameter) {
if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) {
return row.settings.diameter.toFixed(2) + " m";
} else {
return (row.settings.diameter * 3.281).toFixed(2) + " ft";
}
}
}
},
{
title: "Grain",
render: row => {
if (row.settings.supportedGrain) {
return GrainDescriber(row.settings.supportedGrain).name;
}
}
},
{
title: "Custom Grain",
render: row => {
return row.settings.customGrain;
}
},
{
title: "Grain Variant",
render: row => {
return row.settings.grainSubtype;
}
},
{
title: "Capacity",
render: row => {
return row.settings.bushelCapacity + " bu";
}
},
{
title: "Bushels",
render: row => {
return row.settings.currentBushels + " bu";
}
},
{
title: "Fill Date",
render: row => {
return row.settings.fillDate;
}
},
{
title: "Initial Moisture",
render: row => {
return row.settings.initialMoisture + "%";
}
}
] as Column<GrainBag>[],
tableSort: new Map([ tableSort: new Map([
["Name", { order: "name", numerical: false }], ["Name", { order: "name", numerical: false }],
["Length", { order: "length", numerical: true }], ["Length", { order: "length", numerical: true }],
@ -708,8 +727,7 @@ export const ObjectExtensions: Map<pond.ObjectType, ObjectExtension> = new Map([
]); ]);
export default function ObjectDescriber(type: pond.ObjectType): ObjectExtension { export default function ObjectDescriber(type: pond.ObjectType): ObjectExtension {
let describer = ObjectExtensions.get(type); return ObjectExtensions.get(type) ?? defaultObject;
return describer ? describer : defaultObject;
} }
/** /**
@ -745,7 +763,8 @@ export function SearchableObjects(): Option[] {
Object.values(pond.ObjectType).forEach(obj => { Object.values(pond.ObjectType).forEach(obj => {
if (typeof obj !== "string") { if (typeof obj !== "string") {
let ext = ObjectDescriber(obj); let ext = ObjectDescriber(obj);
if (ext.tableColumns.length > 0) { const columns = ext.getTableColumns ? ext.getTableColumns() : ext.tableColumns;
if (columns.length > 0) {
options.push({ options.push({
label: ext.name, label: ext.name,
value: obj value: obj

View file

@ -1,6 +1,5 @@
import { Box, Button, Grid2 as Grid } from "@mui/material"; import { Box, Button, Grid2 as Grid } from "@mui/material";
// import { getTableIcons } from "common/ResponsiveTable"; // import { getTableIcons } from "common/ResponsiveTable";
import SearchBar from "common/SearchBar";
import SearchSelect, { Option } from "common/SearchSelect"; import SearchSelect, { Option } from "common/SearchSelect";
// import MaterialTable, { MTableToolbar } from "material-table"; // import MaterialTable, { MTableToolbar } from "material-table";
// import { Bin, BinYard, Field } from "models"; // import { Bin, BinYard, Field } from "models";
@ -22,9 +21,8 @@ import {
import { useCallback, useEffect, useState } from "react"; import { useCallback, useEffect, useState } from "react";
import TeamSearch from "teams/TeamSearch"; import TeamSearch from "teams/TeamSearch";
import BulkBinSettings from "./bulkEditForms/bulkBinSettings"; import BulkBinSettings from "./bulkEditForms/bulkBinSettings";
import BulkGrainBagSettings from "./bulkEditForms/bulkGrainBagSettings";
import ResponsiveTable from "common/ResponsiveTable"; import ResponsiveTable from "common/ResponsiveTable";
import { cloneDeep, indexOf } from "lodash"; import { cloneDeep } from "lodash";
//import BulkGateSettings from "./bulkEditForms/bulkGateSettings"; //import BulkGateSettings from "./bulkEditForms/bulkGateSettings";
interface customButton { interface customButton {
@ -61,6 +59,9 @@ export default function ObjectTable(props: Props) {
const [selectedUser, setSelectedUser] = useState<string>(as ?? user.id()); const [selectedUser, setSelectedUser] = useState<string>(as ?? user.id());
const [selectedPageRows, setSelectedPageRows] = useState<Map<number, number[]>>(new Map())//key is the page value is an array of row indexes for that page const [selectedPageRows, setSelectedPageRows] = useState<Map<number, number[]>>(new Map())//key is the page value is an array of row indexes for that page
const [tableLoading, setTableLoading] = useState(false); const [tableLoading, setTableLoading] = useState(false);
const objectExtension = ObjectDescriber(currentType);
const columns = objectExtension.getTableColumns ? objectExtension.getTableColumns(user) : objectExtension.tableColumns;
//the api's to load all the objects available to look at in this table //the api's to load all the objects available to look at in this table
const binAPI = useBinAPI(); const binAPI = useBinAPI();
const binyardAPI = useBinYardAPI(); const binyardAPI = useBinYardAPI();
@ -366,7 +367,7 @@ export default function ObjectTable(props: Props) {
<ResponsiveTable <ResponsiveTable
rows={tableData} rows={tableData}
columns={ObjectDescriber(currentType).tableColumns} columns={columns}
onRowClick={rowClickFunction} onRowClick={rowClickFunction}
total={objectTotal} total={objectTotal}
pageSize={pageSize} pageSize={pageSize}
@ -383,7 +384,7 @@ export default function ObjectTable(props: Props) {
key="objectList" key="objectList"
title={ObjectDescriber(currentType).name} title={ObjectDescriber(currentType).name}
icons={getTableIcons()} icons={getTableIcons()}
columns={ObjectDescriber(currentType).tableColumns} columns={ObjectDescriber(currentType, user).tableColumns}
data={tableData} data={tableData}
page={tablePage} page={tablePage}
totalCount={objectTotal} totalCount={objectTotal}
@ -399,10 +400,10 @@ export default function ObjectTable(props: Props) {
}} }}
onOrderChange={(by, direction) => { onOrderChange={(by, direction) => {
if (by !== -1) { if (by !== -1) {
let colName = ObjectDescriber(currentType).tableColumns[by].title?.toString(); let colName = ObjectDescriber(currentType, user).tableColumns[by].title?.toString();
let order; let order;
if (colName) { if (colName) {
order = ObjectDescriber(currentType).tableSort.get(colName); order = ObjectDescriber(currentType, user).tableSort.get(colName);
setCurrentOrder(order?.order); setCurrentOrder(order?.order);
setCurrentDirection(direction); setCurrentDirection(direction);
setIsNumerical(order?.numerical); setIsNumerical(order?.numerical);

View file

@ -4,7 +4,7 @@ import { GrainOptions } from "grain";
import { pond } from "protobuf-ts/pond"; import { pond } from "protobuf-ts/pond";
import { useBinAPI, useGlobalState, useSnackbar } from "providers"; import { useBinAPI, useGlobalState, useSnackbar } from "providers";
import React, { useState } from "react"; import React, { useState } from "react";
import { fahrenheitToCelsius, getDistanceUnit, getTemperatureUnit } from "utils"; import { fahrenheitToCelsius } from "utils";
interface Props { interface Props {
selectedBins: pond.Bin[]; selectedBins: pond.Bin[];
@ -19,7 +19,7 @@ export default function BulkBinSettings(props: Props) {
const gridItemWidth = 3; const gridItemWidth = 3;
// bin settings variables // bin settings variables
const [name, setName] = useState<string | undefined>(); const [name, setName] = useState<string | undefined>();
const [{as}] = useGlobalState(); const [{as, user}] = useGlobalState();
const [grainType, setGrainType] = useState<pond.Grain | undefined>(); const [grainType, setGrainType] = useState<pond.Grain | undefined>();
const [grainOption, setGrainOption] = useState<Option | null>(); const [grainOption, setGrainOption] = useState<Option | null>();
const [height, setHeight] = useState<number | undefined>(); const [height, setHeight] = useState<number | undefined>();
@ -32,10 +32,10 @@ export default function BulkBinSettings(props: Props) {
const [lowTemp, setLowTemp] = useState<number | undefined>(); const [lowTemp, setLowTemp] = useState<number | undefined>();
const convertedDistance = (enteredDistance: number) => { const convertedDistance = (enteredDistance: number) => {
if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) { if (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) {
return enteredDistance * 100; return enteredDistance * 100;
} }
if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET) { if (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET) {
return enteredDistance * 30.48; return enteredDistance * 30.48;
} }
return enteredDistance; return enteredDistance;
@ -43,7 +43,7 @@ export default function BulkBinSettings(props: Props) {
const convertedTemp = (enteredTemp: number) => { const convertedTemp = (enteredTemp: number) => {
let t = enteredTemp; let t = enteredTemp;
if (getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) { if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
t = fahrenheitToCelsius(enteredTemp); t = fahrenheitToCelsius(enteredTemp);
} }
return t; return t;
@ -168,7 +168,7 @@ export default function BulkBinSettings(props: Props) {
InputProps={{ InputProps={{
endAdornment: ( endAdornment: (
<InputAdornment position="end"> <InputAdornment position="end">
{getDistanceUnit() !== pond.DistanceUnit.DISTANCE_UNIT_FEET ? "m" : "ft"} {user.distanceUnit() !== pond.DistanceUnit.DISTANCE_UNIT_FEET ? "m" : "ft"}
</InputAdornment> </InputAdornment>
) )
}} }}
@ -186,7 +186,7 @@ export default function BulkBinSettings(props: Props) {
InputProps={{ InputProps={{
endAdornment: ( endAdornment: (
<InputAdornment position="end"> <InputAdornment position="end">
{getDistanceUnit() !== pond.DistanceUnit.DISTANCE_UNIT_FEET ? "m" : "ft"} {user.distanceUnit() !== pond.DistanceUnit.DISTANCE_UNIT_FEET ? "m" : "ft"}
</InputAdornment> </InputAdornment>
) )
}} }}
@ -204,7 +204,7 @@ export default function BulkBinSettings(props: Props) {
InputProps={{ InputProps={{
endAdornment: ( endAdornment: (
<InputAdornment position="end"> <InputAdornment position="end">
{getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT {user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT
? "°F" ? "°F"
: "°C"} : "°C"}
</InputAdornment> </InputAdornment>
@ -224,7 +224,7 @@ export default function BulkBinSettings(props: Props) {
InputProps={{ InputProps={{
endAdornment: ( endAdornment: (
<InputAdornment position="end"> <InputAdornment position="end">
{getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT {user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT
? "°F" ? "°F"
: "°C"} : "°C"}
</InputAdornment> </InputAdornment>

View file

@ -5,7 +5,6 @@ import { GrainBag } from "models/GrainBag";
import { pond } from "protobuf-ts/pond"; import { pond } from "protobuf-ts/pond";
import { useGlobalState, useGrainBagAPI, useSnackbar } from "providers"; import { useGlobalState, useGrainBagAPI, useSnackbar } from "providers";
import React, { useState } from "react"; import React, { useState } from "react";
import { getDistanceUnit } from "utils";
interface Props { interface Props {
selectedBags: GrainBag[]; selectedBags: GrainBag[];
@ -28,10 +27,10 @@ export default function BulkGrainBagSettings(props: Props) {
const [bushels, setBushels] = useState<number | undefined>(); const [bushels, setBushels] = useState<number | undefined>();
const [fillDate, setFillDate] = useState<string | undefined>(); const [fillDate, setFillDate] = useState<string | undefined>();
const [initialMoisture, setInitialMoisture] = useState<number | undefined>(); const [initialMoisture, setInitialMoisture] = useState<number | undefined>();
const [{as}] = useGlobalState(); const [{ as, user }] = useGlobalState();
const convertedDistance = (enteredDistance: number) => { const convertedDistance = (enteredDistance: number) => {
if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET) { if (user.distanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET) {
return enteredDistance / 3.281; return enteredDistance / 3.281;
} }
return enteredDistance; return enteredDistance;

View file

@ -63,7 +63,7 @@ import {
} from "providers"; } from "providers";
import React, { SetStateAction, useCallback, useEffect, useState } from "react"; import React, { SetStateAction, useCallback, useEffect, useState } from "react";
// import { getThemeType } from "theme"; // import { getThemeType } from "theme";
import { getGrainUnit, stringToMaterialColour } from "utils"; import { stringToMaterialColour } from "utils";
//import InfiniteScroll from "react-infinite-scroller"; //import InfiniteScroll from "react-infinite-scroller";
import PageContainer from "./PageContainer"; import PageContainer from "./PageContainer";
import ObjectTable from "objects/ObjectTable"; import ObjectTable from "objects/ObjectTable";
@ -934,7 +934,7 @@ export default function Bins(props: Props) {
return " bu"; return " bu";
} }
switch (getGrainUnit()) { switch (user.grainUnit()) {
case pond.GrainUnit.GRAIN_UNIT_TONNE: case pond.GrainUnit.GRAIN_UNIT_TONNE:
return " mT"; return " mT";
case pond.GrainUnit.GRAIN_UNIT_TON: case pond.GrainUnit.GRAIN_UNIT_TON:
@ -947,9 +947,9 @@ export default function Bins(props: Props) {
const customQuantityDisplay = (customInventory: pond.CustomInventory, bushels: number) => { const customQuantityDisplay = (customInventory: pond.CustomInventory, bushels: number) => {
let amount = bushels let amount = bushels
if(customInventory.bushelsPerTonne > 1){ if(customInventory.bushelsPerTonne > 1){
if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE){ if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE){
amount = bushels/customInventory.bushelsPerTonne amount = bushels/customInventory.bushelsPerTonne
}else if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON){ }else if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON){
amount = bushels/(customInventory.bushelsPerTonne*0.907) amount = bushels/(customInventory.bushelsPerTonne*0.907)
} }
} }
@ -959,9 +959,9 @@ export default function Bins(props: Props) {
const supportedGrainDisplay = (grain: pond.Grain, bushels: number) => { const supportedGrainDisplay = (grain: pond.Grain, bushels: number) => {
const describer = GrainDescriber(grain) const describer = GrainDescriber(grain)
let amount = bushels let amount = bushels
if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE){ if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE){
amount = bushels/describer.bushelsPerTonne amount = bushels/describer.bushelsPerTonne
}else if (getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON){ }else if (user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON){
amount = bushels/describer.bushelsPerTon amount = bushels/describer.bushelsPerTon
} }
return Math.round(amount*100)/100 return Math.round(amount*100)/100

View file

@ -11,7 +11,7 @@ import PageContainer from "./PageContainer";
export default function Contracts() { export default function Contracts() {
const [openSettings, setOpenSettings] = useState(false); const [openSettings, setOpenSettings] = useState(false);
const [{ as }] = useGlobalState() const [{ as, user }] = useGlobalState()
const contractAPI = useContractAPI(); const contractAPI = useContractAPI();
const [contracts, setContracts] = useState<Contract[]>([]); const [contracts, setContracts] = useState<Contract[]>([]);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
@ -42,7 +42,7 @@ export default function Contracts() {
let contractPermissions: Map<string, pond.Permission[]> = new Map(); let contractPermissions: Map<string, pond.Permission[]> = new Map();
if (resp.data.contracts){ if (resp.data.contracts){
resp.data.contracts.forEach(contract => { resp.data.contracts.forEach(contract => {
let c = Contract.create(contract); let c = Contract.create(contract, user);
contracts.push(c); contracts.push(c);
let p = pond.EvaluatePermissionsResponse.fromObject( let p = pond.EvaluatePermissionsResponse.fromObject(
resp.data.contractPermissions[c.key()] resp.data.contractPermissions[c.key()]
@ -56,7 +56,7 @@ export default function Contracts() {
}) })
.catch(err => {}); .catch(err => {});
} }
}, [contractAPI, year, as]); // eslint-disable-line react-hooks/exhaustive-deps }, [contractAPI, year, as, user]); // eslint-disable-line react-hooks/exhaustive-deps
return ( return (
<PageContainer> <PageContainer>

View file

@ -3,7 +3,8 @@ import Grid from '@mui/material/Grid2';
import { Component, Device, Interaction, User } from "models"; import { Component, Device, Interaction, User } from "models";
import { getContextKeys, getContextTypes } from "pbHelpers/Context"; import { getContextKeys, getContextTypes } from "pbHelpers/Context";
import { useDeviceAPI, useGlobalState, useSnackbar } from "providers"; import { useDeviceAPI, useGlobalState, useSnackbar } from "providers";
import { useEffect, useState } from "react"; import { useDeviceStatusStreams } from "hooks/useDeviceStatusStreams";
import { useCallback, useEffect, useMemo, useState } from "react";
import { useLocation, useParams } from "react-router-dom"; import { useLocation, useParams } from "react-router-dom";
import PageContainer from "./PageContainer"; import PageContainer from "./PageContainer";
import { useHTTP, useMobile } from "hooks"; import { useHTTP, useMobile } from "hooks";
@ -23,6 +24,25 @@ import DeviceWizard from "device/DeviceWizard";
import DeviceScannedComponents from "device/autoDetect/DeviceScannedComponents"; import DeviceScannedComponents from "device/autoDetect/DeviceScannedComponents";
import { useWebSocket } from "hooks/useWebSocket"; import { useWebSocket } from "hooks/useWebSocket";
type TimestampedReading = { timestamp?: string };
function getTimestampMillis(timestamp?: string): number | undefined {
if (!timestamp) return undefined;
const parsed = Date.parse(timestamp);
return Number.isNaN(parsed) ? undefined : parsed;
}
function pickNewestReading<T extends TimestampedReading>(
current: T | null | undefined,
incoming: T | null | undefined
): T | null | undefined {
const currentTs = getTimestampMillis(current?.timestamp);
const incomingTs = getTimestampMillis(incoming?.timestamp);
if (incomingTs === undefined) return current ?? incoming;
if (currentTs === undefined) return incoming ?? current;
return incomingTs >= currentTs ? incoming : current;
}
export interface DevicePageData { export interface DevicePageData {
device: Device; device: Device;
components: Component[]; components: Component[];
@ -38,7 +58,7 @@ export default function DevicePage() {
const isMobile = useMobile() const isMobile = useMobile()
const deviceID = useParams<{ deviceID: string }>()?.deviceID ?? ""; const deviceID = useParams<{ deviceID: string }>()?.deviceID ?? "";
const groupID = useParams<{ groupID: string }>()?.groupID ?? ""; const groupID = useParams<{ groupID: string }>()?.groupID ?? "";
const { state } = useLocation(); const { state, pathname: locationPathname } = useLocation();
const [{ as, team, user }] = useGlobalState() const [{ as, team, user }] = useGlobalState()
// console.log(state) // console.log(state)
const [device, setDevice] = useState<Device>(state?.device ? Device.create(state.device) : Device.create()) const [device, setDevice] = useState<Device>(state?.device ? Device.create(state.device) : Device.create())
@ -68,6 +88,63 @@ export default function DevicePage() {
const { token } = useHTTP(); const { token } = useHTTP();
const liveContextKeys = useMemo(() => getContextKeys(), [locationPathname]);
const liveContextTypes = useMemo(() => getContextTypes(), [locationPathname]);
/** Matches getDevicePageData: omit ?as= when first context type is "team". */
const liveAs =
as && !(liveContextTypes.length > 0 && liveContextTypes[0] === "team")
? as
: undefined;
const liveStatusDeviceIds = useMemo(
() => (deviceID ? [deviceID] : []),
[deviceID]
);
const handleLiveDeviceStatus = useCallback(
(streamDeviceId: string, status: pond.DeviceStatus) => {
setDevice((prev) => {
if (prev.id().toString() !== streamDeviceId) return prev;
const updated = Device.clone(prev);
const incomingStatus = pond.DeviceStatus.fromObject(status);
const currentStatus = prev.status ?? pond.DeviceStatus.create();
incomingStatus.plenum = pickNewestReading(currentStatus.plenum, incomingStatus.plenum);
incomingStatus.sen5x = pickNewestReading(currentStatus.sen5x, incomingStatus.sen5x);
incomingStatus.co = pickNewestReading(currentStatus.co, incomingStatus.co);
incomingStatus.co2 = pickNewestReading(currentStatus.co2, incomingStatus.co2);
incomingStatus.no2 = pickNewestReading(currentStatus.no2, incomingStatus.no2);
incomingStatus.o2 = pickNewestReading(currentStatus.o2, incomingStatus.o2);
incomingStatus.lel = pickNewestReading(currentStatus.lel, incomingStatus.lel);
incomingStatus.h2s = pickNewestReading(currentStatus.h2s, incomingStatus.h2s);
const currentLastActive = getTimestampMillis(currentStatus.lastActive);
const incomingLastActive = getTimestampMillis(incomingStatus.lastActive);
if (
currentLastActive !== undefined &&
incomingLastActive !== undefined &&
incomingLastActive < currentLastActive
) {
incomingStatus.lastActive = currentStatus.lastActive;
}
updated.status = incomingStatus;
return updated;
});
},
[]
);
useDeviceStatusStreams({
deviceIds: liveStatusDeviceIds,
onStatusUpdate: handleLiveDeviceStatus,
token,
keys: liveContextKeys,
types: liveContextTypes,
as: liveAs,
enabled: Boolean(deviceID && token),
});
const loadDevice = () => { const loadDevice = () => {
if (loading) return if (loading) return
if (state?.devicePageData) { if (state?.devicePageData) {
@ -173,7 +250,7 @@ export default function DevicePage() {
// When the backend receives a new reading and updates a component, // When the backend receives a new reading and updates a component,
// this fires and updates the component in state without a page refresh. // this fires and updates the component in state without a page refresh.
useWebSocket<{ key: string; component: Component } | null>({ useWebSocket<{ key: string; component: Component } | null>({
path: `/live/devices/${deviceID}/components`, path: `/v1/live/devices/${deviceID}/components`,
parse: (e) => { parse: (e) => {
try { try {
const raw = JSON.parse(e.data); const raw = JSON.parse(e.data);
@ -197,32 +274,9 @@ export default function DevicePage() {
return updated; return updated;
}); });
}, },
token, keys: liveContextKeys,
enabled: !!deviceID, types: liveContextTypes,
}); as: liveAs,
// --- Real-time device updates (optional) ---
// Updates device-level info (name, status, lastActive, etc.)
useWebSocket<Device | null>({
path: `/live/devices/${deviceID}`,
parse: (e) => {
try {
const raw = JSON.parse(e.data);
const dev = Device.any(raw);
if (!dev?.settings) {
console.warn("[ws] received device without settings:", raw);
return null;
}
return dev;
} catch (err) {
console.warn("[ws] failed to parse device:", err);
return null;
}
},
onMessage: (updatedDevice) => {
if (!updatedDevice) return;
setDevice(updatedDevice);
},
token, token,
enabled: !!deviceID, enabled: !!deviceID,
}); });

View file

@ -264,10 +264,50 @@ export default function Devices() {
) )
}, []) }, [])
/** Same context chain as deviceAPI.list / GetContext on the backend (path + group tab + ?as=). */
const statusStreamKeys = useMemo(() => {
if (tab !== "all") {
const g = groups[parseInt(tab)]
if (g) {
const keys = [...getContextKeys()]
keys.push(g.id().toString())
return keys
}
}
return getContextKeys()
}, [tab, groups, location.pathname])
const statusStreamTypes = useMemo(() => {
if (tab !== "all") {
const g = groups[parseInt(tab)]
if (g) {
const types = [...getContextTypes()]
types.push("group")
return types
}
}
return getContextTypes()
}, [tab, groups, location.pathname])
/** Match Device page / getDevicePageData: omit ?as= when URL context already starts with team. */
const statusStreamAs = useMemo(() => {
const types =
tab !== "all" && groups[parseInt(tab)]
? [...getContextTypes(), "group"]
: getContextTypes()
if (as && !(types.length > 0 && types[0] === "team")) {
return as
}
return undefined
}, [as, tab, groups, location.pathname])
useDeviceStatusStreams({ useDeviceStatusStreams({
deviceIds, deviceIds,
onStatusUpdate: handleStatusUpdate, onStatusUpdate: handleStatusUpdate,
token, token,
keys: statusStreamKeys,
types: statusStreamTypes,
as: statusStreamAs,
enabled: devices.length > 0 && !!token, enabled: devices.length > 0 && !!token,
}) })

View file

@ -32,7 +32,6 @@ import TemperatureIcon from "component/TemperatureIcon";
import FuelIcon from "products/CommonIcons/fuelIcon"; import FuelIcon from "products/CommonIcons/fuelIcon";
import moment from "moment"; import moment from "moment";
import Warning from "@mui/icons-material/Warning"; import Warning from "@mui/icons-material/Warning";
import { getTemperatureUnit } from "utils";
import ResponsiveDialog from "common/ResponsiveDialog"; import ResponsiveDialog from "common/ResponsiveDialog";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
@ -286,7 +285,7 @@ export default function Heater() {
const tempUnit = () => { const tempUnit = () => {
let unit = "°C"; let unit = "°C";
if (getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) { if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
unit = "°F"; unit = "°F";
} }
return unit; return unit;

View file

@ -13,12 +13,13 @@ import {
yellow yellow
} from "@mui/material/colors"; } from "@mui/material/colors";
import { GraphType } from "common/Graph"; import { GraphType } from "common/Graph";
import { User } from "models";
import { IsCardController } from "products/DeviceProduct"; import { IsCardController } from "products/DeviceProduct";
import { pond } from "protobuf-ts/pond"; import { pond } from "protobuf-ts/pond";
import { quack } from "protobuf-ts/quack"; import { quack } from "protobuf-ts/quack";
import { getTextSecondary } from "theme/text"; import { getTextSecondary } from "theme/text";
// import { getTextSecondary } from "theme"; // import { getTextSecondary } from "theme";
import { getDistanceUnit, getPressureUnit, getTemperatureUnit, roundTo } from "utils"; import { roundTo } from "utils";
import { extract } from "utils/types"; import { extract } from "utils/types";
interface Enumeration { interface Enumeration {
@ -64,7 +65,8 @@ export class MeasurementDescriber {
measurementType: quack.MeasurementType, measurementType: quack.MeasurementType,
componentType: quack.ComponentType, componentType: quack.ComponentType,
componentSubtype: number, componentSubtype: number,
product?: pond.DeviceProduct product?: pond.DeviceProduct,
user?: User
) { ) {
this.measurementType = measurementType; this.measurementType = measurementType;
this.details = { this.details = {
@ -87,7 +89,7 @@ export class MeasurementDescriber {
case quack.MeasurementType.MEASUREMENT_TYPE_INVALID: case quack.MeasurementType.MEASUREMENT_TYPE_INVALID:
break; break;
case quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE: case quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE:
let temperatureUnit = getTemperatureUnit(); let temperatureUnit = user?.tempUnit() ?? pond.TemperatureUnit.TEMPERATURE_UNIT_CELSIUS;
let isFahrenheit = temperatureUnit === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT; let isFahrenheit = temperatureUnit === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT;
this.details.label = "Temperature"; this.details.label = "Temperature";
this.details.unit = isFahrenheit ? "°F" : "°C"; this.details.unit = isFahrenheit ? "°F" : "°C";
@ -233,7 +235,8 @@ export class MeasurementDescriber {
} }
break; break;
case quack.MeasurementType.MEASUREMENT_TYPE_PRESSURE: case quack.MeasurementType.MEASUREMENT_TYPE_PRESSURE:
let pressureUnit = getPressureUnit(); let pressureUnit =
user?.pressureUnit() ?? pond.PressureUnit.PRESSURE_UNIT_INCHES_OF_WATER;
let isIWG = pressureUnit === pond.PressureUnit.PRESSURE_UNIT_INCHES_OF_WATER; let isIWG = pressureUnit === pond.PressureUnit.PRESSURE_UNIT_INCHES_OF_WATER;
this.details.label = "Pressure"; this.details.label = "Pressure";
this.details.unit = isIWG ? "iwg" : "kPa"; this.details.unit = isIWG ? "iwg" : "kPa";
@ -437,7 +440,8 @@ export class MeasurementDescriber {
this.details.max = 100000; this.details.max = 100000;
break; break;
case quack.MeasurementType.MEASUREMENT_TYPE_DISTANCE_CM: case quack.MeasurementType.MEASUREMENT_TYPE_DISTANCE_CM:
let distanceUnit = getDistanceUnit(); let distanceUnit =
user?.distanceUnit() ?? pond.DistanceUnit.DISTANCE_UNIT_FEET;
this.details.label = "Distance"; this.details.label = "Distance";
this.details.unit = "cm"; this.details.unit = "cm";
if (distanceUnit === pond.DistanceUnit.DISTANCE_UNIT_FEET) { if (distanceUnit === pond.DistanceUnit.DISTANCE_UNIT_FEET) {
@ -451,7 +455,8 @@ export class MeasurementDescriber {
this.details.max = 100000; this.details.max = 100000;
break; break;
case quack.MeasurementType.MEASUREMENT_TYPE_SPEED: case quack.MeasurementType.MEASUREMENT_TYPE_SPEED:
let speedUnit = getDistanceUnit() let speedUnit =
user?.distanceUnit() ?? pond.DistanceUnit.DISTANCE_UNIT_FEET;
this.details.label = "Speed"; this.details.label = "Speed";
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.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"];

View file

@ -2,8 +2,8 @@ import { Box, Typography } from "@mui/material";
import GrainDescriber from "grain/GrainDescriber"; import GrainDescriber from "grain/GrainDescriber";
import { Transaction } from "models/Transaction"; import { Transaction } from "models/Transaction";
import { pond } from "protobuf-ts/pond"; import { pond } from "protobuf-ts/pond";
import { useGlobalState } from "providers";
import React from "react"; import React from "react";
import { getGrainUnit } from "utils";
interface Props { interface Props {
transaction: Transaction; transaction: Transaction;
@ -11,13 +11,14 @@ interface Props {
export default function TransactionDataDisplay(props: Props) { export default function TransactionDataDisplay(props: Props) {
const { transaction } = props; const { transaction } = props;
const [{ user }] = useGlobalState();
console.log(transaction) console.log(transaction)
const grainDisplay = (gt: pond.GrainTransaction) => { const grainDisplay = (gt: pond.GrainTransaction) => {
if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && gt.bushelsPerTonne > 1){ if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TONNE && gt.bushelsPerTonne > 1){
return "Weight: " + Math.round(gt.bushels / gt.bushelsPerTonne*100)/100 + " mT" return "Weight: " + Math.round(gt.bushels / gt.bushelsPerTonne*100)/100 + " mT"
} }
if(getGrainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && gt.bushelsPerTonne > 1){ if(user.grainUnit() === pond.GrainUnit.GRAIN_UNIT_TON && gt.bushelsPerTonne > 1){
return "Weight: " + Math.round(gt.bushels / (gt.bushelsPerTonne*0.907)*100)/100 + " t" return "Weight: " + Math.round(gt.bushels / (gt.bushelsPerTonne*0.907)*100)/100 + " t"
} }
return "Bushels: " + gt.bushels return "Bushels: " + gt.bushels

16
src/utils/getWsBaseUrl.ts Normal file
View file

@ -0,0 +1,16 @@
/**
* Origin for Pond WebSocket URLs.
*
* VITE_APP_API_URL usually ends with /v1 (same base axios uses for REST). WS paths are
* written as /v1/live/..., so we strip a trailing /v1 from the env URL to avoid /v1/v1/.
*/
export function getWsBaseUrl(): string {
const apiUrl = import.meta.env.VITE_APP_API_URL;
if (apiUrl) {
let ws = apiUrl.replace(/^http/, "ws");
ws = ws.replace(/\/v1\/?$/, "");
return ws.replace(/\/$/, "") || ws;
}
const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
return `${protocol}//${window.location.host}`;
}

View file

@ -1,5 +1,6 @@
import { pond } from "protobuf-ts/pond"; import { pond } from "protobuf-ts/pond";
//function is deprecated, use User.tempUnit() instead
export function getTemperatureUnit(): pond.TemperatureUnit { export function getTemperatureUnit(): pond.TemperatureUnit {
return localStorage.getItem("temperature") === "f" return localStorage.getItem("temperature") === "f"
? pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT ? pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT
@ -31,6 +32,7 @@ export const fahrenheitToCelsius = (fahrenheit: number) => {
return (fahrenheit - 32) * (5 / 9); return (fahrenheit - 32) * (5 / 9);
}; };
// function is deprecated, use User.pressureUnit() instead
export function getPressureUnit(): pond.PressureUnit { export function getPressureUnit(): pond.PressureUnit {
return localStorage.getItem("pressure") === "kpa" return localStorage.getItem("pressure") === "kpa"
? pond.PressureUnit.PRESSURE_UNIT_KILOPASCALS ? pond.PressureUnit.PRESSURE_UNIT_KILOPASCALS
@ -44,6 +46,7 @@ export function setPressureUnit(unit: pond.PressureUnit) {
); );
} }
// function is deprecated, use User.distanceUnit() instead
export function getDistanceUnit(): pond.DistanceUnit { export function getDistanceUnit(): pond.DistanceUnit {
return localStorage.getItem("distance") === "m" return localStorage.getItem("distance") === "m"
? pond.DistanceUnit.DISTANCE_UNIT_METERS ? pond.DistanceUnit.DISTANCE_UNIT_METERS
@ -54,6 +57,7 @@ export function setDistanceUnit(unit: pond.DistanceUnit) {
localStorage.setItem("distance", unit === pond.DistanceUnit.DISTANCE_UNIT_METERS ? "m" : "ft"); localStorage.setItem("distance", unit === pond.DistanceUnit.DISTANCE_UNIT_METERS ? "m" : "ft");
} }
// function is deprecated, use User.grainUnit() instead
export function getGrainUnit(): pond.GrainUnit { export function getGrainUnit(): pond.GrainUnit {
switch(localStorage.getItem("grainUnit")){ switch(localStorage.getItem("grainUnit")){
case "mT": case "mT":
@ -81,13 +85,11 @@ export function setGrainUnit(unit: pond.GrainUnit) {
default: default:
localStorage.setItem("grainUnit", "bu") localStorage.setItem("grainUnit", "bu")
} }
// localStorage.setItem("grainUnit", unit === pond.GrainUnit.GRAIN_UNIT_WEIGHT ? "mT" : "bu");
} }
export const distanceConversion = (val: number) => { export const distanceConversion = (val: number, distanceUnit: pond.DistanceUnit) => {
let converted = val; let converted = val;
if (distanceUnit === pond.DistanceUnit.DISTANCE_UNIT_METERS) {
if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) {
converted = val / 3.281; converted = val / 3.281;
} }
return converted; return converted;