diff --git a/.env.production b/.env.production index 5950b945..996d6bee 100644 --- a/.env.production +++ b/.env.production @@ -1,7 +1,20 @@ +# Application VITE_ALERT_EDITOR=false VITE_FORMULA_EDITOR=true VITE_INSTRUMENT_CHART=true VITE_CROSS_SECTION=true VITE_DEVELOPMENT_BANNER=false +VITE_REPORT_DOWNLOAD=false + +# API VITE_API_URL=https://midas.sec.usace.army.mil/api VITE_URL_BASE_PATH=/midas + + +# EXTERNAL APIS +VITE_CWMS_API_URL=https://cwms-data.usace.army.mil/cwms-data + +# Keycloak +VITE_KC_URL=https://identity.sec.usace.army.mil/auth/ +VITE_KC_REALM=cwbi +VITE_KC_CLIENT_ID=midas diff --git a/.env.test b/.env.test index 0783a221..6ed6f5c6 100644 --- a/.env.test +++ b/.env.test @@ -1,7 +1,19 @@ +# Application VITE_ALERT_EDITOR=false VITE_FORMULA_EDITOR=true VITE_INSTRUMENT_CHART=true VITE_CROSS_SECTION=true VITE_DEVELOPMENT_BANNER=false +VITE_REPORT_DOWNLOAD=false + +# API VITE_API_URL=https://midas-test.cwbi.us/api VITE_URL_BASE_PATH=/midas + +# EXTERNAL APIS +VITE_CWMS_API_URL=https://cwms-data.usace.army.mil/cwms-data + +# Keycloak +VITE_KC_URL=https://identity-test.cwbi.us/auth/ +VITE_KC_REALM=cwbi +VITE_KC_CLIENT_ID=midas diff --git a/.github/workflows/develop.yml b/.github/workflows/develop.yml index 595b3ceb..5ca8b4fb 100644 --- a/.github/workflows/develop.yml +++ b/.github/workflows/develop.yml @@ -17,8 +17,14 @@ jobs: VITE_INSTRUMENT_CHART: true VITE_CROSS_SECTION: true VITE_DEVELOPMENT_BANNER: true + VITE_REPORT_DOWNLOAD: true VITE_API_URL: https://develop-midas-api.rsgis.dev + VITE_CWMS_API_URL: https://cwms-data.usace.army.mil/cwms-data VITE_URL_BASE_PATH: '' + VITE_KC_URL: https://identity-test.cwbi.us/auth/ + VITE_KC_REALM: cwbi + VITE_KC_CLIENT_ID: midas + VITE_BASE_REDIRECT_URI: https://develop-midas.rsgis.dev/ runs-on: ubuntu-latest strategy: matrix: diff --git a/package-lock.json b/package-lock.json index 1046b8f9..cc567a4b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "hhd-ui", - "version": "0.15.4", + "version": "0.17.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "hhd-ui", - "version": "0.15.4", + "version": "0.17.0", "dependencies": { "@ag-grid-community/client-side-row-model": "^30.0.3", "@ag-grid-community/core": "^30.0.3", @@ -19,6 +19,7 @@ "@iconify/utils": "^2.1.22", "@mui/icons-material": "^5.14.0", "@mui/material": "^5.14.2", + "@tanstack/react-query": "^5.36.2", "@tanstack/react-table": "^8.9.3", "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^14.0.0", @@ -28,15 +29,18 @@ "date-fns": "^2.30.0", "graceful-fs": "^4.2.11", "internal-nav-helper": "^3.1.0", + "keycloak-js": "^25.0.2", "lodash.debounce": "^4.0.8", "lodash.isequal": "^4.5.0", "luxon": "^3.3.0", "money-clip": "^3.0.5", + "mui-color-input": "^2.0.3", "ol": "^7.4.0", "plotly.js": "^2.25.2", "process": "^0.11.10", "proj4": "^2.9.0", "react": "^18.2.0", + "react-beautiful-dnd": "^13.1.1", "react-color": "^2.19.3", "react-csv": "^2.2.2", "react-datepicker": "^4.16.0", @@ -72,6 +76,7 @@ "rollup-plugin-visualizer": "^5.9.2", "sass": "^1.63.6", "source-map-explorer": "^2.5.3", + "typescript": "^5.4.5", "vite": "^4.5.2", "vite-plugin-checker": "^0.6.1", "vite-plugin-svgr": "^3.2.0" @@ -724,11 +729,11 @@ } }, "node_modules/@babel/runtime": { - "version": "7.22.6", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.6.tgz", - "integrity": "sha512-wDb5pWm4WDdF6LFUde3Jl8WzPA+3ZbxYqkC6xAXuD3irdEHN1k0NfTRrJD8ZD378SJ61miMLCqIOXYhd8x+AJQ==", + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.4.tgz", + "integrity": "sha512-dkxf7+hn8mFBwKjs9bvBlArzLVxVbS8usaPUDd5p2a9JCL9tB8OaOVN1isD4+Xyk4ns89/xeOmbQvgdK7IIVdA==", "dependencies": { - "regenerator-runtime": "^0.13.11" + "regenerator-runtime": "^0.14.0" }, "engines": { "node": ">=6.9.0" @@ -792,6 +797,14 @@ "findup": "bin/findup.js" } }, + "node_modules/@ctrl/tinycolor": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-4.1.0.tgz", + "integrity": "sha512-WyOx8cJQ+FQus4Mm4uPIZA64gbk3Wxh0so5Lcii0aJifqwoVOlfFtorjLE0Hen4OYyHZMXDWqMmaQemBhgxFRQ==", + "engines": { + "node": ">=14" + } + }, "node_modules/@emotion/babel-plugin": { "version": "11.11.0", "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz", @@ -933,70 +946,6 @@ "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz", "integrity": "sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==" }, - "node_modules/@esbuild/android-arm": { - "version": "0.18.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.12.tgz", - "integrity": "sha512-LIxaNIQfkFZbTLb4+cX7dozHlAbAshhFE5PKdro0l+FnCpx1GDJaQ2WMcqm+ToXKMt8p8Uojk/MFRuGyz3V5Sw==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.18.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.12.tgz", - "integrity": "sha512-BMAlczRqC/LUt2P97E4apTBbkvS9JTJnp2DKFbCwpZ8vBvXVbNdqmvzW/OsdtI/+mGr+apkkpqGM8WecLkPgrA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.18.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.12.tgz", - "integrity": "sha512-zU5MyluNsykf5cOJ0LZZZjgAHbhPJ1cWfdH1ZXVMXxVMhEV0VZiZXQdwBBVvmvbF28EizeK7obG9fs+fpmS0eQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.18.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.12.tgz", - "integrity": "sha512-zUZMep7YONnp6954QOOwEBwFX9svlKd3ov6PkxKd53LGTHsp/gy7vHaPGhhjBmEpqXEXShi6dddjIkmd+NgMsA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, "node_modules/@esbuild/darwin-x64": { "version": "0.18.12", "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.12.tgz", @@ -1013,278 +962,6 @@ "node": ">=12" } }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.18.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.12.tgz", - "integrity": "sha512-GIIHtQXqgeOOqdG16a/A9N28GpkvjJnjYMhOnXVbn3EDJcoItdR58v/pGN31CHjyXDc8uCcRnFWmqaJt24AYJg==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.18.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.12.tgz", - "integrity": "sha512-zK0b9a1/0wZY+6FdOS3BpZcPc1kcx2G5yxxfEJtEUzVxI6n/FrC2Phsxj/YblPuBchhBZ/1wwn7AyEBUyNSa6g==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.18.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.12.tgz", - "integrity": "sha512-y75OijvrBE/1XRrXq1jtrJfG26eHeMoqLJ2dwQNwviwTuTtHGCojsDO6BJNF8gU+3jTn1KzJEMETytwsFSvc+Q==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.18.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.12.tgz", - "integrity": "sha512-JKgG8Q/LL/9sw/iHHxQyVMoQYu3rU3+a5Z87DxC+wAu3engz+EmctIrV+FGOgI6gWG1z1+5nDDbXiRMGQZXqiw==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.18.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.12.tgz", - "integrity": "sha512-yoRIAqc0B4lDIAAEFEIu9ttTRFV84iuAl0KNCN6MhKLxNPfzwCBvEMgwco2f71GxmpBcTtn7KdErueZaM2rEvw==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.18.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.12.tgz", - "integrity": "sha512-qYgt3dHPVvf/MgbIBpJ4Sup/yb9DAopZ3a2JgMpNKIHUpOdnJ2eHBo/aQdnd8dJ21X/+sS58wxHtA9lEazYtXQ==", - "cpu": [ - "loong64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.18.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.12.tgz", - "integrity": "sha512-wHphlMLK4ufNOONqukELfVIbnGQJrHJ/mxZMMrP2jYrPgCRZhOtf0kC4yAXBwnfmULimV1qt5UJJOw4Kh13Yfg==", - "cpu": [ - "mips64el" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.18.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.12.tgz", - "integrity": "sha512-TeN//1Ft20ZZW41+zDSdOI/Os1bEq5dbvBvYkberB7PHABbRcsteeoNVZFlI0YLpGdlBqohEpjrn06kv8heCJg==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.18.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.12.tgz", - "integrity": "sha512-AgUebVS4DoAblBgiB2ACQ/8l4eGE5aWBb8ZXtkXHiET9mbj7GuWt3OnsIW/zX+XHJt2RYJZctbQ2S/mDjbp0UA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.18.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.12.tgz", - "integrity": "sha512-dJ3Rb3Ei2u/ysSXd6pzleGtfDdc2MuzKt8qc6ls8vreP1G3B7HInX3i7gXS4BGeVd24pp0yqyS7bJ5NHaI9ing==", - "cpu": [ - "s390x" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.18.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.12.tgz", - "integrity": "sha512-OrNJMGQbPaVyHHcDF8ybNSwu7TDOfX8NGpXCbetwOSP6txOJiWlgQnRymfC9ocR1S0Y5PW0Wb1mV6pUddqmvmQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.18.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.12.tgz", - "integrity": "sha512-55FzVCAiwE9FK8wWeCRuvjazNRJ1QqLCYGZVB6E8RuQuTeStSwotpSW4xoRGwp3a1wUsaVCdYcj5LGCASVJmMg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.18.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.12.tgz", - "integrity": "sha512-qnluf8rfb6Y5Lw2tirfK2quZOBbVqmwxut7GPCIJsM8lc4AEUj9L8y0YPdLaPK0TECt4IdyBdBD/KRFKorlK3g==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.18.12", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.12.tgz", - "integrity": "sha512-+RkKpVQR7bICjTOPUpkTBTaJ4TFqQBX5Ywyd/HSdDkQGn65VPkTsR/pL4AMvuMWy+wnXgIl4EY6q4mVpJal8Kg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.18.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.12.tgz", - "integrity": "sha512-GNHuciv0mFM7ouzsU0+AwY+7eV4Mgo5WnbhfDCQGtpvOtD1vbOiRjPYG6dhmMoFyBjj+pNqQu2X+7DKn0KQ/Gw==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.18.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.12.tgz", - "integrity": "sha512-kR8cezhYipbbypGkaqCTWIeu4zID17gamC8YTPXYtcN3E5BhhtTnwKBn9I0PJur/T6UVwIEGYzkffNL0lFvxEw==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.18.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.12.tgz", - "integrity": "sha512-O0UYQVkvfM/jO8a4OwoV0mAKSJw+mjWTAd1MJd/1FCX6uiMdLmMRPK/w6e9OQ0ob2WGxzIm9va/KG0Ja4zIOgg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", @@ -1373,12 +1050,31 @@ "license": "MIT" }, "node_modules/@floating-ui/dom": { - "version": "1.0.4", - "license": "MIT", + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.3.tgz", + "integrity": "sha512-RnDthu3mzPlQ31Ss/BTwQ1zjzIhr3lk1gZB1OC56h/1vEtaXkESrOqL5fQVMfXpwGtRwX+YsZBdyHtJMQnkArw==", "dependencies": { - "@floating-ui/core": "^1.0.1" + "@floating-ui/core": "^1.0.0", + "@floating-ui/utils": "^0.2.0" } }, + "node_modules/@floating-ui/react-dom": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.8.tgz", + "integrity": "sha512-HOdqOt3R3OGeTKidaLvJKcgg75S6tibQ3Tif4eyd91QnIJWr0NLvoXFpJA/j8HqkFSL68GDca9AuyWEHlhyClw==", + "dependencies": { + "@floating-ui/dom": "^1.6.1" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.1.tgz", + "integrity": "sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q==" + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz", @@ -1624,25 +1320,24 @@ } }, "node_modules/@mui/base": { - "version": "5.0.0-beta.8", - "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.8.tgz", - "integrity": "sha512-b4vVjMZx5KzzEMf4arXKoeV5ZegAMOoPwoy1vfUBwhvXc2QtaaAyBp50U7OA2L06Leubc1A+lEp3eqwZoFn87g==", - "dependencies": { - "@babel/runtime": "^7.22.6", - "@emotion/is-prop-valid": "^1.2.1", - "@mui/types": "^7.2.4", - "@mui/utils": "^5.14.1", + "version": "5.0.0-beta.40", + "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.40.tgz", + "integrity": "sha512-I/lGHztkCzvwlXpjD2+SNmvNQvB4227xBXhISPjEaJUXGImOQ9f3D2Yj/T3KasSI/h0MLWy74X0J6clhPmsRbQ==", + "dependencies": { + "@babel/runtime": "^7.23.9", + "@floating-ui/react-dom": "^2.0.8", + "@mui/types": "^7.2.14", + "@mui/utils": "^5.15.14", "@popperjs/core": "^2.11.8", - "clsx": "^1.2.1", - "prop-types": "^15.8.1", - "react-is": "^18.2.0" + "clsx": "^2.1.0", + "prop-types": "^15.8.1" }, "engines": { "node": ">=12.0.0" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/mui" + "url": "https://opencollective.com/mui-org" }, "peerDependencies": { "@types/react": "^17.0.0 || ^18.0.0", @@ -1655,18 +1350,21 @@ } } }, - "node_modules/@mui/base/node_modules/react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + "node_modules/@mui/base/node_modules/clsx": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.0.tgz", + "integrity": "sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==", + "engines": { + "node": ">=6" + } }, "node_modules/@mui/core-downloads-tracker": { - "version": "5.14.2", - "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.14.2.tgz", - "integrity": "sha512-x+c/MgDL1t/IIy5lDbMlrDouFG5DYZbl3DP4dbbuhlpPFBnE9glYwmJEee/orVHQpOPwLxCAIWQs+2DKSaBVWQ==", + "version": "5.15.14", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.15.14.tgz", + "integrity": "sha512-on75VMd0XqZfaQW+9pGjSNiqW+ghc5E2ZSLRBXwcXl/C4YzjfyjrLPhrEpKnR9Uym9KXBvxrhoHfPcczYHweyA==", "funding": { "type": "opencollective", - "url": "https://opencollective.com/mui" + "url": "https://opencollective.com/mui-org" } }, "node_modules/@mui/icons-material": { @@ -1695,19 +1393,19 @@ } }, "node_modules/@mui/material": { - "version": "5.14.2", - "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.14.2.tgz", - "integrity": "sha512-TgNR4/YRL11RifsnMWNhITNCkGJYVz20SCvVJBBoU5Y/KhUNSSJxjDpEB8VrnY+sUsV0NigLCkHZJglfsiS3Pw==", - "dependencies": { - "@babel/runtime": "^7.22.6", - "@mui/base": "5.0.0-beta.8", - "@mui/core-downloads-tracker": "^5.14.2", - "@mui/system": "^5.14.1", - "@mui/types": "^7.2.4", - "@mui/utils": "^5.14.1", - "@types/react-transition-group": "^4.4.6", - "clsx": "^1.2.1", - "csstype": "^3.1.2", + "version": "5.15.14", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.15.14.tgz", + "integrity": "sha512-kEbRw6fASdQ1SQ7LVdWR5OlWV3y7Y54ZxkLzd6LV5tmz+NpO3MJKZXSfgR0LHMP7meKsPiMm4AuzV0pXDpk/BQ==", + "dependencies": { + "@babel/runtime": "^7.23.9", + "@mui/base": "5.0.0-beta.40", + "@mui/core-downloads-tracker": "^5.15.14", + "@mui/system": "^5.15.14", + "@mui/types": "^7.2.14", + "@mui/utils": "^5.15.14", + "@types/react-transition-group": "^4.4.10", + "clsx": "^2.1.0", + "csstype": "^3.1.3", "prop-types": "^15.8.1", "react-is": "^18.2.0", "react-transition-group": "^4.4.5" @@ -1717,7 +1415,7 @@ }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/mui" + "url": "https://opencollective.com/mui-org" }, "peerDependencies": { "@emotion/react": "^11.5.0", @@ -1738,18 +1436,26 @@ } } }, + "node_modules/@mui/material/node_modules/clsx": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.0.tgz", + "integrity": "sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==", + "engines": { + "node": ">=6" + } + }, "node_modules/@mui/material/node_modules/react-is": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" }, "node_modules/@mui/private-theming": { - "version": "5.13.7", - "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.13.7.tgz", - "integrity": "sha512-qbSr+udcij5F9dKhGX7fEdx2drXchq7htLNr2Qg2Ma+WJ6q0ERlEqGSBiPiVDJkptcjeVL4DGmcf1wl5+vD4EA==", + "version": "5.15.14", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.15.14.tgz", + "integrity": "sha512-UH0EiZckOWcxiXLX3Jbb0K7rC8mxTr9L9l6QhOZxYc4r8FHUkefltV9VDGLrzCaWh30SQiJvAEd7djX3XXY6Xw==", "dependencies": { - "@babel/runtime": "^7.22.5", - "@mui/utils": "^5.13.7", + "@babel/runtime": "^7.23.9", + "@mui/utils": "^5.15.14", "prop-types": "^15.8.1" }, "engines": { @@ -1757,7 +1463,7 @@ }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/mui" + "url": "https://opencollective.com/mui-org" }, "peerDependencies": { "@types/react": "^17.0.0 || ^18.0.0", @@ -1770,13 +1476,13 @@ } }, "node_modules/@mui/styled-engine": { - "version": "5.13.2", - "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.13.2.tgz", - "integrity": "sha512-VCYCU6xVtXOrIN8lcbuPmoG+u7FYuOERG++fpY74hPpEWkyFQG97F+/XfTQVYzlR2m7nPjnwVUgATcTCMEaMvw==", + "version": "5.15.14", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.15.14.tgz", + "integrity": "sha512-RILkuVD8gY6PvjZjqnWhz8fu68dVkqhM5+jYWfB5yhlSQKg+2rHkmEwm75XIeAqI3qwOndK6zELK5H6Zxn4NHw==", "dependencies": { - "@babel/runtime": "^7.21.0", + "@babel/runtime": "^7.23.9", "@emotion/cache": "^11.11.0", - "csstype": "^3.1.2", + "csstype": "^3.1.3", "prop-types": "^15.8.1" }, "engines": { @@ -1784,7 +1490,7 @@ }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/mui" + "url": "https://opencollective.com/mui-org" }, "peerDependencies": { "@emotion/react": "^11.4.1", @@ -1801,17 +1507,17 @@ } }, "node_modules/@mui/system": { - "version": "5.14.1", - "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.14.1.tgz", - "integrity": "sha512-u+xlsU34Jdkgx1CxmBnIC4Y08uPdVX5iEd3S/1dggDFtOGp+Lj8xmKRJAQ8PJOOJLOh8pDwaZx4AwXikL4l1QA==", - "dependencies": { - "@babel/runtime": "^7.22.6", - "@mui/private-theming": "^5.13.7", - "@mui/styled-engine": "^5.13.2", - "@mui/types": "^7.2.4", - "@mui/utils": "^5.14.1", - "clsx": "^1.2.1", - "csstype": "^3.1.2", + "version": "5.15.14", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.15.14.tgz", + "integrity": "sha512-auXLXzUaCSSOLqJXmsAaq7P96VPRXg2Rrz6OHNV7lr+kB8lobUF+/N84Vd9C4G/wvCXYPs5TYuuGBRhcGbiBGg==", + "dependencies": { + "@babel/runtime": "^7.23.9", + "@mui/private-theming": "^5.15.14", + "@mui/styled-engine": "^5.15.14", + "@mui/types": "^7.2.14", + "@mui/utils": "^5.15.14", + "clsx": "^2.1.0", + "csstype": "^3.1.3", "prop-types": "^15.8.1" }, "engines": { @@ -1819,7 +1525,7 @@ }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/mui" + "url": "https://opencollective.com/mui-org" }, "peerDependencies": { "@emotion/react": "^11.5.0", @@ -1839,12 +1545,20 @@ } } }, + "node_modules/@mui/system/node_modules/clsx": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.0.tgz", + "integrity": "sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==", + "engines": { + "node": ">=6" + } + }, "node_modules/@mui/types": { - "version": "7.2.4", - "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.4.tgz", - "integrity": "sha512-LBcwa8rN84bKF+f5sDyku42w1NTxaPgPyYKODsh01U1fVstTClbUoSA96oyRBnSNyEiAVjKm6Gwx9vjR+xyqHA==", + "version": "7.2.14", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.14.tgz", + "integrity": "sha512-MZsBZ4q4HfzBsywtXgM1Ksj6HDThtiwmOKUXH1pKYISI9gAVXCNHNpo7TlGoGrBaYWZTdNoirIN7JsQcQUjmQQ==", "peerDependencies": { - "@types/react": "*" + "@types/react": "^17.0.0 || ^18.0.0" }, "peerDependenciesMeta": { "@types/react": { @@ -1853,13 +1567,12 @@ } }, "node_modules/@mui/utils": { - "version": "5.14.1", - "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.14.1.tgz", - "integrity": "sha512-39KHKK2JeqRmuUcLDLwM+c2XfVC136C5/yUyQXmO2PVbOb2Bol4KxtkssEqCbTwg87PSCG3f1Tb0keRsK7cVGw==", + "version": "5.15.14", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.15.14.tgz", + "integrity": "sha512-0lF/7Hh/ezDv5X7Pry6enMsbYyGKjADzvHyo3Qrc/SSlTsQ1VkbDMbH0m2t3OR5iIVLwMoxwM7yGd+6FCMtTFA==", "dependencies": { - "@babel/runtime": "^7.22.6", - "@types/prop-types": "^15.7.5", - "@types/react-is": "^18.2.1", + "@babel/runtime": "^7.23.9", + "@types/prop-types": "^15.7.11", "prop-types": "^15.8.1", "react-is": "^18.2.0" }, @@ -1868,10 +1581,16 @@ }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/mui" + "url": "https://opencollective.com/mui-org" }, "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0", "react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, "node_modules/@mui/utils/node_modules/react-is": { @@ -2047,6 +1766,30 @@ "version": "0.24.51", "license": "MIT" }, + "node_modules/@tanstack/query-core": { + "version": "5.36.1", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.36.1.tgz", + "integrity": "sha512-BteWYEPUcucEu3NBcDAgKuI4U25R9aPrHSP6YSf2NvaD2pSlIQTdqOfLRsxH9WdRYg7k0Uom35Uacb6nvbIMJg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.36.2", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.36.2.tgz", + "integrity": "sha512-bHNa+5dead+j6SA8WVlEOPxcGfteVFgdyFTCFcxBgjnPf0fFpHUc7aNZBCnvmPXqy/BeQa9zTuU9ectb7i8ZXA==", + "dependencies": { + "@tanstack/query-core": "5.36.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18.0.0" + } + }, "node_modules/@tanstack/react-table": { "version": "8.9.3", "resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.9.3.tgz", @@ -2226,6 +1969,15 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/hoist-non-react-statics": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.5.tgz", + "integrity": "sha512-SbcrWzkKBw2cdwRTwQAswfpB9g9LJWfjtUeW/jvNwbhC8cpmmNYVePa+ncbUe0rGTQ7G3Ff6mYUN2VMfLVr+Sg==", + "dependencies": { + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0" + } + }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.4", "license": "MIT" @@ -2312,8 +2064,9 @@ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==" }, "node_modules/@types/prop-types": { - "version": "15.7.5", - "license": "MIT" + "version": "15.7.12", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz", + "integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==" }, "node_modules/@types/react": { "version": "18.2.14", @@ -2333,18 +2086,21 @@ "@types/react": "*" } }, - "node_modules/@types/react-is": { - "version": "18.2.1", - "resolved": "https://registry.npmjs.org/@types/react-is/-/react-is-18.2.1.tgz", - "integrity": "sha512-wyUkmaaSZEzFZivD8F2ftSyAfk6L+DfFliVj/mYdOXbVjRcS87fQJLTnhk6dRZPuJjI+9g6RZJO4PNCngUrmyw==", + "node_modules/@types/react-redux": { + "version": "7.1.33", + "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.33.tgz", + "integrity": "sha512-NF8m5AjWCkert+fosDsN3hAlHzpjSiXlVy9EgQEmLoBhaNXbmyeGs/aj5dQzKuF+/q+S7JQagorGDW8pJ28Hmg==", "dependencies": { - "@types/react": "*" + "@types/hoist-non-react-statics": "^3.3.0", + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0", + "redux": "^4.0.0" } }, "node_modules/@types/react-transition-group": { - "version": "4.4.6", - "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.6.tgz", - "integrity": "sha512-VnCdSxfcm08KjsJVQcfBmhEQAPnLB8G08hAxn39azX1qYBQ/5RVQuoHuKIcfKOdncuaUvEpFKFzEvbtIMsfVew==", + "version": "4.4.10", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.10.tgz", + "integrity": "sha512-hT/+s0VQs2ojCX823m60m5f0sL5idt9SO6Tj6Dg+rdphGPIeJbJ6CxvBYkgkGKrYeDjvIpKTR38UzmtHJOGW3Q==", "dependencies": { "@types/react": "*" } @@ -2711,10 +2467,12 @@ } }, "node_modules/braces": { - "version": "3.0.2", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "license": "MIT", "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -2834,9 +2592,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001576", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001576.tgz", - "integrity": "sha512-ff5BdakGe2P3SQsMsiqmt1Lc8221NR1VzHj5jXN5vBny9A6fpze94HiVV/n7XRosOlsShJcvMv5mdnpjOGCEgg==", + "version": "1.0.30001636", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001636.tgz", + "integrity": "sha512-bMg2vmr8XBsbL6Lr0UHXy/21m84FTxDLWn2FSqMd5PrlbMxwJlQnC2YWYxVgp66PZE+BBNF2jYQUBKCo1FDeZg==", "dev": true, "funding": [ { @@ -3090,6 +2848,14 @@ "node": ">= 8" } }, + "node_modules/css-box-model": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.1.tgz", + "integrity": "sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==", + "dependencies": { + "tiny-invariant": "^1.0.6" + } + }, "node_modules/css-font": { "version": "1.2.0", "license": "MIT", @@ -3146,9 +2912,9 @@ "license": "MIT" }, "node_modules/csstype": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", - "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==" + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" }, "node_modules/d": { "version": "1.0.1", @@ -3569,7 +3335,9 @@ "license": "ISC" }, "node_modules/ejs": { - "version": "3.1.8", + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -3727,12 +3495,15 @@ } }, "node_modules/es5-ext": { - "version": "0.10.62", + "version": "0.10.64", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz", + "integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==", "hasInstallScript": true, "license": "ISC", "dependencies": { "es6-iterator": "^2.0.3", "es6-symbol": "^3.1.3", + "esniff": "^2.0.1", "next-tick": "^1.1.0" }, "engines": { @@ -4123,10 +3894,31 @@ "engines": { "node": ">=10" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/esniff": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz", + "integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==", + "license": "ISC", + "dependencies": { + "d": "^1.0.1", + "es5-ext": "^0.10.62", + "event-emitter": "^0.3.5", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.10" } }, + "node_modules/esniff/node_modules/type": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/type/-/type-2.7.3.tgz", + "integrity": "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==", + "license": "ISC" + }, "node_modules/espree": { "version": "9.6.0", "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.0.tgz", @@ -4193,6 +3985,16 @@ "node": ">=0.10.0" } }, + "node_modules/event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==", + "license": "MIT", + "dependencies": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, "node_modules/events": { "version": "3.3.0", "license": "MIT", @@ -4321,9 +4123,10 @@ "license": "MIT" }, "node_modules/fast-loops": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/fast-loops/-/fast-loops-1.1.3.tgz", - "integrity": "sha512-8EZzEP0eKkEEVX+drtd9mtuQ+/QrlfW/5MlwcwK5Nds6EkZ/tRzEexkzUY2mIssnAyVLT+TKHuRXmFNNXYUd6g==" + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/fast-loops/-/fast-loops-1.1.4.tgz", + "integrity": "sha512-8dbd3XWoKCTms18ize6JmQF1SFnnfj5s0B7rRry22EofgMu7B6LKHVh+XfFqFGsqnbH54xgeO83PzpKI+ODhlg==", + "license": "MIT" }, "node_modules/fast-shallow-equal": { "version": "1.0.0", @@ -4382,7 +4185,9 @@ } }, "node_modules/fill-range": { - "version": "7.0.1", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" @@ -5329,6 +5134,8 @@ }, "node_modules/is-number": { "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "license": "MIT", "engines": { "node": ">=0.12.0" @@ -5676,6 +5483,11 @@ "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-2.2.1.tgz", "integrity": "sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ==" }, + "node_modules/js-sha256": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/js-sha256/-/js-sha256-0.11.0.tgz", + "integrity": "sha512-6xNlKayMZvds9h1Y1VWc0fQHQ82BxTXizWPEtEeGvmOUYpBRy4gbWroHLpzowe6xiQhHpelCQiE7HEdznyBL9Q==" + }, "node_modules/js-tokens": { "version": "4.0.0", "license": "MIT" @@ -5761,10 +5573,28 @@ "node": ">=4.0" } }, + "node_modules/jwt-decode": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz", + "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==", + "engines": { + "node": ">=18" + } + }, "node_modules/kdbush": { "version": "3.0.0", "license": "ISC" }, + "node_modules/keycloak-js": { + "version": "25.0.2", + "resolved": "https://registry.npmjs.org/keycloak-js/-/keycloak-js-25.0.2.tgz", + "integrity": "sha512-ACLf5O5PqzfDJwGqvLpqM0kflYWmyl3+T7M2C23gztJYccDxdfNP54+B9OkXz2GnDpLUId0ceoA+lbHw9t4Wng==", + "license": "Apache-2.0", + "dependencies": { + "js-sha256": "^0.11.0", + "jwt-decode": "^4.0.0" + } + }, "node_modules/kolorist": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/kolorist/-/kolorist-1.8.0.tgz", @@ -6064,6 +5894,27 @@ "version": "2.1.2", "license": "MIT" }, + "node_modules/mui-color-input": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/mui-color-input/-/mui-color-input-2.0.3.tgz", + "integrity": "sha512-rAd040qQ0Y+8dk4gE8kkCiJ/vCgA0j4vv1quJ43BfORTFE3uHarHj0xY1Vo9CPbojtx1f5vW+CjckYPRIZPIRg==", + "dependencies": { + "@ctrl/tinycolor": "^4.0.3" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@mui/material": "^5.0.0", + "@types/react": "^18.0.0", + "react": "^18.0.0", + "react-dom": "^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/mumath": { "version": "3.3.4", "license": "Unlicense", @@ -6814,6 +6665,11 @@ "performance-now": "^2.1.0" } }, + "node_modules/raf-schd": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/raf-schd/-/raf-schd-4.0.3.tgz", + "integrity": "sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ==" + }, "node_modules/rbush": { "version": "3.0.1", "license": "MIT", @@ -6831,6 +6687,29 @@ "node": ">=0.10.0" } }, + "node_modules/react-beautiful-dnd": { + "version": "13.1.1", + "resolved": "https://registry.npmjs.org/react-beautiful-dnd/-/react-beautiful-dnd-13.1.1.tgz", + "integrity": "sha512-0Lvs4tq2VcrEjEgDXHjT98r+63drkKEgqyxdA7qD3mvKwga6a5SscbdLPO2IExotU1jW8L0Ksdl0Cj2AF67nPQ==", + "dependencies": { + "@babel/runtime": "^7.9.2", + "css-box-model": "^1.2.0", + "memoize-one": "^5.1.1", + "raf-schd": "^4.0.2", + "react-redux": "^7.2.0", + "redux": "^4.0.4", + "use-memo-one": "^1.1.1" + }, + "peerDependencies": { + "react": "^16.8.5 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.5 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/react-beautiful-dnd/node_modules/memoize-one": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", + "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==" + }, "node_modules/react-color": { "version": "2.19.3", "license": "MIT", @@ -6935,6 +6814,30 @@ "react-dom": "^16.8.0 || ^17 || ^18" } }, + "node_modules/react-redux": { + "version": "7.2.9", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.9.tgz", + "integrity": "sha512-Gx4L3uM182jEEayZfRbI/G11ZpYdNAnBs70lFVMNdHJI76XYtR+7m0MN+eAs7UHBPhWXcnFPaS+9owSCJQHNpQ==", + "dependencies": { + "@babel/runtime": "^7.15.4", + "@types/react-redux": "^7.1.20", + "hoist-non-react-statics": "^3.3.2", + "loose-envify": "^1.4.0", + "prop-types": "^15.7.2", + "react-is": "^17.0.2" + }, + "peerDependencies": { + "react": "^16.8.3 || ^17 || ^18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, "node_modules/react-resizable-panels": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/react-resizable-panels/-/react-resizable-panels-2.0.7.tgz", @@ -7090,6 +6993,14 @@ "node": ">=8" } }, + "node_modules/redux": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz", + "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==", + "dependencies": { + "@babel/runtime": "^7.9.2" + } + }, "node_modules/redux-bundler": { "version": "28.0.3", "license": "MIT" @@ -7103,9 +7014,9 @@ } }, "node_modules/regenerator-runtime": { - "version": "0.13.11", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" }, "node_modules/regexp.prototype.flags": { "version": "1.4.3", @@ -8127,8 +8038,7 @@ "node_modules/tiny-invariant": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.1.tgz", - "integrity": "sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw==", - "dev": true + "integrity": "sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw==" }, "node_modules/tinycolor2": { "version": "1.4.2", @@ -8173,6 +8083,8 @@ }, "node_modules/to-regex-range": { "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "license": "MIT", "dependencies": { "is-number": "^7.0.0" @@ -8278,17 +8190,16 @@ } }, "node_modules/typescript": { - "version": "4.8.4", + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", "dev": true, - "license": "Apache-2.0", - "optional": true, - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" }, "engines": { - "node": ">=4.2.0" + "node": ">=14.17" } }, "node_modules/ufo": { @@ -8399,6 +8310,14 @@ } } }, + "node_modules/use-memo-one": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/use-memo-one/-/use-memo-one-1.1.3.tgz", + "integrity": "sha512-g66/K7ZQGYrI6dy8GLpVcMsBp4s17xNkYJVSMvTEevGy3nDxHOfE6z8BVE22+5G5x7t3+bhzrlTDB7ObrEE0cQ==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/util": { "version": "0.12.5", "license": "MIT", @@ -8415,10 +8334,11 @@ "license": "MIT" }, "node_modules/vite": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.2.tgz", - "integrity": "sha512-tBCZBNSBbHQkaGyhGCDUGqeo2ph8Fstyp6FMSvTtsXeZSPpSMGlviAOav2hxVTqFcx8Hj/twtWKsMJXNY0xI8w==", + "version": "4.5.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.3.tgz", + "integrity": "sha512-kQL23kMeX92v3ph7IauVkXkikdDRsYMGTVl5KY2E9OY4ONLvkHf04MDTbnfo6NKxZiDLWzVpP5oTa8hQD8U3dg==", "dev": true, + "license": "MIT", "dependencies": { "esbuild": "^0.18.10", "postcss": "^8.4.27", @@ -9611,11 +9531,11 @@ } }, "@babel/runtime": { - "version": "7.22.6", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.6.tgz", - "integrity": "sha512-wDb5pWm4WDdF6LFUde3Jl8WzPA+3ZbxYqkC6xAXuD3irdEHN1k0NfTRrJD8ZD378SJ61miMLCqIOXYhd8x+AJQ==", + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.4.tgz", + "integrity": "sha512-dkxf7+hn8mFBwKjs9bvBlArzLVxVbS8usaPUDd5p2a9JCL9tB8OaOVN1isD4+Xyk4ns89/xeOmbQvgdK7IIVdA==", "requires": { - "regenerator-runtime": "^0.13.11" + "regenerator-runtime": "^0.14.0" } }, "@babel/template": { @@ -9663,6 +9583,11 @@ "commander": "^2.15.1" } }, + "@ctrl/tinycolor": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-4.1.0.tgz", + "integrity": "sha512-WyOx8cJQ+FQus4Mm4uPIZA64gbk3Wxh0so5Lcii0aJifqwoVOlfFtorjLE0Hen4OYyHZMXDWqMmaQemBhgxFRQ==" + }, "@emotion/babel-plugin": { "version": "11.11.0", "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz", @@ -9784,34 +9709,6 @@ "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz", "integrity": "sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==" }, - "@esbuild/android-arm": { - "version": "0.18.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.12.tgz", - "integrity": "sha512-LIxaNIQfkFZbTLb4+cX7dozHlAbAshhFE5PKdro0l+FnCpx1GDJaQ2WMcqm+ToXKMt8p8Uojk/MFRuGyz3V5Sw==", - "dev": true, - "optional": true - }, - "@esbuild/android-arm64": { - "version": "0.18.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.12.tgz", - "integrity": "sha512-BMAlczRqC/LUt2P97E4apTBbkvS9JTJnp2DKFbCwpZ8vBvXVbNdqmvzW/OsdtI/+mGr+apkkpqGM8WecLkPgrA==", - "dev": true, - "optional": true - }, - "@esbuild/android-x64": { - "version": "0.18.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.12.tgz", - "integrity": "sha512-zU5MyluNsykf5cOJ0LZZZjgAHbhPJ1cWfdH1ZXVMXxVMhEV0VZiZXQdwBBVvmvbF28EizeK7obG9fs+fpmS0eQ==", - "dev": true, - "optional": true - }, - "@esbuild/darwin-arm64": { - "version": "0.18.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.12.tgz", - "integrity": "sha512-zUZMep7YONnp6954QOOwEBwFX9svlKd3ov6PkxKd53LGTHsp/gy7vHaPGhhjBmEpqXEXShi6dddjIkmd+NgMsA==", - "dev": true, - "optional": true - }, "@esbuild/darwin-x64": { "version": "0.18.12", "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.12.tgz", @@ -9819,125 +9716,6 @@ "dev": true, "optional": true }, - "@esbuild/freebsd-arm64": { - "version": "0.18.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.12.tgz", - "integrity": "sha512-GIIHtQXqgeOOqdG16a/A9N28GpkvjJnjYMhOnXVbn3EDJcoItdR58v/pGN31CHjyXDc8uCcRnFWmqaJt24AYJg==", - "dev": true, - "optional": true - }, - "@esbuild/freebsd-x64": { - "version": "0.18.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.12.tgz", - "integrity": "sha512-zK0b9a1/0wZY+6FdOS3BpZcPc1kcx2G5yxxfEJtEUzVxI6n/FrC2Phsxj/YblPuBchhBZ/1wwn7AyEBUyNSa6g==", - "dev": true, - "optional": true - }, - "@esbuild/linux-arm": { - "version": "0.18.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.12.tgz", - "integrity": "sha512-y75OijvrBE/1XRrXq1jtrJfG26eHeMoqLJ2dwQNwviwTuTtHGCojsDO6BJNF8gU+3jTn1KzJEMETytwsFSvc+Q==", - "dev": true, - "optional": true - }, - "@esbuild/linux-arm64": { - "version": "0.18.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.12.tgz", - "integrity": "sha512-JKgG8Q/LL/9sw/iHHxQyVMoQYu3rU3+a5Z87DxC+wAu3engz+EmctIrV+FGOgI6gWG1z1+5nDDbXiRMGQZXqiw==", - "dev": true, - "optional": true - }, - "@esbuild/linux-ia32": { - "version": "0.18.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.12.tgz", - "integrity": "sha512-yoRIAqc0B4lDIAAEFEIu9ttTRFV84iuAl0KNCN6MhKLxNPfzwCBvEMgwco2f71GxmpBcTtn7KdErueZaM2rEvw==", - "dev": true, - "optional": true - }, - "@esbuild/linux-loong64": { - "version": "0.18.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.12.tgz", - "integrity": "sha512-qYgt3dHPVvf/MgbIBpJ4Sup/yb9DAopZ3a2JgMpNKIHUpOdnJ2eHBo/aQdnd8dJ21X/+sS58wxHtA9lEazYtXQ==", - "dev": true, - "optional": true - }, - "@esbuild/linux-mips64el": { - "version": "0.18.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.12.tgz", - "integrity": "sha512-wHphlMLK4ufNOONqukELfVIbnGQJrHJ/mxZMMrP2jYrPgCRZhOtf0kC4yAXBwnfmULimV1qt5UJJOw4Kh13Yfg==", - "dev": true, - "optional": true - }, - "@esbuild/linux-ppc64": { - "version": "0.18.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.12.tgz", - "integrity": "sha512-TeN//1Ft20ZZW41+zDSdOI/Os1bEq5dbvBvYkberB7PHABbRcsteeoNVZFlI0YLpGdlBqohEpjrn06kv8heCJg==", - "dev": true, - "optional": true - }, - "@esbuild/linux-riscv64": { - "version": "0.18.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.12.tgz", - "integrity": "sha512-AgUebVS4DoAblBgiB2ACQ/8l4eGE5aWBb8ZXtkXHiET9mbj7GuWt3OnsIW/zX+XHJt2RYJZctbQ2S/mDjbp0UA==", - "dev": true, - "optional": true - }, - "@esbuild/linux-s390x": { - "version": "0.18.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.12.tgz", - "integrity": "sha512-dJ3Rb3Ei2u/ysSXd6pzleGtfDdc2MuzKt8qc6ls8vreP1G3B7HInX3i7gXS4BGeVd24pp0yqyS7bJ5NHaI9ing==", - "dev": true, - "optional": true - }, - "@esbuild/linux-x64": { - "version": "0.18.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.12.tgz", - "integrity": "sha512-OrNJMGQbPaVyHHcDF8ybNSwu7TDOfX8NGpXCbetwOSP6txOJiWlgQnRymfC9ocR1S0Y5PW0Wb1mV6pUddqmvmQ==", - "dev": true, - "optional": true - }, - "@esbuild/netbsd-x64": { - "version": "0.18.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.12.tgz", - "integrity": "sha512-55FzVCAiwE9FK8wWeCRuvjazNRJ1QqLCYGZVB6E8RuQuTeStSwotpSW4xoRGwp3a1wUsaVCdYcj5LGCASVJmMg==", - "dev": true, - "optional": true - }, - "@esbuild/openbsd-x64": { - "version": "0.18.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.12.tgz", - "integrity": "sha512-qnluf8rfb6Y5Lw2tirfK2quZOBbVqmwxut7GPCIJsM8lc4AEUj9L8y0YPdLaPK0TECt4IdyBdBD/KRFKorlK3g==", - "dev": true, - "optional": true - }, - "@esbuild/sunos-x64": { - "version": "0.18.12", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.12.tgz", - "integrity": "sha512-+RkKpVQR7bICjTOPUpkTBTaJ4TFqQBX5Ywyd/HSdDkQGn65VPkTsR/pL4AMvuMWy+wnXgIl4EY6q4mVpJal8Kg==", - "dev": true, - "optional": true - }, - "@esbuild/win32-arm64": { - "version": "0.18.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.12.tgz", - "integrity": "sha512-GNHuciv0mFM7ouzsU0+AwY+7eV4Mgo5WnbhfDCQGtpvOtD1vbOiRjPYG6dhmMoFyBjj+pNqQu2X+7DKn0KQ/Gw==", - "dev": true, - "optional": true - }, - "@esbuild/win32-ia32": { - "version": "0.18.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.12.tgz", - "integrity": "sha512-kR8cezhYipbbypGkaqCTWIeu4zID17gamC8YTPXYtcN3E5BhhtTnwKBn9I0PJur/T6UVwIEGYzkffNL0lFvxEw==", - "dev": true, - "optional": true - }, - "@esbuild/win32-x64": { - "version": "0.18.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.12.tgz", - "integrity": "sha512-O0UYQVkvfM/jO8a4OwoV0mAKSJw+mjWTAd1MJd/1FCX6uiMdLmMRPK/w6e9OQ0ob2WGxzIm9va/KG0Ja4zIOgg==", - "dev": true, - "optional": true - }, "@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", @@ -9997,11 +9775,27 @@ "version": "1.0.1" }, "@floating-ui/dom": { - "version": "1.0.4", + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.3.tgz", + "integrity": "sha512-RnDthu3mzPlQ31Ss/BTwQ1zjzIhr3lk1gZB1OC56h/1vEtaXkESrOqL5fQVMfXpwGtRwX+YsZBdyHtJMQnkArw==", + "requires": { + "@floating-ui/core": "^1.0.0", + "@floating-ui/utils": "^0.2.0" + } + }, + "@floating-ui/react-dom": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.8.tgz", + "integrity": "sha512-HOdqOt3R3OGeTKidaLvJKcgg75S6tibQ3Tif4eyd91QnIJWr0NLvoXFpJA/j8HqkFSL68GDca9AuyWEHlhyClw==", "requires": { - "@floating-ui/core": "^1.0.1" + "@floating-ui/dom": "^1.6.1" } }, + "@floating-ui/utils": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.1.tgz", + "integrity": "sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q==" + }, "@humanwhocodes/config-array": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz", @@ -10175,31 +9969,30 @@ "version": "3.1.0" }, "@mui/base": { - "version": "5.0.0-beta.8", - "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.8.tgz", - "integrity": "sha512-b4vVjMZx5KzzEMf4arXKoeV5ZegAMOoPwoy1vfUBwhvXc2QtaaAyBp50U7OA2L06Leubc1A+lEp3eqwZoFn87g==", - "requires": { - "@babel/runtime": "^7.22.6", - "@emotion/is-prop-valid": "^1.2.1", - "@mui/types": "^7.2.4", - "@mui/utils": "^5.14.1", + "version": "5.0.0-beta.40", + "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.40.tgz", + "integrity": "sha512-I/lGHztkCzvwlXpjD2+SNmvNQvB4227xBXhISPjEaJUXGImOQ9f3D2Yj/T3KasSI/h0MLWy74X0J6clhPmsRbQ==", + "requires": { + "@babel/runtime": "^7.23.9", + "@floating-ui/react-dom": "^2.0.8", + "@mui/types": "^7.2.14", + "@mui/utils": "^5.15.14", "@popperjs/core": "^2.11.8", - "clsx": "^1.2.1", - "prop-types": "^15.8.1", - "react-is": "^18.2.0" + "clsx": "^2.1.0", + "prop-types": "^15.8.1" }, "dependencies": { - "react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + "clsx": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.0.tgz", + "integrity": "sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==" } } }, "@mui/core-downloads-tracker": { - "version": "5.14.2", - "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.14.2.tgz", - "integrity": "sha512-x+c/MgDL1t/IIy5lDbMlrDouFG5DYZbl3DP4dbbuhlpPFBnE9glYwmJEee/orVHQpOPwLxCAIWQs+2DKSaBVWQ==" + "version": "5.15.14", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.15.14.tgz", + "integrity": "sha512-on75VMd0XqZfaQW+9pGjSNiqW+ghc5E2ZSLRBXwcXl/C4YzjfyjrLPhrEpKnR9Uym9KXBvxrhoHfPcczYHweyA==" }, "@mui/icons-material": { "version": "5.14.0", @@ -10210,24 +10003,29 @@ } }, "@mui/material": { - "version": "5.14.2", - "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.14.2.tgz", - "integrity": "sha512-TgNR4/YRL11RifsnMWNhITNCkGJYVz20SCvVJBBoU5Y/KhUNSSJxjDpEB8VrnY+sUsV0NigLCkHZJglfsiS3Pw==", - "requires": { - "@babel/runtime": "^7.22.6", - "@mui/base": "5.0.0-beta.8", - "@mui/core-downloads-tracker": "^5.14.2", - "@mui/system": "^5.14.1", - "@mui/types": "^7.2.4", - "@mui/utils": "^5.14.1", - "@types/react-transition-group": "^4.4.6", - "clsx": "^1.2.1", - "csstype": "^3.1.2", + "version": "5.15.14", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.15.14.tgz", + "integrity": "sha512-kEbRw6fASdQ1SQ7LVdWR5OlWV3y7Y54ZxkLzd6LV5tmz+NpO3MJKZXSfgR0LHMP7meKsPiMm4AuzV0pXDpk/BQ==", + "requires": { + "@babel/runtime": "^7.23.9", + "@mui/base": "5.0.0-beta.40", + "@mui/core-downloads-tracker": "^5.15.14", + "@mui/system": "^5.15.14", + "@mui/types": "^7.2.14", + "@mui/utils": "^5.15.14", + "@types/react-transition-group": "^4.4.10", + "clsx": "^2.1.0", + "csstype": "^3.1.3", "prop-types": "^15.8.1", "react-is": "^18.2.0", "react-transition-group": "^4.4.5" }, "dependencies": { + "clsx": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.0.tgz", + "integrity": "sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==" + }, "react-is": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", @@ -10236,55 +10034,61 @@ } }, "@mui/private-theming": { - "version": "5.13.7", - "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.13.7.tgz", - "integrity": "sha512-qbSr+udcij5F9dKhGX7fEdx2drXchq7htLNr2Qg2Ma+WJ6q0ERlEqGSBiPiVDJkptcjeVL4DGmcf1wl5+vD4EA==", + "version": "5.15.14", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.15.14.tgz", + "integrity": "sha512-UH0EiZckOWcxiXLX3Jbb0K7rC8mxTr9L9l6QhOZxYc4r8FHUkefltV9VDGLrzCaWh30SQiJvAEd7djX3XXY6Xw==", "requires": { - "@babel/runtime": "^7.22.5", - "@mui/utils": "^5.13.7", + "@babel/runtime": "^7.23.9", + "@mui/utils": "^5.15.14", "prop-types": "^15.8.1" } }, "@mui/styled-engine": { - "version": "5.13.2", - "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.13.2.tgz", - "integrity": "sha512-VCYCU6xVtXOrIN8lcbuPmoG+u7FYuOERG++fpY74hPpEWkyFQG97F+/XfTQVYzlR2m7nPjnwVUgATcTCMEaMvw==", + "version": "5.15.14", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.15.14.tgz", + "integrity": "sha512-RILkuVD8gY6PvjZjqnWhz8fu68dVkqhM5+jYWfB5yhlSQKg+2rHkmEwm75XIeAqI3qwOndK6zELK5H6Zxn4NHw==", "requires": { - "@babel/runtime": "^7.21.0", + "@babel/runtime": "^7.23.9", "@emotion/cache": "^11.11.0", - "csstype": "^3.1.2", + "csstype": "^3.1.3", "prop-types": "^15.8.1" } }, "@mui/system": { - "version": "5.14.1", - "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.14.1.tgz", - "integrity": "sha512-u+xlsU34Jdkgx1CxmBnIC4Y08uPdVX5iEd3S/1dggDFtOGp+Lj8xmKRJAQ8PJOOJLOh8pDwaZx4AwXikL4l1QA==", - "requires": { - "@babel/runtime": "^7.22.6", - "@mui/private-theming": "^5.13.7", - "@mui/styled-engine": "^5.13.2", - "@mui/types": "^7.2.4", - "@mui/utils": "^5.14.1", - "clsx": "^1.2.1", - "csstype": "^3.1.2", + "version": "5.15.14", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.15.14.tgz", + "integrity": "sha512-auXLXzUaCSSOLqJXmsAaq7P96VPRXg2Rrz6OHNV7lr+kB8lobUF+/N84Vd9C4G/wvCXYPs5TYuuGBRhcGbiBGg==", + "requires": { + "@babel/runtime": "^7.23.9", + "@mui/private-theming": "^5.15.14", + "@mui/styled-engine": "^5.15.14", + "@mui/types": "^7.2.14", + "@mui/utils": "^5.15.14", + "clsx": "^2.1.0", + "csstype": "^3.1.3", "prop-types": "^15.8.1" + }, + "dependencies": { + "clsx": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.0.tgz", + "integrity": "sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==" + } } }, "@mui/types": { - "version": "7.2.4", - "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.4.tgz", - "integrity": "sha512-LBcwa8rN84bKF+f5sDyku42w1NTxaPgPyYKODsh01U1fVstTClbUoSA96oyRBnSNyEiAVjKm6Gwx9vjR+xyqHA==", + "version": "7.2.14", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.14.tgz", + "integrity": "sha512-MZsBZ4q4HfzBsywtXgM1Ksj6HDThtiwmOKUXH1pKYISI9gAVXCNHNpo7TlGoGrBaYWZTdNoirIN7JsQcQUjmQQ==", "requires": {} }, "@mui/utils": { - "version": "5.14.1", - "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.14.1.tgz", - "integrity": "sha512-39KHKK2JeqRmuUcLDLwM+c2XfVC136C5/yUyQXmO2PVbOb2Bol4KxtkssEqCbTwg87PSCG3f1Tb0keRsK7cVGw==", + "version": "5.15.14", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.15.14.tgz", + "integrity": "sha512-0lF/7Hh/ezDv5X7Pry6enMsbYyGKjADzvHyo3Qrc/SSlTsQ1VkbDMbH0m2t3OR5iIVLwMoxwM7yGd+6FCMtTFA==", "requires": { - "@babel/runtime": "^7.22.6", - "@types/prop-types": "^15.7.5", - "@types/react-is": "^18.2.1", + "@babel/runtime": "^7.23.9", + "@types/prop-types": "^15.7.11", "prop-types": "^15.8.1", "react-is": "^18.2.0" }, @@ -10421,6 +10225,19 @@ "@sinclair/typebox": { "version": "0.24.51" }, + "@tanstack/query-core": { + "version": "5.36.1", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.36.1.tgz", + "integrity": "sha512-BteWYEPUcucEu3NBcDAgKuI4U25R9aPrHSP6YSf2NvaD2pSlIQTdqOfLRsxH9WdRYg7k0Uom35Uacb6nvbIMJg==" + }, + "@tanstack/react-query": { + "version": "5.36.2", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.36.2.tgz", + "integrity": "sha512-bHNa+5dead+j6SA8WVlEOPxcGfteVFgdyFTCFcxBgjnPf0fFpHUc7aNZBCnvmPXqy/BeQa9zTuU9ectb7i8ZXA==", + "requires": { + "@tanstack/query-core": "5.36.1" + } + }, "@tanstack/react-table": { "version": "8.9.3", "resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.9.3.tgz", @@ -10535,6 +10352,15 @@ "version": "1.0.0", "dev": true }, + "@types/hoist-non-react-statics": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.5.tgz", + "integrity": "sha512-SbcrWzkKBw2cdwRTwQAswfpB9g9LJWfjtUeW/jvNwbhC8cpmmNYVePa+ncbUe0rGTQ7G3Ff6mYUN2VMfLVr+Sg==", + "requires": { + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0" + } + }, "@types/istanbul-lib-coverage": { "version": "2.0.4" }, @@ -10603,7 +10429,9 @@ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==" }, "@types/prop-types": { - "version": "15.7.5" + "version": "15.7.12", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz", + "integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==" }, "@types/react": { "version": "18.2.14", @@ -10623,18 +10451,21 @@ "@types/react": "*" } }, - "@types/react-is": { - "version": "18.2.1", - "resolved": "https://registry.npmjs.org/@types/react-is/-/react-is-18.2.1.tgz", - "integrity": "sha512-wyUkmaaSZEzFZivD8F2ftSyAfk6L+DfFliVj/mYdOXbVjRcS87fQJLTnhk6dRZPuJjI+9g6RZJO4PNCngUrmyw==", + "@types/react-redux": { + "version": "7.1.33", + "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.33.tgz", + "integrity": "sha512-NF8m5AjWCkert+fosDsN3hAlHzpjSiXlVy9EgQEmLoBhaNXbmyeGs/aj5dQzKuF+/q+S7JQagorGDW8pJ28Hmg==", "requires": { - "@types/react": "*" + "@types/hoist-non-react-statics": "^3.3.0", + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0", + "redux": "^4.0.0" } }, "@types/react-transition-group": { - "version": "4.4.6", - "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.6.tgz", - "integrity": "sha512-VnCdSxfcm08KjsJVQcfBmhEQAPnLB8G08hAxn39azX1qYBQ/5RVQuoHuKIcfKOdncuaUvEpFKFzEvbtIMsfVew==", + "version": "4.4.10", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.10.tgz", + "integrity": "sha512-hT/+s0VQs2ojCX823m60m5f0sL5idt9SO6Tj6Dg+rdphGPIeJbJ6CxvBYkgkGKrYeDjvIpKTR38UzmtHJOGW3Q==", "requires": { "@types/react": "*" } @@ -10879,9 +10710,11 @@ } }, "braces": { - "version": "3.0.2", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "requires": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" } }, "browserslist": { @@ -10934,9 +10767,9 @@ "dev": true }, "caniuse-lite": { - "version": "1.0.30001576", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001576.tgz", - "integrity": "sha512-ff5BdakGe2P3SQsMsiqmt1Lc8221NR1VzHj5jXN5vBny9A6fpze94HiVV/n7XRosOlsShJcvMv5mdnpjOGCEgg==", + "version": "1.0.30001636", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001636.tgz", + "integrity": "sha512-bMg2vmr8XBsbL6Lr0UHXy/21m84FTxDLWn2FSqMd5PrlbMxwJlQnC2YWYxVgp66PZE+BBNF2jYQUBKCo1FDeZg==", "dev": true }, "canvas-fit": { @@ -11111,6 +10944,14 @@ "which": "^2.0.1" } }, + "css-box-model": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.1.tgz", + "integrity": "sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==", + "requires": { + "tiny-invariant": "^1.0.6" + } + }, "css-font": { "version": "1.2.0", "requires": { @@ -11158,9 +10999,9 @@ "version": "1.0.3" }, "csstype": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", - "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==" + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" }, "d": { "version": "1.0.1", @@ -11443,7 +11284,9 @@ "version": "2.2.4" }, "ejs": { - "version": "3.1.8", + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", "dev": true, "requires": { "jake": "^10.8.5" @@ -11563,10 +11406,13 @@ } }, "es5-ext": { - "version": "0.10.62", + "version": "0.10.64", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz", + "integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==", "requires": { "es6-iterator": "^2.0.3", "es6-symbol": "^3.1.3", + "esniff": "^2.0.1", "next-tick": "^1.1.0" } }, @@ -11852,6 +11698,24 @@ "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==", "dev": true }, + "esniff": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz", + "integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==", + "requires": { + "d": "^1.0.1", + "es5-ext": "^0.10.62", + "event-emitter": "^0.3.5", + "type": "^2.7.2" + }, + "dependencies": { + "type": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/type/-/type-2.7.3.tgz", + "integrity": "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==" + } + } + }, "espree": { "version": "9.6.0", "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.0.tgz", @@ -11889,6 +11753,15 @@ "esutils": { "version": "2.0.3" }, + "event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==", + "requires": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, "events": { "version": "3.3.0" }, @@ -11984,9 +11857,9 @@ "version": "2.0.6" }, "fast-loops": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/fast-loops/-/fast-loops-1.1.3.tgz", - "integrity": "sha512-8EZzEP0eKkEEVX+drtd9mtuQ+/QrlfW/5MlwcwK5Nds6EkZ/tRzEexkzUY2mIssnAyVLT+TKHuRXmFNNXYUd6g==" + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/fast-loops/-/fast-loops-1.1.4.tgz", + "integrity": "sha512-8dbd3XWoKCTms18ize6JmQF1SFnnfj5s0B7rRry22EofgMu7B6LKHVh+XfFqFGsqnbH54xgeO83PzpKI+ODhlg==" }, "fast-shallow-equal": { "version": "1.0.0", @@ -12036,7 +11909,9 @@ } }, "fill-range": { - "version": "7.0.1", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "requires": { "to-regex-range": "^5.0.1" } @@ -12654,7 +12529,9 @@ "dev": true }, "is-number": { - "version": "7.0.0" + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" }, "is-number-object": { "version": "1.0.7", @@ -12859,6 +12736,11 @@ "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-2.2.1.tgz", "integrity": "sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ==" }, + "js-sha256": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/js-sha256/-/js-sha256-0.11.0.tgz", + "integrity": "sha512-6xNlKayMZvds9h1Y1VWc0fQHQ82BxTXizWPEtEeGvmOUYpBRy4gbWroHLpzowe6xiQhHpelCQiE7HEdznyBL9Q==" + }, "js-tokens": { "version": "4.0.0" }, @@ -12916,9 +12798,23 @@ "object.assign": "^4.1.3" } }, + "jwt-decode": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz", + "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==" + }, "kdbush": { "version": "3.0.0" }, + "keycloak-js": { + "version": "25.0.2", + "resolved": "https://registry.npmjs.org/keycloak-js/-/keycloak-js-25.0.2.tgz", + "integrity": "sha512-ACLf5O5PqzfDJwGqvLpqM0kflYWmyl3+T7M2C23gztJYccDxdfNP54+B9OkXz2GnDpLUId0ceoA+lbHw9t4Wng==", + "requires": { + "js-sha256": "^0.11.0", + "jwt-decode": "^4.0.0" + } + }, "kolorist": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/kolorist/-/kolorist-1.8.0.tgz", @@ -13135,6 +13031,14 @@ "ms": { "version": "2.1.2" }, + "mui-color-input": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/mui-color-input/-/mui-color-input-2.0.3.tgz", + "integrity": "sha512-rAd040qQ0Y+8dk4gE8kkCiJ/vCgA0j4vv1quJ43BfORTFE3uHarHj0xY1Vo9CPbojtx1f5vW+CjckYPRIZPIRg==", + "requires": { + "@ctrl/tinycolor": "^4.0.3" + } + }, "mumath": { "version": "3.3.4", "requires": { @@ -13630,6 +13534,11 @@ "performance-now": "^2.1.0" } }, + "raf-schd": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/raf-schd/-/raf-schd-4.0.3.tgz", + "integrity": "sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ==" + }, "rbush": { "version": "3.0.1", "requires": { @@ -13642,6 +13551,27 @@ "loose-envify": "^1.1.0" } }, + "react-beautiful-dnd": { + "version": "13.1.1", + "resolved": "https://registry.npmjs.org/react-beautiful-dnd/-/react-beautiful-dnd-13.1.1.tgz", + "integrity": "sha512-0Lvs4tq2VcrEjEgDXHjT98r+63drkKEgqyxdA7qD3mvKwga6a5SscbdLPO2IExotU1jW8L0Ksdl0Cj2AF67nPQ==", + "requires": { + "@babel/runtime": "^7.9.2", + "css-box-model": "^1.2.0", + "memoize-one": "^5.1.1", + "raf-schd": "^4.0.2", + "react-redux": "^7.2.0", + "redux": "^4.0.4", + "use-memo-one": "^1.1.1" + }, + "dependencies": { + "memoize-one": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", + "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==" + } + } + }, "react-color": { "version": "2.19.3", "requires": { @@ -13707,6 +13637,19 @@ "warning": "^4.0.2" } }, + "react-redux": { + "version": "7.2.9", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.9.tgz", + "integrity": "sha512-Gx4L3uM182jEEayZfRbI/G11ZpYdNAnBs70lFVMNdHJI76XYtR+7m0MN+eAs7UHBPhWXcnFPaS+9owSCJQHNpQ==", + "requires": { + "@babel/runtime": "^7.15.4", + "@types/react-redux": "^7.1.20", + "hoist-non-react-statics": "^3.3.2", + "loose-envify": "^1.4.0", + "prop-types": "^15.7.2", + "react-is": "^17.0.2" + } + }, "react-resizable-panels": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/react-resizable-panels/-/react-resizable-panels-2.0.7.tgz", @@ -13824,6 +13767,14 @@ "strip-indent": "^3.0.0" } }, + "redux": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz", + "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==", + "requires": { + "@babel/runtime": "^7.9.2" + } + }, "redux-bundler": { "version": "28.0.3" }, @@ -13832,9 +13783,9 @@ "requires": {} }, "regenerator-runtime": { - "version": "0.13.11", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" }, "regexp.prototype.flags": { "version": "1.4.3", @@ -14547,8 +14498,7 @@ "tiny-invariant": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.1.tgz", - "integrity": "sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw==", - "dev": true + "integrity": "sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw==" }, "tinycolor2": { "version": "1.4.2" @@ -14576,6 +14526,8 @@ }, "to-regex-range": { "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "requires": { "is-number": "^7.0.0" } @@ -14651,10 +14603,10 @@ } }, "typescript": { - "version": "4.8.4", - "dev": true, - "optional": true, - "peer": true + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", + "dev": true }, "ufo": { "version": "1.4.0", @@ -14724,6 +14676,12 @@ "version": "1.1.2", "requires": {} }, + "use-memo-one": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/use-memo-one/-/use-memo-one-1.1.3.tgz", + "integrity": "sha512-g66/K7ZQGYrI6dy8GLpVcMsBp4s17xNkYJVSMvTEevGy3nDxHOfE6z8BVE22+5G5x7t3+bhzrlTDB7ObrEE0cQ==", + "requires": {} + }, "util": { "version": "0.12.5", "requires": { @@ -14738,9 +14696,9 @@ "version": "1.0.2" }, "vite": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.2.tgz", - "integrity": "sha512-tBCZBNSBbHQkaGyhGCDUGqeo2ph8Fstyp6FMSvTtsXeZSPpSMGlviAOav2hxVTqFcx8Hj/twtWKsMJXNY0xI8w==", + "version": "4.5.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.3.tgz", + "integrity": "sha512-kQL23kMeX92v3ph7IauVkXkikdDRsYMGTVl5KY2E9OY4ONLvkHf04MDTbnfo6NKxZiDLWzVpP5oTa8hQD8U3dg==", "dev": true, "requires": { "esbuild": "^0.18.10", diff --git a/package.json b/package.json index 33f0407a..37b61199 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hhd-ui", - "version": "0.15.7", + "version": "0.17.0", "private": true, "dependencies": { "@ag-grid-community/client-side-row-model": "^30.0.3", @@ -14,6 +14,7 @@ "@iconify/utils": "^2.1.22", "@mui/icons-material": "^5.14.0", "@mui/material": "^5.14.2", + "@tanstack/react-query": "^5.36.2", "@tanstack/react-table": "^8.9.3", "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^14.0.0", @@ -23,15 +24,18 @@ "date-fns": "^2.30.0", "graceful-fs": "^4.2.11", "internal-nav-helper": "^3.1.0", + "keycloak-js": "^25.0.2", "lodash.debounce": "^4.0.8", "lodash.isequal": "^4.5.0", "luxon": "^3.3.0", "money-clip": "^3.0.5", + "mui-color-input": "^2.0.3", "ol": "^7.4.0", "plotly.js": "^2.25.2", "process": "^0.11.10", "proj4": "^2.9.0", "react": "^18.2.0", + "react-beautiful-dnd": "^13.1.1", "react-color": "^2.19.3", "react-csv": "^2.2.2", "react-datepicker": "^4.16.0", @@ -67,6 +71,7 @@ "rollup-plugin-visualizer": "^5.9.2", "sass": "^1.63.6", "source-map-explorer": "^2.5.3", + "typescript": "^5.4.5", "vite": "^4.5.2", "vite-plugin-checker": "^0.6.1", "vite-plugin-svgr": "^3.2.0" diff --git a/public/silent-check-sso.html b/public/silent-check-sso.html new file mode 100644 index 00000000..128f00a3 --- /dev/null +++ b/public/silent-check-sso.html @@ -0,0 +1,9 @@ + + + + + + + \ No newline at end of file diff --git a/src/app-bundles/batch-plot-configurations-bundle.js b/src/app-bundles/batch-plot-configurations-bundle.js index 968b4c2a..b3c14806 100644 --- a/src/app-bundles/batch-plot-configurations-bundle.js +++ b/src/app-bundles/batch-plot-configurations-bundle.js @@ -1,14 +1,37 @@ -import createRestBundle from './create-rest-bundle'; import { createSelector } from 'redux-bundler'; +import createRestBundle from './create-rest-bundle'; + +const getTimeseriesFromDisplay = (plotType, display, timeseries) => { + switch (plotType) { + case 'scatter-line': + return timeseries.filter((ts) => + (display?.traces?.map(trace => trace.timeseries_id) || []).includes(ts.id) + ); + case 'bullseye': + return timeseries.filter(ts => + [display?.x_axis_timeseries_id, display?.y_axis_timeseries_id].includes(ts.id) + ); + case 'contour': + return timeseries.filter(ts => + display?.timeseries_ids?.includes(ts.id) + ); + case 'profile': + return display?.instrument_id; + default: + throw new Error(`Invalid Plot Type: ${plotType}. Expected one of: ['scatter-line', 'bullseye', 'contour', 'profile']`); + } +}; + export default createRestBundle({ name: 'batchPlotConfigurations', uid: 'id', persist: false, - getTemplate: '/projects/:projectId/plot_configurations', - putTemplate: '/projects/:projectId/plot_configurations/{:item.id}', + getTemplate: '/projects/:projectId/plot_configs', + putTemplate: '/projects/:projectId/plot_configs/{:item.id}', + // @TODO: Remove the `postTemplate` postTemplate: '/projects/:projectId/plot_configurations', - deleteTemplate: '/projects/:projectId/plot_configurations/{:item.id}', + deleteTemplate: '/projects/:projectId/plot_configs/{:item.id}', fetchActions: ['URL_UPDATED', 'PROJECTS_FETCH_FINISHED'], forceFetchActions: ['BATCHPLOTCONFIGURATIONS_SAVE_FINISHED'], urlParamSelectors: ['selectProjectsIdByRoute'], @@ -29,6 +52,35 @@ export default createRestBundle({ store.doBatchPlotMapAddData(); }, + /** + * Save plot plot config settings. note: formData payloads differ based on plotType. + * + * @param {string} plotType one of ['scatter-line', 'profile', 'contour', 'bullseye'] + * @param {string} id Batch Plot Config Id + * @param {object} formData api-defined trace structure for related plotType + * @returns + */ + doSaveBatchPlotConfiguration: (plotType, id = null, formData = {}) => ({ dispatch, store, apiPost, apiPut }) => { + const uriMap = { + 'scatter-line': 'scatter_line_plots', + 'profile': 'profile_plots', + 'contour': 'contour_plots', + 'bullseye': 'bullseye_plots', + }; + const method = !id ? apiPost : apiPut; + const projectId = store.selectProjectsIdByRoute()?.projectId; + const uri = `/projects/${projectId}/plot_configs/${uriMap[plotType]}${!id ? '' : `/${id}`}`; + + method(uri, formData, (err, _body) => { + if (err) { + dispatch({ type: 'BATCH_PLOT_CONFIGURATION_SAVE_ERROR', payload: err }); + } else { + dispatch({ type: 'BATCH_PLOT_CONFIGURATION_SAVED' }); + store.doBatchPlotConfigurationsFetch(); + } + }); + }, + selectBatchPlotConfigurationsRaw: (state) => state.batchPlotConfigurations, selectBatchPlotConfigurationsActiveId: (state) => state.batchPlotConfigurations._activeBatchPlotConfigurationId, @@ -47,11 +99,12 @@ export default createRestBundle({ timeseries.length ) { batchPlotConfigurations.forEach((config) => { - const activeTS = timeseries.filter((ts) => - (config.timeseries_id || []).includes(ts.id) - ); - instrumentMap[config.id] = instruments.filter((i) => - activeTS.some((ts) => ts.instrument_id === i.id) + const { plot_type, display } = config || {}; + const activeTS = getTimeseriesFromDisplay(plot_type, display, timeseries); + + instrumentMap[config.id] = plot_type === 'profile' + ? instruments.filter(i => i.id === activeTS) + : instruments.filter(i => activeTS.some((ts) => ts.instrument_id === i.id) ); }); } diff --git a/src/app-bundles/collection-group-bundle.js b/src/app-bundles/collection-group-bundle.js index 52f93b0c..88e2d2d0 100644 --- a/src/app-bundles/collection-group-bundle.js +++ b/src/app-bundles/collection-group-bundle.js @@ -19,7 +19,7 @@ export default createRestBundle({ ], urlParamSelectors: ['selectProjectsIdByRoute'], prefetch: (store) => { - const hash = store.selectHash(); + const hash = store.selectHashStripQuery(); const pathname = store.selectRelativePathname(); const whiteList = ['dashboard']; diff --git a/src/app-bundles/create-auth-bundle.js b/src/app-bundles/create-auth-bundle.js deleted file mode 100644 index f1bc2bf7..00000000 --- a/src/app-bundles/create-auth-bundle.js +++ /dev/null @@ -1,187 +0,0 @@ -import { createSelector } from 'redux-bundler'; -import xhr from 'xhr'; - -const getTokenPart = function (token, part) { - const splitToken = token.split('.'); - return splitToken[part]; -}; - -const createAuthBundle = (opts) => { - const defaults = { - url: 'https://corpsmap-dev.sec.usace.army.mil/cwbi/auth/basic', - name: 'auth', - token: null, - redirectOnLogout: null, - verifyInterval: 1000 * 60, - }; - - const config = Object.assign({}, defaults, opts); - - if (opts.appId) - config.url = `https://corpsmap-dev.sec.usace.army.mil/cwbi/goauth/token/${opts.appId}`; - - return { - name: config.name, - - getReducer: () => { - const initialState = { - url: config.url, - mockToken: config.token, - token: config.token, - error: null, - mock: config.mock, - shouldVerifyToken: true, - redirectOnLogout: config.redirectOnLogout, - }; - - return (state = initialState, { type, payload }) => { - switch (type) { - case 'AUTH_LOGGED_IN': - case 'AUTH_LOGGED_OUT': - case 'AUTH_ERROR': - case 'AUTH_VERIFY_TOKEN': - return Object.assign({}, state, payload); - default: - return state; - } - }; - }, - - doAuthLogin: () => ({ dispatch, store }) => { - const isMock = store.selectAuthIsMocked(); - if (isMock) { - const token = store.selectAuthTokenMockRaw(); - dispatch({ - type: 'AUTH_LOGGED_IN', - payload: { token: token, error: null, shouldVerifyToken: true }, - }); - } else { - const url = store.selectAuthUrl(); - //@todo move to fetch api at some point - try { - xhr(url, (err, response, body) => { - if (err) { - throw new Error(`Login Response not ok. ${err}`); - } else { - const token = typeof body === 'string' ? body : JSON.parse(body); - dispatch({ - type: 'AUTH_LOGGED_IN', - payload: { token: token, error: null, shouldVerifyToken: true }, - }); - } - }); - } catch (err) { - // eslint-disable-next-line no-console - if (import.meta.env.DEV) console.error(err); - dispatch({ - type: 'AUTH_ERROR', - payload: { msg: 'Error Logging In', err: err }, - }); - } - } - }, - - doAuthLogout: () => ({ dispatch, store }) => { - if (store.selectAuthTokenRaw()) { - dispatch({ - type: 'AUTH_LOGGED_OUT', - payload: { token: null, error: null }, - }); - store.doRemoveProfile(); - const redirect = store.selectAuthRedirectOnLogout(); - if (redirect) store.doUpdateRelativeUrl(redirect); - } - }, - - doAuthVerifyToken: () => ({ dispatch, store }) => { - dispatch({ - type: 'AUTH_VERIFY_TOKEN', - payload: { shouldVerifyToken: false }, - }); - const isExpired = store.selectAuthTokenIsExpired(); - if (isExpired) { - store.doAuthLogout(); - } else { - window.setTimeout(store.doAuthVerifyToken, config.verifyInterval); - } - }, - - selectAuthRedirectOnLogout: (state) => state.auth.redirectOnLogout, - - selectAuthIsMocked: (state) => state.auth.mock, - - selectAuthUrl: (state) => state.auth.url, - - // select parts of the token itself - - selectAuthTokenRaw: (state) => state.auth.token, - - selectAuthTokenMockRaw: (state) => state.auth.mockToken, - - selectAuthTokenHeader: createSelector('selectAuthTokenRaw', (token) => { - if (!token) return {}; - return JSON.parse(window.atob(getTokenPart(token, 0))); - }), - - selectAuthTokenPayload: createSelector('selectAuthTokenRaw', (token) => { - if (!token) return {}; - return JSON.parse(window.atob(getTokenPart(token, 1))); - }), - - // select info about token expiration - - selectAuthTokenExp: createSelector('selectAuthTokenPayload', (payload) => { - if (!Object.prototype.hasOwnProperty.call(payload, 'exp')) return null; - return payload.exp; - }), - - selectAuthTokenIsExpired: createSelector('selectAuthTokenExp', (exp) => { - if (!exp) return true; - return exp < Math.floor(Date.now() / 1000); - }), - - // select parts of the payload - - selectAuthUsername: createSelector('selectAuthTokenPayload', (payload) => { - if (!Object.prototype.hasOwnProperty.call(payload, 'name')) return null; - return payload.name; - }), - - selectAuthEdipi: createSelector('selectAuthTokenPayload', (payload) => { - if (!Object.prototype.hasOwnProperty.call(payload, 'sub')) return null; - return payload.sub; - }), - - selectAuthRoles: createSelector('selectAuthTokenPayload', (payload) => { - if (!Object.prototype.hasOwnProperty.call(payload, 'roles')) return []; - return payload.roles; - }), - - selectAuthGroups: createSelector('selectAuthRoles', (roles) => roles.map((role) => { - const roleArr = role.split('.'); - return roleArr[0]; - })), - - selectAuthGroupRoles: createSelector('selectAuthRoles', (roles) => { - const groupRoles = {}; - roles - .map((role) => role.split('.')) - .forEach((role) => { - if (!Object.prototype.hasOwnProperty.call(groupRoles, role[0])) groupRoles[role[0]] = []; - groupRoles[role[0]].push(role[1]); - }); - return groupRoles; - }), - - selectAuthIsLoggedIn: (state) => !!state.auth.token, - - reactAuthShouldVerifyToken: (state) => { - if (state.auth.shouldVerifyToken) - return { actionCreator: 'doAuthVerifyToken' }; - }, - - persistActions: ['AUTH_LOGGED_IN', 'AUTH_LOGGED_OUT'], - }; -}; - -export default createAuthBundle; diff --git a/src/app-bundles/create-jwt-api-bundle.js b/src/app-bundles/create-jwt-api-bundle.js index 6782402b..fe708ca2 100644 --- a/src/app-bundles/create-jwt-api-bundle.js +++ b/src/app-bundles/create-jwt-api-bundle.js @@ -1,3 +1,5 @@ +import { getToken } from '../userService.ts'; + const arrayIze = (thing) => !thing || Array.isArray(thing) ? thing : [thing]; const shouldSkipToken = (method, path, unless) => { @@ -92,7 +94,6 @@ const createJwtApiBundle = (opts) => { const defaults = { name: 'api', root: '', - tokenSelector: 'selectAuthToken', unless: null, }; @@ -103,7 +104,6 @@ const createJwtApiBundle = (opts) => { // selectors const selectRoot = `select${uCaseName}Root`; const selectUnless = `select${uCaseName}Unless`; - const selectTokenSelector = `select${uCaseName}TokenSelector`; return { name: config.name, @@ -112,7 +112,6 @@ const createJwtApiBundle = (opts) => { const initialData = { root: config.root, unless: config.unless, - tokenSelector: config.tokenSelector, }; return (state = initialData) => state; @@ -120,24 +119,25 @@ const createJwtApiBundle = (opts) => { [selectRoot]: (state) => state[config.name].root, [selectUnless]: (state) => state[config.name].unless, - [selectTokenSelector]: (state) => state[config.name].tokenSelector, getExtraArgs: (store) => { const getCommonItems = () => ({ root: store[selectRoot](), unless: store[selectUnless](), - tokenSelector: store[selectTokenSelector](), }); - const defaultHeaders = token => ({ - Authorization: `Bearer ${token}`, - }); + const defaultHeaders = token => ( + token ? { + Authorization: `Bearer ${token}`, + } : {} + ); return { apiFetch: (path, options = {}) => { - const { root, unless, tokenSelector } = getCommonItems(); + const { root, unless } = getCommonItems(); if (!shouldSkipToken(options.method, path, unless)) { - const token = store[tokenSelector](); + const token = getToken(); + if (!token) return null; else { options.headers = { ...defaultHeaders(token) }; @@ -147,13 +147,14 @@ const createJwtApiBundle = (opts) => { }, apiGet: (path, callback) => { - const { root, unless, tokenSelector } = getCommonItems(); + const { root, unless } = getCommonItems(); const options = { method: 'GET', }; if (!shouldSkipToken(options.method, path, unless)) { - const token = store[tokenSelector](); + const token = getToken(); + if (!token) return null; else { options.headers = { ...defaultHeaders(token) }; @@ -163,7 +164,7 @@ const createJwtApiBundle = (opts) => { }, apiPut: (path, payload, callback) => { - const { root, unless, tokenSelector } = getCommonItems(); + const { root, unless } = getCommonItems(); const options = { method: 'PUT', headers: { @@ -171,7 +172,7 @@ const createJwtApiBundle = (opts) => { }, }; if (!shouldSkipToken(options.method, path, unless)) { - const token = store[tokenSelector](); + const token = getToken(); if (!token) return null; else { options.headers = { @@ -187,7 +188,7 @@ const createJwtApiBundle = (opts) => { }, apiPost: (path, payload, callback) => { - const { root, unless, tokenSelector } = getCommonItems(); + const { root, unless } = getCommonItems(); const options = { method: 'POST', headers: { @@ -195,7 +196,7 @@ const createJwtApiBundle = (opts) => { }, }; if (!shouldSkipToken(options.method, path, unless)) { - const token = store[tokenSelector](); + const token = getToken(); if (!token) return null; else { options.headers = { @@ -212,12 +213,12 @@ const createJwtApiBundle = (opts) => { }, apiDelete: (path, callback) => { - const { root, unless, tokenSelector } = getCommonItems(); + const { root, unless } = getCommonItems(); const options = { method: 'DELETE', }; if (!shouldSkipToken(options.method, path, unless)) { - const token = store[tokenSelector](); + const token = getToken(); if (!token) return null; else { options.headers = { ...defaultHeaders(token) }; diff --git a/src/app-bundles/create-rest-bundle.js b/src/app-bundles/create-rest-bundle.js index d9314891..785023a9 100644 --- a/src/app-bundles/create-rest-bundle.js +++ b/src/app-bundles/create-rest-bundle.js @@ -33,70 +33,6 @@ const decorateUrlWithItem = (urlTemplate, item) => { return url; }; -/** - * Check to see if a particular token part exists against a given value - */ -function checkTokenPart(tokenRoles, val, idx) { - let match = false; - tokenRoles.forEach((tokenRole) => { - const tokenPart = tokenRole.split('.')[idx]; - if (tokenPart === val) match = true; - }); - return match; -} - -/** - * Check one array of roles against another array of roles accounting - * for wildcards and org substitution - */ -function checkRoles(roles, tokenRolesJoined, orgsActiveSlug) { - let pass = false; - for (let i = 0; i < roles.length; i++) { - let role = roles[i]; - role = role.replace( - ':ORG.', - `${orgsActiveSlug ? orgsActiveSlug.toUpperCase() : ''}.` - ); - - // let super users through no matter what - if (tokenRolesJoined.indexOf('APP.SYSADMIN') !== -1) { - pass = true; - break; - } - - // first let's test if this role is in tokenRoles, if so, pass and move on - if (tokenRolesJoined.indexOf(role) !== -1) { - pass = true; - break; - } - - // ok, let's check to see if we have a wildcard - if (role.indexOf('*') !== -1) { - // if both parts are * then pass is true - if (role === '*.*') { - pass = true; - break; - } - - // otherwise we've got to check both parts separately - const parts = role.split('.'); - - // looks like we do, is it in the org position? - if (parts[0] === '*') { - // if so, check tokenRoles for the role - if (checkTokenPart(tokenRolesJoined, parts[1], 1)) pass = true; - if (pass) break; - } - - // how about the role position? - if (parts[1] === '*') { - if (checkTokenPart(tokenRolesJoined, parts[0], 0)) pass = true; - if (pass) break; - } - } - } - return pass; -} /** * Main Bundle Creator export @@ -183,9 +119,7 @@ const createRestBundle = (opts) => { const selectForceFetch = `select${uCaseName}ForceFetch`; const selectAbortReason = `select${uCaseName}AbortReason`; const selectAllowRoles = `select${uCaseName}AllowRoles`; - const selectIsAllowedRole = `select${uCaseName}IsAllowedRole`; const selectDisallowRoles = `select${uCaseName}DisallowRoles`; - const selectIsDisallowedRole = `select${uCaseName}IsDisallowedRole`; const selectPageSize = `select${uCaseName}PageSize`; const selectSortBy = `select${uCaseName}SortBy`; const selectSortAsc = `select${uCaseName}SortAsc`; @@ -274,19 +208,6 @@ const createRestBundle = (opts) => { }, }); - const isAllowed = store[selectIsAllowedRole](); - const isDisallowed = store[selectIsDisallowedRole](); - if (!isAllowed || isDisallowed) { - dispatch({ - type: actions.FETCH_ABORT, - payload: { - _isLoading: false, - _abortReason: 'User is not allowed to run this query', - }, - }); - return; - } - const url = decorateUrlWithItem(store[selectGetUrl](), item); let fetchCount = store[selectFetchCount](); const isStale = store[selectIsStale](); @@ -738,25 +659,9 @@ const createRestBundle = (opts) => { ), [selectAllowRoles]: (state) => state[config.name]._allowRoles, - - [selectIsAllowedRole]: createSelector( - selectAllowRoles, - 'selectAuthRoles', - checkRoles - ), - [selectDisallowRoles]: (state) => state[config.name]._disallowRoles, - - [selectIsDisallowedRole]: createSelector( - selectDisallowRoles, - 'selectAuthRoles', - checkRoles - ), - [selectSortBy]: (state) => state[config.name]._sortBy, - [selectSortAsc]: (state) => state[config.name]._sortAsc, - [reactShouldFetch]: (state) => { if (state[config.name]._shouldFetch) return { actionCreator: doFetch }; }, diff --git a/src/app-bundles/domains-bundle.js b/src/app-bundles/domains-bundle.js index 8d8a876e..1ac45078 100644 --- a/src/app-bundles/domains-bundle.js +++ b/src/app-bundles/domains-bundle.js @@ -12,8 +12,8 @@ export default createRestBundle({ putTemplate: '/domains/{:item.id}', postTemplate: '/domains', deleteTemplate: '/domains/{:item.id}', - fetchActions: ['URL_UPDATED', 'AUTH_LOGGED_IN'], - forceFetchActions: ['EXPLOREMAP_INITIALIZE_START'], + fetchActions: ['URL_UPDATED'], + forceFetchActions: ['@@INIT'], addons: { selectDomainsItemsByGroup: createSelector('selectDomainsItems', (items) => { if (!items) return null; diff --git a/src/app-bundles/explore-data-bundle.js b/src/app-bundles/explore-data-bundle.js index 45fe8062..b4c49d29 100644 --- a/src/app-bundles/explore-data-bundle.js +++ b/src/app-bundles/explore-data-bundle.js @@ -1,5 +1,6 @@ import { createSelector } from 'redux-bundler'; +import { getToken } from '../userService.ts'; import { seriesStyles } from '../common/helpers/utils'; const exploreDataBundle = { @@ -65,14 +66,13 @@ const exploreDataBundle = { dispatch({ type: 'EXPLORE_DATA_LOADING', payload: true }); const apiRoot = store.selectApiRoot(); - const token = store.selectAuthTokenRaw(); await fetch(`${apiRoot}/explorer?before=${before}&after=${after}`, { method: 'POST', mode: 'cors', headers: { 'Content-Type': 'application/json', - 'Authorization': 'Bearer ' + token, + 'Authorization': 'Bearer ' + getToken(), }, body: JSON.stringify(instrumentIds), }) @@ -89,7 +89,7 @@ const exploreDataBundle = { mode: 'cors', headers: { 'Content-Type': 'application/json', - 'Authorization': 'Bearer ' + token, + 'Authorization': 'Bearer ' + getToken(), }, body: JSON.stringify(instrumentIds), }) diff --git a/src/app-bundles/home-data-bundle.js b/src/app-bundles/home-data-bundle.js index c90f0317..e931c961 100644 --- a/src/app-bundles/home-data-bundle.js +++ b/src/app-bundles/home-data-bundle.js @@ -9,7 +9,7 @@ export default createRestBundle({ persist: true, routeParam: null, getTemplate: '/home', - fetchActions: ['URL_UPDATED', 'AUTH_LOGGED_IN'], + fetchActions: ['URL_UPDATED'], forceFetchActions: [], prefetch: store => store.selectRelativePathname() === '/', addons: { @@ -26,7 +26,7 @@ export default createRestBundle({ }), selectHashStripQuery: createSelector('selectHash', hash => { - const queryRegex = /\?.*/g; + const queryRegex = /[?&].*/g; return hash ? hash.replace(queryRegex, '') : null; }), diff --git a/src/app-bundles/inclinometer-measurements.js b/src/app-bundles/inclinometer-measurements.js index 72e95b01..9dbf3fe3 100644 --- a/src/app-bundles/inclinometer-measurements.js +++ b/src/app-bundles/inclinometer-measurements.js @@ -23,7 +23,7 @@ export default createRestBundle({ ], mergeItems: true, prefetch: (store) => { - const hash = store.selectHash(); + const hash = store.selectHashStripQuery(); const pathname = store.selectRelativePathname(); const whitelist = []; diff --git a/src/app-bundles/index.js b/src/app-bundles/index.js index 4891c014..5fa934b2 100644 --- a/src/app-bundles/index.js +++ b/src/app-bundles/index.js @@ -4,7 +4,6 @@ import { createUrlBundle, } from 'redux-bundler'; // Required change from @corpsmap/create-auth-bundle; -import createAuthBundle from './create-auth-bundle'; // Required change from @corpsmap/create-jwt-api-bundle; import createJwtApiBundle from './create-jwt-api-bundle'; import createUrlBasePathBundle from './create-url-base-path-bundle'; @@ -58,6 +57,7 @@ import projectionBundle from './projection-bundle'; import projectsBundle from './projects-bundle'; import qualityControl from './quality-control'; import rainfallBundle from './rainfall-bundle'; +import reportConfigurationsBundle from './report-configurations-bundle'; import routesBundle from './routes-bundle'; import submittalsBundle from './submittals-bundle'; import timeseriesBundle from './time-series-bundle'; @@ -65,43 +65,35 @@ import timeseriesMeasurementBundle from './time-series-measurements-bundle'; import uploadBundle from './upload-bundle'; import usersBundle from './users-bundle'; -// Mock Token User -// const mockTokenPublic = -// 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIwIiwibmFtZSI6IlVzZXIuVGVzdCIsImlhdCI6MTUxNjIzOTAyMiwiZXhwIjoyMDAwMDAwMDAwLCJyb2xlcyI6W119._N-sAWgMhYsWhwIf44_SGSMGSgnnM8tntlswsBqjYDo'; -const mockTokenApplicationAdmin = - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIyIiwibmFtZSI6IlVzZXIuQXBwbGljYXRpb25BZG1pbiIsImlhdCI6MTUxNjIzOTAyMiwiZXhwIjoyMDAwMDAwMDAwLCJyb2xlcyI6W119.aKaDNBnuhQyXI6zvzn-dAg8SxJSP3mQEx5FTSmJbYog'; -// const mockTokenProjectAdmin = -// 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIzIiwibmFtZSI6IlVzZXIuUHJvamVjdEFkbWluIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjIwMDAwMDAwMDAsInJvbGVzIjpbXX0.P2Cb6s3Kq0hHsfXEczFcUvpQuR8TTV88U4RDvcPabMM'; -// const mockTokenProjectMember = -// 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI0IiwibmFtZSI6IlVzZXIuUHJvamVjdE1lbWJlciIsImlhdCI6MTUxNjIzOTAyMiwiZXhwIjoyMDAwMDAwMDAwLCJyb2xlcyI6W119.ujBvw9bCksuSbXGJreIpdXZcVIHtb8GhgviBTvrO9AQ'; - -const jwtPaths = [ - '/members', - '/datalogger', +const inclusionPaths = [ + '/instruments', + '/instrument_groups', + '/collection_groups', + '/domains', + '/timeseries', ]; +const exactPaths = [ + '/home', + '/projects', + '/districts', +] + export default composeBundles( - createAuthBundle({ - appId: '07f1223f-f208-4b71-aa43-5d5f27cd8ed9', - redirectOnLogout: '/', - mock: import.meta.env.DEV ? true : false, - token: import.meta.env.DEV ? mockTokenApplicationAdmin : null, - }), createJwtApiBundle({ root: import.meta.env.DEV ? 'http://localhost:8080' : import.meta.env.VITE_API_URL, - tokenSelector: 'selectAuthTokenRaw', unless: { - // GET requests do not include token unless path starts with /my_ or includes any path in the jwtPaths array. - // Need token to figure out who "me" is + // Example Scenario: + // + // GET requests do not include token unless path is in the nonTokenPaths array. custom: ({ method, path }) => { if (method === 'GET') { - if (path.slice(0, 4) === '/my_' || jwtPaths.some(el => path.includes(el))) { - return false; + if (inclusionPaths.some(el => path.includes(el)) || exactPaths.some(el => path === el)) { + return true; } - return true; } return false; }, @@ -160,6 +152,7 @@ export default composeBundles( projectsBundle, qualityControl, rainfallBundle, + reportConfigurationsBundle, routesBundle, submittalsBundle, timeseriesBundle, diff --git a/src/app-bundles/instrument-bundle.js b/src/app-bundles/instrument-bundle.js index 525cdec5..155d71d2 100644 --- a/src/app-bundles/instrument-bundle.js +++ b/src/app-bundles/instrument-bundle.js @@ -30,7 +30,7 @@ export default createRestBundle({ ], urlParamSelectors: ['selectProjectsIdByRoute'], prefetch: (store) => { - const hash = store.selectHash(); + const hash = store.selectHashStripQuery(); const pathname = store.selectRelativePathname(); const whiteList = [ 'dashboard', @@ -153,10 +153,19 @@ export default createRestBundle({ properties: { ...rest }, }; - const inStatusFilter = statusFilters.length ? statusFilters.includes(status) : true; + const inStatusFilter = () => { + if (statusFilters.length) { + if (statusFilters.includes('inactive')) { + return [...statusFilters, 'lost', 'destroyed', 'abandoned'].includes(status); + + } else { + return statusFilters.includes(status); + } + } else return true; + }; const inTypeFilter = typeFilters.length ? typeFilters.includes(type_id) : true; - return (inStatusFilter && inTypeFilter) + return (inStatusFilter() && inTypeFilter) ? feature : null; }).filter(e => e), diff --git a/src/app-bundles/instrument-group-bundle.js b/src/app-bundles/instrument-group-bundle.js index 15b7d132..cc5e0008 100644 --- a/src/app-bundles/instrument-group-bundle.js +++ b/src/app-bundles/instrument-group-bundle.js @@ -19,7 +19,7 @@ export default createRestBundle({ ], urlParamSelectors: ['selectProjectsIdByRoute'], prefetch: (store) => { - const hash = store.selectHash(); + const hash = store.selectHashStripQuery(); const pathname = store.selectRelativePathname(); const whiteList = ['dashboard', 'explorer']; diff --git a/src/app-bundles/instrument-sensors-bundle.js b/src/app-bundles/instrument-sensors-bundle.js index 3efed25a..7e397648 100644 --- a/src/app-bundles/instrument-sensors-bundle.js +++ b/src/app-bundles/instrument-sensors-bundle.js @@ -31,11 +31,12 @@ export default { selectInstrumentSensorsMeasurements: (state) => state.instrumentSensors.measurements, selectInstrumentSensorsLastFetched: (state) => state.instrumentSensors._lastFetched, - doFetchInstrumentSensorsById: (type) => ({ dispatch, store, apiGet }) => { + doFetchInstrumentSensorsById: (type, id = null) => ({ dispatch, store, apiGet }) => { dispatch({ type: 'INSTRUMENT_SENSORS_BY_ID_FETCH_START' }); - const { instrumentId } = store.selectInstrumentsIdByRoute(); + const { instrumentId } = store.selectInstrumentsIdByRoute() || {}; + const uriId = id || instrumentId; - const url = `/instruments/${type}/${instrumentId}/segments`; + const url = `/instruments/${type}/${uriId}/segments`; apiGet(url, (err, body) => { if (err) { @@ -52,11 +53,13 @@ export default { }); }, - doUpdateInstrumentSensor: (type, formData) => ({ dispatch, store, apiPut }) => { + doUpdateInstrumentSensor: (type, formData, id = null) => ({ dispatch, store, apiPut }) => { dispatch({ type: 'INSTRUMENT_SENSOR_UPDATE_START' }); - const { instrumentId } = store.selectInstrumentsIdByRoute(); - const url = `/instruments/${type}/${instrumentId}/segments`; + const { instrumentId } = store.selectInstrumentsIdByRoute() || {}; + const uriId = id || instrumentId; + + const url = `/instruments/${type}/${uriId}/segments`; apiPut(url, formData, (err, _body) => { if (err) { @@ -68,10 +71,12 @@ export default { }); }, - doFetchInstrumentSensorMeasurements: (type, before, after) => ({ dispatch, store, apiGet }) => { + doFetchInstrumentSensorMeasurements: (type, before, after, id = null) => ({ dispatch, store, apiGet }) => { dispatch({ type: 'SENSOR_MEASUREMENTS_FETCH_START' }); - const { instrumentId } = store.selectInstrumentsIdByRoute(); - const url = `/instruments/${type}/${instrumentId}/measurements?before=${before}&after=${after}`; + const { instrumentId } = store.selectInstrumentsIdByRoute() || {}; + const uriId = id || instrumentId; + + const url = `/instruments/${type}/${uriId}/measurements?before=${before}&after=${after}`; apiGet(url, (err, body) => { if (err) { diff --git a/src/app-bundles/profile-bundle.js b/src/app-bundles/profile-bundle.js index 06f12ca7..1415cf63 100644 --- a/src/app-bundles/profile-bundle.js +++ b/src/app-bundles/profile-bundle.js @@ -1,3 +1,4 @@ +import { isLoggedIn } from '../userService.ts'; import createRestBundle from './create-rest-bundle'; import { createSelector } from 'redux-bundler'; @@ -12,8 +13,8 @@ export default createRestBundle({ putTemplate: '/profiles/{:item.slug}', postTemplate: '/profiles', deleteTemplate: '', - fetchActions: ['AUTH_LOGGED_IN'], - forceFetchActions: ['PROFILE_SAVE_FINISHED'], + fetchActions: ['URL_UPDATED'], + forceFetchActions: ['PROFILE_SAVE_FINISHED', '@@INIT'], reduceFurther: (state, { type, payload }) => { switch (type) { case 'PROFILE_REMOVE_PROFILE': @@ -72,32 +73,15 @@ export default createRestBundle({ }, {}); }), reactProfileExists: createSelector( - 'selectAuthIsLoggedIn', - 'selectRelativePathname', - 'selectProfileIsLoading', + 'selectProfileFlags', 'selectProfileActive', - (isLoggedIn, path, profileIsLoading, profile) => { - if (isLoggedIn && !profileIsLoading) { - if (!profile) { - if (path !== '/signup') - return { - actionCreator: 'doUpdateRelativeUrl', - args: ['/signup'], - }; - } - } - } - ), - reactProfileCreatedRedirect: createSelector( - 'selectProfileActive', - 'selectAuthIsLoggedIn', - 'selectRelativePathname', - (profile, isLoggedIn, path) => { - if (path === '/signup' && (profile || !isLoggedIn)) + (flags, profile) => { + if (isLoggedIn() && !flags._isLoading && !profile) { return { - actionCreator: 'doUpdateRelativeUrl', - args: ['/'], + actionCreator: 'doProfileSave', + args: [{}], }; + } } ), }, diff --git a/src/app-bundles/project-members-bundle.js b/src/app-bundles/project-members-bundle.js index dc3535da..4bb52a07 100644 --- a/src/app-bundles/project-members-bundle.js +++ b/src/app-bundles/project-members-bundle.js @@ -15,7 +15,7 @@ export default createRestBundle({ 'PROJECTMEMBERS_DELETE_USER_FINISHED', ], prefetch: (store) => { - const hash = store.selectHash(); + const hash = store.selectHashStripQuery(); const whiteList = ['admin']; return whiteList.includes(hash); diff --git a/src/app-bundles/projects-bundle.js b/src/app-bundles/projects-bundle.js index 12babdf8..98248236 100644 --- a/src/app-bundles/projects-bundle.js +++ b/src/app-bundles/projects-bundle.js @@ -12,7 +12,7 @@ export default createRestBundle({ putTemplate: '/projects/{:item.id}', postTemplate: '/projects', deleteTemplate: '/projects/{:item.id}', - fetchActions: ['URL_UPDATED', 'AUTH_LOGGED_IN'], + fetchActions: ['URL_UPDATED'], forceFetchActions: ['PROJECTS_SAVE_FINISHED', 'PROJECTS_DELETE_FINISHED'], reduceFurther: (state, { type, payload }) => { switch (type) { diff --git a/src/app-bundles/rainfall-bundle.js b/src/app-bundles/rainfall-bundle.js index ec27b2c2..cda09021 100644 --- a/src/app-bundles/rainfall-bundle.js +++ b/src/app-bundles/rainfall-bundle.js @@ -59,7 +59,7 @@ const rainfallBundle = { x: [], y: [], xaxis: 'x', - yaxis: 'y2', + yaxis: 'y3', line: { color: '#0062ff', }, diff --git a/src/app-bundles/report-configurations-bundle.js b/src/app-bundles/report-configurations-bundle.js new file mode 100644 index 00000000..f5d6bcb2 --- /dev/null +++ b/src/app-bundles/report-configurations-bundle.js @@ -0,0 +1,95 @@ +import { toast } from 'react-toastify'; + +import { tUpdateError, tUpdateSuccess } from '../common/helpers/toast-helpers'; + +export default { + name: 'reportConfigurations', + getReducer: () => { + const initialState = { + projectConfigs: [], + }; + + return (state = initialState, { type, payload }) => { + switch (type) { + case 'UPDATE_REPORT_CONFIGURATIONS': + return { + ...state, + projectConfigs: payload, + }; + default: + return state; + } + }; + }, + + selectReportConfigurationsRaw: state => state.reportConfigurations, + selectProjectReportConfigurations: state => state.reportConfigurations.projectConfigs, + selectProjectReportConfigurationsDownloads: state => state.reportConfigurations.downloads, + + doFetchReportConfigurationsByProjectId: () => ({ dispatch, store, apiGet }) => { + const projectId = store.selectProjectsIdByRoute()?.projectId; + const uri = `/projects/${projectId}/report_configs`; + + apiGet(uri, (err, body) => { + if (err) { + // eslint-disable-next-line no-console + console.log('todo', err); + } else { + dispatch({ + type: 'UPDATE_REPORT_CONFIGURATIONS', + payload: body, + }); + } + }); + }, + + doCreateNewReportConfiguration: (data) => ({ store, apiPost }) => { + const toastId = toast.loading('Creating Report Configuration...'); + const projectId = store.selectProjectsIdByRoute()?.projectId; + const uri = `/projects/${projectId}/report_configs`; + + const body = { + ...data, + project_id: projectId, + }; + + apiPost(uri, body, (err, _body) => { + if (err) { + tUpdateError(toastId, "Failed to create Report Configuration"); + } else { + tUpdateSuccess(toastId, "Successfully created Report Configuration"); + store.doFetchReportConfigurationsByProjectId(); + } + }); + }, + + doUpdateReportConfiguration: (data) => ({ store, apiPut }) => { + const toastId = toast.loading('Updating Report Configuration...'); + const projectId = store.selectProjectsIdByRoute()?.projectId; + const uri = `/projects/${projectId}/report_configs/${data?.id}`; + + apiPut(uri, data, (err, _body) => { + if (err) { + tUpdateError(toastId, "Failed to update Report Configuration"); + } else { + tUpdateSuccess(toastId, "Successfully updated Report Configuration"); + store.doFetchReportConfigurationsByProjectId(); + } + }); + }, + + doDeleteReportConfiguration: ({ reportConfigurationId }) => ({ store, apiDelete }) => { + const projectId = store.selectProjectsIdByRoute()?.projectId; + + const uri = `/projects/${projectId}/report_configs/${reportConfigurationId}`; + + apiDelete(uri, (err, _body) => { + if (err) { + // eslint-disable-next-line no-console + console.log('todo', err); + } else { + store.doFetchReportConfigurationsByProjectId(); + } + }); + }, +}; diff --git a/src/app-bundles/routes-bundle.js b/src/app-bundles/routes-bundle.js index b305c482..25b41992 100644 --- a/src/app-bundles/routes-bundle.js +++ b/src/app-bundles/routes-bundle.js @@ -6,11 +6,9 @@ import InstrumentGroup from '../app-pages/instrument-group'; import Help from '../app-pages/help/help'; import Home from '../app-pages/home/home'; import Instrument from '../app-pages/instrument/details'; -import Logout from '../app-pages/logout'; import NotFound from '../app-pages/404'; import Profile from '../app-pages/profile/userProfile'; import Project from '../app-pages/project'; -import SignUp from '../app-pages/signup/signup'; const base = import.meta.env.VITE_URL_BASE_PATH ?? '' @@ -20,8 +18,6 @@ export default createRouteBundle( [`${base}/`]: Home, [`${base}/admin`]: AdminPage, [`${base}/help`]: Help, - [`${base}/logout`]: Logout, - [`${base}/signup`]: SignUp, [`${base}/profile`]: Profile, [`${base}/not-found`]: NotFound, [`${base}/:projectSlug`]: Project, diff --git a/src/app-bundles/time-series-measurements-bundle.js b/src/app-bundles/time-series-measurements-bundle.js index eea2c441..0ad16eef 100644 --- a/src/app-bundles/time-series-measurements-bundle.js +++ b/src/app-bundles/time-series-measurements-bundle.js @@ -27,7 +27,7 @@ export default createRestBundle({ ], mergeItems: true, prefetch: (store) => { - const hash = store.selectHash(); + const hash = store.selectHashStripQuery(); const pathname = store.selectRelativePathname(); const whitelist = []; @@ -44,8 +44,8 @@ export default createRestBundle({ dispatch({ type: 'TIMESERIES_FETCH_BY_ID_START', payload: {} }); const [after, before] = dateRange; - const isoAfter = after ? after.toISOString() : afterDate; - const isoBefore = before ? before.toISOString() : beforeDate; + const isoAfter = after ? new Date(after)?.toISOString() : afterDate; + const isoBefore = before ? new Date(before)?.toISOString() : beforeDate; const url = `/timeseries/${timeseriesId}/measurements?after=${isoAfter}&before=${isoBefore}&threshold=${threshold}`; const flags = store['selectTimeseriesMeasurementsFlags'](); diff --git a/src/app-bundles/upload-bundle.js b/src/app-bundles/upload-bundle.js index e50507f3..96291525 100644 --- a/src/app-bundles/upload-bundle.js +++ b/src/app-bundles/upload-bundle.js @@ -396,7 +396,7 @@ const uploadBundle = { : setAllTo[1]; } else { // If field not mapped, set to null; if required field, push error - const data = row[sourceKey] || fieldMap[key]; + const data = row[sourceKey]; if (!data) { parsedRow[key] = null; if (config.required) parsedRow.errors.push(key); diff --git a/src/app-components/chart/chart.jsx b/src/app-components/chart/chart.jsx index f80e0b7c..a2cbad0a 100644 --- a/src/app-components/chart/chart.jsx +++ b/src/app-components/chart/chart.jsx @@ -76,8 +76,6 @@ const Chart = ({ style={{ width: '100%', height: '100%' }} onInitialized={updateState} onUpdate={updateState} - // eslint-disable-next-line no-console - onClick={(e) => console.log(e)} /> ); diff --git a/src/app-components/chart/minify-plotly.js b/src/app-components/chart/minify-plotly.js index 4d88ab1e..436fe61a 100644 --- a/src/app-components/chart/minify-plotly.js +++ b/src/app-components/chart/minify-plotly.js @@ -3,6 +3,7 @@ import * as Bar from 'plotly.js/lib/bar'; import * as Pie from 'plotly.js/lib/pie'; import * as Surface from 'plotly.js/lib/surface'; import * as Scatter3D from 'plotly.js/lib/scatter3d'; +import * as Contour from 'plotly.js/lib/contour'; Plotly.register([ /* @@ -11,6 +12,7 @@ Plotly.register([ List of available imports can be found here `node_modules/plotly.js/lib/index.js` */ Bar, + Contour, Pie, Surface, Scatter3D, diff --git a/src/app-components/domain-select.jsx b/src/app-components/domain-select.jsx index 6af4b462..c04b2da6 100644 --- a/src/app-components/domain-select.jsx +++ b/src/app-components/domain-select.jsx @@ -10,13 +10,15 @@ export default connect( useLabelAsDefault = false, onChange, domain, + label = '', + ...customProps }) => { const options = domainsItemsByGroup[domain]?.map(item => ( { value: item.id, label: item.value } )) || []; return ( - <> +
{!options || !options.length ? ( No Options... ) : ( @@ -28,12 +30,12 @@ export default connect( const item = domainsItemsByGroup[domain]?.find(el => el.value === value?.label); onChange(item); }} - renderInput={(params) => } + renderInput={(params) => } options={options} fullWidth /> )} - +
); } ); diff --git a/src/app-components/hero/hero.jsx b/src/app-components/hero/hero.jsx index e2c231c2..36b6bfe1 100644 --- a/src/app-components/hero/hero.jsx +++ b/src/app-components/hero/hero.jsx @@ -4,6 +4,7 @@ import { Timeline } from '@mui/icons-material'; import bg1 from '../../img/bg-1.jpg'; import cwbiLogo from '../../img/cwbi-logo.png'; import ggmLogo from '../../img/GGM-Logo.png'; +import { isLoggedIn } from '../../userService.ts'; import './hero.scss'; @@ -16,11 +17,29 @@ const Hero = () => ( CWBI Logo window.open('https://cwbi.usace.army.mil/', '_blank')} /> + + UI Release Notes + + + API Release Notes +

@@ -30,6 +49,9 @@ const Hero = () => (

Monitoring Instrumentation Data Acquisition System

+ {!isLoggedIn() &&

+ We've migrated to login.gov!
Please sign in with an account associated with your government issued employee id (PIV/CAC). Your MIDAS profile will be linked upon login. +

}
( > IPM SubCOP + + Contact Support +
); diff --git a/src/app-components/hero/hero.scss b/src/app-components/hero/hero.scss index c5f6213b..2379690a 100644 --- a/src/app-components/hero/hero.scss +++ b/src/app-components/hero/hero.scss @@ -7,8 +7,9 @@ justify-content: space-between; .logo-left { - margin-top: 95px; - margin-left: 15px; + margin-top: 108px; + margin-left: 35px; + text-align: center; } .logo-right { @@ -16,4 +17,5 @@ margin-right: 25px; text-align: center; } -} \ No newline at end of file +} + diff --git a/src/app-components/navigation/navBar.jsx b/src/app-components/navigation/navBar.jsx index 6cd0e0b7..437ccc78 100644 --- a/src/app-components/navigation/navBar.jsx +++ b/src/app-components/navigation/navBar.jsx @@ -1,11 +1,14 @@ import React, { useState, useEffect } from 'react'; import { connect } from 'redux-bundler-react'; +import { AppBar, Box, Toolbar, Typography } from '@mui/material'; import { ChevronRight, Timeline } from '@mui/icons-material'; import DevBanner from './devBanner'; import NavItem from './navItem'; import ProfileMenu from './profileMenu'; -import { classArray } from '../../common/helpers/utils'; +import { useWindowSize } from 'react-use'; + +import { doLogin, isLoggedIn } from '../../userService.ts'; import './navigation.scss'; @@ -18,95 +21,88 @@ const customTheme = { theme: 'transparent', hideBrand: true, }, - '/signup': { - brand: 'Home', - }, }; const NavBar = connect( - 'doAuthLogin', - 'selectAuthIsLoggedIn', 'selectProjectsByRoute', 'selectRelativePathname', ({ - doAuthLogin, - authIsLoggedIn, projectsByRoute: project, relativePathname: pathname, }) => { const [hideBrand, setHideBrand] = useState(false); - const [brand, setBrand] = useState(null); + const [hideProject, setHideProject] = useState(false); + const [brand, setBrand] = useState('MIDAS'); const [theme, setTheme] = useState('primary'); + const { width } = useWindowSize(); - const showDevBanner = import.meta.env.VITE_DEVELOPMENT_BANNER === 'true'; - - const navClass = classArray([ - 'navbar', - 'navbar-expand-lg', - 'navbar-dark', - theme !== 'transparent' ? - showDevBanner ? 'fixed-top-banner' : 'fixed-top' - : showDevBanner ? 'transparent-nav-banner' : '', - `bg-${theme}`, - ]); + const isTransparent = theme === 'transparent'; + const showDevBanner = import.meta.env.VITE_DEVELOPMENT_BANNER === 'true'; useEffect(() => { const { hideBrand, brand, theme } = customTheme[pathname] || {}; setHideBrand(hideBrand); - setBrand(brand); + setBrand(brand || 'MIDAS'); setTheme(theme || 'primary'); }, [pathname]); + useEffect(() => { + if (width < 750) { + setHideProject(true); + } else { + setHideProject(false); + } + }); + return ( <> {showDevBanner && } - + + + + ); } diff --git a/src/app-components/navigation/navItem.jsx b/src/app-components/navigation/navItem.jsx index 79434cd6..385a6c45 100644 --- a/src/app-components/navigation/navItem.jsx +++ b/src/app-components/navigation/navItem.jsx @@ -9,6 +9,7 @@ const NavItem = connect( const cls = classArray([ 'pointer', 'nav-item', + 'd-inline-block', pathname.indexOf(href) !== -1 && href !== '/' && 'active', ]); @@ -19,11 +20,11 @@ const NavItem = connect( return !hidden ? handler ? (
  • - {children} + {children}
  • ) : (
  • - + {children}
  • diff --git a/src/app-components/navigation/navigation.scss b/src/app-components/navigation/navigation.scss index 51f9f956..f54faaff 100644 --- a/src/app-components/navigation/navigation.scss +++ b/src/app-components/navigation/navigation.scss @@ -28,10 +28,7 @@ margin-top: -10px; padding: 0 15px 5px 15px; position: relative; - - // > { - // flex: 1; - // } + max-width: 100vw; .secondary-nav-heading { border-bottom: 1px solid #d6d7d8; @@ -40,13 +37,13 @@ padding: 0 10px 4.5px; position: absolute; top: 0; - width: 100vw; + min-width: 100vw; + max-width: 100vw; z-index: -1; } .secondary-nav-page-content { left: 0px; - overflow: visible; padding: 10px; position: absolute; top: 30px; diff --git a/src/app-components/navigation/profileMenu.jsx b/src/app-components/navigation/profileMenu.jsx index f2a61bcc..f32801ad 100644 --- a/src/app-components/navigation/profileMenu.jsx +++ b/src/app-components/navigation/profileMenu.jsx @@ -1,46 +1,65 @@ -import React from 'react'; +import React, { useState } from 'react'; import { connect } from 'redux-bundler-react'; +import { Box, Divider, Menu, MenuItem, Tooltip } from '@mui/material'; -import Dropdown from '../dropdown'; import RoleFilter from '../role-filter'; - -const getInitials = (name = '') => { - let initials = ['U', 'N']; - let parts = name.split('.'); - if (parts[1] && parts[1][0]) initials[0] = parts[1][0]; - if (parts[0] && parts[0][0]) initials[1] = parts[0][0]; - return initials.join(''); -}; +import { doLogout, getInitials, getUsername } from '../../userService.ts'; const ProfileMenu = connect( - 'selectAuthTokenPayload', - ({ authTokenPayload: user }) => ( - { + const [anchorElNav, setAnchorElNav] = useState(null); + + const navigateTo = href => { + setAnchorElNav(null); + doUpdateRelativeUrl(href); + }; + + return ( + + + setAnchorElNav(anchorElNav ? null : e.currentTarget)} + > + {getInitials()} + + + setAnchorElNav(null)} > - {`${getInitials(user.name)}`} - - } - > - {/* ADMIN ONLY, NO Allowed Roles */} - - Site Administration -
    -
    - My Profile - - Logout - Currently logged in as {user.name} - - - ) + {/* SITE ADMIN ONLY, NO Allowed Roles */} + + navigateTo('/admin')}>Site Administration + + + {/* ALL Roles (Logged In) */} + navigateTo('/profile')}>My Profile + doLogout()} className='d-flex flex-column align-items-start'> + Logout + Currently logged in as {getUsername()} + +
    +
    + ); + }, ); export default ProfileMenu; diff --git a/src/app-components/navigation/secondaryNavBar.jsx b/src/app-components/navigation/secondaryNavBar.jsx index 563b811b..7cfc9e36 100644 --- a/src/app-components/navigation/secondaryNavBar.jsx +++ b/src/app-components/navigation/secondaryNavBar.jsx @@ -1,7 +1,25 @@ import React, { useEffect, useState } from 'react'; import { connect } from 'redux-bundler-react'; +import { Box, Tab, Tabs, tabsClasses } from '@mui/material'; -import TabContainer from '../tab/tabContainer'; +const CustomTabPanel = props => { + const { children, value, active, paddingTop } = props; + + return ( + + ); +} const SecondaryNavBar = connect( 'selectHashStripQuery', @@ -9,43 +27,49 @@ const SecondaryNavBar = connect( hashStripQuery, navLinks = [], }) => { - const getIndex = () => navLinks.findIndex(elem => elem.uri == `#${hashStripQuery}`); - - const defaultTab = hashStripQuery ? getIndex() : () => { + const defaultTab = hashStripQuery ? `#${hashStripQuery}` : () => { location.hash = navLinks[0].uri; - return 0; + return navLinks[0].uri; }; const [navTab, setNavTab] = useState(defaultTab); - const [forceUpdateIncrement, setForceUpdateIncrement] = useState(0); - const onTabChange = (_, index) => { - location.hash = navLinks[index].uri; - setNavTab(index); + const onTabChange = (_, newHash) => { + location.hash = newHash; + setNavTab(newHash); }; + // Handle External Hash Manipulation (eg: navbar) useEffect(() => { - if (navLinks[navTab]) { - if (`#${hashStripQuery}` !== navLinks[navTab].uri) { - setForceUpdateIncrement(forceUpdateIncrement + 1); - } - } else { - location.assign('/not-found'); + if (navTab !== `#${hashStripQuery}`) { + setNavTab(`#${hashStripQuery}`); } - }, [navLinks, hashStripQuery, navTab, setForceUpdateIncrement]); + }, [navTab, hashStripQuery, setNavTab]); return ( -
    -
    - -
    + <> + + + {navLinks.map(link => ( + + ))} + + + {navLinks.map(link => ( + + {link.content} + + ))} + ); } ); diff --git a/src/app-components/pageContent.jsx b/src/app-components/pageContent.jsx index d74c32a9..04bf6676 100644 --- a/src/app-components/pageContent.jsx +++ b/src/app-components/pageContent.jsx @@ -4,6 +4,27 @@ import { connect } from 'redux-bundler-react'; import { classArray } from '../common/helpers/utils'; const hasDevBanner = import.meta.env.VITE_DEVELOPMENT_BANNER === 'true'; +// const blacklist = ['/', '/help']; + +// const PageContent = connect( +// 'selectRelativePathname', +// ({ +// relativePathname: pathname, +// children, +// }) => { +// const pageClasses = classArray([ +// 'page-margin', +// hasDevBanner && 'banner', +// ]); + +// return ( +//
    +// {children} +//
    +// ); +// } +// ); + const blacklist = ['/', '/help']; const PageContent = connect( diff --git a/src/app-components/profile-form.jsx b/src/app-components/profile-form.jsx deleted file mode 100644 index c818607b..00000000 --- a/src/app-components/profile-form.jsx +++ /dev/null @@ -1,66 +0,0 @@ -import React, { - useState, - forwardRef, - useImperativeHandle, - useEffect, -} from 'react'; - -export default forwardRef(({ item, onSave }, ref) => { - const [username, setUsername] = useState((item && item.userName) || ''); - const [email, setEmail] = useState((item && item.email) || ''); - const [errors, setErrors] = useState(null); - - useEffect(() => { - if (!username || !email) { - setErrors('Username and Email are requred'); - } else { - setErrors(null); - } - }, [username, email, setErrors]); - - const handleSave = () => { - if (!errors) { - onSave({ - id: (item && item.id) || null, - username, - email, - }); - } - }; - - useImperativeHandle(ref, () => ({ - save: () => { - handleSave(); - }, - })); - - return ( -
    -
    - - { - setUsername(e.target.value); - }} - className='form-control' - type='text' - placeholder='Text input' - /> -
    -
    - - { - setEmail(e.target.value); - }} - className='form-control' - type='text' - placeholder='Text input' - /> -
    - {errors} -
    - ); -}); diff --git a/src/app-components/tab/tab.scss b/src/app-components/tab/tab.scss index ba12b57b..c6fc80f5 100644 --- a/src/app-components/tab/tab.scss +++ b/src/app-components/tab/tab.scss @@ -1,4 +1,7 @@ .nav-item { + display: table-cell; + min-width: fit-content; + white-space: nowrap; user-select: none; &.active { @@ -12,7 +15,9 @@ } .nav-tabs { + display: table-row; border-bottom: 1px solid #c8cacb !important; + overflow: hidden !important; .nav-link.active { border-color: #c8cacb #c8cacb #fff !important; diff --git a/src/app-components/tab/tabContainer.jsx b/src/app-components/tab/tabContainer.jsx index 517df11e..f0a1cf83 100644 --- a/src/app-components/tab/tabContainer.jsx +++ b/src/app-components/tab/tabContainer.jsx @@ -1,6 +1,8 @@ -import React, { useEffect, useState } from 'react'; +import React, { createRef, useEffect, useState } from 'react'; +import { useDeepCompareEffect } from 'react-use'; import TabItem from './tabItem'; +import useWindowDimensions from '../../customHooks/useWindowDimensions'; import './tab.scss'; @@ -24,8 +26,11 @@ const TabContainer = ({ changeTabDelay = 0, ...customProps }) => { + const ref = createRef(); + const [displayedTabs, setDisplayedTabs] = useState(tabs); const [tabIndex, setTabIndex] = useState(defaultTab); const [isDisabled, setIsDisabled] = useState(false); + const { width: windowWidth } = useWindowDimensions(); const changeTab = (title, index) => { onTabChange(title, index); @@ -39,10 +44,31 @@ const TabContainer = ({ } }, [isDisabled, setIsDisabled]); + useDeepCompareEffect(() => { + const diff = tabs.length - displayedTabs.length; + + if (ref.current.clientWidth > windowWidth) { + const clone = [...tabs]; + + clone.splice((tabs.length - diff) - 2, diff + 2); + setDisplayedTabs(clone); + } else { + if (diff && ((ref.current.clientWidth + 165) < windowWidth)) { + const clone = [...tabs]; + + if (diff !== 1) { + clone.splice(tabs.length - (diff - 1), diff - 1); + } + + setDisplayedTabs(clone); + } + } + }, [ref, windowWidth, tabs, displayedTabs]); + return ( -
    -
      - {tabs.map((t, i) => ( +
      +
        + {displayedTabs.map((t, i) => ( column.header, - cell: val => column.render ? column.render(val?.row?.original) : val.getValue(), + cell: val => column.render ? column.render(val?.row?.original, val.getValue()) : val.getValue(), enableSorting: !!column.isSortable, enableColumnFilter: !!column.isFilterable, }) diff --git a/src/app-pages/explorer/map-legend.jsx b/src/app-pages/explorer/map-legend.jsx index 9756896b..d71116bf 100644 --- a/src/app-pages/explorer/map-legend.jsx +++ b/src/app-pages/explorer/map-legend.jsx @@ -7,7 +7,13 @@ import { IconButton } from '@mui/material'; import Card from '../../app-components/card'; import { classArray } from '../../common/helpers/utils'; -const STATUS_ORDER = ['Active', 'Lost', 'Inactive', 'Abandoned', 'Destroyed']; +const STATUS_ORDER = [ + 'Active', + // 'Lost', + 'Inactive', + // 'Abandoned', + // 'Destroyed', +]; const getLegendItemClasses = (allItems = [], currentItem) => { const isEmpty = allItems.length === 0; @@ -57,7 +63,7 @@ export default connect( value: s.value, order: STATUS_ORDER.indexOf(title), }; - }).sort((a, b) => (a.order > b.order ? 1 : -1)); + }).filter(el => el.order !== -1).sort((a, b) => (a.order > b.order ? 1 : -1)); const currentTypes = instrument_type.filter(type => instruments.some(i => i.type_id === type.id)); diff --git a/src/app-pages/help/apiHelp.jsx b/src/app-pages/help/apiHelp.jsx index 5b83cc77..60a04a8c 100644 --- a/src/app-pages/help/apiHelp.jsx +++ b/src/app-pages/help/apiHelp.jsx @@ -18,8 +18,8 @@ const ApiHelp = () => (


        -

        Click the link below to view the Swagger Documentation for the API:

        - {apiURL}/swagger/index.html +

        Click the link below to view the Documentation for the API:

        + {apiURL}/docs
        diff --git a/src/app-pages/home/project-list.jsx b/src/app-pages/home/project-list.jsx index 87c02b85..ab921620 100644 --- a/src/app-pages/home/project-list.jsx +++ b/src/app-pages/home/project-list.jsx @@ -80,50 +80,48 @@ export default connect( const [inputString, setInputString] = useState(''); const columnHelper = createColumnHelper(); - const columns = useMemo( - () => [ - columnHelper.accessor('districtId', { - header: 'District', - id: 'districtId', - enableColumnFilter: false, - sortingFn: (a, b) => sortByDistrictName(a, b, districts), - cell: (info) => ( - <> - {info.row.getCanExpand() && ( - - {info.row.getIsExpanded() ? ( - - ) : ( - - )} - - )} - {mapDistrictName(info.getValue(), info.row.subRows.length, districts)} - - ), - }), - columnHelper.accessor('title', { - header: 'Project Name', - id: 'title', - enableColumnFilter: false, - cell: (info) => ( - {info.getValue()} - ), - }), - columnHelper.accessor('instrumentCount', { - header: 'Instrument Count', - id: 'instrumentCount', - enableColumnFilter: false, - enableSorting: false, - }), - columnHelper.accessor('instrumentGroupCount', { - header: 'Instrument Group Count', - id: 'instrumentGroupCount', - enableColumnFilter: false, - enableSorting: false, - }), - ], - [districts]); + const columns = useMemo(() => [ + columnHelper.accessor('districtId', { + header: 'District', + id: 'districtId', + enableColumnFilter: false, + sortingFn: (a, b) => sortByDistrictName(a, b, districts), + cell: (info) => ( + <> + {info.row.getCanExpand() && ( + + {info.row.getIsExpanded() ? ( + + ) : ( + + )} + + )} + {mapDistrictName(info.getValue(), info.row.subRows.length, districts)} + + ), + }), + columnHelper.accessor('title', { + header: 'Project Name', + id: 'title', + enableColumnFilter: false, + cell: (info) => ( + {info.getValue()} + ), + }), + columnHelper.accessor('instrumentCount', { + header: 'Instrument Count', + id: 'instrumentCount', + enableColumnFilter: false, + enableSorting: false, + }), + columnHelper.accessor('instrumentGroupCount', { + header: 'Instrument Group Count', + id: 'instrumentGroupCount', + enableColumnFilter: false, + enableSorting: false, + }), + ], [districts]); const onChange = selected => { const { value } = selected; diff --git a/src/app-pages/instrument-group/instrument-group-form.jsx b/src/app-pages/instrument-group/instrument-group-form.jsx index b5d5f92f..c9c3980f 100644 --- a/src/app-pages/instrument-group/instrument-group-form.jsx +++ b/src/app-pages/instrument-group/instrument-group-form.jsx @@ -79,7 +79,7 @@ export default connect( saveIsSubmit customClosingLogic onCancel={() => doModalClose()} - onDelete={handleDelete} + onDelete={item && item.id ? handleDelete : null} />
      diff --git a/src/app-pages/instrument/cwms-timeseries/cwmsTimeseries.jsx b/src/app-pages/instrument/cwms-timeseries/cwmsTimeseries.jsx new file mode 100644 index 00000000..0080cfc8 --- /dev/null +++ b/src/app-pages/instrument/cwms-timeseries/cwmsTimeseries.jsx @@ -0,0 +1,156 @@ +import React, { useRef, useState } from 'react'; +import ReactDatePicker from 'react-datepicker'; +import { AgGridReact } from '@ag-grid-community/react'; +import { Button } from '@mui/material'; +import { ClientSideRowModelModule } from '@ag-grid-community/client-side-row-model'; +import { connect } from 'redux-bundler-react'; +import { DateTime } from 'luxon'; +import { endOfDay, startOfDay, subDays } from 'date-fns'; +import { Icon } from '@iconify/react'; + +import CwmsTimeseriesListItem from './cwmsTimeseriesListItem.jsx'; +import NewCwmsTimeseriesModal from './newCwmsTimeseries.jsx'; +import { useGetMidasCwmsTimeseries, useGetCwmsTimeseriesMeasurements } from '../../../app-services/collections/cwms-timeseries.ts'; + +const CwmsTimeseries = connect( + 'doModalOpen', + 'selectProjectsIdByRoute', + 'selectInstrumentsByRoute', + ({ + doModalOpen, + projectsIdByRoute: project, + instrumentsByRoute: instrument, + }) => { + const grid = useRef(null); + + const { projectId } = project || {}; + const { id } = instrument || {}; + + const [activeTimeseries, setActiveTimeseries] = useState(null); + const [dateRange, setDateRange] = useState([subDays(startOfDay(new Date()), 1), endOfDay(new Date())]); + + const { data: midasTimeseries } = useGetMidasCwmsTimeseries({ projectId, instrumentId: id }); + const { data: cwmsMeasurements, isLoading } = useGetCwmsTimeseriesMeasurements( + { + name: activeTimeseries?.cwms_timeseries_id, + office: activeTimeseries?.cwms_office_id, + begin: DateTime.fromJSDate(dateRange[0]).toISO(), + end: DateTime.fromJSDate(dateRange[1]).toISO(), + }, + { enabled: !!activeTimeseries?.cwms_timeseries_id }, + ); + + const { values = [] } = cwmsMeasurements || {}; + + const data = values.map(el => { + if (el?.length < 2) return null; + + return { + timestamp: DateTime.fromMillis(el[0]).toFormat('D HH:mm:ss'), + value: el[1], + }; + }).filter(e => e); + + return ( + <> +
      +
      + CWMS Timeseries are timeseries from the external source, Corps Water Management System (CWMS), that are manually associated to instruments within the MIDAS + system. Use this panel to manage the connected timeseries and view the data associated with them. While connections can be made, the data it provides is strictly read-only. +
      +
      +
      +
      + +
      +
      + {!!activeTimeseries?.id && ( +
      +
      + setDateRange(prev => [startOfDay(date), prev[1]])} + /> +
      +
      +  -  +
      +
      + setDateRange(prev => [prev[0], endOfDay(date)])} + /> +
      +
      + )} +
      +
      +
      +
      +
        + {midasTimeseries?.length ? ( + <> + {midasTimeseries.map(t => ( + setActiveTimeseries(activeTimeseries?.id === t.id ? null : item)} + /> + ))} + + ) : ( + No Timeseries Configured + )} +
      +
      +
      + {isLoading ? ( +
      + +
      + ) : ( +
      + +
      + )} +
      +
      + + ); + }, +); + +export default CwmsTimeseries; \ No newline at end of file diff --git a/src/app-pages/instrument/cwms-timeseries/cwmsTimeseriesListItem.jsx b/src/app-pages/instrument/cwms-timeseries/cwmsTimeseriesListItem.jsx new file mode 100644 index 00000000..6a5bde47 --- /dev/null +++ b/src/app-pages/instrument/cwms-timeseries/cwmsTimeseriesListItem.jsx @@ -0,0 +1,48 @@ +import React, { useRef } from 'react'; +import { connect } from 'redux-bundler-react'; +import { Edit } from '@mui/icons-material'; + +import RoleFilter from '../../../app-components/role-filter'; +import { classnames } from '../../../common/helpers/utils'; +import NewCwmsTimeseriesModal from './newCwmsTimeseries'; + +export default connect( + 'selectProjectsByRoute', + 'doModalOpen', + ({ projectsByRoute: project, doModalOpen, item, onClick, active }) => { + const li = useRef(null); + const itemClass = classnames({ + 'pointer': true, + 'list-group-item': true, + active: active, + }); + + return ( + item && ( +
    • { + if (e.currentTarget === li.current) onClick(item); + }} + > + + + +
      {item.name}
      +
      + {`${item.parameter} in ${item.unit}`} +
      +
    • + ) + ); + } +); diff --git a/src/app-pages/instrument/cwms-timeseries/newCwmsTimeseries.jsx b/src/app-pages/instrument/cwms-timeseries/newCwmsTimeseries.jsx new file mode 100644 index 00000000..1c969791 --- /dev/null +++ b/src/app-pages/instrument/cwms-timeseries/newCwmsTimeseries.jsx @@ -0,0 +1,152 @@ +import React, { useMemo, useState } from 'react'; +import { Autocomplete, TextField } from '@mui/material'; +import { connect } from 'redux-bundler-react'; +import { useQueryClient } from '@tanstack/react-query'; + +import * as Modal from '../../../app-components/modal'; +import DomainSelect from '../../../app-components/domain-select.jsx'; +import { useGetCwmsOffices, useGetCwmsTimeseries, usePostMidasCwmsTimeseries, usePutMidasCwmsTimeseries } from '../../../app-services/collections/cwms-timeseries.ts'; + +const generateOfficesOptions = cwmsOffices => { + if (!cwmsOffices?.length) return []; + + return cwmsOffices.map(office => ({ + label: `${office[`long-name`]} (${office?.name})`, + value: office?.name, + })); +}; + +const generateCwmsTimeseriesOptions = cwmsTimeseries => { + const { total, entries } = cwmsTimeseries || {}; + + if (!total || !entries) return []; + + return entries.map(entry => ({ + _extents: entry?.extents?.length ? entry.extents[0] : {}, + label: entry?.name, + value: entry?.name, + })); +}; + +const NewCwmsTimeseriesModal = connect( + 'doModalClose', + 'doInstrumentTimeseriesDelete', + ({ + doModalClose, + doInstrumentTimeseriesDelete, + projectId, + instrumentId, + item, + isEdit, + }) => { + const client = useQueryClient(); + const midasCwmsTimeseriesCreator = usePostMidasCwmsTimeseries(client); + const midasCwmsTimeseriesUpdater = usePutMidasCwmsTimeseries(client); + + const [selectedOffice, setSelectedOffice] = useState(isEdit ? item?.cwms_office_id : ''); + const [selectedTimeseries, setSelectedTimeseries] = useState(isEdit ? item?.cwms_timeseries_id : ''); + const [name, setName] = useState(isEdit ? item?.name : ''); + const [parameterId, setParameterId] = useState(isEdit ? item?.parameter_id : ''); + const [unitId, setUnitId] = useState(isEdit ? item?.unit_id : ''); + + const { data: cwmsOffices } = useGetCwmsOffices({}); + const cwmsOfficesOptions = useMemo(() => generateOfficesOptions(cwmsOffices), [cwmsOffices]); + + const { data: cwmsTimeseries } = useGetCwmsTimeseries({ office: selectedOffice?.value || selectedOffice }, { + enabled: !!selectedOffice?.value || !!selectedOffice, + }); + const cwmsTimeseriesOptions = useMemo(() => generateCwmsTimeseriesOptions(cwmsTimeseries), [cwmsTimeseries]); + + return ( + + + + Use the filters below to query for CWMS Timeseries: +
      + opt.value === val || opt.value === val.value} + onChange={(_e, value) => setSelectedOffice(value)} + renderInput={(params) => } + options={cwmsOfficesOptions} + fullWidth + /> + {selectedOffice && ( + opt.value === val || opt.value === val.value} + onChange={(_e, value) => setSelectedTimeseries(value)} + renderInput={(params) => } + options={cwmsTimeseriesOptions} + fullWidth + /> + )} + {selectedTimeseries && ( + <> +
      +

      The following fields will be used by MIDAS exclusively and will not change any data in regards to the CWMS timeseries:

      + setName(e.target.value)} + /> + setParameterId(val?.id)} + domain='parameter' + label='Parameter' + /> + setUnitId(val?.id)} + domain='unit' + label='Unit' + /> + + )} +
      + doInstrumentTimeseriesDelete(item, () => { + client.invalidateQueries('midasCwmsTimeseries'); + doModalClose(); + }, true) : null} + saveIsDisabled={!selectedTimeseries || !name || !parameterId || !unitId} + saveText={isEdit ? 'Save Changes' : 'Add to Instrument'} + onSave={() => { + if (isEdit) { + midasCwmsTimeseriesUpdater.mutate({ projectId, instrumentId, timeseriesId: item?.id, body: { + instrument_id: instrumentId, + cwms_office_id: typeof selectedOffice === 'string' ? selectedOffice : selectedOffice.value, + cwms_timeseries_id: typeof selectedTimeseries === 'string' ? selectedTimeseries : selectedTimeseries.value, + cwms_extent_earliest_time: typeof selectedTimeseries === 'string' ? item?.cwms_extent_earliest_time : selectedTimeseries._extents['earliest-time'], + parameter_id: parameterId, + unit_id: unitId, + name, + }}) + } else { + midasCwmsTimeseriesCreator.mutate({ projectId, instrumentId, body: [{ + instrument_id: instrumentId, + cwms_office_id: selectedOffice.value, + cwms_timeseries_id: selectedTimeseries.value, + cwms_extent_earliest_time: selectedTimeseries._extents['earliest-time'], + parameter_id: parameterId, + unit_id: unitId, + name, + }]}) + } + }} + /> +
      + ); + }, +); + +export default NewCwmsTimeseriesModal; diff --git a/src/app-pages/instrument/details.jsx b/src/app-pages/instrument/details.jsx index 1d216388..6c6582da 100644 --- a/src/app-pages/instrument/details.jsx +++ b/src/app-pages/instrument/details.jsx @@ -179,19 +179,23 @@ export default connect(
    -
    - -
    - {isShapeArray && ( -
    - -
    - )} - {isIPI && ( -
    - + +
    +
    - )} + {isShapeArray && ( +
    + +
    + )} + {isIPI && ( +
    + +
    + )} +
    diff --git a/src/app-pages/instrument/notes.jsx b/src/app-pages/instrument/notes.jsx index bfeedf5b..40c47f44 100644 --- a/src/app-pages/instrument/notes.jsx +++ b/src/app-pages/instrument/notes.jsx @@ -105,22 +105,22 @@ const NoteItem = ({ note, editable, save }) => { }; export default connect( + 'selectProfileActive', 'selectProjectsByRoute', 'selectInstrumentNotesItems', - 'selectAuthEdipi', 'doInstrumentNotesSave', 'doInstrumentNotesDelete', ({ + profileActive, projectsByRoute: project, instrumentNotesItems: notes, - authEdipi, doInstrumentNotesSave: save, doInstrumentNotesDelete: del, }) => { const [isAdding, setIsAdding] = useState(false); - const edipi = Number(authEdipi); const sorted = notes.sort(); + const { id } = profileActive || {}; return ( project && ( @@ -133,7 +133,7 @@ export default connect( note={note} save={save} del={del} - editable={edipi === note.creator_id} + editable={id === note.creator_id} /> )) : No Notes Added}
  • diff --git a/src/app-pages/instrument/settings.jsx b/src/app-pages/instrument/settings.jsx index 788638e6..9e22d711 100644 --- a/src/app-pages/instrument/settings.jsx +++ b/src/app-pages/instrument/settings.jsx @@ -5,6 +5,7 @@ import AlertEditor from './alert/alert-editor'; import Card from '../../app-components/card'; import Chart from './chart/chart'; import Constants from './constants/constants'; +import CwmsTimeseries from './cwms-timeseries/cwmsTimeseries'; import FormulaEditor from './formula/formula'; import TabContainer from '../../app-components/tab'; import Timeseries from './timeseries/timeseries'; @@ -17,12 +18,14 @@ export default connect( timeseriesMeasurementsItemsObject: measurements, instrumentsByRoute: instrument, }) => { + const { type, show_cwms_tab } = instrument || {}; + // const alertsReady = import.meta.env.VITE_ALERT_EDITOR === 'true'; const alertsReady = false; const forumlaReady = import.meta.env.VITE_FORMULA_EDITOR === 'true'; const chartReady = import.meta.env.VITE_INSTRUMENT_CHART === 'true'; - const isShapeArray = instrument?.type === 'SAA'; - const isIPI = instrument?.type === 'IPI'; + const isShapeArray = type === 'SAA'; + const isIPI = type === 'IPI'; const tabs = [ alertsReady && { @@ -34,6 +37,9 @@ export default connect( }, { title: 'Timeseries', content: , + }, show_cwms_tab && { + title: 'CWMS Timeseries', + content: , }, (isShapeArray || isIPI) && { title: 'Sensors', content: , @@ -48,7 +54,11 @@ export default connect( return ( - + ); } diff --git a/src/app-pages/instrument/timeseries/timeseries-list-item.jsx b/src/app-pages/instrument/timeseries/timeseries-list-item.jsx index 2a98023f..5263ad0d 100644 --- a/src/app-pages/instrument/timeseries/timeseries-list-item.jsx +++ b/src/app-pages/instrument/timeseries/timeseries-list-item.jsx @@ -28,8 +28,9 @@ export default connect( +
    + + + )} + + ) +}; + +export default PlotSymbology; \ No newline at end of file diff --git a/src/app-pages/project/batch-plotting/modals/components/SecondaryAxis.jsx b/src/app-pages/project/batch-plotting/modals/components/SecondaryAxis.jsx new file mode 100644 index 00000000..31733eb6 --- /dev/null +++ b/src/app-pages/project/batch-plotting/modals/components/SecondaryAxis.jsx @@ -0,0 +1,125 @@ +import React, { useState } from 'react'; +import { Button, FormControl, Input, InputLabel, MenuItem, Select } from '@mui/material'; +import { connect } from 'redux-bundler-react'; + +const generateTimeseriesOptions = (timeseriesIds = [], timeseries = []) => { + const options = []; + + timeseriesIds.forEach(id => { + const found = timeseries.find(el => el.id === id); + + if (found) options.push({ + value: id, + label: `${found?.instrument} - ${found?.name} (${found?.parameter})`, + }); + }); + + return options; +}; + +const SecondaryAxis = connect( + 'selectInstrumentTimeseriesItems', + ({ + instrumentTimeseriesItems: timeseries, + chartData, + plotConfig, + doSaveBatchPlotConfiguration, + }) => { + const { display, plot_type, id } = plotConfig || {}; + + const [axisSettings, setAxisSettings] = useState({ + primaryTitle: display?.layout?.y_axis_title || 'Measurement', + secondaryTitle: display?.layout?.y2_axis_title, + timeseriesIds: display?.traces?.filter(trace => trace.y_axis === 'y2').map(el => el.timeseries_id) || [], + }); + const chartTimeseriesIds = chartData?.map(d => d.timeseriesId); + const timeseriesOptions = generateTimeseriesOptions(chartTimeseriesIds, timeseries); + + const saveSecondaryAxisSettings = () => { + const { primaryTitle, secondaryTitle, timeseriesIds } = axisSettings; + + const newTraces = display.traces.map(trace => { + let axis = 'y1'; + if (timeseriesIds.includes(trace.timeseries_id)) { + axis = 'y2'; + } + return { + ...trace, + y_axis: axis, + }; + }); + + doSaveBatchPlotConfiguration( + plot_type, + id, + { + ...plotConfig, + display: { + ...display, + layout: { + ...display.layout, + y_axis_title: primaryTitle, + y2_axis_title: secondaryTitle, + }, + traces: newTraces, + }, + } + ); + }; + + return ( + <> + + Primary Axis Title + setAxisSettings(prev => ({ ...prev, primaryTitle: e.target.value }))} + /> + +
    + + Fill in the information below to apply timeseries to a secondary axis on the batch plot. If no timeseries are provided, the secondary axis will not be displayed on the plot. + + + Secondary Axis Title + setAxisSettings(prev => ({ ...prev, secondaryTitle: e.target.value }))} + /> + + + Timeseries + + + + + ); + }, +); + +export default SecondaryAxis; diff --git a/src/app-pages/project/batch-plotting/modals/components/Thresholds.jsx b/src/app-pages/project/batch-plotting/modals/components/Thresholds.jsx new file mode 100644 index 00000000..98e22275 --- /dev/null +++ b/src/app-pages/project/batch-plotting/modals/components/Thresholds.jsx @@ -0,0 +1,199 @@ +import React, { useState } from 'react'; +import { Button, Checkbox, IconButton, Input } from '@mui/material'; +import { Check, DeleteOutline } from '@mui/icons-material'; +import { MuiColorInput } from 'mui-color-input'; + +const newThreshold = { + name: 'Threshold', + data_point: 0, + enabled: true, + color: '#000000', +}; + +const Thresholds = ({ + plotConfig, + doSaveBatchPlotConfiguration, +}) => { + const { display, plot_type, id } = plotConfig || {}; + const currentShapes = display?.layout?.custom_shapes || []; + const [workingData, setWorkingData] = useState({}); + const [key, setKey] = useState(0); + + const saveThreshold = (threshold) => { + const shapes = display?.layout?.custom_shapes || []; + + if (!threshold) shapes.push(newThreshold); + else { + const workingShape = shapes[threshold.index]; + shapes.splice(threshold.index, 1, { + ...workingShape, + ...threshold, + }) + } + + doSaveBatchPlotConfiguration( + plot_type, + id, + { + ...plotConfig, + display: { + ...display, + layout: { + ...display.layout, + custom_shapes: shapes, + }, + }, + } + ); + + if (threshold) { + setWorkingData(prev => ({ ...prev, [threshold.index]: undefined })) + } + + setKey(prev => prev + 1); + }; + + const deleteThreshold = index => { + const shapes = display?.layout?.custom_shapes || []; + shapes.splice(index, 1); + + doSaveBatchPlotConfiguration( + plot_type, + id, + { + ...plotConfig, + display: { + ...plotConfig.display, + layout: { + ...plotConfig.display.layout, + custom_shapes: shapes, + }, + }, + } + ); + + setKey(prev => prev + 1); + }; + + return ( +
    + {currentShapes.length ? ( + <> + + Below is this list of Thresholds for the current plot, including generated thresholds from alerts and manually entered thresholds. + Each manual threshold's name and value can be edited, disabled for display, or removed completely. + +
    +
    + Name +
    +
    + Value +
    +
    + Color +
    +
    + Enabled +
    +
    +
    +
    + {currentShapes.map((shape, index) => ( + <> +
    +
    + { + setWorkingData(prev => ({ ...prev, [index]: { ...prev[index], name: e.target.value }})); + }} + /> +
    +
    + { + setWorkingData(prev => ({ ...prev, [index]: { ...prev[index], data_point: Number(e.target.value) }})); + }} + /> +
    +
    + { + setWorkingData(prev => ({ ...prev, [index]: { ...prev[index], color: val }})); + }} + /> + { + setWorkingData(prev => ({ ...prev, [index]: { ...prev[index], color: val }})); + }} + /> +
    +
    + { + setWorkingData(prev => ({ ...prev, [index]: { ...prev[index], enabled: checked }})); + }} + /> +
    +
    + { + saveThreshold({ + ...workingData[index], + index, + }); + }} + > + + + deleteThreshold(index)} + > + + +
    +
    +
    + + ))} + + ) : ( + + No thresholds set. Click the Add Threshold button to create one. + + )} + +
    + ) +} + +export default Thresholds; diff --git a/src/app-pages/project/batch-plotting/modals/components/_bullseyeDisplay.jsx b/src/app-pages/project/batch-plotting/modals/components/_bullseyeDisplay.jsx new file mode 100644 index 00000000..56edebe1 --- /dev/null +++ b/src/app-pages/project/batch-plotting/modals/components/_bullseyeDisplay.jsx @@ -0,0 +1,77 @@ +import React, { useMemo, useState } from 'react'; +import { Autocomplete, TextField } from '@mui/material'; +import { connect } from 'redux-bundler-react'; + +import { formatTimeseriesOptions, formatTimeseriesId } from '../../helper'; +import { useDeepCompareEffect } from 'react-use'; + +const formatDisplay = (xTimeseries, yTimeseries, display) => { + return { + ...display, + x_axis_timeseries_id: xTimeseries?.value, + y_axis_timeseries_id: yTimeseries?.value, + }; +}; + +const BullseyeDisplay = connect( + 'selectInstrumentTimeseriesItems', + ({ + instrumentTimeseriesItems: timeseries, + display, + onChange, + }) => { + const { x_axis_timeseries_id, y_axis_timeseries_id } = display || {}; + const options = useMemo(() => formatTimeseriesOptions(timeseries).sort((a, b) => -b.label.localeCompare(a.label)), [timeseries]); + + const [xTimeseries, setXTimeseries] = useState(formatTimeseriesId(x_axis_timeseries_id, timeseries)); + const [yTimeseries, setYTimeseries] = useState(formatTimeseriesId(y_axis_timeseries_id, timeseries)); + + useDeepCompareEffect(() => { + const newDisplay = formatDisplay(xTimeseries, yTimeseries, display); + onChange(newDisplay, (!!xTimeseries && !!yTimeseries)); + }, [xTimeseries, yTimeseries, onChange]); + + return ( + <> + opt.instrumentName} + getOptionLabel={opt => opt.label} + options={options} + value={xTimeseries} + isOptionEqualToValue={(opt, val) => val ? opt?.value === val?.value : false} + onChange={(e, val) => setXTimeseries(val)} + renderInput={(params) => ( + + )} + /> + opt.instrumentName} + getOptionLabel={opt => opt.label} + options={options} + value={yTimeseries} + isOptionEqualToValue={(opt, val) => val ? opt?.value === val?.value : false} + onChange={(e, val) => setYTimeseries(val)} + renderInput={(params) => ( + + )} + /> + + ); + }, +); + +export default BullseyeDisplay; diff --git a/src/app-pages/project/batch-plotting/modals/components/_contourDisplay.jsx b/src/app-pages/project/batch-plotting/modals/components/_contourDisplay.jsx new file mode 100644 index 00000000..1dfafbb4 --- /dev/null +++ b/src/app-pages/project/batch-plotting/modals/components/_contourDisplay.jsx @@ -0,0 +1,141 @@ +import React, { useMemo, useState } from 'react'; +import isEqual from 'lodash.isequal'; +import { Autocomplete, Checkbox, FormControl, FormControlLabel, FormGroup, InputLabel, MenuItem, Select, TextField } from '@mui/material'; +import { connect } from 'redux-bundler-react'; +import { useDeepCompareEffect } from 'react-use'; + +import { formatTimeseriesOptions, formatTimeseriesId } from '../../helper'; + +const formatDisplay = (oldDisplay, newOptions) => { + const { + selectedDuration, + isContourSmoothing, + isGradientSmoothing, + isShowLabels, + selectedTimeseries, + } = newOptions; + + return { + ...oldDisplay, + timeseries_ids: selectedTimeseries?.map(t => t.value), + show_labels: isShowLabels, + contour_smoothing: isContourSmoothing, + gradient_smoothing: isGradientSmoothing, + locf_backfill: selectedDuration, + }; +}; + +const ContourDisplay = connect( + 'selectInstrumentTimeseriesItems', + ({ + instrumentTimeseriesItems: timeseries, + display, + onChange, + }) => { + const { contour_smoothing, gradient_smoothing, show_labels, locf_backfill, timeseries_ids } = display || {}; + + const [selectedDuration, setSelectedDuration] = useState(locf_backfill || ''); + const [isContourSmoothing, setIsContourSmoothing] = useState(contour_smoothing || false); + const [isGradientSmoothing, setIsGradientSmoothing] = useState(gradient_smoothing || false); + const [isShowLabels, setIsShowLabels] = useState(show_labels || false); + const [selectedTimeseries, setSelectedTimeseries] = useState(timeseries_ids ? timeseries_ids.map(id => formatTimeseriesId(id, timeseries)) : []); + + const timeseriesOptions = useMemo(() => formatTimeseriesOptions(timeseries).sort((a, b) => -b.label.localeCompare(a.label)), [timeseries]); + + useDeepCompareEffect(() => { + const newDisplay = formatDisplay(display, { + selectedDuration, + isContourSmoothing, + isGradientSmoothing, + isShowLabels, + selectedTimeseries, + }); + + if (!isEqual(newDisplay, display)) { + onChange(newDisplay, (!!selectedDuration && !!selectedTimeseries?.length)); + } + }, [selectedDuration, selectedTimeseries, isShowLabels, isContourSmoothing, isGradientSmoothing, onChange]); + + return ( + <> +
    + opt.instrumentName} + getOptionLabel={option => option.label} + options={timeseriesOptions} + value={selectedTimeseries} + isOptionEqualToValue={(opt, val) => val ? opt?.value === val.value : false} + onChange={(_e, val) => setSelectedTimeseries(val)} + renderInput={(params) => ( + + )} + /> + + Duration + + + + setIsContourSmoothing(checked)} + checked={isContourSmoothing} + /> + )} + /> + setIsGradientSmoothing(checked)} + checked={isGradientSmoothing} + /> + )} + /> + setIsShowLabels(checked)} + checked={isShowLabels} + /> + )} + /> + + + ); + }, +); + +export default ContourDisplay; diff --git a/src/app-pages/project/batch-plotting/modals/components/_profileDisplay.jsx b/src/app-pages/project/batch-plotting/modals/components/_profileDisplay.jsx new file mode 100644 index 00000000..07c29da8 --- /dev/null +++ b/src/app-pages/project/batch-plotting/modals/components/_profileDisplay.jsx @@ -0,0 +1,46 @@ +import React, { useEffect, useMemo, useState } from 'react'; +import { Autocomplete, TextField } from '@mui/material'; +import { connect } from 'redux-bundler-react'; +import { formatInstrument, formatInstrumentOptions } from '../../helper'; + +const ProfileDisplay = connect( + 'selectInstrumentsItems', + ({ + instrumentsItems: instruments, + display, + onChange, + }) => { + const { instrument_id } = display || {}; + const [newInstrument, setNewInstrument] = useState(formatInstrument(instrument_id, instruments)); + + const instrumentOptions = useMemo(() => formatInstrumentOptions(instruments), [instruments]); + + useEffect(() => { + onChange({ + instrument_id: newInstrument, + }, !!newInstrument); + }, [newInstrument, onChange]); + + return ( + opt.label} + options={instrumentOptions} + value={newInstrument} + isOptionEqualToValue={(opt, val) => val ? opt?.value === val?.value : false} + onChange={(e, val) => setNewInstrument(val)} + renderInput={(params) => ( + + )} + /> + ); + }, +); + +export default ProfileDisplay; diff --git a/src/app-pages/project/batch-plotting/modals/components/_scatterLineDisplay.jsx b/src/app-pages/project/batch-plotting/modals/components/_scatterLineDisplay.jsx new file mode 100644 index 00000000..53b652b1 --- /dev/null +++ b/src/app-pages/project/batch-plotting/modals/components/_scatterLineDisplay.jsx @@ -0,0 +1,66 @@ +import React, { useEffect, useMemo, useState } from 'react'; +import { Autocomplete, TextField } from '@mui/material'; +import { connect } from 'redux-bundler-react'; +import { formatTimeseriesOptions, formatTimeseriesId } from '../../helper'; + +const formatDisplay = (timeseries, display) => { + return { + ...display, + traces: timeseries.map((ts, index) => ({ + timeseries_id: ts.value, + color: '#ffffff', + trace_order: index, + y_axis: 'y1', + line_style: 'solid', + show_markers: true, + width: 3, + })) + }; +}; + +const ScatterLineDisplay = connect( + 'selectInstrumentTimeseriesItems', + ({ + instrumentTimeseriesItems: instrumentTimeseries, + display, + onChange = () => {}, + }) => { + const { traces = [] } = display || {}; + const timeseries = useMemo(() => formatTimeseriesOptions(instrumentTimeseries), [instrumentTimeseries]); + + const [timeseriesInput, setTimeseriesInput] = useState(''); + const [selectedTimeseries, setSelectedTimeseries] = useState(traces.length ? traces.map(trace => formatTimeseriesId(trace.timeseries_id, instrumentTimeseries)) : []); + + useEffect(() => { + const newDisplay = formatDisplay(selectedTimeseries, display); + onChange(newDisplay, !!selectedTimeseries.length); + }, [selectedTimeseries, formatDisplay, onChange]); + + return ( + opt.instrumentName} + getOptionLabel={option => option.label} + options={timeseries.sort((a, b) => -b.label.localeCompare(a.label))} + value={selectedTimeseries} + isOptionEqualToValue={(opt, val) => val ? opt?.value === val.value : false} + onChange={(e, val) => setSelectedTimeseries(val)} + renderInput={(params) => ( + setTimeseriesInput(e.target.value)} + variant='outlined' + label='Timeseries' + /> + )} + /> + ); + }, +); + +export default ScatterLineDisplay; diff --git a/src/app-pages/project/batch-plotting/tab-content/batch-plot-chart.jsx b/src/app-pages/project/batch-plotting/tab-content/batch-plot-chart.jsx deleted file mode 100644 index 3f143b7a..00000000 --- a/src/app-pages/project/batch-plotting/tab-content/batch-plot-chart.jsx +++ /dev/null @@ -1,155 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import { connect } from 'redux-bundler-react'; -import { subDays } from 'date-fns'; - -import Chart from '../../../../app-components/chart/chart'; -import ChartErrors from '../components/batch-plot-errors'; -import ChartSettings from '../components/batch-plot-chart-settings'; -import { generateNewChartData } from '../helper'; - -const BatchPlotChart = connect( - 'doPrintSetData', - 'doInstrumentTimeseriesSetActiveId', - 'doTimeseriesMeasurementsFetchById', - 'doBatchPlotConfigurationsSave', - 'selectBatchPlotConfigurationsActiveId', - 'selectBatchPlotConfigurationsItemsObject', - 'selectTimeseriesMeasurementsItems', - 'selectInstrumentTimeseriesItems', - ({ - doPrintSetData, - doInstrumentTimeseriesSetActiveId, - doTimeseriesMeasurementsFetchById, - doBatchPlotConfigurationsSave, - batchPlotConfigurationsActiveId, - batchPlotConfigurationsItemsObject, - timeseriesMeasurementsItems, - instrumentTimeseriesItems, - }) => { - const [timeseriesIds, setTimeseriesId] = useState([]); - const [measurements, setMeasurements] = useState([]); - const [chartData, setChartData] = useState([]); - const [dateRange, setDateRange] = useState([subDays(new Date(), 365), new Date()]); - const [threshold, setThreshold] = useState(3000); - const [withPrecipitation, setWithPrecipitation] = useState(false); - const [chartSettings, setChartSettings] = useState({ auto_range: false }); - - const layout = { - xaxis: { - autorange: chartSettings?.auto_range, - range: dateRange, - title: 'Date', - showline: true, - mirror: true, - }, - yaxis: { - title: 'Measurement', - showline: true, - mirror: true, - domain: [0, withPrecipitation ? 0.66 : 1], - }, - ...(withPrecipitation && { - yaxis2: { - title: 'Rainfall', - autorange: 'reversed', - showline: true, - mirror: true, - domain: [0.66, 1], - }, - }), - autosize: true, - dragmode: 'pan', - height: 600, - }; - - const savePlotSettings = (params) => { - timeseriesIds.forEach(id => doTimeseriesMeasurementsFetchById({ timeseriesId: id, dateRange, threshold })); - doBatchPlotConfigurationsSave(...params); - }; - - /** Load specific timeseries ids into state when new configurations are loaded */ - useEffect(() => { - const config = batchPlotConfigurationsItemsObject[batchPlotConfigurationsActiveId]; - setTimeseriesId((config || {}).timeseries_id || []); - setChartSettings(config); - }, [ - batchPlotConfigurationsActiveId, - batchPlotConfigurationsItemsObject, - setTimeseriesId, - setChartSettings, - ]); - - /** Fetch the timeseries measurements in regards to date range */ - useEffect(() => { - // @TODO: new endpoint: fetch -> batchPlotMeasurements(plotConfigId); - timeseriesIds.forEach(id => doTimeseriesMeasurementsFetchById({ timeseriesId: id, dateRange, threshold })); - }, [timeseriesIds, doInstrumentTimeseriesSetActiveId]); - - /** Extract specific measurements from the store that relate to our set timeseries */ - useEffect(() => { - const measurementItems = timeseriesIds.map(id => - timeseriesMeasurementsItems.find(elem => elem.timeseries_id === id) - ); - - setMeasurements(measurementItems); - }, [timeseriesIds, timeseriesMeasurementsItems, setMeasurements]); - - /** When we get new measurements, update chart data */ - useEffect(() => { - const newData = generateNewChartData(measurements, instrumentTimeseriesItems, chartSettings); - - setChartData(newData); - }, [measurements, instrumentTimeseriesItems, withPrecipitation, chartSettings]); - - /** When chart data changes, see if there is precip data to adjust plot */ - useEffect(() => { - if ( - chartData.length && - chartData.find(elem => elem?.yaxis === 'y2') - ) { - setWithPrecipitation(true); - } else { - setWithPrecipitation(false); - } - doPrintSetData(chartData, layout); - }, [chartData, setWithPrecipitation]); - - return ( - <> - - - {chartSettings ? ( - <> -
    - - - ) : null} - - ); - } -); - -export default BatchPlotChart; diff --git a/src/app-pages/project/batch-plotting/tab-content/depth-chart.jsx b/src/app-pages/project/batch-plotting/tab-content/depth-chart.jsx deleted file mode 100644 index d6e21fda..00000000 --- a/src/app-pages/project/batch-plotting/tab-content/depth-chart.jsx +++ /dev/null @@ -1,219 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import { connect } from 'redux-bundler-react'; -import { Slider } from '@mui/material'; -import { DateTime } from 'luxon'; - -import Chart from '../../../../app-components/chart/chart'; - -const colors = [ - '#800000', - '#000075', - '#e6194B', - '#3cb44b', - '#911eb4', - '#fabed4', -]; - -const formatData = (data = [], indexes = []) => { - const inclinometerIds = Object.keys(data); - if (!inclinometerIds.length) return {}; - - const workingData = data[inclinometerIds[0]].inclinometers; - - const depthIncrements = workingData.map((datum, i) => { - const { time, values } = datum; - - const valueDisplacement = values.sort((a, b) => b.nDepth - a.nDepth); - - return { time, valueDisplacement, colorIndex: i }; - }).sort((a, b) => DateTime.fromISO(a.time).toMillis() - DateTime.fromISO(b.time).toMillis()); - - const relevantData = depthIncrements.slice(indexes[0], indexes[1] + 1); - const dataArray = []; - - for (let i = 0; i < relevantData.length; i++) { - const { valueDisplacement, time, colorIndex } = relevantData[i]; - - dataArray.push( - valueDisplacement.reduce((accum, current, ind) => { - const { depth, aIncrement, bIncrement } = current; - - if (ind === 0) { - accum.nDepth.push(depth); - accum.aIncrement.push(aIncrement); - accum.bIncrement.push(bIncrement); - accum.time = DateTime.fromISO(time).toFormat('MMM dd, yyyy hh:mm:ss'); - accum.colorIndex = colorIndex; - } else { - accum.nDepth.push(depth); - accum.aIncrement.push(accum.aIncrement[ind - 1] + aIncrement); - accum.bIncrement.push(accum.bIncrement[ind - 1] + bIncrement); - accum.time = DateTime.fromISO(time).toFormat('MMM dd, yyyy hh:mm:ss'); - accum.colorIndex = colorIndex; - } - - return accum; - }, { - nDepth: [], - aIncrement: [], - bIncrement: [], - time: '', - colorIndex: '', - }) - ) - } - - return { depthIncrements, dataArray, relevantData }; -}; - -const build3dTraces = (dataArray, unit) => dataArray.map(data => ( - { - x: data.aIncrement, - y: data.bIncrement, - z: data.nDepth, - mode: 'markers+lines', - marker: { size: 3, color: colors[data.colorIndex % colors.length] }, - line: { width: 1 }, - type: 'scatter3d', - name: `${data.time} Cumulative Displacement (in ${unit})`, - } - - // If client wants A and B Displacement on the 3-D plot, add these back in and adjust function to a forEach using push logic. - // , { - // x: data.aIncrement, - // y: new Array(data.bIncrement.length).fill(0), - // z: data.nDepth, - // mode: 'markers+lines', - // marker: { size: 5, color: 'green' }, - // type: 'scatter3d', - // name: `A Displacement (in ${unit})`, - // }, { - // x: new Array(data.aIncrement.length).fill(0), - // y: data.bIncrement, - // z: data.nDepth, - // mode: 'markers+lines', - // marker: { size: 5, color: 'orange' }, - // type: 'scatter3d', - // name: `B Displacement (in ${unit})` - // } -)); - -const build2dTrace = (dataArray, key, unit) => dataArray.map(data => ( - { - x: data[key], - y: data.nDepth, - mode: 'markers+lines', - marker: { size: 5, color: colors[data.colorIndex % colors.length] }, - line: { width: 1 }, - type: 'scatter', - name: `${key} Displacement (in ${unit})`, - hovertemplate: ` - ${data.time}
    - Depth: %{y}
    - Displacement: %{x}
    - - `, - } -)); - -const DepthChart = connect( - 'doFetchInclinometerMeasurementsByTimeseriesId', - 'selectCurrentInclinometerMeasurements', - ({ - doFetchInclinometerMeasurementsByTimeseriesId, - currentInclinometerMeasurements, - inclinometerTimeseriesIds, - }) => { - const [sliderVal, setSliderVal] = useState([0, 0]); - - useEffect(() => { - inclinometerTimeseriesIds.forEach(id => { - doFetchInclinometerMeasurementsByTimeseriesId(id); - }); - }, [inclinometerTimeseriesIds, doFetchInclinometerMeasurementsByTimeseriesId]); - - const inclinometerIds = Object.keys(currentInclinometerMeasurements || {}); - const isMetric = false; - const unit = isMetric ? 'mm' : 'inches'; - const { dataArray = [], depthIncrements = [] } = formatData(currentInclinometerMeasurements, sliderVal, isMetric); - - const config = { - repsonsive: true, - displaylogo: false, - displayModeBar: true, - scrollZoom: true, - }; - - const layout3d = { - autosize: true, - height: 800, - scene: { - xaxis: { title: `A-Displacement (in ${unit})` }, - yaxis: { title: `B-Displacement (in ${unit})` }, - zaxis: { title: 'Depth', autorange: 'reversed' }, - }, - legend: { - 'orientation': 'h', - }, - }; - - const layoutTall = (key) => ({ - showlegend: false, - autosize: true, - height: 800, - yaxis: { - autorange: 'reversed', - title: `Depth in Feet`, - }, - xaxis: { - title: `${key}-Displacement in ${unit}`, - }, - }); - - const incrementData = build3dTraces(dataArray, unit); - - return inclinometerIds.length ? ( - <> -
    -
    - -
    -
    - -
    -
    - -
    -
    -
    -
    - {DateTime.fromISO(depthIncrements[val]?.time).toFormat('MMM dd, yyyy hh:mm:ss')}} - onChange={(_e, newVal) => setSliderVal(newVal)} - /> -
    -
    - - ) : Loading Chart Data....; - }, -); - -export default DepthChart; diff --git a/src/app-pages/project/dashboard/cards/batchPlotCard.jsx b/src/app-pages/project/dashboard/cards/batchPlotCard.jsx index 2d1c4b96..ae8ec9ef 100644 --- a/src/app-pages/project/dashboard/cards/batchPlotCard.jsx +++ b/src/app-pages/project/dashboard/cards/batchPlotCard.jsx @@ -28,10 +28,8 @@ const BatchPlotCard = connect( {plots.length ? ( -
    - {plots.map((plot, i) => { - if (i < 5) return createReportRow(project, plot); - })} +
    + {plots.map((plot, _i) => createReportRow(project, plot))}
    ) : (

    diff --git a/src/app-pages/project/dashboard/cards/instrumentStatusCard.jsx b/src/app-pages/project/dashboard/cards/instrumentStatusCard.jsx index 0d5b1f59..b23fcb8f 100644 --- a/src/app-pages/project/dashboard/cards/instrumentStatusCard.jsx +++ b/src/app-pages/project/dashboard/cards/instrumentStatusCard.jsx @@ -22,6 +22,8 @@ const InstrumentStatusCard = connect( const instrumentsByStatus = reduceInstrumentsByStatus(instruments); const dataValues = Object.values(instrumentsByStatus); + + // NOTE: Possibly remove yellow -> gray return ( diff --git a/src/app-pages/project/dashboard/cards/reportConfigsCard.jsx b/src/app-pages/project/dashboard/cards/reportConfigsCard.jsx new file mode 100644 index 00000000..37c92f4d --- /dev/null +++ b/src/app-pages/project/dashboard/cards/reportConfigsCard.jsx @@ -0,0 +1,154 @@ +import React, { useState } from 'react'; +import { connect } from 'redux-bundler-react'; +import { Download, Menu } from '@mui/icons-material'; +import { toast } from 'react-toastify'; + +import Badge from '../../../../app-components/badge'; +import Button from '../../../../app-components/button'; +import Card from '../../../../app-components/card'; +import Dropdown from '../../../../app-components/dropdown'; +import NewReportConfigContent from '../modals/newReportConfigContent'; +import RoleFilter from '../../../../app-components/role-filter'; +import Table from '../../../../app-components/table'; +import { downloadFinalReport, useGetReportStatus, useInitializeReportDownload } from '../../../../app-services/collections/report-configuration-download.ts'; +import { tUpdateSuccess } from '../../../../common/helpers/toast-helpers.js'; + +import '../../project.scss'; + +const downloadEnabled = import.meta.env.VITE_REPORT_DOWNLOAD === 'true'; + +const NoReportConfigs = ({ doModalOpen, project }) => ( +

    + No Report Configurations in this project. +
    + + + Click the button below to add a new Report Configuration. + +
    +); + +const ReportConfigsCard = connect( + 'doModalOpen', + 'selectProjectReportConfigurations', + 'selectProjectsByRoute', + ({ + doModalOpen, + projectReportConfigurations: reportConfigs, + projectsByRoute: project, + }) => { + const [toastId, setToastId] = useState(undefined); + + const { id: projectId } = project; + const { data: jobDetails, mutate: initReportJobMutator } = useInitializeReportDownload(projectId); + const { report_config_id: reportConfigId, id: jobId } = jobDetails || {}; + + const { data: currentJob } = useGetReportStatus({ projectId, reportConfigId, jobId }, { + enabled: !!((projectId && reportConfigId && jobId) && (['INIT', 'IN_PROGRESS'].includes(jobDetails?.status))), + refetchInterval: 3000, + }); + const { file_key, progress } = currentJob || {}; + + if (file_key && progress === 100) { + tUpdateSuccess( + toastId, + 'Your file is ready, click this message to open!', + { + autoClose: false, + onClose: () => { + downloadFinalReport({ projectId, reportConfigId, jobId }); + } + } + ); + } + + const beginDownloadReportJob = id => { + const tId = toast.loading('Getting your report ready for download. This may take a minute...'); + setToastId(tId); + initReportJobMutator(id); + }; + + return ( + + +
    + + Report Configurations + + + + } + buttonClasses={['m-0', 'p-0']} + menuClasses={['dropdown-menu-right']} + > + doModalOpen(NewReportConfigContent, {}, 'lg')}> + Create Report Configuation + + + +
    +
    + + {reportConfigs.length ? ( + ( +