From 07fbc7d8483c3fec6edf40188c32e71ebfaa7322 Mon Sep 17 00:00:00 2001 From: spearec Date: Tue, 23 May 2023 21:32:13 +0000 Subject: [PATCH 01/15] Convert all uses of react-bootstrap to reactstrap - upgrade to bootstrap v5 and reactstrap v9 - uses of react-bootstrap (modals and buttons) now use reactstrap - convert HeaderButtonsComponent to a NavBar - preliminary work with adding in-form feedback for validation --- package-lock.json | 760 ++++-------------- package.json | 5 +- .../app/components/ChartSelectComponent.tsx | 5 +- .../ConfirmActionModalComponent.tsx | 48 +- .../app/components/DashboardComponent.tsx | 2 +- .../app/components/HeaderButtonsComponent.tsx | 147 ++-- .../components/LanguageSelectorComponent.tsx | 123 +-- .../app/components/UIOptionsComponent.tsx | 6 - .../components/UnsavedWarningComponent.tsx | 5 +- .../conversion/ConversionViewComponent.tsx | 4 +- .../CreateConversionModalComponent.tsx | 201 +++-- .../EditConversionModalComponent.tsx | 169 ++-- .../groups/CreateGroupModalComponent.tsx | 369 +++++---- .../groups/EditGroupModalComponent.tsx | 448 +++++------ .../meters/CreateMeterModalComponent.tsx | 686 ++++++++-------- .../meters/EditMeterModalComponent.tsx | 714 ++++++++-------- .../unit/CreateUnitModalComponent.tsx | 240 +++--- .../unit/EditUnitModalComponent.tsx | 248 +++--- .../containers/LanguageSelectorContainer.ts | 32 - src/client/app/styles/modalStyle.tsx | 3 +- src/client/app/translations/data.js | 24 +- 21 files changed, 1810 insertions(+), 2429 deletions(-) delete mode 100644 src/client/app/containers/LanguageSelectorContainer.ts diff --git a/package-lock.json b/package-lock.json index a1e30676f..a2df7bbf0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,7 @@ "axios": "~0.24.0", "bcryptjs": "~2.4.3", "body-parser": "~1.20.1", - "bootstrap": "~4.3.1", + "bootstrap": "~5.2.3", "cookie-parser": "~1.4.4", "csv": "~5.3.2", "debug": "~4.3.2", @@ -40,7 +40,6 @@ "query-string": "~7.0.1", "rc-slider": "~8.6.6", "react": "~17.0.2", - "react-bootstrap": "~2.2.3", "react-dom": "~17.0.2", "react-dropzone": "~12.0.4", "react-intl": "~5.22.0", @@ -51,7 +50,7 @@ "react-router-dom": "~5.3.0", "react-select": "~5.2.0", "react-tooltip": "~4.2.20", - "reactstrap": "~8.10.0", + "reactstrap": "~9.1.10", "redux": "~4.1.2", "redux-thunk": "~2.4.0", "request": "~2.88.2", @@ -1873,20 +1872,20 @@ } }, "node_modules/@babel/runtime": { - "version": "7.17.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.2.tgz", - "integrity": "sha512-hzeyJyMA1YGdJTuWU0e/j4wKXrU4OMFvY2MSlaI9B7VQb0r5cxTE3EAIS2Q7Tn2RIcDkRvTA/v2JsAEhxe99uw==", + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.5.tgz", + "integrity": "sha512-8jI69toZqqcsnqGGqwGS4Qb1VwLOEp4hz+CXPywcvjs60u3B4Pom/U/7rm4W8tMOYEB+E9wgD0mW1l3r8qlI9Q==", "dependencies": { - "regenerator-runtime": "^0.13.4" + "regenerator-runtime": "^0.13.11" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/runtime/node_modules/regenerator-runtime": { - "version": "0.13.5", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz", - "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==" + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" }, "node_modules/@babel/template": { "version": "7.16.7", @@ -2355,27 +2354,6 @@ "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", "dev": true }, - "node_modules/@hypnosphi/create-react-context": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@hypnosphi/create-react-context/-/create-react-context-0.3.1.tgz", - "integrity": "sha512-V1klUed202XahrWJLLOT3EXNeCpFHCcJntdFGI15ntCwau+jfT386w7OFTMaCqOgXUH1fa0w/I1oZs+i/Rfr0A==", - "dependencies": { - "gud": "^1.0.0", - "warning": "^4.0.3" - }, - "peerDependencies": { - "prop-types": "^15.0.0", - "react": ">=0.14.0" - } - }, - "node_modules/@hypnosphi/create-react-context/node_modules/warning": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", - "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", - "dependencies": { - "loose-envify": "^1.0.0" - } - }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", @@ -2595,25 +2573,14 @@ } }, "node_modules/@popperjs/core": { - "version": "2.11.5", - "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.5.tgz", - "integrity": "sha512-9X2obfABZuDVLCgPK9aX0a/x4jaOEweTTWE2+9sr0Qqqevj2Uv5XorvusThmc9XGYpS9yI+fhh8RTafBtGposw==", + "version": "2.11.7", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.7.tgz", + "integrity": "sha512-Cr4OjIkipTtcXKjAsm8agyleBuDHvxzeBoa1v543lbv1YaIwQjESsVcmjiWiPEbC1FIeHOG/Op9kdCmAmiS3Kw==", "funding": { "type": "opencollective", "url": "https://opencollective.com/popperjs" } }, - "node_modules/@react-aria/ssr": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.1.2.tgz", - "integrity": "sha512-amXY11ImpokvkTMeKRHjsSsG7v1yzzs6yeqArCyBIk60J3Yhgxwx9Cah+Uu/804ATFwqzN22AXIo7SdtIaMP+g==", - "dependencies": { - "@babel/runtime": "^7.6.2" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0-rc.1" - } - }, "node_modules/@redux-devtools/core": { "version": "3.11.0", "resolved": "https://registry.npmjs.org/@redux-devtools/core/-/core-3.11.0.tgz", @@ -2677,54 +2644,6 @@ "redux": "^3.4.0 || ^4.0.0" } }, - "node_modules/@restart/hooks": { - "version": "0.4.7", - "resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.4.7.tgz", - "integrity": "sha512-ZbjlEHcG+FQtpDPHd7i4FzNNvJf2enAwZfJbpM8CW7BhmOAbsHpZe3tsHwfQUrBuyrxWqPYp2x5UMnilWcY22A==", - "dependencies": { - "dequal": "^2.0.2" - }, - "peerDependencies": { - "react": ">=16.8.0" - } - }, - "node_modules/@restart/ui": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@restart/ui/-/ui-1.2.0.tgz", - "integrity": "sha512-oIh2t3tG8drZtZ9SlaV5CY6wGsUViHk8ZajjhcI+74IQHyWy+AnxDv8rJR5wVgsgcgrPBUvGNkC1AEdcGNPaLQ==", - "dependencies": { - "@babel/runtime": "^7.13.16", - "@popperjs/core": "^2.10.1", - "@react-aria/ssr": "^3.0.1", - "@restart/hooks": "^0.4.0", - "@types/warning": "^3.0.0", - "dequal": "^2.0.2", - "dom-helpers": "^5.2.0", - "uncontrollable": "^7.2.1", - "warning": "^4.0.3" - }, - "peerDependencies": { - "react": ">=16.14.0", - "react-dom": ">=16.14.0" - } - }, - "node_modules/@restart/ui/node_modules/dom-helpers": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", - "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", - "dependencies": { - "@babel/runtime": "^7.8.7", - "csstype": "^3.0.2" - } - }, - "node_modules/@restart/ui/node_modules/warning": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", - "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", - "dependencies": { - "loose-envify": "^1.0.0" - } - }, "node_modules/@sindresorhus/is": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", @@ -3033,11 +2952,6 @@ "@types/node": "*" } }, - "node_modules/@types/invariant": { - "version": "2.2.35", - "resolved": "https://registry.npmjs.org/@types/invariant/-/invariant-2.2.35.tgz", - "integrity": "sha512-DxX1V9P8zdJPYQat1gHyY0xj3efl8gnMVjiM9iCY6y27lj+PoQWkgjt8jDqmovPqULkKVpKRg8J36iQiA+EtEg==" - }, "node_modules/@types/json-schema": { "version": "7.0.9", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", @@ -3180,9 +3094,9 @@ } }, "node_modules/@types/react-transition-group": { - "version": "4.4.4", - "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.4.tgz", - "integrity": "sha512-7gAPz7anVK5xzbeQW9wFBDg7G++aPLAFY0QaSMOou9rJZpbuI58WAuJrgu+qR92l61grlnCUe7AFX8KGahAgug==", + "version": "4.4.6", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.6.tgz", + "integrity": "sha512-VnCdSxfcm08KjsJVQcfBmhEQAPnLB8G08hAxn39azX1qYBQ/5RVQuoHuKIcfKOdncuaUvEpFKFzEvbtIMsfVew==", "dependencies": { "@types/react": "*" } @@ -3236,11 +3150,6 @@ "@types/node": "*" } }, - "node_modules/@types/warning": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/warning/-/warning-3.0.0.tgz", - "integrity": "sha1-DSUBJorY+ZYrdA04fEZU9fjiPlI=" - }, "node_modules/@types/ws": { "version": "8.2.2", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.2.2.tgz", @@ -4677,15 +4586,21 @@ "dev": true }, "node_modules/bootstrap": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.3.1.tgz", - "integrity": "sha512-rXqOmH1VilAt2DyPzluTi2blhk17bO7ef+zLLPlWvG494pDxcM234pJ8wTc/6R40UWizAIIMgxjvxZg5kmsbag==", - "engines": { - "node": ">=6" - }, + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.2.3.tgz", + "integrity": "sha512-cEKPM+fwb3cT8NzQZYEu4HilJ3anCrWqh3CHAok1p9jXqMPsPTBhU25fBckEJHJ/p+tTxTFTsFQGM+gaHpi3QQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/twbs" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/bootstrap" + } + ], "peerDependencies": { - "jquery": "1.9.1 - 3", - "popper.js": "^1.14.7" + "@popperjs/core": "^2.11.6" } }, "node_modules/boxen": { @@ -5243,9 +5158,9 @@ "integrity": "sha1-ZqDmQBGBbjcZaCj9yMjBRzEshjQ=" }, "node_modules/classnames": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz", - "integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==" + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz", + "integrity": "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==" }, "node_modules/clean-stack": { "version": "2.2.0", @@ -6200,6 +6115,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz", "integrity": "sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==", + "dev": true, "dependencies": { "is-arguments": "^1.0.4", "is-date-object": "^1.0.1", @@ -6257,6 +6173,7 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, "dependencies": { "object-keys": "^1.0.12" }, @@ -6316,14 +6233,6 @@ "node": ">= 0.6" } }, - "node_modules/dequal": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.2.tgz", - "integrity": "sha512-q9K8BlJVxK7hQYqa6XISGmBZbtQQWVXSrRrWreHC94rMt1QL/Impruc+7p2CYSYuVIUr+YCt6hjrs1kkdJRTug==", - "engines": { - "node": ">=6" - } - }, "node_modules/des.js": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.1.tgz", @@ -6454,11 +6363,12 @@ "integrity": "sha512-pHuazgqrsTFrGU2WLDdXxCFabkdQDx72ddkraZNih1KsMcN5qsRSTR9O4VJRlwTPCPb5COYg3LOfiMHHcPInHg==" }, "node_modules/dom-helpers": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-3.4.0.tgz", - "integrity": "sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", "dependencies": { - "@babel/runtime": "^7.1.2" + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" } }, "node_modules/domain-browser": { @@ -6681,6 +6591,7 @@ "version": "1.19.1", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.19.1.tgz", "integrity": "sha512-2vJ6tjA/UfqLm2MPs7jxVybLoB8i1t1Jd9R3kISld20sIxPcTbLuggQOUxeWeAvIUkduv/CfMjuh4WmiXr2v9w==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "es-to-primitive": "^1.2.1", @@ -6720,6 +6631,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, "dependencies": { "is-callable": "^1.1.4", "is-date-object": "^1.0.1", @@ -8061,6 +7973,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "get-intrinsic": "^1.1.1" @@ -8452,11 +8365,6 @@ "node": ">=4.x" } }, - "node_modules/gud": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/gud/-/gud-1.0.0.tgz", - "integrity": "sha512-zGEOVKFM5sVPPrYs7J5/hYEw2Pof8KCyOwyhG8sAF26mCAeUFAcYPu1mwB7hhpIP29zOIBaDqwuHdLp0jvZXjw==" - }, "node_modules/handle-thing": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", @@ -8499,6 +8407,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==", + "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -8542,6 +8451,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dev": true, "dependencies": { "has-symbols": "^1.0.2" }, @@ -8970,6 +8880,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", + "dev": true, "dependencies": { "get-intrinsic": "^1.1.0", "has": "^1.0.3", @@ -9066,6 +8977,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.0.4.tgz", "integrity": "sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA==", + "dev": true, "engines": { "node": ">= 0.4" } @@ -9084,6 +8996,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, "dependencies": { "has-bigints": "^1.0.1" }, @@ -9118,6 +9031,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "has-tostringtag": "^1.0.0" @@ -9160,6 +9074,7 @@ "version": "1.2.4", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz", "integrity": "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==", + "dev": true, "engines": { "node": ">= 0.4" }, @@ -9195,6 +9110,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", + "dev": true, "engines": { "node": ">= 0.4" }, @@ -9347,6 +9263,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz", "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==", + "dev": true, "engines": { "node": ">= 0.4" }, @@ -9379,6 +9296,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.6.tgz", "integrity": "sha512-bEVOqiRcvo3zO1+G2lVMy+gkkEm9Yh7cDMRusKKu5ZJKPUYSJwICTKZrNKHA2EbSP0Tu0+6B/emsYNHZyn6K8g==", + "dev": true, "dependencies": { "has-tostringtag": "^1.0.0" }, @@ -9439,6 +9357,7 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "has-tostringtag": "^1.0.0" @@ -9454,6 +9373,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.1.tgz", "integrity": "sha512-IU0NmyknYZN0rChcKhRO1X8LYz5Isj/Fsqh8NJOSf+N/hCOTwy29F32Ik7a+QszE63IdvmwdTPDd6cZ5pg4cwA==", + "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -9462,6 +9382,7 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, "dependencies": { "has-tostringtag": "^1.0.0" }, @@ -9486,6 +9407,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", + "dev": true, "dependencies": { "has-symbols": "^1.0.1" }, @@ -9536,6 +9458,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.1.tgz", "integrity": "sha512-b2jKc2pQZjaeFYWEf7ScFj+Be1I+PXmlu572Q8coTXZ+LD/QQZ7ShPMst8h16riVgyXTQwUsFEl74mDvc/3MHQ==", + "dev": true, "dependencies": { "call-bind": "^1.0.0" }, @@ -9621,12 +9544,6 @@ "node": ">=8" } }, - "node_modules/jquery": { - "version": "3.6.3", - "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.3.tgz", - "integrity": "sha512-bZ5Sy3YzKo9Fyc8wH2iIQK4JImJ6R0GWI9kL1/k7Z91ZBNgkRXE6U0JfHIizZbort8ZunhSI3jw9I6253ahKfg==", - "peer": true - }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -11059,6 +10976,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.2.tgz", "integrity": "sha512-5lHCz+0uufF6wZ7CRFWJN3hp8Jqblpgve06U5CMQ3f//6iDjPr2PEo9MWCjEssDsa+UZEL4PkFpr+BMop6aKzQ==", + "dev": true, "dependencies": { "define-properties": "^1.1.3", "es-abstract": "^1.17.5" @@ -11082,6 +11000,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", + "dev": true, "dependencies": { "call-bind": "^1.0.0", "define-properties": "^1.1.3", @@ -11725,16 +11644,6 @@ "resolved": "https://registry.npmjs.org/polybooljs/-/polybooljs-1.2.0.tgz", "integrity": "sha1-tDkMLgedTCYtOyUExiiNlbp6R1g=" }, - "node_modules/popper.js": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz", - "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==", - "deprecated": "You can find the new Popper v2 at @popperjs/core, this package is dedicated to the legacy v1", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/popperjs" - } - }, "node_modules/portfinder": { "version": "1.0.28", "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz", @@ -11959,26 +11868,6 @@ "react-is": "^16.8.1" } }, - "node_modules/prop-types-extra": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/prop-types-extra/-/prop-types-extra-1.1.1.tgz", - "integrity": "sha512-59+AHNnHYCdiC+vMwY52WmvP5dM3QLeoumYuEyceQDi9aEhtwN9zIQ2ZNo25sMyXnbh32h+P1ezDsUpUH3JAew==", - "dependencies": { - "react-is": "^16.3.2", - "warning": "^4.0.0" - }, - "peerDependencies": { - "react": ">=0.14.0" - } - }, - "node_modules/prop-types-extra/node_modules/warning": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", - "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", - "dependencies": { - "loose-envify": "^1.0.0" - } - }, "node_modules/prop-types/node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -12377,83 +12266,6 @@ "node": ">=0.10.0" } }, - "node_modules/react-bootstrap": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-2.2.3.tgz", - "integrity": "sha512-gXsAEBdDUHnOpJ2C+DDQ4mFt7tN6u6qWnTH3tqiE9jUvV6gGY8uHFp0iGBsM+yjrBwmR6bqCBFh8Z82aQj1LSw==", - "dependencies": { - "@babel/runtime": "^7.17.2", - "@restart/hooks": "^0.4.6", - "@restart/ui": "^1.2.0", - "@types/invariant": "^2.2.35", - "@types/prop-types": "^15.7.4", - "@types/react": ">=16.14.8", - "@types/react-transition-group": "^4.4.4", - "@types/warning": "^3.0.0", - "classnames": "^2.3.1", - "dom-helpers": "^5.2.1", - "invariant": "^2.2.4", - "prop-types": "^15.8.1", - "prop-types-extra": "^1.1.0", - "react-transition-group": "^4.4.2", - "uncontrollable": "^7.2.1", - "warning": "^4.0.3" - }, - "peerDependencies": { - "react": ">=16.14.0", - "react-dom": ">=16.14.0" - } - }, - "node_modules/react-bootstrap/node_modules/dom-helpers": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", - "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", - "dependencies": { - "@babel/runtime": "^7.8.7", - "csstype": "^3.0.2" - } - }, - "node_modules/react-bootstrap/node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-bootstrap/node_modules/prop-types": { - "version": "15.8.1", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", - "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "dependencies": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.13.1" - } - }, - "node_modules/react-bootstrap/node_modules/react-transition-group": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.2.tgz", - "integrity": "sha512-/RNYfRAMlZwDSr6z4zNKV6xu53/e2BuaBbGhbyYIXTrmgu/bGHzmqOs7mJSJBHy9Ud+ApHx3QjrkKSp1pxvlFg==", - "dependencies": { - "@babel/runtime": "^7.5.5", - "dom-helpers": "^5.0.1", - "loose-envify": "^1.4.0", - "prop-types": "^15.6.2" - }, - "peerDependencies": { - "react": ">=16.6.0", - "react-dom": ">=16.6.0" - } - }, - "node_modules/react-bootstrap/node_modules/warning": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", - "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", - "dependencies": { - "loose-envify": "^1.0.0" - } - }, "node_modules/react-dom": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz", @@ -12509,6 +12321,11 @@ "react-is": "^16.13.1" } }, + "node_modules/react-fast-compare": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz", + "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==" + }, "node_modules/react-intl": { "version": "5.22.0", "resolved": "https://registry.npmjs.org/react-intl/-/react-intl-5.22.0.tgz", @@ -12652,20 +12469,17 @@ } }, "node_modules/react-popper": { - "version": "1.3.11", - "resolved": "https://registry.npmjs.org/react-popper/-/react-popper-1.3.11.tgz", - "integrity": "sha512-VSA/bS+pSndSF2fiasHK/PTEEAyOpX60+H5EPAjoArr8JGm+oihu4UbrqcEBpQibJxBVCpYyjAX7abJ+7DoYVg==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/react-popper/-/react-popper-2.3.0.tgz", + "integrity": "sha512-e1hj8lL3uM+sgSR4Lxzn5h1GxBlpa4CQz0XLF8kx4MDrDRWY0Ena4c97PUeSX9i5W3UAfDP0z0FXCTQkoXUl3Q==", "dependencies": { - "@babel/runtime": "^7.1.2", - "@hypnosphi/create-react-context": "^0.3.1", - "deep-equal": "^1.1.1", - "popper.js": "^1.14.4", - "prop-types": "^15.6.1", - "typed-styles": "^0.0.7", + "react-fast-compare": "^3.0.1", "warning": "^4.0.2" }, "peerDependencies": { - "react": "0.14.x || ^15.0.0 || ^16.0.0 || ^17.0.0" + "@popperjs/core": "^2.0.0", + "react": "^16.8.0 || ^17 || ^18", + "react-dom": "^16.8.0 || ^17 || ^18" } }, "node_modules/react-popper/node_modules/warning": { @@ -12789,30 +12603,6 @@ "react-dom": "^16.8.0 || ^17.0.0" } }, - "node_modules/react-select/node_modules/dom-helpers": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", - "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", - "dependencies": { - "@babel/runtime": "^7.8.7", - "csstype": "^3.0.2" - } - }, - "node_modules/react-select/node_modules/react-transition-group": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.2.tgz", - "integrity": "sha512-/RNYfRAMlZwDSr6z4zNKV6xu53/e2BuaBbGhbyYIXTrmgu/bGHzmqOs7mJSJBHy9Ud+ApHx3QjrkKSp1pxvlFg==", - "dependencies": { - "@babel/runtime": "^7.5.5", - "dom-helpers": "^5.0.1", - "loose-envify": "^1.4.0", - "prop-types": "^15.6.2" - }, - "peerDependencies": { - "react": ">=16.6.0", - "react-dom": ">=16.6.0" - } - }, "node_modules/react-tooltip": { "version": "4.2.20", "resolved": "https://registry.npmjs.org/react-tooltip/-/react-tooltip-4.2.20.tgz", @@ -12838,18 +12628,18 @@ } }, "node_modules/react-transition-group": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.9.0.tgz", - "integrity": "sha512-+HzNTCHpeQyl4MJ/bdE0u6XRMe9+XG/+aL4mCxVN4DnPBQ0/5bfHWPDuOZUzYdMj94daZaZdCCc1Dzt9R/xSSg==", + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", "dependencies": { - "dom-helpers": "^3.4.0", + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", "loose-envify": "^1.4.0", - "prop-types": "^15.6.2", - "react-lifecycles-compat": "^3.0.4" + "prop-types": "^15.6.2" }, "peerDependencies": { - "react": ">=15.0.0", - "react-dom": ">=15.0.0" + "react": ">=16.6.0", + "react-dom": ">=16.6.0" } }, "node_modules/react/node_modules/object-assign": { @@ -12861,19 +12651,20 @@ } }, "node_modules/reactstrap": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/reactstrap/-/reactstrap-8.10.0.tgz", - "integrity": "sha512-MsFUB/fRZj6Orf8Mxc93iYuAs+9ngnFmy2cfYlzkmc4vi5oM4u6ziY/DsO71lDG3cotxHRyS3Flr51cuYv+IEQ==", + "version": "9.1.10", + "resolved": "https://registry.npmjs.org/reactstrap/-/reactstrap-9.1.10.tgz", + "integrity": "sha512-VMDiQNV8hKug1GFOHEymYUYxDnueYl44F+wREABuYy2RgvzDq7OL7T/T8OpUz6JAA+f9LeeHohXxFbpb+a/VmA==", "dependencies": { "@babel/runtime": "^7.12.5", + "@popperjs/core": "^2.6.0", "classnames": "^2.2.3", "prop-types": "^15.5.8", - "react-popper": "^1.3.6", - "react-transition-group": "^2.3.1" + "react-popper": "^2.2.4", + "react-transition-group": "^4.4.2" }, "peerDependencies": { - "react": ">=16.3.0", - "react-dom": ">=16.3.0" + "react": ">=16.8.0", + "react-dom": ">=16.8.0" } }, "node_modules/readable-stream": { @@ -12975,6 +12766,7 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.1.tgz", "integrity": "sha512-pMR7hBVUUGI7PMA37m2ofIdQCsomVnas+Jn5UPGAHQ+/LlwKm/aTLJHdasmHRzlfeZwHiAOaRSo2rbBDm3nNUQ==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.1.3" @@ -14326,6 +14118,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.1.3" @@ -14338,6 +14131,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz", "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", + "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.1.3" @@ -14944,11 +14738,6 @@ "resolved": "https://registry.npmjs.org/type-name/-/type-name-2.0.2.tgz", "integrity": "sha1-7+fUEj2KxSr/9/QMfk3sUmYAj7Q=" }, - "node_modules/typed-styles": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/typed-styles/-/typed-styles-0.0.7.tgz", - "integrity": "sha512-pzP0PWoZUhsECYjABgCGQlRGL1n7tOHsgwYv3oIiEpJwGhFTuty/YNeduxQYzXXa3Ge5BdT6sHYIQYpl4uJ+5Q==" - }, "node_modules/typedarray": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", @@ -14989,6 +14778,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", "integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==", + "dev": true, "dependencies": { "function-bind": "^1.1.1", "has-bigints": "^1.0.1", @@ -14999,20 +14789,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/uncontrollable": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-7.2.1.tgz", - "integrity": "sha512-svtcfoTADIB0nT9nltgjujTi7BzVmwjZClOmskKu/E8FW9BXzg9os8OLr4f8Dlnk0rYWJIWr4wv9eKUXiQvQwQ==", - "dependencies": { - "@babel/runtime": "^7.6.3", - "@types/react": ">=16.9.11", - "invariant": "^2.2.4", - "react-lifecycles-compat": "^3.0.4" - }, - "peerDependencies": { - "react": ">=15.0.0" - } - }, "node_modules/undefsafe": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", @@ -15960,6 +15736,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, "dependencies": { "is-bigint": "^1.0.1", "is-boolean-object": "^1.1.0", @@ -17474,17 +17251,17 @@ } }, "@babel/runtime": { - "version": "7.17.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.2.tgz", - "integrity": "sha512-hzeyJyMA1YGdJTuWU0e/j4wKXrU4OMFvY2MSlaI9B7VQb0r5cxTE3EAIS2Q7Tn2RIcDkRvTA/v2JsAEhxe99uw==", + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.5.tgz", + "integrity": "sha512-8jI69toZqqcsnqGGqwGS4Qb1VwLOEp4hz+CXPywcvjs60u3B4Pom/U/7rm4W8tMOYEB+E9wgD0mW1l3r8qlI9Q==", "requires": { - "regenerator-runtime": "^0.13.4" + "regenerator-runtime": "^0.13.11" }, "dependencies": { "regenerator-runtime": { - "version": "0.13.5", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz", - "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==" + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" } } }, @@ -17910,25 +17687,6 @@ "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", "dev": true }, - "@hypnosphi/create-react-context": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@hypnosphi/create-react-context/-/create-react-context-0.3.1.tgz", - "integrity": "sha512-V1klUed202XahrWJLLOT3EXNeCpFHCcJntdFGI15ntCwau+jfT386w7OFTMaCqOgXUH1fa0w/I1oZs+i/Rfr0A==", - "requires": { - "gud": "^1.0.0", - "warning": "^4.0.3" - }, - "dependencies": { - "warning": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", - "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", - "requires": { - "loose-envify": "^1.0.0" - } - } - } - }, "@jridgewell/gen-mapping": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", @@ -18115,17 +17873,9 @@ } }, "@popperjs/core": { - "version": "2.11.5", - "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.5.tgz", - "integrity": "sha512-9X2obfABZuDVLCgPK9aX0a/x4jaOEweTTWE2+9sr0Qqqevj2Uv5XorvusThmc9XGYpS9yI+fhh8RTafBtGposw==" - }, - "@react-aria/ssr": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.1.2.tgz", - "integrity": "sha512-amXY11ImpokvkTMeKRHjsSsG7v1yzzs6yeqArCyBIk60J3Yhgxwx9Cah+Uu/804ATFwqzN22AXIo7SdtIaMP+g==", - "requires": { - "@babel/runtime": "^7.6.2" - } + "version": "2.11.7", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.7.tgz", + "integrity": "sha512-Cr4OjIkipTtcXKjAsm8agyleBuDHvxzeBoa1v543lbv1YaIwQjESsVcmjiWiPEbC1FIeHOG/Op9kdCmAmiS3Kw==" }, "@redux-devtools/core": { "version": "3.11.0", @@ -18178,49 +17928,6 @@ "lodash": "^4.17.21" } }, - "@restart/hooks": { - "version": "0.4.7", - "resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.4.7.tgz", - "integrity": "sha512-ZbjlEHcG+FQtpDPHd7i4FzNNvJf2enAwZfJbpM8CW7BhmOAbsHpZe3tsHwfQUrBuyrxWqPYp2x5UMnilWcY22A==", - "requires": { - "dequal": "^2.0.2" - } - }, - "@restart/ui": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@restart/ui/-/ui-1.2.0.tgz", - "integrity": "sha512-oIh2t3tG8drZtZ9SlaV5CY6wGsUViHk8ZajjhcI+74IQHyWy+AnxDv8rJR5wVgsgcgrPBUvGNkC1AEdcGNPaLQ==", - "requires": { - "@babel/runtime": "^7.13.16", - "@popperjs/core": "^2.10.1", - "@react-aria/ssr": "^3.0.1", - "@restart/hooks": "^0.4.0", - "@types/warning": "^3.0.0", - "dequal": "^2.0.2", - "dom-helpers": "^5.2.0", - "uncontrollable": "^7.2.1", - "warning": "^4.0.3" - }, - "dependencies": { - "dom-helpers": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", - "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", - "requires": { - "@babel/runtime": "^7.8.7", - "csstype": "^3.0.2" - } - }, - "warning": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", - "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", - "requires": { - "loose-envify": "^1.0.0" - } - } - } - }, "@sindresorhus/is": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", @@ -18506,11 +18213,6 @@ "@types/node": "*" } }, - "@types/invariant": { - "version": "2.2.35", - "resolved": "https://registry.npmjs.org/@types/invariant/-/invariant-2.2.35.tgz", - "integrity": "sha512-DxX1V9P8zdJPYQat1gHyY0xj3efl8gnMVjiM9iCY6y27lj+PoQWkgjt8jDqmovPqULkKVpKRg8J36iQiA+EtEg==" - }, "@types/json-schema": { "version": "7.0.9", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", @@ -18652,9 +18354,9 @@ } }, "@types/react-transition-group": { - "version": "4.4.4", - "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.4.tgz", - "integrity": "sha512-7gAPz7anVK5xzbeQW9wFBDg7G++aPLAFY0QaSMOou9rJZpbuI58WAuJrgu+qR92l61grlnCUe7AFX8KGahAgug==", + "version": "4.4.6", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.6.tgz", + "integrity": "sha512-VnCdSxfcm08KjsJVQcfBmhEQAPnLB8G08hAxn39azX1qYBQ/5RVQuoHuKIcfKOdncuaUvEpFKFzEvbtIMsfVew==", "requires": { "@types/react": "*" } @@ -18708,11 +18410,6 @@ "@types/node": "*" } }, - "@types/warning": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/warning/-/warning-3.0.0.tgz", - "integrity": "sha1-DSUBJorY+ZYrdA04fEZU9fjiPlI=" - }, "@types/ws": { "version": "8.2.2", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.2.2.tgz", @@ -19822,9 +19519,9 @@ } }, "bootstrap": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.3.1.tgz", - "integrity": "sha512-rXqOmH1VilAt2DyPzluTi2blhk17bO7ef+zLLPlWvG494pDxcM234pJ8wTc/6R40UWizAIIMgxjvxZg5kmsbag==", + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.2.3.tgz", + "integrity": "sha512-cEKPM+fwb3cT8NzQZYEu4HilJ3anCrWqh3CHAok1p9jXqMPsPTBhU25fBckEJHJ/p+tTxTFTsFQGM+gaHpi3QQ==", "requires": {} }, "boxen": { @@ -20266,9 +19963,9 @@ "integrity": "sha1-ZqDmQBGBbjcZaCj9yMjBRzEshjQ=" }, "classnames": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz", - "integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==" + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz", + "integrity": "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==" }, "clean-stack": { "version": "2.2.0", @@ -21073,6 +20770,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz", "integrity": "sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==", + "dev": true, "requires": { "is-arguments": "^1.0.4", "is-date-object": "^1.0.1", @@ -21118,6 +20816,7 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, "requires": { "object-keys": "^1.0.12" } @@ -21161,11 +20860,6 @@ "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" }, - "dequal": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.2.tgz", - "integrity": "sha512-q9K8BlJVxK7hQYqa6XISGmBZbtQQWVXSrRrWreHC94rMt1QL/Impruc+7p2CYSYuVIUr+YCt6hjrs1kkdJRTug==" - }, "des.js": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.1.tgz", @@ -21288,11 +20982,12 @@ "integrity": "sha512-pHuazgqrsTFrGU2WLDdXxCFabkdQDx72ddkraZNih1KsMcN5qsRSTR9O4VJRlwTPCPb5COYg3LOfiMHHcPInHg==" }, "dom-helpers": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-3.4.0.tgz", - "integrity": "sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", "requires": { - "@babel/runtime": "^7.1.2" + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" } }, "domain-browser": { @@ -21486,6 +21181,7 @@ "version": "1.19.1", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.19.1.tgz", "integrity": "sha512-2vJ6tjA/UfqLm2MPs7jxVybLoB8i1t1Jd9R3kISld20sIxPcTbLuggQOUxeWeAvIUkduv/CfMjuh4WmiXr2v9w==", + "dev": true, "requires": { "call-bind": "^1.0.2", "es-to-primitive": "^1.2.1", @@ -21519,6 +21215,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, "requires": { "is-callable": "^1.1.4", "is-date-object": "^1.0.1", @@ -22546,6 +22243,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "dev": true, "requires": { "call-bind": "^1.0.2", "get-intrinsic": "^1.1.1" @@ -22893,11 +22591,6 @@ "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", "dev": true }, - "gud": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/gud/-/gud-1.0.0.tgz", - "integrity": "sha512-zGEOVKFM5sVPPrYs7J5/hYEw2Pof8KCyOwyhG8sAF26mCAeUFAcYPu1mwB7hhpIP29zOIBaDqwuHdLp0jvZXjw==" - }, "handle-thing": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", @@ -22929,7 +22622,8 @@ "has-bigints": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", - "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==" + "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==", + "dev": true }, "has-flag": { "version": "3.0.0", @@ -22961,6 +22655,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dev": true, "requires": { "has-symbols": "^1.0.2" } @@ -23292,6 +22987,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", + "dev": true, "requires": { "get-intrinsic": "^1.1.0", "has": "^1.0.3", @@ -23377,7 +23073,8 @@ "is-arguments": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.0.4.tgz", - "integrity": "sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA==" + "integrity": "sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA==", + "dev": true }, "is-arrayish": { "version": "0.2.1", @@ -23393,6 +23090,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, "requires": { "has-bigints": "^1.0.1" } @@ -23415,6 +23113,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, "requires": { "call-bind": "^1.0.2", "has-tostringtag": "^1.0.0" @@ -23433,7 +23132,8 @@ "is-callable": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz", - "integrity": "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==" + "integrity": "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==", + "dev": true }, "is-ci": { "version": "2.0.0", @@ -23456,7 +23156,8 @@ "is-date-object": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", - "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==" + "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", + "dev": true }, "is-docker": { "version": "2.2.1", @@ -23551,7 +23252,8 @@ "is-negative-zero": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz", - "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==" + "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==", + "dev": true }, "is-npm": { "version": "5.0.0", @@ -23569,6 +23271,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.6.tgz", "integrity": "sha512-bEVOqiRcvo3zO1+G2lVMy+gkkEm9Yh7cDMRusKKu5ZJKPUYSJwICTKZrNKHA2EbSP0Tu0+6B/emsYNHZyn6K8g==", + "dev": true, "requires": { "has-tostringtag": "^1.0.0" } @@ -23608,6 +23311,7 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, "requires": { "call-bind": "^1.0.2", "has-tostringtag": "^1.0.0" @@ -23616,12 +23320,14 @@ "is-shared-array-buffer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.1.tgz", - "integrity": "sha512-IU0NmyknYZN0rChcKhRO1X8LYz5Isj/Fsqh8NJOSf+N/hCOTwy29F32Ik7a+QszE63IdvmwdTPDd6cZ5pg4cwA==" + "integrity": "sha512-IU0NmyknYZN0rChcKhRO1X8LYz5Isj/Fsqh8NJOSf+N/hCOTwy29F32Ik7a+QszE63IdvmwdTPDd6cZ5pg4cwA==", + "dev": true }, "is-string": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, "requires": { "has-tostringtag": "^1.0.0" } @@ -23640,6 +23346,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", + "dev": true, "requires": { "has-symbols": "^1.0.1" } @@ -23672,6 +23379,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.1.tgz", "integrity": "sha512-b2jKc2pQZjaeFYWEf7ScFj+Be1I+PXmlu572Q8coTXZ+LD/QQZ7ShPMst8h16riVgyXTQwUsFEl74mDvc/3MHQ==", + "dev": true, "requires": { "call-bind": "^1.0.0" } @@ -23741,12 +23449,6 @@ } } }, - "jquery": { - "version": "3.6.3", - "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.3.tgz", - "integrity": "sha512-bZ5Sy3YzKo9Fyc8wH2iIQK4JImJ6R0GWI9kL1/k7Z91ZBNgkRXE6U0JfHIizZbort8ZunhSI3jw9I6253ahKfg==", - "peer": true - }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -24876,6 +24578,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.2.tgz", "integrity": "sha512-5lHCz+0uufF6wZ7CRFWJN3hp8Jqblpgve06U5CMQ3f//6iDjPr2PEo9MWCjEssDsa+UZEL4PkFpr+BMop6aKzQ==", + "dev": true, "requires": { "define-properties": "^1.1.3", "es-abstract": "^1.17.5" @@ -24890,6 +24593,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", + "dev": true, "requires": { "call-bind": "^1.0.0", "define-properties": "^1.1.3", @@ -25399,11 +25103,6 @@ "resolved": "https://registry.npmjs.org/polybooljs/-/polybooljs-1.2.0.tgz", "integrity": "sha1-tDkMLgedTCYtOyUExiiNlbp6R1g=" }, - "popper.js": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz", - "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==" - }, "portfinder": { "version": "1.0.28", "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz", @@ -25579,25 +25278,6 @@ } } }, - "prop-types-extra": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/prop-types-extra/-/prop-types-extra-1.1.1.tgz", - "integrity": "sha512-59+AHNnHYCdiC+vMwY52WmvP5dM3QLeoumYuEyceQDi9aEhtwN9zIQ2ZNo25sMyXnbh32h+P1ezDsUpUH3JAew==", - "requires": { - "react-is": "^16.3.2", - "warning": "^4.0.0" - }, - "dependencies": { - "warning": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", - "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", - "requires": { - "loose-envify": "^1.0.0" - } - } - } - }, "protocol-buffers-schema": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-3.6.0.tgz", @@ -25936,74 +25616,6 @@ } } }, - "react-bootstrap": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-2.2.3.tgz", - "integrity": "sha512-gXsAEBdDUHnOpJ2C+DDQ4mFt7tN6u6qWnTH3tqiE9jUvV6gGY8uHFp0iGBsM+yjrBwmR6bqCBFh8Z82aQj1LSw==", - "requires": { - "@babel/runtime": "^7.17.2", - "@restart/hooks": "^0.4.6", - "@restart/ui": "^1.2.0", - "@types/invariant": "^2.2.35", - "@types/prop-types": "^15.7.4", - "@types/react": ">=16.14.8", - "@types/react-transition-group": "^4.4.4", - "@types/warning": "^3.0.0", - "classnames": "^2.3.1", - "dom-helpers": "^5.2.1", - "invariant": "^2.2.4", - "prop-types": "^15.8.1", - "prop-types-extra": "^1.1.0", - "react-transition-group": "^4.4.2", - "uncontrollable": "^7.2.1", - "warning": "^4.0.3" - }, - "dependencies": { - "dom-helpers": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", - "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", - "requires": { - "@babel/runtime": "^7.8.7", - "csstype": "^3.0.2" - } - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" - }, - "prop-types": { - "version": "15.8.1", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", - "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "requires": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.13.1" - } - }, - "react-transition-group": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.2.tgz", - "integrity": "sha512-/RNYfRAMlZwDSr6z4zNKV6xu53/e2BuaBbGhbyYIXTrmgu/bGHzmqOs7mJSJBHy9Ud+ApHx3QjrkKSp1pxvlFg==", - "requires": { - "@babel/runtime": "^7.5.5", - "dom-helpers": "^5.0.1", - "loose-envify": "^1.4.0", - "prop-types": "^15.6.2" - } - }, - "warning": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", - "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", - "requires": { - "loose-envify": "^1.0.0" - } - } - } - }, "react-dom": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz", @@ -26048,6 +25660,11 @@ } } }, + "react-fast-compare": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz", + "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==" + }, "react-intl": { "version": "5.22.0", "resolved": "https://registry.npmjs.org/react-intl/-/react-intl-5.22.0.tgz", @@ -26166,16 +25783,11 @@ } }, "react-popper": { - "version": "1.3.11", - "resolved": "https://registry.npmjs.org/react-popper/-/react-popper-1.3.11.tgz", - "integrity": "sha512-VSA/bS+pSndSF2fiasHK/PTEEAyOpX60+H5EPAjoArr8JGm+oihu4UbrqcEBpQibJxBVCpYyjAX7abJ+7DoYVg==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/react-popper/-/react-popper-2.3.0.tgz", + "integrity": "sha512-e1hj8lL3uM+sgSR4Lxzn5h1GxBlpa4CQz0XLF8kx4MDrDRWY0Ena4c97PUeSX9i5W3UAfDP0z0FXCTQkoXUl3Q==", "requires": { - "@babel/runtime": "^7.1.2", - "@hypnosphi/create-react-context": "^0.3.1", - "deep-equal": "^1.1.1", - "popper.js": "^1.14.4", - "prop-types": "^15.6.1", - "typed-styles": "^0.0.7", + "react-fast-compare": "^3.0.1", "warning": "^4.0.2" }, "dependencies": { @@ -26283,28 +25895,6 @@ "memoize-one": "^5.0.0", "prop-types": "^15.6.0", "react-transition-group": "^4.3.0" - }, - "dependencies": { - "dom-helpers": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", - "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", - "requires": { - "@babel/runtime": "^7.8.7", - "csstype": "^3.0.2" - } - }, - "react-transition-group": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.2.tgz", - "integrity": "sha512-/RNYfRAMlZwDSr6z4zNKV6xu53/e2BuaBbGhbyYIXTrmgu/bGHzmqOs7mJSJBHy9Ud+ApHx3QjrkKSp1pxvlFg==", - "requires": { - "@babel/runtime": "^7.5.5", - "dom-helpers": "^5.0.1", - "loose-envify": "^1.4.0", - "prop-types": "^15.6.2" - } - } } }, "react-tooltip": { @@ -26324,26 +25914,27 @@ } }, "react-transition-group": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.9.0.tgz", - "integrity": "sha512-+HzNTCHpeQyl4MJ/bdE0u6XRMe9+XG/+aL4mCxVN4DnPBQ0/5bfHWPDuOZUzYdMj94daZaZdCCc1Dzt9R/xSSg==", + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", "requires": { - "dom-helpers": "^3.4.0", + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", "loose-envify": "^1.4.0", - "prop-types": "^15.6.2", - "react-lifecycles-compat": "^3.0.4" + "prop-types": "^15.6.2" } }, "reactstrap": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/reactstrap/-/reactstrap-8.10.0.tgz", - "integrity": "sha512-MsFUB/fRZj6Orf8Mxc93iYuAs+9ngnFmy2cfYlzkmc4vi5oM4u6ziY/DsO71lDG3cotxHRyS3Flr51cuYv+IEQ==", + "version": "9.1.10", + "resolved": "https://registry.npmjs.org/reactstrap/-/reactstrap-9.1.10.tgz", + "integrity": "sha512-VMDiQNV8hKug1GFOHEymYUYxDnueYl44F+wREABuYy2RgvzDq7OL7T/T8OpUz6JAA+f9LeeHohXxFbpb+a/VmA==", "requires": { "@babel/runtime": "^7.12.5", + "@popperjs/core": "^2.6.0", "classnames": "^2.2.3", "prop-types": "^15.5.8", - "react-popper": "^1.3.6", - "react-transition-group": "^2.3.1" + "react-popper": "^2.2.4", + "react-transition-group": "^4.4.2" } }, "readable-stream": { @@ -26436,6 +26027,7 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.1.tgz", "integrity": "sha512-pMR7hBVUUGI7PMA37m2ofIdQCsomVnas+Jn5UPGAHQ+/LlwKm/aTLJHdasmHRzlfeZwHiAOaRSo2rbBDm3nNUQ==", + "dev": true, "requires": { "call-bind": "^1.0.2", "define-properties": "^1.1.3" @@ -27526,6 +27118,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==", + "dev": true, "requires": { "call-bind": "^1.0.2", "define-properties": "^1.1.3" @@ -27535,6 +27128,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz", "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", + "dev": true, "requires": { "call-bind": "^1.0.2", "define-properties": "^1.1.3" @@ -28016,11 +27610,6 @@ "resolved": "https://registry.npmjs.org/type-name/-/type-name-2.0.2.tgz", "integrity": "sha1-7+fUEj2KxSr/9/QMfk3sUmYAj7Q=" }, - "typed-styles": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/typed-styles/-/typed-styles-0.0.7.tgz", - "integrity": "sha512-pzP0PWoZUhsECYjABgCGQlRGL1n7tOHsgwYv3oIiEpJwGhFTuty/YNeduxQYzXXa3Ge5BdT6sHYIQYpl4uJ+5Q==" - }, "typedarray": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", @@ -28054,6 +27643,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", "integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==", + "dev": true, "requires": { "function-bind": "^1.1.1", "has-bigints": "^1.0.1", @@ -28061,17 +27651,6 @@ "which-boxed-primitive": "^1.0.2" } }, - "uncontrollable": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-7.2.1.tgz", - "integrity": "sha512-svtcfoTADIB0nT9nltgjujTi7BzVmwjZClOmskKu/E8FW9BXzg9os8OLr4f8Dlnk0rYWJIWr4wv9eKUXiQvQwQ==", - "requires": { - "@babel/runtime": "^7.6.3", - "@types/react": ">=16.9.11", - "invariant": "^2.2.4", - "react-lifecycles-compat": "^3.0.4" - } - }, "undefsafe": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", @@ -28795,6 +28374,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, "requires": { "is-bigint": "^1.0.1", "is-boolean-object": "^1.1.0", diff --git a/package.json b/package.json index f683da5fe..6c1a68b6f 100644 --- a/package.json +++ b/package.json @@ -72,7 +72,7 @@ "axios": "~0.24.0", "bcryptjs": "~2.4.3", "body-parser": "~1.20.1", - "bootstrap": "~4.3.1", + "bootstrap": "~5.2.3", "cookie-parser": "~1.4.4", "csv": "~5.3.2", "debug": "~4.3.2", @@ -99,7 +99,6 @@ "query-string": "~7.0.1", "rc-slider": "~8.6.6", "react": "~17.0.2", - "react-bootstrap": "~2.2.3", "react-dom": "~17.0.2", "react-dropzone": "~12.0.4", "react-intl": "~5.22.0", @@ -110,7 +109,7 @@ "react-router-dom": "~5.3.0", "react-select": "~5.2.0", "react-tooltip": "~4.2.20", - "reactstrap": "~8.10.0", + "reactstrap": "~9.1.10", "redux": "~4.1.2", "redux-thunk": "~2.4.0", "request": "~2.88.2", diff --git a/src/client/app/components/ChartSelectComponent.tsx b/src/client/app/components/ChartSelectComponent.tsx index c5af145fd..8c4f9f865 100644 --- a/src/client/app/components/ChartSelectComponent.tsx +++ b/src/client/app/components/ChartSelectComponent.tsx @@ -7,14 +7,11 @@ import * as _ from 'lodash'; import { ChartTypes } from '../types/redux/graph'; import { FormattedMessage } from 'react-intl'; import TooltipMarkerComponent from './TooltipMarkerComponent'; -import Dropdown from 'reactstrap/lib/Dropdown'; -import DropdownItem from 'reactstrap/lib/DropdownItem'; -import DropdownToggle from 'reactstrap/lib/DropdownToggle'; -import DropdownMenu from 'reactstrap/lib/DropdownMenu'; import { useDispatch, useSelector } from 'react-redux'; import { State } from '../types/redux/state'; import { useState } from 'react'; import { SelectOption } from '../types/items'; +import { Dropdown, DropdownToggle, DropdownMenu, DropdownItem } from 'reactstrap'; /** * A component that allows users to select which chart should be displayed. diff --git a/src/client/app/components/ConfirmActionModalComponent.tsx b/src/client/app/components/ConfirmActionModalComponent.tsx index 1e3df13e8..bb9ecef82 100644 --- a/src/client/app/components/ConfirmActionModalComponent.tsx +++ b/src/client/app/components/ConfirmActionModalComponent.tsx @@ -2,9 +2,10 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import * as React from 'react'; -import { Modal, Button } from 'react-bootstrap'; import '../styles/modal.css'; import translate from '../utils/translate'; +import { Button, Modal, ModalBody, ModalFooter, ModalHeader } from 'reactstrap'; +import { formInputStyle } from '../styles/modalStyle'; interface ConfirmActionModalComponentProps { // Control this through the parent component to open/close this modal @@ -45,49 +46,30 @@ export default function ConfirmActionModalComponent(props: ConfirmActionModalCom props.handleClose(); } - const formInputStyle: React.CSSProperties = { - paddingBottom: '5px' - } - - const tableStyle: React.CSSProperties = { - width: '100%' - }; - return ( <> - - - - {props.actionTitle ? props.actionTitle : translate('confirm.action')} - - + + + {props.actionTitle ? props.actionTitle : translate('confirm.action')} + {/* when any of the conversion are changed call one of the functions. */} - -
-
- {/* Modal content */} -
-
- {/* SourceId input*/} -
-

{props.actionConfirmMessage}

-
-
-
-
+ + {/* SourceId input*/} +
+

{props.actionConfirmMessage}

- - +
+ {/* Do not execute the actionFunction and instead close the action confirm modal */} - {/* Execute the action function and close the action confirm modal */} - - + ); diff --git a/src/client/app/components/DashboardComponent.tsx b/src/client/app/components/DashboardComponent.tsx index e6786033e..ce2757e54 100644 --- a/src/client/app/components/DashboardComponent.tsx +++ b/src/client/app/components/DashboardComponent.tsx @@ -12,7 +12,7 @@ import SpinnerComponent from './SpinnerComponent'; import {ChartTypes} from '../types/redux/graph'; import * as moment from 'moment'; import {TimeInterval} from '../../../common/TimeInterval'; -import Button from 'reactstrap/lib/Button'; +import { Button} from 'reactstrap'; import { FormattedMessage } from 'react-intl'; import TooltipMarkerComponent from './TooltipMarkerComponent'; diff --git a/src/client/app/components/HeaderButtonsComponent.tsx b/src/client/app/components/HeaderButtonsComponent.tsx index 0d69c6012..5b671200b 100644 --- a/src/client/app/components/HeaderButtonsComponent.tsx +++ b/src/client/app/components/HeaderButtonsComponent.tsx @@ -4,7 +4,6 @@ import * as React from 'react'; import { useState, useEffect } from 'react'; -import Dropdown from 'react-bootstrap/Dropdown'; import { Link } from 'react-router-dom'; import { FormattedMessage } from 'react-intl'; import MenuModalComponent from './MenuModalComponent'; @@ -19,6 +18,8 @@ import { deleteToken } from '../utils/token'; import { clearCurrentUser } from '../actions/currentUser'; import { State } from '../types/redux/state'; import { useDispatch, useSelector } from 'react-redux'; +import { Navbar, Nav, NavLink, UncontrolledDropdown, DropdownToggle, DropdownMenu, DropdownItem } from 'reactstrap'; +import LanguageSelectorComponent from './LanguageSelectorComponent'; export default function HeaderButtonsComponent(args: { showCollapsedMenuButton: boolean, isModal: boolean }) { const dispatch = useDispatch(); @@ -133,12 +134,6 @@ export default function HeaderButtonsComponent(args: { showCollapsedMenuButton: })); }, [currentUser]); - // Style for dropdown - const dropAlign: React.CSSProperties = { - right: 0, - margin: 0 - }; - // Handle actions on logout. const handleLogOut = () => { if (unsavedChangesState) { @@ -163,76 +158,94 @@ export default function HeaderButtonsComponent(args: { showCollapsedMenuButton: ) : null}
- - {/* There is an issue where the help popup goes off the page. When this - happens, you lose help text and you generally don't see the help text - if you click the help icon a second time. Why this is the case and how to - get the placement correct is unclear. However, if the menuTitle is long enough - to shift the help icon to the left then there is enough space for the help - text box and this does not happen. The current possibilities for menuTitle - do this so the issue is not seen by the user. */} - {state.menuTitle} - - + +
); diff --git a/src/client/app/components/LanguageSelectorComponent.tsx b/src/client/app/components/LanguageSelectorComponent.tsx index a965d47e5..74f2b90e4 100644 --- a/src/client/app/components/LanguageSelectorComponent.tsx +++ b/src/client/app/components/LanguageSelectorComponent.tsx @@ -3,97 +3,50 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import * as React from 'react'; - import { LanguageTypes } from '../types/redux/i18n'; -import { UpdateDefaultLanguageAction } from '../types/redux/admin'; import { FormattedMessage } from 'react-intl'; import TooltipMarkerComponent from './TooltipMarkerComponent'; -import Dropdown from 'reactstrap/lib/Dropdown'; -import DropdownItem from 'reactstrap/lib/DropdownItem'; -import DropdownToggle from 'reactstrap/lib/DropdownToggle'; -import DropdownMenu from 'reactstrap/lib/DropdownMenu'; - -interface LanguageSelectProps { - selectedLanguage: LanguageTypes; - changeLanguage(languageType: LanguageTypes): UpdateDefaultLanguageAction; -} - -interface DropdownState { - dropdownOpen: boolean; - compareSortingDropdownOpen: boolean; -} - -// Convert the i18n language type to its full name. -enum LanguageNames { - en = 'English', - fr = 'Français', - es = 'Español' -} +import { DropdownItem, DropdownMenu, DropdownToggle, UncontrolledDropdown } from 'reactstrap'; +import { useDispatch, useSelector } from 'react-redux'; +import { updateDefaultLanguage } from '../actions/admin'; +import { State } from '../types/redux/state'; +import TooltipHelpContainer from '../containers/TooltipHelpContainer'; /** * A component that allows users to select which language the page should be displayed in. */ -export default class LanguageSelectorComponent extends React.Component { - constructor(props: LanguageSelectProps) { - super(props); - this.handleChangeLanguage = this.handleChangeLanguage.bind(this); - this.toggleDropdown = this.toggleDropdown.bind(this); - this.state = { - dropdownOpen: false, - compareSortingDropdownOpen: false - }; - } - - public render() { - const divBottomPadding: React.CSSProperties = { - paddingBottom: '15px' - }; - - const labelStyle: React.CSSProperties = { - fontWeight: 'bold', - margin: 0 - }; - - return ( -
-

- : -

- - - {/* Show the currently selected language as its name */} - {LanguageNames[this.props.selectedLanguage]} - - - this.handleChangeLanguage(LanguageTypes.en)} - > - English - - this.handleChangeLanguage(LanguageTypes.fr)} - > - Français - - this.handleChangeLanguage(LanguageTypes.es)} - > - Español - - - -
+export default function LanguageSelectorComponent() { + const dispatch = useDispatch(); + + const selectedLanguage = useSelector((state: State) => state.admin.defaultLanguage); + + return ( +
+ + + + + + dispatch(updateDefaultLanguage(LanguageTypes.en))} + disabled={selectedLanguage === LanguageTypes.en}> + English + + dispatch(updateDefaultLanguage(LanguageTypes.fr))} + disabled={selectedLanguage === LanguageTypes.fr}> + Français + + dispatch(updateDefaultLanguage(LanguageTypes.es))} + disabled={selectedLanguage === LanguageTypes.es}> + Español + + + -
-
- ); - } - - private handleChangeLanguage(value: LanguageTypes) { - this.props.changeLanguage(value); - } - - private toggleDropdown() { - this.setState(prevState => ({ dropdownOpen: !prevState.dropdownOpen })); - } + + +
+ ); } \ No newline at end of file diff --git a/src/client/app/components/UIOptionsComponent.tsx b/src/client/app/components/UIOptionsComponent.tsx index 2c5379b49..3d0eeaa31 100644 --- a/src/client/app/components/UIOptionsComponent.tsx +++ b/src/client/app/components/UIOptionsComponent.tsx @@ -12,7 +12,6 @@ import ChartSelectComponent from './ChartSelectComponent'; import ChartDataSelectComponent from './ChartDataSelectComponent'; import { ChangeBarStackingAction, ChangeCompareSortingOrderAction, SetOptionsVisibility } from '../types/redux/graph'; import ChartLinkContainer from '../containers/ChartLinkContainer'; -import LanguageSelectorContainer from '../containers/LanguageSelectorContainer' import { ChartTypes } from '../types/redux/graph'; import { ComparePeriod, SortingOrder } from '../utils/calculateCompare'; import TooltipMarkerComponent from './TooltipMarkerComponent'; @@ -258,11 +257,6 @@ class UIOptionsComponent extends React.Component - {/* Language selector dropdown */} -
- -
-
- {/* Creates a child ConversionModalEditComponent */} diff --git a/src/client/app/components/conversion/CreateConversionModalComponent.tsx b/src/client/app/components/conversion/CreateConversionModalComponent.tsx index 853183dfb..bcbf8020a 100644 --- a/src/client/app/components/conversion/CreateConversionModalComponent.tsx +++ b/src/client/app/components/conversion/CreateConversionModalComponent.tsx @@ -5,8 +5,7 @@ import * as React from 'react'; import { useDispatch } from 'react-redux'; import { useState, useEffect } from 'react'; -import { Modal, Button } from 'react-bootstrap'; -import { Input } from 'reactstrap'; +import { Button, Input, Modal, ModalBody, ModalFooter, ModalHeader } from 'reactstrap'; import { FormattedMessage } from 'react-intl'; import translate from '../../utils/translate'; import '../../styles/modal.css'; @@ -155,121 +154,111 @@ export default function CreateConversionModalComponent(props: CreateConversionMo return ( <> {/* Show modal button */} - - - - - -
- -
-
-
+ + + + +
+ +
+
{/* when any of the conversion are changed call one of the functions. */} - -
-
- {/* Modal content */} -
-
- {/* Source unit input*/} -
- - handleNumberChange(e)}> - {} - {Object.values(unitsSorted).map(unitData => { - return () - })} - -
- {/* Destination unit input*/} -
- - handleNumberChange(e)}> - {} - {Object.values(unitsSorted).map(unitData => { - return () - })} - -
- {/* Bidirectional Y/N input*/} -
- - handleBooleanChange(e)}> - {Object.keys(TrueFalseType).map(key => { - return () - })} - -
- {/* Slope input*/} -
- - handleNumberChange(e)} - /> -
- {/* Intercept input*/} -
- - handleNumberChange(e)} - required value={state.intercept} /> -
- {/* Note input*/} -
- - handleStringChange(e)} - value={state.note} /> -
-
-
-
+ + {/* Source unit input*/} +
+ + handleNumberChange(e)}> + {} + {Object.values(unitsSorted).map(unitData => { + return () + })} + +
+ {/* Destination unit input*/} +
+ + handleNumberChange(e)}> + {} + {Object.values(unitsSorted).map(unitData => { + return () + })} + +
+ {/* Bidirectional Y/N input*/} +
+ + handleBooleanChange(e)}> + {Object.keys(TrueFalseType).map(key => { + return () + })} + +
+ {/* Slope input*/} +
+ + handleNumberChange(e)} + /> +
+ {/* Intercept input*/} +
+ + handleNumberChange(e)} + required value={state.intercept} /> +
+ {/* Note input*/} +
+ + handleStringChange(e)} + value={state.note} />
- - +
+ {/* Hides the modal */} - {/* On click calls the function handleSaveChanges in this component */} - - + ); diff --git a/src/client/app/components/conversion/EditConversionModalComponent.tsx b/src/client/app/components/conversion/EditConversionModalComponent.tsx index cbfd31633..3d15a28fc 100644 --- a/src/client/app/components/conversion/EditConversionModalComponent.tsx +++ b/src/client/app/components/conversion/EditConversionModalComponent.tsx @@ -5,8 +5,7 @@ import * as React from 'react'; // Realize that * is already imported from react import { useState } from 'react'; import { useDispatch } from 'react-redux'; -import { Modal, Button } from 'react-bootstrap'; -import { Input } from 'reactstrap'; +import { Button, Input, Modal, ModalBody, ModalFooter, ModalHeader } from 'reactstrap'; import { FormattedMessage } from 'react-intl'; import translate from '../../utils/translate'; import TooltipMarkerComponent from '../TooltipMarkerComponent'; @@ -143,104 +142,94 @@ export default function EditConversionModalComponent(props: EditConversionModalC actionFunction={handleDeleteConversion} actionConfirmText={deleteConfirmText} actionRejectText={deleteRejectText} /> - - - - -
- -
-
-
+ + + + +
+ +
+ +
{/* when any of the conversion are changed call one of the functions. */} - -
-
- {/* Modal content */} -
-
- {/* Source unit - display only */} -
- - - -
- {/* Destination unit - display only */} -
- - - -
- {/* Bidirectional Y/N input */} -
- - handleBooleanChange(e)}> - {Object.keys(TrueFalseType).map(key => { - return () - })} - -
- {/* Slope input */} -
- - handleNumberChange(e)} /> -
- {/* Intercept input */} -
- - handleNumberChange(e)} /> -
- {/* Note input */} -
- - handleStringChange(e)} /> -
-
-
-
-
+ + {/* Source unit - display only */} +
+ + + +
+ {/* Destination unit - display only */} +
+ + + +
+ {/* Bidirectional Y/N input */} +
+ + handleBooleanChange(e)}> + {Object.keys(TrueFalseType).map(key => { + return () + })} + +
+ {/* Slope input */} +
+ + handleNumberChange(e)} /> +
+ {/* Intercept input */} +
+ + handleNumberChange(e)} /> +
+ {/* Note input */} +
+ + handleStringChange(e)} />
- - - {/* Hides the modal */} - {/* On click calls the function handleSaveChanges in this component */} - - + ); diff --git a/src/client/app/components/groups/CreateGroupModalComponent.tsx b/src/client/app/components/groups/CreateGroupModalComponent.tsx index d307e3537..738d375a4 100644 --- a/src/client/app/components/groups/CreateGroupModalComponent.tsx +++ b/src/client/app/components/groups/CreateGroupModalComponent.tsx @@ -8,8 +8,7 @@ import MultiSelectComponent from '../MultiSelectComponent'; import { SelectOption } from '../../types/items'; import { useDispatch, useSelector } from 'react-redux'; import { State } from 'types/redux/state'; -import { Modal, Button } from 'react-bootstrap'; -import { Input } from 'reactstrap'; +import { Button, Input, InputGroup, Modal, ModalBody, ModalFooter, ModalHeader } from 'reactstrap'; import { FormattedMessage } from 'react-intl'; import translate from '../../utils/translate'; import TooltipMarkerComponent from '../TooltipMarkerComponent'; @@ -289,208 +288,198 @@ export default function CreateGroupModalComponent(props: CreateGroupModalCompone return ( <> {/* Show modal button */} - - - - - -
- -
-
-
+ + + + +
+ +
+
{/* when any of the group properties are changed call one of the functions. */} {loggedInAsAdmin && // only render when logged in as Admin - -
-
- {/* Modal content */} -
-
- {/* Name input */} -
- - handleStringChange(e)} - required value={state.name} /> -
- {/* default graphic unit input */} - < div style={formInputStyle}> - - handleNumberChange(e)}> - {/* First list the selectable ones and then the rest as disabled. */} - {Array.from(graphicUnitsState.compatibleGraphicUnits).map(unit => { - return () - })} - {Array.from(graphicUnitsState.incompatibleGraphicUnits).map(unit => { - return () - })} - -
- {/* Displayable input */} -
- - handleBooleanChange(e)}> - {Object.keys(TrueFalseType).map(key => { - return () - })} - -
- {/* Area input */} -
- - handleNumberChange(e)} /> -
- {/* meter area unit input */} -
- - handleStringChange(e)}> - {Object.keys(AreaUnitType).map(key => { - return () - })} - -
- {/* Calculate sum of meter areas */} -
- - -
- {/* GPS input */} -
- - handleStringChange(e)} - value={getGPSString(state.gps)} /> -
- {/* Note input */} -
- - handleStringChange(e)} - value={state.note} /> -
- {/* The child meters in this group */} - { -
- : - { - // The meters changed so update the current list of deep meters - // Get the currently included/selected meters as an array of the ids. - const updatedChildMeters = newSelectedMeterOptions.map(meter => { return meter.value; }); - // The id is not really needed so set to -1 since same function for edit. - const newDeepMeters = metersInChangedGroup({ ...state, childMeters: updatedChildMeters, id: -1 }); - // The choice may have invalidated the default graphic unit so it needs - // to be reset to no unit. - // The selection encodes this information in the color but recalculate - // to see if this is the case. - // Get the units compatible with the new set of deep meters in group. - const newAllowedDGU = unitsCompatibleWithMeters(new Set(newDeepMeters)); - // Add no unit (-99) since that is okay so no change needed if current default graphic unit. - newAllowedDGU.add(-99); - let dgu = state.defaultGraphicUnit; - if (!newAllowedDGU.has(dgu)) { - // The current default graphic unit is not compatible so set to no unit and warn admin. - notifyUser(`${translate('group.create.nounit')} "${unitsState[dgu].identifier}"`); - dgu = -99; - } - // Update the deep meter, child meter & default graphic unit state based on the changes. - // Note could update child meters above to avoid updating state value for metersInChangedGroup but want - // to avoid too many state updates. - // It is possible the default graphic unit is unchanged but just do this. - setState({ ...state, deepMeters: newDeepMeters, childMeters: updatedChildMeters, defaultGraphicUnit: dgu }); - }} - /> -
- } - {/* The child groups in this group */} - {
- : - { - // The groups changed so update the current list of deep meters - // Get the currently included/selected meters as an array of the ids. - const updatedChildGroups = newSelectedGroupOptions.map(group => { return group.value; }); - // The id is not really needed so set to -1 since same function for edit. - const newDeepMeters = metersInChangedGroup({ ...state, childGroups: updatedChildGroups, id: -1 }); - // The choice may have invalidated the default graphic unit so it needs - // to be reset to no unit. - // The selection encodes this information in the color but recalculate - // to see if this is the case. - // Get the units compatible with the new set of deep meters in group. - const newAllowedDGU = unitsCompatibleWithMeters(new Set(newDeepMeters)); - // Add no unit (-99) since that is okay so no change needed if current default graphic unit. - newAllowedDGU.add(-99); - let dgu = state.defaultGraphicUnit; - if (!newAllowedDGU.has(dgu)) { - // The current default graphic unit is not compatible so set to no unit and warn admin. - notifyUser(`${translate('group.create.nounit')} "${unitsState[dgu].identifier}"`); - dgu = -99; - } - // Update the deep meter, child meter & default graphic unit state based on the changes. - // Note could update child groups above to avoid updating state value for metersInChangedGroup but want - // to avoid too many state updates. - // It is possible the default graphic unit is unchanged but just do this. - setState({ ...state, deepMeters: newDeepMeters, childGroups: updatedChildGroups, defaultGraphicUnit: dgu }); - }} - /> -
+ + {/* Name input */} +
+ + handleStringChange(e)} + required value={state.name} /> +
+ {/* default graphic unit input */} +
+ + handleNumberChange(e)}> + {/* First list the selectable ones and then the rest as disabled. */} + {Array.from(graphicUnitsState.compatibleGraphicUnits).map(unit => { + return () + })} + {Array.from(graphicUnitsState.incompatibleGraphicUnits).map(unit => { + return () + })} + +
+ {/* Displayable input */} +
+ + handleBooleanChange(e)}> + {Object.keys(TrueFalseType).map(key => { + return () + })} + +
+ {/* Area input */} +
+ + + + handleNumberChange(e)} /> + {/* Calculate sum of meter areas */} + + +
+ {/* meter area unit input */} +
+ + handleStringChange(e)}> + {Object.keys(AreaUnitType).map(key => { + return () + })} + +
+ {/* GPS input */} +
+ + handleStringChange(e)} + value={getGPSString(state.gps)} /> +
+ {/* Note input */} +
+ + handleStringChange(e)} + value={state.note} /> +
+ {/* The child meters in this group */} + { +
+ : + { + // The meters changed so update the current list of deep meters + // Get the currently included/selected meters as an array of the ids. + const updatedChildMeters = newSelectedMeterOptions.map(meter => { return meter.value; }); + // The id is not really needed so set to -1 since same function for edit. + const newDeepMeters = metersInChangedGroup({ ...state, childMeters: updatedChildMeters, id: -1 }); + // The choice may have invalidated the default graphic unit so it needs + // to be reset to no unit. + // The selection encodes this information in the color but recalculate + // to see if this is the case. + // Get the units compatible with the new set of deep meters in group. + const newAllowedDGU = unitsCompatibleWithMeters(new Set(newDeepMeters)); + // Add no unit (-99) since that is okay so no change needed if current default graphic unit. + newAllowedDGU.add(-99); + let dgu = state.defaultGraphicUnit; + if (!newAllowedDGU.has(dgu)) { + // The current default graphic unit is not compatible so set to no unit and warn admin. + notifyUser(`${translate('group.create.nounit')} "${unitsState[dgu].identifier}"`); + dgu = -99; } - {/* All (deep) meters in this group */} -
- : - -
-
-
+ // Update the deep meter, child meter & default graphic unit state based on the changes. + // Note could update child meters above to avoid updating state value for metersInChangedGroup but want + // to avoid too many state updates. + // It is possible the default graphic unit is unchanged but just do this. + setState({ ...state, deepMeters: newDeepMeters, childMeters: updatedChildMeters, defaultGraphicUnit: dgu }); + }} + />
+ } + {/* The child groups in this group */} + {
+ : + { + // The groups changed so update the current list of deep meters + // Get the currently included/selected meters as an array of the ids. + const updatedChildGroups = newSelectedGroupOptions.map(group => { return group.value; }); + // The id is not really needed so set to -1 since same function for edit. + const newDeepMeters = metersInChangedGroup({ ...state, childGroups: updatedChildGroups, id: -1 }); + // The choice may have invalidated the default graphic unit so it needs + // to be reset to no unit. + // The selection encodes this information in the color but recalculate + // to see if this is the case. + // Get the units compatible with the new set of deep meters in group. + const newAllowedDGU = unitsCompatibleWithMeters(new Set(newDeepMeters)); + // Add no unit (-99) since that is okay so no change needed if current default graphic unit. + newAllowedDGU.add(-99); + let dgu = state.defaultGraphicUnit; + if (!newAllowedDGU.has(dgu)) { + // The current default graphic unit is not compatible so set to no unit and warn admin. + notifyUser(`${translate('group.create.nounit')} "${unitsState[dgu].identifier}"`); + dgu = -99; + } + // Update the deep meter, child meter & default graphic unit state based on the changes. + // Note could update child groups above to avoid updating state value for metersInChangedGroup but want + // to avoid too many state updates. + // It is possible the default graphic unit is unchanged but just do this. + setState({ ...state, deepMeters: newDeepMeters, childGroups: updatedChildGroups, defaultGraphicUnit: dgu }); + }} + /> +
+ } + {/* All (deep) meters in this group */} +
+ : +
- } - + } + {/* Hides the modal */} - {/* On click calls the function handleSaveChanges in this component */} - - + ); diff --git a/src/client/app/components/groups/EditGroupModalComponent.tsx b/src/client/app/components/groups/EditGroupModalComponent.tsx index 22c343179..5c166a080 100644 --- a/src/client/app/components/groups/EditGroupModalComponent.tsx +++ b/src/client/app/components/groups/EditGroupModalComponent.tsx @@ -10,8 +10,7 @@ import MultiSelectComponent from '../MultiSelectComponent'; import { SelectOption } from '../../types/items'; import { useDispatch, useSelector } from 'react-redux'; import { State } from 'types/redux/state'; -import { Modal, Button } from 'react-bootstrap'; -import { Input } from 'reactstrap'; +import { Button, Input, InputGroup, Modal, ModalBody, ModalFooter, ModalHeader } from 'reactstrap'; import { FormattedMessage } from 'react-intl'; import translate from '../../utils/translate'; import TooltipMarkerComponent from '../TooltipMarkerComponent'; @@ -29,12 +28,12 @@ import { } from '../../utils/determineCompatibleUnits'; import { ConversionArray } from '../../types/conversionArray'; import { GPSPoint, isValidGPSInput } from '../../utils/calibration'; -import { notifyUser, getGPSString, nullToEmptyString, noUnitTranslated } from '../../utils/input'; +import { notifyUser, getGPSString, nullToEmptyString } from '../../utils/input'; import { GroupDefinition } from '../../types/redux/groups'; import ConfirmActionModalComponent from '../ConfirmActionModalComponent' import { DataType } from '../../types/Datasources'; import { groupsApi } from '../../utils/api'; -import { formInputStyle, tableStyle, requiredStyle, tooltipBaseStyle } from '../../styles/modalStyle'; +import { tableStyle, requiredStyle, tooltipBaseStyle, formInputStyle } from '../../styles/modalStyle'; import { AreaUnitType, getAreaUnitConversion } from '../../utils/getAreaUnitConversion'; interface EditGroupModalComponentProps { @@ -52,8 +51,6 @@ export default function EditGroupModalComponent(props: EditGroupModalComponentPr // Meter state const metersState = useSelector((state: State) => state.meters.byMeterID); - // unit state - const unitState = useSelector((state: State) => state.units.units); // Group state used on other pages const globalGroupsState = useSelector((state: State) => state.groups.byGroupID); // Make a local copy of the group data so we can update during the edit process. @@ -399,257 +396,222 @@ export default function EditGroupModalComponent(props: EditGroupModalComponentPr actionFunction={handleDeleteGroup} actionConfirmText={deleteConfirmText} actionRejectText={deleteRejectText} /> - + {/* In a number of the items that follow, what is shown varies on whether you are an admin. */} - - - -
- + + + +
+ +
+
+ + {/* Name input, disabled if not admin */} +
+ + handleStringChange(e)} + required value={groupState.name} + disabled={!loggedInAsAdmin}/> +
+ {/* default graphic unit input, disabled if not admin */} +
+ + handleNumberChange(e)} + disabled={!loggedInAsAdmin}> + {/* First list the selectable ones and then the rest as disabled. */} + {Array.from(graphicUnitsState.compatibleGraphicUnits).map(unit => { + return () + })} + {Array.from(graphicUnitsState.incompatibleGraphicUnits).map(unit => { + return () + })} + +
+ {loggedInAsAdmin && <> + {/* Displayable input, only for admin. */} +
+ + handleBooleanChange(e)}> + {Object.keys(TrueFalseType).map(key => { + return () + })} +
- - - -
-
- {/* Modal content */} -
-
- {/* Name where input if admin or shown if now */} - {loggedInAsAdmin ? -
- - handleStringChange(e)} - required value={groupState.name} /> -
- : -
- {groupState.name} -
- } - {/* default graphic unit input or display */} - {loggedInAsAdmin ? - < div style={formInputStyle}> - - handleNumberChange(e)}> - {/* First list the selectable ones and then the rest as disabled. */} - {Array.from(graphicUnitsState.compatibleGraphicUnits).map(unit => { - return () - })} - {Array.from(graphicUnitsState.incompatibleGraphicUnits).map(unit => { - return () - })} - -
- : -
- {/* Use meter translation id string since same one wanted. */} - {/* This is the default graphic unit associated with the group or no unit if none. */} - - {/* Not exactly sure why but must force a starting space after the label */} - {groupState.defaultGraphicUnit === -99 ? ' ' + noUnitTranslated().identifier : ' ' + unitState[groupState.defaultGraphicUnit].identifier} -
- } - {/* Displayable input, only for admin. */} - {loggedInAsAdmin && -
- - handleBooleanChange(e)}> - {Object.keys(TrueFalseType).map(key => { - return () - })} - -
- } - {/* Area input, only for admin. */} - {loggedInAsAdmin && -
- - handleNumberChange(e)} /> -
- } - {/* meter area unit input */} - {loggedInAsAdmin && -
- - handleStringChange(e)}> - {Object.keys(AreaUnitType).map(key => { - return () - })} - -
- } - {/* Calculate sum of meter areas */} - {loggedInAsAdmin && -
- - -
- } - {/* GPS input, only for admin. */} - {loggedInAsAdmin && -
- - handleStringChange(e)} - value={getGPSString(groupState.gps)} /> -
- } - {/* Note input, only for admin. */} - {loggedInAsAdmin && -
- - handleStringChange(e)} - value={nullToEmptyString(groupState.note)} /> -
- } - {/* The child meters in this group */} - {loggedInAsAdmin ? -
- : - { - // The meters changed so verify update is okay and deal with appropriately. - // The length of selected meters should only vary by 1 since each change is handled separately. - // Compare the new length to the original length that is the same as - // the number of child meters of group being edited. - if (newSelectedMeterOptions.length === groupState.childMeters.length + 1) { - // A meter was selected so it is considered for adding. - // The newly selected item is always the last one. - // Now attempt to add the child to see if okay. - const childAdded = await assignChildToGroup(newSelectedMeterOptions[newSelectedMeterOptions.length - 1].value, DataType.Meter); - if (!childAdded) { - // The new child meter was rejected so remove it. It is the last one. - newSelectedMeterOptions.pop(); - } - } else { - // Could have removed any item so figure out which one it is. Need to convert options to ids. - const removedMeter = _.difference(groupState.childMeters, newSelectedMeterOptions.map(item => { return item.value; })); - // There should only be one removed item. - const removedMeterId = removedMeter[0]; - const childRemoved = removeChildFromGroup(removedMeterId, DataType.Meter) - if (!childRemoved) { - // The new child meter removal was rejected so put it back. Should only be one item so no need to sort. - newSelectedMeterOptions.push({ - value: removedMeterId, - label: metersState[removedMeterId].identifier - // isDisabled not needed since only used for selected and not display. - } as SelectOption - ); - } - } - }} - /> -
- : -
- : - -
+ {/* Area input, only for admin. */} +
+ + + + handleNumberChange(e)} /> + {/* Calculate sum of meter areas */} + + +
+ {/* meter area unit input */} +
+ + handleStringChange(e)}> + {Object.keys(AreaUnitType).map(key => { + return () + })} + +
+ {/* GPS input, only for admin. */} +
+ + handleStringChange(e)} + value={getGPSString(groupState.gps)} /> +
+ {/* Note input, only for admin. */} +
+ + handleStringChange(e)} + value={nullToEmptyString(groupState.note)} /> +
+ } + {/* The child meters in this group */} + {loggedInAsAdmin ? +
+ : + { + // The meters changed so verify update is okay and deal with appropriately. + // The length of selected meters should only vary by 1 since each change is handled separately. + // Compare the new length to the original length that is the same as + // the number of child meters of group being edited. + if (newSelectedMeterOptions.length === groupState.childMeters.length + 1) { + // A meter was selected so it is considered for adding. + // The newly selected item is always the last one. + // Now attempt to add the child to see if okay. + const childAdded = await assignChildToGroup(newSelectedMeterOptions[newSelectedMeterOptions.length - 1].value, DataType.Meter); + if (!childAdded) { + // The new child meter was rejected so remove it. It is the last one. + newSelectedMeterOptions.pop(); + } + } else { + // Could have removed any item so figure out which one it is. Need to convert options to ids. + const removedMeter = _.difference(groupState.childMeters, newSelectedMeterOptions.map(item => { return item.value; })); + // There should only be one removed item. + const removedMeterId = removedMeter[0]; + const childRemoved = removeChildFromGroup(removedMeterId, DataType.Meter) + if (!childRemoved) { + // The new child meter removal was rejected so put it back. Should only be one item so no need to sort. + newSelectedMeterOptions.push({ + value: removedMeterId, + label: metersState[removedMeterId].identifier + // isDisabled not needed since only used for selected and not display. + } as SelectOption + ); + } } - {/* The child groups in this group */} - {loggedInAsAdmin ? -
- : - { - // The groups changed so verify update is okay and deal with appropriately. - // The length of of selected groups should only vary by 1 since each change is handled separately. - // Compare the new length to the original length that is the same as - // the number of child groups of group being edited. - if (newSelectedGroupOptions.length === groupState.childGroups.length + 1) { - // A group was selected so it is considered for adding. - // The newly selected item is always the last one. - // Now attempt to add the child to see if okay. - const childAdded = await assignChildToGroup(newSelectedGroupOptions[newSelectedGroupOptions.length - 1].value, DataType.Group); - if (!childAdded) { - // The new child meter was rejected so remove it. It is the last one. - newSelectedGroupOptions.pop(); - } - } else { - // Could have removed any item so figure out which one it is. Need to convert options to ids. - const removedGroup = _.difference(groupState.childGroups, newSelectedGroupOptions.map(item => { return item.value; })); - // There should only be one removed item. - const removedGroupId = removedGroup[0]; - const childRemoved = removeChildFromGroup(removedGroupId, DataType.Group) - if (!childRemoved) { - // The new child group removal was rejected so put it back. Should only be one item so no need to sort. - newSelectedGroupOptions.push({ - value: removedGroupId, - // The name should not have changed since cannot be group editing but use the edit state to be consistent. - label: editGroupsState[removedGroupId].name - // isDisabled not needed since only used for selected and not display. - } as SelectOption - ); - } - } - }} - /> -
- : -
- : - -
+ }} + /> +
+ : +
+ : + +
+ } + {/* The child groups in this group */} + {loggedInAsAdmin ? +
+ : + { + // The groups changed so verify update is okay and deal with appropriately. + // The length of of selected groups should only vary by 1 since each change is handled separately. + // Compare the new length to the original length that is the same as + // the number of child groups of group being edited. + if (newSelectedGroupOptions.length === groupState.childGroups.length + 1) { + // A group was selected so it is considered for adding. + // The newly selected item is always the last one. + // Now attempt to add the child to see if okay. + const childAdded = await assignChildToGroup(newSelectedGroupOptions[newSelectedGroupOptions.length - 1].value, DataType.Group); + if (!childAdded) { + // The new child meter was rejected so remove it. It is the last one. + newSelectedGroupOptions.pop(); + } + } else { + // Could have removed any item so figure out which one it is. Need to convert options to ids. + const removedGroup = _.difference(groupState.childGroups, newSelectedGroupOptions.map(item => { return item.value; })); + // There should only be one removed item. + const removedGroupId = removedGroup[0]; + const childRemoved = removeChildFromGroup(removedGroupId, DataType.Group) + if (!childRemoved) { + // The new child group removal was rejected so put it back. Should only be one item so no need to sort. + newSelectedGroupOptions.push({ + value: removedGroupId, + // The name should not have changed since cannot be group editing but use the edit state to be consistent. + label: editGroupsState[removedGroupId].name + // isDisabled not needed since only used for selected and not display. + } as SelectOption + ); + } } - {/* All (deep) meters in this group */} -
- : - -
-
-
+ }} + />
-
-
- + : +
+ : + +
+ } + {/* All (deep) meters in this group */} + : + +
+ {/* Delete, discard & save buttons if admin and close button if not. */} {loggedInAsAdmin ?
{/* delete group */} - {/* Hides the modal */} - {/* On click calls the function handleSaveChanges in this component */} -
@@ -658,7 +620,7 @@ export default function EditGroupModalComponent(props: EditGroupModalComponentPr } - +
); diff --git a/src/client/app/components/meters/CreateMeterModalComponent.tsx b/src/client/app/components/meters/CreateMeterModalComponent.tsx index 891b654f6..df826f3c0 100644 --- a/src/client/app/components/meters/CreateMeterModalComponent.tsx +++ b/src/client/app/components/meters/CreateMeterModalComponent.tsx @@ -2,8 +2,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import * as React from 'react'; -import { Modal, Button } from 'react-bootstrap'; -import { Input } from 'reactstrap'; +import { Button, Input, Modal, ModalBody, ModalFooter, ModalHeader } from 'reactstrap'; import { FormattedMessage } from 'react-intl'; import translate from '../../utils/translate'; import { useDispatch, useSelector } from 'react-redux'; @@ -325,364 +324,353 @@ export default function CreateMeterModalComponent(props: CreateMeterModalCompone return ( <> {/* Show modal button */} - - - - - - -
- -
-
-
+ + + + +
+ +
+
{/* when any of the Meter values are changed call one of the functions. */} {loggedInAsAdmin && // only render when logged in as Admin - -
-
- {/* Modal content */} -
-
- {/* Identifier input */} -
- - handleStringChange(e)} - value={state.identifier} /> -
- {/* Name input */} -
- - handleStringChange(e)} - required value={state.name} /> -
- {/* meter unit input */} -
- - handleNumberChange(e)}> - {} - {Array.from(dropdownsState.compatibleUnits).map(unit => { - return () - })} - {Array.from(dropdownsState.incompatibleUnits).map(unit => { - return () - })} - -
- {/* default graphic unit input */} -
- - handleNumberChange(e)}> - {} - {Array.from(dropdownsState.compatibleGraphicUnits).map(unit => { - return () - })} - {Array.from(dropdownsState.incompatibleGraphicUnits).map(unit => { - return () - })} - -
- {/* Enabled input */} -
- - handleBooleanChange(e)}> - {Object.keys(TrueFalseType).map(key => { - return () - })} - -
- {/* Displayable input */} -
- - handleBooleanChange(e)}> - {Object.keys(TrueFalseType).map(key => { - return () - })} - -
- {/* Meter type input */} -
- - handleStringChange(e)}> - {/* The default value is a blank string so then tell user to select one. */} - {} - {/* The dB expects lowercase. */} - {Object.keys(MeterType).map(key => { - return () - })} - -
- {/* Meter reading frequency */} -
- - handleStringChange(e)} - value={state.readingFrequency} /> -
- {/* URL input */} -
- - handleStringChange(e)} - value={state.url} /> -
- {/* Area input */} -
- - handleNumberChange(e)} /> -
- {/* meter area unit input */} -
- - handleStringChange(e)}> - {Object.keys(AreaUnitType).map(key => { - return () - })} - -
- {/* GPS input */} -
- - handleStringChange(e)} - value={state.gps} /> -
- {/* note input */} -
- - handleStringChange(e)} - value={state.note} - placeholder='Note' /> -
- {/* cumulative input */} -
- - handleBooleanChange(e)}> - {Object.keys(TrueFalseType).map(key => { - return () - })} - -
- {/* cumulativeReset input */} -
- - handleBooleanChange(e)}> - {Object.keys(TrueFalseType).map(key => { - return () - })} - -
- {/* cumulativeResetStart input */} -
- - handleStringChange(e)} - value={state.cumulativeResetStart} - placeholder="HH:MM:SS" /> -
- {/* cumulativeResetEnd input */} -
- - handleStringChange(e)} - value={state.cumulativeResetEnd} - placeholder="HH:MM:SS" /> -
- {/* endOnlyTime input */} -
- - handleBooleanChange(e)}> - {Object.keys(TrueFalseType).map(key => { - return () - })} - -
- {/* readingGap input */} -
- - handleNumberChange(e)} - min="0" - defaultValue={state.readingGap} /> -
- {/* readingVariation input */} -
- - handleNumberChange(e)} - min="0" - defaultValue={state.readingVariation} /> -
- {/* readingDuplication input */} -
- - handleNumberChange(e)} - step="1" - min="1" - max="9" - defaultValue={state.readingDuplication} /> -
- {/* timeSort input */} -
- - handleStringChange(e)}> - {Object.keys(MeterTimeSortType).map(key => { - // This is a bit of a hack but it should work fine. The TypeSortTypes and MeterTimeSortType should be in sync. - // The translation is on the former so we use that enum name there but loop on the other to get the value desired. - return () - })} - -
- {/* Timezone input */} -
- - handleTimeZoneChange(timeZone)} /> -
- {/* reading input */} -
- - handleNumberChange(e)} - defaultValue={state.reading} /> -
- {/* startTimestamp input */} -
- - handleStringChange(e)} - placeholder="YYYY-MM-DD HH:MM:SS" - value={state.startTimestamp} /> -
- {/* endTimestamp input */} -
- - handleStringChange(e)} - placeholder="YYYY-MM-DD HH:MM:SS" - value={state.endTimestamp} /> -
- {/* endTimestamp input */} -
- - handleStringChange(e)} - placeholder="YYYY-MM-DD HH:MM:SS" - value={state.previousEnd} /> -
-
-
-
+ + {/* Identifier input */} +
+ + handleStringChange(e)} + value={state.identifier} /> +
+ {/* Name input */} +
+ + handleStringChange(e)} + required value={state.name} /> +
+ {/* meter unit input */} +
+ + handleNumberChange(e)}> + {} + {Array.from(dropdownsState.compatibleUnits).map(unit => { + return () + })} + {Array.from(dropdownsState.incompatibleUnits).map(unit => { + return () + })} + +
+ {/* default graphic unit input */} +
+ + handleNumberChange(e)}> + {} + {Array.from(dropdownsState.compatibleGraphicUnits).map(unit => { + return () + })} + {Array.from(dropdownsState.incompatibleGraphicUnits).map(unit => { + return () + })} + +
+ {/* Enabled input */} +
+ + handleBooleanChange(e)}> + {Object.keys(TrueFalseType).map(key => { + return () + })} + +
+ {/* Displayable input */} +
+ + handleBooleanChange(e)}> + {Object.keys(TrueFalseType).map(key => { + return () + })} + +
+ {/* Meter type input */} +
+ + handleStringChange(e)}> + {/* The default value is a blank string so then tell user to select one. */} + {} + {/* The dB expects lowercase. */} + {Object.keys(MeterType).map(key => { + return () + })} + +
+ {/* Meter reading frequency */} +
+ + handleStringChange(e)} + value={state.readingFrequency} /> +
+ {/* URL input */} +
+ + handleStringChange(e)} + value={state.url} /> +
+ {/* Area input */} +
+ + handleNumberChange(e)} /> +
+ {/* meter area unit input */} +
+ + handleStringChange(e)}> + {Object.keys(AreaUnitType).map(key => { + return () + })} + +
+ {/* GPS input */} +
+ + handleStringChange(e)} + value={state.gps} /> +
+ {/* note input */} +
+ + handleStringChange(e)} + value={state.note} + placeholder='Note' /> +
+ {/* cumulative input */} +
+ + handleBooleanChange(e)}> + {Object.keys(TrueFalseType).map(key => { + return () + })} + +
+ {/* cumulativeReset input */} +
+ + handleBooleanChange(e)}> + {Object.keys(TrueFalseType).map(key => { + return () + })} + +
+ {/* cumulativeResetStart input */} +
+ + handleStringChange(e)} + value={state.cumulativeResetStart} + placeholder="HH:MM:SS" /> +
+ {/* cumulativeResetEnd input */} +
+ + handleStringChange(e)} + value={state.cumulativeResetEnd} + placeholder="HH:MM:SS" /> +
+ {/* endOnlyTime input */} +
+ + handleBooleanChange(e)}> + {Object.keys(TrueFalseType).map(key => { + return () + })} + +
+ {/* readingGap input */} +
+ + handleNumberChange(e)} + min="0" + defaultValue={state.readingGap} /> +
+ {/* readingVariation input */} +
+ + handleNumberChange(e)} + min="0" + defaultValue={state.readingVariation} /> +
+ {/* readingDuplication input */} +
+ + handleNumberChange(e)} + step="1" + min="1" + max="9" + defaultValue={state.readingDuplication} /> +
+ {/* timeSort input */} +
+ + handleStringChange(e)}> + {Object.keys(MeterTimeSortType).map(key => { + // This is a bit of a hack but it should work fine. The TypeSortTypes and MeterTimeSortType should be in sync. + // The translation is on the former so we use that enum name there but loop on the other to get the value desired. + return () + })} + +
+ {/* Timezone input */} +
+ + handleTimeZoneChange(timeZone)} /> +
+ {/* reading input */} +
+ + handleNumberChange(e)} + defaultValue={state.reading} /> +
+ {/* startTimestamp input */} +
+ + handleStringChange(e)} + placeholder="YYYY-MM-DD HH:MM:SS" + value={state.startTimestamp} /> +
+ {/* endTimestamp input */} +
+ + handleStringChange(e)} + placeholder="YYYY-MM-DD HH:MM:SS" + value={state.endTimestamp} /> +
+ {/* endTimestamp input */} +
+ + handleStringChange(e)} + placeholder="YYYY-MM-DD HH:MM:SS" + value={state.previousEnd} />
- } - +
} + {/* Hides the modal */} - {/* On click calls the function handleSaveChanges in this component */} - - + ); diff --git a/src/client/app/components/meters/EditMeterModalComponent.tsx b/src/client/app/components/meters/EditMeterModalComponent.tsx index 5d7565bc4..3ca470eb7 100644 --- a/src/client/app/components/meters/EditMeterModalComponent.tsx +++ b/src/client/app/components/meters/EditMeterModalComponent.tsx @@ -3,8 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import * as React from 'react'; -import { Modal, Button } from 'react-bootstrap'; -import { Input } from 'reactstrap'; +import { Button, FormFeedback, Input, Modal, ModalBody, ModalFooter, ModalHeader } from 'reactstrap'; import { FormattedMessage } from 'react-intl'; import translate from '../../utils/translate'; import { useDispatch, useSelector } from 'react-redux'; @@ -109,6 +108,17 @@ export default function EditMeterModalComponent(props: EditMeterModalComponentPr // Dropdowns const [dropdownsState, setDropdownsState] = useState(dropdownsStateDefaults); + + const [validMeter, setValidMeter] = useState(false); + useEffect(() => { + setValidMeter( + state.name !== '' && + (state.area === 0 || (state.area > 0 && state.areaUnit !== AreaUnitType.none)) && + state.readingGap >= 0 && + state.readingVariation >= 0 + && (state.readingDuplication >= 1 && state.readingDuplication <= 9) + ); + }, [state.area, state.name, state.readingGap, state.readingVariation, state.readingDuplication, state.areaUnit]); /* End State */ // Reset the state to default values @@ -170,40 +180,11 @@ export default function EditMeterModalComponent(props: EditMeterModalComponentPr // Only validate and store if any changes. if (meterHasChanges) { - // TODO Maybe should do as a single popup? // Set default identifier as name if left blank state.identifier = (!state.identifier || state.identifier.length === 0) ? state.name : state.identifier; - // Check if area is non-negative - if (state.area < 0) { - notifyUser(translate('area.invalid') + state.area + '.'); - inputOk = false; - } else if (state.area > 0 && state.areaUnit === AreaUnitType.none) { - // If the meter has an assigned area, it must have a unit - notifyUser(translate('area.but.no.unit')); - inputOk = false; - } - - // Check reading gap is at least zero. - if (state.readingGap < 0) { - notifyUser(translate('reading.gap.invalid') + state.readingGap + '.'); - inputOk = false; - } - - // Check reading variation is at least zero. - if (state.readingVariation < 0) { - notifyUser(translate('reading.variation.invalid') + state.readingVariation + '.'); - inputOk = false; - } - - // Check reading duplication is between 1 and 9. - if (state.readingDuplication < 1 || state.readingDuplication > 9) { - notifyUser(translate('duplication.invalid') + state.area + '.'); - inputOk = false; - } - // Check GPS entered. // Validate GPS is okay and take from string to GPSPoint to submit. const gpsInput = state.gps; @@ -342,344 +323,355 @@ export default function EditMeterModalComponent(props: EditMeterModalComponentPr return ( <> - - - - -
- -
-
-
- {/* when any of the meter are changed call one of the functions. */} + + + + +
+ +
+
+ {/* when any of the Meter values are changed call one of the functions. */} {loggedInAsAdmin && // only render when logged in as Admin - -
-
- {/* Modal content */} -
-
- {/* Identifier input */} -
- - handleStringChange(e)} - value={state.identifier} /> -
- {/* Name input */} -
- - handleStringChange(e)} - required value={state.name} /> -
- {/* meter unit input */} -
- - handleNumberChange(e)}> - {Array.from(dropdownsState.compatibleUnits).map(unit => { - return () - })} - {Array.from(dropdownsState.incompatibleUnits).map(unit => { - return () - })} - -
- {/* default graphic unit input */} -
- - handleNumberChange(e)}> - {Array.from(dropdownsState.compatibleGraphicUnits).map(unit => { - return () - })} - {Array.from(dropdownsState.incompatibleGraphicUnits).map(unit => { - return () - })} - -
- {/* Enabled input */} -
- - handleBooleanChange(e)}> - {Object.keys(TrueFalseType).map(key => { - return () - })} - -
- {/* Displayable input */} -
- - handleBooleanChange(e)}> - {Object.keys(TrueFalseType).map(key => { - return () - })} - -
- {/* Meter type input */} -
- - handleStringChange(e)}> - {/* The dB expects lowercase. */} - {Object.keys(MeterType).map(key => { - return () - })} - -
- {/* Meter reading frequency */} -
- - handleStringChange(e)} - value={state.readingFrequency} /> -
- {/* URL input */} -
- - handleStringChange(e)} - value={nullToEmptyString(state.url)} /> -
- {/* Area input */} -
- - handleNumberChange(e)} /> -
- {/* meter area unit input */} -
- - handleStringChange(e)}> - {Object.keys(AreaUnitType).map(key => { - return () - })} - -
- {/* GPS input */} -
- - handleStringChange(e)} - value={getGPSString(state.gps)} /> -
- {/* note input */} -
- - handleStringChange(e)} - value={nullToEmptyString(state.note)} - placeholder='Note' /> -
- {/* cumulative input */} -
- - handleBooleanChange(e)}> - {Object.keys(TrueFalseType).map(key => { - return () - })} - -
- {/* cumulativeReset input */} -
- - handleBooleanChange(e)}> - {Object.keys(TrueFalseType).map(key => { - return () - })} - -
- {/* cumulativeResetStart input */} -
- - handleStringChange(e)} - value={state.cumulativeResetStart} - placeholder="HH:MM:SS" /> -
- {/* cumulativeResetEnd input */} -
- - handleStringChange(e)} - value={state?.cumulativeResetEnd} - placeholder="HH:MM:SS" /> -
- {/* endOnlyTime input */} -
- - handleBooleanChange(e)}> - {Object.keys(TrueFalseType).map(key => { - return () - })} - -
- {/* readingGap input */} -
- - handleNumberChange(e)} - min="0" - value={state?.readingGap} /> -
- {/* readingVariation input */} -
- - handleNumberChange(e)} - min="0" - defaultValue={state?.readingVariation} /> -
- {/* readingDuplication input */} -
- - handleNumberChange(e)} - step="1" - min="1" - max="9" - defaultValue={state?.readingDuplication} /> -
- {/* timeSort input */} -
- - handleStringChange(e)}> - {Object.keys(MeterTimeSortType).map(key => { - // This is a bit of a hack but it should work fine. The TypeSortTypes and MeterTimeSortType should be in sync. - // The translation is on the former so we use that enum name there but loop on the other to get the value desired. - return () - })} - -
- {/* Timezone input */} -
- - handleTimeZoneChange(timeZone)} /> -
- {/* reading input */} -
- - handleNumberChange(e)} - defaultValue={state?.reading} /> -
- {/* startTimestamp input */} -
- - handleStringChange(e)} - placeholder="YYYY-MM-DD HH:MM:SS" - value={state?.startTimestamp} /> -
- {/* endTimestamp input */} -
- - handleStringChange(e)} - placeholder="YYYY-MM-DD HH:MM:SS" - value={state?.endTimestamp} /> -
- {/* previousEnd input */} -
- - handleStringChange(e)} - placeholder="YYYY-MM-DD HH:MM:SS" - value={state?.previousEnd} /> -
-
-
-
+ + {/* Identifier input */} +
+ + handleStringChange(e)} + value={state.identifier} /> +
+ {/* Name input */} +
+ + handleStringChange(e)} + required value={state.name} /> +
+ {/* meter unit input */} +
+ + handleNumberChange(e)}> + {Array.from(dropdownsState.compatibleUnits).map(unit => { + return () + })} + {Array.from(dropdownsState.incompatibleUnits).map(unit => { + return () + })} + +
+ {/* default graphic unit input */} +
+ + handleNumberChange(e)}> + {Array.from(dropdownsState.compatibleGraphicUnits).map(unit => { + return () + })} + {Array.from(dropdownsState.incompatibleGraphicUnits).map(unit => { + return () + })} + +
+ {/* Enabled input */} +
+ + handleBooleanChange(e)}> + {Object.keys(TrueFalseType).map(key => { + return () + })} + +
+ {/* Displayable input */} +
+ + handleBooleanChange(e)}> + {Object.keys(TrueFalseType).map(key => { + return () + })} + +
+ {/* Meter type input */} +
+ + handleStringChange(e)}> + {/* The dB expects lowercase. */} + {Object.keys(MeterType).map(key => { + return () + })} + +
+ {/* Meter reading frequency */} +
+ + handleStringChange(e)} + value={state.readingFrequency} /> +
+ {/* URL input */} +
+ + handleStringChange(e)} + value={nullToEmptyString(state.url)} />
- } - + {/* Area input */} +
+ + handleNumberChange(e)} + invalid={state.area < 0} /> + + + +
+ {/* meter area unit input */} +
+ + handleStringChange(e)} + invalid={state.area > 0 && state.areaUnit === AreaUnitType.none}> + {Object.keys(AreaUnitType).map(key => { + return () + })} + + + + +
+ {/* GPS input */} +
+ + handleStringChange(e)} + value={getGPSString(state.gps)} /> +
+ {/* note input */} +
+ + handleStringChange(e)} + value={nullToEmptyString(state.note)} + placeholder='Note' /> +
+ {/* cumulative input */} +
+ + handleBooleanChange(e)}> + {Object.keys(TrueFalseType).map(key => { + return () + })} + +
+ {/* cumulativeReset input */} +
+ + handleBooleanChange(e)}> + {Object.keys(TrueFalseType).map(key => { + return () + })} + +
+ {/* cumulativeResetStart input */} +
+ + handleStringChange(e)} + value={state.cumulativeResetStart} + placeholder="HH:MM:SS" /> +
+ {/* cumulativeResetEnd input */} +
+ + handleStringChange(e)} + value={state?.cumulativeResetEnd} + placeholder="HH:MM:SS" /> +
+ {/* endOnlyTime input */} +
+ + handleBooleanChange(e)}> + {Object.keys(TrueFalseType).map(key => { + return () + })} + +
+ {/* readingGap input */} +
+ + handleNumberChange(e)} + min="0" + defaultValue={state?.readingGap} + invalid={state?.readingGap < 0}/> + + + +
+ {/* readingVariation input */} +
+ + handleNumberChange(e)} + min="0" + defaultValue={state?.readingVariation} + invalid={state?.readingVariation < 0} /> + + + +
+ {/* readingDuplication input */} +
+ + handleNumberChange(e)} + step="1" + min="1" + max="9" + defaultValue={state?.readingDuplication} + invalid={state?.readingDuplication < 1 || state?.readingDuplication > 9}/> + + + +
+ {/* timeSort input */} +
+ + handleStringChange(e)}> + {Object.keys(MeterTimeSortType).map(key => { + // This is a bit of a hack but it should work fine. The TypeSortTypes and MeterTimeSortType should be in sync. + // The translation is on the former so we use that enum name there but loop on the other to get the value desired. + return () + })} + +
+ {/* Timezone input */} +
+ + handleTimeZoneChange(timeZone)} /> +
+ {/* reading input */} +
+ + handleNumberChange(e)} + defaultValue={state?.reading} /> +
+ {/* startTimestamp input */} +
+ + handleStringChange(e)} + placeholder="YYYY-MM-DD HH:MM:SS" + value={state?.startTimestamp} /> +
+ {/* endTimestamp input */} +
+ + handleStringChange(e)} + placeholder="YYYY-MM-DD HH:MM:SS" + value={state?.endTimestamp} /> +
+ {/* previousEnd input */} +
+ + handleStringChange(e)} + placeholder="YYYY-MM-DD HH:MM:SS" + value={state?.previousEnd} /> +
+
+ } + {/* Hides the modal */} - {/* On click calls the function handleSaveChanges in this component */} - - + ); diff --git a/src/client/app/components/unit/CreateUnitModalComponent.tsx b/src/client/app/components/unit/CreateUnitModalComponent.tsx index 0494683a6..0282610a4 100644 --- a/src/client/app/components/unit/CreateUnitModalComponent.tsx +++ b/src/client/app/components/unit/CreateUnitModalComponent.tsx @@ -4,8 +4,7 @@ import * as React from 'react'; import { useDispatch } from 'react-redux'; import { useState } from 'react'; -import { Modal, Button } from 'react-bootstrap'; -import { Input } from 'reactstrap'; +import { Button, Input, Modal, ModalBody, ModalFooter, ModalHeader } from 'reactstrap'; import { FormattedMessage } from 'react-intl'; import translate from '../../utils/translate'; import '../../styles/modal.css'; @@ -95,140 +94,129 @@ export default function CreateUnitModalComponent() { return ( <> {/* Show modal button */} - - - - - - -
- -
-
-
+ + + + +
+ +
+
{/* when any of the unit properties are changed call one of the functions. */} - -
-
- {/* Modal content */} -
-
- {/* Identifier input */} -
- - handleStringChange(e)} - value={state.identifier} /> -
- {/* Name input */} -
- - handleStringChange(e)} - value={state.name} /> -
- {/* Type of unit input */} -
- - handleStringChange(e)} - value={state.typeOfUnit}> - {Object.keys(UnitType).map(key => { - return () - })} - -
- {/* Unit represent input */} -
- - handleStringChange(e)} - value={state.unitRepresent}> - {Object.keys(UnitRepresentType).map(key => { - return () - })} - -
- {/* Displayable type input */} -
- - handleStringChange(e)} - value={state.displayable} > - {Object.keys(DisplayableType).map(key => { - return () - })} - -
- {/* Preferred display input */} -
- - handleBooleanChange(e)}> - {Object.keys(TrueFalseType).map(key => { - return () - })} - -
- {/* Seconds in rate input */} -
- - handleNumberChange(e)} - value={state.secInRate} - // TODO validate negative input by typing for rate but database stops it. - // This stops negative input by use of arrows to change value. - min="1" /> -
- {/* Suffix input */} -
- - handleStringChange(e)} - value={state.suffix} /> -
- {/* Note input */} -
- - handleStringChange(e)} - value={state.note} /> -
-
-
-
+ + {/* Identifier input */} +
+ + handleStringChange(e)} + value={state.identifier} /> +
+ {/* Name input */} +
+ + handleStringChange(e)} + value={state.name} /> +
+ {/* Type of unit input */} +
+ + handleStringChange(e)} + value={state.typeOfUnit}> + {Object.keys(UnitType).map(key => { + return () + })} + +
+ {/* Unit represent input */} +
+ + handleStringChange(e)} + value={state.unitRepresent}> + {Object.keys(UnitRepresentType).map(key => { + return () + })} + +
+ {/* Displayable type input */} +
+ + handleStringChange(e)} + value={state.displayable} > + {Object.keys(DisplayableType).map(key => { + return () + })} + +
+ {/* Preferred display input */} +
+ + handleBooleanChange(e)}> + {Object.keys(TrueFalseType).map(key => { + return () + })} + +
+ {/* Seconds in rate input */} +
+ + handleNumberChange(e)} + value={state.secInRate} + // TODO validate negative input by typing for rate but database stops it. + // This stops negative input by use of arrows to change value. + min="1" /> +
+ {/* Suffix input */} +
+ + handleStringChange(e)} + value={state.suffix} /> +
+ {/* Note input */} +
+ + handleStringChange(e)} + value={state.note} />
- - +
+ {/* Hides the modal */} - {/* On click calls the function handleSaveChanges in this component */} - - + ); diff --git a/src/client/app/components/unit/EditUnitModalComponent.tsx b/src/client/app/components/unit/EditUnitModalComponent.tsx index 2b57532e0..06bf0def1 100644 --- a/src/client/app/components/unit/EditUnitModalComponent.tsx +++ b/src/client/app/components/unit/EditUnitModalComponent.tsx @@ -6,8 +6,7 @@ import store from '../../index'; //Realize that * is already imported from react import { useState } from 'react'; import { useDispatch } from 'react-redux'; -import { Modal, Button } from 'react-bootstrap'; -import { Input } from 'reactstrap'; +import { Button, Input, Modal, ModalBody, ModalFooter, ModalHeader } from 'reactstrap'; import { FormattedMessage } from 'react-intl'; import translate from '../../utils/translate'; import TooltipMarkerComponent from '../TooltipMarkerComponent'; @@ -157,142 +156,131 @@ export default function EditUnitModalComponent(props: EditUnitModalComponentProp return ( <> - - - - -
- -
-
-
+ + + + +
+ +
+
{/* when any of the unit are changed call one of the functions. */} - -
-
- {/* Modal content */} -
-
- {/* Identifier input */} -
-
- handleStringChange(e)} - value={state.identifier} - placeholder="Identifier" /> -
- {/* Name input */} -
- - handleStringChange(e)} - value={state.name} /> -
- {/* Type of unit input */} -
-
- handleStringChange(e)} - value={state.typeOfUnit}> - {Object.keys(UnitType).map(key => { - return () - })} - -
- {/* Unit represent input */} -
-
- handleStringChange(e)}> - {Object.keys(UnitRepresentType).map(key => { - return () - })} - -
- {/* Displayable type input */} -
-
- handleStringChange(e)}> - {Object.keys(DisplayableType).map(key => { - return () - })} - -
- {/* Preferred display input */} -
-
- handleBooleanChange(e)}> - {Object.keys(TrueFalseType).map(key => { - return () - })} - -
- {/* Seconds in rate input */} -
-
- handleNumberChange(e)} - placeholder="Sec In Rate" - // TODO validate negative input by typing for rate but database stops it. - // This stops negative input by use of arrows to change value. - min="1" /> -
- {/* Suffix input */} -
-
- handleStringChange(e)} /> -
- {/* Note input */} -
-
- handleStringChange(e)} /> -
-
-
-
-
+ + {/* Identifier input */} +
+
+ handleStringChange(e)} + value={state.identifier} + placeholder="Identifier" /> +
+ {/* Name input */} +
+ + handleStringChange(e)} + value={state.name} /> +
+ {/* Type of unit input */} +
+
+ handleStringChange(e)} + value={state.typeOfUnit}> + {Object.keys(UnitType).map(key => { + return () + })} + +
+ {/* Unit represent input */} +
+
+ handleStringChange(e)}> + {Object.keys(UnitRepresentType).map(key => { + return () + })} + +
+ {/* Displayable type input */} +
+
+ handleStringChange(e)}> + {Object.keys(DisplayableType).map(key => { + return () + })} + +
+ {/* Preferred display input */} +
+
+ handleBooleanChange(e)}> + {Object.keys(TrueFalseType).map(key => { + return () + })} + +
+ {/* Seconds in rate input */} +
+
+ handleNumberChange(e)} + placeholder="Sec In Rate" + // TODO validate negative input by typing for rate but database stops it. + // This stops negative input by use of arrows to change value. + min="1" /> +
+ {/* Suffix input */} +
+
+ handleStringChange(e)} /> +
+ {/* Note input */} +
+
+ handleStringChange(e)} />
- - +
+ {/* Hides the modal */} - {/* On click calls the function handleSaveChanges in this component */} - - + ); diff --git a/src/client/app/containers/LanguageSelectorContainer.ts b/src/client/app/containers/LanguageSelectorContainer.ts deleted file mode 100644 index e4bc86a58..000000000 --- a/src/client/app/containers/LanguageSelectorContainer.ts +++ /dev/null @@ -1,32 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -import { connect } from 'react-redux'; -import LanguageSelectorComponent from '../components/LanguageSelectorComponent'; -import { State } from '../types/redux/state'; -import { Dispatch } from '../types/redux/actions'; -import { updateDefaultLanguage } from '../actions/admin'; -import { LanguageTypes } from '../types/redux/i18n'; - - -/* -* Passes the current redux state of the language selection, and turns it into props for the React -* component, which is what will be visible on the page. Makes it possible to access -* your reducer state objects from within your React components. -*/ -function mapStateToProps(state: State) { - return { - selectedLanguage: state.admin.defaultLanguage - }; -} - -// Function to dispatch the changed language choice -function mapDispatchToProps(dispatch: Dispatch) { - return { - changeLanguage: (languageType: LanguageTypes) => dispatch(updateDefaultLanguage(languageType)) - }; -} - -// function that connects the React container to the Redux store of states -export default connect(mapStateToProps, mapDispatchToProps)(LanguageSelectorComponent); \ No newline at end of file diff --git a/src/client/app/styles/modalStyle.tsx b/src/client/app/styles/modalStyle.tsx index 09dbc26a4..26c0007f1 100644 --- a/src/client/app/styles/modalStyle.tsx +++ b/src/client/app/styles/modalStyle.tsx @@ -7,7 +7,8 @@ export const formInputStyle: React.CSSProperties = { }; export const tableStyle: React.CSSProperties = { - width: '100%' + width: '90%', + margin: 'auto' }; export const requiredStyle: React.CSSProperties = { diff --git a/src/client/app/translations/data.js b/src/client/app/translations/data.js index 18c2f5b30..fd457a07a 100644 --- a/src/client/app/translations/data.js +++ b/src/client/app/translations/data.js @@ -20,7 +20,6 @@ const localeData = { "area": "Area", "area.but.no.unit": "You have entered a nonzero area but no area unit.", "area.error": "Please enter a number for area", - "area.invalid": "Area must be a positive value. You gave ", "area.normalize": "Normalize by Area", "area.calculate.auto": "Calculate Group Area", "AreaUnitType.feet": "sq. feet", @@ -128,6 +127,8 @@ const localeData = { "DisplayableType.all": "all", "DisplayableType.admin": "admin", "duplication.invalid": "The reading duplication must be an integer between 1 and 9 but you gave ", + "error.negative": "Cannot be negative.", + "error.bounds": "Must be between {min} and {max}.", "edit": "Edit", "edited": "edited", "edit.a.group": "Edit a Group", @@ -162,7 +163,7 @@ const localeData = { "group.area.unit": "Group Area Unit:", "group.children.error": "Groups must have at least one child meter or group.", "group.create.nounit": "The default graphic unit was changed to no unit from ", - "group.defaultGraphicUnit": "Default Graphic Unit", + "group.defaultGraphicUnit": "Default Graphic Unit:", "group.delete.group": "Delete Group", "group.delete.issue": "is contained in the following groups and cannot be deleted", "group.details": "Group Details", @@ -189,6 +190,9 @@ const localeData = { "group.successfully.edited.group": "Successfully edited group.", "groups.select": "Select Groups", "has.used": "has used", + "header.data": "Data", + "header.options": "Options", + "help": "Help", "help.admin.conversioncreate": "This page allows admins to create conversions. Please visit {link} for further details and information.", "help.admin.conversionedit": "This page allows admins to edit conversions. Please visit {link} for further details and information.", "help.admin.conversionview": "This page allows admins to view and edit conversions. Please visit {link} for further details and information.", @@ -464,7 +468,6 @@ const localeData = { "area": "Région", "area.but.no.unit": "(Need French) You have entered a nonzero area but no area unit.", "area.error": "(Need French) Please enter a number for area", - "area.invalid": "(Need French) Area must be a positive value. You gave ", "area.normalize": "(Need French) Normalize by Area", "area.calculate.auto": "(Need French) Calculate Group Area", "AreaUnitType.feet": "pieds carrés", @@ -572,6 +575,8 @@ const localeData = { "DisplayableType.all": "(Need French) all", "DisplayableType.admin": "(Need French) admin", "duplication.invalid": "(Need French) The reading duplication must be an integer between 1 and 9 but you gave ", + "error.negative": "(Need French) Cannot be negative.", + "error.bounds": "(Need French) Must be between {min} and {max}.", "edit": "Modifier", "edited": "édité", "edit.a.group": "Modifier le Groupe", @@ -606,7 +611,7 @@ const localeData = { "group.area.unit": "(Need French) Group Area Unit:", "group.children.error": "(Need French) Groups must have at least one child meter or group.", "group.create.nounit": "(Need French) The default graphic unit was changed to no unit from ", - "group.defaultGraphicUnit": "(Need French) Default Graphic Unit", + "group.defaultGraphicUnit": "(Need French) Default Graphic Unit:", "group.delete.group": "(Need French) Delete Group", "group.delete.issue": "(Need French) is contained in the following groups and cannot be deleted", "group.details": "(Need French) Group Details", @@ -633,6 +638,9 @@ const localeData = { "group.successfully.edited.group": "(Need French) Successfully edited group.", "groups.select": "Sélectionnez des Groupes", "has.used": "a utilisé", + "header.data": "Données", + "header.options": "Options", + "help": "(Need French) Help", "help.admin.conversioncreate": "(Need French) This page allows admins to create conversions. Please visit {link} for further details and information.", "help.admin.conversionedit": "(Need French) This page allows admins to edit conversions. Please visit {link} for further details and information.", "help.admin.conversionview": "(Need French) This page allows admins to view and edit conversions. Please visit {link} for further details and information.", @@ -908,7 +916,6 @@ const localeData = { "area": "Área", "area.but.no.unit": "(Need Spanish)You have entered a nonzero area but no area unit.", "area.error": "Por favor entra un numero por la área", - "area.invalid": "(Need Spanish) Area must be a positive value. You gave ", "area.normalize": "(Need Spanish) Normalize by Area", "area.calculate.auto": "(Need Spanish) Calculate Group Area", "AreaUnitType.feet": "pies cuadrados", @@ -1016,6 +1023,8 @@ const localeData = { "DisplayableType.all": "(Need Spanish) all", "DisplayableType.admin": "(Need Spanish) admin", "duplication.invalid": "(Need Spanish) The reading duplication must be an integer between 1 and 9 but you gave ", + "error.negative": "(Need Spanish) Cannot be negative.", + "error.bounds": "(Need Spanish) Must be between {min} and {max}.", "edit": "Editar", "edited": "Editado", "edit.a.group": "Editar un grupo", @@ -1050,7 +1059,7 @@ const localeData = { "group.area.unit": "(Need Spanish) Group Area Unit:", "group.children.error": "(Need Spanish) Groups must have at least one child meter or group.", "group.create.nounit": "(Need Spanish) The default graphic unit was changed to no unit from ", - "group.defaultGraphicUnit": "(Need Spanish) Default Graphic Unit", + "group.defaultGraphicUnit": "(Need Spanish) Default Graphic Unit:", "group.delete.group": "(Need Spanish) Delete Group", "group.delete.issue": "(Need Spanish) is contained in the following groups and cannot be deleted", "group.details": "(Need Spanish) Group Details", @@ -1077,6 +1086,9 @@ const localeData = { "group.successfully.edited.group": "(Need Spanish) Successfully edited group.", "groups.select": "Seleccionar grupos", "has.used": "ha utilizado", + "header.data": "Datos", + "header.options": "Opciones", + "help": "(Need Spanish) Help", "help.admin.conversioncreate": "(Need Spanish) This page allows admins to create conversions. Please visit {link} for further details and information.", "help.admin.conversionedit": "(Need Spanish) This page allows admins to edit conversions. Please visit {link} for further details and information.", "help.admin.conversionview": "(Need Spanish) This page allows admins to view and edit conversions. Please visit {link} for further details and information.", From b482c0f3c5e7411319202bdd1547a72440e9306c Mon Sep 17 00:00:00 2001 From: spearec Date: Tue, 6 Jun 2023 14:21:19 +0000 Subject: [PATCH 02/15] Add in-form validation to modals - Adding instant validation and feedback to the modal create/edit forms - some design discussions are needed before the final layout can be done --- src/client/app/components/HomeComponent.tsx | 2 +- .../CreateConversionModalComponent.tsx | 58 +- .../EditConversionModalComponent.tsx | 25 +- .../groups/CreateGroupModalComponent.tsx | 328 ++++---- .../groups/EditGroupModalComponent.tsx | 62 +- .../meters/CreateMeterModalComponent.tsx | 769 +++++++++--------- .../meters/EditMeterModalComponent.tsx | 680 ++++++++-------- .../unit/CreateUnitModalComponent.tsx | 50 +- .../unit/EditUnitModalComponent.tsx | 38 +- .../app/containers/csv/UploadCSVContainer.tsx | 2 +- src/client/app/translations/data.js | 16 +- 11 files changed, 1051 insertions(+), 979 deletions(-) diff --git a/src/client/app/components/HomeComponent.tsx b/src/client/app/components/HomeComponent.tsx index adbc662cc..c5429b253 100644 --- a/src/client/app/components/HomeComponent.tsx +++ b/src/client/app/components/HomeComponent.tsx @@ -15,8 +15,8 @@ import TooltipHelpContainer from '../containers/TooltipHelpContainer'; export default function HomeComponent() { return (
- +
diff --git a/src/client/app/components/conversion/CreateConversionModalComponent.tsx b/src/client/app/components/conversion/CreateConversionModalComponent.tsx index bcbf8020a..25a2e2c23 100644 --- a/src/client/app/components/conversion/CreateConversionModalComponent.tsx +++ b/src/client/app/components/conversion/CreateConversionModalComponent.tsx @@ -5,7 +5,7 @@ import * as React from 'react'; import { useDispatch } from 'react-redux'; import { useState, useEffect } from 'react'; -import { Button, Input, Modal, ModalBody, ModalFooter, ModalHeader } from 'reactstrap'; +import { Button, FormFeedback, FormGroup, Input, Label, Modal, ModalBody, ModalFooter, ModalHeader } from 'reactstrap'; import { FormattedMessage } from 'react-intl'; import translate from '../../utils/translate'; import '../../styles/modal.css'; @@ -16,7 +16,7 @@ import { addConversion } from '../../actions/conversions'; import { UnitDataById } from 'types/redux/units'; import { ConversionData } from 'types/redux/conversions'; import * as _ from 'lodash'; -import {formInputStyle, tableStyle, requiredStyle, tooltipBaseStyle} from '../../styles/modalStyle'; +import {formInputStyle, tableStyle, tooltipBaseStyle} from '../../styles/modalStyle'; interface CreateConversionModalComponentProps { conversionsState: ConversionData[]; @@ -169,13 +169,15 @@ export default function CreateConversionModalComponent(props: CreateConversionMo {/* when any of the conversion are changed call one of the functions. */} {/* Source unit input*/} -
- + + handleNumberChange(e)}> + onChange={e => handleNumberChange(e)} + invalid={state.sourceId === -999}> {) })} -
+ + + + {/* Destination unit input*/} -
- + + handleNumberChange(e)}> + onChange={e => handleNumberChange(e)} + invalid={state.destinationId === -999}> {) })} -
+ + + + {/* Bidirectional Y/N input*/} -
- + + handleBooleanChange(e)}> @@ -219,35 +230,38 @@ export default function CreateConversionModalComponent(props: CreateConversionMo return () })} -
+ {/* Slope input*/} -
- + + handleNumberChange(e)} /> -
+ {/* Intercept input*/} -
- + + handleNumberChange(e)} required value={state.intercept} /> -
+ {/* Note input*/} -
- + + handleStringChange(e)} value={state.note} /> -
+
{/* Hides the modal */} diff --git a/src/client/app/components/conversion/EditConversionModalComponent.tsx b/src/client/app/components/conversion/EditConversionModalComponent.tsx index 3d15a28fc..b9eafebee 100644 --- a/src/client/app/components/conversion/EditConversionModalComponent.tsx +++ b/src/client/app/components/conversion/EditConversionModalComponent.tsx @@ -5,7 +5,7 @@ import * as React from 'react'; // Realize that * is already imported from react import { useState } from 'react'; import { useDispatch } from 'react-redux'; -import { Button, Input, Modal, ModalBody, ModalFooter, ModalHeader } from 'reactstrap'; +import { Button, Input, Label, Modal, ModalBody, ModalFooter, ModalHeader } from 'reactstrap'; import { FormattedMessage } from 'react-intl'; import translate from '../../utils/translate'; import TooltipMarkerComponent from '../TooltipMarkerComponent'; @@ -17,7 +17,7 @@ import { TrueFalseType } from '../../types/items'; import { ConversionData } from '../../types/redux/conversions'; import { UnitDataById } from 'types/redux/units'; import ConfirmActionModalComponent from '../ConfirmActionModalComponent' -import { formInputStyle, tableStyle, requiredStyle, tooltipBaseStyle } from '../../styles/modalStyle'; +import { formInputStyle, tableStyle, tooltipBaseStyle } from '../../styles/modalStyle'; interface EditConversionModalComponentProps { show: boolean; @@ -149,14 +149,14 @@ export default function EditConversionModalComponent(props: EditConversionModalC
- {/* when any of the conversion are changed call one of the functions. */} {/* Source unit - display only */}
- + {/* Destination unit - display only */}
- + {/* Bidirectional Y/N input */}
- + {/* Slope input */}
- + handleNumberChange(e)} />
{/* Intercept input */}
- + {/* Note input */}
- + { + setValidGroup(state.name !== '' && (state.area === 0 || (state.area > 0 && state.areaUnit !== AreaUnitType.none))); + }, [state.area, state.areaUnit, state.name]); /* End State */ // Sums the area of the group's deep meters. It will tell the admin if any meters are omitted from the calculation, @@ -166,16 +171,6 @@ export default function CreateGroupModalComponent(props: CreateGroupModalCompone // true if inputted values are okay. Then can submit. let inputOk = true; - // Check if area is non-negative - if (state.area < 0) { - notifyUser(translate('area.invalid') + state.area + '.'); - inputOk = false; - } else if (state.area > 0 && state.areaUnit == AreaUnitType.none) { - // If the group has an assigned area, it must have a unit - notifyUser(translate('area.but.no.unit')); - inputOk = false; - } - // Check GPS entered. const gpsInput = state.gps; let gps: GPSPoint | null = null; @@ -301,146 +296,129 @@ export default function CreateGroupModalComponent(props: CreateGroupModalCompone
{/* when any of the group properties are changed call one of the functions. */} - {loggedInAsAdmin && // only render when logged in as Admin - - {/* Name input */} -
- - handleStringChange(e)} - required value={state.name} /> -
- {/* default graphic unit input */} -
- - handleNumberChange(e)}> - {/* First list the selectable ones and then the rest as disabled. */} - {Array.from(graphicUnitsState.compatibleGraphicUnits).map(unit => { - return () - })} - {Array.from(graphicUnitsState.incompatibleGraphicUnits).map(unit => { - return () - })} - -
- {/* Displayable input */} -
- - handleBooleanChange(e)}> - {Object.keys(TrueFalseType).map(key => { - return () - })} - -
- {/* Area input */} -
- - - - handleNumberChange(e)} /> - {/* Calculate sum of meter areas */} - - -
- {/* meter area unit input */} -
- - handleStringChange(e)}> - {Object.keys(AreaUnitType).map(key => { - return () - })} - -
- {/* GPS input */} -
- + + {/* Name input */} +
+ + handleStringChange(e)} + required value={state.name} + invalid={state.name === ''}/> + + + +
+ {/* default graphic unit input */} +
+ + handleNumberChange(e)}> + {/* First list the selectable ones and then the rest as disabled. */} + {Array.from(graphicUnitsState.compatibleGraphicUnits).map(unit => { + return () + })} + {Array.from(graphicUnitsState.incompatibleGraphicUnits).map(unit => { + return () + })} + +
+ {/* Displayable input */} +
+ + handleBooleanChange(e)}> + {Object.keys(TrueFalseType).map(key => { + return () + })} + +
+ {/* Area input */} +
+ + + handleStringChange(e)} - value={getGPSString(state.gps)} /> -
- {/* Note input */} + id='area' + name="area" + type="number" + min="0" + // cannot use defaultValue because it won't update when area is auto calculated + // this makes the validation redundant but still a good idea + value={state.area} + onChange={e => handleNumberChange(e)} + invalid={state.area < 0}/> + {/* Calculate sum of meter areas */} + + + + + +
+ {/* meter area unit input */} +
+ + handleStringChange(e)} + invalid={state.area > 0 && state.areaUnit === AreaUnitType.none}> + {Object.keys(AreaUnitType).map(key => { + return () + })} + + + + +
+ {/* GPS input */} +
+ + handleStringChange(e)} + value={getGPSString(state.gps)} /> +
+ {/* Note input */} +
+ + handleStringChange(e)} + value={state.note} /> +
+ {/* The child meters in this group */} + {
- - handleStringChange(e)} - value={state.note} /> -
- {/* The child meters in this group */} - { -
- : - { - // The meters changed so update the current list of deep meters - // Get the currently included/selected meters as an array of the ids. - const updatedChildMeters = newSelectedMeterOptions.map(meter => { return meter.value; }); - // The id is not really needed so set to -1 since same function for edit. - const newDeepMeters = metersInChangedGroup({ ...state, childMeters: updatedChildMeters, id: -1 }); - // The choice may have invalidated the default graphic unit so it needs - // to be reset to no unit. - // The selection encodes this information in the color but recalculate - // to see if this is the case. - // Get the units compatible with the new set of deep meters in group. - const newAllowedDGU = unitsCompatibleWithMeters(new Set(newDeepMeters)); - // Add no unit (-99) since that is okay so no change needed if current default graphic unit. - newAllowedDGU.add(-99); - let dgu = state.defaultGraphicUnit; - if (!newAllowedDGU.has(dgu)) { - // The current default graphic unit is not compatible so set to no unit and warn admin. - notifyUser(`${translate('group.create.nounit')} "${unitsState[dgu].identifier}"`); - dgu = -99; - } - // Update the deep meter, child meter & default graphic unit state based on the changes. - // Note could update child meters above to avoid updating state value for metersInChangedGroup but want - // to avoid too many state updates. - // It is possible the default graphic unit is unchanged but just do this. - setState({ ...state, deepMeters: newDeepMeters, childMeters: updatedChildMeters, defaultGraphicUnit: dgu }); - }} - /> -
- } - {/* The child groups in this group */} - {
- : + : { - // The groups changed so update the current list of deep meters + options={groupChildrenState.meterSelectOptions} + selectedOptions={metersToSelectOptions()} + placeholder={translate('select.meters')} + onValuesChange={(newSelectedMeterOptions: SelectOption[]) => { + // The meters changed so update the current list of deep meters // Get the currently included/selected meters as an array of the ids. - const updatedChildGroups = newSelectedGroupOptions.map(group => { return group.value; }); + const updatedChildMeters = newSelectedMeterOptions.map(meter => { return meter.value; }); // The id is not really needed so set to -1 since same function for edit. - const newDeepMeters = metersInChangedGroup({ ...state, childGroups: updatedChildGroups, id: -1 }); + const newDeepMeters = metersInChangedGroup({ ...state, childMeters: updatedChildMeters, id: -1 }); // The choice may have invalidated the default graphic unit so it needs // to be reset to no unit. // The selection encodes this information in the color but recalculate @@ -456,27 +434,63 @@ export default function CreateGroupModalComponent(props: CreateGroupModalCompone dgu = -99; } // Update the deep meter, child meter & default graphic unit state based on the changes. - // Note could update child groups above to avoid updating state value for metersInChangedGroup but want + // Note could update child meters above to avoid updating state value for metersInChangedGroup but want // to avoid too many state updates. // It is possible the default graphic unit is unchanged but just do this. - setState({ ...state, deepMeters: newDeepMeters, childGroups: updatedChildGroups, defaultGraphicUnit: dgu }); + setState({ ...state, deepMeters: newDeepMeters, childMeters: updatedChildMeters, defaultGraphicUnit: dgu }); }} />
- } - {/* All (deep) meters in this group */} -
- : - -
-
} + } + {/* The child groups in this group */} + {
+ : + { + // The groups changed so update the current list of deep meters + // Get the currently included/selected meters as an array of the ids. + const updatedChildGroups = newSelectedGroupOptions.map(group => { return group.value; }); + // The id is not really needed so set to -1 since same function for edit. + const newDeepMeters = metersInChangedGroup({ ...state, childGroups: updatedChildGroups, id: -1 }); + // The choice may have invalidated the default graphic unit so it needs + // to be reset to no unit. + // The selection encodes this information in the color but recalculate + // to see if this is the case. + // Get the units compatible with the new set of deep meters in group. + const newAllowedDGU = unitsCompatibleWithMeters(new Set(newDeepMeters)); + // Add no unit (-99) since that is okay so no change needed if current default graphic unit. + newAllowedDGU.add(-99); + let dgu = state.defaultGraphicUnit; + if (!newAllowedDGU.has(dgu)) { + // The current default graphic unit is not compatible so set to no unit and warn admin. + notifyUser(`${translate('group.create.nounit')} "${unitsState[dgu].identifier}"`); + dgu = -99; + } + // Update the deep meter, child meter & default graphic unit state based on the changes. + // Note could update child groups above to avoid updating state value for metersInChangedGroup but want + // to avoid too many state updates. + // It is possible the default graphic unit is unchanged but just do this. + setState({ ...state, deepMeters: newDeepMeters, childGroups: updatedChildGroups, defaultGraphicUnit: dgu }); + }} + /> +
+ } + {/* All (deep) meters in this group */} +
+ : + +
+ {/* Hides the modal */} {/* On click calls the function handleSaveChanges in this component */} - diff --git a/src/client/app/components/groups/EditGroupModalComponent.tsx b/src/client/app/components/groups/EditGroupModalComponent.tsx index 5c166a080..a3b892cee 100644 --- a/src/client/app/components/groups/EditGroupModalComponent.tsx +++ b/src/client/app/components/groups/EditGroupModalComponent.tsx @@ -10,7 +10,7 @@ import MultiSelectComponent from '../MultiSelectComponent'; import { SelectOption } from '../../types/items'; import { useDispatch, useSelector } from 'react-redux'; import { State } from 'types/redux/state'; -import { Button, Input, InputGroup, Modal, ModalBody, ModalFooter, ModalHeader } from 'reactstrap'; +import { Button, FormFeedback, Input, InputGroup, Label, Modal, ModalBody, ModalFooter, ModalHeader } from 'reactstrap'; import { FormattedMessage } from 'react-intl'; import translate from '../../utils/translate'; import TooltipMarkerComponent from '../TooltipMarkerComponent'; @@ -33,7 +33,7 @@ import { GroupDefinition } from '../../types/redux/groups'; import ConfirmActionModalComponent from '../ConfirmActionModalComponent' import { DataType } from '../../types/Datasources'; import { groupsApi } from '../../utils/api'; -import { tableStyle, requiredStyle, tooltipBaseStyle, formInputStyle } from '../../styles/modalStyle'; +import { tableStyle, tooltipBaseStyle, formInputStyle } from '../../styles/modalStyle'; import { AreaUnitType, getAreaUnitConversion } from '../../utils/getAreaUnitConversion'; interface EditGroupModalComponentProps { @@ -115,6 +115,11 @@ export default function EditGroupModalComponent(props: EditGroupModalComponentPr // Dropdowns state const [groupChildrenState, setGroupChildrenState] = useState(groupChildrenDefaults) const [graphicUnitsState, setGraphicUnitsState] = useState(graphicUnitsStateDefaults); + + const [validGroup, setValidGroup] = useState(false); + useEffect(() => { + setValidGroup(groupState.name !== '' && (groupState.area === 0 || (groupState.area > 0 && groupState.areaUnit !== AreaUnitType.none))); + }, [groupState.area, groupState.areaUnit, groupState.name]); /* End State */ /* Confirm Delete Modal */ @@ -247,15 +252,6 @@ export default function EditGroupModalComponent(props: EditGroupModalComponentPr ); // Only validate and store if any changes. if (groupHasChanges) { - // Check if area is non-negative - if (groupState.area < 0) { - notifyUser(translate('area.invalid') + groupState.area + '.'); - inputOk = false; - } else if (groupState.area > 0 && groupState.areaUnit == AreaUnitType.none) { - // If the group has an assigned area, it must have a unit - notifyUser(translate('area.but.no.unit')); - inputOk = false; - } //Check GPS is okay. const gpsInput = groupState.gps; let gps: GPSPoint | null = null; @@ -408,18 +404,25 @@ export default function EditGroupModalComponent(props: EditGroupModalComponentPr {/* Name input, disabled if not admin */}
- + handleStringChange(e)} required value={groupState.name} - disabled={!loggedInAsAdmin}/> + disabled={!loggedInAsAdmin} + invalid={groupState.name === ''}/> + + +
{/* default graphic unit input, disabled if not admin */}
- + {/* Displayable input, only for admin. */}
- + {/* Area input, only for admin. */}
- + handleNumberChange(e)} /> + onChange={e => handleNumberChange(e)} + invalid={groupState.area < 0} /> {/* Calculate sum of meter areas */} + + +
{/* meter area unit input */}
- + handleStringChange(e)}> + onChange={e => handleStringChange(e)} + invalid={groupState.area > 0 && groupState.areaUnit === AreaUnitType.none}> {Object.keys(AreaUnitType).map(key => { return () })} + + +
{/* GPS input, only for admin. */}
- + handleStringChange(e)} @@ -490,9 +506,9 @@ export default function EditGroupModalComponent(props: EditGroupModalComponentPr
{/* Note input, only for admin. */}
- + handleStringChange(e)} @@ -611,7 +627,7 @@ export default function EditGroupModalComponent(props: EditGroupModalComponentPr {/* On click calls the function handleSaveChanges in this component */} -
diff --git a/src/client/app/components/meters/CreateMeterModalComponent.tsx b/src/client/app/components/meters/CreateMeterModalComponent.tsx index df826f3c0..06c3e3855 100644 --- a/src/client/app/components/meters/CreateMeterModalComponent.tsx +++ b/src/client/app/components/meters/CreateMeterModalComponent.tsx @@ -2,7 +2,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import * as React from 'react'; -import { Button, Input, Modal, ModalBody, ModalFooter, ModalHeader } from 'reactstrap'; +import { Button, FormFeedback, Input, Label, Modal, ModalBody, ModalFooter, ModalHeader } from 'reactstrap'; import { FormattedMessage } from 'react-intl'; import translate from '../../utils/translate'; import { useDispatch, useSelector } from 'react-redux'; @@ -16,13 +16,12 @@ import TooltipHelpContainer from '../../containers/TooltipHelpContainer'; import { TrueFalseType } from '../../types/items'; import TimeZoneSelect from '../TimeZoneSelect'; import { GPSPoint, isValidGPSInput } from '../../utils/calibration'; -import { isRoleAdmin } from '../../utils/hasPermissions'; import { UnitData } from '../../types/redux/units'; import { unitsCompatibleWithUnit } from '../../utils/determineCompatibleUnits'; import { ConversionArray } from '../../types/conversionArray'; import { AreaUnitType } from '../../utils/getAreaUnitConversion'; import { notifyUser } from '../../utils/input' -import { formInputStyle, tableStyle, requiredStyle, tooltipBaseStyle } from '../../styles/modalStyle'; +import { formInputStyle, tableStyle, tooltipBaseStyle } from '../../styles/modalStyle'; // TODO Moved the possible meters/graphic units calculations up to the details component @@ -36,10 +35,6 @@ export default function CreateMeterModalComponent(props: CreateMeterModalCompone const dispatch = useDispatch(); - // Check for admin status - const currentUser = useSelector((state: State) => state.currentUser.profile); - const loggedInAsAdmin = (currentUser !== null) && isRoleAdmin(currentUser.role); - // Admin state so can get the default reading frequency. const adminState = useSelector((state: State) => state.admin) @@ -113,6 +108,32 @@ export default function CreateMeterModalComponent(props: CreateMeterModalCompone // Dropdowns const [dropdownsState, setDropdownsState] = useState(dropdownsStateDefaults); + + const [validMeter, setValidMeter] = useState(false); + useEffect(() => { + setValidMeter( + state.name !== '' && + (state.area === 0 || (state.area > 0 && state.areaUnit !== AreaUnitType.none)) && + state.readingGap >= 0 && + state.readingVariation >= 0 && + (state.readingDuplication >= 1 && state.readingDuplication <= 9) && + state.readingFrequency !== '' && + state.unitId !== -999 && + state.defaultGraphicUnit !== -999 && + state.meterType !== '' + ); + }, [ + state.area, + state.name, + state.readingGap, + state.readingVariation, + state.readingDuplication, + state.areaUnit, + state.readingFrequency, + state.unitId, + state.defaultGraphicUnit, + state.meterType + ]); /* End State */ // Reset the state to default values @@ -142,52 +163,6 @@ export default function CreateMeterModalComponent(props: CreateMeterModalCompone // Set default identifier as name if left blank state.identifier = (!state.identifier || state.identifier.length === 0) ? state.name : state.identifier; - // Check if area is non-negative - if (state.area < 0) { - notifyUser(translate('area.invalid') + state.area + '.'); - inputOk = false; - } else if (state.area > 0 && state.areaUnit == AreaUnitType.none) { - // If the meter has an assigned area, it must have a unit - notifyUser(translate('area.but.no.unit')); - inputOk = false; - } - - // Check reading gap is at least zero. - if (state.readingGap < 0) { - notifyUser(translate('reading.gap.invalid') + state.readingGap + '.'); - inputOk = false; - } - - // Check reading variation is at least zero. - if (state.readingVariation < 0) { - notifyUser(translate('reading.variation.invalid') + state.readingVariation + '.'); - inputOk = false; - } - - // Check reading duplication is between 1 and 9. - if (state.readingDuplication < 1 || state.readingDuplication > 9) { - notifyUser(translate('duplication.invalid') + state.readingDuplication + '.'); - inputOk = false; - } - - // A meter unit must be selected. - if (state.unitId === -999) { - notifyUser(translate('meter.unit.invalid')); - inputOk = false; - } - - // A meter default graphic unit must be selected. - if (state.defaultGraphicUnit === -999) { - notifyUser(translate('meter.graphic.invalid')); - inputOk = false; - } - - // A meter type must be selected. - if (state.meterType === '') { - notifyUser(translate('meter.type.invalid')); - inputOk = false; - } - // Check GPS entered. // Validate GPS is okay and take from string to GPSPoint to submit. const gpsInput = state.gps; @@ -336,338 +311,374 @@ export default function CreateMeterModalComponent(props: CreateMeterModalCompone
{/* when any of the Meter values are changed call one of the functions. */} - {loggedInAsAdmin && // only render when logged in as Admin - - {/* Identifier input */} -
- - handleStringChange(e)} - value={state.identifier} /> -
- {/* Name input */} -
- - handleStringChange(e)} - required value={state.name} /> -
- {/* meter unit input */} -
- - handleNumberChange(e)}> - {} - {Array.from(dropdownsState.compatibleUnits).map(unit => { - return () - })} - {Array.from(dropdownsState.incompatibleUnits).map(unit => { - return () - })} - -
- {/* default graphic unit input */} -
- - handleNumberChange(e)}> - {} - {Array.from(dropdownsState.compatibleGraphicUnits).map(unit => { - return () - })} - {Array.from(dropdownsState.incompatibleGraphicUnits).map(unit => { - return () - })} - -
- {/* Enabled input */} -
- - handleBooleanChange(e)}> - {Object.keys(TrueFalseType).map(key => { - return () - })} - -
- {/* Displayable input */} -
- - handleBooleanChange(e)}> - {Object.keys(TrueFalseType).map(key => { - return () - })} - -
- {/* Meter type input */} -
- - handleStringChange(e)}> - {/* The default value is a blank string so then tell user to select one. */} - {} - {/* The dB expects lowercase. */} - {Object.keys(MeterType).map(key => { - return () - })} - -
- {/* Meter reading frequency */} -
- - handleStringChange(e)} - value={state.readingFrequency} /> -
- {/* URL input */} -
- - handleStringChange(e)} - value={state.url} /> -
- {/* Area input */} -
- - handleNumberChange(e)} /> -
- {/* meter area unit input */} -
- - handleStringChange(e)}> - {Object.keys(AreaUnitType).map(key => { - return () - })} - -
- {/* GPS input */} -
- - handleStringChange(e)} - value={state.gps} /> -
- {/* note input */} -
- - handleStringChange(e)} - value={state.note} - placeholder='Note' /> -
- {/* cumulative input */} -
- - handleBooleanChange(e)}> - {Object.keys(TrueFalseType).map(key => { - return () - })} - -
- {/* cumulativeReset input */} -
- - handleBooleanChange(e)}> - {Object.keys(TrueFalseType).map(key => { - return () - })} - -
- {/* cumulativeResetStart input */} -
- - handleStringChange(e)} - value={state.cumulativeResetStart} - placeholder="HH:MM:SS" /> -
- {/* cumulativeResetEnd input */} -
- - handleStringChange(e)} - value={state.cumulativeResetEnd} - placeholder="HH:MM:SS" /> -
- {/* endOnlyTime input */} -
- - handleBooleanChange(e)}> - {Object.keys(TrueFalseType).map(key => { - return () - })} - -
- {/* readingGap input */} -
- - handleNumberChange(e)} - min="0" - defaultValue={state.readingGap} /> -
- {/* readingVariation input */} -
- - handleNumberChange(e)} - min="0" - defaultValue={state.readingVariation} /> -
- {/* readingDuplication input */} -
- - handleNumberChange(e)} - step="1" - min="1" - max="9" - defaultValue={state.readingDuplication} /> -
- {/* timeSort input */} -
- - handleStringChange(e)}> - {Object.keys(MeterTimeSortType).map(key => { - // This is a bit of a hack but it should work fine. The TypeSortTypes and MeterTimeSortType should be in sync. - // The translation is on the former so we use that enum name there but loop on the other to get the value desired. - return () - })} - -
- {/* Timezone input */} -
- - handleTimeZoneChange(timeZone)} /> -
- {/* reading input */} -
- - handleNumberChange(e)} - defaultValue={state.reading} /> -
- {/* startTimestamp input */} -
- - handleStringChange(e)} - placeholder="YYYY-MM-DD HH:MM:SS" - value={state.startTimestamp} /> -
- {/* endTimestamp input */} -
- - handleStringChange(e)} - placeholder="YYYY-MM-DD HH:MM:SS" - value={state.endTimestamp} /> -
- {/* endTimestamp input */} -
- - handleStringChange(e)} - placeholder="YYYY-MM-DD HH:MM:SS" - value={state.previousEnd} /> -
-
} + + {/* Identifier input */} +
+ + handleStringChange(e)} + value={state.identifier} /> +
+ {/* Name input */} +
+ {/* */} + + handleStringChange(e)} + required value={state.name} + invalid={state.name === ''} /> + + + +
+ {/* meter unit input */} +
+ + handleNumberChange(e)} + invalid={state.unitId === -999}> + {} + {Array.from(dropdownsState.compatibleUnits).map(unit => { + return () + })} + {Array.from(dropdownsState.incompatibleUnits).map(unit => { + return () + })} + + +
+ {/* default graphic unit input */} +
+ + handleNumberChange(e)} + invalid={state.defaultGraphicUnit === -999}> + {} + {Array.from(dropdownsState.compatibleGraphicUnits).map(unit => { + return () + })} + {Array.from(dropdownsState.incompatibleGraphicUnits).map(unit => { + return () + })} + + +
+ {/* Enabled input */} +
+ + handleBooleanChange(e)}> + {Object.keys(TrueFalseType).map(key => { + return () + })} + +
+ {/* Displayable input */} +
+ + handleBooleanChange(e)}> + {Object.keys(TrueFalseType).map(key => { + return () + })} + +
+ {/* Meter type input */} +
+ + handleStringChange(e)} + invalid={state.meterType === ''}> + {/* The default value is a blank string so then tell user to select one. */} + {} + {/* The dB expects lowercase. */} + {Object.keys(MeterType).map(key => { + return () + })} + + +
+ {/* Meter reading frequency */} +
+ + handleStringChange(e)} + value={state.readingFrequency} + invalid={state.readingFrequency === ''}/> + + + +
+ {/* URL input */} +
+ + handleStringChange(e)} + value={state.url} /> +
+ {/* Area input */} +
+ + handleNumberChange(e)} + invalid={state.area < 0} /> + + + +
+ {/* meter area unit input */} +
+ + handleStringChange(e)} + invalid={state.area > 0 && state.areaUnit === AreaUnitType.none}> + {Object.keys(AreaUnitType).map(key => { + return () + })} + + + + +
+ {/* GPS input */} +
+ + handleStringChange(e)} + value={state.gps} /> +
+ {/* note input */} +
+ + handleStringChange(e)} + value={state.note} + placeholder='Note' /> +
+ {/* cumulative input */} +
+ + handleBooleanChange(e)}> + {Object.keys(TrueFalseType).map(key => { + return () + })} + +
+ {/* cumulativeReset input */} +
+ + handleBooleanChange(e)}> + {Object.keys(TrueFalseType).map(key => { + return () + })} + +
+ {/* cumulativeResetStart input */} +
+ + handleStringChange(e)} + value={state.cumulativeResetStart} + placeholder="HH:MM:SS" /> +
+ {/* cumulativeResetEnd input */} +
+ + handleStringChange(e)} + value={state.cumulativeResetEnd} + placeholder="HH:MM:SS" /> +
+ {/* endOnlyTime input */} +
+ + handleBooleanChange(e)}> + {Object.keys(TrueFalseType).map(key => { + return () + })} + +
+ {/* readingGap input */} +
+ + handleNumberChange(e)} + min="0" + defaultValue={state.readingGap} + invalid={state?.readingGap < 0}/> + + + +
+ {/* readingVariation input */} +
+ + handleNumberChange(e)} + min="0" + defaultValue={state.readingVariation} + invalid={state?.readingVariation < 0} /> + + + +
+ {/* readingDuplication input */} +
+ + handleNumberChange(e)} + step="1" + min="1" + max="9" + defaultValue={state.readingDuplication} + invalid={state?.readingDuplication < 1 || state?.readingDuplication > 9}/> + + + +
+ {/* timeSort input */} +
+ + handleStringChange(e)}> + {Object.keys(MeterTimeSortType).map(key => { + // This is a bit of a hack but it should work fine. The TypeSortTypes and MeterTimeSortType should be in sync. + // The translation is on the former so we use that enum name there but loop on the other to get the value desired. + return () + })} + +
+ {/* Timezone input */} +
+ + handleTimeZoneChange(timeZone)} /> +
+ {/* reading input */} +
+ + handleNumberChange(e)} + defaultValue={state.reading} /> +
+ {/* startTimestamp input */} +
+ + handleStringChange(e)} + placeholder="YYYY-MM-DD HH:MM:SS" + value={state.startTimestamp} /> +
+ {/* endTimestamp input */} +
+ + handleStringChange(e)} + placeholder="YYYY-MM-DD HH:MM:SS" + value={state.endTimestamp} /> +
+ {/* endTimestamp input */} +
+ + handleStringChange(e)} + placeholder="YYYY-MM-DD HH:MM:SS" + value={state.previousEnd} /> +
+
{/* Hides the modal */} {/* On click calls the function handleSaveChanges in this component */} - diff --git a/src/client/app/components/meters/EditMeterModalComponent.tsx b/src/client/app/components/meters/EditMeterModalComponent.tsx index 3ca470eb7..d8ff02b38 100644 --- a/src/client/app/components/meters/EditMeterModalComponent.tsx +++ b/src/client/app/components/meters/EditMeterModalComponent.tsx @@ -18,7 +18,6 @@ import TooltipHelpContainer from '../../containers/TooltipHelpContainer'; import { TrueFalseType } from '../../types/items'; import TimeZoneSelect from '../TimeZoneSelect'; import { GPSPoint, isValidGPSInput } from '../../utils/calibration'; -import { isRoleAdmin } from '../../utils/hasPermissions'; import { UnitData } from '../../types/redux/units'; import { unitsCompatibleWithUnit } from '../../utils/determineCompatibleUnits'; import { ConversionArray } from '../../types/conversionArray'; @@ -41,10 +40,6 @@ export default function EditMeterModalComponent(props: EditMeterModalComponentPr // The current meter's state of meter being edited. It should always be valid. const meterState = useSelector((state: State) => state.meters.byMeterID[props.meter.id]); - // Check for admin status - const currentUser = useSelector((state: State) => state.currentUser.profile); - const loggedInAsAdmin = (currentUser !== null) && isRoleAdmin(currentUser.role); - // Set existing meter values const values = { id: props.meter.id, @@ -115,10 +110,11 @@ export default function EditMeterModalComponent(props: EditMeterModalComponentPr state.name !== '' && (state.area === 0 || (state.area > 0 && state.areaUnit !== AreaUnitType.none)) && state.readingGap >= 0 && - state.readingVariation >= 0 - && (state.readingDuplication >= 1 && state.readingDuplication <= 9) + state.readingVariation >= 0 && + (state.readingDuplication >= 1 && state.readingDuplication <= 9) && + state.readingFrequency !== '' ); - }, [state.area, state.name, state.readingGap, state.readingVariation, state.readingDuplication, state.areaUnit]); + }, [state.area, state.name, state.readingGap, state.readingVariation, state.readingDuplication, state.areaUnit, state.readingFrequency]); /* End State */ // Reset the state to default values @@ -180,8 +176,6 @@ export default function EditMeterModalComponent(props: EditMeterModalComponentPr // Only validate and store if any changes. if (meterHasChanges) { - // TODO Maybe should do as a single popup? - // Set default identifier as name if left blank state.identifier = (!state.identifier || state.identifier.length === 0) ? state.name : state.identifier; @@ -332,336 +326,342 @@ export default function EditMeterModalComponent(props: EditMeterModalComponentPr
{/* when any of the Meter values are changed call one of the functions. */} - {loggedInAsAdmin && // only render when logged in as Admin - - {/* Identifier input */} -
- - handleStringChange(e)} - value={state.identifier} /> -
- {/* Name input */} -
- - handleStringChange(e)} - required value={state.name} /> -
- {/* meter unit input */} -
- - handleNumberChange(e)}> - {Array.from(dropdownsState.compatibleUnits).map(unit => { - return () - })} - {Array.from(dropdownsState.incompatibleUnits).map(unit => { - return () - })} - -
- {/* default graphic unit input */} -
- - handleNumberChange(e)}> - {Array.from(dropdownsState.compatibleGraphicUnits).map(unit => { - return () - })} - {Array.from(dropdownsState.incompatibleGraphicUnits).map(unit => { - return () - })} - -
- {/* Enabled input */} -
- - handleBooleanChange(e)}> - {Object.keys(TrueFalseType).map(key => { - return () - })} - -
- {/* Displayable input */} -
- - handleBooleanChange(e)}> - {Object.keys(TrueFalseType).map(key => { - return () - })} - -
- {/* Meter type input */} -
- - handleStringChange(e)}> - {/* The dB expects lowercase. */} - {Object.keys(MeterType).map(key => { - return () - })} - -
- {/* Meter reading frequency */} -
- - handleStringChange(e)} - value={state.readingFrequency} /> -
- {/* URL input */} -
- - handleStringChange(e)} - value={nullToEmptyString(state.url)} /> -
- {/* Area input */} -
- - handleNumberChange(e)} - invalid={state.area < 0} /> - - - -
- {/* meter area unit input */} -
- - handleStringChange(e)} - invalid={state.area > 0 && state.areaUnit === AreaUnitType.none}> - {Object.keys(AreaUnitType).map(key => { - return () - })} - - - - -
- {/* GPS input */} -
- - handleStringChange(e)} - value={getGPSString(state.gps)} /> -
- {/* note input */} -
- - handleStringChange(e)} - value={nullToEmptyString(state.note)} - placeholder='Note' /> -
- {/* cumulative input */} -
- - handleBooleanChange(e)}> - {Object.keys(TrueFalseType).map(key => { - return () - })} - -
- {/* cumulativeReset input */} -
- - handleBooleanChange(e)}> - {Object.keys(TrueFalseType).map(key => { - return () - })} - -
- {/* cumulativeResetStart input */} -
- - handleStringChange(e)} - value={state.cumulativeResetStart} - placeholder="HH:MM:SS" /> -
- {/* cumulativeResetEnd input */} -
- - handleStringChange(e)} - value={state?.cumulativeResetEnd} - placeholder="HH:MM:SS" /> -
- {/* endOnlyTime input */} -
- - handleBooleanChange(e)}> - {Object.keys(TrueFalseType).map(key => { - return () - })} - -
- {/* readingGap input */} -
- - handleNumberChange(e)} - min="0" - defaultValue={state?.readingGap} - invalid={state?.readingGap < 0}/> - - - -
- {/* readingVariation input */} -
- - handleNumberChange(e)} - min="0" - defaultValue={state?.readingVariation} - invalid={state?.readingVariation < 0} /> - - - -
- {/* readingDuplication input */} -
- - handleNumberChange(e)} - step="1" - min="1" - max="9" - defaultValue={state?.readingDuplication} - invalid={state?.readingDuplication < 1 || state?.readingDuplication > 9}/> - - - -
- {/* timeSort input */} -
- - handleStringChange(e)}> - {Object.keys(MeterTimeSortType).map(key => { - // This is a bit of a hack but it should work fine. The TypeSortTypes and MeterTimeSortType should be in sync. - // The translation is on the former so we use that enum name there but loop on the other to get the value desired. - return () - })} - -
- {/* Timezone input */} -
- - handleTimeZoneChange(timeZone)} /> -
- {/* reading input */} -
- - handleNumberChange(e)} - defaultValue={state?.reading} /> -
- {/* startTimestamp input */} -
- - handleStringChange(e)} - placeholder="YYYY-MM-DD HH:MM:SS" - value={state?.startTimestamp} /> -
- {/* endTimestamp input */} -
- - handleStringChange(e)} - placeholder="YYYY-MM-DD HH:MM:SS" - value={state?.endTimestamp} /> -
- {/* previousEnd input */} -
- - handleStringChange(e)} - placeholder="YYYY-MM-DD HH:MM:SS" - value={state?.previousEnd} /> -
-
- } + + {/* Identifier input */} +
+ + handleStringChange(e)} + value={state.identifier} /> +
+ {/* Name input */} +
+ + handleStringChange(e)} + value={state.name} + invalid={state.name === ''} /> + + + +
+ {/* meter unit input */} +
+ + handleNumberChange(e)}> + {Array.from(dropdownsState.compatibleUnits).map(unit => { + return () + })} + {Array.from(dropdownsState.incompatibleUnits).map(unit => { + return () + })} + +
+ {/* default graphic unit input */} +
+ + handleNumberChange(e)}> + {Array.from(dropdownsState.compatibleGraphicUnits).map(unit => { + return () + })} + {Array.from(dropdownsState.incompatibleGraphicUnits).map(unit => { + return () + })} + +
+ {/* Enabled input */} +
+ + handleBooleanChange(e)}> + {Object.keys(TrueFalseType).map(key => { + return () + })} + +
+ {/* Displayable input */} +
+ + handleBooleanChange(e)}> + {Object.keys(TrueFalseType).map(key => { + return () + })} + +
+ {/* Meter type input */} +
+ + handleStringChange(e)}> + {/* The dB expects lowercase. */} + {Object.keys(MeterType).map(key => { + return () + })} + +
+ {/* Meter reading frequency */} +
+ + handleStringChange(e)} + value={state.readingFrequency} + invalid={state.readingFrequency === ''}/> + + + +
+ {/* URL input */} +
+ + handleStringChange(e)} + value={nullToEmptyString(state.url)} /> +
+ {/* Area input */} +
+ + handleNumberChange(e)} + invalid={state.area < 0} /> + + + +
+ {/* meter area unit input */} +
+ + handleStringChange(e)} + invalid={state.area > 0 && state.areaUnit === AreaUnitType.none}> + {Object.keys(AreaUnitType).map(key => { + return () + })} + + + + +
+ {/* GPS input */} +
+ + handleStringChange(e)} + value={getGPSString(state.gps)} /> +
+ {/* note input */} +
+ + handleStringChange(e)} + value={nullToEmptyString(state.note)} + placeholder='Note' /> +
+ {/* cumulative input */} +
+ + handleBooleanChange(e)}> + {Object.keys(TrueFalseType).map(key => { + return () + })} + +
+ {/* cumulativeReset input */} +
+ + handleBooleanChange(e)}> + {Object.keys(TrueFalseType).map(key => { + return () + })} + +
+ {/* cumulativeResetStart input */} +
+ + handleStringChange(e)} + value={state.cumulativeResetStart} + placeholder="HH:MM:SS" /> +
+ {/* cumulativeResetEnd input */} +
+ + handleStringChange(e)} + value={state?.cumulativeResetEnd} + placeholder="HH:MM:SS" /> +
+ {/* endOnlyTime input */} +
+ + handleBooleanChange(e)}> + {Object.keys(TrueFalseType).map(key => { + return () + })} + +
+ {/* readingGap input */} +
+ + handleNumberChange(e)} + min="0" + defaultValue={state?.readingGap} + invalid={state?.readingGap < 0}/> + + + +
+ {/* readingVariation input */} +
+ + handleNumberChange(e)} + min="0" + defaultValue={state?.readingVariation} + invalid={state?.readingVariation < 0} /> + + + +
+ {/* readingDuplication input */} +
+ + handleNumberChange(e)} + step="1" + min="1" + max="9" + defaultValue={state?.readingDuplication} + invalid={state?.readingDuplication < 1 || state?.readingDuplication > 9}/> + + + +
+ {/* timeSort input */} +
+ + handleStringChange(e)}> + {Object.keys(MeterTimeSortType).map(key => { + // This is a bit of a hack but it should work fine. The TypeSortTypes and MeterTimeSortType should be in sync. + // The translation is on the former so we use that enum name there but loop on the other to get the value desired. + return () + })} + +
+ {/* Timezone input */} +
+ + handleTimeZoneChange(timeZone)} /> +
+ {/* reading input */} +
+ + handleNumberChange(e)} + defaultValue={state?.reading} /> +
+ {/* startTimestamp input */} +
+ + handleStringChange(e)} + placeholder="YYYY-MM-DD HH:MM:SS" + value={state?.startTimestamp} /> +
+ {/* endTimestamp input */} +
+ + handleStringChange(e)} + placeholder="YYYY-MM-DD HH:MM:SS" + value={state?.endTimestamp} /> +
+ {/* previousEnd input */} +
+ + handleStringChange(e)} + placeholder="YYYY-MM-DD HH:MM:SS" + value={state?.previousEnd} /> +
+
{/* Hides the modal */}
{/* Name input */}
- + handleStringChange(e)} - value={state.name} /> + value={state.name} + invalid={state.name === ''}/> + + +
{/* Type of unit input */}
@@ -183,10 +187,12 @@ export default function CreateUnitModalComponent() { name='secInRate' type='number' onChange={e => handleNumberChange(e)} - value={state.secInRate} - // TODO validate negative input by typing for rate but database stops it. - // This stops negative input by use of arrows to change value. - min="1" /> + defaultValue={state.secInRate} + min="1" + invalid={state.secInRate < 1} /> + + +
{/* Suffix input */}
@@ -213,7 +219,7 @@ export default function CreateUnitModalComponent() { {/* On click calls the function handleSaveChanges in this component */} - diff --git a/src/client/app/components/unit/EditUnitModalComponent.tsx b/src/client/app/components/unit/EditUnitModalComponent.tsx index 06bf0def1..67e735293 100644 --- a/src/client/app/components/unit/EditUnitModalComponent.tsx +++ b/src/client/app/components/unit/EditUnitModalComponent.tsx @@ -4,9 +4,9 @@ import * as React from 'react'; import store from '../../index'; //Realize that * is already imported from react -import { useState } from 'react'; +import { useEffect, useState } from 'react'; import { useDispatch } from 'react-redux'; -import { Button, Input, Modal, ModalBody, ModalFooter, ModalHeader } from 'reactstrap'; +import { Button, FormFeedback, Input, Modal, ModalBody, ModalFooter, ModalHeader } from 'reactstrap'; import { FormattedMessage } from 'react-intl'; import translate from '../../utils/translate'; import TooltipMarkerComponent from '../TooltipMarkerComponent'; @@ -17,7 +17,7 @@ import { submitEditedUnit } from '../../actions/units'; import { UnitData, DisplayableType, UnitRepresentType, UnitType } from '../../types/redux/units'; import { TrueFalseType } from '../../types/items'; import { notifyUser } from '../../utils/input' -import { formInputStyle, tableStyle, requiredStyle, tooltipBaseStyle } from '../../styles/modalStyle'; +import { formInputStyle, tableStyle, tooltipBaseStyle } from '../../styles/modalStyle'; interface EditUnitModalComponentProps { show: boolean; @@ -59,6 +59,11 @@ export default function EditUnitModalComponent(props: EditUnitModalComponentProp const handleNumberChange = (e: React.ChangeEvent) => { setState({ ...state, [e.target.name]: Number(e.target.value) }); } + + const [validUnit, setValidUnit] = useState(false); + useEffect(() => { + setValidUnit(state.name !== '' && state.secInRate >= 1); + }, [state.name, state.secInRate]); /* End State */ // Reset the state to default values @@ -77,19 +82,12 @@ export default function EditUnitModalComponent(props: EditUnitModalComponentProp // Validate the changes and return true if we should update this unit. // Two reasons for not updating the unit: - // 0. The rate is not greater than 0. // 1. typeOfUnit is changed from meter to something else while some meters are still linked with this unit // 2. There are no changes const shouldUpdateUnit = (): boolean => { // true if inputted values are okay and there are changes. let inputOk = true; - // Check for case 0 - if (state.secInRate <= 0) { - notifyUser(`${translate('unit.rate.error')} ${state.secInRate}.`); - inputOk = false; - } - // Check for case 1 if (props.unit.typeOfUnit === UnitType.meter && state.typeOfUnit !== UnitType.meter) { // Get an array of all meters @@ -178,12 +176,16 @@ export default function EditUnitModalComponent(props: EditUnitModalComponentProp
{/* Name input */}
- + handleStringChange(e)} - value={state.name} /> + value={state.name} + invalid={state.name === ''}/> + + +
{/* Type of unit input */}
@@ -243,12 +245,14 @@ export default function EditUnitModalComponent(props: EditUnitModalComponentProp handleNumberChange(e)} placeholder="Sec In Rate" - // TODO validate negative input by typing for rate but database stops it. - // This stops negative input by use of arrows to change value. - min="1" /> + min="1" + invalid={state.secInRate < 1} /> + + +
{/* Suffix input */}
@@ -277,7 +281,7 @@ export default function EditUnitModalComponent(props: EditUnitModalComponentProp {/* On click calls the function handleSaveChanges in this component */} - diff --git a/src/client/app/containers/csv/UploadCSVContainer.tsx b/src/client/app/containers/csv/UploadCSVContainer.tsx index 6808f45e7..210b839f7 100644 --- a/src/client/app/containers/csv/UploadCSVContainer.tsx +++ b/src/client/app/containers/csv/UploadCSVContainer.tsx @@ -269,8 +269,8 @@ export default class UploadCSVContainer extends React.Component<{}, UploadCSVCon } return (
- +
- ); - } - - private openModal() { - this.setState({ showModal: true }); - } - - private closeModal() { - this.setState({ showModal: false }); - } -} diff --git a/src/client/app/components/meters/EditMeterModalComponent.tsx b/src/client/app/components/meters/EditMeterModalComponent.tsx index 3f213c95e..6a105ebdf 100644 --- a/src/client/app/components/meters/EditMeterModalComponent.tsx +++ b/src/client/app/components/meters/EditMeterModalComponent.tsx @@ -417,7 +417,7 @@ export default function EditMeterModalComponent(props: EditMeterModalComponentPr - + {/* Enabled input */} @@ -457,7 +457,7 @@ export default function EditMeterModalComponent(props: EditMeterModalComponentPr - + {/* Meter type input */} @@ -489,7 +489,7 @@ export default function EditMeterModalComponent(props: EditMeterModalComponentPr - + {/* URL input */} @@ -513,7 +513,7 @@ export default function EditMeterModalComponent(props: EditMeterModalComponentPr value={getGPSString(state.gps)} /> - + {/* Area input */} @@ -559,7 +559,7 @@ export default function EditMeterModalComponent(props: EditMeterModalComponentPr value={nullToEmptyString(state.note)} placeholder='Note' /> - + {/* cumulative input */} @@ -613,7 +613,7 @@ export default function EditMeterModalComponent(props: EditMeterModalComponentPr placeholder="HH:MM:SS" /> - + {/* endOnlyTime input */} @@ -644,7 +644,7 @@ export default function EditMeterModalComponent(props: EditMeterModalComponentPr - + {/* readingVariation input */} @@ -678,7 +678,7 @@ export default function EditMeterModalComponent(props: EditMeterModalComponentPr - + {/* timeSort input */} From af1338ab882122a8527dc4766d88e568da8c7f72 Mon Sep 17 00:00:00 2001 From: spearec Date: Tue, 11 Jul 2023 15:10:06 +0000 Subject: [PATCH 10/15] misc fixes - fix default language not being set properly - fix a couple jsdoc comments - add validation for groups to not allow submission without child meters --- src/client/app/actions/admin.ts | 4 ++++ .../components/ConfirmActionModalComponent.tsx | 10 ++-------- src/client/app/components/HeaderComponent.tsx | 5 +++-- .../app/components/InitializationComponent.tsx | 4 ---- .../components/LanguageSelectorComponent.tsx | 5 +++-- .../app/components/MenuModalComponent.tsx | 4 ++++ .../groups/CreateGroupModalComponent.tsx | 18 +++++++----------- .../groups/EditGroupModalComponent.tsx | 12 ++++++++---- 8 files changed, 31 insertions(+), 31 deletions(-) diff --git a/src/client/app/actions/admin.ts b/src/client/app/actions/admin.ts index cf1e4a932..f89b983e7 100644 --- a/src/client/app/actions/admin.ts +++ b/src/client/app/actions/admin.ts @@ -14,6 +14,7 @@ import translate from '../utils/translate'; import { LanguageTypes } from '../types/redux/i18n'; import * as moment from 'moment'; import { AreaUnitType } from '../utils/getAreaUnitConversion'; +import { updateSelectedLanguage } from './options'; export function updateSelectedMeter(meterID: number): t.UpdateImportMeterAction { return { type: ActionType.UpdateImportMeter, meterID }; @@ -95,6 +96,9 @@ function fetchPreferences(): Thunk { if (preferences.defaultAreaNormalization !== state.graph.areaNormalization) { dispatch2(toggleAreaNormalization()); } + if (preferences.defaultLanguage !== state.options.selectedLanguage) { + dispatch2(updateSelectedLanguage(preferences.defaultLanguage)); + } }); } }; diff --git a/src/client/app/components/ConfirmActionModalComponent.tsx b/src/client/app/components/ConfirmActionModalComponent.tsx index c17a429a8..3753f02eb 100644 --- a/src/client/app/components/ConfirmActionModalComponent.tsx +++ b/src/client/app/components/ConfirmActionModalComponent.tsx @@ -5,7 +5,6 @@ import * as React from 'react'; import '../styles/modal.css'; import translate from '../utils/translate'; import { Button, Modal, ModalBody, ModalFooter, ModalHeader } from 'reactstrap'; -import { formInputStyle } from '../styles/modalStyle'; interface ConfirmActionModalComponentProps { // Control this through the parent component to open/close this modal @@ -52,13 +51,8 @@ export default function ConfirmActionModalComponent(props: ConfirmActionModalCom {props.actionTitle ? props.actionTitle : translate('confirm.action')} - {/* when any of the conversion are changed call one of the functions. */} - - {/* SourceId input*/} -
-

{props.actionConfirmMessage}

-
-
+ {/* Passed message is already translated */} + {props.actionConfirmMessage} {/* Do not execute the actionFunction and instead close the action confirm modal */}
{/* collapse menu if optionsVisibility is false */} - {!showOptions ? + {getPage() === '' && !showOptions ? : } @@ -61,5 +63,4 @@ export default function HeaderComponent() {
); - } diff --git a/src/client/app/components/InitializationComponent.tsx b/src/client/app/components/InitializationComponent.tsx index 635cffbf4..6e3e7e115 100644 --- a/src/client/app/components/InitializationComponent.tsx +++ b/src/client/app/components/InitializationComponent.tsx @@ -17,7 +17,6 @@ import { fetchPreferencesIfNeeded } from '../actions/admin'; import { fetchMapsDetails } from '../actions/map'; import { fetchUnitsDetailsIfNeeded } from '../actions/units'; import { fetchConversionsDetailsIfNeeded } from '../actions/conversions'; -import { updateSelectedLanguage } from '../actions/options'; /** * Initializes OED redux with needed details @@ -29,8 +28,6 @@ export default function InitializationComponent() { let notificationSystem: NotificationSystem; - const defaultLanguage = useSelector((state: State) => state.admin.defaultLanguage); - // Only run once by making it depend on an empty array. useEffect(() => { dispatch(fetchMetersDetailsIfNeeded()); @@ -39,7 +36,6 @@ export default function InitializationComponent() { dispatch(fetchMapsDetails()); dispatch(fetchUnitsDetailsIfNeeded()); dispatch(fetchConversionsDetailsIfNeeded()); - dispatch(updateSelectedLanguage(defaultLanguage)); ConversionArray.fetchPik(); }, []); diff --git a/src/client/app/components/LanguageSelectorComponent.tsx b/src/client/app/components/LanguageSelectorComponent.tsx index 01e0dbfaa..786d3af7e 100644 --- a/src/client/app/components/LanguageSelectorComponent.tsx +++ b/src/client/app/components/LanguageSelectorComponent.tsx @@ -12,6 +12,7 @@ import { updateSelectedLanguage } from '../actions/options'; /** * A component that allows users to select which language the page should be displayed in. + * @returns Language selector element for navbar */ export default function LanguageSelectorComponent() { const dispatch = useDispatch(); @@ -20,7 +21,7 @@ export default function LanguageSelectorComponent() { const version = useSelector((state: State) => state.version.version); return ( -
+ <> @@ -48,6 +49,6 @@ export default function LanguageSelectorComponent() { -
+ ); } \ No newline at end of file diff --git a/src/client/app/components/MenuModalComponent.tsx b/src/client/app/components/MenuModalComponent.tsx index 24cad5549..ac38f1089 100644 --- a/src/client/app/components/MenuModalComponent.tsx +++ b/src/client/app/components/MenuModalComponent.tsx @@ -11,6 +11,10 @@ import ReactTooltip from 'react-tooltip'; import { useState } from 'react'; import getPage from '../utils/getPage'; +/** + * React component to define the collapsed menu modal + * @returns Modal element + */ export default function MenuModalComponent() { const [showModal, setShowModal] = useState(false); const toggleModal = () => { setShowModal(!showModal); } diff --git a/src/client/app/components/groups/CreateGroupModalComponent.tsx b/src/client/app/components/groups/CreateGroupModalComponent.tsx index 5911d75f3..4efb3593c 100644 --- a/src/client/app/components/groups/CreateGroupModalComponent.tsx +++ b/src/client/app/components/groups/CreateGroupModalComponent.tsx @@ -117,14 +117,18 @@ export default function CreateGroupModalComponent(props: CreateGroupModalCompone */ const [validGroup, setValidGroup] = useState(false); useEffect(() => { - setValidGroup(state.name !== '' && (state.area === 0 || (state.area > 0 && state.areaUnit !== AreaUnitType.none))); - }, [state.area, state.areaUnit, state.name]); + setValidGroup( + state.name !== '' && + (state.area === 0 || (state.area > 0 && state.areaUnit !== AreaUnitType.none)) && + (state.deepMeters.length > 0) + ); + }, [state.area, state.areaUnit, state.name, state.deepMeters]); /* End State */ // Sums the area of the group's deep meters. It will tell the admin if any meters are omitted from the calculation, // or if any other errors are encountered. const handleAutoCalculateArea = () => { - if (state.deepMeters != undefined && state.deepMeters.length > 0) { + if (state.deepMeters.length > 0) { if (state.areaUnit != AreaUnitType.none) { let areaSum = 0; let notifyMsg = ''; @@ -209,13 +213,6 @@ export default function CreateGroupModalComponent(props: CreateGroupModalCompone } } - // Do not allow groups without any child meters and groups. From a practical standpoint, this - // means there are no deep children. - if (state.deepMeters?.length === 0) { - notifyUser(translate('group.children.error')); - inputOk = false; - } - if (inputOk) { // The input passed validation. // GPS may have been updated so create updated state to submit. @@ -298,7 +295,6 @@ export default function CreateGroupModalComponent(props: CreateGroupModalCompone - diff --git a/src/client/app/components/groups/EditGroupModalComponent.tsx b/src/client/app/components/groups/EditGroupModalComponent.tsx index ee343535b..a94d8d91c 100644 --- a/src/client/app/components/groups/EditGroupModalComponent.tsx +++ b/src/client/app/components/groups/EditGroupModalComponent.tsx @@ -126,12 +126,16 @@ export default function EditGroupModalComponent(props: EditGroupModalComponentPr Name cannot be blank Area must be positive or zero If area is nonzero, area unit must be set - Group must have at least one child (checked on submit) + Group must have at least one child */ const [validGroup, setValidGroup] = useState(false); useEffect(() => { - setValidGroup(groupState.name !== '' && (groupState.area === 0 || (groupState.area > 0 && groupState.areaUnit !== AreaUnitType.none))); - }, [groupState.area, groupState.areaUnit, groupState.name]); + setValidGroup( + groupState.name !== '' && + (groupState.area === 0 || (groupState.area > 0 && groupState.areaUnit !== AreaUnitType.none)) && + (groupState.deepMeters.length > 0) + ); + }, [groupState.area, groupState.areaUnit, groupState.name, groupState.deepMeters]); /* End State */ /* Confirm Delete Modal */ @@ -167,7 +171,7 @@ export default function EditGroupModalComponent(props: EditGroupModalComponentPr // Sums the area of the group's deep meters. It will tell the admin if any meters are omitted from the calculation, // or if any other errors are encountered. const handleAutoCalculateArea = () => { - if (groupState.deepMeters != undefined && groupState.deepMeters.length > 0) { + if (groupState.deepMeters.length > 0) { if (groupState.areaUnit != AreaUnitType.none) { let areaSum = 0; let notifyMsg = ''; From f0d2e7976857334b2614e9a4f364cb17bd1ce555 Mon Sep 17 00:00:00 2001 From: spearec Date: Thu, 13 Jul 2023 16:00:23 +0000 Subject: [PATCH 11/15] Fix SecInRate validation and remove unused text - some tooltips and text entries were removed by this pr but still in data.js --- .../app/components/TooltipHelpComponent.tsx | 10 ++---- .../CreateConversionModalComponent.tsx | 2 +- .../EditConversionModalComponent.tsx | 1 + .../unit/CreateUnitModalComponent.tsx | 4 +-- .../unit/EditUnitModalComponent.tsx | 6 ++-- src/client/app/translations/data.js | 34 ++----------------- 6 files changed, 12 insertions(+), 45 deletions(-) diff --git a/src/client/app/components/TooltipHelpComponent.tsx b/src/client/app/components/TooltipHelpComponent.tsx index d73457d43..32c606c74 100644 --- a/src/client/app/components/TooltipHelpComponent.tsx +++ b/src/client/app/components/TooltipHelpComponent.tsx @@ -34,6 +34,8 @@ export default class TooltipHelpComponent extends React.Component> = { @@ -63,15 +65,7 @@ export default class TooltipHelpComponent extends React.Component { - /* + /* Create Conversion Validation: Source equals destination: invalid conversion Conversion exists: invalid conversion Conversion does not exist: diff --git a/src/client/app/components/conversion/EditConversionModalComponent.tsx b/src/client/app/components/conversion/EditConversionModalComponent.tsx index 7b410be3c..b1331531c 100644 --- a/src/client/app/components/conversion/EditConversionModalComponent.tsx +++ b/src/client/app/components/conversion/EditConversionModalComponent.tsx @@ -115,6 +115,7 @@ export default function EditConversionModalComponent(props: EditConversionModalC // Currently using the old functionality which is to compare inherited prop values to state values // If there is a difference between props and state, then a change was made // Side note, we could probably just set a boolean when any input i + // Edit Conversion Validation: is not needed as no breaking edits can be made const handleSaveChanges = () => { // Close the modal first to avoid repeat clicks props.handleClose(); diff --git a/src/client/app/components/unit/CreateUnitModalComponent.tsx b/src/client/app/components/unit/CreateUnitModalComponent.tsx index c90a0fdec..09ba5762e 100644 --- a/src/client/app/components/unit/CreateUnitModalComponent.tsx +++ b/src/client/app/components/unit/CreateUnitModalComponent.tsx @@ -214,9 +214,9 @@ export default function CreateUnitModalComponent() { onChange={e => handleNumberChange(e)} defaultValue={state.secInRate} min="1" - invalid={state.secInRate < 1} /> + invalid={state.secInRate <= 0} /> - + {/* Suffix input */} diff --git a/src/client/app/components/unit/EditUnitModalComponent.tsx b/src/client/app/components/unit/EditUnitModalComponent.tsx index fc457ec18..b86220e75 100644 --- a/src/client/app/components/unit/EditUnitModalComponent.tsx +++ b/src/client/app/components/unit/EditUnitModalComponent.tsx @@ -72,7 +72,7 @@ export default function EditUnitModalComponent(props: EditUnitModalComponentProp */ const [validUnit, setValidUnit] = useState(false); useEffect(() => { - setValidUnit(state.name !== '' && state.secInRate >= 1); + setValidUnit(state.name !== '' && state.secInRate > 0); }, [state.name, state.secInRate]); /* End State */ @@ -277,9 +277,9 @@ export default function EditUnitModalComponent(props: EditUnitModalComponentProp onChange={e => handleNumberChange(e)} placeholder="Sec In Rate" min="1" - invalid={state.secInRate < 1} /> + invalid={state.secInRate <= 0} /> - + {/* Suffix input */} diff --git a/src/client/app/translations/data.js b/src/client/app/translations/data.js index 87df5cbf0..c14b919ff 100644 --- a/src/client/app/translations/data.js +++ b/src/client/app/translations/data.js @@ -131,10 +131,9 @@ const localeData = { "DisplayableType.none": "none", "DisplayableType.all": "all", "DisplayableType.admin": "admin", - "duplication.invalid": "The reading duplication must be an integer between 1 and 9 but you gave ", "error.bounds": "Must be between {min} and {max}.", "error.displayable": "Displayable will be set to false.", - "error.greater.or.equal": "Must be greater than or equal to {min}.", + "error.greater": "Must be greater than {min}.", "error.negative": "Cannot be negative.", "error.required": "Required field.", "error.gps": "Latitude must be between -90 and 90, and Longitude must be between -180 and 180.", @@ -171,7 +170,6 @@ const localeData = { "group.area.calculate.error.no.meters": "No meters in group", "group.area.calculate.error.group.unit": "No group area unit", "group.area.unit": "Group Area Unit:", - "group.children.error": "Groups must have at least one child meter or group.", "group.create.nounit": "The default graphic unit was changed to no unit from ", "group.defaultGraphicUnit": "Default Graphic Unit:", "group.delete.group": "Delete Group", @@ -234,9 +232,7 @@ const localeData = { "help.home.compare.interval.tip": "Selects the time interval (Day, Week or 4 Weeks) to compare for current to previous. Please see {link} for further details and information.", "help.home.compare.sort.tip": "Allows user to select the order of multiple comparison graphs to be Alphabetical (by name), Ascending (greatest to least reduction in usage) and Descending (least to greatest reduction in usage). Please see {link} for further details and information.", "help.home.export.graph.data": "With the \"Export graph data\" button, one can export the data for the graph when viewing either a line or compare graphic. The zoom and scroll feature on the line graph allows you to control the time frame of the data exported. The \"Export graph data\" button gives the data points for the graph and not the original meter data. The \"Export graph meter data\" gives the underlying meter data (line graphs only). Please visit {link} for further details and information on when meter data export is allowed.", - "help.home.header": "The \"Groups\", \"Meters\", \"Home\", \"Admin\" and \"Log in\" buttons allow you to get to other OED pages as described {link0}. See the help on each page and/or visit {link1} for meters and {link2} for groups information and {link3} for maps. Admin login features can be found {link4}.", "help.home.hide.or.show.options": "With the \"Hide options\" button the options buttons and dropdown menus on the left and top of the OED window are all hidden. A new \"Menu\" button at the top, right of the web page is available to see these options including a button to \"Show options\" to reverse this choice. When the web page window becomes too small the options will automatically hide. Please visit {link} for further details and information.", - "help.home.language": "Select your desired display language. Please visit {link} for further details and information.", "help.home.select.groups": "Groups aggregate (sum the usage) of any combination of groups and meters. You can choose which groups to view in your graphic from the \"Groups:\" dropdown menu. Note you can type in text to limit which groups are shown. The Groups button in the top, right side of the window allows you to see more details about each group and, if you are an admin, to edit the groups. Please visit {link} for further details and information.", "help.home.select.maps": "Maps are a spacial representation of a site. You can choose which map to view from the \"Maps:\" dropdown menu. Note you can type in text to limit which meters are shown. The Maps button in the top, right side of the window allows you to see more details about each map and, if you are an admin, to edit the maps. Please visit {link} for further details and information.", "help.home.select.meters": "Meters are the basic unit of usage and generally represent the readings from a single usage meter. You can choose which meters to view in your graphic from the \"Meters:\" dropdown menu. Note you can type in text to limit which meters are shown. The Meters button in the top, right side of the window allows you to see more details about each meter and, if you are an admin, to edit the meters. Please visit {link} for further details and information.", @@ -315,14 +311,12 @@ const localeData = { "meter.failed.to.create.meter": "Failed to create a meter with message: ", "meter.failed.to.edit.meter": "Failed to edit meter with message: ", "meter.gps": "GPS: latitude, longitude", - "meter.graphic.invalid": "A value must be chosen for the meter default graphic unit.", "meter.hidden": "At least one meter is not visible to you", "meter.id": "ID", "meter.identifier": "Identifier:", "meter.input.error": "Input invalid so meter not created or edited.", "meter.note": "Note:", "meter.unit.change.requires": "needs to be changed before changing this unit's type", - "meter.unit.invalid": "A value must be chosen for the meter unit.", "meter.unitName": "Unit:", "meter.url": "URL:", "meter.is.displayable": "Display Enabled", @@ -342,7 +336,6 @@ const localeData = { "meter.timeSort": "Time Sort:", "meter.time.zone": "Time Zone:", "meter.type": "Type:", - "meter.type.invalid": "A value must be chosen for the meter type.", "minute": "Minute", "more.energy": "more energy", "name": "Name", @@ -364,8 +357,6 @@ const localeData = { "per.second": "Per Second", "projected.to.be.used": "projected to be used", "rate": "Rate", - "reading.gap.invalid": "Reading gap must be at least zero. You gave ", - "reading.variation.invalid": "Reading variation must be at least zero. You gave ", "redo.cik.and.refresh.db.views": "Processing changes. This may take a while.", "redraw": "Redraw", "remove": "Remove", @@ -427,7 +418,6 @@ const localeData = { "unit.none": "no unit", "unit.note": "Note:", "unit.preferred.display": "Preferred Display:", - "unit.remove": "Remove", "unit.represent": "Unit Represent:", "unit.sec.in.rate": "Sec in Rate:", @@ -590,10 +580,9 @@ const localeData = { "DisplayableType.none": "(Need French) none", "DisplayableType.all": "(Need French) all", "DisplayableType.admin": "(Need French) admin", - "duplication.invalid": "(Need French) The reading duplication must be an integer between 1 and 9 but you gave ", "error.bounds": "(Need French) Must be between {min} and {max}.", "error.displayable": "(Need French) Displayable will be set to false.", - "error.greater.or.equal": "(Need French) Must be greater than or equal to {min}.", + "error.greater": "(Need French) Must be greater than {min}.", "error.negative": "(Need French) Cannot be negative.", "error.required": "(Need French) Required field.", "edit": "Modifier", @@ -629,7 +618,6 @@ const localeData = { "group.area.calculate.error.no.meters": "(Need French) No meters in group", "group.area.calculate.error.group.unit": "(Need French) No group area unit", "group.area.unit": "(Need French) Group Area Unit:", - "group.children.error": "(Need French) Groups must have at least one child meter or group.", "group.create.nounit": "(Need French) The default graphic unit was changed to no unit from ", "group.defaultGraphicUnit": "(Need French) Default Graphic Unit:", "group.delete.group": "(Need French) Delete Group", @@ -692,9 +680,7 @@ const localeData = { "help.home.compare.interval.tip": "(Need French) Selects the time interval (Day, Week or 4 Weeks) to compare for current to previous. Please see {link} for further details and information.", "help.home.compare.sort.tip": "(Need French) Allows user to select the order of multiple comparison graphs to be Alphabetical (by name), Ascending (greatest to least reduction in usage) and Descending (least to greatest reduction in usage). Please see {link} for further details and information.", "help.home.export.graph.data": "(Need French) With the \"Export graph data\" button, one can export the data for the graph when viewing either a line or compare graphic. The zoom and scroll feature on the line graph allows you to control the time frame of the data exported. The \"Export graph data\" button gives the data points for the graph and not the original meter data. The \"Export graph meter data\" gives the underlying meter data (line graphs only). Please visit {link} for further details and information on when meter data export is allowed.", - "help.home.header": "(Need French) The \"Groups\", \"Meters\", \"Home\", \"Admin\" and \"Log in\" buttons allow you to get to other OED pages as described {link0}. See the help on each page and/or visit {link1} for meters and {link2} for groups information and {link3} for maps. Admin login features can be found {link4}.", "help.home.hide.or.show.options": "(Need French) With the \"Hide options\" button the options buttons and dropdown menus on the left and top of the OED window are all hidden. A new \"Menu\" button at the top, right of the web page is available to see these options including a button to \"Show options\" to reverse this choice. When the web page window becomes too small the options will automatically hide. Please visit {link} for further details and information.", - "help.home.language": "(Need French) Select your desired display language. Please visit {link} for further details and information.", "help.home.select.groups": "(Need French) Groups aggregate (sum the usage) of any combination of groups and meters. You can choose which groups to view in your graphic from the \"Groups:\" dropdown menu. Note you can type in text to limit which groups are shown. The Groups button in the top, right side of the window allows you to see more details about each group and, if you are an admin, to edit the groups. Please visit {link} for further details and information.", "help.home.select.maps": "(Need French) Maps are a spacial representation of a site. You can choose which map to view from the \"Maps:\" dropdown menu. Note you can type in text to limit which meters are shown. The Maps button in the top, right side of the window allows you to see more details about each map and, if you are an admin, to edit the maps. Please visit {link} for further details and information.", "help.home.select.meters": "(Need French) Meters are the basic unit of usage and generally represent the readings from a single usage meter. You can choose which meters to view in your graphic from the \"Meters:\" dropdown menu. Note you can type in text to limit which meters are shown. The Meters button in the top, right side of the window allows you to see more details about each meter and, if you are an admin, to edit the meters. Please visit {link} for further details and information.", @@ -773,14 +759,12 @@ const localeData = { "meter.failed.to.create.meter": "(Need French) Failed to create a meter with message: ", "meter.failed.to.edit.meter": "(Need French) Failed to edit meter with message: ", "meter.gps": "(Need French) Meter GPS: latitude, longitude", - "meter.graphic.invalid": "(Need French) A value must be chosen for the meter default graphic unit.", "meter.hidden": "(Need French) At least one meter is not visible to you", "meter.id": "Identifiant du Mèters", "meter.identifier": "(Need French) Meter identifier", "meter.input.error": "(Need French) Input invalid so meter not created or edited.", "meter.note": "(Need French) Note:", "meter.unit.change.requires": "(Need French) needs to be changed before changing this unit's type", - "meter.unit.invalid": "(Need French) A value must be chosen for the meter unit.", "meter.unitName": "(Need French) Unit:", "meter.url": "URL du Mèters", "meter.is.displayable": "Affichage Activées", @@ -800,7 +784,6 @@ const localeData = { "meter.timeSort": "(need French) Time Sort:", "meter.time.zone": "fuseau horaire du mètre", "meter.type": "Type de Mèters", - "meter.type.invalid": "(Need French) A value must be chosen for the meter type.", "minute": "(need French) Minute", "more.energy": "plus d'énergie", "name": "Nom", @@ -822,8 +805,6 @@ const localeData = { "per.second": "(Need French) Per Second", "projected.to.be.used": "projeté pour être utilisé", "rate": "(need French) Rate", - "reading.gap.invalid": "(need French) Reading gap must be at least zero. You gave ", - "reading.variation.invalid": "(need French) Reading variation must be at least zero. You gave ", "redo.cik.and.refresh.db.views": "(need French) Processing changes. This may take a while", "redraw": "Redessiner", "remove": "(need French) Remove", @@ -1047,10 +1028,9 @@ const localeData = { "DisplayableType.none": "(Need Spanish) none", "DisplayableType.all": "(Need Spanish) all", "DisplayableType.admin": "(Need Spanish) admin", - "duplication.invalid": "(Need Spanish) The reading duplication must be an integer between 1 and 9 but you gave ", "error.bounds": "(Need Spanish) Must be between {min} and {max}.", "error.displayable": "(Need Spanish) Displayable will be set to false.", - "error.greater.or.equal": "(Need Spanish) Must be greater than or equal to {min}.", + "error.greater": "(Need French) Must be greater than {min}.", "error.negative": "(Need Spanish) Cannot be negative.", "error.required": "(Need Spanish) Required field.", "edit": "Editar", @@ -1086,7 +1066,6 @@ const localeData = { "group.area.calculate.error.no.meters": "(Need Spanish) No meters in group", "group.area.calculate.error.group.unit": "(Need Spanish) No group area unit", "group.area.unit": "(Need Spanish) Group Area Unit:", - "group.children.error": "(Need Spanish) Groups must have at least one child meter or group.", "group.create.nounit": "(Need Spanish) The default graphic unit was changed to no unit from ", "group.defaultGraphicUnit": "(Need Spanish) Default Graphic Unit:", "group.delete.group": "(Need Spanish) Delete Group", @@ -1149,9 +1128,7 @@ const localeData = { "help.home.compare.interval.tip": "Selecciona el intervalo de tiempo (Día, Semana o Cuatro semanas) para comparar el actual con el anterior. Por favor, vea {link} para más detalles e información.", "help.home.compare.sort.tip": "Permite al usuario seleccionar el orden de los gráficos de comparación múltiple para que sean alfabéticos (por nombre), ascendentes (de mayor a menor reducción de uso) y descendentes (de menor a mayor reducción de uso). Por favor, vea {link} para más detalles e información.", "help.home.export.graph.data": "Con el botón \"Exportar data de gráfico\", uno puede exportar los datos del gráfico al ver una línea o comparar el gráfico. La función de zoom y desplazamiento en el gráfico de líneas le permite controlar el período de tiempo de los datos exportados. El \"Exportar data de gráfico\" botón da el puntos de dato para el gráfico y no los datos sin procesar de medidor. La \"Exportar el dato gráfhico de medidor\" proporciona los datos del medidor subyacente (solo gráficos de líneas). Visite {link} para obtener más detalles e información.", - "help.home.header": "Los botones \"Grupos\", \"Medidores\", \"Hogar\", \"Administrador\" e \"Entrar\" le permiten acceder a otras páginas del OED como se describe {link0}. Ver la ayuda en cada pagina y/o visitar {link1} para medidores y {link2} para grupos de información y {link3} para mapas. Las funciones de inicio de sesión de los administradores se pueden encontrar {link4}.", "help.home.hide.or.show.options": "Con el botón \"Opciones de muelle\", los botones de opciones y los menús desplegables en la parte superior e izquierda de la ventana de OED están ocultos. Un nuevo botón \"Menú\" en la parte superior derecha de la página web está disponible para ver estas opciones, incluso un botón para \"mostrar opciones\" para revertir esta elección. Cuando la ventana de la página web se vuelve demasiado pequeña, las opciones se ocultarán automáticamente. Visite {link} para obtener más detalles e información.", - "help.home.language": "Seleccione el idioma de visualización que desee. Visite {link} para obtener más detalles e información.", "help.home.select.groups": "Los grupos se agregan (suman el uso) de cualquier combinación de grupos y medidores. Puede elegir qué grupos ver en su gráfico desde el menú desplegable \"Groups:\" . Tenga en cuenta que puede escribir texto para limitar que grupos se muestran. El botón Grupos en la parte superior derecha de la ventana le permite ver más detalles sobre cada grupo y, si es un administrador, editar los grupos. Visite {link} para obtener más detalles e información.", "help.home.select.maps": "Los mapas son una representación espacial de un sitio. Puede elegir el mapa que desea ver en el menú desplegable \"Mapas:\". Tenga en cuenta que puede introducir texto para limitar los metros que se muestran. El botón de Mapas en la parte superior derecha de la ventana le permite ver más detalles sobre cada mapa y, si es un administrador, editar los mapas. Por favor, visite {link} para más detalles e información.", "help.home.select.meters": "Medidor son la unidad básica de uso y generalmente representan las lecturas de un solo medidor de uso. Puede elegir qué medidores ver en su gráfico desde el menú desplegable \"Meters:\". Tenga en cuenta que puede escribir texto para limitar los medidores que se muestran. El botón Medidores en la parte superior derecha de la ventana le permite ver más detalles sobre cada medidor y, si es un administrador, editar los medidores . Visite {link} para obtener más detalles e información.", @@ -1230,14 +1207,12 @@ const localeData = { "meter.failed.to.create.meter": "(Need Spanish) Failed to create a meter with message: ", "meter.failed.to.edit.meter": "(Need Spanish) Failed to edit meter with message: ", "meter.gps": "Medidor GPS: latitud, longitud", - "meter.graphic.invalid": "(Need Spanish) A value must be chosen for the meter default graphic unit.", "meter.hidden": "(Need Spanish) At least one meter is not visible to you", "meter.id": "ID del medidor", "meter.identifier": "Identificador de medidor", "meter.input.error": "(Need Spanish) Input invalid so meter not created or edited.", "meter.note": "(need Spanish) Note:", "meter.unit.change.requires": "(Need Spanish) needs to be changed before changing this unit's type", - "meter.unit.invalid": "(Need Spanish) A value must be chosen for the meter unit.", "meter.unitName": "(need Spanish) Unit:", "meter.url": "URL del medidor", "meter.is.displayable": "El medidor es visualizable", @@ -1257,7 +1232,6 @@ const localeData = { "meter.timeSort": "(need Spanish) Time Sort:", "meter.time.zone": "Zona horaria del medidor", "meter.type": "Tipo de medidor", - "meter.type.invalid": "(Need Spanish) A value must be chosen for the meter type.", "minute": "Minuto", "more.energy": "más energía", "name": "Nombre", @@ -1279,8 +1253,6 @@ const localeData = { "per.second": "(need Spanish) Per Second", "projected.to.be.used": "proyectado para ser utilizado", "rate": "(need Spanish) Rate", - "reading.gap.invalid": "(need Spanish) Reading gap must be at least zero. You gave ", - "reading.variation.invalid": "(need Spanish) Reading variation must be at least zero. You gave ", "redo.cik.and.refresh.db.views": "(need Spanish) Processing changes. This may take a while.", "redraw": "Redibujar", "remove": "Eliminar", From 8481beeae59236618f2c8338a2cb96f83e99cf1a Mon Sep 17 00:00:00 2001 From: spearec Date: Sun, 23 Jul 2023 15:39:12 +0000 Subject: [PATCH 12/15] fix conversion checking and data.js entries --- .../CreateConversionModalComponent.tsx | 22 ++++++++++++++----- .../groups/CreateGroupModalComponent.tsx | 4 ++-- .../groups/EditGroupModalComponent.tsx | 2 +- src/client/app/translations/data.js | 8 ++++--- 4 files changed, 25 insertions(+), 11 deletions(-) diff --git a/src/client/app/components/conversion/CreateConversionModalComponent.tsx b/src/client/app/components/conversion/CreateConversionModalComponent.tsx index 201ced53b..33f414c9b 100644 --- a/src/client/app/components/conversion/CreateConversionModalComponent.tsx +++ b/src/client/app/components/conversion/CreateConversionModalComponent.tsx @@ -73,7 +73,7 @@ export default function CreateConversionModalComponent(props: CreateConversionMo //Update the valid conversion state any time the source id, destination id, or bidirectional status changes useEffect(() => { - setValidConversion(isValidConversion(state.sourceId, state.destinationId, state.bidirectional)); + setValidConversion(isValidConversion(state.sourceId, state.destinationId, state.bidirectional, state.slope, state.intercept)); }, [state.sourceId, state.destinationId, state.bidirectional]); // Reset the state to default values @@ -86,9 +86,11 @@ export default function CreateConversionModalComponent(props: CreateConversionMo * @param sourceId New conversion sourceId * @param destinationId New conversion destinationId * @param bidirectional New conversion bidirectional status + * @param slope New slope + * @param intercept New intercept * @returns boolean representing if new conversion is valid or not */ - const isValidConversion = (sourceId: number, destinationId: number, bidirectional: boolean) => { + const isValidConversion = (sourceId: number, destinationId: number, bidirectional: boolean, slope: number, intercept: number) => { /* Create Conversion Validation: Source equals destination: invalid conversion Conversion exists: invalid conversion @@ -107,6 +109,10 @@ export default function CreateConversionModalComponent(props: CreateConversionMo return false; } + if(slope === null || intercept === null) { + return false; + } + // Source or destination not set if (sourceId === -999 || destinationId === -999) { return false @@ -255,8 +261,6 @@ export default function CreateConversionModalComponent(props: CreateConversionMo - - {/* Bidirectional Y/N input*/} @@ -281,7 +285,11 @@ export default function CreateConversionModalComponent(props: CreateConversionMo type='number' defaultValue={state.slope} onChange={e => handleNumberChange(e)} + invalid={state.slope == null} /> + + + @@ -293,7 +301,11 @@ export default function CreateConversionModalComponent(props: CreateConversionMo name='intercept' type='number' onChange={e => handleNumberChange(e)} - required value={state.intercept} /> + value={state.intercept} + invalid={state.intercept == null} /> + + + diff --git a/src/client/app/components/groups/CreateGroupModalComponent.tsx b/src/client/app/components/groups/CreateGroupModalComponent.tsx index 4efb3593c..4d1cfa07f 100644 --- a/src/client/app/components/groups/CreateGroupModalComponent.tsx +++ b/src/client/app/components/groups/CreateGroupModalComponent.tsx @@ -113,7 +113,7 @@ export default function CreateGroupModalComponent(props: CreateGroupModalCompone Name cannot be blank Area must be positive or zero If area is nonzero, area unit must be set - Group must have at least one child (checked on submit) + Group must have at least one child (i.e has deep child meters) */ const [validGroup, setValidGroup] = useState(false); useEffect(() => { @@ -280,7 +280,7 @@ export default function CreateGroupModalComponent(props: CreateGroupModalCompone }); } // If any of these change then it needs to be updated. - // metersState normally does not change but can so include.')} + // metersState normally does not change but can so include. // pik is needed since the compatible units is not correct until pik is available. }, [ConversionArray.pikAvailable(), metersState, state.deepMeters]); diff --git a/src/client/app/components/groups/EditGroupModalComponent.tsx b/src/client/app/components/groups/EditGroupModalComponent.tsx index a94d8d91c..ed8fd22fe 100644 --- a/src/client/app/components/groups/EditGroupModalComponent.tsx +++ b/src/client/app/components/groups/EditGroupModalComponent.tsx @@ -126,7 +126,7 @@ export default function EditGroupModalComponent(props: EditGroupModalComponentPr Name cannot be blank Area must be positive or zero If area is nonzero, area unit must be set - Group must have at least one child + Group must have at least one child (i.e has deep child meters) */ const [validGroup, setValidGroup] = useState(false); useEffect(() => { diff --git a/src/client/app/translations/data.js b/src/client/app/translations/data.js index c14b919ff..b83109d53 100644 --- a/src/client/app/translations/data.js +++ b/src/client/app/translations/data.js @@ -134,9 +134,9 @@ const localeData = { "error.bounds": "Must be between {min} and {max}.", "error.displayable": "Displayable will be set to false.", "error.greater": "Must be greater than {min}.", + "error.gps": "Latitude must be between -90 and 90, and Longitude must be between -180 and 180.", "error.negative": "Cannot be negative.", "error.required": "Required field.", - "error.gps": "Latitude must be between -90 and 90, and Longitude must be between -180 and 180.", "edit": "Edit", "edited": "edited", "edit.a.group": "Edit a Group", @@ -583,6 +583,7 @@ const localeData = { "error.bounds": "(Need French) Must be between {min} and {max}.", "error.displayable": "(Need French) Displayable will be set to false.", "error.greater": "(Need French) Must be greater than {min}.", + "error.gps": "(Need French) Latitude must be between -90 and 90, and Longitude must be between -180 and 180.", "error.negative": "(Need French) Cannot be negative.", "error.required": "(Need French) Required field.", "edit": "Modifier", @@ -648,7 +649,7 @@ const localeData = { "has.no.data": "(Need French) has no current data", "has.used": "a utilisé", "header.pages": "(Need French) Pages", - "header.options": "Options", + "header.options": "(Need French) Options", "help": "(Need French) Help", "help.admin.conversioncreate": "(Need French) This page allows admins to create conversions. Please visit {link} for further details and information.", "help.admin.conversionedit": "(Need French) This page allows admins to edit conversions. Please visit {link} for further details and information.", @@ -1030,7 +1031,8 @@ const localeData = { "DisplayableType.admin": "(Need Spanish) admin", "error.bounds": "(Need Spanish) Must be between {min} and {max}.", "error.displayable": "(Need Spanish) Displayable will be set to false.", - "error.greater": "(Need French) Must be greater than {min}.", + "error.greater": "(Need Spanish) Must be greater than {min}.", + "error.gps": "(Need Spanish) Latitude must be between -90 and 90, and Longitude must be between -180 and 180.", "error.negative": "(Need Spanish) Cannot be negative.", "error.required": "(Need Spanish) Required field.", "edit": "Editar", From f168f75b197a570bd5b2935051d3ff9bef17c8b7 Mon Sep 17 00:00:00 2001 From: spearec Date: Mon, 24 Jul 2023 17:22:06 +0000 Subject: [PATCH 13/15] Requested changes - move help URL to a single exported constant - fix uses of double quotes - require conversion slope and intercept inputs to always have values --- .../app/components/HeaderButtonsComponent.tsx | 4 +- .../components/LanguageSelectorComponent.tsx | 5 +- .../app/components/TooltipHelpComponent.tsx | 89 ++++++++++--------- .../CreateConversionModalComponent.tsx | 27 ++---- .../EditConversionModalComponent.tsx | 16 ++-- .../groups/CreateGroupModalComponent.tsx | 10 +-- .../groups/EditGroupModalComponent.tsx | 12 +-- .../meters/CreateMeterModalComponent.tsx | 46 +++++----- .../meters/EditMeterModalComponent.tsx | 42 ++++----- .../unit/CreateUnitModalComponent.tsx | 2 +- .../unit/EditUnitModalComponent.tsx | 24 ++--- 11 files changed, 134 insertions(+), 143 deletions(-) diff --git a/src/client/app/components/HeaderButtonsComponent.tsx b/src/client/app/components/HeaderButtonsComponent.tsx index 3dc16e5c0..fa35a79c8 100644 --- a/src/client/app/components/HeaderButtonsComponent.tsx +++ b/src/client/app/components/HeaderButtonsComponent.tsx @@ -18,6 +18,7 @@ import { useDispatch, useSelector } from 'react-redux'; import { Navbar, Nav, NavLink, UncontrolledDropdown, DropdownToggle, DropdownMenu, DropdownItem } from 'reactstrap'; import LanguageSelectorComponent from './LanguageSelectorComponent'; import { toggleOptionsVisibility } from '../actions/graph'; +import { BASE_URL } from './TooltipHelpComponent'; /** * React Component that defines the header buttons at the top of a page @@ -72,6 +73,7 @@ export default function HeaderButtonsComponent() { const optionsVisibility = useSelector((state: State) => state.graph.optionsVisibility); // OED version is needed for help redirect const version = useSelector((state: State) => state.version.version); + const HELP_URL = BASE_URL + version; // This updates which page is disabled because it is the one you are on. useEffect(() => { @@ -248,7 +250,7 @@ export default function HeaderButtonsComponent() { + href={HELP_URL}> diff --git a/src/client/app/components/LanguageSelectorComponent.tsx b/src/client/app/components/LanguageSelectorComponent.tsx index 786d3af7e..bb744bd38 100644 --- a/src/client/app/components/LanguageSelectorComponent.tsx +++ b/src/client/app/components/LanguageSelectorComponent.tsx @@ -9,6 +9,7 @@ import { DropdownItem, DropdownMenu, DropdownToggle, UncontrolledDropdown } from import { useDispatch, useSelector } from 'react-redux'; import { State } from '../types/redux/state'; import { updateSelectedLanguage } from '../actions/options'; +import { BASE_URL } from './TooltipHelpComponent'; /** * A component that allows users to select which language the page should be displayed in. @@ -20,6 +21,8 @@ export default function LanguageSelectorComponent() { const selectedLanguage = useSelector((state: State) => state.options.selectedLanguage); const version = useSelector((state: State) => state.version.version); + const HELP_URL = BASE_URL + version; + return ( <> @@ -44,7 +47,7 @@ export default function LanguageSelectorComponent() { + href={HELP_URL + '/language.html'}> diff --git a/src/client/app/components/TooltipHelpComponent.tsx b/src/client/app/components/TooltipHelpComponent.tsx index 32c606c74..bb0d206f9 100644 --- a/src/client/app/components/TooltipHelpComponent.tsx +++ b/src/client/app/components/TooltipHelpComponent.tsx @@ -13,6 +13,14 @@ interface TooltipHelpProps { version: string; fetchVersionIfNeeded(): Promise; } + +// Normal/live URL for OED help pages +// Exported to HeaderButtonsComponent and LanguageSelectorComponent +export const BASE_URL = 'https://openenergydashboard.github.io/help/' +// Debug URL where need to put own GitHub ID before .github.io. +// This works if you have a fork of the web pages and setup your GitHub account to serve them up. +// export const BASE_URL = `https://xxx.github.io/OpenEnergyDashboard.github.io/help/`; + export default class TooltipHelpComponent extends React.Component { constructor(props: TooltipHelpProps) { super(props); @@ -29,53 +37,46 @@ export default class TooltipHelpComponent extends React.Component> = { - 'help.admin.conversioncreate': { link: `${BASE_URL}/adminConversionCreating.html` }, - 'help.admin.conversionedit': { link: `${BASE_URL}/adminConversionEditing.html` }, - 'help.admin.conversionview': { link: `${BASE_URL}/adminConversionViewing.html` }, - 'help.admin.groupcreate': { link: `${BASE_URL}/adminGroupCreating.html` }, - 'help.admin.groupedit': { link: `${BASE_URL}/adminGroupEditing.html` }, - 'help.admin.groupview': { link: `${BASE_URL}/adminGroupViewing.html` }, - 'help.admin.header': { link: `${BASE_URL}/adminPreferences.html` }, - 'help.admin.mapview': { link: `${BASE_URL}/adminMap.html` }, - 'help.admin.metercreate': { link: `${BASE_URL}/adminMeterCreating.html` }, - 'help.admin.meteredit': { link: `${BASE_URL}/adminMeterEditing.html` }, - 'help.admin.meterview': { link: `${BASE_URL}/adminMeterViewing.html` }, - 'help.admin.unitcreate': { link: `${BASE_URL}/adminUnitCreating.html` }, - 'help.admin.unitedit': { link: `${BASE_URL}/adminUnitEditing.html` }, - 'help.admin.unitview': { link: `${BASE_URL}/adminUnitViewing.html` }, - 'help.admin.user': { link: `${BASE_URL}/adminUser.html` }, - 'help.csv.header': { link: `${BASE_URL}/adminDataAcquisition.html` }, - 'help.home.area.normalize': { link: `${BASE_URL}/areaNormalization.html` }, - 'help.home.bar.custom.slider.tip': { link: `${BASE_URL}/barGraphic.html#usage` }, - 'help.home.bar.interval.tip': { link: `${BASE_URL}/barGraphic.html#usage` }, - 'help.home.bar.stacking.tip': { link: `${BASE_URL}/barGraphic.html#barStacking` }, + 'help.admin.conversioncreate': { link: `${HELP_URL}/adminConversionCreating.html` }, + 'help.admin.conversionedit': { link: `${HELP_URL}/adminConversionEditing.html` }, + 'help.admin.conversionview': { link: `${HELP_URL}/adminConversionViewing.html` }, + 'help.admin.groupcreate': { link: `${HELP_URL}/adminGroupCreating.html` }, + 'help.admin.groupedit': { link: `${HELP_URL}/adminGroupEditing.html` }, + 'help.admin.groupview': { link: `${HELP_URL}/adminGroupViewing.html` }, + 'help.admin.header': { link: `${HELP_URL}/adminPreferences.html` }, + 'help.admin.mapview': { link: `${HELP_URL}/adminMap.html` }, + 'help.admin.metercreate': { link: `${HELP_URL}/adminMeterCreating.html` }, + 'help.admin.meteredit': { link: `${HELP_URL}/adminMeterEditing.html` }, + 'help.admin.meterview': { link: `${HELP_URL}/adminMeterViewing.html` }, + 'help.admin.unitcreate': { link: `${HELP_URL}/adminUnitCreating.html` }, + 'help.admin.unitedit': { link: `${HELP_URL}/adminUnitEditing.html` }, + 'help.admin.unitview': { link: `${HELP_URL}/adminUnitViewing.html` }, + 'help.admin.user': { link: `${HELP_URL}/adminUser.html` }, + 'help.csv.header': { link: `${HELP_URL}/adminDataAcquisition.html` }, + 'help.home.area.normalize': { link: `${HELP_URL}/areaNormalization.html` }, + 'help.home.bar.custom.slider.tip': { link: `${HELP_URL}/barGraphic.html#usage` }, + 'help.home.bar.interval.tip': { link: `${HELP_URL}/barGraphic.html#usage` }, + 'help.home.bar.stacking.tip': { link: `${HELP_URL}/barGraphic.html#barStacking` }, 'help.home.chart.plotly.controls': { link: 'https://plotly.com/chart-studio-help/getting-to-know-the-plotly-modebar/' }, - 'help.home.chart.redraw.restore': { link: `${BASE_URL}/lineGraphic.html#redrawRestore` }, - 'help.home.chart.select': { link: `${BASE_URL}/graphType.html` }, - 'help.home.compare.interval.tip': { link: `${BASE_URL}/compareGraphic.html#usage` }, - 'help.home.compare.sort.tip': { link: `${BASE_URL}/compareGraphic.html#usage` }, - 'help.home.export.graph.data': { link: `${BASE_URL}/export.html` }, - 'help.home.hide.or.show.options': { link: `${BASE_URL}/hideOptions.html` }, - 'help.home.map.interval.tip': { link: `${BASE_URL}/mapGraphic.html#usage` }, - 'help.home.select.groups': { link: `${BASE_URL}/graphingGroups.html` }, - 'help.home.select.maps': { link: `${BASE_URL}/mapGraphic.html` }, - 'help.home.select.meters': { link: `${BASE_URL}/graphingMeters.html` }, - 'help.home.select.units': { link: `${BASE_URL}/graphingUnits.html` }, - 'help.home.toggle.chart.link': { link: `${BASE_URL}/chartLink.html` }, - 'help.groups.groupdetails': { link: `${BASE_URL}/groupDetails.html` }, - 'help.groups.groupview': { link: `${BASE_URL}/groupViewing.html` }, - 'help.maps.mapview': { link: `${BASE_URL}/mapGraphic.html` }, - 'help.meters.meterview': { link: `${BASE_URL}/meterViewing.html` } + 'help.home.chart.redraw.restore': { link: `${HELP_URL}/lineGraphic.html#redrawRestore` }, + 'help.home.chart.select': { link: `${HELP_URL}/graphType.html` }, + 'help.home.compare.interval.tip': { link: `${HELP_URL}/compareGraphic.html#usage` }, + 'help.home.compare.sort.tip': { link: `${HELP_URL}/compareGraphic.html#usage` }, + 'help.home.export.graph.data': { link: `${HELP_URL}/export.html` }, + 'help.home.hide.or.show.options': { link: `${HELP_URL}/hideOptions.html` }, + 'help.home.map.interval.tip': { link: `${HELP_URL}/mapGraphic.html#usage` }, + 'help.home.select.groups': { link: `${HELP_URL}/graphingGroups.html` }, + 'help.home.select.maps': { link: `${HELP_URL}/mapGraphic.html` }, + 'help.home.select.meters': { link: `${HELP_URL}/graphingMeters.html` }, + 'help.home.select.units': { link: `${HELP_URL}/graphingUnits.html` }, + 'help.home.toggle.chart.link': { link: `${HELP_URL}/chartLink.html` }, + 'help.groups.groupdetails': { link: `${HELP_URL}/groupDetails.html` }, + 'help.groups.groupview': { link: `${HELP_URL}/groupViewing.html` }, + 'help.maps.mapview': { link: `${HELP_URL}/mapGraphic.html` }, + 'help.meters.meterview': { link: `${HELP_URL}/meterViewing.html` } }; return ( diff --git a/src/client/app/components/conversion/CreateConversionModalComponent.tsx b/src/client/app/components/conversion/CreateConversionModalComponent.tsx index 33f414c9b..34962e41e 100644 --- a/src/client/app/components/conversion/CreateConversionModalComponent.tsx +++ b/src/client/app/components/conversion/CreateConversionModalComponent.tsx @@ -73,7 +73,7 @@ export default function CreateConversionModalComponent(props: CreateConversionMo //Update the valid conversion state any time the source id, destination id, or bidirectional status changes useEffect(() => { - setValidConversion(isValidConversion(state.sourceId, state.destinationId, state.bidirectional, state.slope, state.intercept)); + setValidConversion(isValidConversion(state.sourceId, state.destinationId, state.bidirectional)); }, [state.sourceId, state.destinationId, state.bidirectional]); // Reset the state to default values @@ -86,11 +86,9 @@ export default function CreateConversionModalComponent(props: CreateConversionMo * @param sourceId New conversion sourceId * @param destinationId New conversion destinationId * @param bidirectional New conversion bidirectional status - * @param slope New slope - * @param intercept New intercept * @returns boolean representing if new conversion is valid or not */ - const isValidConversion = (sourceId: number, destinationId: number, bidirectional: boolean, slope: number, intercept: number) => { + const isValidConversion = (sourceId: number, destinationId: number, bidirectional: boolean) => { /* Create Conversion Validation: Source equals destination: invalid conversion Conversion exists: invalid conversion @@ -109,10 +107,6 @@ export default function CreateConversionModalComponent(props: CreateConversionMo return false; } - if(slope === null || intercept === null) { - return false; - } - // Source or destination not set if (sourceId === -999 || destinationId === -999) { return false @@ -283,13 +277,8 @@ export default function CreateConversionModalComponent(props: CreateConversionMo id='slope' name='slope' type='number' - defaultValue={state.slope} - onChange={e => handleNumberChange(e)} - invalid={state.slope == null} - /> - - - + value={state.slope} + onChange={e => handleNumberChange(e)} /> @@ -300,17 +289,11 @@ export default function CreateConversionModalComponent(props: CreateConversionMo id='intercept' name='intercept' type='number' - onChange={e => handleNumberChange(e)} value={state.intercept} - invalid={state.intercept == null} /> - - - + onChange={e => handleNumberChange(e)} /> - - {/* Note input*/} diff --git a/src/client/app/components/conversion/EditConversionModalComponent.tsx b/src/client/app/components/conversion/EditConversionModalComponent.tsx index b1331531c..5fa22c48e 100644 --- a/src/client/app/components/conversion/EditConversionModalComponent.tsx +++ b/src/client/app/components/conversion/EditConversionModalComponent.tsx @@ -210,8 +210,9 @@ export default function EditConversionModalComponent(props: EditConversionModalC handleNumberChange(e)} /> @@ -221,9 +222,9 @@ export default function EditConversionModalComponent(props: EditConversionModalC handleNumberChange(e)} /> @@ -233,9 +234,10 @@ export default function EditConversionModalComponent(props: EditConversionModalC handleStringChange(e)} /> diff --git a/src/client/app/components/groups/CreateGroupModalComponent.tsx b/src/client/app/components/groups/CreateGroupModalComponent.tsx index 4d1cfa07f..be59cf8e7 100644 --- a/src/client/app/components/groups/CreateGroupModalComponent.tsx +++ b/src/client/app/components/groups/CreateGroupModalComponent.tsx @@ -325,7 +325,7 @@ export default function CreateGroupModalComponent(props: CreateGroupModalCompone {/* Identifier input */} - + handleStringChange(e)} @@ -344,7 +344,7 @@ export default function CreateMeterModalComponent(props: CreateMeterModalCompone {/* Name input */} - + {translate('meter.unitName')} handleNumberChange(e)} @@ -517,9 +517,9 @@ export default function CreateMeterModalComponent(props: CreateMeterModalCompone handleNumberChange(e)} invalid={state.area < 0} /> @@ -596,7 +596,7 @@ export default function CreateMeterModalComponent(props: CreateMeterModalCompone autoComplete='off' onChange={e => handleStringChange(e)} value={state.cumulativeResetStart} - placeholder="HH:MM:SS" /> + placeholder='HH:MM:SS' /> {/* cumulativeResetEnd input */} @@ -608,7 +608,7 @@ export default function CreateMeterModalComponent(props: CreateMeterModalCompone autoComplete='off' onChange={e => handleStringChange(e)} value={state.cumulativeResetEnd} - placeholder="HH:MM:SS" /> + placeholder='HH:MM:SS' /> @@ -634,7 +634,7 @@ export default function CreateMeterModalComponent(props: CreateMeterModalCompone name='readingGap' type='number' onChange={e => handleNumberChange(e)} - min="0" + min='0' defaultValue={state.readingGap} invalid={state?.readingGap < 0}/> @@ -648,10 +648,10 @@ export default function CreateMeterModalComponent(props: CreateMeterModalCompone handleNumberChange(e)} - min="0" + min='0' defaultValue={state.readingVariation} invalid={state?.readingVariation < 0} /> @@ -663,12 +663,12 @@ export default function CreateMeterModalComponent(props: CreateMeterModalCompone handleNumberChange(e)} - step="1" - min="1" - max="9" + step='1' + min='1' + max='9' defaultValue={state.readingDuplication} invalid={state?.readingDuplication < 1 || state?.readingDuplication > 9}/> @@ -703,8 +703,8 @@ export default function CreateMeterModalComponent(props: CreateMeterModalCompone handleNumberChange(e)} defaultValue={state.reading} /> @@ -717,7 +717,7 @@ export default function CreateMeterModalComponent(props: CreateMeterModalCompone type='text' autoComplete='on' onChange={e => handleStringChange(e)} - placeholder="YYYY-MM-DD HH:MM:SS" + placeholder='YYYY-MM-DD HH:MM:SS' value={state.startTimestamp} /> {/* endTimestamp input */} @@ -729,7 +729,7 @@ export default function CreateMeterModalComponent(props: CreateMeterModalCompone type='text' autoComplete='on' onChange={e => handleStringChange(e)} - placeholder="YYYY-MM-DD HH:MM:SS" + placeholder='YYYY-MM-DD HH:MM:SS' value={state.endTimestamp} /> {/* endTimestamp input */} @@ -741,7 +741,7 @@ export default function CreateMeterModalComponent(props: CreateMeterModalCompone type='text' autoComplete='on' onChange={e => handleStringChange(e)} - placeholder="YYYY-MM-DD HH:MM:SS" + placeholder='YYYY-MM-DD HH:MM:SS' value={state.previousEnd} /> diff --git a/src/client/app/components/meters/EditMeterModalComponent.tsx b/src/client/app/components/meters/EditMeterModalComponent.tsx index 88e5d18bd..af4939d97 100644 --- a/src/client/app/components/meters/EditMeterModalComponent.tsx +++ b/src/client/app/components/meters/EditMeterModalComponent.tsx @@ -363,7 +363,7 @@ export default function EditMeterModalComponent(props: EditMeterModalComponentPr handleStringChange(e)} @@ -391,7 +391,7 @@ export default function EditMeterModalComponent(props: EditMeterModalComponentPr handleNumberChange(e)}> @@ -523,9 +523,9 @@ export default function EditMeterModalComponent(props: EditMeterModalComponentPr handleNumberChange(e)} invalid={state.area < 0} /> @@ -602,7 +602,7 @@ export default function EditMeterModalComponent(props: EditMeterModalComponentPr autoComplete='off' onChange={e => handleStringChange(e)} value={state.cumulativeResetStart} - placeholder="HH:MM:SS" /> + placeholder='HH:MM:SS' /> {/* cumulativeResetEnd input */} @@ -614,7 +614,7 @@ export default function EditMeterModalComponent(props: EditMeterModalComponentPr autoComplete='off' onChange={e => handleStringChange(e)} value={state?.cumulativeResetEnd} - placeholder="HH:MM:SS" /> + placeholder='HH:MM:SS' /> @@ -640,7 +640,7 @@ export default function EditMeterModalComponent(props: EditMeterModalComponentPr name='readingGap' type='number' onChange={e => handleNumberChange(e)} - min="0" + min='0' defaultValue={state?.readingGap} invalid={state?.readingGap < 0} /> @@ -654,10 +654,10 @@ export default function EditMeterModalComponent(props: EditMeterModalComponentPr handleNumberChange(e)} - min="0" + min='0' defaultValue={state?.readingVariation} invalid={state?.readingVariation < 0} /> @@ -669,12 +669,12 @@ export default function EditMeterModalComponent(props: EditMeterModalComponentPr handleNumberChange(e)} - step="1" - min="1" - max="9" + step='1' + min='1' + max='9' defaultValue={state?.readingDuplication} invalid={state?.readingDuplication < 1 || state?.readingDuplication > 9} /> @@ -709,8 +709,8 @@ export default function EditMeterModalComponent(props: EditMeterModalComponentPr handleNumberChange(e)} defaultValue={state?.reading} /> @@ -723,7 +723,7 @@ export default function EditMeterModalComponent(props: EditMeterModalComponentPr type='text' autoComplete='on' onChange={e => handleStringChange(e)} - placeholder="YYYY-MM-DD HH:MM:SS" + placeholder='YYYY-MM-DD HH:MM:SS' value={state?.startTimestamp} /> {/* endTimestamp input */} @@ -735,7 +735,7 @@ export default function EditMeterModalComponent(props: EditMeterModalComponentPr type='text' autoComplete='on' onChange={e => handleStringChange(e)} - placeholder="YYYY-MM-DD HH:MM:SS" + placeholder='YYYY-MM-DD HH:MM:SS' value={state?.endTimestamp} /> {/* previousEnd input */} @@ -747,7 +747,7 @@ export default function EditMeterModalComponent(props: EditMeterModalComponentPr type='text' autoComplete='on' onChange={e => handleStringChange(e)} - placeholder="YYYY-MM-DD HH:MM:SS" + placeholder='YYYY-MM-DD HH:MM:SS' value={state?.previousEnd} /> diff --git a/src/client/app/components/unit/CreateUnitModalComponent.tsx b/src/client/app/components/unit/CreateUnitModalComponent.tsx index 09ba5762e..af89cec1d 100644 --- a/src/client/app/components/unit/CreateUnitModalComponent.tsx +++ b/src/client/app/components/unit/CreateUnitModalComponent.tsx @@ -213,7 +213,7 @@ export default function CreateUnitModalComponent() { type='number' onChange={e => handleNumberChange(e)} defaultValue={state.secInRate} - min="1" + min='1' invalid={state.secInRate <= 0} /> diff --git a/src/client/app/components/unit/EditUnitModalComponent.tsx b/src/client/app/components/unit/EditUnitModalComponent.tsx index b86220e75..c828328cf 100644 --- a/src/client/app/components/unit/EditUnitModalComponent.tsx +++ b/src/client/app/components/unit/EditUnitModalComponent.tsx @@ -180,12 +180,12 @@ export default function EditUnitModalComponent(props: EditUnitModalComponentProp handleStringChange(e)} value={state.identifier} - placeholder="Identifier" /> + placeholder='Identifier' /> {/* Name input */} @@ -272,11 +272,11 @@ export default function EditUnitModalComponent(props: EditUnitModalComponentProp handleNumberChange(e)} - placeholder="Sec In Rate" - min="1" + placeholder='Sec In Rate' + min='1' invalid={state.secInRate <= 0} /> @@ -287,10 +287,10 @@ export default function EditUnitModalComponent(props: EditUnitModalComponentProp handleStringChange(e)} /> @@ -299,10 +299,10 @@ export default function EditUnitModalComponent(props: EditUnitModalComponentProp handleStringChange(e)} /> From 7437d1034a62e9405b9151dd9f9efd1b4b53fc1d Mon Sep 17 00:00:00 2001 From: spearec Date: Wed, 26 Jul 2023 20:33:16 +0000 Subject: [PATCH 14/15] Move help tooltips and prevent meter unit display - Various help tooltips move to be inline - Fixed #907, preventing admins from setting meter units to be displayable --- .../app/components/ChartDataSelectComponent.tsx | 6 +++--- src/client/app/components/ChartSelectComponent.tsx | 4 +--- .../components/groups/EditGroupModalComponent.tsx | 1 + .../components/unit/CreateUnitModalComponent.tsx | 13 +++++++++++-- .../app/components/unit/EditUnitModalComponent.tsx | 14 +++++++++++--- src/client/app/translations/data.js | 9 ++++++--- 6 files changed, 33 insertions(+), 14 deletions(-) diff --git a/src/client/app/components/ChartDataSelectComponent.tsx b/src/client/app/components/ChartDataSelectComponent.tsx index 30670c91a..898f02e26 100644 --- a/src/client/app/components/ChartDataSelectComponent.tsx +++ b/src/client/app/components/ChartDataSelectComponent.tsx @@ -291,6 +291,7 @@ export default function ChartDataSelectComponent() {

: +

-

: +

-

: +

{/* TODO this could be converted to a regular Select component */} @@ -375,7 +376,6 @@ export default function ChartDataSelectComponent() { else { dispatch(changeSelectedUnit(-99)); } }} /> -
); diff --git a/src/client/app/components/ChartSelectComponent.tsx b/src/client/app/components/ChartSelectComponent.tsx index 779e6732b..9b07a59dd 100644 --- a/src/client/app/components/ChartSelectComponent.tsx +++ b/src/client/app/components/ChartSelectComponent.tsx @@ -36,6 +36,7 @@ export default function ChartSelectComponent() {

: +

setExpand(!expand)}> @@ -70,9 +71,6 @@ export default function ChartSelectComponent() { -
- -
); } diff --git a/src/client/app/components/groups/EditGroupModalComponent.tsx b/src/client/app/components/groups/EditGroupModalComponent.tsx index a86291bc2..5842ee309 100644 --- a/src/client/app/components/groups/EditGroupModalComponent.tsx +++ b/src/client/app/components/groups/EditGroupModalComponent.tsx @@ -469,6 +469,7 @@ export default function EditGroupModalComponent(props: EditGroupModalComponentPr {/* default graphic unit display for non-admin */} + {/* TODO: This component still displays a dropdown arrow, even though a user cannot use the dropdown */} handleStringChange(e)} - value={state.displayable} > + value={state.displayable} + invalid={state.displayable != DisplayableType.none && state.typeOfUnit == UnitType.meter}> {Object.keys(DisplayableType).map(key => { - return () + return () })} + + + {/* Preferred display input */} diff --git a/src/client/app/components/unit/EditUnitModalComponent.tsx b/src/client/app/components/unit/EditUnitModalComponent.tsx index c828328cf..b6c2ff08d 100644 --- a/src/client/app/components/unit/EditUnitModalComponent.tsx +++ b/src/client/app/components/unit/EditUnitModalComponent.tsx @@ -146,6 +146,10 @@ export default function EditUnitModalComponent(props: EditUnitModalComponentProp const shouldRefreshReadingViews = props.unit.unitRepresent !== state.unitRepresent || (props.unit.secInRate !== state.secInRate && (props.unit.unitRepresent === UnitRepresentType.flow || props.unit.unitRepresent === UnitRepresentType.raw)); + // set displayable to none if unit is meter + if(state.typeOfUnit == UnitType.meter && state.displayable != DisplayableType.none) { + state.displayable = DisplayableType.none; + } // Save our changes by dispatching the submitEditedUnit action dispatch(submitEditedUnit(state, shouldRedoCik, shouldRefreshReadingViews)); // The updated unit is not fetched to save time. However, the identifier might have been @@ -234,7 +238,6 @@ export default function EditUnitModalComponent(props: EditUnitModalComponentProp - {/* Displayable type input */} @@ -243,11 +246,16 @@ export default function EditUnitModalComponent(props: EditUnitModalComponentProp name='displayable' type='select' value={state.displayable} - onChange={e => handleStringChange(e)}> + onChange={e => handleStringChange(e)} + invalid={state.displayable != DisplayableType.none && state.typeOfUnit == UnitType.meter}> {Object.keys(DisplayableType).map(key => { - return () + return () })} + + + {/* Preferred display input */} diff --git a/src/client/app/translations/data.js b/src/client/app/translations/data.js index b83109d53..b873e2bac 100644 --- a/src/client/app/translations/data.js +++ b/src/client/app/translations/data.js @@ -132,7 +132,8 @@ const localeData = { "DisplayableType.all": "all", "DisplayableType.admin": "admin", "error.bounds": "Must be between {min} and {max}.", - "error.displayable": "Displayable will be set to false.", + "error.displayable": "Displayable will be set to false because no unit is selected.", + "error.displayable.meter": "Meter units cannot be displayed.", "error.greater": "Must be greater than {min}.", "error.gps": "Latitude must be between -90 and 90, and Longitude must be between -180 and 180.", "error.negative": "Cannot be negative.", @@ -581,7 +582,8 @@ const localeData = { "DisplayableType.all": "(Need French) all", "DisplayableType.admin": "(Need French) admin", "error.bounds": "(Need French) Must be between {min} and {max}.", - "error.displayable": "(Need French) Displayable will be set to false.", + "error.displayable": "(Need French) Displayable will be set to false because no unit is selected.", + "error.displayable.meter": "(Need French) Meter units cannot be displayed.", "error.greater": "(Need French) Must be greater than {min}.", "error.gps": "(Need French) Latitude must be between -90 and 90, and Longitude must be between -180 and 180.", "error.negative": "(Need French) Cannot be negative.", @@ -1030,7 +1032,8 @@ const localeData = { "DisplayableType.all": "(Need Spanish) all", "DisplayableType.admin": "(Need Spanish) admin", "error.bounds": "(Need Spanish) Must be between {min} and {max}.", - "error.displayable": "(Need Spanish) Displayable will be set to false.", + "error.displayable": "(Need Spanish) Displayable will be set to false because no unit is selected.", + "error.displayable.meter": "(Need Spanish) Meter units cannot be displayed.", "error.greater": "(Need Spanish) Must be greater than {min}.", "error.gps": "(Need Spanish) Latitude must be between -90 and 90, and Longitude must be between -180 and 180.", "error.negative": "(Need Spanish) Cannot be negative.", From 9bc3d8e93d59ca47c8feb8c87a6249c9615787fb Mon Sep 17 00:00:00 2001 From: Steven Huss-Lederman Date: Thu, 27 Jul 2023 15:50:38 -0500 Subject: [PATCH 15/15] small rewording of msg --- src/client/app/translations/data.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/client/app/translations/data.js b/src/client/app/translations/data.js index 95305f6e8..9efe38f1f 100644 --- a/src/client/app/translations/data.js +++ b/src/client/app/translations/data.js @@ -135,7 +135,7 @@ const localeData = { "DisplayableType.admin": "admin", "error.bounds": "Must be between {min} and {max}.", "error.displayable": "Displayable will be set to false because no unit is selected.", - "error.displayable.meter": "Meter units cannot be displayed.", + "error.displayable.meter": "Meter units will set displayable to none.", "error.greater": "Must be greater than {min}.", "error.gps": "Latitude must be between -90 and 90, and Longitude must be between -180 and 180.", "error.negative": "Cannot be negative.", @@ -588,7 +588,7 @@ const localeData = { "DisplayableType.admin": "(Need French) admin", "error.bounds": "(Need French) Must be between {min} and {max}.", "error.displayable": "(Need French) Displayable will be set to false because no unit is selected.", - "error.displayable.meter": "(Need French) Meter units cannot be displayed.", + "error.displayable.meter": "(Need French) Meter units will set displayable to none.", "error.greater": "(Need French) Must be greater than {min}.", "error.gps": "(Need French) Latitude must be between -90 and 90, and Longitude must be between -180 and 180.", "error.negative": "(Need French) Cannot be negative.", @@ -1041,7 +1041,7 @@ const localeData = { "DisplayableType.admin": "(Need Spanish) admin", "error.bounds": "(Need Spanish) Must be between {min} and {max}.", "error.displayable": "(Need Spanish) Displayable will be set to false because no unit is selected.", - "error.displayable.meter": "(Need Spanish) Meter units cannot be displayed.", + "error.displayable.meter": "(Need Spanish) Meter units will set displayable to none.", "error.greater": "(Need Spanish) Must be greater than {min}.", "error.gps": "(Need Spanish) Latitude must be between -90 and 90, and Longitude must be between -180 and 180.", "error.negative": "(Need Spanish) Cannot be negative.",