merged timestamped status stuff
This commit is contained in:
commit
92d4892c65
39 changed files with 1753 additions and 422 deletions
256
package-lock.json
generated
256
package-lock.json
generated
|
|
@ -42,7 +42,7 @@
|
||||||
"mui-tel-input": "^7.0.0",
|
"mui-tel-input": "^7.0.0",
|
||||||
"notistack": "^3.0.1",
|
"notistack": "^3.0.1",
|
||||||
"openweathermap-ts": "^1.2.10",
|
"openweathermap-ts": "^1.2.10",
|
||||||
"protobuf-ts": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#dev",
|
"protobuf-ts": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#staging",
|
||||||
"query-string": "^9.2.1",
|
"query-string": "^9.2.1",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-beautiful-dnd": "^13.1.1",
|
"react-beautiful-dnd": "^13.1.1",
|
||||||
|
|
@ -2208,16 +2208,20 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@eslint-community/eslint-utils": {
|
"node_modules/@eslint-community/eslint-utils": {
|
||||||
"version": "4.4.0",
|
"version": "4.9.0",
|
||||||
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz",
|
||||||
"integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==",
|
"integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"eslint-visitor-keys": "^3.3.0"
|
"eslint-visitor-keys": "^3.4.3"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
||||||
},
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/eslint"
|
||||||
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
|
"eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
|
||||||
}
|
}
|
||||||
|
|
@ -2235,21 +2239,23 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@eslint-community/regexpp": {
|
"node_modules/@eslint-community/regexpp": {
|
||||||
"version": "4.11.1",
|
"version": "4.12.1",
|
||||||
"resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.1.tgz",
|
"resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz",
|
||||||
"integrity": "sha512-m4DVN9ZqskZoLU5GlWZadwDnYo3vAEydiUayB9widCl9ffWx2IvPnp6n3on5rJmziJSw9Bv+Z3ChDVdMwXCY8Q==",
|
"integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^12.0.0 || ^14.0.0 || >=16.0.0"
|
"node": "^12.0.0 || ^14.0.0 || >=16.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@eslint/config-array": {
|
"node_modules/@eslint/config-array": {
|
||||||
"version": "0.18.0",
|
"version": "0.21.0",
|
||||||
"resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.18.0.tgz",
|
"resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz",
|
||||||
"integrity": "sha512-fTxvnS1sRMu3+JjXwJG0j/i4RT9u4qJ+lqS/yCGap4lH4zZGzQ7tu+xZqQmcMZq5OBZDL4QRxQzRjkWcGt8IVw==",
|
"integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint/object-schema": "^2.1.4",
|
"@eslint/object-schema": "^2.1.6",
|
||||||
"debug": "^4.3.1",
|
"debug": "^4.3.1",
|
||||||
"minimatch": "^3.1.2"
|
"minimatch": "^3.1.2"
|
||||||
},
|
},
|
||||||
|
|
@ -2257,20 +2263,38 @@
|
||||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@eslint/core": {
|
"node_modules/@eslint/config-helpers": {
|
||||||
"version": "0.7.0",
|
"version": "0.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.0.tgz",
|
||||||
"integrity": "sha512-xp5Jirz5DyPYlPiKat8jaq0EmYvDXKKpzTbxXMpT9eqlRJkRKIz9AGMdlvYjih+im+QlhWrpvVjl8IPC/lHlUw==",
|
"integrity": "sha512-WUFvV4WoIwW8Bv0KeKCIIEgdSiFOsulyN0xrMu+7z43q/hkOLXjvb5u7UC9jDxvRzcrbEmuZBX5yJZz1741jog==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@eslint/core": "^0.16.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@eslint/core": {
|
||||||
|
"version": "0.16.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.16.0.tgz",
|
||||||
|
"integrity": "sha512-nmC8/totwobIiFcGkDza3GIKfAw1+hLiYVrh3I1nIomQ8PEr5cxg34jnkmGawul/ep52wGRAcyeDCNtWKSOj4Q==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/json-schema": "^7.0.15"
|
||||||
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@eslint/eslintrc": {
|
"node_modules/@eslint/eslintrc": {
|
||||||
"version": "3.1.0",
|
"version": "3.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz",
|
||||||
"integrity": "sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==",
|
"integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ajv": "^6.12.4",
|
"ajv": "^6.12.4",
|
||||||
"debug": "^4.3.2",
|
"debug": "^4.3.2",
|
||||||
|
|
@ -2294,6 +2318,7 @@
|
||||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
||||||
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fast-deep-equal": "^3.1.1",
|
"fast-deep-equal": "^3.1.1",
|
||||||
"fast-json-stable-stringify": "^2.0.0",
|
"fast-json-stable-stringify": "^2.0.0",
|
||||||
|
|
@ -2310,6 +2335,7 @@
|
||||||
"resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
|
||||||
"integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
|
"integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
},
|
},
|
||||||
|
|
@ -2321,15 +2347,20 @@
|
||||||
"version": "0.4.1",
|
"version": "0.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
||||||
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
|
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@eslint/js": {
|
"node_modules/@eslint/js": {
|
||||||
"version": "9.13.0",
|
"version": "9.37.0",
|
||||||
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.13.0.tgz",
|
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.37.0.tgz",
|
||||||
"integrity": "sha512-IFLyoY4d72Z5y/6o/BazFBezupzI/taV8sGumxTAVw3lXG9A6md1Dc34T9s1FoD/an9pJH8RHbAxsaEbBed9lA==",
|
"integrity": "sha512-jaS+NJ+hximswBG6pjNX0uEJZkrT0zwpVi3BA3vX22aFGjJjmgSTSmPpZCRKmoBL5VY/M6p0xsSJx7rk7sy5gg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://eslint.org/donate"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@eslint/object-schema": {
|
"node_modules/@eslint/object-schema": {
|
||||||
|
|
@ -2337,16 +2368,19 @@
|
||||||
"resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz",
|
"resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz",
|
||||||
"integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==",
|
"integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@eslint/plugin-kit": {
|
"node_modules/@eslint/plugin-kit": {
|
||||||
"version": "0.2.3",
|
"version": "0.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.0.tgz",
|
||||||
"integrity": "sha512-2b/g5hRmpbb1o4GnTZax9N9m0FXzz9OV42ZzI4rDDMDuHUqigAiQCEWChBWCY4ztAGVRjoWT19v0yMmc5/L5kA==",
|
"integrity": "sha512-sB5uyeq+dwCWyPi31B2gQlVlo+j5brPlWx4yZBrEaRo/nhdDE8Xke1gsGgtiBdaBTxuTkceLVuVt/pclrasb0A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@eslint/core": "^0.16.0",
|
||||||
"levn": "^0.4.1"
|
"levn": "^0.4.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
|
|
@ -2405,18 +2439,20 @@
|
||||||
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
|
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
|
||||||
"integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==",
|
"integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.18.0"
|
"node": ">=18.18.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@humanfs/node": {
|
"node_modules/@humanfs/node": {
|
||||||
"version": "0.16.5",
|
"version": "0.16.7",
|
||||||
"resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.5.tgz",
|
"resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz",
|
||||||
"integrity": "sha512-KSPA4umqSG4LHYRodq31VDwKAvaTF4xmVlzM8Aeh4PlU1JQ3IG0wiA8C25d3RQ9nJyM3mBHyI53K06VVL/oFFg==",
|
"integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@humanfs/core": "^0.19.0",
|
"@humanfs/core": "^0.19.1",
|
||||||
"@humanwhocodes/retry": "^0.3.0"
|
"@humanwhocodes/retry": "^0.4.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.18.0"
|
"node": ">=18.18.0"
|
||||||
|
|
@ -2436,10 +2472,11 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@humanwhocodes/retry": {
|
"node_modules/@humanwhocodes/retry": {
|
||||||
"version": "0.3.1",
|
"version": "0.4.3",
|
||||||
"resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz",
|
||||||
"integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==",
|
"integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.18"
|
"node": ">=18.18"
|
||||||
},
|
},
|
||||||
|
|
@ -2622,20 +2659,6 @@
|
||||||
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz",
|
||||||
"integrity": "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q=="
|
"integrity": "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q=="
|
||||||
},
|
},
|
||||||
"node_modules/@mapbox/mapbox-sdk/node_modules/form-data": {
|
|
||||||
"version": "3.0.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.3.tgz",
|
|
||||||
"integrity": "sha512-q5YBMeWy6E2Un0nMGWMgI65MAKtaylxfNJGJxpGh45YDciZB4epbWpaAfImil6CPAPTYB4sh0URQNDRIZG5F2w==",
|
|
||||||
"dependencies": {
|
|
||||||
"asynckit": "^0.4.0",
|
|
||||||
"combined-stream": "^1.0.8",
|
|
||||||
"es-set-tostringtag": "^2.1.0",
|
|
||||||
"mime-types": "^2.1.35"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@mapbox/parse-mapbox-token": {
|
"node_modules/@mapbox/parse-mapbox-token": {
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@mapbox/parse-mapbox-token/-/parse-mapbox-token-0.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/@mapbox/parse-mapbox-token/-/parse-mapbox-token-0.2.0.tgz",
|
||||||
|
|
@ -6614,10 +6637,11 @@
|
||||||
"integrity": "sha512-d8XPSGjfyzlXC3Xx891DJRyZfqk5JU0BJrDQcsWomFIV1/BIzPW5HDH5iDdWpqWaav0YVIEzT1RHTwWr0FFshA=="
|
"integrity": "sha512-d8XPSGjfyzlXC3Xx891DJRyZfqk5JU0BJrDQcsWomFIV1/BIzPW5HDH5iDdWpqWaav0YVIEzT1RHTwWr0FFshA=="
|
||||||
},
|
},
|
||||||
"node_modules/acorn": {
|
"node_modules/acorn": {
|
||||||
"version": "8.14.0",
|
"version": "8.15.0",
|
||||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
|
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
|
||||||
"integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==",
|
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"bin": {
|
"bin": {
|
||||||
"acorn": "bin/acorn"
|
"acorn": "bin/acorn"
|
||||||
},
|
},
|
||||||
|
|
@ -6630,6 +6654,7 @@
|
||||||
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
|
||||||
"integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
|
"integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
|
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
|
||||||
}
|
}
|
||||||
|
|
@ -6669,7 +6694,8 @@
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
||||||
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
|
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"license": "Python-2.0"
|
||||||
},
|
},
|
||||||
"node_modules/arr-union": {
|
"node_modules/arr-union": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
|
|
@ -6786,14 +6812,32 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/axios": {
|
"node_modules/axios": {
|
||||||
"version": "1.8.2",
|
"version": "1.12.2",
|
||||||
"integrity": "sha512-ls4GYBm5aig9vWx8AWDSGLpnpDQRtWAfrjU+EuytuODrFBkqesN2RkOQCBzrA1RQNHw1SmRMSDDDSwzNAYQ6Rg==",
|
"resolved": "https://registry.npmjs.org/axios/-/axios-1.12.2.tgz",
|
||||||
|
"integrity": "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"follow-redirects": "^1.15.6",
|
"follow-redirects": "^1.15.6",
|
||||||
"form-data": "^4.0.0",
|
"form-data": "^4.0.4",
|
||||||
"proxy-from-env": "^1.1.0"
|
"proxy-from-env": "^1.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/axios/node_modules/form-data": {
|
||||||
|
"version": "4.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
|
||||||
|
"integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"asynckit": "^0.4.0",
|
||||||
|
"combined-stream": "^1.0.8",
|
||||||
|
"es-set-tostringtag": "^2.1.0",
|
||||||
|
"hasown": "^2.0.2",
|
||||||
|
"mime-types": "^2.1.12"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/babel-plugin-macros": {
|
"node_modules/babel-plugin-macros": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz",
|
||||||
|
|
@ -8029,30 +8073,33 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/eslint": {
|
"node_modules/eslint": {
|
||||||
"version": "9.13.0",
|
"version": "9.37.0",
|
||||||
"integrity": "sha512-EYZK6SX6zjFHST/HRytOdA/zE72Cq/bfw45LSyuwrdvcclb/gqV8RRQxywOBEWO2+WDpva6UZa4CcDeJKzUCFA==",
|
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.37.0.tgz",
|
||||||
|
"integrity": "sha512-XyLmROnACWqSxiGYArdef1fItQd47weqB7iwtfr9JHwRrqIXZdcFMvvEcL9xHCmL0SNsOvF0c42lWyM1U5dgig==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/eslint-utils": "^4.2.0",
|
"@eslint-community/eslint-utils": "^4.8.0",
|
||||||
"@eslint-community/regexpp": "^4.11.0",
|
"@eslint-community/regexpp": "^4.12.1",
|
||||||
"@eslint/config-array": "^0.18.0",
|
"@eslint/config-array": "^0.21.0",
|
||||||
"@eslint/core": "^0.7.0",
|
"@eslint/config-helpers": "^0.4.0",
|
||||||
"@eslint/eslintrc": "^3.1.0",
|
"@eslint/core": "^0.16.0",
|
||||||
"@eslint/js": "9.13.0",
|
"@eslint/eslintrc": "^3.3.1",
|
||||||
"@eslint/plugin-kit": "^0.2.0",
|
"@eslint/js": "9.37.0",
|
||||||
"@humanfs/node": "^0.16.5",
|
"@eslint/plugin-kit": "^0.4.0",
|
||||||
|
"@humanfs/node": "^0.16.6",
|
||||||
"@humanwhocodes/module-importer": "^1.0.1",
|
"@humanwhocodes/module-importer": "^1.0.1",
|
||||||
"@humanwhocodes/retry": "^0.3.1",
|
"@humanwhocodes/retry": "^0.4.2",
|
||||||
"@types/estree": "^1.0.6",
|
"@types/estree": "^1.0.6",
|
||||||
"@types/json-schema": "^7.0.15",
|
"@types/json-schema": "^7.0.15",
|
||||||
"ajv": "^6.12.4",
|
"ajv": "^6.12.4",
|
||||||
"chalk": "^4.0.0",
|
"chalk": "^4.0.0",
|
||||||
"cross-spawn": "^7.0.2",
|
"cross-spawn": "^7.0.6",
|
||||||
"debug": "^4.3.2",
|
"debug": "^4.3.2",
|
||||||
"escape-string-regexp": "^4.0.0",
|
"escape-string-regexp": "^4.0.0",
|
||||||
"eslint-scope": "^8.1.0",
|
"eslint-scope": "^8.4.0",
|
||||||
"eslint-visitor-keys": "^4.1.0",
|
"eslint-visitor-keys": "^4.2.1",
|
||||||
"espree": "^10.2.0",
|
"espree": "^10.4.0",
|
||||||
"esquery": "^1.5.0",
|
"esquery": "^1.5.0",
|
||||||
"esutils": "^2.0.2",
|
"esutils": "^2.0.2",
|
||||||
"fast-deep-equal": "^3.1.3",
|
"fast-deep-equal": "^3.1.3",
|
||||||
|
|
@ -8066,8 +8113,7 @@
|
||||||
"lodash.merge": "^4.6.2",
|
"lodash.merge": "^4.6.2",
|
||||||
"minimatch": "^3.1.2",
|
"minimatch": "^3.1.2",
|
||||||
"natural-compare": "^1.4.0",
|
"natural-compare": "^1.4.0",
|
||||||
"optionator": "^0.9.3",
|
"optionator": "^0.9.3"
|
||||||
"text-table": "^0.2.0"
|
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"eslint": "bin/eslint.js"
|
"eslint": "bin/eslint.js"
|
||||||
|
|
@ -8107,10 +8153,11 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/eslint-scope": {
|
"node_modules/eslint-scope": {
|
||||||
"version": "8.1.0",
|
"version": "8.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz",
|
||||||
"integrity": "sha512-14dSvlhaVhKKsa9Fx1l8A17s7ah7Ef7wCakJ10LYk6+GYmP9yDti2oq2SEwcyndt6knfcZyhyxwY3i9yL78EQw==",
|
"integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esrecurse": "^4.3.0",
|
"esrecurse": "^4.3.0",
|
||||||
"estraverse": "^5.2.0"
|
"estraverse": "^5.2.0"
|
||||||
|
|
@ -8123,10 +8170,11 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/eslint-visitor-keys": {
|
"node_modules/eslint-visitor-keys": {
|
||||||
"version": "4.2.0",
|
"version": "4.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
|
||||||
"integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==",
|
"integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
},
|
},
|
||||||
|
|
@ -8150,18 +8198,6 @@
|
||||||
"url": "https://github.com/sponsors/epoberezkin"
|
"url": "https://github.com/sponsors/epoberezkin"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/eslint/node_modules/escape-string-regexp": {
|
|
||||||
"version": "4.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
|
|
||||||
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
|
|
||||||
"dev": true,
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/eslint/node_modules/json-schema-traverse": {
|
"node_modules/eslint/node_modules/json-schema-traverse": {
|
||||||
"version": "0.4.1",
|
"version": "0.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
||||||
|
|
@ -8169,14 +8205,15 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/espree": {
|
"node_modules/espree": {
|
||||||
"version": "10.3.0",
|
"version": "10.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",
|
||||||
"integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==",
|
"integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"acorn": "^8.14.0",
|
"acorn": "^8.15.0",
|
||||||
"acorn-jsx": "^5.3.2",
|
"acorn-jsx": "^5.3.2",
|
||||||
"eslint-visitor-keys": "^4.2.0"
|
"eslint-visitor-keys": "^4.2.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
|
|
@ -8202,6 +8239,7 @@
|
||||||
"resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
|
||||||
"integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
|
"integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"estraverse": "^5.2.0"
|
"estraverse": "^5.2.0"
|
||||||
},
|
},
|
||||||
|
|
@ -8519,13 +8557,16 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/form-data": {
|
"node_modules/form-data": {
|
||||||
"version": "4.0.1",
|
"version": "3.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.4.tgz",
|
||||||
"integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==",
|
"integrity": "sha512-f0cRzm6dkyVYV3nPoooP8XlccPQukegwhAnpoLcXy+X+A8KfpGOoXwDr9FLZd3wzgLaBGQBE3lY93Zm/i1JvIQ==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"asynckit": "^0.4.0",
|
"asynckit": "^0.4.0",
|
||||||
"combined-stream": "^1.0.8",
|
"combined-stream": "^1.0.8",
|
||||||
"mime-types": "^2.1.12"
|
"es-set-tostringtag": "^2.1.0",
|
||||||
|
"hasown": "^2.0.2",
|
||||||
|
"mime-types": "^2.1.35"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 6"
|
"node": ">= 6"
|
||||||
|
|
@ -9690,6 +9731,7 @@
|
||||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
|
||||||
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
|
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"argparse": "^2.0.1"
|
"argparse": "^2.0.1"
|
||||||
},
|
},
|
||||||
|
|
@ -10911,7 +10953,7 @@
|
||||||
},
|
},
|
||||||
"node_modules/protobuf-ts": {
|
"node_modules/protobuf-ts": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#27131a61e101761d17d533bcf3d68c68d53e5860",
|
"resolved": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#c65c13a8eb98331c6648e401219debf8c9adc657",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"protobufjs": "^6.8.8"
|
"protobufjs": "^6.8.8"
|
||||||
}
|
}
|
||||||
|
|
@ -12586,6 +12628,7 @@
|
||||||
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
|
||||||
"integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
|
"integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
},
|
},
|
||||||
|
|
@ -12701,12 +12744,6 @@
|
||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/text-table": {
|
|
||||||
"version": "0.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
|
|
||||||
"integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"node_modules/tiny-inflate": {
|
"node_modules/tiny-inflate": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz",
|
||||||
|
|
@ -13720,10 +13757,11 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite": {
|
"node_modules/vite": {
|
||||||
"version": "6.3.5",
|
"version": "6.3.6",
|
||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-6.3.6.tgz",
|
||||||
"integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==",
|
"integrity": "sha512-0msEVHJEScQbhkbVTb/4iHZdJ6SXp/AvxL2sjwYQFfBqleHtnCqv1J3sa9zbWz/6kW1m9Tfzn92vW+kZ1WV6QA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esbuild": "^0.25.0",
|
"esbuild": "^0.25.0",
|
||||||
"fdir": "^6.4.4",
|
"fdir": "^6.4.4",
|
||||||
|
|
|
||||||
|
|
@ -249,9 +249,9 @@ export default function BinCard(props: Props) {
|
||||||
noWrap
|
noWrap
|
||||||
style={{ fontSize: "0.85rem", fontWeight: 650, color: tempColour }}>
|
style={{ fontSize: "0.85rem", fontWeight: 650, color: tempColour }}>
|
||||||
{display +
|
{display +
|
||||||
(user.settings.temperatureUnit === pond.TemperatureUnit.TEMPERATURE_UNIT_CELSIUS
|
(user.settings.temperatureUnit === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT
|
||||||
? "°C"
|
? "°F"
|
||||||
: "°F")}
|
: "°C")}
|
||||||
</Typography>
|
</Typography>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -124,7 +124,7 @@ export default function BinsList(props: Props) {
|
||||||
xs: 6,
|
xs: 6,
|
||||||
sm: 4,
|
sm: 4,
|
||||||
md: 3,
|
md: 3,
|
||||||
lg: 2,
|
lg: 1.5,
|
||||||
}}
|
}}
|
||||||
key={i}
|
key={i}
|
||||||
className={classes.gridListTile}
|
className={classes.gridListTile}
|
||||||
|
|
|
||||||
|
|
@ -92,7 +92,7 @@ export default function DisplayDrawer(props: Props) {
|
||||||
classes={{ paper: classes.drawerPaper }}>
|
classes={{ paper: classes.drawerPaper }}>
|
||||||
<Box
|
<Box
|
||||||
className={isMobile ? classes.drawerMobile : classes.drawerDesktop}
|
className={isMobile ? classes.drawerMobile : classes.drawerDesktop}
|
||||||
style={{ width: isMobile ? "100%" : width }}>
|
style={{ width: isMobile ? "100%" : width, height: isMobile ? drawerHeight + "vh" : 0}}>
|
||||||
<Box className={classes.header} style={{ width: isMobile ? "100%" : width }}>
|
<Box className={classes.header} style={{ width: isMobile ? "100%" : width }}>
|
||||||
<Box display="flex" justifyContent="space-between" alignItems="center">
|
<Box display="flex" justifyContent="space-between" alignItems="center">
|
||||||
<Box>
|
<Box>
|
||||||
|
|
|
||||||
|
|
@ -114,6 +114,29 @@ export default function StatusDust(props: Props) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function compareTimestamps(timestamp1: string, timestamp2: string): number {
|
||||||
|
const date1 = new Date(timestamp1);
|
||||||
|
const date2 = new Date(timestamp2);
|
||||||
|
|
||||||
|
// Check if dates are valid
|
||||||
|
if (isNaN(date1.getTime()) || isNaN(date2.getTime())) {
|
||||||
|
throw new Error("Invalid RFC3339 timestamp format");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare using getTime() for millisecond precision
|
||||||
|
return date1.getTime() - date2.getTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (device.status.sen5x?.timestamp) {
|
||||||
|
const now = new Date();
|
||||||
|
const oneDayAgo = new Date(now.getTime() - 24 * 60 * 60 * 1000);
|
||||||
|
if (compareTimestamps(device.status.sen5x?.timestamp, oneDayAgo.toISOString()) < 0) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip title={tooltip()}>
|
<Tooltip title={tooltip()}>
|
||||||
<PulseBox color={color} className={classes.box} >
|
<PulseBox color={color} className={classes.box} >
|
||||||
|
|
|
||||||
|
|
@ -152,9 +152,31 @@ export default function StatusGas(props: Props) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function compareTimestamps(timestamp1: string, timestamp2: string): number {
|
||||||
|
const date1 = new Date(timestamp1);
|
||||||
|
const date2 = new Date(timestamp2);
|
||||||
|
|
||||||
|
// Check if dates are valid
|
||||||
|
if (isNaN(date1.getTime()) || isNaN(date2.getTime())) {
|
||||||
|
throw new Error("Invalid RFC3339 timestamp format");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare using getTime() for millisecond precision
|
||||||
|
return date1.getTime() - date2.getTime();
|
||||||
|
}
|
||||||
|
|
||||||
const gasDisplay = () => {
|
const gasDisplay = () => {
|
||||||
const gas = gasType === "all" ? gasTypes[currentIndex] : gasType
|
const gas = gasType === "all" ? gasTypes[currentIndex] : gasType
|
||||||
// if (gasType !== "all") {
|
// if (gasType !== "all") {
|
||||||
|
if (device.status[gas]?.timestamp) {
|
||||||
|
const now = new Date();
|
||||||
|
const oneDayAgo = new Date(now.getTime() - 24 * 60 * 60 * 1000);
|
||||||
|
if (compareTimestamps(device.status[gas]?.timestamp, oneDayAgo.toISOString()) < 0) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return null
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<Tooltip title={tooltip()}>
|
<Tooltip title={tooltip()}>
|
||||||
<PulseBox color={color} className={gasType === "all" ? classes.boxTall : classes.box}>
|
<PulseBox color={color} className={gasType === "all" ? classes.boxTall : classes.box}>
|
||||||
|
|
|
||||||
|
|
@ -80,6 +80,29 @@ export default function StatusPlenum(props: Props) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function compareTimestamps(timestamp1: string, timestamp2: string): number {
|
||||||
|
const date1 = new Date(timestamp1);
|
||||||
|
const date2 = new Date(timestamp2);
|
||||||
|
|
||||||
|
// Check if dates are valid
|
||||||
|
if (isNaN(date1.getTime()) || isNaN(date2.getTime())) {
|
||||||
|
throw new Error("Invalid RFC3339 timestamp format");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare using getTime() for millisecond precision
|
||||||
|
return date1.getTime() - date2.getTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (device.status.plenum?.timestamp) {
|
||||||
|
const now = new Date();
|
||||||
|
const oneDayAgo = new Date(now.getTime() - 24 * 60 * 60 * 1000);
|
||||||
|
if (compareTimestamps(device.status.plenum?.timestamp, oneDayAgo.toISOString()) < 0) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip title={tooltip()}>
|
<Tooltip title={tooltip()}>
|
||||||
<PulseBox color={color} className={classes.box} >
|
<PulseBox color={color} className={classes.box} >
|
||||||
|
|
|
||||||
|
|
@ -120,6 +120,29 @@ export default function StatusSen5x(props: Props) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function compareTimestamps(timestamp1: string, timestamp2: string): number {
|
||||||
|
const date1 = new Date(timestamp1);
|
||||||
|
const date2 = new Date(timestamp2);
|
||||||
|
|
||||||
|
// Check if dates are valid
|
||||||
|
if (isNaN(date1.getTime()) || isNaN(date2.getTime())) {
|
||||||
|
throw new Error("Invalid RFC3339 timestamp format");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare using getTime() for millisecond precision
|
||||||
|
return date1.getTime() - date2.getTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (device.status.sen5x?.timestamp) {
|
||||||
|
const now = new Date();
|
||||||
|
const oneDayAgo = new Date(now.getTime() - 24 * 60 * 60 * 1000);
|
||||||
|
if (compareTimestamps(device.status.sen5x?.timestamp, oneDayAgo.toISOString()) < 0) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip title={tooltip()}>
|
<Tooltip title={tooltip()}>
|
||||||
<PulseBox color={color} className={classes.box} >
|
<PulseBox color={color} className={classes.box} >
|
||||||
|
|
|
||||||
|
|
@ -285,7 +285,6 @@ export default function ComponentCard(props: Props) {
|
||||||
// ) : (
|
// ) : (
|
||||||
<UnitMeasurementSummary
|
<UnitMeasurementSummary
|
||||||
component={component}
|
component={component}
|
||||||
excludedNodes={component.settings.excludedNodes}
|
|
||||||
reading={UnitMeasurement.convertLastMeasurement(measurements)}
|
reading={UnitMeasurement.convertLastMeasurement(measurements)}
|
||||||
//dense
|
//dense
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -449,7 +449,7 @@ export default function ComponentForm(props: Props) {
|
||||||
let minimum = min ?? 0;
|
let minimum = min ?? 0;
|
||||||
let maximum = max ?? 100;
|
let maximum = max ?? 100;
|
||||||
if (compMode && form.component.settings.calibrate) {
|
if (compMode && form.component.settings.calibrate) {
|
||||||
return Number(form.offset) <= maximum && Number(form.offset) > minimum;
|
return Number(form.offset) <= maximum && Number(form.offset) >= minimum;
|
||||||
}
|
}
|
||||||
return !isNaN(Number(form.offset));
|
return !isNaN(Number(form.offset));
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -270,7 +270,7 @@ export default function UpgradeDevice(props: Props) {
|
||||||
<Divider />
|
<Divider />
|
||||||
<DialogContent className={classes.dialogContent}>
|
<DialogContent className={classes.dialogContent}>
|
||||||
<DialogContentText id="alert-dialog-slide-description">
|
<DialogContentText id="alert-dialog-slide-description">
|
||||||
Upgrading from {currentFirmware()} to {latestFirmware()}
|
Upgrading {device.platformName()} from {currentFirmware()} to {latestFirmware()}
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
It is recommended that the device be plugged in during the upgrade as it will take
|
It is recommended that the device be plugged in during the upgrade as it will take
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,13 @@
|
||||||
import {
|
import {
|
||||||
|
Accordion,
|
||||||
|
AccordionDetails,
|
||||||
|
AccordionSummary,
|
||||||
Box,
|
Box,
|
||||||
Button,
|
Button,
|
||||||
|
Grid2,
|
||||||
|
IconButton,
|
||||||
Paper,
|
Paper,
|
||||||
|
Stack,
|
||||||
Tab,
|
Tab,
|
||||||
Table,
|
Table,
|
||||||
TableBody,
|
TableBody,
|
||||||
|
|
@ -9,16 +15,26 @@ import {
|
||||||
TableContainer,
|
TableContainer,
|
||||||
TableHead,
|
TableHead,
|
||||||
TableRow,
|
TableRow,
|
||||||
Tabs
|
Tabs,
|
||||||
|
Typography
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import React, { useCallback, useEffect, useState } from "react";
|
import React, { useCallback, useEffect, useState } from "react";
|
||||||
import { useMobile, useSnackbar } from "hooks";
|
import { useMobile, useSnackbar, useUserAPI } from "hooks";
|
||||||
import { useGlobalState, useFieldAPI } from "providers";
|
import { useGlobalState, useFieldAPI, useJohnDeereProxyAPI, useCNHiProxyAPI } from "providers";
|
||||||
//import HarvestTable from "harvestPlan/HarvestTable";
|
//import HarvestTable from "harvestPlan/HarvestTable";
|
||||||
import { Field } from "models";
|
import { Field, fieldScope, teamScope } from "models";
|
||||||
import GrainDescriber from "grain/GrainDescriber";
|
import GrainDescriber from "grain/GrainDescriber";
|
||||||
import { pond } from "protobuf-ts/pond";
|
import { pond } from "protobuf-ts/pond";
|
||||||
import HarvestSettings from "harvestPlan/HarvestSettings";
|
import HarvestSettings from "harvestPlan/HarvestSettings";
|
||||||
|
import ResponsiveTable, { Column } from "common/ResponsiveTable";
|
||||||
|
import FieldMinimap from "./Fieldminimap";
|
||||||
|
import FieldActions from "./FieldActions";
|
||||||
|
import FieldSettings from "./FieldSettings";
|
||||||
|
import { ArrowForward, ExpandMore, Settings } from "@mui/icons-material";
|
||||||
|
import { cloneDeep } from "lodash";
|
||||||
|
import ShareAllFields from "./ShareAllFields";
|
||||||
|
import EventBlocker from "common/EventBlocker";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
interface TabPanelProps {
|
interface TabPanelProps {
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
|
|
@ -26,128 +42,244 @@ interface TabPanelProps {
|
||||||
value: any;
|
value: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
function TabPanelMine(props: TabPanelProps) {
|
|
||||||
const { children, value, index, ...other } = props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
role="tabpanel"
|
|
||||||
hidden={value !== index}
|
|
||||||
aria-labelledby={`simple-tab-${index}`}
|
|
||||||
style={{ height: "94%" }}
|
|
||||||
{...other}>
|
|
||||||
{value === index && <React.Fragment>{children}</React.Fragment>}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function FieldList() {
|
export default function FieldList() {
|
||||||
const [{ as }] = useGlobalState();
|
const [{ as }] = useGlobalState();
|
||||||
const isMobile = useMobile();
|
const isMobile = useMobile();
|
||||||
const fieldAPI = useFieldAPI();
|
const fieldAPI = useFieldAPI();
|
||||||
|
const jdAPI = useJohnDeereProxyAPI();
|
||||||
|
const cnhAPI = useCNHiProxyAPI();
|
||||||
const { openSnack } = useSnackbar();
|
const { openSnack } = useSnackbar();
|
||||||
const [fields, setFields] = useState<Field[]>([]);
|
const [tabValue, setTabValue] = React.useState(0);
|
||||||
const [value, setValue] = React.useState(0);
|
|
||||||
const [openHarvestSettings, setOpenHarvestSettings] = useState(false);
|
const [openHarvestSettings, setOpenHarvestSettings] = useState(false);
|
||||||
const [fieldForPlan, setFieldForPlan] = useState(Field.create());
|
// const [fieldForPlan, setFieldForPlan] = useState(Field.create());
|
||||||
|
const [fields, setFields] = useState<Field[]>([]);
|
||||||
|
const [total, setTotal] = useState(0)
|
||||||
|
const [rowsPerPage, setRowsPerPage] = useState(5)
|
||||||
|
const [page, setPage] = useState(0)
|
||||||
|
const [selectedField, setSelectedField] = useState<Field>()
|
||||||
|
const [openFieldSettings, setOpenFieldSettings] = useState(false)
|
||||||
|
const [shareOpen, setShareOpen] = useState(false)
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const handleChange = (event: React.ChangeEvent<{}>, newValue: number) => {
|
const handleChange = (event: React.ChangeEvent<{}>, newValue: number) => {
|
||||||
setValue(newValue);
|
setTabValue(newValue);
|
||||||
|
setPage(0)
|
||||||
};
|
};
|
||||||
|
|
||||||
const loadFields = useCallback(() => {
|
const loadAdaptiveFields = useCallback(() => {
|
||||||
fieldAPI
|
fieldAPI
|
||||||
.listFields(500, 0, "asc", "fieldName", undefined, as)
|
.listFields(rowsPerPage, page*rowsPerPage, "asc", "fieldName", undefined, as)
|
||||||
.then(resp => {
|
.then(resp => {
|
||||||
setFields(resp.data.fields.map(f => Field.any(f)));
|
setFields(resp.data.fields.map(f => Field.create(f)));
|
||||||
// resp.data.fields.forEach(f => {
|
setTotal(resp.data.total)
|
||||||
// if (f.settings) {
|
|
||||||
// fieldAPI.updateField(f.settings.key, f.settings);
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
openSnack("Failed to load Field Mapping");
|
openSnack("Failed to load Fields");
|
||||||
});
|
});
|
||||||
}, [fieldAPI, as, openSnack]);
|
}, [fieldAPI, as, openSnack, page, rowsPerPage]);
|
||||||
|
|
||||||
|
const loadJDFields = useCallback(() => {
|
||||||
|
jdAPI.listFields(rowsPerPage, page*rowsPerPage, as)
|
||||||
|
.then(resp => {
|
||||||
|
resp.data.fields ? setFields(resp.data.fields.map(f => Field.create(f))) : setFields([])
|
||||||
|
}).catch(err => {
|
||||||
|
openSnack("Failed to load John Deere Fields");
|
||||||
|
})
|
||||||
|
},[jdAPI, as, openSnack, page, rowsPerPage])
|
||||||
|
|
||||||
|
const loadCNHFields = useCallback(() => {
|
||||||
|
cnhAPI.listFields(rowsPerPage, page*rowsPerPage, as)
|
||||||
|
.then(resp => {
|
||||||
|
resp.data.fields ? setFields(resp.data.fields.map(f => Field.create(f))) : setFields([])
|
||||||
|
}).catch(err => {
|
||||||
|
openSnack("Failed to load John Deere Fields");
|
||||||
|
})
|
||||||
|
},[cnhAPI, as, openSnack, page, rowsPerPage])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadFields();
|
//based on the current tab load the correct fields
|
||||||
}, [loadFields]);
|
switch(tabValue){
|
||||||
|
case 0:
|
||||||
|
loadAdaptiveFields();
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
loadJDFields();
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
loadCNHFields();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}, [loadAdaptiveFields, loadJDFields, loadCNHFields, tabValue]);
|
||||||
|
|
||||||
const calcTotalAcres = () => {
|
const goTo = (field: Field) => {
|
||||||
let totalAcres = 0;
|
let path = field.key();
|
||||||
fields.forEach(field => {
|
navigate(path, { state: {field: field, permissions: field.permissions} });
|
||||||
totalAcres += field.acres();
|
}
|
||||||
});
|
|
||||||
totalAcres = Math.round(totalAcres);
|
|
||||||
return totalAcres;
|
|
||||||
};
|
|
||||||
|
|
||||||
const listFieldsInfo = fields.map((field, index) => (
|
const fieldActions = (field: Field) => {
|
||||||
<TableRow key={index}>
|
return (
|
||||||
<TableCell>{field.fieldName()}</TableCell>
|
<EventBlocker>
|
||||||
<TableCell>{field.landLoc()}</TableCell>
|
<IconButton
|
||||||
<TableCell>
|
|
||||||
{field.crop() === pond.Grain.GRAIN_CUSTOM
|
|
||||||
? field.customType()
|
|
||||||
: GrainDescriber(field.crop()).name}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell>{field.calculateAcres()}</TableCell>
|
|
||||||
<TableCell>
|
|
||||||
{field.permissions.includes(pond.Permission.PERMISSION_WRITE) && (
|
|
||||||
<Button
|
|
||||||
variant="contained"
|
|
||||||
color="primary"
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setFieldForPlan(field);
|
setSelectedField(field)
|
||||||
setOpenHarvestSettings(true);
|
setOpenFieldSettings(true)
|
||||||
}}>
|
}}><Settings /></IconButton>
|
||||||
New Crop Plan
|
<FieldActions field={field} permissions={field.permissions} refreshCallback={()=>{}}/>
|
||||||
</Button>
|
{isMobile && <IconButton onClick={() => {goTo(field)}}><ArrowForward /></IconButton>}
|
||||||
)}
|
</EventBlocker>
|
||||||
</TableCell>
|
)
|
||||||
</TableRow>
|
}
|
||||||
));
|
|
||||||
|
const columns: Column<Field>[] = [
|
||||||
|
{
|
||||||
|
title: "View",
|
||||||
|
render: row => {
|
||||||
|
return (
|
||||||
|
<Box margin={1} height={200} width={200}>
|
||||||
|
<FieldMinimap field={row} squared borderSpacing={0.001}/>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Field Name",
|
||||||
|
render: row => {
|
||||||
|
return <Typography paddingLeft={2}>{row.name()}</Typography>
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Current Crop",
|
||||||
|
render: row => {
|
||||||
|
return <Typography paddingLeft={2}>{GrainDescriber(row.crop()).name}</Typography>
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Acres",
|
||||||
|
render: row => {
|
||||||
|
return <Typography paddingLeft={2}>{row.acres()}</Typography>
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Actions",
|
||||||
|
hidden: tabValue > 0,
|
||||||
|
render: row => fieldActions(row)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
const fieldTable = (fields: Field[]) => {
|
||||||
|
return (
|
||||||
|
<ResponsiveTable<Field>
|
||||||
|
columns={columns}
|
||||||
|
rows={fields}
|
||||||
|
onRowClick={(row) => {
|
||||||
|
!isMobile && goTo(row)
|
||||||
|
}}
|
||||||
|
setPage={(newPage)=>{
|
||||||
|
setPage(newPage)
|
||||||
|
}}
|
||||||
|
handleRowsPerPageChange={(e)=>{setRowsPerPage(e.target.value)}}
|
||||||
|
page={page}
|
||||||
|
pageSize={rowsPerPage}
|
||||||
|
total={total}
|
||||||
|
resizeable
|
||||||
|
loadMore={() => {
|
||||||
|
let currentFields = cloneDeep(fields)
|
||||||
|
switch(tabValue){
|
||||||
|
case 0:
|
||||||
|
fieldAPI.listFields(rowsPerPage, currentFields.length, "asc", "fieldName", undefined, as).then(resp => {
|
||||||
|
setFields(currentFields.concat(resp.data.fields.map(f => Field.create(f))))
|
||||||
|
}).catch(err => {
|
||||||
|
openSnack("Failed to load more fields")
|
||||||
|
})
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
jdAPI.listFields(rowsPerPage, currentFields.length, as).then(resp => {
|
||||||
|
resp.data.fields ? setFields(currentFields.concat(resp.data.fields.map(f => Field.create(f)))) : setFields([])
|
||||||
|
}).catch(err => {
|
||||||
|
openSnack("Failed to load more fields")
|
||||||
|
})
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
cnhAPI.listFields(rowsPerPage, currentFields.length, as).then(resp => {
|
||||||
|
resp.data.fields ? setFields(currentFields.concat(resp.data.fields.map(f => Field.create(f)))) : setFields([])
|
||||||
|
}).catch(err => {
|
||||||
|
openSnack("Failed to load more fields")
|
||||||
|
})
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
renderMobile={(row, index) => {
|
||||||
|
return (
|
||||||
|
<Accordion>
|
||||||
|
<AccordionSummary expandIcon={<ExpandMore />}>
|
||||||
|
<Grid2 alignItems="center" width="100%" container direction={"row"} justifyContent="space-between" wrap="nowrap">
|
||||||
|
<Grid2>
|
||||||
|
<Typography textAlign="center">
|
||||||
|
{row.name()}
|
||||||
|
</Typography>
|
||||||
|
</Grid2>
|
||||||
|
<Grid2>
|
||||||
|
{fieldActions(row)}
|
||||||
|
</Grid2>
|
||||||
|
</Grid2>
|
||||||
|
</AccordionSummary>
|
||||||
|
<AccordionDetails>
|
||||||
|
<Grid2 alignItems="center" width="100%" container direction={"row"} justifyContent="space-between" wrap="nowrap">
|
||||||
|
<Grid2 size={6}>
|
||||||
|
<Box height={150} width="100%">
|
||||||
|
<FieldMinimap field={row} borderSpacing={0.001} squared />
|
||||||
|
</Box>
|
||||||
|
</Grid2>
|
||||||
|
<Grid2 size={6}>
|
||||||
|
<Stack spacing={2}>
|
||||||
|
<Typography textAlign="center">{row.grainName()}</Typography>
|
||||||
|
<Typography textAlign="center">{row.acres()} Acres</Typography>
|
||||||
|
</Stack>
|
||||||
|
<Button variant="contained" color="primary">Go</Button>
|
||||||
|
</Grid2>
|
||||||
|
</Grid2>
|
||||||
|
</AccordionDetails>
|
||||||
|
</Accordion>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
|
<Button
|
||||||
|
onClick={()=>{setShareOpen(true)}}
|
||||||
|
variant="contained"
|
||||||
|
color="primary">
|
||||||
|
Share All
|
||||||
|
</Button>
|
||||||
<Tabs
|
<Tabs
|
||||||
value={value}
|
value={tabValue}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
TabIndicatorProps={{ style: { background: "rgba(255,255,0,255)" } }}>
|
TabIndicatorProps={{ style: { background: "rgba(255,255,0,255)" } }}>
|
||||||
<Tab label="Field Overview" />
|
<Tab label="Adaptive" />
|
||||||
{/* {!isMobile && <Tab label="Current Harvest Plans" />}
|
<Tab label="John Deere" />
|
||||||
{!isMobile && <Tab label="Plan History" />} */}
|
<Tab label="Case New Holland" />
|
||||||
</Tabs>
|
</Tabs>
|
||||||
<TabPanelMine index={0} value={value}>
|
{fieldTable(fields)}
|
||||||
<TableContainer component={Paper}>
|
{/* <HarvestSettings
|
||||||
<Table style={{ minWidth: 1000 }}>
|
|
||||||
<TableHead>
|
|
||||||
<TableRow>
|
|
||||||
<TableCell>Field Name</TableCell>
|
|
||||||
<TableCell>Land Location</TableCell>
|
|
||||||
<TableCell>Main Crop Type</TableCell>
|
|
||||||
<TableCell>Acres ({calcTotalAcres()} Total)</TableCell>
|
|
||||||
<TableCell>Create New Plan</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
</TableHead>
|
|
||||||
<TableBody>{listFieldsInfo}</TableBody>
|
|
||||||
</Table>
|
|
||||||
</TableContainer>
|
|
||||||
</TabPanelMine>
|
|
||||||
{/* need to re-factor the harvest table file before restoring these functions */}
|
|
||||||
{/* <TabPanelMine index={1} value={value}>
|
|
||||||
<HarvestTable fields={fields} display="current" />
|
|
||||||
</TabPanelMine>
|
|
||||||
<TabPanelMine index={2} value={value}>
|
|
||||||
<HarvestTable fields={fields} display="history" />
|
|
||||||
</TabPanelMine> */}
|
|
||||||
<HarvestSettings
|
|
||||||
open={openHarvestSettings}
|
open={openHarvestSettings}
|
||||||
close={() => setOpenHarvestSettings(false)}
|
close={() => setOpenHarvestSettings(false)}
|
||||||
field={fieldForPlan}
|
field={fieldForPlan}
|
||||||
|
/> */}
|
||||||
|
<FieldSettings
|
||||||
|
selectedField={selectedField}
|
||||||
|
removeField={() => {
|
||||||
|
//will need to re-load the fields after one is deleted
|
||||||
|
loadAdaptiveFields()
|
||||||
|
}}
|
||||||
|
open={openFieldSettings}
|
||||||
|
onClose={() => {
|
||||||
|
setOpenFieldSettings(false);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
|
<ShareAllFields open={shareOpen} close={() => {setShareOpen(false)}}/>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -152,9 +152,7 @@ export default function FieldSettings(props: Props) {
|
||||||
|
|
||||||
const fieldInformation = () => {
|
const fieldInformation = () => {
|
||||||
return (
|
return (
|
||||||
<Box>
|
<React.Fragment>
|
||||||
<Box>
|
|
||||||
<DialogTitle>Field Information</DialogTitle>
|
|
||||||
<TextField
|
<TextField
|
||||||
value={fieldName}
|
value={fieldName}
|
||||||
margin="normal"
|
margin="normal"
|
||||||
|
|
@ -258,7 +256,14 @@ export default function FieldSettings(props: Props) {
|
||||||
Field Colour
|
Field Colour
|
||||||
<ColourPicker onChange={color => setFeatColor(color)} />
|
<ColourPicker onChange={color => setFeatColor(color)} />
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ResponsiveDialog fullWidth open={props.open} onClose={onClose}>
|
||||||
|
<DialogTitle>Field Information</DialogTitle>
|
||||||
|
<DialogContent>{fieldInformation()}</DialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Grid container direction="row">
|
<Grid container direction="row">
|
||||||
<Grid>
|
<Grid>
|
||||||
|
|
@ -282,13 +287,6 @@ export default function FieldSettings(props: Props) {
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ResponsiveDialog fullWidth fullScreen={false} open={props.open} onClose={onClose}>
|
|
||||||
<DialogContent>{fieldInformation()}</DialogContent>
|
|
||||||
</ResponsiveDialog>
|
</ResponsiveDialog>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
61
src/field/Fieldminimap.tsx
Normal file
61
src/field/Fieldminimap.tsx
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
import { CircularProgress } from "@mui/material";
|
||||||
|
import { Field } from "models";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import Map from "react-map-gl/mapbox";
|
||||||
|
import 'mapbox-gl/dist/mapbox-gl.css';
|
||||||
|
import GeoMapLayer from "maps/mapLayers/geoMapLayer";
|
||||||
|
import { Feature, FeatureCollection } from "geojson";
|
||||||
|
import { GeometryMapping } from "models/GeometryMapping";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
field: Field
|
||||||
|
squared?: boolean
|
||||||
|
borderSpacing?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function FieldMinimap(props: Props) {
|
||||||
|
const { field, squared, borderSpacing } = props
|
||||||
|
const MAPBOX_TOKEN = import.meta.env.VITE_MAPBOX_ACCESS_TOKEN;
|
||||||
|
const [geoCollection, setGeoCollection] = useState<FeatureCollection>();
|
||||||
|
|
||||||
|
useEffect(()=>{
|
||||||
|
let fieldData = field.settings.fieldGeoData
|
||||||
|
if(fieldData){
|
||||||
|
let fieldFeature = GeometryMapping.geoJSON(fieldData.geoShape, fieldData.shapes, fieldData.holes) as Feature;
|
||||||
|
fieldFeature.id = fieldData.objectKey;
|
||||||
|
fieldFeature.properties = {
|
||||||
|
title: fieldData.title,
|
||||||
|
objectKey: fieldData.objectKey,
|
||||||
|
fill: fieldData.colour,
|
||||||
|
lineWidth: fieldData.geoShape === "LineString" ? 5 : 2,
|
||||||
|
origin: fieldData.origin
|
||||||
|
};
|
||||||
|
let collection: FeatureCollection = {
|
||||||
|
type: "FeatureCollection",
|
||||||
|
features: [fieldFeature]
|
||||||
|
};
|
||||||
|
setGeoCollection(collection);
|
||||||
|
}
|
||||||
|
},[field])
|
||||||
|
|
||||||
|
|
||||||
|
return geoCollection ?
|
||||||
|
(
|
||||||
|
<Map
|
||||||
|
zoom={1}
|
||||||
|
maxBounds={
|
||||||
|
field.fieldBounds(borderSpacing, squared)
|
||||||
|
}
|
||||||
|
reuseMaps
|
||||||
|
style={{width: "100%", height: "100%"}}
|
||||||
|
mapboxAccessToken={MAPBOX_TOKEN}
|
||||||
|
mapStyle="mapbox://styles/mapbox/satellite-streets-v11"
|
||||||
|
>
|
||||||
|
<GeoMapLayer
|
||||||
|
objectCollection={geoCollection}
|
||||||
|
/>
|
||||||
|
</Map>
|
||||||
|
)
|
||||||
|
:
|
||||||
|
(<CircularProgress />)
|
||||||
|
}
|
||||||
201
src/field/ShareAllFields.tsx
Normal file
201
src/field/ShareAllFields.tsx
Normal file
|
|
@ -0,0 +1,201 @@
|
||||||
|
import { Box, Button, Checkbox, DialogActions, DialogContent, DialogTitle, FormControl, FormControlLabel, FormGroup, FormLabel, Grid2, Stack, Switch, TextField } from "@mui/material";
|
||||||
|
import ResponsiveDialog from "common/ResponsiveDialog";
|
||||||
|
import { cloneDeep } from "lodash";
|
||||||
|
import { pond } from "protobuf-ts/pond";
|
||||||
|
import { useFieldAPI, useSnackbar } from "providers";
|
||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
import TeamSearch from "teams/TeamSearch";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
open: boolean
|
||||||
|
close: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ShareAllFields(props: Props) {
|
||||||
|
const {open, close} = props
|
||||||
|
const [teamShare, setTeamShare] = useState(false)
|
||||||
|
const fieldAPI = useFieldAPI();
|
||||||
|
const { openSnack } = useSnackbar()
|
||||||
|
const [team, setTeam] = useState<string>("")
|
||||||
|
const [user, setUser] = useState<string>("")
|
||||||
|
const [sharedPermissions, setSharedPermissions] = useState<pond.Permission[]>([
|
||||||
|
pond.Permission.PERMISSION_READ
|
||||||
|
])
|
||||||
|
|
||||||
|
const share = () => {
|
||||||
|
if(teamShare){
|
||||||
|
//if sharing to team use shareAllByKey
|
||||||
|
fieldAPI.shareAllByKey(team, sharedPermissions)
|
||||||
|
.then(resp => {
|
||||||
|
openSnack("Shared all fields to team")
|
||||||
|
}).catch(err => {
|
||||||
|
openSnack("There was a problem sharing the fields")
|
||||||
|
})
|
||||||
|
}else{
|
||||||
|
//if sharing to user use shareAll
|
||||||
|
fieldAPI.shareAll(user, sharedPermissions)
|
||||||
|
.then(resp => {
|
||||||
|
openSnack("Shared all fields to user")
|
||||||
|
}).catch(err => {
|
||||||
|
openSnack("There was a problem sharing the fields")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const target = () => {
|
||||||
|
// toggle for whether sharing to user or team
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<Grid2 container alignItems="center">
|
||||||
|
<Grid2>User</Grid2>
|
||||||
|
<Grid2>
|
||||||
|
<Switch
|
||||||
|
color="default"
|
||||||
|
value={teamShare}
|
||||||
|
checked={teamShare}
|
||||||
|
onChange={(_, checked) => {
|
||||||
|
setTeamShare(checked)
|
||||||
|
}}
|
||||||
|
name="storage"
|
||||||
|
/>
|
||||||
|
</Grid2>
|
||||||
|
<Grid2>Team</Grid2>
|
||||||
|
</Grid2>
|
||||||
|
{teamShare ?
|
||||||
|
<Box>
|
||||||
|
<TeamSearch label="Team" setTeamCallback={(teamKey) => {setTeam(teamKey)}}/>
|
||||||
|
</Box> :
|
||||||
|
<Box>
|
||||||
|
<TextField
|
||||||
|
label="User Email"
|
||||||
|
fullWidth
|
||||||
|
value={user}
|
||||||
|
onChange={(e) => {
|
||||||
|
setUser(e.target.value)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>}
|
||||||
|
</React.Fragment>
|
||||||
|
)
|
||||||
|
// if sharing to user have a text field
|
||||||
|
}
|
||||||
|
|
||||||
|
const changePermissions = (checked: boolean, permission: pond.Permission) => {
|
||||||
|
let currentPerms = cloneDeep(sharedPermissions)
|
||||||
|
if(checked){
|
||||||
|
//if the permissions does not include the permission add it
|
||||||
|
if(!sharedPermissions.includes(permission)) currentPerms.push(permission)
|
||||||
|
}else{
|
||||||
|
//if the permissions includes the permission remove it
|
||||||
|
if(sharedPermissions.includes(permission)) currentPerms.splice(currentPerms.indexOf(permission), 1)
|
||||||
|
}
|
||||||
|
setSharedPermissions(currentPerms)
|
||||||
|
}
|
||||||
|
|
||||||
|
const permissions = () => {
|
||||||
|
// the checkboxes of permissions
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<FormControl sx={{marginTop: 2}} component="fieldset" fullWidth variant="outlined">
|
||||||
|
<FormLabel component="legend">Permissions</FormLabel>
|
||||||
|
<FormGroup>
|
||||||
|
<FormControlLabel
|
||||||
|
control={
|
||||||
|
<Checkbox
|
||||||
|
disabled={sharedPermissions.includes(pond.Permission.PERMISSION_READ)}
|
||||||
|
checked={sharedPermissions.includes(pond.Permission.PERMISSION_READ)}
|
||||||
|
onChange={(_, checked) => {changePermissions(checked, pond.Permission.PERMISSION_READ)}}
|
||||||
|
value={pond.Permission.PERMISSION_READ as pond.Permission}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label="View"
|
||||||
|
labelPlacement="end"
|
||||||
|
/>
|
||||||
|
<FormControlLabel
|
||||||
|
control={
|
||||||
|
<Checkbox
|
||||||
|
checked={sharedPermissions.includes(pond.Permission.PERMISSION_WRITE)}
|
||||||
|
onChange={(_, checked) => {changePermissions(checked, pond.Permission.PERMISSION_WRITE)}}
|
||||||
|
value={pond.Permission.PERMISSION_WRITE as pond.Permission}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label="Edit"
|
||||||
|
labelPlacement="end"
|
||||||
|
/>
|
||||||
|
<FormControlLabel
|
||||||
|
control={
|
||||||
|
<Checkbox
|
||||||
|
checked={sharedPermissions.includes(pond.Permission.PERMISSION_SHARE)}
|
||||||
|
onChange={(_, checked) => {changePermissions(checked, pond.Permission.PERMISSION_SHARE)}}
|
||||||
|
value={pond.Permission.PERMISSION_SHARE as pond.Permission}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label="Share"
|
||||||
|
labelPlacement="end"
|
||||||
|
/>
|
||||||
|
<FormControlLabel
|
||||||
|
control={
|
||||||
|
<Checkbox
|
||||||
|
checked={sharedPermissions.includes(pond.Permission.PERMISSION_USERS)}
|
||||||
|
onChange={(_, checked) => {changePermissions(checked, pond.Permission.PERMISSION_USERS)}}
|
||||||
|
value={pond.Permission.PERMISSION_USERS as pond.Permission}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label="Users"
|
||||||
|
labelPlacement="end"
|
||||||
|
/>
|
||||||
|
<FormControlLabel
|
||||||
|
control={
|
||||||
|
<Checkbox
|
||||||
|
checked={sharedPermissions.includes(pond.Permission.PERMISSION_FILE_MANAGEMENT)}
|
||||||
|
onChange={(_, checked) => {changePermissions(checked, pond.Permission.PERMISSION_FILE_MANAGEMENT)}}
|
||||||
|
value={pond.Permission.PERMISSION_FILE_MANAGEMENT as pond.Permission}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label="Files"
|
||||||
|
labelPlacement="end"
|
||||||
|
/>
|
||||||
|
{/* billing */}
|
||||||
|
<FormControlLabel
|
||||||
|
control={
|
||||||
|
<Checkbox
|
||||||
|
checked={sharedPermissions.includes(pond.Permission.PERMISSION_BILLING)}
|
||||||
|
onChange={(_, checked) => {changePermissions(checked, pond.Permission.PERMISSION_BILLING)}}
|
||||||
|
value={pond.Permission.PERMISSION_BILLING as pond.Permission}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label="Billing"
|
||||||
|
labelPlacement="end"
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
</FormControl>
|
||||||
|
</React.Fragment>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const actions = () => {
|
||||||
|
//the buttons to shaare or cancel
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<Button onClick={close}>Close</Button>
|
||||||
|
<Button onClick={share} variant="contained" color="primary">Share</Button>
|
||||||
|
</React.Fragment>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ResponsiveDialog open={open} onClose={close}>
|
||||||
|
<DialogTitle>Share All Fields</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<Box minWidth={300}>
|
||||||
|
{target()}
|
||||||
|
{permissions()}
|
||||||
|
</Box>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
{actions()}
|
||||||
|
</DialogActions>
|
||||||
|
</ResponsiveDialog>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -249,9 +249,9 @@ export default function GateSVG(props: Props) {
|
||||||
<g key={"finalTemp"} id={"finalTemp"} data-name={"finalTemp"}>
|
<g key={"finalTemp"} id={"finalTemp"} data-name={"finalTemp"}>
|
||||||
<text fontSize={7} className={classes.fontBase} x={100} y={85}>
|
<text fontSize={7} className={classes.fontBase} x={100} y={85}>
|
||||||
{convertFinalTemp(finalTemp).toFixed(2)}°
|
{convertFinalTemp(finalTemp).toFixed(2)}°
|
||||||
{user.settings.temperatureUnit === pond.TemperatureUnit.TEMPERATURE_UNIT_CELSIUS
|
{user.settings.temperatureUnit === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT
|
||||||
? "C"
|
? "°F"
|
||||||
: "F"}
|
: "°C"}
|
||||||
</text>
|
</text>
|
||||||
</g>
|
</g>
|
||||||
);
|
);
|
||||||
|
|
@ -260,9 +260,9 @@ export default function GateSVG(props: Props) {
|
||||||
<g key={"ambientTemp"} id={"ambientTemp"} data-name={"ambientTemp"}>
|
<g key={"ambientTemp"} id={"ambientTemp"} data-name={"ambientTemp"}>
|
||||||
<text fontSize={7} className={classes.fontBase} x={30} y={114}>
|
<text fontSize={7} className={classes.fontBase} x={30} y={114}>
|
||||||
Ambient: {ambientTemp}°
|
Ambient: {ambientTemp}°
|
||||||
{user.settings.temperatureUnit === pond.TemperatureUnit.TEMPERATURE_UNIT_CELSIUS
|
{user.settings.temperatureUnit === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT
|
||||||
? "C"
|
? "°F"
|
||||||
: "F"}
|
: "°C"}
|
||||||
</text>
|
</text>
|
||||||
</g>
|
</g>
|
||||||
);
|
);
|
||||||
|
|
@ -271,13 +271,13 @@ export default function GateSVG(props: Props) {
|
||||||
<g key={"tempChain"} id={"tempChain"} data-name={"tempChain"}>
|
<g key={"tempChain"} id={"tempChain"} data-name={"tempChain"}>
|
||||||
<text fontSize={7} className={classes.fontBase} x={30} y={149}>
|
<text fontSize={7} className={classes.fontBase} x={30} y={149}>
|
||||||
T1: {innerTemps.t1}°
|
T1: {innerTemps.t1}°
|
||||||
{user.settings.temperatureUnit === pond.TemperatureUnit.TEMPERATURE_UNIT_CELSIUS
|
{user.settings.temperatureUnit === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT
|
||||||
? "C"
|
? "°F"
|
||||||
: "F"}
|
: "°C"}
|
||||||
, T2: {innerTemps.t2}°
|
, T2: {innerTemps.t2}°
|
||||||
{user.settings.temperatureUnit === pond.TemperatureUnit.TEMPERATURE_UNIT_CELSIUS
|
{user.settings.temperatureUnit === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT
|
||||||
? "C"
|
? "°F"
|
||||||
: "F"}
|
: "°C"}
|
||||||
</text>
|
</text>
|
||||||
</g>
|
</g>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -7,16 +7,17 @@ import {
|
||||||
TextField
|
TextField
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import ResponsiveDialog from "common/ResponsiveDialog";
|
import ResponsiveDialog from "common/ResponsiveDialog";
|
||||||
|
import { Option } from "common/SearchSelect";
|
||||||
import { Field, HarvestPlan, Task } from "models";
|
import { Field, HarvestPlan, Task } from "models";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import { pond } from "protobuf-ts/pond";
|
import { pond } from "protobuf-ts/pond";
|
||||||
import { useGlobalState, useHarvestPlanAPI, useTaskAPI } from "providers";
|
import { useFieldAPI, useGlobalState, useHarvestPlanAPI, useTaskAPI } from "providers";
|
||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
close: () => void;
|
close: () => void;
|
||||||
fields: Field[];
|
fields?: Field[];
|
||||||
plan: HarvestPlan;
|
plan: HarvestPlan;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -29,10 +30,14 @@ export default function DuplicateHarvestPlan(props: Props) {
|
||||||
const harvestPlanAPI = useHarvestPlanAPI();
|
const harvestPlanAPI = useHarvestPlanAPI();
|
||||||
const taskAPI = useTaskAPI();
|
const taskAPI = useTaskAPI();
|
||||||
const [ready, setReady] = useState(false);
|
const [ready, setReady] = useState(false);
|
||||||
|
const [fieldOptions, setFieldOptions] = useState<Field[]>([])
|
||||||
|
const fieldAPI = useFieldAPI();
|
||||||
|
|
||||||
const loadTasks = useCallback(() => {
|
const loadTasks = useCallback(() => {
|
||||||
taskAPI.listTasks(50, 0, "asc", "start", plan.key(), undefined).then(resp => {
|
taskAPI.listTasks(50, 0, "asc", "start", plan.key(), undefined).then(resp => {
|
||||||
|
if(resp.data.tasks){
|
||||||
setPlanTasks(resp.data.tasks.map(t => Task.any(t)));
|
setPlanTasks(resp.data.tasks.map(t => Task.any(t)));
|
||||||
|
}
|
||||||
setReady(true);
|
setReady(true);
|
||||||
});
|
});
|
||||||
}, [taskAPI, plan]);
|
}, [taskAPI, plan]);
|
||||||
|
|
@ -41,9 +46,25 @@ export default function DuplicateHarvestPlan(props: Props) {
|
||||||
loadTasks();
|
loadTasks();
|
||||||
}, [loadTasks]);
|
}, [loadTasks]);
|
||||||
|
|
||||||
|
useEffect(()=>{
|
||||||
|
if(fields){
|
||||||
|
//if fields were passed in use those
|
||||||
|
setFieldOptions(fields)
|
||||||
|
}else{
|
||||||
|
//otherwise load them
|
||||||
|
fieldAPI.listFields(50, 0, "asc", "fieldName").then(resp => {
|
||||||
|
setFieldOptions(resp.data.fields.map(f => Field.create(f)))
|
||||||
|
}).catch(err => {
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},[fields])
|
||||||
|
|
||||||
|
//this should load the fields here
|
||||||
|
|
||||||
const duplicate = () => {
|
const duplicate = () => {
|
||||||
let planSettings = HarvestPlan.clone(plan).settings;
|
let planSettings = HarvestPlan.clone(plan).settings;
|
||||||
planSettings.field = fields[newFieldIndex].key();
|
planSettings.field = fieldOptions[newFieldIndex].key();
|
||||||
planSettings.createDate = moment.now();
|
planSettings.createDate = moment.now();
|
||||||
if (title !== "") {
|
if (title !== "") {
|
||||||
planSettings.title = title;
|
planSettings.title = title;
|
||||||
|
|
@ -69,7 +90,7 @@ export default function DuplicateHarvestPlan(props: Props) {
|
||||||
value={newFieldIndex}
|
value={newFieldIndex}
|
||||||
onChange={e => setNewFieldIndex(+e.target.value)}
|
onChange={e => setNewFieldIndex(+e.target.value)}
|
||||||
color="primary">
|
color="primary">
|
||||||
{fields.map((option, i) => (
|
{fieldOptions.map((option, i) => (
|
||||||
<MenuItem key={option.key()} value={i}>
|
<MenuItem key={option.key()} value={i}>
|
||||||
{option.fieldName()}
|
{option.fieldName()}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
|
@ -98,8 +119,8 @@ export default function DuplicateHarvestPlan(props: Props) {
|
||||||
color="primary"
|
color="primary"
|
||||||
disabled={
|
disabled={
|
||||||
!ready ||
|
!ready ||
|
||||||
(fields[newFieldIndex] &&
|
(fieldOptions[newFieldIndex] &&
|
||||||
!fields[newFieldIndex].permissions.includes(pond.Permission.PERMISSION_WRITE))
|
!fieldOptions[newFieldIndex].permissions.includes(pond.Permission.PERMISSION_WRITE))
|
||||||
}>
|
}>
|
||||||
Duplicate
|
Duplicate
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ import ShareObjectIcon from "@mui/icons-material/Share";
|
||||||
import ObjectUsersIcon from "@mui/icons-material/AccountCircle";
|
import ObjectUsersIcon from "@mui/icons-material/AccountCircle";
|
||||||
import ObjectTeamsIcon from "@mui/icons-material/SupervisedUserCircle";
|
import ObjectTeamsIcon from "@mui/icons-material/SupervisedUserCircle";
|
||||||
import { pond } from "protobuf-ts/pond";
|
import { pond } from "protobuf-ts/pond";
|
||||||
import React, { useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import ObjectUsers from "user/ObjectUsers";
|
import ObjectUsers from "user/ObjectUsers";
|
||||||
import ShareObject from "user/ShareObject";
|
import ShareObject from "user/ShareObject";
|
||||||
import { isOffline } from "utils/environment";
|
import { isOffline } from "utils/environment";
|
||||||
|
|
@ -43,7 +43,7 @@ const useStyles = makeStyles((theme: Theme) => ({
|
||||||
interface Props {
|
interface Props {
|
||||||
plan: HarvestPlan;
|
plan: HarvestPlan;
|
||||||
planField: Field;
|
planField: Field;
|
||||||
fieldList: Field[];
|
fieldList?: Field[];
|
||||||
permissions: pond.Permission[];
|
permissions: pond.Permission[];
|
||||||
refreshCallback: (updatedPlan?: HarvestPlan) => void;
|
refreshCallback: (updatedPlan?: HarvestPlan) => void;
|
||||||
hideNew?: boolean;
|
hideNew?: boolean;
|
||||||
|
|
@ -215,7 +215,7 @@ export default function HarvestPlanActions(props: Props) {
|
||||||
setUpdatePlan(true);
|
setUpdatePlan(true);
|
||||||
setOpenState({ ...openState, settings: true });
|
setOpenState({ ...openState, settings: true });
|
||||||
}}>
|
}}>
|
||||||
<Edit /> Edit or Add Activity
|
<Edit /> Activities
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
<IconButton
|
<IconButton
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ import {
|
||||||
import { Notes } from "@mui/icons-material";
|
import { Notes } from "@mui/icons-material";
|
||||||
import GrainDescriber from "grain/GrainDescriber";
|
import GrainDescriber from "grain/GrainDescriber";
|
||||||
import { Field, HarvestPlan } from "models";
|
import { Field, HarvestPlan } from "models";
|
||||||
import React, { useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { getThemeType } from "theme";
|
import { getThemeType } from "theme";
|
||||||
import Chat from "chat/Chat";
|
import Chat from "chat/Chat";
|
||||||
import { pond } from "protobuf-ts/pond";
|
import { pond } from "protobuf-ts/pond";
|
||||||
|
|
@ -23,7 +23,7 @@ interface Props {
|
||||||
permissions: pond.Permission[];
|
permissions: pond.Permission[];
|
||||||
planField: Field;
|
planField: Field;
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
fieldList: Field[];
|
fieldList?: Field[];
|
||||||
changePlan: (updatedPlan?: HarvestPlan) => void;
|
changePlan: (updatedPlan?: HarvestPlan) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -175,7 +175,7 @@ export default function HarvestPlanDisplay(props: Props) {
|
||||||
<Typography
|
<Typography
|
||||||
variant="h5"
|
variant="h5"
|
||||||
style={{ fontWeight: 700, marginTop: "auto", marginBottom: "auto" }}>
|
style={{ fontWeight: 700, marginTop: "auto", marginBottom: "auto" }}>
|
||||||
Crop Plan{plan.key() !== "" ? " - " + plan.settings.title : " - None Found"}
|
{plan.key() !== "" ? plan.settings.title : "None Found"}
|
||||||
</Typography>
|
</Typography>
|
||||||
{!isMobile && permissions.includes(pond.Permission.PERMISSION_WRITE) && (
|
{!isMobile && permissions.includes(pond.Permission.PERMISSION_WRITE) && (
|
||||||
<HarvestPlanActions
|
<HarvestPlanActions
|
||||||
|
|
|
||||||
202
src/harvestPlan/HarvestPlanTable.tsx
Normal file
202
src/harvestPlan/HarvestPlanTable.tsx
Normal file
|
|
@ -0,0 +1,202 @@
|
||||||
|
import { ExpandMore } from "@mui/icons-material"
|
||||||
|
import { Accordion, AccordionDetails, AccordionSummary, Box, Theme, Typography } from "@mui/material"
|
||||||
|
import { makeStyles } from "@mui/styles"
|
||||||
|
import ResponsiveTable, { Column } from "common/ResponsiveTable"
|
||||||
|
import GrainDescriber from "grain/GrainDescriber"
|
||||||
|
import { cloneDeep } from "lodash"
|
||||||
|
import { Field, HarvestPlan } from "models"
|
||||||
|
import { useHarvestPlanAPI } from "providers"
|
||||||
|
import { useCallback, useEffect, useState } from "react"
|
||||||
|
import { getThemeType } from "theme"
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
field: Field
|
||||||
|
}
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme: Theme) => ({
|
||||||
|
dark: {
|
||||||
|
backgroundColor: getThemeType() === "light" ? "rgb(245, 245, 245)" : "rgb(50, 50, 50)",
|
||||||
|
padding: 5
|
||||||
|
},
|
||||||
|
light: {
|
||||||
|
backgroundColor: getThemeType() === "light" ? "rgb(235, 235, 235)" : "rgb(60, 60, 60)",
|
||||||
|
padding: 5
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
export default function HarvestPlanTable(props: Props){
|
||||||
|
const {field} = props
|
||||||
|
const harvestAPI = useHarvestPlanAPI()
|
||||||
|
const [page, setPage] = useState(0)
|
||||||
|
const [pageSize, setPageSize] = useState(10)
|
||||||
|
const [loadedPlans, setLoadedPlans] = useState<HarvestPlan[]>([])
|
||||||
|
const [total, setTotal] = useState(0)
|
||||||
|
const classes = useStyles()
|
||||||
|
|
||||||
|
useEffect(()=>{
|
||||||
|
harvestAPI.listHarvestPlans(pageSize, page*pageSize, "desc", "harvestYear", field.key()).then(resp => {
|
||||||
|
if(resp.data.harvestPlan){
|
||||||
|
setLoadedPlans(resp.data.harvestPlan.map(hp => HarvestPlan.create(hp)))
|
||||||
|
}
|
||||||
|
setTotal(resp.data.total)
|
||||||
|
})
|
||||||
|
},[page, pageSize, field])
|
||||||
|
|
||||||
|
const columns: Column<HarvestPlan>[] = [
|
||||||
|
//these columns would be the ones on by default
|
||||||
|
//field - hideable
|
||||||
|
{
|
||||||
|
title: "Field",
|
||||||
|
hidden: field !== undefined,
|
||||||
|
render: (row) => <Typography align="center">{row.field()}</Typography>
|
||||||
|
},
|
||||||
|
//title
|
||||||
|
{
|
||||||
|
title: "Plan",
|
||||||
|
render: (row) => <Typography align="center">{row.settings.title}</Typography>
|
||||||
|
},
|
||||||
|
//year
|
||||||
|
{
|
||||||
|
title: "Year",
|
||||||
|
render: (row) => <Typography align="center">{row.harvestYear()}</Typography>
|
||||||
|
},
|
||||||
|
//grain - crop_type
|
||||||
|
{
|
||||||
|
title: "Grain",
|
||||||
|
render: (row) => <Typography align="center">{GrainDescriber(row.cropType()).name}</Typography>
|
||||||
|
},
|
||||||
|
//variant - grain_type
|
||||||
|
{
|
||||||
|
title: "Variant",
|
||||||
|
render: (row) => <Typography align="center">{row.grainType()}</Typography>
|
||||||
|
},
|
||||||
|
//yield target
|
||||||
|
{
|
||||||
|
title: "Yield Target (bu)",
|
||||||
|
render: (row) => <Typography align="right">{row.yieldTarget()}</Typography>
|
||||||
|
},
|
||||||
|
//actual yield
|
||||||
|
{
|
||||||
|
title: "Actual Yield (bu)",
|
||||||
|
render: (row) => <Typography align="right">{row.actualYield()}</Typography>
|
||||||
|
},
|
||||||
|
//bushel price
|
||||||
|
{
|
||||||
|
title: "Bushel Price",
|
||||||
|
render: (row) => <Typography align="right">{row.bushelPrice()}</Typography>
|
||||||
|
},
|
||||||
|
//material cost
|
||||||
|
{
|
||||||
|
title: "Material Cost",
|
||||||
|
render: (row) => <Typography align="right">{row.totalMaterialCost()}</Typography>
|
||||||
|
},
|
||||||
|
//equipment cost
|
||||||
|
{
|
||||||
|
title: "Equipment Cost",
|
||||||
|
render: (row) => <Typography align="right">{row.totalEquipmentCost()}</Typography>
|
||||||
|
}
|
||||||
|
//these columns could be optional (disabled by default but turned on later?)
|
||||||
|
//pre-seed materials
|
||||||
|
//pre-seed equpment
|
||||||
|
//seed materials
|
||||||
|
//seed equpment
|
||||||
|
//post-seed materials
|
||||||
|
//post-seed equpment
|
||||||
|
//harvest materials
|
||||||
|
//harvest equpment
|
||||||
|
//fall materials
|
||||||
|
//fall equpment
|
||||||
|
]
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ResponsiveTable<HarvestPlan>
|
||||||
|
columns={columns}
|
||||||
|
title={"Harvest Plans"}
|
||||||
|
resizeable
|
||||||
|
page={page}
|
||||||
|
pageSize={pageSize}
|
||||||
|
rows={loadedPlans}
|
||||||
|
total={total}
|
||||||
|
handleRowsPerPageChange={(e)=>{
|
||||||
|
setPageSize(e.target.value)
|
||||||
|
}}
|
||||||
|
setPage={(page)=>{
|
||||||
|
setPage(page)
|
||||||
|
}}
|
||||||
|
loadMore={() => {
|
||||||
|
harvestAPI.listHarvestPlans(pageSize, loadedPlans.length, "desc", "harvestYear", field.key()).then(resp => {
|
||||||
|
let currentPlans = cloneDeep(loadedPlans)
|
||||||
|
let newPlans: HarvestPlan[] = []
|
||||||
|
if(resp.data.harvestPlan){
|
||||||
|
newPlans = resp.data.harvestPlan.map(hp => HarvestPlan.create(hp))
|
||||||
|
}
|
||||||
|
setLoadedPlans(currentPlans.concat(newPlans))
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
renderMobile={(row, index) => {
|
||||||
|
// since these are more about previous harvest plans i am only showing the final tallys rather than the breakdowns
|
||||||
|
return (
|
||||||
|
<Accordion>
|
||||||
|
<AccordionSummary expandIcon={<ExpandMore />}>
|
||||||
|
<Typography sx={{fontWeight: 650}}>
|
||||||
|
{row.settings.title} - {row.harvestYear()}
|
||||||
|
</Typography>
|
||||||
|
</AccordionSummary>
|
||||||
|
<AccordionDetails>
|
||||||
|
{/* grain */}
|
||||||
|
<Box className={classes.light} style={{ display: "flex", justifyContent: "space-between" }}>
|
||||||
|
<Typography variant="subtitle1">Grain:</Typography>
|
||||||
|
<Typography variant="subtitle1">{GrainDescriber(row.cropType()).name}</Typography>
|
||||||
|
</Box>
|
||||||
|
{/* variant */}
|
||||||
|
<Box className={classes.dark} style={{ display: "flex", justifyContent: "space-between" }}>
|
||||||
|
<Typography variant="subtitle1">Variant:</Typography>
|
||||||
|
<Typography variant="subtitle1">{row.grainType()}</Typography>
|
||||||
|
</Box>
|
||||||
|
{/* yield target */}
|
||||||
|
{/* <Box className={classes.dark} style={{ display: "flex", justifyContent: "space-between" }}>
|
||||||
|
<Typography variant="subtitle1">Yield Target:</Typography>
|
||||||
|
<Typography variant="subtitle1">{row.yieldTarget()}bu</Typography>
|
||||||
|
</Box> */}
|
||||||
|
{/* actual yield */}
|
||||||
|
<Box className={classes.light} style={{ display: "flex", justifyContent: "space-between" }}>
|
||||||
|
<Typography variant="subtitle1">Actual Yield:</Typography>
|
||||||
|
<Typography variant="subtitle1">{row.actualYield()}bu</Typography>
|
||||||
|
</Box>
|
||||||
|
{/* bushel price */}
|
||||||
|
<Box className={classes.dark} style={{ display: "flex", justifyContent: "space-between" }}>
|
||||||
|
<Typography variant="subtitle1">Bushel Price:</Typography>
|
||||||
|
<Typography variant="subtitle1">${row.bushelPrice()}</Typography>
|
||||||
|
</Box>
|
||||||
|
{/* material cost */}
|
||||||
|
<Box className={classes.light} style={{ display: "flex", justifyContent: "space-between" }}>
|
||||||
|
<Typography variant="subtitle1">Material Cost:</Typography>
|
||||||
|
<Typography variant="subtitle1">${row.totalMaterialCost() * field.acres()}</Typography>
|
||||||
|
</Box>
|
||||||
|
{/* equipment cost */}
|
||||||
|
<Box className={classes.dark} style={{ display: "flex", justifyContent: "space-between" }}>
|
||||||
|
<Typography variant="subtitle1">Equipment Cost:</Typography>
|
||||||
|
<Typography variant="subtitle1">${row.totalEquipmentCost() * field.acres()}</Typography>
|
||||||
|
</Box>
|
||||||
|
{/* pre-seed materials */}
|
||||||
|
{/* pre-seed equpment */}
|
||||||
|
{/* seed materials */}
|
||||||
|
{/* seed equpment */}
|
||||||
|
{/* post-seed materials */}
|
||||||
|
{/* post-seed equpment */}
|
||||||
|
{/* harvest materials */}
|
||||||
|
{/* harvest equpment */}
|
||||||
|
{/* fall materials */}
|
||||||
|
{/* fall equpment */}
|
||||||
|
{/* total cost */}
|
||||||
|
<Box className={classes.light} style={{ display: "flex", justifyContent: "space-between" }}>
|
||||||
|
<Typography variant="subtitle1">Total Cost:</Typography>
|
||||||
|
<Typography variant="subtitle1">${(row.totalEquipmentCost() + row.totalMaterialCost()) * field.acres()}</Typography>
|
||||||
|
</Box>
|
||||||
|
</AccordionDetails>
|
||||||
|
</Accordion>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -210,7 +210,7 @@ export default function LibraCartAccess() {
|
||||||
</Grid>
|
</Grid>
|
||||||
<Box paddingTop={2}>
|
<Box paddingTop={2}>
|
||||||
<Typography>
|
<Typography>
|
||||||
Libra Cart data will sync every 6 hours, if the data is needed immediately you can select the Sync button. It may take up to 15 minutes for the data to appear in our platform.
|
Libra Cart data will sync every 6 hours, if the data is needed immediately you can select the Sync button.
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -241,7 +241,7 @@ export default function MapBase(props: Props) {
|
||||||
homeMarkerAPI
|
homeMarkerAPI
|
||||||
.listHomeMarkers(1, 0, undefined, undefined, undefined, undefined, as)
|
.listHomeMarkers(1, 0, undefined, undefined, undefined, undefined, as)
|
||||||
.then(resp => {
|
.then(resp => {
|
||||||
if (resp.data.homeMarker.length < 1) {
|
if (!resp.data.homeMarker || resp.data.homeMarker.length < 1) {
|
||||||
setHomePin(undefined);
|
setHomePin(undefined);
|
||||||
setHaveHome(false);
|
setHaveHome(false);
|
||||||
setStartingView(props.currentView);
|
setStartingView(props.currentView);
|
||||||
|
|
@ -271,6 +271,7 @@ export default function MapBase(props: Props) {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
|
console.log(err)
|
||||||
openSnack("Could not load Home Pin");
|
openSnack("Could not load Home Pin");
|
||||||
});
|
});
|
||||||
}, [as, homeMarkerAPI, openSnack, props.ignoreHomeLoad, user]); // eslint-disable-line react-hooks/exhaustive-deps
|
}, [as, homeMarkerAPI, openSnack, props.ignoreHomeLoad, user]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||||
|
|
|
||||||
|
|
@ -273,7 +273,7 @@ export default function FieldDrawer(props: Props) {
|
||||||
<Weather longitude={field.center().longitude} latitude={field.center().latitude} />
|
<Weather longitude={field.center().longitude} latitude={field.center().latitude} />
|
||||||
</TabPanelMine>
|
</TabPanelMine>
|
||||||
<TabPanelMine value={value} index={2}>
|
<TabPanelMine value={value} index={2}>
|
||||||
<TaskViewer drawerView objectKey={field.key()} loadKeys={taskLoadKeys} />
|
<TaskViewer drawerView objectKey={field.key()} loadKeys={taskLoadKeys} overlayButton />
|
||||||
</TabPanelMine>
|
</TabPanelMine>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ interface Props {
|
||||||
objectCollection: FeatureCollection;
|
objectCollection: FeatureCollection;
|
||||||
measurementCollection?: FeatureCollection;
|
measurementCollection?: FeatureCollection;
|
||||||
showTitle?: boolean;
|
showTitle?: boolean;
|
||||||
setInteractiveLayers: (layers: string[]) => void;
|
setInteractiveLayers?: (layers: string[]) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function GeoMapLayer(props: Props) {
|
export default function GeoMapLayer(props: Props) {
|
||||||
|
|
@ -18,7 +18,9 @@ export default function GeoMapLayer(props: Props) {
|
||||||
// }, [measurementCollection, objectCollection]);
|
// }, [measurementCollection, objectCollection]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if(setInteractiveLayers){
|
||||||
setInteractiveLayers(["shapefill", "shapeborder", "measurementBorder"]);
|
setInteractiveLayers(["shapefill", "shapeborder", "measurementBorder"]);
|
||||||
|
}
|
||||||
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -60,6 +62,7 @@ export default function GeoMapLayer(props: Props) {
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Source>
|
</Source>
|
||||||
|
{measurementCollection &&
|
||||||
<Source type="geojson" data={measurementCollection} id={"measurements"}>
|
<Source type="geojson" data={measurementCollection} id={"measurements"}>
|
||||||
<Layer
|
<Layer
|
||||||
id="measurementBorder"
|
id="measurementBorder"
|
||||||
|
|
@ -90,6 +93,7 @@ export default function GeoMapLayer(props: Props) {
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Source>
|
</Source>
|
||||||
|
}
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -221,6 +221,7 @@ import { Result } from "@mapbox/mapbox-gl-geocoder";
|
||||||
.then(resp => {
|
.then(resp => {
|
||||||
let fields: Map<string, Field> = new Map();
|
let fields: Map<string, Field> = new Map();
|
||||||
let fieldEntries: Result[] = [];
|
let fieldEntries: Result[] = [];
|
||||||
|
if(resp.data.fields){
|
||||||
resp.data.fields.forEach(f => {
|
resp.data.fields.forEach(f => {
|
||||||
let field = Field.any(f);
|
let field = Field.any(f);
|
||||||
fields.set(field.key(), field);
|
fields.set(field.key(), field);
|
||||||
|
|
@ -239,6 +240,7 @@ import { Result } from "@mapbox/mapbox-gl-geocoder";
|
||||||
relevance: 0
|
relevance: 0
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
}
|
||||||
setFields(fields);
|
setFields(fields);
|
||||||
setFieldSearchEntries(fieldEntries);
|
setFieldSearchEntries(fieldEntries);
|
||||||
})
|
})
|
||||||
|
|
@ -314,6 +316,8 @@ import { Result } from "@mapbox/mapbox-gl-geocoder";
|
||||||
let bagOp: BagModel[] = [];
|
let bagOp: BagModel[] = [];
|
||||||
let bagMarkerData: Map<string, MarkerData> = new Map();
|
let bagMarkerData: Map<string, MarkerData> = new Map();
|
||||||
let bagEntries: Result[] = [];
|
let bagEntries: Result[] = [];
|
||||||
|
if(resp.data.grainBags){
|
||||||
|
|
||||||
resp.data.grainBags.forEach(bag => {
|
resp.data.grainBags.forEach(bag => {
|
||||||
let b = BagModel.create(bag);
|
let b = BagModel.create(bag);
|
||||||
bags.set(b.key(), b);
|
bags.set(b.key(), b);
|
||||||
|
|
@ -361,6 +365,7 @@ import { Result } from "@mapbox/mapbox-gl-geocoder";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
setBagOptions(bagOp);
|
setBagOptions(bagOp);
|
||||||
setGrainBags(bags);
|
setGrainBags(bags);
|
||||||
grainBagsRef.current = bags;
|
grainBagsRef.current = bags;
|
||||||
|
|
@ -382,6 +387,8 @@ import { Result } from "@mapbox/mapbox-gl-geocoder";
|
||||||
binYardAPI
|
binYardAPI
|
||||||
.listBinYards(400, 0, "asc", undefined, undefined, undefined, as)
|
.listBinYards(400, 0, "asc", undefined, undefined, undefined, as)
|
||||||
.then(resp => {
|
.then(resp => {
|
||||||
|
if(resp.data.yard){
|
||||||
|
|
||||||
resp.data.yard.forEach(yard => {
|
resp.data.yard.forEach(yard => {
|
||||||
if (yard.settings) {
|
if (yard.settings) {
|
||||||
yardMap.set(yard.settings.key, yard.settings);
|
yardMap.set(yard.settings.key, yard.settings);
|
||||||
|
|
@ -429,6 +436,7 @@ import { Result } from "@mapbox/mapbox-gl-geocoder";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
setYardMarkerData(newYardMarkers);
|
setYardMarkerData(newYardMarkers);
|
||||||
setYardSearchEntries(yardEntries);
|
setYardSearchEntries(yardEntries);
|
||||||
setBinYards(yardMap);
|
setBinYards(yardMap);
|
||||||
|
|
@ -447,6 +455,9 @@ import { Result } from "@mapbox/mapbox-gl-geocoder";
|
||||||
let markerData: Map<string, MarkerData> = new Map();
|
let markerData: Map<string, MarkerData> = new Map();
|
||||||
let searchEntries: Result[] = [];
|
let searchEntries: Result[] = [];
|
||||||
let binOp: IBin[] = [];
|
let binOp: IBin[] = [];
|
||||||
|
if(resp.data.bins){
|
||||||
|
|
||||||
|
|
||||||
resp.data.bins.forEach(bin => {
|
resp.data.bins.forEach(bin => {
|
||||||
let b = IBin.create(bin);
|
let b = IBin.create(bin);
|
||||||
binMap.set(b.key(), b);
|
binMap.set(b.key(), b);
|
||||||
|
|
@ -495,6 +506,7 @@ import { Result } from "@mapbox/mapbox-gl-geocoder";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
setBinMarkerData(markerData);
|
setBinMarkerData(markerData);
|
||||||
setBinSearchEntries(searchEntries);
|
setBinSearchEntries(searchEntries);
|
||||||
setBins(binMap);
|
setBins(binMap);
|
||||||
|
|
@ -514,6 +526,9 @@ import { Result } from "@mapbox/mapbox-gl-geocoder";
|
||||||
let searchEntries: Result[] = [];
|
let searchEntries: Result[] = [];
|
||||||
|
|
||||||
let options: DeviceModel[] = [];
|
let options: DeviceModel[] = [];
|
||||||
|
if(resp.data.devices){
|
||||||
|
|
||||||
|
|
||||||
resp.data.devices.forEach((device, i) => {
|
resp.data.devices.forEach((device, i) => {
|
||||||
//add device to the main map
|
//add device to the main map
|
||||||
let d = DeviceModel.any(device);
|
let d = DeviceModel.any(device);
|
||||||
|
|
@ -553,6 +568,7 @@ import { Result } from "@mapbox/mapbox-gl-geocoder";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
setDevices(map);
|
setDevices(map);
|
||||||
setDeviceOptions(options);
|
setDeviceOptions(options);
|
||||||
setDeviceMarkerData(newDevMarkers);
|
setDeviceMarkerData(newDevMarkers);
|
||||||
|
|
@ -725,6 +741,9 @@ import { Result } from "@mapbox/mapbox-gl-geocoder";
|
||||||
let fmMap: Map<string, FieldMarker> = new Map();
|
let fmMap: Map<string, FieldMarker> = new Map();
|
||||||
let markerData: Map<string, MarkerData> = new Map();
|
let markerData: Map<string, MarkerData> = new Map();
|
||||||
let searchEntries: Result[] = [];
|
let searchEntries: Result[] = [];
|
||||||
|
if(resp.data.fieldMarker){
|
||||||
|
|
||||||
|
|
||||||
resp.data.fieldMarker.forEach(fieldMarker => {
|
resp.data.fieldMarker.forEach(fieldMarker => {
|
||||||
let fm = FieldMarker.create(fieldMarker);
|
let fm = FieldMarker.create(fieldMarker);
|
||||||
fmMap.set(fm.key(), fm);
|
fmMap.set(fm.key(), fm);
|
||||||
|
|
@ -760,6 +779,7 @@ import { Result } from "@mapbox/mapbox-gl-geocoder";
|
||||||
place_type: ["fieldMarker"]
|
place_type: ["fieldMarker"]
|
||||||
} as Result);
|
} as Result);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
setFieldMarkers(fmMap);
|
setFieldMarkers(fmMap);
|
||||||
setFieldMarkerData(markerData);
|
setFieldMarkerData(markerData);
|
||||||
setFieldMarkerSearchEntries(searchEntries);
|
setFieldMarkerSearchEntries(searchEntries);
|
||||||
|
|
|
||||||
|
|
@ -175,6 +175,7 @@ export default function AviationMapController(props: Props) {
|
||||||
let terminalMarkers: Map<string, MarkerData> = new Map();
|
let terminalMarkers: Map<string, MarkerData> = new Map();
|
||||||
let terminalOps: Terminal[] = [];
|
let terminalOps: Terminal[] = [];
|
||||||
let searchEntries: Result[] = [];
|
let searchEntries: Result[] = [];
|
||||||
|
if(resp.data.terminals){
|
||||||
resp.data.terminals.forEach(term => {
|
resp.data.terminals.forEach(term => {
|
||||||
let terminal = Terminal.create(term);
|
let terminal = Terminal.create(term);
|
||||||
terminalMap.set(terminal.key, terminal);
|
terminalMap.set(terminal.key, terminal);
|
||||||
|
|
@ -207,6 +208,7 @@ export default function AviationMapController(props: Props) {
|
||||||
terminalOps.push(terminal);
|
terminalOps.push(terminal);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
setTerminals(terminalMap);
|
setTerminals(terminalMap);
|
||||||
setTerminalMarkers(terminalMarkers);
|
setTerminalMarkers(terminalMarkers);
|
||||||
setTerminalSearchEntries(searchEntries);
|
setTerminalSearchEntries(searchEntries);
|
||||||
|
|
|
||||||
|
|
@ -142,6 +142,7 @@ export default function ConstructionMapController(props: Props) {
|
||||||
siteAPI
|
siteAPI
|
||||||
.listSitesPageData(50, 0, "asc", "key", undefined, as)
|
.listSitesPageData(50, 0, "asc", "key", undefined, as)
|
||||||
.then(resp => {
|
.then(resp => {
|
||||||
|
if(resp.data.sites){
|
||||||
resp.data.sites.forEach(site => {
|
resp.data.sites.forEach(site => {
|
||||||
if (!site) return;
|
if (!site) return;
|
||||||
let newSite: Site = Site.any(site.site);
|
let newSite: Site = Site.any(site.site);
|
||||||
|
|
@ -179,6 +180,7 @@ export default function ConstructionMapController(props: Props) {
|
||||||
place_type: ["site"]
|
place_type: ["site"]
|
||||||
} as Result);
|
} as Result);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
setSiteMarkerData(sm);
|
setSiteMarkerData(sm);
|
||||||
setSites(s);
|
setSites(s);
|
||||||
setLoadingSites(false);
|
setLoadingSites(false);
|
||||||
|
|
|
||||||
|
|
@ -109,6 +109,35 @@ export class Device {
|
||||||
return m;
|
return m;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public platformName(): string {
|
||||||
|
switch (this.settings.platform) {
|
||||||
|
case pond.DevicePlatform.DEVICE_PLATFORM_PHOTON:
|
||||||
|
return "Photon"
|
||||||
|
case pond.DevicePlatform.DEVICE_PLATFORM_ELECTRON:
|
||||||
|
return "Electron"
|
||||||
|
case pond.DevicePlatform.DEVICE_PLATFORM_V2_CELLULAR:
|
||||||
|
return "V2 Cellular"
|
||||||
|
case pond.DevicePlatform.DEVICE_PLATFORM_V2_WIFI:
|
||||||
|
return "V2 Wifi"
|
||||||
|
case pond.DevicePlatform.DEVICE_PLATFORM_V2_WIFI_S3:
|
||||||
|
return "V2 Wifi S3"
|
||||||
|
case pond.DevicePlatform.DEVICE_PLATFORM_V2_CELLULAR_S3:
|
||||||
|
return "V2 Cellular S3"
|
||||||
|
case pond.DevicePlatform.DEVICE_PLATFORM_V2_CELLULAR_BLACK:
|
||||||
|
return "V2 Cellular Black"
|
||||||
|
case pond.DevicePlatform.DEVICE_PLATFORM_V2_CELLULAR_GREEN:
|
||||||
|
return "V2 Callular Green"
|
||||||
|
case pond.DevicePlatform.DEVICE_PLATFORM_V2_WIFI_BLUE:
|
||||||
|
return "V2 Wifi Blue"
|
||||||
|
case pond.DevicePlatform.DEVICE_PLATFORM_V2_CELLULAR_BLUE:
|
||||||
|
return "V2 Cellular Blue"
|
||||||
|
case pond.DevicePlatform.DEVICE_PLATFORM_V2_ETHERNET_BLUE:
|
||||||
|
return "V2 Ethernet Blue"
|
||||||
|
default:
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public featureSupported(feature: string): boolean {
|
public featureSupported(feature: string): boolean {
|
||||||
let versions = featureVersions.get(feature);
|
let versions = featureVersions.get(feature);
|
||||||
if (!versions) {
|
if (!versions) {
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import area from "@turf/area";
|
||||||
import { Feature } from "geojson";
|
import { Feature } from "geojson";
|
||||||
import { GeometryMapping } from "./GeometryMapping";
|
import { GeometryMapping } from "./GeometryMapping";
|
||||||
import GrainDescriber from "grain/GrainDescriber";
|
import GrainDescriber from "grain/GrainDescriber";
|
||||||
|
import { LngLat, LngLatBounds } from "mapbox-gl";
|
||||||
|
|
||||||
export class Field {
|
export class Field {
|
||||||
public settings: pond.FieldSettings = pond.FieldSettings.create();
|
public settings: pond.FieldSettings = pond.FieldSettings.create();
|
||||||
|
|
@ -14,9 +15,11 @@ export class Field {
|
||||||
public static create(pf?: pond.Field): Field {
|
public static create(pf?: pond.Field): Field {
|
||||||
let my = new Field();
|
let my = new Field();
|
||||||
if (pf) {
|
if (pf) {
|
||||||
my.settings = pond.FieldSettings.fromObject(cloneDeep(or(pf.settings, {})));
|
//the from object is required here to deal with a protobuf quirk where enums would be seen as strings at runtime, but the code sees them as the actual value
|
||||||
my.status = pond.FieldStatus.fromObject(cloneDeep(or(pf.status, {})));
|
let field = pond.Field.fromObject(cloneDeep(or(pf, pond.Field.create())))
|
||||||
my.permissions = pf.fieldPermissions;
|
my.settings = field.settings ? field.settings : pond.FieldSettings.create()
|
||||||
|
my.status = field.status ? field.status : pond.FieldStatus.create()
|
||||||
|
my.permissions = field.fieldPermissions
|
||||||
}
|
}
|
||||||
return my;
|
return my;
|
||||||
}
|
}
|
||||||
|
|
@ -152,6 +155,60 @@ export class Field {
|
||||||
return coords;
|
return coords;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a LngLatBounds object containing the southwest and northeast corners of a box to contain the field, spacing can also be provided
|
||||||
|
* to pad the sides of the bounding area
|
||||||
|
* @param spacing number(optional) - an optional paramater that will expand the bounding area by the given long lat amount
|
||||||
|
* @param squared boolean(optional) - when true will adjust the bounds by extending the narrower edge to match the wider edge
|
||||||
|
* @returns LngLatBounds - an object containing the southwest and northeast coordinates of a bounding box
|
||||||
|
*/
|
||||||
|
public fieldBounds(spacing?: number, squared?: boolean): LngLatBounds {
|
||||||
|
let minLong = 0;
|
||||||
|
let minLat = 0;
|
||||||
|
let maxLong = 0;
|
||||||
|
let maxLat = 0;
|
||||||
|
if (this.settings.fieldGeoData) {
|
||||||
|
this.settings.fieldGeoData.shapes.forEach(shape => {
|
||||||
|
shape.points.forEach(pair => {
|
||||||
|
if (pair.longitude < minLong || minLong === 0) minLong = pair.longitude;
|
||||||
|
if (pair.longitude > maxLong || maxLong === 0) maxLong = pair.longitude;
|
||||||
|
if (pair.latitude < minLat || minLat === 0) minLat = pair.latitude;
|
||||||
|
if (pair.latitude > maxLat || maxLat === 0) maxLat = pair.latitude;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// let southWest = [minLong,minLat]
|
||||||
|
// let northEast = [maxLong,maxLat]
|
||||||
|
if (squared){
|
||||||
|
//make sure the bounds are squared so as to fit the entire field in the map
|
||||||
|
//get the diff between the longs and the lats
|
||||||
|
let longDiff = maxLong - minLong
|
||||||
|
let latDiff = maxLat - minLat
|
||||||
|
//find which is narrower
|
||||||
|
if(longDiff > latDiff){
|
||||||
|
//if the longitude is the narrow bound apply half of the diff between the diffs to the min and max lats
|
||||||
|
let halfDiff = (longDiff - latDiff)/2
|
||||||
|
minLat = minLat - halfDiff
|
||||||
|
maxLat = maxLat + halfDiff
|
||||||
|
} else {
|
||||||
|
//if the longitude is not the narrow bound apply half of the diff between the diffs to the min and max longs
|
||||||
|
let halfDiff = (latDiff - longDiff)/2
|
||||||
|
minLong = minLong - halfDiff
|
||||||
|
maxLong = maxLong + halfDiff
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(spacing){
|
||||||
|
minLong = minLong - spacing
|
||||||
|
minLat = minLat - spacing
|
||||||
|
maxLong = maxLong + spacing
|
||||||
|
maxLat = maxLat + spacing
|
||||||
|
}
|
||||||
|
return new LngLatBounds(
|
||||||
|
new LngLat(minLong, minLat),
|
||||||
|
new LngLat(maxLong, maxLat)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public grainName(): string {
|
public grainName(): string {
|
||||||
if (this.grain() !== pond.Grain.GRAIN_INVALID) {
|
if (this.grain() !== pond.Grain.GRAIN_INVALID) {
|
||||||
if (this.grain() === pond.Grain.GRAIN_CUSTOM) {
|
if (this.grain() === pond.Grain.GRAIN_CUSTOM) {
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,7 @@ const Contract = lazy(() => import("pages/Contract"));
|
||||||
const JohnDeere = lazy(() => import("pages/JohnDeere"));
|
const JohnDeere = lazy(() => import("pages/JohnDeere"));
|
||||||
const CNHi = lazy(() => import("pages/CNHi"));
|
const CNHi = lazy(() => import("pages/CNHi"));
|
||||||
const LibraCart = lazy(() => import("pages/LibraCart"));
|
const LibraCart = lazy(() => import("pages/LibraCart"));
|
||||||
|
const FieldPage = lazy(()=>import("pages/Field"));
|
||||||
|
|
||||||
export const appendToUrl = (appendage: number | string) => {
|
export const appendToUrl = (appendage: number | string) => {
|
||||||
const basePath = location.pathname.replace(/\/$/, "");
|
const basePath = location.pathname.replace(/\/$/, "");
|
||||||
|
|
@ -70,6 +71,7 @@ export default function Router() {
|
||||||
<Route path="terminals/*" element={<TerminalGatesRoute />} />
|
<Route path="terminals/*" element={<TerminalGatesRoute />} />
|
||||||
<Route path="jobsites/*" element={<JobsitesRoute />} />
|
<Route path="jobsites/*" element={<JobsitesRoute />} />
|
||||||
<Route path="heaters/*" element={<ObjectHeatersRoute />} />
|
<Route path="heaters/*" element={<ObjectHeatersRoute />} />
|
||||||
|
<Route path="fields/*" element={<FieldsRoute />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -284,6 +286,21 @@ export default function Router() {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const FieldsRoute = () => {
|
||||||
|
return (
|
||||||
|
<Routes>
|
||||||
|
<Route
|
||||||
|
path=""
|
||||||
|
element={<Fields />}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="/:fieldID"
|
||||||
|
element={<FieldPage />}
|
||||||
|
/>
|
||||||
|
</Routes>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (isLoading) return null;
|
if (isLoading) return null;
|
||||||
// if (!isAuthenticated) {
|
// if (!isAuthenticated) {
|
||||||
// loginWithRedirect()
|
// loginWithRedirect()
|
||||||
|
|
@ -325,7 +342,6 @@ export default function Router() {
|
||||||
{user.hasFeature("admin") &&
|
{user.hasFeature("admin") &&
|
||||||
<Route path="logs" element={<Logs />} />
|
<Route path="logs" element={<Logs />} />
|
||||||
}
|
}
|
||||||
<Route path="fields" element={<Fields />} />
|
|
||||||
<Route path="marketplace" element={<Marketplace />} />
|
<Route path="marketplace" element={<Marketplace />} />
|
||||||
{user.hasFeature("admin") && (
|
{user.hasFeature("admin") && (
|
||||||
<Route path="firmware" element={<Firmware />} />
|
<Route path="firmware" element={<Firmware />} />
|
||||||
|
|
|
||||||
|
|
@ -208,6 +208,118 @@ export default function Devices() {
|
||||||
})
|
})
|
||||||
}, [tab])
|
}, [tab])
|
||||||
|
|
||||||
|
function compareTimestamps(timestamp1: string, timestamp2: string): number {
|
||||||
|
const date1 = new Date(timestamp1);
|
||||||
|
const date2 = new Date(timestamp2);
|
||||||
|
|
||||||
|
// Check if dates are valid
|
||||||
|
if (isNaN(date1.getTime()) || isNaN(date2.getTime())) {
|
||||||
|
throw new Error("Invalid RFC3339 timestamp format");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare using getTime() for millisecond precision
|
||||||
|
return date1.getTime() - date2.getTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
const doesDeviceHavePlenum = (device: pond.Device) => {
|
||||||
|
if (device.status?.plenum?.temperature && device.status?.plenum?.timestamp) {
|
||||||
|
if (device.status.plenum?.timestamp) {
|
||||||
|
const now = new Date();
|
||||||
|
const oneDayAgo = new Date(now.getTime() - 24 * 60 * 60 * 1000);
|
||||||
|
if (compareTimestamps(device.status.plenum?.timestamp, oneDayAgo.toISOString()) < 0) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const doesDeviceHaveSen5x = (device: pond.Device) => {
|
||||||
|
if (device.status?.sen5x?.temperature && device.status?.sen5x?.timestamp) {
|
||||||
|
if (device.status.sen5x?.timestamp) {
|
||||||
|
const now = new Date();
|
||||||
|
const oneDayAgo = new Date(now.getTime() - 24 * 60 * 60 * 1000);
|
||||||
|
if (compareTimestamps(device.status.sen5x?.timestamp, oneDayAgo.toISOString()) < 0) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// if (device.status?.o2) setHasO2(true)
|
||||||
|
// if (device.status?.co) setHasCo(true)
|
||||||
|
// if (device.status?.co2) setHasCo2(true)
|
||||||
|
// if (device.status?.no2) setHasNo2(true)
|
||||||
|
|
||||||
|
const doesDeviceHaveO2 = (device: pond.Device) => {
|
||||||
|
if (device.status?.o2?.ppm && device.status?.o2?.timestamp) {
|
||||||
|
if (device.status.sen5x?.timestamp) {
|
||||||
|
const now = new Date();
|
||||||
|
const oneDayAgo = new Date(now.getTime() - 24 * 60 * 60 * 1000);
|
||||||
|
if (compareTimestamps(device.status.o2?.timestamp, oneDayAgo.toISOString()) < 0) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const doesDeviceHaveCo = (device: pond.Device) => {
|
||||||
|
if (device.status?.co?.ppm && device.status?.co?.timestamp) {
|
||||||
|
if (device.status.co?.timestamp) {
|
||||||
|
const now = new Date();
|
||||||
|
const oneDayAgo = new Date(now.getTime() - 24 * 60 * 60 * 1000);
|
||||||
|
if (compareTimestamps(device.status.co?.timestamp, oneDayAgo.toISOString()) < 0) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const doesDeviceHaveCo2 = (device: pond.Device) => {
|
||||||
|
if (device.status?.co2?.ppm && device.status?.co2?.timestamp) {
|
||||||
|
if (device.status.co2?.timestamp) {
|
||||||
|
const now = new Date();
|
||||||
|
const oneDayAgo = new Date(now.getTime() - 24 * 60 * 60 * 1000);
|
||||||
|
if (compareTimestamps(device.status.co2?.timestamp, oneDayAgo.toISOString()) < 0) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const doesDeviceHaveNo2 = (device: pond.Device) => {
|
||||||
|
if (device.status?.no2?.ppm && device.status?.no2?.timestamp) {
|
||||||
|
if (device.status.no2?.timestamp) {
|
||||||
|
const now = new Date();
|
||||||
|
const oneDayAgo = new Date(now.getTime() - 24 * 60 * 60 * 1000);
|
||||||
|
if (compareTimestamps(device.status.no2?.timestamp, oneDayAgo.toISOString()) < 0) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
console.log(hasPlenums)
|
||||||
|
}, [hasPlenums])
|
||||||
|
|
||||||
const openProvisionDialog = () => {
|
const openProvisionDialog = () => {
|
||||||
setIsProvisionDialogOpen(true);
|
setIsProvisionDialogOpen(true);
|
||||||
};
|
};
|
||||||
|
|
@ -319,12 +431,13 @@ export default function Devices() {
|
||||||
).then(resp => {
|
).then(resp => {
|
||||||
let newDevices: Device[] = []
|
let newDevices: Device[] = []
|
||||||
resp.data.devices.forEach(device => {
|
resp.data.devices.forEach(device => {
|
||||||
if (device.status?.plenum?.temperature) setHasPlenums(true)
|
setHasPlenums(doesDeviceHavePlenum(device))
|
||||||
if (device.status?.sen5x?.temperature) setHasSen5x(true)
|
// setHasPlenums(true)
|
||||||
if (device.status?.o2) setHasO2(true)
|
setHasSen5x(doesDeviceHaveSen5x(device))
|
||||||
if (device.status?.co) setHasCo(true)
|
setHasO2(doesDeviceHaveO2(device))
|
||||||
if (device.status?.co2) setHasCo2(true)
|
setHasCo(doesDeviceHaveCo(device))
|
||||||
if (device.status?.no2) setHasNo2(true)
|
setHasCo2(doesDeviceHaveCo2(device))
|
||||||
|
setHasNo2(doesDeviceHaveNo2(device))
|
||||||
newDevices.push(Device.create(device))
|
newDevices.push(Device.create(device))
|
||||||
})
|
})
|
||||||
setDevices(newDevices)
|
setDevices(newDevices)
|
||||||
|
|
@ -809,12 +922,12 @@ export default function Devices() {
|
||||||
).then(resp => {
|
).then(resp => {
|
||||||
let newDevices: Device[] = []
|
let newDevices: Device[] = []
|
||||||
resp.data.devices.forEach(device => {
|
resp.data.devices.forEach(device => {
|
||||||
if (device.status?.plenum?.temperature) setHasPlenums(true)
|
setHasPlenums(doesDeviceHavePlenum(device))
|
||||||
if (device.status?.sen5x?.temperature) setHasSen5x(true)
|
setHasSen5x(doesDeviceHaveSen5x(device))
|
||||||
if (device.status?.o2) setHasO2(true)
|
setHasO2(doesDeviceHaveO2(device))
|
||||||
if (device.status?.co) setHasCo(true)
|
setHasCo(doesDeviceHaveCo(device))
|
||||||
if (device.status?.co2) setHasCo2(true)
|
setHasCo2(doesDeviceHaveCo2(device))
|
||||||
if (device.status?.no2) setHasNo2(true)
|
setHasNo2(doesDeviceHaveNo2(device))
|
||||||
newDevices.push(Device.create(device))
|
newDevices.push(Device.create(device))
|
||||||
})
|
})
|
||||||
setDevices(currentRows.concat(newDevices))
|
setDevices(currentRows.concat(newDevices))
|
||||||
|
|
|
||||||
314
src/pages/Field.tsx
Normal file
314
src/pages/Field.tsx
Normal file
|
|
@ -0,0 +1,314 @@
|
||||||
|
import { Field, fieldScope, HarvestPlan, teamScope } from "models";
|
||||||
|
import { useFieldAPI, useGlobalState, useHarvestPlanAPI } from "providers";
|
||||||
|
import React, { useCallback, useEffect, useState } from "react";
|
||||||
|
import { useLocation, useNavigate, useParams } from "react-router-dom";
|
||||||
|
import PageContainer from "./PageContainer";
|
||||||
|
import { useMobile, useSnackbar, useUserAPI } from "hooks";
|
||||||
|
import { Box, Card, Grid2, IconButton, Tab, Tabs, Theme, Typography, darken } from "@mui/material";
|
||||||
|
import FieldActions from "field/FieldActions";
|
||||||
|
import { Settings } from "@mui/icons-material";
|
||||||
|
import { pond } from "protobuf-ts/pond";
|
||||||
|
import FieldMinimap from "field/Fieldminimap";
|
||||||
|
import Weather from "weather/weather";
|
||||||
|
import TaskViewer from "tasks/TaskViewer";
|
||||||
|
import HarvestPlanDisplay from "harvestPlan/HarvestPlanDisplay";
|
||||||
|
import { cloneDeep } from "lodash";
|
||||||
|
import HarvestPlanTable from "harvestPlan/HarvestPlanTable";
|
||||||
|
import { makeStyles } from "@mui/styles";
|
||||||
|
import FieldSettings from "field/FieldSettings";
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme: Theme) => {
|
||||||
|
return ({
|
||||||
|
card: {
|
||||||
|
height: "100%",
|
||||||
|
bgcolor: darken(theme.palette.background.paper, 0.05),
|
||||||
|
},
|
||||||
|
sectionHeader: {
|
||||||
|
fontSize: 35,
|
||||||
|
fontWeight: 650
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
interface TabPanelProps {
|
||||||
|
children?: React.ReactNode;
|
||||||
|
index: any;
|
||||||
|
value: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
function TabPanel(props: TabPanelProps) {
|
||||||
|
const { children, value, index, ...other } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
role="tabpanel"
|
||||||
|
hidden={value !== index}
|
||||||
|
aria-labelledby={`simple-tab-${index}`}
|
||||||
|
style={{ height: "94%", paddingTop: "15px" }}
|
||||||
|
{...other}>
|
||||||
|
{value === index && <React.Fragment>{children}</React.Fragment>}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function FieldPage() {
|
||||||
|
const { state } = useLocation();
|
||||||
|
//const [{ as }] = useGlobalState()
|
||||||
|
const classes = useStyles();
|
||||||
|
const fieldAPI = useFieldAPI();
|
||||||
|
const userAPI = useUserAPI();
|
||||||
|
const [{as, user}] = useGlobalState();
|
||||||
|
const {openSnack} = useSnackbar();
|
||||||
|
const fieldID = useParams<{ fieldID: string }>()?.fieldID ?? "";
|
||||||
|
const [field, setField] = useState<Field>(state?.field ? Field.create(state.field) : Field.create())
|
||||||
|
const [permissions, setPermissions] = useState<pond.Permission[]>(state?.permissions ? state.permissions : [])
|
||||||
|
const isMobile = useMobile()
|
||||||
|
const [openFieldSettings, setOpenFieldSettings] = useState(false)
|
||||||
|
const [planLoading, setPlanLoading] = useState(false)
|
||||||
|
const [hPlan, setHPlan] = useState<HarvestPlan>(HarvestPlan.create());
|
||||||
|
const [loading, setLoading] = useState(false)
|
||||||
|
const hPlanAPI = useHarvestPlanAPI();
|
||||||
|
const [tabVal, setTabVal] = useState(0)
|
||||||
|
const navigate = useNavigate()
|
||||||
|
|
||||||
|
const loadField = useCallback(() => {
|
||||||
|
if(state.field){
|
||||||
|
fieldAPI.getField(state.field).then(resp => {
|
||||||
|
setField(Field.create(resp.data))
|
||||||
|
}).catch(err => {
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},[as])
|
||||||
|
|
||||||
|
const goTo = () => {
|
||||||
|
navigate("/fields");
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(()=>{
|
||||||
|
// if already loading do nothing
|
||||||
|
if(loading) return
|
||||||
|
//if the field is passed through in the state just use it
|
||||||
|
if(state?.field){
|
||||||
|
let field = Field.create(state.field)
|
||||||
|
field.permissions = state.permissions
|
||||||
|
setField(field)
|
||||||
|
setPermissions(state.permissions)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//otherwise load the field
|
||||||
|
setLoading(true)
|
||||||
|
fieldAPI.getField(fieldID, as).then(resp => {
|
||||||
|
setField(Field.create(resp.data))
|
||||||
|
|
||||||
|
}).catch(err => {
|
||||||
|
openSnack("Failed to load Field")
|
||||||
|
}).finally(() => {
|
||||||
|
setLoading(false)
|
||||||
|
})
|
||||||
|
//also get the permissions
|
||||||
|
let scope = fieldScope(fieldID);
|
||||||
|
if (as) {
|
||||||
|
scope = teamScope(as)
|
||||||
|
}
|
||||||
|
userAPI.getUser(user.id(), scope).then(resp => {
|
||||||
|
let f = cloneDeep(field)
|
||||||
|
f.permissions = resp.permissions
|
||||||
|
setField(f)
|
||||||
|
setPermissions(resp.permissions);
|
||||||
|
});
|
||||||
|
},[loadField, fieldID, as])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (field.key() !== "") {
|
||||||
|
hPlanAPI
|
||||||
|
.listHarvestPlans(1, 0, "desc", "createDate", field.key(), as)
|
||||||
|
.then(resp => {
|
||||||
|
if (resp.data.harvestPlan.length > 0) {
|
||||||
|
let plan = resp.data.harvestPlan[0];
|
||||||
|
setHPlan(HarvestPlan.any(plan));
|
||||||
|
} else {
|
||||||
|
setHPlan(HarvestPlan.create());
|
||||||
|
}
|
||||||
|
setPlanLoading(false);
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
//openSnack("Failed to load plan");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [field, as, hPlanAPI]);
|
||||||
|
|
||||||
|
const fieldActions = () => {
|
||||||
|
return (
|
||||||
|
<Grid2 container direction={"row"} justifyContent="space-between" alignContent="center" alignItems="center">
|
||||||
|
<Grid2>
|
||||||
|
<Typography className={classes.sectionHeader}>
|
||||||
|
{field.name()}
|
||||||
|
</Typography>
|
||||||
|
</Grid2>
|
||||||
|
<Grid2>
|
||||||
|
<IconButton
|
||||||
|
onClick={() => {
|
||||||
|
setOpenFieldSettings(true)
|
||||||
|
}}><Settings /></IconButton>
|
||||||
|
<FieldActions field={field} permissions={permissions} refreshCallback={()=>{}}/>
|
||||||
|
</Grid2>
|
||||||
|
</Grid2>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const map = () => {
|
||||||
|
return (
|
||||||
|
<Box minHeight={550} height="100%">
|
||||||
|
<FieldMinimap field={field} borderSpacing={0.001} squared/>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const weather = () => {
|
||||||
|
return (<Weather longitude={field.center().longitude} latitude={field.center().latitude} />)
|
||||||
|
}
|
||||||
|
|
||||||
|
const harvestPlans = () => {
|
||||||
|
return (
|
||||||
|
<HarvestPlanDisplay
|
||||||
|
plan={hPlan}
|
||||||
|
permissions={permissions}
|
||||||
|
planField={field}
|
||||||
|
loading={planLoading}
|
||||||
|
changePlan={updatedPlan => {
|
||||||
|
if (updatedPlan) {
|
||||||
|
setHPlan(updatedPlan);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>)
|
||||||
|
}
|
||||||
|
|
||||||
|
const tasks = () => {
|
||||||
|
let taskLoadKeys: string[] = [];
|
||||||
|
if (!planLoading) {
|
||||||
|
field.key() !== "" && taskLoadKeys.push(field.key());
|
||||||
|
//hPlan.key() !== "" && taskLoadKeys.push(hPlan.key());
|
||||||
|
}
|
||||||
|
return (<TaskViewer drawerView objectKey={field.key()} loadKeys={taskLoadKeys} overlayButton={isMobile}/>)
|
||||||
|
}
|
||||||
|
|
||||||
|
const desktopView = () => {
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<Box>
|
||||||
|
<Grid2 container spacing={1}>
|
||||||
|
<Grid2 size={4}>
|
||||||
|
<Card raised className={classes.card}>
|
||||||
|
{map()}
|
||||||
|
</Card>
|
||||||
|
</Grid2>
|
||||||
|
<Grid2 size={3}>
|
||||||
|
<Card raised className={classes.card}>
|
||||||
|
{weather()}
|
||||||
|
</Card>
|
||||||
|
</Grid2>
|
||||||
|
<Grid2 size={5}>
|
||||||
|
<Card raised className={classes.card}>
|
||||||
|
{tasks()}
|
||||||
|
</Card>
|
||||||
|
</Grid2>
|
||||||
|
</Grid2>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Typography className={classes.sectionHeader}>
|
||||||
|
Harvest Overview
|
||||||
|
</Typography>
|
||||||
|
<Grid2 container spacing={1}>
|
||||||
|
<Grid2 size={4}>
|
||||||
|
<Card raised className={classes.card}>
|
||||||
|
{harvestPlans()}
|
||||||
|
</Card>
|
||||||
|
</Grid2>
|
||||||
|
<Grid2 size={8}>
|
||||||
|
<Card raised>
|
||||||
|
<HarvestPlanTable field={field}/>
|
||||||
|
</Card>
|
||||||
|
</Grid2>
|
||||||
|
</Grid2>
|
||||||
|
</Box>
|
||||||
|
</React.Fragment>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const mobileView = () => {
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<Tabs
|
||||||
|
value={tabVal}
|
||||||
|
onChange={(_, val) => {
|
||||||
|
setTabVal(val)
|
||||||
|
}}
|
||||||
|
TabIndicatorProps={{ style: { background: "rgba(255,255,0,255)" } }}>
|
||||||
|
<Tab label="Overview" />
|
||||||
|
<Tab label="Activities" />
|
||||||
|
<Tab label="Harvest" />
|
||||||
|
</Tabs>
|
||||||
|
<TabPanel value={tabVal} index={0}>
|
||||||
|
<Grid2 container spacing={2} direction="column">
|
||||||
|
<Grid2 size={12}>
|
||||||
|
<Card raised sx={{height: "50vh"}}>
|
||||||
|
{map()}
|
||||||
|
</Card>
|
||||||
|
</Grid2>
|
||||||
|
<Grid2 size={12}>
|
||||||
|
<Card raised>
|
||||||
|
{weather()}
|
||||||
|
</Card>
|
||||||
|
</Grid2>
|
||||||
|
</Grid2>
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel value={tabVal} index={1}>
|
||||||
|
<Card raised>
|
||||||
|
{tasks()}
|
||||||
|
</Card>
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel value={tabVal} index={2}>
|
||||||
|
<Grid2>
|
||||||
|
<Grid2 size={12}>
|
||||||
|
<Card raised className={classes.card}>
|
||||||
|
{harvestPlans()}
|
||||||
|
</Card>
|
||||||
|
</Grid2>
|
||||||
|
<Grid2>
|
||||||
|
<HarvestPlanTable field={field}/>
|
||||||
|
</Grid2>
|
||||||
|
</Grid2>
|
||||||
|
</TabPanel>
|
||||||
|
</React.Fragment>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const fieldSettings = () => {
|
||||||
|
return (
|
||||||
|
<FieldSettings
|
||||||
|
open={openFieldSettings}
|
||||||
|
onClose={() => {
|
||||||
|
setOpenFieldSettings(false)
|
||||||
|
}}
|
||||||
|
selectedField={field}
|
||||||
|
updateFields={() => {
|
||||||
|
loadField()
|
||||||
|
}}
|
||||||
|
removeField={()=>{
|
||||||
|
goTo()
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PageContainer>
|
||||||
|
<Box padding={2}>
|
||||||
|
{fieldActions()}
|
||||||
|
{isMobile ? mobileView() : desktopView()}
|
||||||
|
</Box>
|
||||||
|
{fieldSettings()}
|
||||||
|
</PageContainer>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -39,6 +39,7 @@ export default function Heaters() {
|
||||||
objectHeaterAPI
|
objectHeaterAPI
|
||||||
.listObjectHeatersPageData(pageSize, tablePage * pageSize, undefined, undefined, searchText, as)
|
.listObjectHeatersPageData(pageSize, tablePage * pageSize, undefined, undefined, searchText, as)
|
||||||
.then(resp => {
|
.then(resp => {
|
||||||
|
// setHeaters(resp.data.heaterData ?? []);
|
||||||
setTotal(resp.data.total)
|
setTotal(resp.data.total)
|
||||||
setHeaters(resp.data.heaterData ?? []);
|
setHeaters(resp.data.heaterData ?? []);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ export default function Tasks() {
|
||||||
return (
|
return (
|
||||||
<PageContainer>
|
<PageContainer>
|
||||||
<Box padding={2}>
|
<Box padding={2}>
|
||||||
<TaskViewer />
|
<TaskViewer overlayButton/>
|
||||||
</Box>
|
</Box>
|
||||||
</PageContainer>
|
</PageContainer>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -182,7 +182,9 @@ export default function Terminals(props: Props) {
|
||||||
if(loading) return
|
if(loading) return
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
terminalAPI.listTerminals(200, 0, "asc", "name", as).then(resp => {
|
terminalAPI.listTerminals(200, 0, "asc", "name", as).then(resp => {
|
||||||
|
if(resp.data.terminals){
|
||||||
setTerminals(resp.data.terminals.map(a => Terminal.any(a)));
|
setTerminals(resp.data.terminals.map(a => Terminal.any(a)));
|
||||||
|
}
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
console.log("There was a problem loading Terminals")
|
console.log("There was a problem loading Terminals")
|
||||||
}).finally(() => {
|
}).finally(() => {
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,11 @@ import { useGlobalState } from "providers";
|
||||||
import React, { createContext, PropsWithChildren, useContext } from "react";
|
import React, { createContext, PropsWithChildren, useContext } from "react";
|
||||||
import { or } from "utils";
|
import { or } from "utils";
|
||||||
import { pondURL } from "./pond";
|
import { pondURL } from "./pond";
|
||||||
|
import { permissionToString } from "pbHelpers/Permission";
|
||||||
|
|
||||||
export interface IFieldAPIContext {
|
export interface IFieldAPIContext {
|
||||||
addField: (field: pond.FieldSettings, otherTeam?: string) => Promise<any>;
|
addField: (field: pond.FieldSettings, otherTeam?: string) => Promise<any>;
|
||||||
getField: (fieldId: string, otherTeam?: string) => Promise<any>;
|
getField: (fieldId: string, otherTeam?: string) => Promise<AxiosResponse<pond.Field>>;
|
||||||
listFields: (
|
listFields: (
|
||||||
limit: number,
|
limit: number,
|
||||||
offset: number,
|
offset: number,
|
||||||
|
|
@ -24,7 +25,15 @@ export interface IFieldAPIContext {
|
||||||
field: pond.FieldSettings,
|
field: pond.FieldSettings,
|
||||||
asRoot?: true,
|
asRoot?: true,
|
||||||
otherTeam?: string
|
otherTeam?: string
|
||||||
) => Promise<AxiosResponse<pond.UpdateSiteResponse>>;
|
) => Promise<AxiosResponse<pond.UpdateFieldResponse>>;
|
||||||
|
shareAll: (
|
||||||
|
email: string,
|
||||||
|
permissions: pond.Permission[],
|
||||||
|
) => Promise<AxiosResponse<any>>
|
||||||
|
shareAllByKey: (
|
||||||
|
key: string,
|
||||||
|
permissions: pond.Permission[]
|
||||||
|
) => Promise<AxiosResponse<any>>
|
||||||
}
|
}
|
||||||
|
|
||||||
export const FieldAPIContext = createContext<IFieldAPIContext>({} as IFieldAPIContext);
|
export const FieldAPIContext = createContext<IFieldAPIContext>({} as IFieldAPIContext);
|
||||||
|
|
@ -44,8 +53,8 @@ export default function FieldProvider(props: PropsWithChildren<Props>) {
|
||||||
|
|
||||||
const getField = (fieldId: string, otherTeam?: string) => {
|
const getField = (fieldId: string, otherTeam?: string) => {
|
||||||
const view = otherTeam ? otherTeam : as
|
const view = otherTeam ? otherTeam : as
|
||||||
if (view) return get(pondURL("/field/" + fieldId + "?as=" + view));
|
if (view) return get<pond.Field>(pondURL("/field/" + fieldId + "?as=" + view));
|
||||||
return get(pondURL("/field/" + fieldId));
|
return get<pond.Field>(pondURL("/field/" + fieldId));
|
||||||
};
|
};
|
||||||
|
|
||||||
const removeField = (key: string, otherTeam?: string) => {
|
const removeField = (key: string, otherTeam?: string) => {
|
||||||
|
|
@ -93,6 +102,36 @@ export default function FieldProvider(props: PropsWithChildren<Props>) {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const shareAll = (email: string, permissions: pond.Permission[]) => {
|
||||||
|
let url = pondURL("/shareAllFields")
|
||||||
|
if (as) url = url + "?as=" + as
|
||||||
|
return new Promise<AxiosResponse>((resolve, reject) => {
|
||||||
|
post(url, {
|
||||||
|
email: email,
|
||||||
|
permissions: permissions.map(permission => permissionToString(permission))
|
||||||
|
}).then(resp => {
|
||||||
|
return resolve(resp)
|
||||||
|
}).catch(err => {
|
||||||
|
return reject(err)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const shareAllByKey = (key: string, permissions: pond.Permission[]) => {
|
||||||
|
let url = pondURL("/shareAllFieldsByKey")
|
||||||
|
if (as) url = url + "?as=" + as
|
||||||
|
return new Promise<AxiosResponse>((resolve, reject) => {
|
||||||
|
post(url, {
|
||||||
|
key: key,
|
||||||
|
permissions: permissions.map(permission => permissionToString(permission))
|
||||||
|
}).then(resp => {
|
||||||
|
return resolve(resp)
|
||||||
|
}).catch(err => {
|
||||||
|
return reject(err)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FieldAPIContext.Provider
|
<FieldAPIContext.Provider
|
||||||
value={{
|
value={{
|
||||||
|
|
@ -100,7 +139,9 @@ export default function FieldProvider(props: PropsWithChildren<Props>) {
|
||||||
getField,
|
getField,
|
||||||
listFields,
|
listFields,
|
||||||
removeField,
|
removeField,
|
||||||
updateField
|
updateField,
|
||||||
|
shareAll,
|
||||||
|
shareAllByKey
|
||||||
}}>
|
}}>
|
||||||
{children}
|
{children}
|
||||||
</FieldAPIContext.Provider>
|
</FieldAPIContext.Provider>
|
||||||
|
|
|
||||||
|
|
@ -135,7 +135,6 @@ export default function PermissionProvider(props: PropsWithChildren<Props>) {
|
||||||
let url = pondURL("/" + scope.kind + "s/" + scope.key + "/shareByKey")
|
let url = pondURL("/" + scope.kind + "s/" + scope.key + "/shareByKey")
|
||||||
if (as) url = pondURL(`/${scope.kind}s/${scope.key}/shareByKey?as=${as}`)
|
if (as) url = pondURL(`/${scope.kind}s/${scope.key}/shareByKey?as=${as}`)
|
||||||
return new Promise<AxiosResponse>((resolve, reject) => {
|
return new Promise<AxiosResponse>((resolve, reject) => {
|
||||||
|
|
||||||
post(url, {
|
post(url, {
|
||||||
key: key,
|
key: key,
|
||||||
permissions: permissions.map(permission => permissionToString(permission))
|
permissions: permissions.map(permission => permissionToString(permission))
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import React, { useState } from "react";
|
||||||
import { EventClickArg } from "@fullcalendar/core";
|
import { EventClickArg } from "@fullcalendar/core";
|
||||||
import { DateClickArg } from "@fullcalendar/interaction";
|
import { DateClickArg } from "@fullcalendar/interaction";
|
||||||
import { useGlobalState } from "providers";
|
import { useGlobalState } from "providers";
|
||||||
import { useMobile, useSnackbar, useThemeType } from "hooks";
|
import { useMobile, useSnackbar } from "hooks";
|
||||||
import { useTaskAPI } from "providers";
|
import { useTaskAPI } from "providers";
|
||||||
import { useEffect, useCallback } from "react";
|
import { useEffect, useCallback } from "react";
|
||||||
import TaskCalendar from "./TaskCalendar";
|
import TaskCalendar from "./TaskCalendar";
|
||||||
|
|
@ -12,7 +12,6 @@ import {
|
||||||
Button,
|
Button,
|
||||||
Fab,
|
Fab,
|
||||||
Grid2 as Grid,
|
Grid2 as Grid,
|
||||||
IconButton,
|
|
||||||
LinearProgress,
|
LinearProgress,
|
||||||
Theme,
|
Theme,
|
||||||
Typography
|
Typography
|
||||||
|
|
@ -21,8 +20,6 @@ import { Task } from "models";
|
||||||
import TaskSettings from "./TaskSettings";
|
import TaskSettings from "./TaskSettings";
|
||||||
import { DayPicker } from "react-day-picker";
|
import { DayPicker } from "react-day-picker";
|
||||||
import "react-day-picker/dist/style.css";
|
import "react-day-picker/dist/style.css";
|
||||||
import { CalendarToday } from "@mui/icons-material";
|
|
||||||
import NotesIcon from "@mui/icons-material/Notes";
|
|
||||||
import AddIcon from "@mui/icons-material/Add";
|
import AddIcon from "@mui/icons-material/Add";
|
||||||
import TaskDrawer from "./TaskDrawer";
|
import TaskDrawer from "./TaskDrawer";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
|
|
@ -33,6 +30,7 @@ interface ViewProps {
|
||||||
label?: string;
|
label?: string;
|
||||||
drawerView?: boolean;
|
drawerView?: boolean;
|
||||||
loadKeys?: string[]; //if you want to load tasks for a specific object(s)
|
loadKeys?: string[]; //if you want to load tasks for a specific object(s)
|
||||||
|
overlayButton?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface TabPanelProps {
|
interface TabPanelProps {
|
||||||
|
|
@ -42,17 +40,7 @@ interface TabPanelProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
const useStyles = makeStyles((theme: Theme) => {
|
const useStyles = makeStyles((theme: Theme) => {
|
||||||
const themeType = useThemeType();
|
|
||||||
|
|
||||||
return ({
|
return ({
|
||||||
avatar: {
|
|
||||||
color: themeType === "light" ? theme.palette.common.black : theme.palette.common.white,
|
|
||||||
backgroundColor: "transparent",
|
|
||||||
width: theme.spacing(5),
|
|
||||||
height: theme.spacing(5),
|
|
||||||
border: "1px solid",
|
|
||||||
borderColor: theme.palette.divider
|
|
||||||
},
|
|
||||||
active: {
|
active: {
|
||||||
color: theme.palette.getContrastText(theme.palette.secondary.main),
|
color: theme.palette.getContrastText(theme.palette.secondary.main),
|
||||||
backgroundColor: theme.palette.secondary.main,
|
backgroundColor: theme.palette.secondary.main,
|
||||||
|
|
@ -64,6 +52,22 @@ const useStyles = makeStyles((theme: Theme) => {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
fab: {
|
fab: {
|
||||||
|
zIndex: 20,
|
||||||
|
background: theme.palette.primary.main,
|
||||||
|
"&:hover": {
|
||||||
|
background: theme.palette.primary.main
|
||||||
|
},
|
||||||
|
"&:focus": {
|
||||||
|
background: theme.palette.primary.main
|
||||||
|
},
|
||||||
|
position: "absolute",
|
||||||
|
bottom: theme.spacing(8), //for mobile navigator
|
||||||
|
right: theme.spacing(2),
|
||||||
|
[theme.breakpoints.up("sm")]: {
|
||||||
|
bottom: theme.spacing(2)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
overlayFab: {
|
||||||
zIndex: 20,
|
zIndex: 20,
|
||||||
background: theme.palette.primary.main,
|
background: theme.palette.primary.main,
|
||||||
"&:hover": {
|
"&:hover": {
|
||||||
|
|
@ -83,19 +87,19 @@ const useStyles = makeStyles((theme: Theme) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
export default function TaskViewer(props: ViewProps) {
|
export default function TaskViewer(props: ViewProps) {
|
||||||
const { objectKey, label, drawerView, loadKeys } = props;
|
const { objectKey, label, drawerView, loadKeys, overlayButton } = props;
|
||||||
const [{ user }] = useGlobalState();
|
const [{ user, as }] = useGlobalState();
|
||||||
const [{ as }] = useGlobalState();
|
//const [{ as }] = useGlobalState();
|
||||||
const taskAPI = useTaskAPI();
|
const taskAPI = useTaskAPI();
|
||||||
const { openSnack } = useSnackbar();
|
const { openSnack } = useSnackbar();
|
||||||
const [tasks, setTasks] = useState<Map<string, Task>>(new Map<string, Task>());
|
const [tasks, setTasks] = useState<Map<string, Task>>(new Map<string, Task>([]));
|
||||||
const [viewTask, setViewTask] = useState<Task>(Task.create());
|
const [viewTask, setViewTask] = useState<Task>();
|
||||||
const [newTaskDialog, setNewTaskDialog] = useState(false);
|
const [newTaskDialog, setNewTaskDialog] = useState(false);
|
||||||
const [editTask, setEditTask] = useState<Task>();
|
const [editTask, setEditTask] = useState<Task>();
|
||||||
const [loaded, setLoaded] = useState(false);
|
const [loaded, setLoaded] = useState(false);
|
||||||
const [currentDate, setCurrentDate] = useState(new Date());
|
const [currentDate, setCurrentDate] = useState(new Date());
|
||||||
const isMobile = useMobile();
|
const isMobile = useMobile();
|
||||||
const [value, setValue] = React.useState(0);
|
const [value, setValue] = useState(0);
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
const [expand, setExpand] = useState(false);
|
const [expand, setExpand] = useState(false);
|
||||||
const [hideCal, setHideCal] = useState(false);
|
const [hideCal, setHideCal] = useState(false);
|
||||||
|
|
@ -181,6 +185,7 @@ export default function TaskViewer(props: ViewProps) {
|
||||||
setLoaded(true);
|
setLoaded(true);
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
|
console.log(err)
|
||||||
openSnack("Failed to load tasks");
|
openSnack("Failed to load tasks");
|
||||||
});
|
});
|
||||||
}, [taskAPI, as, user, openSnack, nextMonth, prevMonth]);
|
}, [taskAPI, as, user, openSnack, nextMonth, prevMonth]);
|
||||||
|
|
@ -221,33 +226,6 @@ export default function TaskViewer(props: ViewProps) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const tabIcons = () => {
|
|
||||||
return (
|
|
||||||
<Box display="flex" justifyContent="left" alignItems="center" zIndex={5}>
|
|
||||||
<IconButton
|
|
||||||
className={value === 0 ? classes.active : classes.avatar}
|
|
||||||
component="span"
|
|
||||||
style={{
|
|
||||||
marginBottom: "0.75rem",
|
|
||||||
marginRight: "0.5rem"
|
|
||||||
}}
|
|
||||||
onClick={() => setValue(0)}>
|
|
||||||
<CalendarToday />
|
|
||||||
</IconButton>
|
|
||||||
<IconButton
|
|
||||||
className={value === 1 ? classes.active : classes.avatar}
|
|
||||||
component="span"
|
|
||||||
style={{
|
|
||||||
marginBottom: "0.75rem",
|
|
||||||
marginRight: "0.5rem"
|
|
||||||
}}
|
|
||||||
onClick={() => setValue(1)}>
|
|
||||||
<NotesIcon />
|
|
||||||
</IconButton>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
function TabPanelMine(props: TabPanelProps) {
|
function TabPanelMine(props: TabPanelProps) {
|
||||||
const { children, value, index, ...other } = props;
|
const { children, value, index, ...other } = props;
|
||||||
|
|
||||||
|
|
@ -271,15 +249,7 @@ export default function TaskViewer(props: ViewProps) {
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
{isMobile || drawerView ? (
|
{isMobile || drawerView ? (
|
||||||
<Box>
|
<Box height={"100%"} position={"relative"}>
|
||||||
<Fab
|
|
||||||
onClick={openDialog}
|
|
||||||
aria-label="Create Task"
|
|
||||||
className={classes.fab}
|
|
||||||
size={isMobile ? "medium" : "large"}>
|
|
||||||
<AddIcon />
|
|
||||||
</Fab>
|
|
||||||
{!drawerView && tabIcons()}
|
|
||||||
<TabPanelMine value={value} index={0}>
|
<TabPanelMine value={value} index={0}>
|
||||||
<Box paddingBottom={2}>
|
<Box paddingBottom={2}>
|
||||||
<TaskCalendar
|
<TaskCalendar
|
||||||
|
|
@ -352,6 +322,13 @@ export default function TaskViewer(props: ViewProps) {
|
||||||
openTask={(taskId: string) => openSelectedTask(taskId)}
|
openTask={(taskId: string) => openSelectedTask(taskId)}
|
||||||
/>
|
/>
|
||||||
</TabPanelMine>
|
</TabPanelMine>
|
||||||
|
<Fab
|
||||||
|
onClick={openDialog}
|
||||||
|
aria-label="Create Task"
|
||||||
|
className={overlayButton ? classes.overlayFab : classes.fab}
|
||||||
|
size={isMobile ? "medium" : "large"}>
|
||||||
|
<AddIcon />
|
||||||
|
</Fab>
|
||||||
</Box>
|
</Box>
|
||||||
) : (
|
) : (
|
||||||
<Grid container direction="row" alignContent="center" alignItems="center">
|
<Grid container direction="row" alignContent="center" alignItems="center">
|
||||||
|
|
@ -441,7 +418,7 @@ export default function TaskViewer(props: ViewProps) {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box height={"100%"}>
|
||||||
{!loaded ? <LinearProgress style={{ marginTop: 20 }} /> : viewer()}
|
{!loaded ? <LinearProgress style={{ marginTop: 20 }} /> : viewer()}
|
||||||
<TaskSettings
|
<TaskSettings
|
||||||
open={newTaskDialog}
|
open={newTaskDialog}
|
||||||
|
|
@ -460,7 +437,7 @@ export default function TaskViewer(props: ViewProps) {
|
||||||
setNewTaskDialog(false);
|
setNewTaskDialog(false);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{
|
{viewTask &&
|
||||||
<TaskDrawer
|
<TaskDrawer
|
||||||
task={viewTask}
|
task={viewTask}
|
||||||
open={openDrawer}
|
open={openDrawer}
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,8 @@ export default defineConfig({
|
||||||
VitePWA({
|
VitePWA({
|
||||||
registerType: 'autoUpdate', // Automatically updates the service worker when a new version is available
|
registerType: 'autoUpdate', // Automatically updates the service worker when a new version is available
|
||||||
workbox: {
|
workbox: {
|
||||||
|
skipWaiting: true, //makes the new worker call skip waiting
|
||||||
|
clientsClaim: true, //makes it claim control of clients as soon as it activates
|
||||||
globPatterns: ['**/*.{js,css,html,png,jpg,svg}'], // Cache all common asset types
|
globPatterns: ['**/*.{js,css,html,png,jpg,svg}'], // Cache all common asset types
|
||||||
maximumFileSizeToCacheInBytes: 20 * 1024 * 1024, // 20 MiB
|
maximumFileSizeToCacheInBytes: 20 * 1024 * 1024, // 20 MiB
|
||||||
cleanupOutdatedCaches: true,
|
cleanupOutdatedCaches: true,
|
||||||
|
|
@ -32,6 +34,14 @@ export default defineConfig({
|
||||||
cacheName: 'assets-cache',
|
cacheName: 'assets-cache',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
//this forces all api calls to go through the network
|
||||||
|
urlPattern: ({ url }) => url.pathname.startsWith('/v1/'),
|
||||||
|
handler: 'NetworkOnly',
|
||||||
|
options: {
|
||||||
|
cacheName: 'api-cache',
|
||||||
|
},
|
||||||
|
},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue