added the cable estimator and the pdf stuff that goes with it
This commit is contained in:
parent
9074120f74
commit
e039a75bf8
26 changed files with 3157 additions and 24 deletions
486
package-lock.json
generated
486
package-lock.json
generated
|
|
@ -24,6 +24,7 @@
|
|||
"@mui/material": "^6.1.6",
|
||||
"@mui/styles": "^6.1.6",
|
||||
"@mui/x-date-pickers": "^7.26.0",
|
||||
"@react-pdf/renderer": "^4.3.0",
|
||||
"@sentry/react": "^8.38.0",
|
||||
"@turf/area": "^7.2.0",
|
||||
"@turf/turf": "^7.2.0",
|
||||
|
|
@ -3216,6 +3217,180 @@
|
|||
"resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz",
|
||||
"integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw=="
|
||||
},
|
||||
"node_modules/@react-pdf/fns": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@react-pdf/fns/-/fns-3.1.2.tgz",
|
||||
"integrity": "sha512-qTKGUf0iAMGg2+OsUcp9ffKnKi41RukM/zYIWMDJ4hRVYSr89Q7e3wSDW/Koqx3ea3Uy/z3h2y3wPX6Bdfxk6g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@react-pdf/font": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@react-pdf/font/-/font-4.0.2.tgz",
|
||||
"integrity": "sha512-/dAWu7Y2RD1RxarDZ9SkYPHgBYOhmcDnet4W/qN/m8k+A2Hr3ja54GymSR7GGxWBtxjKtNauVKrTa9LS1n8WUw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@react-pdf/pdfkit": "^4.0.3",
|
||||
"@react-pdf/types": "^2.9.0",
|
||||
"fontkit": "^2.0.2",
|
||||
"is-url": "^1.2.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-pdf/image": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@react-pdf/image/-/image-3.0.3.tgz",
|
||||
"integrity": "sha512-lvP5ryzYM3wpbO9bvqLZYwEr5XBDX9jcaRICvtnoRqdJOo7PRrMnmB4MMScyb+Xw10mGeIubZAAomNAG5ONQZQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@react-pdf/png-js": "^3.0.0",
|
||||
"jay-peg": "^1.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-pdf/layout": {
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@react-pdf/layout/-/layout-4.4.0.tgz",
|
||||
"integrity": "sha512-Aq+Cc6JYausWLoks2FvHe3PwK9cTuvksB2uJ0AnkKJEUtQbvCq8eCRb1bjbbwIji9OzFRTTzZij7LzkpKHjIeA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@react-pdf/fns": "3.1.2",
|
||||
"@react-pdf/image": "^3.0.3",
|
||||
"@react-pdf/primitives": "^4.1.1",
|
||||
"@react-pdf/stylesheet": "^6.1.0",
|
||||
"@react-pdf/textkit": "^6.0.0",
|
||||
"@react-pdf/types": "^2.9.0",
|
||||
"emoji-regex": "^10.3.0",
|
||||
"queue": "^6.0.1",
|
||||
"yoga-layout": "^3.2.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-pdf/pdfkit": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@react-pdf/pdfkit/-/pdfkit-4.0.3.tgz",
|
||||
"integrity": "sha512-k+Lsuq8vTwWsCqTp+CCB4+2N+sOTFrzwGA7aw3H9ix/PDWR9QksbmNg0YkzGbLAPI6CeawmiLHcf4trZ5ecLPQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@react-pdf/png-js": "^3.0.0",
|
||||
"browserify-zlib": "^0.2.0",
|
||||
"crypto-js": "^4.2.0",
|
||||
"fontkit": "^2.0.2",
|
||||
"jay-peg": "^1.1.1",
|
||||
"linebreak": "^1.1.0",
|
||||
"vite-compatible-readable-stream": "^3.6.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-pdf/png-js": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@react-pdf/png-js/-/png-js-3.0.0.tgz",
|
||||
"integrity": "sha512-eSJnEItZ37WPt6Qv5pncQDxLJRK15eaRwPT+gZoujP548CodenOVp49GST8XJvKMFt9YqIBzGBV/j9AgrOQzVA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"browserify-zlib": "^0.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-pdf/primitives": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@react-pdf/primitives/-/primitives-4.1.1.tgz",
|
||||
"integrity": "sha512-IuhxYls1luJb7NUWy6q5avb1XrNaVj9bTNI40U9qGRuS6n7Hje/8H8Qi99Z9UKFV74bBP3DOf3L1wV2qZVgVrQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@react-pdf/reconciler": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@react-pdf/reconciler/-/reconciler-1.1.4.tgz",
|
||||
"integrity": "sha512-oTQDiR/t4Z/Guxac88IavpU2UgN7eR0RMI9DRKvKnvPz2DUasGjXfChAdMqDNmJJxxV26mMy9xQOUV2UU5/okg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"object-assign": "^4.1.1",
|
||||
"scheduler": "0.25.0-rc-603e6108-20241029"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-pdf/reconciler/node_modules/scheduler": {
|
||||
"version": "0.25.0-rc-603e6108-20241029",
|
||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0-rc-603e6108-20241029.tgz",
|
||||
"integrity": "sha512-pFwF6H1XrSdYYNLfOcGlM28/j8CGLu8IvdrxqhjWULe2bPcKiKW4CV+OWqR/9fT52mywx65l7ysNkjLKBda7eA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@react-pdf/render": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@react-pdf/render/-/render-4.3.0.tgz",
|
||||
"integrity": "sha512-MdWfWaqO6d7SZD75TZ2z5L35V+cHpyA43YNRlJNG0RJ7/MeVGDQv12y/BXOJgonZKkeEGdzM3EpAt9/g4E22WA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@react-pdf/fns": "3.1.2",
|
||||
"@react-pdf/primitives": "^4.1.1",
|
||||
"@react-pdf/textkit": "^6.0.0",
|
||||
"@react-pdf/types": "^2.9.0",
|
||||
"abs-svg-path": "^0.1.1",
|
||||
"color-string": "^1.9.1",
|
||||
"normalize-svg-path": "^1.1.0",
|
||||
"parse-svg-path": "^0.1.2",
|
||||
"svg-arc-to-cubic-bezier": "^3.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-pdf/renderer": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@react-pdf/renderer/-/renderer-4.3.0.tgz",
|
||||
"integrity": "sha512-28gpA69fU9ZQrDzmd5xMJa1bDf8t0PT3ApUKBl2PUpoE/x4JlvCB5X66nMXrfFrgF2EZrA72zWQAkvbg7TE8zw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@react-pdf/fns": "3.1.2",
|
||||
"@react-pdf/font": "^4.0.2",
|
||||
"@react-pdf/layout": "^4.4.0",
|
||||
"@react-pdf/pdfkit": "^4.0.3",
|
||||
"@react-pdf/primitives": "^4.1.1",
|
||||
"@react-pdf/reconciler": "^1.1.4",
|
||||
"@react-pdf/render": "^4.3.0",
|
||||
"@react-pdf/types": "^2.9.0",
|
||||
"events": "^3.3.0",
|
||||
"object-assign": "^4.1.1",
|
||||
"prop-types": "^15.6.2",
|
||||
"queue": "^6.0.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-pdf/stylesheet": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@react-pdf/stylesheet/-/stylesheet-6.1.0.tgz",
|
||||
"integrity": "sha512-BGZ2sYNUp38VJUegjva/jsri3iiRGnVNjWI+G9dTwAvLNOmwFvSJzqaCsEnqQ/DW5mrTBk/577FhDY7pv6AidA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@react-pdf/fns": "3.1.2",
|
||||
"@react-pdf/types": "^2.9.0",
|
||||
"color-string": "^1.9.1",
|
||||
"hsl-to-hex": "^1.0.0",
|
||||
"media-engine": "^1.0.3",
|
||||
"postcss-value-parser": "^4.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-pdf/textkit": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@react-pdf/textkit/-/textkit-6.0.0.tgz",
|
||||
"integrity": "sha512-fDt19KWaJRK/n2AaFoVm31hgGmpygmTV7LsHGJNGZkgzXcFyLsx+XUl63DTDPH3iqxj3xUX128t104GtOz8tTw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@react-pdf/fns": "3.1.2",
|
||||
"bidi-js": "^1.0.2",
|
||||
"hyphen": "^1.6.4",
|
||||
"unicode-properties": "^1.4.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-pdf/types": {
|
||||
"version": "2.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@react-pdf/types/-/types-2.9.0.tgz",
|
||||
"integrity": "sha512-ckj80vZLlvl9oYrQ4tovEaqKWP3O06Eb1D48/jQWbdwz1Yh7Y9v1cEmwlP8ET+a1Whp8xfdM0xduMexkuPANCQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@react-pdf/font": "^4.0.2",
|
||||
"@react-pdf/primitives": "^4.1.1",
|
||||
"@react-pdf/stylesheet": "^6.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@remix-run/router": {
|
||||
"version": "1.21.0",
|
||||
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.21.0.tgz",
|
||||
|
|
@ -3735,6 +3910,15 @@
|
|||
"sourcemap-codec": "^1.4.8"
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/helpers": {
|
||||
"version": "0.5.15",
|
||||
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
|
||||
"integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"tslib": "^2.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@szmarczak/http-timer": {
|
||||
"version": "4.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz",
|
||||
|
|
@ -6597,6 +6781,12 @@
|
|||
"url": "https://opencollective.com/vitest"
|
||||
}
|
||||
},
|
||||
"node_modules/abs-svg-path": {
|
||||
"version": "0.1.1",
|
||||
"resolved": "https://registry.npmjs.org/abs-svg-path/-/abs-svg-path-0.1.1.tgz",
|
||||
"integrity": "sha512-d8XPSGjfyzlXC3Xx891DJRyZfqk5JU0BJrDQcsWomFIV1/BIzPW5HDH5iDdWpqWaav0YVIEzT1RHTwWr0FFshA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
"version": "8.14.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
|
||||
|
|
@ -6858,6 +7048,35 @@
|
|||
"resolved": "https://registry.npmjs.org/base-64/-/base-64-0.1.0.tgz",
|
||||
"integrity": "sha512-Y5gU45svrR5tI2Vt/X9GPd3L0HNIKzGu202EjxrXMpuc2V2CiKgemAbUUsqYmZJvPtCXoUKjNZwBJzsNScUbXA=="
|
||||
},
|
||||
"node_modules/base64-js": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
|
||||
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/bidi-js": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz",
|
||||
"integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"require-from-string": "^2.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/bignumber.js": {
|
||||
"version": "9.1.2",
|
||||
"resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz",
|
||||
|
|
@ -6889,6 +7108,24 @@
|
|||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/brotli": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/brotli/-/brotli-1.3.3.tgz",
|
||||
"integrity": "sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"base64-js": "^1.1.2"
|
||||
}
|
||||
},
|
||||
"node_modules/browserify-zlib": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz",
|
||||
"integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"pako": "~1.0.5"
|
||||
}
|
||||
},
|
||||
"node_modules/browserslist": {
|
||||
"version": "4.24.4",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz",
|
||||
|
|
@ -7153,6 +7390,15 @@
|
|||
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz",
|
||||
"integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow=="
|
||||
},
|
||||
"node_modules/clone": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz",
|
||||
"integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/clone-response": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz",
|
||||
|
|
@ -7190,9 +7436,18 @@
|
|||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/color-string": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
|
||||
"integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"color-name": "^1.0.0",
|
||||
"simple-swizzle": "^0.2.2"
|
||||
}
|
||||
},
|
||||
"node_modules/combined-stream": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
|
|
@ -7302,6 +7557,12 @@
|
|||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/crypto-js": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz",
|
||||
"integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/crypto-random-string": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz",
|
||||
|
|
@ -7759,6 +8020,12 @@
|
|||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/dfa": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/dfa/-/dfa-1.2.0.tgz",
|
||||
"integrity": "sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/doc-path": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/doc-path/-/doc-path-4.1.1.tgz",
|
||||
|
|
@ -7820,6 +8087,12 @@
|
|||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/emoji-regex": {
|
||||
"version": "10.4.0",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz",
|
||||
"integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/end-of-stream": {
|
||||
"version": "1.4.4",
|
||||
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
|
||||
|
|
@ -8443,6 +8716,23 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/fontkit": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/fontkit/-/fontkit-2.0.4.tgz",
|
||||
"integrity": "sha512-syetQadaUEDNdxdugga9CpEYVaQIxOwk7GlwZWWZ19//qW4zE5bknOKeMBDYAASwnpaSHKJITRLMF9m1fp3s6g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@swc/helpers": "^0.5.12",
|
||||
"brotli": "^1.3.2",
|
||||
"clone": "^2.1.2",
|
||||
"dfa": "^1.2.0",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"restructure": "^3.0.0",
|
||||
"tiny-inflate": "^1.0.3",
|
||||
"unicode-properties": "^1.4.0",
|
||||
"unicode-trie": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/for-each": {
|
||||
"version": "0.3.5",
|
||||
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz",
|
||||
|
|
@ -9012,6 +9302,21 @@
|
|||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/hsl-to-hex": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/hsl-to-hex/-/hsl-to-hex-1.0.0.tgz",
|
||||
"integrity": "sha512-K6GVpucS5wFf44X0h2bLVRDsycgJmf9FF2elg+CrqD8GcFU8c6vYhgXn8NjUkFCwj+xDFb70qgLbTUm6sxwPmA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"hsl-to-rgb-for-reals": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/hsl-to-rgb-for-reals": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/hsl-to-rgb-for-reals/-/hsl-to-rgb-for-reals-1.1.1.tgz",
|
||||
"integrity": "sha512-LgOWAkrN0rFaQpfdWBQlv/VhkOxb5AsBjk6NQVx4yEzWS923T07X0M1Y0VNko2H52HeSpZrZNNMJ0aFqsdVzQg==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/http-cache-semantics": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz",
|
||||
|
|
@ -9031,6 +9336,12 @@
|
|||
"node": ">=10.19.0"
|
||||
}
|
||||
},
|
||||
"node_modules/hyphen": {
|
||||
"version": "1.10.6",
|
||||
"resolved": "https://registry.npmjs.org/hyphen/-/hyphen-1.10.6.tgz",
|
||||
"integrity": "sha512-fXHXcGFTXOvZTSkPJuGOQf5Lv5T/R2itiiCVPg9LxAje5D00O0pP83yJShFq5V89Ly//Gt6acj7z8pbBr34stw==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/hyphenate-style-name": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.1.0.tgz",
|
||||
|
|
@ -9121,7 +9432,6 @@
|
|||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/internal-slot": {
|
||||
|
|
@ -9561,6 +9871,12 @@
|
|||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/is-url": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz",
|
||||
"integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/is-weakmap": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz",
|
||||
|
|
@ -9648,6 +9964,15 @@
|
|||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/jay-peg": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/jay-peg/-/jay-peg-1.1.1.tgz",
|
||||
"integrity": "sha512-D62KEuBxz/ip2gQKOEhk/mx14o7eiFRaU+VNNSP4MOiIkwb/D6B3G1Mfas7C/Fit8EsSV2/IWjZElx/Gs6A4ww==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"restructure": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/js-tokens": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
|
|
@ -9907,6 +10232,25 @@
|
|||
"resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.11.15.tgz",
|
||||
"integrity": "sha512-M7+rtYi9l5RvMmHyjyoF3BHHUpXTYdJ0PezZGHNs0GyW1lO+K7jxlXpbdIb7a56h0nqLYdjIw+E+z0ciGaJP7g=="
|
||||
},
|
||||
"node_modules/linebreak": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/linebreak/-/linebreak-1.1.0.tgz",
|
||||
"integrity": "sha512-MHp03UImeVhB7XZtjd0E4n6+3xr5Dq/9xI/5FptGk5FrbDR3zagPa2DS6U8ks/3HjbKWG9Q1M2ufOzxV2qLYSQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"base64-js": "0.0.8",
|
||||
"unicode-trie": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/linebreak/node_modules/base64-js": {
|
||||
"version": "0.0.8",
|
||||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-0.0.8.tgz",
|
||||
"integrity": "sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/lines-and-columns": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
|
||||
|
|
@ -10112,6 +10456,12 @@
|
|||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/media-engine": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/media-engine/-/media-engine-1.0.3.tgz",
|
||||
"integrity": "sha512-aa5tG6sDoK+k70B9iEX1NeyfT8ObCKhNDs6lJVpwF6r8vhUfuKMslIcirq6HIUYuuUYLefcEQOn9bSBOvawtwg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/memoize-one": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz",
|
||||
|
|
@ -10416,6 +10766,15 @@
|
|||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/normalize-svg-path": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/normalize-svg-path/-/normalize-svg-path-1.1.0.tgz",
|
||||
"integrity": "sha512-r9KHKG2UUeB5LoTouwDzBy2VxXlHsiM6fyLQvnJa0S5hrhzqElH/CH7TUGhT1fVvIYBIKf3OpY4YJ4CK+iaqHg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"svg-arc-to-cubic-bezier": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/normalize-url": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz",
|
||||
|
|
@ -10609,6 +10968,12 @@
|
|||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/pako": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
|
||||
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
|
||||
"license": "(MIT AND Zlib)"
|
||||
},
|
||||
"node_modules/parent-module": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
|
||||
|
|
@ -10637,6 +11002,12 @@
|
|||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/parse-svg-path": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/parse-svg-path/-/parse-svg-path-0.1.2.tgz",
|
||||
"integrity": "sha512-JyPSBnkTJ0AI8GGJLfMXvKq42cj5c006fnLz6fXy6zfoVjJizi8BNTpu8on8ziI1cKy9d9DGNuY17Ce7wuejpQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/path-exists": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
|
||||
|
|
@ -10805,6 +11176,12 @@
|
|||
"node": "^10 || ^12 || >=14"
|
||||
}
|
||||
},
|
||||
"node_modules/postcss-value-parser": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
|
||||
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/potpack": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/potpack/-/potpack-2.0.0.tgz",
|
||||
|
|
@ -10920,6 +11297,15 @@
|
|||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/queue": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz",
|
||||
"integrity": "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"inherits": "~2.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/queue-microtask": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
||||
|
|
@ -11712,7 +12098,6 @@
|
|||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
|
||||
"integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
|
|
@ -11769,6 +12154,12 @@
|
|||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/restructure": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/restructure/-/restructure-3.0.2.tgz",
|
||||
"integrity": "sha512-gSfoiOEA0VPE6Tukkrr7I0RBdE0s7H1eFCDBk05l1KIQT1UIKNc5JZy6jdyW6eYH3aR3g5b3PuL77rq0hvwtAw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/reusify": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
|
||||
|
|
@ -11877,7 +12268,6 @@
|
|||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
|
|
@ -12153,6 +12543,21 @@
|
|||
"integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/simple-swizzle": {
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
|
||||
"integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-arrayish": "^0.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/simple-swizzle/node_modules/is-arrayish": {
|
||||
"version": "0.3.2",
|
||||
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
|
||||
"integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/skmeans": {
|
||||
"version": "0.9.7",
|
||||
"resolved": "https://registry.npmjs.org/skmeans/-/skmeans-0.9.7.tgz",
|
||||
|
|
@ -12345,6 +12750,15 @@
|
|||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/string_decoder": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
|
||||
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safe-buffer": "~5.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/string.prototype.matchall": {
|
||||
"version": "4.0.12",
|
||||
"resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz",
|
||||
|
|
@ -12535,6 +12949,12 @@
|
|||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/svg-arc-to-cubic-bezier": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/svg-arc-to-cubic-bezier/-/svg-arc-to-cubic-bezier-3.2.0.tgz",
|
||||
"integrity": "sha512-djbJ/vZKZO+gPoSDThGNpKDO+o+bAeA4XQKovvkNCqnIS2t+S4qnLAGQhyyrulhCFRl1WWzAp0wUDV8PpTVU3g==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/sweepline-intersections": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/sweepline-intersections/-/sweepline-intersections-1.5.0.tgz",
|
||||
|
|
@ -12598,6 +13018,12 @@
|
|||
"integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/tiny-inflate": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz",
|
||||
"integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tiny-invariant": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz",
|
||||
|
|
@ -13024,6 +13450,16 @@
|
|||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/unicode-properties": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/unicode-properties/-/unicode-properties-1.4.1.tgz",
|
||||
"integrity": "sha512-CLjCCLQ6UuMxWnbIylkisbRj31qxHPAurvena/0iwSVbQ2G1VY5/HjV0IRabOEbDHlzZlRdCrD4NhB0JtU40Pg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"base64-js": "^1.3.0",
|
||||
"unicode-trie": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/unicode-property-aliases-ecmascript": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz",
|
||||
|
|
@ -13034,6 +13470,22 @@
|
|||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/unicode-trie": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unicode-trie/-/unicode-trie-2.0.0.tgz",
|
||||
"integrity": "sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"pako": "^0.2.5",
|
||||
"tiny-inflate": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/unicode-trie/node_modules/pako": {
|
||||
"version": "0.2.9",
|
||||
"resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz",
|
||||
"integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/union-value": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz",
|
||||
|
|
@ -13131,6 +13583,12 @@
|
|||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/validate-npm-package-license": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
|
||||
|
|
@ -13739,6 +14197,20 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/vite-compatible-readable-stream": {
|
||||
"version": "3.6.1",
|
||||
"resolved": "https://registry.npmjs.org/vite-compatible-readable-stream/-/vite-compatible-readable-stream-3.6.1.tgz",
|
||||
"integrity": "sha512-t20zYkrSf868+j/p31cRIGN28Phrjm3nRSLR2fyc2tiWi4cZGVdv68yNlwnIINTkMTmPoMiSlc0OadaO7DXZaQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"inherits": "^2.0.3",
|
||||
"string_decoder": "^1.1.1",
|
||||
"util-deprecate": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/vite-node": {
|
||||
"version": "3.0.7",
|
||||
"resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.0.7.tgz",
|
||||
|
|
@ -14478,6 +14950,12 @@
|
|||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/yoga-layout": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/yoga-layout/-/yoga-layout-3.2.1.tgz",
|
||||
"integrity": "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ==",
|
||||
"license": "MIT"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@
|
|||
"@mui/material": "^6.1.6",
|
||||
"@mui/styles": "^6.1.6",
|
||||
"@mui/x-date-pickers": "^7.26.0",
|
||||
"@react-pdf/renderer": "^4.3.0",
|
||||
"@sentry/react": "^8.38.0",
|
||||
"@turf/area": "^7.2.0",
|
||||
"@turf/turf": "^7.2.0",
|
||||
|
|
|
|||
373
src/cableEstimator/binSVGs/sideView.tsx
Normal file
373
src/cableEstimator/binSVGs/sideView.tsx
Normal file
|
|
@ -0,0 +1,373 @@
|
|||
import { colors } from "@mui/material";
|
||||
import { jsonBin } from "common/DataImports/BinCables/BinCableImporter";
|
||||
import { useEffect, useState } from "react";
|
||||
import { distanceConversion } from "utils";
|
||||
|
||||
interface Props {
|
||||
bin: jsonBin;
|
||||
}
|
||||
|
||||
interface svgPoint {
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
|
||||
export default function SideView(props: Props) {
|
||||
const svgViewBoxSize = 250;
|
||||
//percentage to of the svg to be the bin drawing from 0 to 1,
|
||||
//NOTE: this is the percentage based on the longest side
|
||||
//ie. if the bin is taller than it is wide the bin will be x percent of the height of the viewbox and the width will be scaled to the height and vice versa
|
||||
const scaleMultiplier = 0.8;
|
||||
const nodeSpacing = 4; //space in feet between the nodes
|
||||
const dimensionFontSize = 7;
|
||||
const gapSize = svgViewBoxSize * 0.05;
|
||||
const { bin } = props;
|
||||
const [scale, setScale] = useState(0); //the scale to multiply the bin dimension by in order to determine how long to draw the line
|
||||
const [hopperHeight, setHopperHeight] = useState(0);
|
||||
const [roofHeight, setRoofHeight] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
//get the peak height of the hopper
|
||||
let h = (bin.Diameter / 2) * Math.tan(bin.HopperAngle * (Math.PI / 180));
|
||||
setHopperHeight(h);
|
||||
setRoofHeight(bin.Peak - bin.Sidewall);
|
||||
|
||||
let heightscale = svgViewBoxSize / (bin.Peak + h);
|
||||
let widthscale = svgViewBoxSize / bin.Diameter;
|
||||
|
||||
//need to use the scale of the longer of the two dimensions
|
||||
setScale((heightscale < widthscale ? heightscale : widthscale) * scaleMultiplier);
|
||||
}, [bin, svgViewBoxSize]);
|
||||
|
||||
const binOutline = () => {
|
||||
let startX = svgViewBoxSize / 2;
|
||||
let startY = 0;
|
||||
//let paddingVal = (padding * svgViewBoxSize)
|
||||
|
||||
return (
|
||||
<path
|
||||
strokeWidth={2}
|
||||
stroke={colors.grey[400]}
|
||||
fillOpacity={0}
|
||||
d={
|
||||
"M " +
|
||||
startX +
|
||||
" " +
|
||||
startY + //starting point
|
||||
" L " +
|
||||
(startX + (bin.Diameter / 2) * scale) +
|
||||
" " +
|
||||
roofHeight * scale + //right side of the bin roof
|
||||
" V " +
|
||||
(roofHeight + bin.Sidewall) * scale + //right sidewall
|
||||
(hopperHeight === 0
|
||||
? " H " + (startX - (bin.Diameter / 2) * scale) //bin bottom if there is no hopper
|
||||
: //else
|
||||
" L " +
|
||||
startX +
|
||||
" " +
|
||||
(roofHeight + hopperHeight + bin.Sidewall) * scale + //right side of the hopper
|
||||
" L " +
|
||||
(startX - (bin.Diameter / 2) * scale) +
|
||||
" " +
|
||||
(roofHeight + bin.Sidewall) * scale) + //left side of the hopper
|
||||
" V " +
|
||||
roofHeight * scale + //left sidewall
|
||||
" Z " //left side of the bin roof (Z command draws the line from where you currently are back to the startign point)
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const cableLines = () => {
|
||||
let cables: JSX.Element[] = [];
|
||||
//let paddingVal = (padding * svgViewBoxSize)
|
||||
|
||||
// line for the roof at the top of the sidewall
|
||||
// cables.push(
|
||||
// <line
|
||||
// key={"roof"}
|
||||
// strokeWidth={2}
|
||||
// stroke="yellow"
|
||||
// x1={0}
|
||||
// strokeDasharray={3}
|
||||
// x2={svgViewBoxSize}
|
||||
// y1={roofHeight*scale + paddingVal}
|
||||
// y2={roofHeight*scale + paddingVal}
|
||||
// />
|
||||
// )
|
||||
|
||||
bin.CableSums.forEach(sum => {
|
||||
let cableLength = 0;
|
||||
bin.Cables.forEach(c => {
|
||||
if (sum.Orbit === c.Orbit) {
|
||||
//numNodes = c.Nodes
|
||||
cableLength = c.Length;
|
||||
}
|
||||
});
|
||||
let numNodes = Math.ceil(cableLength / nodeSpacing);
|
||||
|
||||
let xVal = svgViewBoxSize / 2 + sum.DistanceFromCenter * scale;
|
||||
let cableStart = sum.DistanceFromCenter * Math.tan(bin.RoofAngle * (Math.PI / 180)) * scale;
|
||||
//need to offset the padding to the top since we are now below the half mark as well as pad the bottom
|
||||
let cableEnd = cableStart + cableLength * scale;
|
||||
|
||||
cables.push(
|
||||
<line
|
||||
key={"orbit" + sum.Orbit}
|
||||
strokeWidth={2}
|
||||
stroke="yellow"
|
||||
x1={xVal}
|
||||
y1={cableStart}
|
||||
x2={xVal}
|
||||
y2={cableEnd}
|
||||
/>
|
||||
);
|
||||
|
||||
let svgSpacing = (cableEnd - cableStart) / numNodes;
|
||||
|
||||
for (let i = 0; i < numNodes; i++) {
|
||||
let nodeY = cableEnd - svgSpacing * i;
|
||||
cables.push(
|
||||
<circle key={"orbit" + sum.Orbit + "node" + i} cx={xVal} cy={nodeY} r={2} fill="blue" />
|
||||
);
|
||||
}
|
||||
});
|
||||
return cables;
|
||||
};
|
||||
|
||||
const dimensionText = (key: string, x: number, y: number, text: string) => {
|
||||
return (
|
||||
<text
|
||||
key={key}
|
||||
style={{ fontSize: dimensionFontSize, fill: "white" }}
|
||||
x={x}
|
||||
y={y}
|
||||
textAnchor="middle"
|
||||
dominantBaseline={"middle"}>
|
||||
{text}
|
||||
</text>
|
||||
);
|
||||
};
|
||||
|
||||
const dimensionPath = (key: string, start: svgPoint, corner: svgPoint, end: svgPoint) => {
|
||||
return (
|
||||
//<line key={key} strokeWidth={1} stroke="white" x1={x1} x2={x2 ?? x1} y1={y1} y2={y2 ?? y1} />
|
||||
<path
|
||||
key={key}
|
||||
strokeWidth={1}
|
||||
stroke="white"
|
||||
fillOpacity={0}
|
||||
d={
|
||||
"M " +
|
||||
start.x +
|
||||
" " +
|
||||
start.y +
|
||||
"L " +
|
||||
corner.x +
|
||||
" " +
|
||||
corner.y +
|
||||
"L " +
|
||||
end.x +
|
||||
" " +
|
||||
end.y
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const bottomDiameter = () => {
|
||||
let d: JSX.Element[] = [];
|
||||
|
||||
let x1 = svgViewBoxSize / 2 - (bin.Diameter / 2) * scale;
|
||||
let x2 = svgViewBoxSize / 2 + (bin.Diameter / 2) * scale;
|
||||
|
||||
let y1 =
|
||||
(roofHeight + bin.Sidewall + hopperHeight) * scale +
|
||||
svgViewBoxSize * (1 - scaleMultiplier) * 0.1;
|
||||
let y2 =
|
||||
(roofHeight + bin.Sidewall + hopperHeight) * scale +
|
||||
svgViewBoxSize * (1 - scaleMultiplier) * 0.25;
|
||||
|
||||
d.push(
|
||||
dimensionText(
|
||||
"bottomText",
|
||||
svgViewBoxSize / 2,
|
||||
y2,
|
||||
distanceConversion(bin.Diameter).toFixed(1)
|
||||
)
|
||||
);
|
||||
|
||||
d.push(
|
||||
dimensionPath(
|
||||
"bottomDiameterLine1",
|
||||
{ x: x1, y: y1 },
|
||||
{ x: x1, y: y2 },
|
||||
{ x: svgViewBoxSize / 2 - gapSize, y: y2 }
|
||||
)
|
||||
);
|
||||
d.push(
|
||||
dimensionPath(
|
||||
"bottomDiameterLine2",
|
||||
{ x: svgViewBoxSize / 2 + gapSize, y: y2 },
|
||||
{ x: x2, y: y2 },
|
||||
{ x: x2, y: y1 }
|
||||
)
|
||||
);
|
||||
|
||||
return d;
|
||||
};
|
||||
|
||||
//height of the sidewall, on the left side - always used
|
||||
const sidwallDimension = () => {
|
||||
let d: JSX.Element[] = [];
|
||||
let centerX = svgViewBoxSize / 2;
|
||||
let x1 = centerX - (bin.Diameter / 2) * scale - svgViewBoxSize * (1 - scaleMultiplier) * 0.1;
|
||||
|
||||
let x2 = x1 - svgViewBoxSize * (1 - scaleMultiplier) * 0.15;
|
||||
let midpoint = (roofHeight + bin.Sidewall / 2) * scale;
|
||||
|
||||
let y1 = roofHeight * scale;
|
||||
let y2 = (roofHeight + bin.Sidewall) * scale;
|
||||
|
||||
d.push(
|
||||
dimensionText("sidewallText", x2, midpoint, distanceConversion(bin.Sidewall).toFixed(1))
|
||||
);
|
||||
d.push(
|
||||
dimensionPath(
|
||||
"sidwallTopLine",
|
||||
{ x: x1, y: y1 },
|
||||
{ x: x2, y: y1 },
|
||||
{ x: x2, y: midpoint - gapSize }
|
||||
)
|
||||
);
|
||||
d.push(
|
||||
dimensionPath(
|
||||
"sidewallbottomLine",
|
||||
{ x: x2, y: midpoint + gapSize },
|
||||
{ x: x2, y: y2 },
|
||||
{ x: x1, y: y2 }
|
||||
)
|
||||
);
|
||||
return d;
|
||||
};
|
||||
|
||||
//height of the bottom of the sidewall to the peak of the bin, on the left side - only used for hopper bins
|
||||
const peakDimension = () => {
|
||||
let d: JSX.Element[] = [];
|
||||
let centerX = svgViewBoxSize / 2;
|
||||
let xWall = centerX - (bin.Diameter / 2) * scale - svgViewBoxSize * (1 - scaleMultiplier) * 0.1;
|
||||
let xPeak = svgViewBoxSize / 2 - svgViewBoxSize * (1 - scaleMultiplier) * 0.1;
|
||||
|
||||
let xText = xWall - svgViewBoxSize * (1 - scaleMultiplier) * 0.5;
|
||||
let midpoint = ((roofHeight + bin.Sidewall) / 2) * scale;
|
||||
|
||||
let yTop = 0;
|
||||
let yBottom = (roofHeight + bin.Sidewall) * scale;
|
||||
|
||||
d.push(
|
||||
dimensionText(
|
||||
"peakText",
|
||||
xText,
|
||||
midpoint,
|
||||
distanceConversion(bin.Sidewall + roofHeight).toFixed(1)
|
||||
)
|
||||
);
|
||||
d.push(
|
||||
dimensionPath(
|
||||
"peakTopLine",
|
||||
{ x: xPeak, y: yTop },
|
||||
{ x: xText, y: yTop },
|
||||
{ x: xText, y: midpoint - gapSize }
|
||||
)
|
||||
);
|
||||
d.push(
|
||||
dimensionPath(
|
||||
"peakbottomLine",
|
||||
{ x: xText, y: midpoint + gapSize },
|
||||
{ x: xText, y: yBottom },
|
||||
{ x: xWall, y: yBottom }
|
||||
)
|
||||
);
|
||||
|
||||
return d;
|
||||
};
|
||||
|
||||
//total height of the bin, on the right side - always used
|
||||
const totalDimension = () => {
|
||||
let d: JSX.Element[] = [];
|
||||
let centerX = svgViewBoxSize / 2;
|
||||
//let paddingVal = (padding* svgViewBoxSize)
|
||||
let binRightX = centerX + (bin.Diameter / 2) * scale;
|
||||
|
||||
let xBinCenter = centerX + svgViewBoxSize * (1 - scaleMultiplier) * 0.1;
|
||||
let xBinWall = binRightX + svgViewBoxSize * (1 - scaleMultiplier) * 0.1;
|
||||
let x2 = binRightX + svgViewBoxSize * (1 - scaleMultiplier) * 0.2;
|
||||
let midpoint = ((roofHeight + bin.Sidewall + hopperHeight) / 2) * scale;
|
||||
|
||||
let y1 = 0;
|
||||
let y2 = (roofHeight + bin.Sidewall + hopperHeight) * scale;
|
||||
|
||||
d.push(
|
||||
dimensionText(
|
||||
"totalText",
|
||||
x2,
|
||||
midpoint,
|
||||
distanceConversion(roofHeight + bin.Sidewall + hopperHeight).toFixed(1)
|
||||
)
|
||||
);
|
||||
d.push(
|
||||
dimensionPath(
|
||||
"totalTopLine",
|
||||
{ x: xBinCenter, y: y1 },
|
||||
{ x: x2, y: y1 },
|
||||
{ x: x2, y: midpoint - gapSize }
|
||||
)
|
||||
);
|
||||
d.push(
|
||||
dimensionPath(
|
||||
"totalBottomLine",
|
||||
{ x: x2, y: midpoint + gapSize },
|
||||
{ x: x2, y: y2 },
|
||||
{ x: hopperHeight > 0 ? xBinCenter : xBinWall, y: y2 }
|
||||
)
|
||||
);
|
||||
|
||||
return d;
|
||||
};
|
||||
|
||||
const binDimensions = () => {
|
||||
let dimensions: JSX.Element[] = [];
|
||||
|
||||
dimensions.push(...bottomDiameter());
|
||||
// sidwall Height
|
||||
dimensions.push(...sidwallDimension());
|
||||
//total bin height
|
||||
dimensions.push(...totalDimension());
|
||||
if (hopperHeight > 0) {
|
||||
//show the height of the peak + sidwall without the hopper
|
||||
dimensions.push(...peakDimension());
|
||||
}
|
||||
|
||||
return dimensions;
|
||||
};
|
||||
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox={
|
||||
"0 " +
|
||||
(svgViewBoxSize * (scaleMultiplier - 1)) / 2 +
|
||||
" " +
|
||||
svgViewBoxSize +
|
||||
" " +
|
||||
svgViewBoxSize
|
||||
}
|
||||
height="100%"
|
||||
width="100%">
|
||||
{cableLines()}
|
||||
{binOutline()}
|
||||
{binDimensions()}
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
73
src/cableEstimator/binSVGs/topView.tsx
Normal file
73
src/cableEstimator/binSVGs/topView.tsx
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
import { colors } from "@mui/material";
|
||||
import { jsonBin } from "common/DataImports/BinCables/BinCableImporter";
|
||||
|
||||
interface Props {
|
||||
bin: jsonBin;
|
||||
}
|
||||
|
||||
export default function TopView(props: Props) {
|
||||
const { bin } = props;
|
||||
const totalRadius = 25;
|
||||
|
||||
const outerEdge = () => {
|
||||
return (
|
||||
<circle
|
||||
cx="50%"
|
||||
cy="50%"
|
||||
r={totalRadius}
|
||||
stroke={colors.grey[400]}
|
||||
strokeWidth="1"
|
||||
fillOpacity={0}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const orbits = () => {
|
||||
let o: JSX.Element[] = [];
|
||||
bin.CableSums.forEach((sum, i) => {
|
||||
//get the radius of the bin
|
||||
let binRadius = bin.Diameter / 2;
|
||||
//determine the percentage the orbit is from the center
|
||||
let perc = sum.DistanceFromCenter / binRadius;
|
||||
//use the percentage to find the radius for the svg
|
||||
let svgRadius = perc * totalRadius;
|
||||
o.push(
|
||||
<circle
|
||||
key={"orbit" + i}
|
||||
cx="50%"
|
||||
cy="50%"
|
||||
r={svgRadius}
|
||||
stroke={colors.grey[400]}
|
||||
strokeWidth="1"
|
||||
fillOpacity={0}
|
||||
/>
|
||||
);
|
||||
//then determine the points the cables will be
|
||||
let angleDist = 360 / sum.NumberOfCables;
|
||||
let currentAngle = 90;
|
||||
for (let k = 0; k < sum.NumberOfCables; k++) {
|
||||
let x = svgRadius * Math.sin(currentAngle * (Math.PI / 180));
|
||||
let y = svgRadius * Math.cos(currentAngle * (Math.PI / 180));
|
||||
o.push(
|
||||
<circle
|
||||
key={"orbit" + i + "cable" + k}
|
||||
r={1.5}
|
||||
cx="50%"
|
||||
cy="50%"
|
||||
transform={"translate(" + x + " " + y + ")"}
|
||||
fill={"blue"}
|
||||
/>
|
||||
);
|
||||
currentAngle += angleDist;
|
||||
}
|
||||
});
|
||||
return o;
|
||||
};
|
||||
|
||||
return (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 55 55" height="100%" width="100%">
|
||||
{outerEdge()}
|
||||
{orbits()}
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
74
src/cableEstimator/cableDisplay.tsx
Normal file
74
src/cableEstimator/cableDisplay.tsx
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
import { Box, Grid2 as Grid, Typography } from "@mui/material";
|
||||
import { jsonBin } from "common/DataImports/BinCables/BinCableImporter";
|
||||
import { useMobile } from "hooks";
|
||||
import { cloneDeep } from "lodash";
|
||||
import SideView from "./binSVGs/sideView";
|
||||
import TopView from "./binSVGs/topView";
|
||||
import CableMounting from "./cableMounting";
|
||||
import CableTable from "./cableTable";
|
||||
|
||||
interface Props {
|
||||
bin: jsonBin;
|
||||
updateBin: (newBin: jsonBin) => void;
|
||||
}
|
||||
|
||||
export default function CableDisplay(props: Props) {
|
||||
const { bin, updateBin } = props;
|
||||
const isMobile = useMobile();
|
||||
return (
|
||||
<Box padding={2}>
|
||||
<Typography style={{ fontSize: 20, fontWeight: 650 }}>
|
||||
{bin.Manufacturer + " " + bin.Model}
|
||||
</Typography>
|
||||
<Grid container direction="row" alignContent="center" alignItems="center" spacing={3}>
|
||||
<Grid size={isMobile ? 12 : 6}>
|
||||
<Box width="100%" paddingLeft={isMobile ? 0 : "15%"} paddingRight={isMobile ? 0 : "15%"}>
|
||||
<SideView bin={bin} />
|
||||
</Box>
|
||||
</Grid>
|
||||
<Grid size={isMobile ? 12 : 6} container direction="column">
|
||||
<Box width="100%">
|
||||
<Grid
|
||||
style={{ paddingLeft: isMobile ? 0 : "30%", paddingRight: isMobile ? 0 : "30%" }}>
|
||||
<TopView bin={bin} />
|
||||
</Grid>
|
||||
<Grid>
|
||||
<CableMounting bin={bin} />
|
||||
</Grid>
|
||||
</Box>
|
||||
</Grid>
|
||||
<Grid size={12}>
|
||||
<Box width="100%">
|
||||
<CableTable
|
||||
bin={bin}
|
||||
cablesChanged={newCables => {
|
||||
//update the json bin with the newly edited cables
|
||||
let newBin = cloneDeep(bin);
|
||||
//if the number of cables has changed will need to adjust the cable sums accordingly
|
||||
let newCounts: Map<number, number> = new Map();
|
||||
//loop through the cables to find the numbers for each orbit
|
||||
newCables.forEach(cable => {
|
||||
let currentOrbit = newCounts.get(cable.Orbit);
|
||||
if (currentOrbit) {
|
||||
newCounts.set(cable.Orbit, (currentOrbit + cable.Count) as number);
|
||||
} else {
|
||||
newCounts.set(cable.Orbit, cable.Count);
|
||||
}
|
||||
});
|
||||
//loop thorugh the sums setting the new counts for each orbit
|
||||
newBin.CableSums.forEach(sum => {
|
||||
let orbitCount = newCounts.get(sum.Orbit);
|
||||
if (orbitCount && orbitCount !== sum.NumberOfCables) {
|
||||
sum.NumberOfCables = orbitCount;
|
||||
}
|
||||
});
|
||||
newBin.Cables = newCables;
|
||||
updateBin(newBin);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
955
src/cableEstimator/cableEstimator.tsx
Normal file
955
src/cableEstimator/cableEstimator.tsx
Normal file
|
|
@ -0,0 +1,955 @@
|
|||
import { useEffect, useState } from "react";
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Card,
|
||||
FormControl,
|
||||
FormControlLabel,
|
||||
FormLabel,
|
||||
Grid2 as Grid,
|
||||
InputAdornment,
|
||||
Radio,
|
||||
RadioGroup,
|
||||
Slider,
|
||||
Switch,
|
||||
TextField,
|
||||
Typography
|
||||
} from "@mui/material";
|
||||
//import MaterialTable from "material-table";
|
||||
//import { getTableIcons } from "common/ResponsiveTable";
|
||||
import CableDisplay from "./cableDisplay";
|
||||
import { distanceConversion, getDistanceUnit } from "utils";
|
||||
import { pond } from "protobuf-ts/pond";
|
||||
import CableQuote from "./cableQuote";
|
||||
import { useMobile } from "hooks";
|
||||
import { Cable, CableSum, jsonBin } from "common/DataImports/BinCables/BinCableImporter";
|
||||
import BinSelector from "bin/BinSelector";
|
||||
import { cloneDeep } from "lodash";
|
||||
import {
|
||||
ConeVolume,
|
||||
CylinderVolume,
|
||||
RadiansToDegrees,
|
||||
TriangleOppositeLength
|
||||
} from "common/TrigFunctions";
|
||||
import ResponsiveTable, { Column } from "common/ResponsiveTable";
|
||||
import { makeStyles } from "@mui/styles";
|
||||
|
||||
const useStyles = makeStyles(() => ({
|
||||
sliderThumb: {
|
||||
// height: 15,
|
||||
// width: 15,
|
||||
marginTop: 0,
|
||||
marginLeft: 0,
|
||||
//backgroundColor: "yellow"
|
||||
},
|
||||
// sliderValLabel: {
|
||||
// left: "calc(-50%)",
|
||||
// top: 22,
|
||||
// "& *": {
|
||||
// background: "transparent",
|
||||
// color: "#fff"
|
||||
// }
|
||||
// }
|
||||
}))
|
||||
|
||||
export default function CableEstimator() {
|
||||
//the conversion constant to convert cubic feet to bushels
|
||||
const conversionConstantFT = 0.7786;
|
||||
const classes = useStyles();
|
||||
const [binOptions, setBinOptions] = useState<jsonBin[]>([]);
|
||||
const [displayedRows, setDisplayedRows] = useState<jsonBin[]>([]);
|
||||
const [tablePage, setTablePage] = useState(0)
|
||||
const [pageSize, setPageSize] = useState(10)
|
||||
const [rowCount, setRowCount] = useState(0)
|
||||
const [selectedBin, setSelectedBin] = useState<jsonBin | undefined>();
|
||||
const [quoteOpen, setQuoteOpen] = useState(false);
|
||||
const [useCustomBin, setUseCustomBin] = useState(false);
|
||||
const [diameterFormEntry, setDiameterFormEntry] = useState(
|
||||
getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS ? "9.1" : "30"
|
||||
);
|
||||
//const [capacityFormEntry, setCapacityFormEntry] = useState("0");
|
||||
const [ringsFormEntry, setRingsFormEntry] = useState("0");
|
||||
const [ringHeightFormEntry, setRingHeightFormEntry] = useState("0");
|
||||
const [ringHeightFt, setRingHeightFt] = useState(0);
|
||||
const [sidewallFormEntry, setSidewallFormEntry] = useState(
|
||||
getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS ? "7" : "23"
|
||||
);
|
||||
const [peakFormEntry, setPeakFormEntry] = useState(
|
||||
getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS ? "9.1" : "30"
|
||||
);
|
||||
//const [eaveToPeakFormEntry, setEaveToPeakFormEntry] = useState("0");
|
||||
const [roofAngleFormEntry, setRoofAngleFormEntry] = useState(
|
||||
getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS ? "7.6" : "25"
|
||||
);
|
||||
const [hopperAngleFormEntry, setHopperAngleFormEntry] = useState("0");
|
||||
const nodeSpacing = 4;
|
||||
const [customBin, setCustomBin] = useState<jsonBin>({
|
||||
ID: 0,
|
||||
Manufacturer: "",
|
||||
Type: "Farm", //all bins are currently farm use
|
||||
Bottom: "Flat Bottom",
|
||||
Diameter: 30,
|
||||
Name: "",
|
||||
Model: "",
|
||||
Capacity: 13942,
|
||||
Rings: 0,
|
||||
Sidewall: 23,
|
||||
Peak: 30, //absolute total height from the ground to the peak of the cone
|
||||
EaveToPeak: 0, //the length along the roof from the peak to the eave (not actually used anywhere at the moment)
|
||||
RoofAngle: 25,
|
||||
HopperAngle: 0,
|
||||
CableSums: [],
|
||||
Cables: []
|
||||
});
|
||||
const isMobile = useMobile();
|
||||
const [tableSearch, setTableSearch] = useState("")
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
const columns: Column<jsonBin>[] = [
|
||||
{
|
||||
title: "Manufacturer",
|
||||
render: (row: jsonBin) => <Box padding={2}>{row.Manufacturer}</Box>
|
||||
},
|
||||
{
|
||||
title: "Model",
|
||||
render: (row: jsonBin) => <Box padding={2}>{row.Model}</Box>
|
||||
},
|
||||
{
|
||||
title: "Capacity",
|
||||
render: (row: jsonBin) => <Box padding={2}>{row.Capacity}</Box>
|
||||
},
|
||||
{
|
||||
title: "Hopper Angle",
|
||||
render: (row: jsonBin) => <Box padding={2}>{row.HopperAngle}</Box>
|
||||
},
|
||||
{
|
||||
title: "Diameter " + (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET ? "(ft)" : "(m)"),
|
||||
render: (row: jsonBin) => <Box padding={2}>{distanceConversion(row.Diameter).toFixed(2)}</Box>
|
||||
},
|
||||
{
|
||||
title: "Sidewall" + (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET ? "(ft)" : "(m)"),
|
||||
render: (row: jsonBin) => <Box padding={2}>{distanceConversion(row.Sidewall).toFixed(2)}</Box>
|
||||
},
|
||||
{
|
||||
title: "Peak Height" + (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET ? "(ft)" : "(m)"),
|
||||
render: (row: jsonBin) => <Box padding={2}>{distanceConversion(row.Peak).toFixed(2)}</Box>
|
||||
},
|
||||
{
|
||||
title: "Eave To Peak" + (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET ? "(ft)" : "(m)"),
|
||||
render: (row: jsonBin) => <Box padding={2}>{distanceConversion(row.EaveToPeak).toFixed(2)}</Box>
|
||||
},
|
||||
{
|
||||
title: "Roof Angle",
|
||||
render: (row: jsonBin) => <Box padding={2}>{row.RoofAngle}</Box>
|
||||
},
|
||||
{
|
||||
title: "Rings",
|
||||
render: (row: jsonBin) => <Box padding={2}>{row.Rings}</Box>
|
||||
}
|
||||
]
|
||||
|
||||
useEffect(()=>{
|
||||
setLoading(true)
|
||||
//create a clone to manipulate
|
||||
let clone: jsonBin[] = cloneDeep(binOptions)
|
||||
//having the search filter in here is quite inefficient as it will filter each time you change pages as well
|
||||
//TODO: re-work the filter so it only filters when the search is changed
|
||||
if(tableSearch !== ""){
|
||||
//filter the data in the cloned list
|
||||
clone = clone.filter(bin => {
|
||||
if(tableSearch.toLowerCase() === bin.Manufacturer.toLowerCase()) return true
|
||||
if(tableSearch.toLowerCase() === bin.Model.toLowerCase()) return true
|
||||
if(tableSearch.toLowerCase() === bin.Capacity.toString().toLowerCase()) return true
|
||||
if(tableSearch.toLowerCase() === bin.HopperAngle.toString().toLowerCase()) return true
|
||||
if(tableSearch.toLowerCase() === bin.Diameter.toString().toLowerCase()) return true
|
||||
if(tableSearch.toLowerCase() === bin.Sidewall.toString().toLowerCase()) return true
|
||||
if(tableSearch.toLowerCase() === bin.Peak.toString().toLowerCase()) return true
|
||||
if(tableSearch.toLowerCase() === bin.EaveToPeak.toString().toLowerCase()) return true
|
||||
if(tableSearch.toLowerCase() === bin.RoofAngle.toString().toLowerCase()) return true
|
||||
if(tableSearch.toLowerCase() === bin.Rings.toString().toLowerCase()) return true
|
||||
return false
|
||||
})
|
||||
}
|
||||
setRowCount(clone.length)
|
||||
//split out the first x amount for the page
|
||||
let rows = clone.splice(tablePage * pageSize, pageSize)
|
||||
setDisplayedRows(rows)
|
||||
setLoading(false)
|
||||
},[binOptions, tablePage, pageSize, tableSearch])
|
||||
|
||||
const binOpTable = () => {
|
||||
return (
|
||||
<ResponsiveTable<jsonBin>
|
||||
title={<Typography style={{ fontSize: 20, fontWeight: 650 }}>Bins</Typography>}
|
||||
isLoading={loading}
|
||||
setSearchText={(search) => {setTableSearch(search)}}
|
||||
onRowClick={(bin) => {
|
||||
setSelectedBin(bin);
|
||||
}}
|
||||
page={tablePage}
|
||||
columns={columns}
|
||||
pageSize={pageSize}
|
||||
setPage={(page) => {setTablePage(page)}}
|
||||
handleRowsPerPageChange={(e) => {setPageSize(e.target.value)}}
|
||||
rows={displayedRows}
|
||||
total={rowCount}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const cableSummaries = () => {
|
||||
//numbers in the summaries are in feet
|
||||
let summaries: CableSum[] = [];
|
||||
if (customBin.Diameter < 24) {
|
||||
summaries.push({
|
||||
Orbit: 0,
|
||||
DistanceFromCenter: 2,
|
||||
NumberOfCables: 1,
|
||||
Bracket: 0
|
||||
});
|
||||
} else if (customBin.Diameter < 36) {
|
||||
summaries.push({
|
||||
Orbit: 1,
|
||||
DistanceFromCenter: 7,
|
||||
NumberOfCables: 3,
|
||||
Bracket: 0
|
||||
});
|
||||
} else if (customBin.Diameter < 42) {
|
||||
summaries.push({
|
||||
Orbit: 0,
|
||||
DistanceFromCenter: 2,
|
||||
NumberOfCables: 1,
|
||||
Bracket: 0
|
||||
});
|
||||
summaries.push({
|
||||
Orbit: 1,
|
||||
DistanceFromCenter: 9,
|
||||
NumberOfCables: 3,
|
||||
Bracket: 0
|
||||
});
|
||||
} else if (customBin.Diameter < 48) {
|
||||
summaries.push({
|
||||
Orbit: 0,
|
||||
DistanceFromCenter: 2,
|
||||
NumberOfCables: 1,
|
||||
Bracket: 0
|
||||
});
|
||||
summaries.push({
|
||||
Orbit: 1,
|
||||
DistanceFromCenter: 14,
|
||||
NumberOfCables: 4,
|
||||
Bracket: 0
|
||||
});
|
||||
} else if (customBin.Diameter < 54) {
|
||||
summaries.push({
|
||||
Orbit: 0,
|
||||
DistanceFromCenter: 2,
|
||||
NumberOfCables: 1,
|
||||
Bracket: 0
|
||||
});
|
||||
summaries.push({
|
||||
Orbit: 1,
|
||||
DistanceFromCenter: 16,
|
||||
NumberOfCables: 5,
|
||||
Bracket: 0
|
||||
});
|
||||
} else if (customBin.Diameter < 60) {
|
||||
summaries.push({
|
||||
Orbit: 0,
|
||||
DistanceFromCenter: 2,
|
||||
NumberOfCables: 1,
|
||||
Bracket: 0
|
||||
});
|
||||
summaries.push({
|
||||
Orbit: 1,
|
||||
DistanceFromCenter: 18,
|
||||
NumberOfCables: 6,
|
||||
Bracket: 0
|
||||
});
|
||||
} else if (customBin.Diameter < 72) {
|
||||
summaries.push({
|
||||
Orbit: 0,
|
||||
DistanceFromCenter: 2,
|
||||
NumberOfCables: 1,
|
||||
Bracket: 0
|
||||
});
|
||||
summaries.push({
|
||||
Orbit: 1,
|
||||
DistanceFromCenter: 20,
|
||||
NumberOfCables: 7,
|
||||
Bracket: 0
|
||||
});
|
||||
} else if (customBin.Diameter < 78) {
|
||||
summaries.push({
|
||||
Orbit: 1,
|
||||
DistanceFromCenter: 10,
|
||||
NumberOfCables: 4,
|
||||
Bracket: 0
|
||||
});
|
||||
summaries.push({
|
||||
Orbit: 2,
|
||||
DistanceFromCenter: 27,
|
||||
NumberOfCables: 9,
|
||||
Bracket: 0
|
||||
});
|
||||
} else if (customBin.Diameter < 90) {
|
||||
summaries.push({
|
||||
Orbit: 1,
|
||||
DistanceFromCenter: 10,
|
||||
NumberOfCables: 4,
|
||||
Bracket: 0
|
||||
});
|
||||
summaries.push({
|
||||
Orbit: 2,
|
||||
DistanceFromCenter: 29,
|
||||
NumberOfCables: 10,
|
||||
Bracket: 0
|
||||
});
|
||||
} else if (customBin.Diameter < 105) {
|
||||
summaries.push({
|
||||
Orbit: 0,
|
||||
DistanceFromCenter: 2,
|
||||
NumberOfCables: 1,
|
||||
Bracket: 0
|
||||
});
|
||||
summaries.push({
|
||||
Orbit: 1,
|
||||
DistanceFromCenter: 18,
|
||||
NumberOfCables: 6,
|
||||
Bracket: 0
|
||||
});
|
||||
summaries.push({
|
||||
Orbit: 2,
|
||||
DistanceFromCenter: 36,
|
||||
NumberOfCables: 12,
|
||||
Bracket: 0
|
||||
});
|
||||
} else {
|
||||
//diameter is 105 or larger
|
||||
summaries.push({
|
||||
Orbit: 1,
|
||||
DistanceFromCenter: 9,
|
||||
NumberOfCables: 3,
|
||||
Bracket: 0
|
||||
});
|
||||
summaries.push({
|
||||
Orbit: 2,
|
||||
DistanceFromCenter: 26.5,
|
||||
NumberOfCables: 9,
|
||||
Bracket: 0
|
||||
});
|
||||
summaries.push({
|
||||
Orbit: 3,
|
||||
DistanceFromCenter: 44,
|
||||
NumberOfCables: 14,
|
||||
Bracket: 0
|
||||
});
|
||||
}
|
||||
|
||||
return summaries;
|
||||
};
|
||||
|
||||
const buildCables = () => {
|
||||
let bin = cloneDeep(customBin);
|
||||
let binRadius = bin.Diameter / 2;
|
||||
//use the diameter of the bin to build the cable summaries (orbit information)
|
||||
let sums = cableSummaries();
|
||||
|
||||
//then use the orbits and the rest of the bins dimensions to build the cables
|
||||
let hasCenter = false;
|
||||
let roofRadians = bin.RoofAngle * (Math.PI / 180);
|
||||
let hopperRadians = bin.HopperAngle * (Math.PI / 180);
|
||||
let newCables: Cable[] = [];
|
||||
|
||||
sums.forEach((sum, i) => {
|
||||
if (i === 0) {
|
||||
hasCenter = true;
|
||||
}
|
||||
let hopperHeight = 0;
|
||||
// calculate the hight of the cone section of the cable here
|
||||
let topHeight = (binRadius - sum.DistanceFromCenter) * Math.tan(roofRadians);
|
||||
//if the hopper angle is greater than 0 calculate the hopper height for the orbit
|
||||
if (bin.HopperAngle > 0) {
|
||||
hopperHeight = (binRadius - sum.DistanceFromCenter) * Math.tan(hopperRadians);
|
||||
}
|
||||
|
||||
//get the maximum value for the top of the bin to the botton
|
||||
let maxCablelength = Math.round((topHeight + bin.Sidewall + hopperHeight) * 10) / 10;
|
||||
|
||||
//shorten cable for the clearance from the bottom and round the cable down to the nearest even number
|
||||
let lenX10 = (maxCablelength - 2) * 10;
|
||||
let cableLen = (lenX10 - (lenX10 % 20)) / 10;
|
||||
|
||||
//set the bracket in the cableSum based on the length of the cable and whether the bin has a center cable
|
||||
if (cableLen < 36) {
|
||||
sum.Bracket = 3;
|
||||
} else {
|
||||
if (hasCenter) {
|
||||
sum.Bracket = 2;
|
||||
} else {
|
||||
sum.Bracket = 1;
|
||||
}
|
||||
}
|
||||
|
||||
// divide the cable by the node spacing to get the number of nodes
|
||||
let sensors = Math.ceil(cableLen / nodeSpacing);
|
||||
|
||||
//add in the cables for each orbit
|
||||
//moisture cables
|
||||
var moistureCables: Cable = {
|
||||
Orbit: sum.Orbit,
|
||||
Length: cableLen,
|
||||
Nodes: sensors,
|
||||
Type: 2,
|
||||
Count: 1
|
||||
};
|
||||
newCables.push(moistureCables);
|
||||
//temp Cables
|
||||
if (sum.NumberOfCables > 1) {
|
||||
let tempCables: Cable = {
|
||||
Orbit: sum.Orbit,
|
||||
Length: cableLen,
|
||||
Nodes: sensors,
|
||||
Type: 1,
|
||||
Count: sum.NumberOfCables - 1
|
||||
};
|
||||
newCables.push(tempCables);
|
||||
}
|
||||
});
|
||||
|
||||
//add the summaries and cables to the bin
|
||||
bin.CableSums = sums;
|
||||
bin.Cables = newCables;
|
||||
console.log(bin);
|
||||
setCustomBin(bin);
|
||||
};
|
||||
|
||||
const calcRoofAngle = (binRad: number, topConeHeight: number) => {
|
||||
//calculate the angle using the the two given sides of a right triangle
|
||||
let angle = RadiansToDegrees(Math.atan(topConeHeight / binRad)); //('arcTan(opposite / adjacent))' and convert to degrees
|
||||
angle = Math.round(angle * 100) / 100; // round to 2 digits
|
||||
return angle;
|
||||
};
|
||||
|
||||
const calcConeHeight = (binRad: number, roofAngle: number) => {
|
||||
return TriangleOppositeLength(binRad, roofAngle);
|
||||
};
|
||||
|
||||
/**
|
||||
* takes in the dimensions of the bin to calculate its bushel capacity
|
||||
* @param diameter
|
||||
* @param topConeHeight
|
||||
* @param sidewallHeight
|
||||
* @param hopperHeight
|
||||
* @returns the bushel capacity of the bin with given dimensions
|
||||
*/
|
||||
const advancedGrainCalc = (bin: jsonBin) => {
|
||||
let tcH = bin.Peak - bin.Sidewall;
|
||||
let sH = bin.Sidewall;
|
||||
let hH = TriangleOppositeLength(bin.Diameter / 2, bin.HopperAngle); //get the height of the hopper since it is not stored in the bin data
|
||||
let d = bin.Diameter;
|
||||
|
||||
//calculate the volumes and convert them to bushels
|
||||
let topConeBushels = ConeVolume(d / 2, tcH) * conversionConstantFT;
|
||||
let sidewallBushels = CylinderVolume(d / 2, sH) * conversionConstantFT;
|
||||
let hopperBushels = ConeVolume(d / 2, hH ?? 0) * conversionConstantFT;
|
||||
|
||||
return Math.round(topConeBushels + sidewallBushels + hopperBushels);
|
||||
};
|
||||
|
||||
const customBinForm = () => {
|
||||
return (
|
||||
<Card style={{ padding: 20 }}>
|
||||
<Grid container spacing={2}>
|
||||
{/* first row */}
|
||||
<Grid size={{xs:12, md:4}}>
|
||||
<TextField
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
label="Bin Manufacturer"
|
||||
value={customBin.Manufacturer}
|
||||
onChange={e => {
|
||||
let bin = cloneDeep(customBin);
|
||||
bin.Manufacturer = e.target.value;
|
||||
setCustomBin(bin);
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid size={{xs:12, md:4}}>
|
||||
<TextField
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
label="Bin Name"
|
||||
value={customBin.Name}
|
||||
onChange={e => {
|
||||
let bin = cloneDeep(customBin);
|
||||
bin.Name = e.target.value;
|
||||
setCustomBin(bin);
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid size={{xs:12, md:4}}>
|
||||
<TextField
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
label="Bin Model"
|
||||
value={customBin.Model}
|
||||
onChange={e => {
|
||||
let bin = cloneDeep(customBin);
|
||||
bin.Model = e.target.value;
|
||||
setCustomBin(bin);
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
{/* second row */}
|
||||
<Grid size={{xs:12, md:4}}>
|
||||
<TextField
|
||||
type="number"
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
label="Diameter"
|
||||
helperText={"The full diameter of the bin"}
|
||||
InputProps={{
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
{getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS ? "m" : "ft"}
|
||||
</InputAdornment>
|
||||
)
|
||||
}}
|
||||
value={diameterFormEntry}
|
||||
error={isNaN(parseFloat(diameterFormEntry))}
|
||||
onChange={e => {
|
||||
setDiameterFormEntry(e.target.value);
|
||||
let val = isNaN(parseFloat(e.target.value)) ? 0 : parseFloat(e.target.value);
|
||||
if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) {
|
||||
val = val * 3.281;
|
||||
}
|
||||
//when the diameter is changed use the current peak height to adjust the roof angle
|
||||
let bin = cloneDeep(customBin);
|
||||
bin.Diameter = val;
|
||||
let newAngle = calcRoofAngle(bin.Diameter / 2, bin.Peak - bin.Sidewall);
|
||||
setRoofAngleFormEntry(newAngle.toString());
|
||||
bin.RoofAngle = newAngle;
|
||||
bin.Capacity = advancedGrainCalc(bin);
|
||||
setCustomBin(bin);
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid size={{xs:12, md:6}}>
|
||||
<TextField
|
||||
type="number"
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
label="Peak Height"
|
||||
InputProps={{
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
{getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS ? "m" : "ft"}
|
||||
</InputAdornment>
|
||||
)
|
||||
}}
|
||||
helperText={
|
||||
"total height from the bottom of the sidewall to the top of the roof, calculated from the diameter, roof angle, and sidewall height of the bin"
|
||||
}
|
||||
value={peakFormEntry}
|
||||
error={isNaN(parseFloat(peakFormEntry))}
|
||||
onChange={e => {
|
||||
setPeakFormEntry(e.target.value);
|
||||
let val = isNaN(parseFloat(e.target.value)) ? 0 : parseFloat(e.target.value);
|
||||
if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) {
|
||||
val = val * 3.281;
|
||||
}
|
||||
//when the peak height is changed use the diameter to calculate the roof angle
|
||||
let bin = cloneDeep(customBin);
|
||||
bin.Peak = val;
|
||||
let newAngle = calcRoofAngle(bin.Diameter / 2, bin.Peak - bin.Sidewall);
|
||||
setRoofAngleFormEntry(newAngle.toString());
|
||||
bin.RoofAngle = newAngle;
|
||||
bin.Capacity = advancedGrainCalc(bin);
|
||||
setCustomBin(bin);
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
{/* third row */}
|
||||
<Grid size={{xs:12, md:4}}>
|
||||
<TextField
|
||||
type="number"
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
label="Rings"
|
||||
helperText={"Number of rings along the sidewall"}
|
||||
value={ringsFormEntry}
|
||||
error={isNaN(parseFloat(ringsFormEntry))}
|
||||
onChange={e => {
|
||||
setRingsFormEntry(e.target.value);
|
||||
let bin = cloneDeep(customBin);
|
||||
let val = isNaN(parseFloat(e.target.value)) ? 0 : parseFloat(e.target.value);
|
||||
bin.Rings = val;
|
||||
//use the new number of rings and ring height to get the sidewall height
|
||||
let sidewallFt = val * ringHeightFt;
|
||||
bin.Sidewall = sidewallFt;
|
||||
if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) {
|
||||
setSidewallFormEntry((sidewallFt / 3.281).toString());
|
||||
} else {
|
||||
setSidewallFormEntry(sidewallFt.toString());
|
||||
}
|
||||
//with the sidewall height changing the roof ange should also change
|
||||
let newAngle = calcRoofAngle(bin.Diameter / 2, bin.Peak - bin.Sidewall);
|
||||
setRoofAngleFormEntry(newAngle.toString());
|
||||
bin.RoofAngle = newAngle;
|
||||
bin.Capacity = advancedGrainCalc(bin);
|
||||
setCustomBin(bin);
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid size={{xs:12, md:4}}>
|
||||
<TextField
|
||||
type="number"
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
label="Height of Ring"
|
||||
helperText={"The Height of a single ring/band of the bin"}
|
||||
InputProps={{
|
||||
endAdornment: <InputAdornment position="end">{"in"}</InputAdornment>
|
||||
}}
|
||||
value={ringHeightFormEntry}
|
||||
error={isNaN(parseFloat(ringHeightFormEntry))}
|
||||
onChange={e => {
|
||||
setRingHeightFormEntry(e.target.value);
|
||||
let inVal = isNaN(parseFloat(e.target.value)) ? 0 : parseFloat(e.target.value);
|
||||
// if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) {
|
||||
// val = val * 3.281;
|
||||
// }
|
||||
let ringFt = inVal / 12;
|
||||
setRingHeightFt(ringFt);
|
||||
//based on this entry and the number of rings calculate the sidwall
|
||||
let bin = cloneDeep(customBin);
|
||||
let sidewallFt = bin.Rings * ringFt;
|
||||
bin.Sidewall = sidewallFt;
|
||||
if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) {
|
||||
setSidewallFormEntry((sidewallFt / 3.281).toString());
|
||||
} else {
|
||||
setSidewallFormEntry(sidewallFt.toString());
|
||||
}
|
||||
//with the sidewall height changing the roof angle should also change with it
|
||||
let newAngle = calcRoofAngle(bin.Diameter / 2, bin.Peak - bin.Sidewall);
|
||||
setRoofAngleFormEntry(newAngle.toString());
|
||||
bin.RoofAngle = newAngle;
|
||||
bin.Capacity = advancedGrainCalc(bin);
|
||||
setCustomBin(bin);
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid size={{xs:12, md:4}}>
|
||||
<TextField
|
||||
type="number"
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
label="Bin Sidewall Height"
|
||||
helperText={"Full height of all of the rings together"}
|
||||
InputProps={{
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
{getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS ? "m" : "ft"}
|
||||
</InputAdornment>
|
||||
)
|
||||
}}
|
||||
value={sidewallFormEntry}
|
||||
error={isNaN(parseFloat(sidewallFormEntry))}
|
||||
onChange={e => {
|
||||
//need to handle conversions here
|
||||
setSidewallFormEntry(e.target.value);
|
||||
let val = isNaN(parseFloat(e.target.value)) ? 0 : parseFloat(e.target.value);
|
||||
//if the value entered is meters convert it to feet for the bin
|
||||
if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) {
|
||||
val = val * 3.281;
|
||||
}
|
||||
let bin = cloneDeep(customBin);
|
||||
bin.Sidewall = val;
|
||||
let newAngle = calcRoofAngle(bin.Diameter / 2, bin.Peak - bin.Sidewall);
|
||||
setRoofAngleFormEntry(newAngle.toString());
|
||||
bin.RoofAngle = newAngle;
|
||||
bin.Capacity = advancedGrainCalc(bin);
|
||||
setCustomBin(bin);
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
{/* fourth row */}
|
||||
<Grid size={{xs:12, md:6}}>
|
||||
<Box display="flex" justifyContent="center" alignItems="center">
|
||||
<Slider
|
||||
classes={{
|
||||
thumb: classes.sliderThumb,
|
||||
//valueLabel: classes.sliderValLabel
|
||||
}}
|
||||
valueLabelDisplay="off"
|
||||
min={20}
|
||||
max={40}
|
||||
name="roofAngle"
|
||||
aria-label="roofAngle"
|
||||
step={0.1}
|
||||
color="secondary"
|
||||
//track={false}
|
||||
value={customBin.RoofAngle}
|
||||
onChange={(_, newValue) => {
|
||||
let val = newValue as number;
|
||||
setRoofAngleFormEntry(val.toString());
|
||||
let bin = cloneDeep(customBin);
|
||||
bin.RoofAngle = val;
|
||||
//adjusting the roof angle should also calculate the new top cone and adjust the peak height of the bin
|
||||
let coneHeight = calcConeHeight(bin.Diameter / 2, bin.RoofAngle);
|
||||
bin.Peak = bin.Sidewall + coneHeight; //assign it as feet
|
||||
let peakHeightDisplay = bin.Sidewall + coneHeight;
|
||||
if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) {
|
||||
peakHeightDisplay = peakHeightDisplay / 3.281;
|
||||
}
|
||||
setPeakFormEntry(peakHeightDisplay.toFixed(1));
|
||||
bin.Capacity = advancedGrainCalc(bin);
|
||||
setCustomBin(bin);
|
||||
}}
|
||||
/>
|
||||
<TextField
|
||||
style={{ marginLeft: 10 }}
|
||||
type="number"
|
||||
variant="outlined"
|
||||
label="Roof Angle"
|
||||
helperText={"Calculated from diameter, sidewall and peak height. Usually 25-35"}
|
||||
InputProps={{
|
||||
endAdornment: <InputAdornment position="end">{"deg"}</InputAdornment>
|
||||
}}
|
||||
value={roofAngleFormEntry}
|
||||
error={isNaN(parseFloat(roofAngleFormEntry))}
|
||||
onChange={e => {
|
||||
setRoofAngleFormEntry(e.target.value);
|
||||
let bin = cloneDeep(customBin);
|
||||
bin.RoofAngle = isNaN(parseFloat(e.target.value))
|
||||
? 0
|
||||
: parseFloat(e.target.value);
|
||||
//adjusting the roof angle should also calculate the new top cone and adjust the peak height of the bin
|
||||
let coneHeight = calcConeHeight(bin.Diameter / 2, bin.RoofAngle);
|
||||
bin.Peak = bin.Sidewall + coneHeight;
|
||||
let peakHeightDisplay = bin.Sidewall + coneHeight;
|
||||
if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) {
|
||||
peakHeightDisplay = peakHeightDisplay / 3.281;
|
||||
}
|
||||
setPeakFormEntry(peakHeightDisplay.toFixed(1));
|
||||
bin.Capacity = advancedGrainCalc(bin);
|
||||
setCustomBin(bin);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Grid>
|
||||
<Grid size={{xs:12, md:6}}>
|
||||
<Box display="flex" justifyContent="center" alignItems="center">
|
||||
<Slider
|
||||
classes={{
|
||||
thumb: classes.sliderThumb,
|
||||
//valueLabel: classes.sliderValLabel,
|
||||
}}
|
||||
valueLabelDisplay="off"
|
||||
min={20}
|
||||
max={40}
|
||||
name="hopperAngle"
|
||||
aria-label="hopperAngle"
|
||||
step={0.1}
|
||||
color="secondary"
|
||||
disabled={customBin.Bottom === "Flat Bottom"}
|
||||
//track={false}
|
||||
value={customBin.HopperAngle}
|
||||
onChange={(_, newValue) => {
|
||||
setHopperAngleFormEntry(newValue.toString());
|
||||
let bin = cloneDeep(customBin);
|
||||
bin.HopperAngle = newValue as number;
|
||||
bin.Capacity = advancedGrainCalc(bin);
|
||||
setCustomBin(bin);
|
||||
}}
|
||||
/>
|
||||
<TextField
|
||||
type="number"
|
||||
style={{ marginLeft: 10 }}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
disabled={customBin.Bottom === "Flat Bottom"}
|
||||
label="Hopper Angle"
|
||||
helperText={"Calculated from the hopper height and diameter"}
|
||||
InputProps={{
|
||||
endAdornment: <InputAdornment position="end">{"deg"}</InputAdornment>
|
||||
}}
|
||||
value={hopperAngleFormEntry}
|
||||
error={isNaN(parseFloat(hopperAngleFormEntry))}
|
||||
onChange={e => {
|
||||
setHopperAngleFormEntry(e.target.value);
|
||||
let bin = cloneDeep(customBin);
|
||||
bin.HopperAngle = isNaN(parseFloat(e.target.value))
|
||||
? 0
|
||||
: parseFloat(e.target.value);
|
||||
bin.Capacity = advancedGrainCalc(bin);
|
||||
setCustomBin(bin);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Grid>
|
||||
{/* bottom */}
|
||||
<Grid>
|
||||
<FormControl
|
||||
component="fieldset"
|
||||
variant="outlined">
|
||||
<FormLabel component="legend">Shape</FormLabel>
|
||||
<RadioGroup
|
||||
aria-label="bin shape"
|
||||
name="binShape"
|
||||
value={customBin.Bottom}
|
||||
onChange={(_, value) => {
|
||||
let bin = cloneDeep(customBin);
|
||||
bin.Bottom = value;
|
||||
if (value === "Hopper Bottom") {
|
||||
bin.HopperAngle = 25;
|
||||
setHopperAngleFormEntry("25");
|
||||
} else {
|
||||
bin.HopperAngle = 0;
|
||||
setHopperAngleFormEntry("0");
|
||||
}
|
||||
bin.Capacity = advancedGrainCalc(bin);
|
||||
setCustomBin(bin);
|
||||
}}>
|
||||
<FormControlLabel
|
||||
key={"flat"}
|
||||
value={"Flat Bottom"}
|
||||
control={<Radio />}
|
||||
label={"Flat Bottom"}
|
||||
/>
|
||||
<FormControlLabel
|
||||
key={"hopper"}
|
||||
value={"Hopper Bottom"}
|
||||
control={<Radio />}
|
||||
label={"Hopper"}
|
||||
/>
|
||||
</RadioGroup>
|
||||
</FormControl>
|
||||
</Grid>
|
||||
</Grid>
|
||||
{/* not strictly necessary for quoting */}
|
||||
{/* <TextField
|
||||
type="number"
|
||||
variant="outlined"
|
||||
label="Bin Capacity"
|
||||
value={capacityFormEntry}
|
||||
error={isNaN(parseFloat(capacityFormEntry))}
|
||||
onChange={e => {
|
||||
setCapacityFormEntry(e.target.value);
|
||||
let bin = cloneDeep(customBin);
|
||||
bin.Capacity = isNaN(parseFloat(e.target.value)) ? 0 : parseFloat(e.target.value);
|
||||
setCustomBin(bin);
|
||||
}}
|
||||
/> */}
|
||||
{/* this dimension isn't actually used for anything */}
|
||||
{/* <TextField
|
||||
type="number"
|
||||
variant="outlined"
|
||||
label="Eave to Peak"
|
||||
value={eaveToPeakFormEntry}
|
||||
error={isNaN(parseFloat(eaveToPeakFormEntry))}
|
||||
onChange={e => {
|
||||
//need to handle conversions here
|
||||
setEaveToPeakFormEntry(e.target.value);
|
||||
let val = isNaN(parseFloat(e.target.value)) ? 0 : parseFloat(e.target.value);
|
||||
//if the value entered is meters convert it to feet for the bin
|
||||
if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) {
|
||||
val = val * 3.281;
|
||||
}
|
||||
let bin = cloneDeep(customBin);
|
||||
bin.EaveToPeak = val;
|
||||
setCustomBin(bin);
|
||||
}}
|
||||
/> */}
|
||||
<Button variant="contained" color="primary" onClick={buildCables}>
|
||||
Generate Cables
|
||||
</Button>
|
||||
<Typography style={{ fontWeight: 650 }}>
|
||||
Estimated Capacity: {customBin.Capacity} bushels
|
||||
</Typography>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
const binDisplay = () => {
|
||||
let displayBin = selectedBin;
|
||||
if (useCustomBin) {
|
||||
displayBin = customBin;
|
||||
}
|
||||
if (displayBin) {
|
||||
return (
|
||||
<CableDisplay
|
||||
bin={displayBin}
|
||||
updateBin={newBin => {
|
||||
setSelectedBin(newBin);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const quoteDisplay = () => {
|
||||
let displayBin = selectedBin;
|
||||
if (useCustomBin) {
|
||||
displayBin = customBin;
|
||||
}
|
||||
if (displayBin) {
|
||||
return (
|
||||
<CableQuote
|
||||
bin={displayBin}
|
||||
open={quoteOpen}
|
||||
close={() => {
|
||||
setQuoteOpen(false);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box padding={2}>
|
||||
<FormControlLabel
|
||||
label="Custom Bin"
|
||||
control={
|
||||
<Switch
|
||||
color="default"
|
||||
value={useCustomBin}
|
||||
checked={useCustomBin}
|
||||
onChange={(_, checked) => {
|
||||
setUseCustomBin(checked);
|
||||
}}
|
||||
name="storage"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
{/* grid of select boxes for pre-built options */}
|
||||
<Grid container direction="row" spacing={2} alignContent="center" alignItems="center">
|
||||
{!useCustomBin && (
|
||||
<Grid size={isMobile ? 12 : 10}>
|
||||
<BinSelector
|
||||
optionsChanged={ops => {
|
||||
setBinOptions(ops);
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
)}
|
||||
<Grid size={isMobile ? 12 : 2}>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
setQuoteOpen(true);
|
||||
}}>
|
||||
Generate Quote
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
{useCustomBin ? customBinForm() : binOpTable()}
|
||||
{/* summary of cable data */}
|
||||
{binDisplay()}
|
||||
{quoteDisplay()}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
71
src/cableEstimator/cableMounting.tsx
Normal file
71
src/cableEstimator/cableMounting.tsx
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
import { Box, Typography } from "@mui/material";
|
||||
import { jsonBin, CableSum, getBracket } from "common/DataImports/BinCables/BinCableImporter";
|
||||
import ResponsiveTable, { Column } from "common/ResponsiveTable";
|
||||
//import { getTableIcons } from "common/ResponsiveTable";
|
||||
//import MaterialTable from "material-table";
|
||||
import { pond } from "protobuf-ts/pond";
|
||||
import { distanceConversion, getDistanceUnit } from "utils";
|
||||
|
||||
interface Props {
|
||||
bin: jsonBin;
|
||||
}
|
||||
|
||||
export default function CableMounting(props: Props) {
|
||||
const { bin } = props;
|
||||
// const [tablePage, setTablePage] = useState(0)
|
||||
// const [pageSize, setPageSize] = useState(10)
|
||||
|
||||
const roofDist = (distance: number) => {
|
||||
return distance / Math.cos(bin.RoofAngle * (Math.PI / 180));
|
||||
};
|
||||
|
||||
const columns: Column<CableSum>[] = [
|
||||
{
|
||||
title: "Orbit",
|
||||
render: (row: CableSum) => <Box padding={2}>{row.Orbit === 0 ? "Center" : row.Orbit}</Box>
|
||||
},
|
||||
{
|
||||
title: "From Center " + (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET ? "(ft)" : "(m)"),
|
||||
render: (row: CableSum) => (
|
||||
<Box padding={2}>{distanceConversion(row.DistanceFromCenter).toFixed(2)}</Box>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: "Along Roof" + (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET ? "(ft)" : "(m)"),
|
||||
render: (row: CableSum) => (
|
||||
<Box padding={2}>{distanceConversion(roofDist(row.DistanceFromCenter)).toFixed(2)}</Box>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: "Bracket",
|
||||
render: (row: CableSum) => {
|
||||
let bracketType = "None"
|
||||
let bracket = getBracket(row.Bracket)
|
||||
if(row.Orbit !== 0 && bracket !== undefined) {
|
||||
bracketType = bracket.Name + " (" + row.NumberOfCables + ")"
|
||||
}else {
|
||||
bracketType = "Unknown"
|
||||
}
|
||||
return (
|
||||
<Box padding={2}>
|
||||
{bracketType}
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
return (
|
||||
<ResponsiveTable<CableSum>
|
||||
title={<Typography style={{ fontSize: 20, fontWeight: 650 }}>Install Guide</Typography>}
|
||||
rows={bin.CableSums}
|
||||
columns={columns}
|
||||
page={0}
|
||||
pageSize={0}
|
||||
handleRowsPerPageChange={()=>{}}
|
||||
setPage={()=>{}}
|
||||
total={0}
|
||||
hidePagination
|
||||
/>
|
||||
)
|
||||
}
|
||||
52
src/cableEstimator/cableQuote.tsx
Normal file
52
src/cableEstimator/cableQuote.tsx
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
import { Button, DialogActions, DialogContent } from "@mui/material";
|
||||
import { PDFViewer, StyleSheet } from "@react-pdf/renderer";
|
||||
import AgPDFTemplate from "common/pdfTemplates/Agriculture/agPDFTemplate";
|
||||
import ResponsiveDialog from "common/ResponsiveDialog";
|
||||
import { useMobile } from "hooks";
|
||||
import { jsonBin } from "common/DataImports/BinCables/BinCableImporter";
|
||||
import PdfContent from "./pdfComponents/pdfContent";
|
||||
|
||||
interface Props {
|
||||
bin: jsonBin;
|
||||
open: boolean;
|
||||
close: () => void;
|
||||
}
|
||||
|
||||
export default function CableQuote(props: Props) {
|
||||
const { open, close, bin } = props;
|
||||
const isMobil = useMobile();
|
||||
const pdfStyle = StyleSheet.create({
|
||||
viewerDesktop: {
|
||||
height: 500 * 1.44,
|
||||
width: 500
|
||||
},
|
||||
viewerMobile: {
|
||||
height: "100%",
|
||||
width: "100%"
|
||||
}
|
||||
});
|
||||
|
||||
const pdfDoc = () => {
|
||||
return <AgPDFTemplate content={<PdfContent bin={bin} />} />;
|
||||
};
|
||||
|
||||
return (
|
||||
<ResponsiveDialog open={open} onClose={close}>
|
||||
<DialogContent>
|
||||
<PDFViewer style={isMobil ? pdfStyle.viewerMobile : pdfStyle.viewerDesktop}>
|
||||
{pdfDoc()}
|
||||
</PDFViewer>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
close();
|
||||
}}>
|
||||
Close
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</ResponsiveDialog>
|
||||
);
|
||||
}
|
||||
142
src/cableEstimator/cableTable.tsx
Normal file
142
src/cableEstimator/cableTable.tsx
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
import { Box, Typography } from "@mui/material";
|
||||
//import { getTableIcons } from "common/ResponsiveTable";
|
||||
//import MaterialTable from "material-table";
|
||||
import { pond } from "protobuf-ts/pond";
|
||||
import { useEffect, useState } from "react";
|
||||
import { distanceConversion, getDistanceUnit } from "utils";
|
||||
import { Cable, jsonBin } from "common/DataImports/BinCables/BinCableImporter";
|
||||
import { cloneDeep } from "lodash";
|
||||
import ResponsiveTable, { Column } from "common/ResponsiveTable";
|
||||
|
||||
interface Props {
|
||||
bin: jsonBin;
|
||||
cablesChanged: (newCables: Cable[]) => void;
|
||||
}
|
||||
|
||||
export default function CableTable(props: Props) {
|
||||
const { bin, /*cablesChanged*/ } = props;
|
||||
//const nodeSpacing = 4; //space between the nodes in feet
|
||||
|
||||
const [cableData, setCableData] = useState(bin.Cables);
|
||||
|
||||
useEffect(() => {
|
||||
let convertedCables: Cable[] = cloneDeep(bin.Cables);
|
||||
convertedCables.forEach(cable => {
|
||||
cable.Length = Math.round(distanceConversion(cable.Length) * 100) / 100;
|
||||
});
|
||||
setCableData(convertedCables);
|
||||
}, [bin]);
|
||||
|
||||
// const table = () => {
|
||||
// return (
|
||||
// <MaterialTable
|
||||
// data={cableData}
|
||||
// title={
|
||||
// <Typography style={{ fontSize: 20, fontWeight: 650 }}>Recommended Cables</Typography>
|
||||
// }
|
||||
// icons={getTableIcons()}
|
||||
// editable={{
|
||||
// onRowUpdate: (newCable, oldCable) =>
|
||||
// new Promise((resolve, reject) => {
|
||||
// const dataUpdate = [...cableData];
|
||||
// if (oldCable) {
|
||||
// //make sure the cables lengths are in feet
|
||||
// if (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_METERS) {
|
||||
// newCable.Length = newCable.Length * 3.281;
|
||||
// oldCable.Length = oldCable.Length * 3.281;
|
||||
// }
|
||||
// //if the length was eddited
|
||||
// if (oldCable.Length !== newCable.Length) {
|
||||
// //recalc the number of nodes
|
||||
// newCable.Nodes = Math.ceil(newCable.Length / nodeSpacing);
|
||||
// }
|
||||
// const index = dataUpdate.indexOf(oldCable);
|
||||
// //this line seems ridiculous, however the browser seems to see newCable.Count as a string if it was the one that was edited in the table
|
||||
// newCable.Count = parseInt(newCable.Count.toString());
|
||||
// dataUpdate[index] = newCable;
|
||||
// }
|
||||
// resolve(cablesChanged(dataUpdate));
|
||||
// })
|
||||
// }}
|
||||
// columns={[
|
||||
// {
|
||||
// title: <Typography style={{ fontSize: 15, fontWeight: 650 }}>Orbit</Typography>,
|
||||
// render: (row: Cable) => <div>{row.Orbit === 0 ? "Center" : row.Orbit.toString()}</div>
|
||||
// },
|
||||
// {
|
||||
// title: <Typography style={{ fontSize: 15, fontWeight: 650 }}>Type</Typography>,
|
||||
// render: (row: Cable) => <div>{row.Type === 2 ? "Moisture" : "Temperature"}</div>
|
||||
// },
|
||||
// {
|
||||
// title: (
|
||||
// <Typography style={{ fontSize: 15, fontWeight: 650 }}>Number of Cables</Typography>
|
||||
// ),
|
||||
// field: "Count",
|
||||
// initialEditValue: 0,
|
||||
// type: "numeric"
|
||||
// },
|
||||
// {
|
||||
// title: (
|
||||
// <Typography style={{ fontSize: 15, fontWeight: 650 }}>
|
||||
// Length{getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET ? "(ft)" : "(m)"}
|
||||
// </Typography>
|
||||
// ),
|
||||
// field: "Length",
|
||||
// initialEditValue: 0,
|
||||
// type: "numeric"
|
||||
// },
|
||||
// {
|
||||
// title: (
|
||||
// <Typography style={{ fontSize: 15, fontWeight: 650 }}>Nodes Per Cable</Typography>
|
||||
// ),
|
||||
// editable: "never",
|
||||
// type: "numeric",
|
||||
// field: "Nodes"
|
||||
// }
|
||||
// ]}
|
||||
// options={{
|
||||
// search: false,
|
||||
// paging: false
|
||||
// }}
|
||||
// />
|
||||
// );
|
||||
// };
|
||||
|
||||
const columns: Column<Cable>[] = [
|
||||
{
|
||||
title: "Orbit",
|
||||
render: (row: Cable) => <Box padding={2}>{row.Orbit === 0 ? "Center" : row.Orbit.toString()}</Box>
|
||||
},
|
||||
{
|
||||
title: "Type",
|
||||
render: (row: Cable) => <Box padding={2}>{row.Type === 2 ? "Moisture" : "Temperature"}</Box>
|
||||
},
|
||||
{
|
||||
title: "Number of Cables",
|
||||
render: (row) => <Box padding={2}>{row.Count}</Box>
|
||||
},
|
||||
{
|
||||
title: "Length" + (getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET ? "(ft)" : "(m)"),
|
||||
render: (row) => <Box padding={2}>{row.Length}</Box>
|
||||
},
|
||||
{
|
||||
title: "Nodes Per Cable",
|
||||
render: (row) => <Box padding={2}>{row.Nodes}</Box>
|
||||
}
|
||||
]
|
||||
|
||||
//TODO: in order to add back in the editable functionality will need to add that to the responsive table
|
||||
return (
|
||||
<ResponsiveTable
|
||||
title={<Typography style={{ fontSize: 20, fontWeight: 650 }}>Recommended Cables</Typography>}
|
||||
columns={columns}
|
||||
rows={cableData}
|
||||
page={0}
|
||||
pageSize={0}
|
||||
total={0}
|
||||
setPage={()=>{}}
|
||||
handleRowsPerPageChange={()=>{}}
|
||||
hidePagination
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
import { StyleSheet, Text, View } from "@react-pdf/renderer";
|
||||
import { pond } from "protobuf-ts/pond";
|
||||
import { getDistanceUnit } from "utils";
|
||||
|
||||
interface Props {
|
||||
borderColour: string;
|
||||
}
|
||||
|
||||
export default function CableTableHeader(props: Props) {
|
||||
const borderColor = props.borderColour;
|
||||
const styles = StyleSheet.create({
|
||||
view: {
|
||||
flexDirection: "row",
|
||||
borderBottomColor: borderColor,
|
||||
borderBottomWidth: 1,
|
||||
alignItems: "center",
|
||||
height: 24,
|
||||
textAlign: "center",
|
||||
fontFamily: "Helvetica-Bold",
|
||||
flexGrow: 1
|
||||
},
|
||||
cell: {
|
||||
width: "20%",
|
||||
borderRightColor: borderColor,
|
||||
borderRightWidth: 1
|
||||
},
|
||||
endCell: {
|
||||
width: "20%"
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<View style={styles.view}>
|
||||
<Text style={styles.cell}>Orbit</Text>
|
||||
<Text style={styles.cell}>Type</Text>
|
||||
<Text style={styles.cell}>Qty</Text>
|
||||
<Text style={styles.cell}>
|
||||
Length {getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET ? "(ft)" : "(m)"}
|
||||
</Text>
|
||||
<Text style={styles.endCell}>Nodes</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
import { StyleSheet, Text, View } from "@react-pdf/renderer";
|
||||
import { jsonBin } from "common/DataImports/BinCables/BinCableImporter";
|
||||
import React from "react";
|
||||
import { distanceConversion } from "utils";
|
||||
|
||||
interface Props {
|
||||
bin: jsonBin;
|
||||
borderColour: string;
|
||||
}
|
||||
|
||||
export default function CableTableRow(props: Props) {
|
||||
const styles = StyleSheet.create({
|
||||
view: {
|
||||
flexDirection: "row",
|
||||
borderBottomColor: props.borderColour,
|
||||
borderBottomWidth: 1,
|
||||
alignItems: "center",
|
||||
height: 24,
|
||||
fontStyle: "normal",
|
||||
fontWeight: 650
|
||||
},
|
||||
cell: {
|
||||
width: "20%",
|
||||
textAlign: "left",
|
||||
borderRightColor: props.borderColour,
|
||||
borderRightWidth: 1,
|
||||
paddingLeft: 8
|
||||
},
|
||||
endCell: {
|
||||
width: "20%",
|
||||
paddingLeft: 8
|
||||
}
|
||||
});
|
||||
|
||||
const rows = () => {
|
||||
let r: JSX.Element[] = [];
|
||||
props.bin.Cables.forEach((cable, i) => {
|
||||
r.push(
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
borderBottomColor: props.borderColour,
|
||||
borderBottomWidth: i === props.bin.Cables.length - 1 ? 0 : 1,
|
||||
alignItems: "center",
|
||||
height: 24
|
||||
}}
|
||||
key={i}>
|
||||
<Text style={styles.cell}>{cable.Orbit === 0 ? "Center" : cable.Orbit}</Text>
|
||||
<Text style={styles.cell}>{cable.Type === 2 ? "Moisture" : "Temperature"}</Text>
|
||||
<Text style={styles.cell}>{cable.Count}</Text>
|
||||
<Text style={styles.cell}>{distanceConversion(cable.Length).toFixed(2)}</Text>
|
||||
<Text style={styles.endCell}>{cable.Nodes}</Text>
|
||||
</View>
|
||||
);
|
||||
});
|
||||
return r;
|
||||
};
|
||||
|
||||
return <React.Fragment>{rows()}</React.Fragment>;
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
import { StyleSheet, Text, View } from "@react-pdf/renderer";
|
||||
import { pond } from "protobuf-ts/pond";
|
||||
import { getDistanceUnit } from "utils";
|
||||
|
||||
interface Props {
|
||||
borderColour: string;
|
||||
}
|
||||
|
||||
export default function MountingTableHeader(props: Props) {
|
||||
const borderColor = props.borderColour;
|
||||
const styles = StyleSheet.create({
|
||||
view: {
|
||||
flexDirection: "row",
|
||||
borderBottomColor: borderColor,
|
||||
borderBottomWidth: 1,
|
||||
alignItems: "center",
|
||||
height: 24,
|
||||
textAlign: "center",
|
||||
fontFamily: "Helvetica-Bold",
|
||||
flexGrow: 1
|
||||
},
|
||||
orbit: {
|
||||
width: "14%",
|
||||
borderRightColor: borderColor,
|
||||
borderRightWidth: 1
|
||||
},
|
||||
centerDist: {
|
||||
width: "25%",
|
||||
borderRightColor: borderColor,
|
||||
borderRightWidth: 1
|
||||
},
|
||||
roofDist: {
|
||||
width: "33%",
|
||||
borderRightColor: borderColor,
|
||||
borderRightWidth: 1
|
||||
},
|
||||
bracket: {
|
||||
width: "28%"
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<View style={styles.view}>
|
||||
<Text style={styles.orbit}>Orbit</Text>
|
||||
<Text style={styles.centerDist}>
|
||||
Radius {getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET ? "(ft)" : "(m)"}
|
||||
</Text>
|
||||
<Text style={styles.roofDist}>
|
||||
Along Roof {getDistanceUnit() === pond.DistanceUnit.DISTANCE_UNIT_FEET ? "(ft)" : "(m)"}
|
||||
</Text>
|
||||
<Text style={styles.bracket}>Bracket</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
import { StyleSheet, Text, View } from "@react-pdf/renderer";
|
||||
import { getBracket, jsonBin } from "common/DataImports/BinCables/BinCableImporter";
|
||||
import React from "react";
|
||||
import { distanceConversion } from "utils";
|
||||
|
||||
interface Props {
|
||||
bin: jsonBin;
|
||||
borderColour: string;
|
||||
}
|
||||
|
||||
export default function MountingTableRow(props: Props) {
|
||||
const styles = StyleSheet.create({
|
||||
orbit: {
|
||||
width: "14%",
|
||||
textAlign: "left",
|
||||
borderRightColor: props.borderColour,
|
||||
borderRightWidth: 1,
|
||||
paddingLeft: 8
|
||||
},
|
||||
centerDist: {
|
||||
width: "25%",
|
||||
textAlign: "left",
|
||||
borderRightColor: props.borderColour,
|
||||
borderRightWidth: 1,
|
||||
paddingLeft: 8
|
||||
},
|
||||
roofDist: {
|
||||
width: "33%",
|
||||
textAlign: "left",
|
||||
paddingLeft: 8,
|
||||
borderRightColor: props.borderColour,
|
||||
borderRightWidth: 1
|
||||
},
|
||||
bracket: {
|
||||
width: "28%",
|
||||
textAlign: "center",
|
||||
fontSize: 8
|
||||
}
|
||||
});
|
||||
|
||||
const roofDist = (distance: number) => {
|
||||
return distance / Math.cos(props.bin.RoofAngle * (Math.PI / 180));
|
||||
};
|
||||
|
||||
const rows = () => {
|
||||
let r: JSX.Element[] = [];
|
||||
props.bin.CableSums.forEach((orbit, i) => {
|
||||
let bracketType = "None"
|
||||
let bracket = getBracket(orbit.Bracket)
|
||||
if(orbit.Orbit !== 0 && bracket !== undefined) {
|
||||
bracketType = bracket.Name + " (" + orbit.NumberOfCables + ")"
|
||||
}else {
|
||||
bracketType = "Unk"
|
||||
}
|
||||
|
||||
r.push(
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
borderBottomColor: props.borderColour,
|
||||
borderBottomWidth: i === props.bin.CableSums.length - 1 ? 0 : 1,
|
||||
alignItems: "center",
|
||||
height: 24
|
||||
}}
|
||||
key={i}>
|
||||
<Text style={styles.orbit}>{orbit.Orbit === 0 ? "C" : orbit.Orbit}</Text>
|
||||
<Text style={styles.centerDist}>
|
||||
{distanceConversion(orbit.DistanceFromCenter).toFixed(2)}
|
||||
</Text>
|
||||
<Text style={styles.roofDist}>
|
||||
{distanceConversion(roofDist(orbit.DistanceFromCenter)).toFixed(2)}
|
||||
</Text>
|
||||
<Text style={styles.bracket}>
|
||||
{bracketType}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
});
|
||||
return r;
|
||||
};
|
||||
|
||||
return <React.Fragment>{rows()}</React.Fragment>;
|
||||
}
|
||||
81
src/cableEstimator/pdfComponents/pdfContent.tsx
Normal file
81
src/cableEstimator/pdfComponents/pdfContent.tsx
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
import { StyleSheet, Text, View } from "@react-pdf/renderer";
|
||||
import { colors } from "@mui/material";
|
||||
import { jsonBin } from "common/DataImports/BinCables/BinCableImporter";
|
||||
import CableTableHeader from "./cableTable/cableTableHeader";
|
||||
import CableTableRow from "./cableTable/cableTableRow";
|
||||
import PDFSideView from "./pdfSVG/pdfSideView";
|
||||
import PDFTopView from "./pdfSVG/pdfTopView";
|
||||
import MountingTableHeader from "./mountingTable/mountingTableHeader";
|
||||
import MountingTableRow from "./mountingTable/mountingTableRows";
|
||||
|
||||
interface Props {
|
||||
bin: jsonBin;
|
||||
}
|
||||
|
||||
export default function PdfContent(props: Props) {
|
||||
const tableBorderColour = colors.grey[800];
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
general: {
|
||||
marginTop: 2,
|
||||
flexDirection: "row",
|
||||
borderTop: "1px solid black",
|
||||
borderBottom: "1px solid black",
|
||||
paddingTop: 12,
|
||||
paddingBottom: 12,
|
||||
fontFamily: "Helvetica-Bold",
|
||||
fontSize: 20,
|
||||
textAlign: "center"
|
||||
},
|
||||
svgLabels: {
|
||||
marginTop: 2,
|
||||
flexDirection: "row",
|
||||
borderTop: "1px solid black",
|
||||
paddingTop: 12,
|
||||
textAlign: "left",
|
||||
fontFamily: "Helvetica-Bold"
|
||||
},
|
||||
cableTable: {
|
||||
borderColor: tableBorderColour,
|
||||
borderWidth: 1
|
||||
}
|
||||
});
|
||||
return (
|
||||
<View>
|
||||
<View style={styles.general}>
|
||||
<Text style={{ width: "34%" }}>{props.bin.Manufacturer}</Text>
|
||||
<Text style={{ width: "33%" }}>{props.bin.Model}</Text>
|
||||
<Text style={{ width: "33%" }}>{props.bin.Bottom}</Text>
|
||||
</View>
|
||||
<View style={styles.svgLabels}>
|
||||
<Text style={{ width: "50%" }}>Side View</Text>
|
||||
<Text style={{ width: "50%" }}>Top View</Text>
|
||||
</View>
|
||||
<View style={{ height: 280, flexDirection: "row" }}>
|
||||
<View style={{ width: 260 }}>
|
||||
<PDFSideView bin={props.bin} />
|
||||
</View>
|
||||
<View style={{ width: 260 }}>
|
||||
<View style={{ height: 195 }}>
|
||||
<PDFTopView bin={props.bin} />
|
||||
</View>
|
||||
<View style={{ minHeight: 65 }}>
|
||||
<Text style={{ fontFamily: "Helvetica-Bold", marginBottom: 5 }}>Install Guide</Text>
|
||||
<View
|
||||
style={{ borderColor: tableBorderColour, borderWidth: 1, flexDirection: "column" }}>
|
||||
<MountingTableHeader borderColour={tableBorderColour} />
|
||||
<MountingTableRow borderColour={tableBorderColour} bin={props.bin} />
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
<Text style={{ fontFamily: "Helvetica-Bold", marginTop: 12, marginBottom: 5 }}>
|
||||
Recommended Cables
|
||||
</Text>
|
||||
<View style={styles.cableTable}>
|
||||
<CableTableHeader borderColour={tableBorderColour} />
|
||||
<CableTableRow bin={props.bin} borderColour={tableBorderColour} />
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
387
src/cableEstimator/pdfComponents/pdfSVG/pdfSideView.tsx
Normal file
387
src/cableEstimator/pdfComponents/pdfSVG/pdfSideView.tsx
Normal file
|
|
@ -0,0 +1,387 @@
|
|||
import { colors } from "@mui/material";
|
||||
import { Circle, Line, Path, Svg, Text } from "@react-pdf/renderer";
|
||||
import { jsonBin } from "common/DataImports/BinCables/BinCableImporter";
|
||||
import { useEffect, useState } from "react";
|
||||
import { distanceConversion } from "utils";
|
||||
|
||||
interface Props {
|
||||
bin: jsonBin;
|
||||
}
|
||||
|
||||
interface svgPoint {
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
|
||||
export default function PDFSideView(props: Props) {
|
||||
const svgViewBoxSize = 500;
|
||||
//percentage to of the svg to be the bin drawing from 0 to 1,
|
||||
//NOTE: this is the percentage based on the longest side
|
||||
//ie. if the bin is taller than it is wide the bin will be x percent of the height of the viewbox and the width will be scaled to the height and vice versa
|
||||
const scaleMultiplier = 0.8;
|
||||
const nodeSpacing = 4; //space in feet between the nodes
|
||||
const dimensionFontSize = 18;
|
||||
const gapSize = svgViewBoxSize * 0.05;
|
||||
const { bin } = props;
|
||||
const [scale, setScale] = useState(0); //the scale to multiply the bin dimension by in order to determine how long to draw the line
|
||||
const [hopperHeight, setHopperHeight] = useState(0);
|
||||
const [roofHeight, setRoofHeight] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
//get the peak height of the hopper
|
||||
let h = (bin.Diameter / 2) * Math.tan(bin.HopperAngle * (Math.PI / 180));
|
||||
setHopperHeight(h);
|
||||
setRoofHeight(bin.Peak - bin.Sidewall);
|
||||
|
||||
let heightscale = svgViewBoxSize / (bin.Peak + h);
|
||||
let widthscale = svgViewBoxSize / bin.Diameter;
|
||||
|
||||
//need to use the scale of the longer of the two dimensions
|
||||
setScale((heightscale < widthscale ? heightscale : widthscale) * scaleMultiplier);
|
||||
}, [bin, svgViewBoxSize]);
|
||||
|
||||
const binOutline = () => {
|
||||
let startX = svgViewBoxSize / 2;
|
||||
let startY = 0;
|
||||
//let paddingVal = (padding * svgViewBoxSize)
|
||||
|
||||
return (
|
||||
<Path
|
||||
strokeWidth={4}
|
||||
stroke={colors.grey[400]}
|
||||
fillOpacity={0}
|
||||
d={
|
||||
"M " +
|
||||
startX +
|
||||
" " +
|
||||
startY + //starting point
|
||||
" L " +
|
||||
(startX + (bin.Diameter / 2) * scale) +
|
||||
" " +
|
||||
roofHeight * scale + //right side of the bin roof
|
||||
" V " +
|
||||
(roofHeight + bin.Sidewall) * scale + //right sidewall
|
||||
(hopperHeight === 0
|
||||
? " H " + (startX - (bin.Diameter / 2) * scale) //bin bottom if there is no hopper
|
||||
: //else
|
||||
" L " +
|
||||
startX +
|
||||
" " +
|
||||
(roofHeight + hopperHeight + bin.Sidewall) * scale + //right side of the hopper
|
||||
" L " +
|
||||
(startX - (bin.Diameter / 2) * scale) +
|
||||
" " +
|
||||
(roofHeight + bin.Sidewall) * scale) + //left side of the hopper
|
||||
" V " +
|
||||
roofHeight * scale + //left sidewall
|
||||
" Z " //left side of the bin roof (Z command draws the line from where you currently are back to the startign point)
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const cableLines = () => {
|
||||
let cables: JSX.Element[] = [];
|
||||
//let paddingVal = (padding * svgViewBoxSize)
|
||||
|
||||
// line for the roof at the top of the sidewall
|
||||
// cables.push(
|
||||
// <line
|
||||
// key={"roof"}
|
||||
// strokeWidth={2}
|
||||
// stroke="yellow"
|
||||
// x1={0}
|
||||
// strokeDasharray={3}
|
||||
// x2={svgViewBoxSize}
|
||||
// y1={roofHeight*scale + paddingVal}
|
||||
// y2={roofHeight*scale + paddingVal}
|
||||
// />
|
||||
// )
|
||||
|
||||
bin.CableSums.forEach(sum => {
|
||||
let cableLength = 0;
|
||||
bin.Cables.forEach(c => {
|
||||
if (sum.Orbit === c.Orbit) {
|
||||
//numNodes = c.Nodes
|
||||
cableLength = c.Length;
|
||||
}
|
||||
});
|
||||
let numNodes = Math.ceil(cableLength / nodeSpacing);
|
||||
|
||||
let xVal = svgViewBoxSize / 2 + sum.DistanceFromCenter * scale;
|
||||
let cableStart = sum.DistanceFromCenter * Math.tan(bin.RoofAngle * (Math.PI / 180)) * scale;
|
||||
//need to offset the padding to the top since we are now below the half mark as well as pad the bottom
|
||||
let cableEnd = cableStart + cableLength * scale;
|
||||
|
||||
cables.push(
|
||||
<Line
|
||||
key={"orbit" + sum.Orbit}
|
||||
strokeWidth={2}
|
||||
stroke={colors.grey[400]}
|
||||
x1={xVal}
|
||||
y1={cableStart}
|
||||
x2={xVal}
|
||||
y2={cableEnd}
|
||||
/>
|
||||
);
|
||||
|
||||
let svgSpacing = (cableEnd - cableStart) / numNodes;
|
||||
|
||||
for (let i = 0; i < numNodes; i++) {
|
||||
let nodeY = cableEnd - svgSpacing * i;
|
||||
cables.push(
|
||||
<Circle key={"orbit" + sum.Orbit + "node" + i} cx={xVal} cy={nodeY} r={2} fill="blue" />
|
||||
);
|
||||
}
|
||||
});
|
||||
return cables;
|
||||
};
|
||||
|
||||
const dimensionText = (key: string, x: number, y: number, text: string) => {
|
||||
return (
|
||||
<Text
|
||||
key={key}
|
||||
style={{ fontSize: dimensionFontSize, fill: "black" }}
|
||||
x={x}
|
||||
y={y}
|
||||
textAnchor="middle"
|
||||
dominantBaseline={"middle"}>
|
||||
{text}
|
||||
</Text>
|
||||
);
|
||||
};
|
||||
|
||||
const dimensionPath = (key: string, start: svgPoint, corner: svgPoint, end: svgPoint) => {
|
||||
return (
|
||||
<Path
|
||||
key={key}
|
||||
strokeWidth={1}
|
||||
stroke="black"
|
||||
fillOpacity={0}
|
||||
d={
|
||||
"M " +
|
||||
start.x +
|
||||
" " +
|
||||
start.y +
|
||||
"L " +
|
||||
corner.x +
|
||||
" " +
|
||||
corner.y +
|
||||
"L " +
|
||||
end.x +
|
||||
" " +
|
||||
end.y
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const bottomDiameter = () => {
|
||||
let d: JSX.Element[] = [];
|
||||
|
||||
let x1 = svgViewBoxSize / 2 - (bin.Diameter / 2) * scale;
|
||||
let x2 = svgViewBoxSize / 2 + (bin.Diameter / 2) * scale;
|
||||
|
||||
let y1 =
|
||||
(roofHeight + bin.Sidewall + hopperHeight) * scale +
|
||||
svgViewBoxSize * (1 - scaleMultiplier) * 0.1;
|
||||
let y2 =
|
||||
(roofHeight + bin.Sidewall + hopperHeight) * scale +
|
||||
svgViewBoxSize * (1 - scaleMultiplier) * 0.25;
|
||||
|
||||
d.push(
|
||||
dimensionText(
|
||||
"bottomText",
|
||||
svgViewBoxSize / 2,
|
||||
y2,
|
||||
distanceConversion(bin.Diameter).toFixed(1)
|
||||
)
|
||||
);
|
||||
|
||||
d.push(
|
||||
dimensionPath(
|
||||
"bottomDiameterLine1",
|
||||
{ x: x1, y: y1 },
|
||||
{ x: x1, y: y2 },
|
||||
{ x: svgViewBoxSize / 2 - gapSize, y: y2 }
|
||||
)
|
||||
);
|
||||
d.push(
|
||||
dimensionPath(
|
||||
"bottomDiameterLine2",
|
||||
{ x: svgViewBoxSize / 2 + gapSize, y: y2 },
|
||||
{ x: x2, y: y2 },
|
||||
{ x: x2, y: y1 }
|
||||
)
|
||||
);
|
||||
|
||||
return d;
|
||||
};
|
||||
|
||||
//height of the sidewall, on the left side - always used
|
||||
const sidwallDimension = () => {
|
||||
let d: JSX.Element[] = [];
|
||||
let centerX = svgViewBoxSize / 2;
|
||||
let x1 = centerX - (bin.Diameter / 2) * scale - svgViewBoxSize * (1 - scaleMultiplier) * 0.1;
|
||||
|
||||
let x2 = x1 - svgViewBoxSize * (1 - scaleMultiplier) * 0.15;
|
||||
let midpoint = (roofHeight + bin.Sidewall / 2) * scale;
|
||||
|
||||
let y1 = roofHeight * scale;
|
||||
let y2 = (roofHeight + bin.Sidewall) * scale;
|
||||
|
||||
d.push(
|
||||
dimensionText("sidewallText", x2, midpoint, distanceConversion(bin.Sidewall).toFixed(1))
|
||||
);
|
||||
d.push(
|
||||
dimensionPath(
|
||||
"sidwallTopLine",
|
||||
{ x: x1, y: y1 },
|
||||
{ x: x2, y: y1 },
|
||||
{ x: x2, y: midpoint - gapSize }
|
||||
)
|
||||
);
|
||||
d.push(
|
||||
dimensionPath(
|
||||
"sidewallbottomLine",
|
||||
{ x: x2, y: midpoint + gapSize },
|
||||
{ x: x2, y: y2 },
|
||||
{ x: x1, y: y2 }
|
||||
)
|
||||
);
|
||||
return d;
|
||||
};
|
||||
|
||||
//height of the bottom of the sidewall to the peak of the bin, on the left side - only used for hopper bins
|
||||
const peakDimension = () => {
|
||||
let d: JSX.Element[] = [];
|
||||
let centerX = svgViewBoxSize / 2;
|
||||
let xWall = centerX - (bin.Diameter / 2) * scale - svgViewBoxSize * (1 - scaleMultiplier) * 0.1;
|
||||
let xPeak = svgViewBoxSize / 2 - svgViewBoxSize * (1 - scaleMultiplier) * 0.1;
|
||||
|
||||
let xText = xWall - svgViewBoxSize * (1 - scaleMultiplier) * 0.5;
|
||||
let midpoint = ((roofHeight + bin.Sidewall) / 2) * scale;
|
||||
|
||||
let yTop = 0;
|
||||
let yBottom = (roofHeight + bin.Sidewall) * scale;
|
||||
|
||||
d.push(
|
||||
dimensionText(
|
||||
"peakText",
|
||||
xText,
|
||||
midpoint,
|
||||
distanceConversion(bin.Sidewall + roofHeight).toFixed(1)
|
||||
)
|
||||
);
|
||||
d.push(
|
||||
dimensionPath(
|
||||
"peakTopLine",
|
||||
{ x: xPeak, y: yTop },
|
||||
{ x: xText, y: yTop },
|
||||
{ x: xText, y: midpoint - gapSize }
|
||||
)
|
||||
);
|
||||
d.push(
|
||||
dimensionPath(
|
||||
"peakbottomLine",
|
||||
{ x: xText, y: midpoint + gapSize },
|
||||
{ x: xText, y: yBottom },
|
||||
{ x: xWall, y: yBottom }
|
||||
)
|
||||
);
|
||||
|
||||
return d;
|
||||
};
|
||||
|
||||
//total height of the bin, on the right side - always used
|
||||
const totalDimension = () => {
|
||||
let d: JSX.Element[] = [];
|
||||
let centerX = svgViewBoxSize / 2;
|
||||
//let paddingVal = (padding* svgViewBoxSize)
|
||||
let binRightX = centerX + (bin.Diameter / 2) * scale;
|
||||
|
||||
let xBinCenter = centerX + svgViewBoxSize * (1 - scaleMultiplier) * 0.1;
|
||||
let xBinWall = binRightX + svgViewBoxSize * (1 - scaleMultiplier) * 0.1;
|
||||
let x2 = binRightX + svgViewBoxSize * (1 - scaleMultiplier) * 0.2;
|
||||
let midpoint = ((roofHeight + bin.Sidewall + hopperHeight) / 2) * scale;
|
||||
|
||||
let y1 = 0;
|
||||
let y2 = (roofHeight + bin.Sidewall + hopperHeight) * scale;
|
||||
|
||||
d.push(
|
||||
dimensionText(
|
||||
"totalText",
|
||||
x2,
|
||||
midpoint,
|
||||
distanceConversion(roofHeight + bin.Sidewall + hopperHeight).toFixed(1)
|
||||
)
|
||||
);
|
||||
d.push(
|
||||
dimensionPath(
|
||||
"peakTopLine",
|
||||
{ x: xBinCenter, y: y1 },
|
||||
{ x: x2, y: y1 },
|
||||
{ x: x2, y: midpoint - gapSize }
|
||||
)
|
||||
);
|
||||
d.push(
|
||||
dimensionPath(
|
||||
"peakbottomLine",
|
||||
{ x: x2, y: midpoint + gapSize },
|
||||
{ x: x2, y: y2 },
|
||||
{ x: hopperHeight > 0 ? xBinCenter : xBinWall, y: y2 }
|
||||
)
|
||||
);
|
||||
|
||||
return d;
|
||||
};
|
||||
|
||||
const binDimensions = () => {
|
||||
let dimensions: JSX.Element[] = [];
|
||||
|
||||
dimensions.push(...bottomDiameter());
|
||||
// sidwall Height
|
||||
dimensions.push(...sidwallDimension());
|
||||
//total bin height
|
||||
dimensions.push(...totalDimension());
|
||||
if (hopperHeight > 0) {
|
||||
//show the height of the peak + sidwall without the hopper
|
||||
dimensions.push(...peakDimension());
|
||||
}
|
||||
|
||||
return dimensions;
|
||||
};
|
||||
|
||||
const grainEstimate = () => {
|
||||
let x = svgViewBoxSize / 2 - (bin.Diameter / 2) * scale;
|
||||
let y =
|
||||
(roofHeight + bin.Sidewall + hopperHeight) * scale +
|
||||
svgViewBoxSize * (1 - scaleMultiplier) * 0.65;
|
||||
return (
|
||||
<Text style={{ fontSize: dimensionFontSize, fill: "black" }} x={x} y={y}>
|
||||
{/* textAnchor="middle"
|
||||
dominantBaseline={"middle"}> */}
|
||||
{"Estimated Capacity: " + bin.Capacity + " Bushels"}
|
||||
</Text>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Svg
|
||||
viewBox={
|
||||
"0 " +
|
||||
(svgViewBoxSize * (scaleMultiplier - 1)) / 2 +
|
||||
" " +
|
||||
svgViewBoxSize +
|
||||
" " +
|
||||
svgViewBoxSize
|
||||
}
|
||||
height="100%"
|
||||
width="100%">
|
||||
{cableLines()}
|
||||
{binOutline()}
|
||||
{binDimensions()}
|
||||
{grainEstimate()}
|
||||
</Svg>
|
||||
);
|
||||
}
|
||||
74
src/cableEstimator/pdfComponents/pdfSVG/pdfTopView.tsx
Normal file
74
src/cableEstimator/pdfComponents/pdfSVG/pdfTopView.tsx
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
import { jsonBin } from "common/DataImports/BinCables/BinCableImporter";
|
||||
import { Circle, Svg } from "@react-pdf/renderer";
|
||||
import { colors } from "@mui/material";
|
||||
|
||||
interface Props {
|
||||
bin: jsonBin;
|
||||
}
|
||||
|
||||
export default function PDFTopView(props: Props) {
|
||||
const { bin } = props;
|
||||
const totalRadius = 240;
|
||||
|
||||
const outerEdge = () => {
|
||||
return (
|
||||
<Circle
|
||||
cx="50%"
|
||||
cy="50%"
|
||||
r={totalRadius}
|
||||
stroke={colors.grey[400]}
|
||||
strokeWidth={4}
|
||||
fillOpacity={0}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const orbits = () => {
|
||||
let o: JSX.Element[] = [];
|
||||
bin.CableSums.forEach((sum, i) => {
|
||||
//get the radius of the bin
|
||||
let binRadius = bin.Diameter / 2;
|
||||
//determine the percentage the orbit is from the center
|
||||
let perc = sum.DistanceFromCenter / binRadius;
|
||||
//use the percentage to find the radius for the svg
|
||||
let svgRadius = perc * totalRadius;
|
||||
o.push(
|
||||
<Circle
|
||||
key={"orbit" + i}
|
||||
cx="50%"
|
||||
cy="50%"
|
||||
r={svgRadius}
|
||||
stroke={colors.grey[400]}
|
||||
strokeWidth={4}
|
||||
fillOpacity={0}
|
||||
/>
|
||||
);
|
||||
//then determine the points the cables will be
|
||||
let angleDist = 360 / sum.NumberOfCables;
|
||||
let currentAngle = 90;
|
||||
for (let k = 0; k < sum.NumberOfCables; k++) {
|
||||
let x = svgRadius * Math.sin(currentAngle * (Math.PI / 180));
|
||||
let y = svgRadius * Math.cos(currentAngle * (Math.PI / 180));
|
||||
o.push(
|
||||
<Circle
|
||||
key={"orbit" + i + "cable" + k}
|
||||
r={12}
|
||||
cx="50%"
|
||||
cy="50%"
|
||||
transform={"translate(" + x + " " + y + ")"}
|
||||
fill={"blue"}
|
||||
/>
|
||||
);
|
||||
currentAngle += angleDist;
|
||||
}
|
||||
});
|
||||
return o;
|
||||
};
|
||||
|
||||
return (
|
||||
<Svg viewBox="0 0 500 500" height="100%" width="100%">
|
||||
{outerEdge()}
|
||||
{orbits()}
|
||||
</Svg>
|
||||
);
|
||||
}
|
||||
|
|
@ -93,6 +93,7 @@ interface Props<T> {
|
|||
selectedRows?: number[]; //which rows are selected will need to be controlled by the parent in order to keep track between page changes
|
||||
customElement?: JSX.Element //element that will be placed in the table head between the table actions and the column header rows
|
||||
mobileView?: boolean //can be used to force the table to use its mobile view for narrow containing elements ie, drawer
|
||||
hidePagination?: boolean //when passed in will hide the pagination at the bottom of the table
|
||||
}
|
||||
|
||||
export default function ResponsiveTable<T extends Object>(props: Props<T>) {
|
||||
|
|
@ -120,7 +121,8 @@ export default function ResponsiveTable<T extends Object>(props: Props<T>) {
|
|||
rowSelect,
|
||||
selectedRows,
|
||||
customElement,
|
||||
mobileView
|
||||
mobileView,
|
||||
hidePagination
|
||||
} = props
|
||||
const classes = useStyles();
|
||||
const columns = typeof(props.columns) === "function" ? props.columns() : props.columns
|
||||
|
|
@ -463,7 +465,7 @@ export default function ResponsiveTable<T extends Object>(props: Props<T>) {
|
|||
<Checkbox
|
||||
checked={selectedRows ? selectedRows.includes(index) : false}
|
||||
onClick={(event) => {event.stopPropagation()}}
|
||||
onChange={(e, checked) => {
|
||||
onChange={(_, checked) => {
|
||||
rowSelect(row, index, checked)
|
||||
}}/>
|
||||
</TableCell>
|
||||
|
|
@ -529,15 +531,17 @@ export default function ResponsiveTable<T extends Object>(props: Props<T>) {
|
|||
}
|
||||
</TableBody>
|
||||
</Table>
|
||||
<TablePagination
|
||||
rowsPerPageOptions={rowsPerPageOptions ? rowsPerPageOptions : [5, 10, 20]}
|
||||
component="div"
|
||||
count={total ? total : 0}
|
||||
rowsPerPage={pageSize}
|
||||
page={page}
|
||||
onPageChange={(_event, page) => handlePageChange(page)}
|
||||
onRowsPerPageChange={handleRowsPerPageChange}
|
||||
/>
|
||||
{!hidePagination &&
|
||||
<TablePagination
|
||||
rowsPerPageOptions={rowsPerPageOptions ? rowsPerPageOptions : [5, 10, 20]}
|
||||
component="div"
|
||||
count={total ? total : 0}
|
||||
rowsPerPage={pageSize}
|
||||
page={page}
|
||||
onPageChange={(_event, page) => handlePageChange(page)}
|
||||
onRowsPerPageChange={handleRowsPerPageChange}
|
||||
/>
|
||||
}
|
||||
<Menu
|
||||
id="userMenu"
|
||||
anchorEl={columnAnchor}
|
||||
|
|
|
|||
41
src/common/pdfTemplates/Agriculture/agPDFTemplate.tsx
Normal file
41
src/common/pdfTemplates/Agriculture/agPDFTemplate.tsx
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
import { Document, Page, StyleSheet, View } from "@react-pdf/renderer";
|
||||
import Header from "./components/header";
|
||||
import Footer from "./components/footer";
|
||||
|
||||
interface Props {
|
||||
content: JSX.Element;
|
||||
}
|
||||
|
||||
export default function AgPDFTemplate(props: Props) {
|
||||
const pdfStyle = StyleSheet.create({
|
||||
page: {
|
||||
fontSize: 11,
|
||||
padding: "5%"
|
||||
},
|
||||
header: {
|
||||
flexDirection: "row",
|
||||
height: "8%"
|
||||
},
|
||||
content: {
|
||||
flexDirection: "row",
|
||||
height: "87%"
|
||||
},
|
||||
footer: {
|
||||
flexDirection: "row",
|
||||
height: "5%"
|
||||
}
|
||||
});
|
||||
return (
|
||||
<Document>
|
||||
<Page size={"A4"} style={pdfStyle.page}>
|
||||
<View style={pdfStyle.header}>
|
||||
<Header />
|
||||
</View>
|
||||
<View style={pdfStyle.content}>{props.content}</View>
|
||||
<View style={pdfStyle.footer}>
|
||||
<Footer />
|
||||
</View>
|
||||
</Page>
|
||||
</Document>
|
||||
);
|
||||
}
|
||||
31
src/common/pdfTemplates/Agriculture/components/footer.tsx
Normal file
31
src/common/pdfTemplates/Agriculture/components/footer.tsx
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
import { Image, StyleSheet, View } from "@react-pdf/renderer";
|
||||
import agFooter from "common/pdfTemplates/Agriculture/components/footerImages/agFooter.png";
|
||||
import React from "react";
|
||||
|
||||
export default function Footer() {
|
||||
const styles = StyleSheet.create({
|
||||
view: {
|
||||
flexDirection: "row",
|
||||
fontStyle: "normal",
|
||||
fontWeight: 650,
|
||||
borderTop: "1px solid black",
|
||||
padding: 10
|
||||
},
|
||||
label: {
|
||||
width: "20%"
|
||||
},
|
||||
email: {
|
||||
width: "40%"
|
||||
},
|
||||
phone: {
|
||||
width: "40%"
|
||||
}
|
||||
});
|
||||
return (
|
||||
<React.Fragment>
|
||||
<View style={styles.view}>
|
||||
<Image src={agFooter} />
|
||||
</View>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 84 KiB |
32
src/common/pdfTemplates/Agriculture/components/header.tsx
Normal file
32
src/common/pdfTemplates/Agriculture/components/header.tsx
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
import { Image, StyleSheet, Text, View } from "@react-pdf/renderer";
|
||||
import moment from "moment";
|
||||
import React from "react";
|
||||
import logo from "assets/whitelabels/AdaptiveAgriculture/AgLogoText.png";
|
||||
|
||||
export default function Header() {
|
||||
const styles = StyleSheet.create({
|
||||
view: {
|
||||
flexDirection: "row",
|
||||
fontStyle: "normal",
|
||||
fontWeight: 650,
|
||||
borderBottom: "1px solid black"
|
||||
},
|
||||
logo: {
|
||||
width: "55%",
|
||||
textAlign: "left",
|
||||
paddingBottom: 15
|
||||
},
|
||||
estimate: {
|
||||
width: "45%",
|
||||
textAlign: "right"
|
||||
}
|
||||
});
|
||||
return (
|
||||
<React.Fragment>
|
||||
<View style={styles.view}>
|
||||
<Image style={styles.logo} src={logo} />
|
||||
<Text style={styles.estimate}>{moment().format("YYYY-MM-DD")}</Text>
|
||||
</View>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
|
@ -23,7 +23,8 @@ const adminFeatures = [
|
|||
"json",
|
||||
"tasks",
|
||||
"teams",
|
||||
"developer"
|
||||
"developer",
|
||||
"installer"
|
||||
];
|
||||
|
||||
export class User {
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ import Heaters from "pages/Heaters";
|
|||
import Heater from "pages/Heater";
|
||||
import Tasks from "pages/Tasks";
|
||||
import Transactions from "pages/Transactions";
|
||||
import BinCableEstimator from "pages/BinCableEstimator";
|
||||
import { useGlobalState } from "providers";
|
||||
//import Site from "pages/Site";
|
||||
|
||||
const DeviceHistory = lazy(() => import("pages/DeviceHistory"));
|
||||
|
|
@ -48,6 +50,7 @@ export default function Router(props: Props) {
|
|||
const { toggleTheme } = props;
|
||||
const { isAuthenticated, loginWithRedirect, isLoading } = useAuth0();
|
||||
const whiteLabel = getWhitelabel();
|
||||
const [{ user }] = useGlobalState();
|
||||
|
||||
const RelativeRoutes = () => {
|
||||
return (
|
||||
|
|
@ -303,6 +306,9 @@ export default function Router(props: Props) {
|
|||
<Route path="users" element={<Users/>} />
|
||||
<Route path="tasks" element={<Tasks />} />
|
||||
<Route path="transactions" element={<Transactions />} />
|
||||
{user.hasFeature("installer") &&
|
||||
<Route path="cableEstimate" element={<BinCableEstimator />} />
|
||||
}
|
||||
|
||||
{/* Map pages */}
|
||||
<Route path="visualFarm" element={<FieldMap />} />
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ import JobsiteIcon from "products/Construction/JobSiteIcon";
|
|||
import { getThemeType } from "theme";
|
||||
import ObjectHeaterIcon from "products/Construction/ObjectHeaterIcon";
|
||||
import TasksIcon from "products/Construction/TasksIcon";
|
||||
import CableIcon from "products/Bindapt/CableIcon";
|
||||
|
||||
const drawerWidth = 230;
|
||||
|
||||
|
|
@ -321,6 +322,20 @@ export default function SideNavigator(props: Props) {
|
|||
{open && <ListItemText primary="Transactions" />}
|
||||
</ListItemButton>
|
||||
</Tooltip>
|
||||
{(user.hasFeature("installer") && isAg || user.hasFeature("admin")) &&
|
||||
<Tooltip title="Cable Estimator" placement="right">
|
||||
<ListItemButton
|
||||
id="tour-cable-estimator"
|
||||
onClick={() => goTo("/cableEstimate")}
|
||||
classes={getClasses("/cableEstimate")}
|
||||
>
|
||||
<ListItemIcon>
|
||||
<CableIcon />
|
||||
</ListItemIcon>
|
||||
{open && <ListItemText primary="Cable Estimator" />}
|
||||
</ListItemButton>
|
||||
</Tooltip>
|
||||
}
|
||||
</List>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
10
src/pages/BinCableEstimator.tsx
Normal file
10
src/pages/BinCableEstimator.tsx
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import CableEstimator from "cableEstimator/cableEstimator";
|
||||
import PageContainer from "./PageContainer";
|
||||
|
||||
export default function BinCableEstimator() {
|
||||
return (
|
||||
<PageContainer>
|
||||
<CableEstimator />
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
|
|
@ -22,16 +22,8 @@ export default function TransactionViewer(props: Props) {
|
|||
const [pageSize, setPageSize] = useState(10)
|
||||
|
||||
useEffect(()=>{
|
||||
// let rows: Transaction[] = []
|
||||
// transactions.forEach((transaction, index) => {
|
||||
// if (index >= tablePage * pageSize && index < (tablePage + 1) * pageSize){
|
||||
// rows.push(transaction)
|
||||
// }
|
||||
// })
|
||||
let clone: Transaction[] = cloneDeep(transactions)
|
||||
let rows = clone.splice(tablePage * pageSize, pageSize)
|
||||
console.log(transactions)
|
||||
console.log(rows)
|
||||
setDisplayedRows(rows)
|
||||
},[transactions, tablePage, pageSize])
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue