diff --git a/package-lock.json b/package-lock.json index 4d6de1f..627cf58 100644 --- a/package-lock.json +++ b/package-lock.json @@ -116,9 +116,9 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.20.10", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.20.10.tgz", - "integrity": "sha512-sEnuDPpOJR/fcafHMjpcpGN5M2jbUGUHwmuWKM/YdPzeEDJg8bgmbcWQFUfE32MQjti1koACvoPVsDe8Uq+idg==", + "version": "7.20.14", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.20.14.tgz", + "integrity": "sha512-0YpKHD6ImkWMEINCyDAD0HLLUH/lPCefG8ld9it8DJB2wnApraKuhgYTvTY1z7UFIfBTGy5LwncZ+5HWWGbhFw==", "engines": { "node": ">=6.9.0" } @@ -180,9 +180,9 @@ } }, "node_modules/@babel/generator": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.7.tgz", - "integrity": "sha512-7wqMOJq8doJMZmP4ApXTzLxSr7+oO2jroJURrVEp6XShrQUObV8Tq/D0NCcoYg2uHqUrjzO0zwBjoYzelxK+sw==", + "version": "7.20.14", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.14.tgz", + "integrity": "sha512-AEmuXHdcD3A52HHXxaTmYlb8q/xMEhoRP67B3T4Oq7lbmSoqroMZzjnGj3+i1io3pdnF8iBYVu4Ilj+c4hBxYg==", "dependencies": { "@babel/types": "^7.20.7", "@jridgewell/gen-mapping": "^0.3.2", @@ -1175,9 +1175,9 @@ } }, "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.20.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.20.11.tgz", - "integrity": "sha512-tA4N427a7fjf1P0/2I4ScsHGc5jcHPbb30xMbaTke2gxDuWpUfXDuX1FEymJwKk4tuGUvGcejAR6HdZVqmmPyw==", + "version": "7.20.14", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.20.14.tgz", + "integrity": "sha512-sMPepQtsOs5fM1bwNvuJJHvaCfOEQfmc01FGw0ELlTpTJj5Ql/zuNRRldYhAPys4ghXdBIQJbRVYi44/7QflQQ==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.20.2" @@ -2253,9 +2253,9 @@ } }, "node_modules/@csstools/selector-specificity": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-2.1.0.tgz", - "integrity": "sha512-zJ6hb3FDgBbO8d2e83vg6zq7tNvDqSq9RwdwfzJ8tdm9JHNvANq2fqwyRn6mlpUb7CwTs5ILdUrGwi9Gk4vY5w==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-2.1.1.tgz", + "integrity": "sha512-jwx+WCqszn53YHOfvFMJJRd/B2GqkCBt+1MJSG6o5/s8+ytHMvDZXsJgUEWLk12UnLd7HYKac4BYU5i/Ron1Cw==", "dev": true, "engines": { "node": "^14 || ^16 || >=18" @@ -2495,9 +2495,9 @@ } }, "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "13.19.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.19.0.tgz", - "integrity": "sha512-dkQ957uSRWHw7CFXLUtUHQI3g3aWApYhfNR2O6jn/907riyTYKVBmxYVROkBcY614FSSeSJh7Xm7SrUWCxvJMQ==", + "version": "13.20.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", + "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", "dev": true, "dependencies": { "type-fest": "^0.20.2" @@ -3402,9 +3402,9 @@ } }, "node_modules/@mui/core-downloads-tracker": { - "version": "5.11.6", - "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.11.6.tgz", - "integrity": "sha512-lbD3qdafBOf2dlqKhOcVRxaPAujX+9UlPC6v8iMugMeAXe0TCgU3QbGXY3zrJsu6ex64WYDpH4y1+WOOBmWMuA==", + "version": "5.11.7", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.11.7.tgz", + "integrity": "sha512-lZgX7XQTk0zVcpwEa80r+T4y09dosnUxWvFPSikU/2Hh5wnyNOek8WfJwGCNsaRiXJHMi5eHY+z8oku4u5lgNw==", "funding": { "type": "opencollective", "url": "https://opencollective.com/mui" @@ -3480,12 +3480,12 @@ } }, "node_modules/@mui/private-theming": { - "version": "5.11.2", - "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.11.2.tgz", - "integrity": "sha512-qZwMaqRFPwlYmqwVKblKBGKtIjJRAj3nsvX93pOmatsXyorW7N/0IPE/swPgz1VwChXhHO75DwBEx8tB+aRMNg==", + "version": "5.11.7", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.11.7.tgz", + "integrity": "sha512-XzRTSZdc8bhuUdjablTNv3kFkZ/XIMlKkOqqJCU0G8W3tWGXpau2DXkafPd1ddjPhF9zF3qLKNGgKCChYItjgA==", "dependencies": { "@babel/runtime": "^7.20.7", - "@mui/utils": "^5.11.2", + "@mui/utils": "^5.11.7", "prop-types": "^15.8.1" }, "engines": { @@ -3629,9 +3629,9 @@ } }, "node_modules/@mui/utils": { - "version": "5.11.2", - "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.11.2.tgz", - "integrity": "sha512-AyizuHHlGdAtH5hOOXBW3kriuIwUIKUIgg0P7LzMvzf6jPhoQbENYqY6zJqfoZ7fAWMNNYT8mgN5EftNGzwE2w==", + "version": "5.11.7", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.11.7.tgz", + "integrity": "sha512-8uyNDeVHZA804Ego20Erv8TpxlbqTe/EbhTI2H1UYr4/RiIbBprat8W4Qqr2UQIsC/b3DLz+0RQ6R/E5BxEcLA==", "dependencies": { "@babel/runtime": "^7.20.7", "@types/prop-types": "^15.7.5", @@ -4805,12 +4805,12 @@ "dev": true }, "node_modules/@typescript-eslint/experimental-utils": { - "version": "5.49.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-5.49.0.tgz", - "integrity": "sha512-veLpCJLYn44Fru7mSvi2doxQMzMCOFSDYdMUQhAzaH1vFYq2RVNpecZ8d18Wh6UMv07yahXkiv/aShWE48iE9Q==", + "version": "5.50.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-5.50.0.tgz", + "integrity": "sha512-gZIhzNRivy0RVqcxjKnQ+ipGc0qolilhBeNmvH+Dvu7Vymug+IfiYxTj2zM7mIlHsw6Q5aH7L7WmuTE3tZyzag==", "dev": true, "dependencies": { - "@typescript-eslint/utils": "5.49.0" + "@typescript-eslint/utils": "5.50.0" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -4824,13 +4824,13 @@ } }, "node_modules/@typescript-eslint/experimental-utils/node_modules/@typescript-eslint/scope-manager": { - "version": "5.49.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.49.0.tgz", - "integrity": "sha512-clpROBOiMIzpbWNxCe1xDK14uPZh35u4QaZO1GddilEzoCLAEz4szb51rBpdgurs5k2YzPtJeTEN3qVbG+LRUQ==", + "version": "5.50.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.50.0.tgz", + "integrity": "sha512-rt03kaX+iZrhssaT974BCmoUikYtZI24Vp/kwTSy841XhiYShlqoshRFDvN1FKKvU2S3gK+kcBW1EA7kNUrogg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.49.0", - "@typescript-eslint/visitor-keys": "5.49.0" + "@typescript-eslint/types": "5.50.0", + "@typescript-eslint/visitor-keys": "5.50.0" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -4841,9 +4841,9 @@ } }, "node_modules/@typescript-eslint/experimental-utils/node_modules/@typescript-eslint/types": { - "version": "5.49.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.49.0.tgz", - "integrity": "sha512-7If46kusG+sSnEpu0yOz2xFv5nRz158nzEXnJFCGVEHWnuzolXKwrH5Bsf9zsNlOQkyZuk0BZKKoJQI+1JPBBg==", + "version": "5.50.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.50.0.tgz", + "integrity": "sha512-atruOuJpir4OtyNdKahiHZobPKFvZnBnfDiyEaBf6d9vy9visE7gDjlmhl+y29uxZ2ZDgvXijcungGFjGGex7w==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -4854,13 +4854,13 @@ } }, "node_modules/@typescript-eslint/experimental-utils/node_modules/@typescript-eslint/typescript-estree": { - "version": "5.49.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.49.0.tgz", - "integrity": "sha512-PBdx+V7deZT/3GjNYPVQv1Nc0U46dAHbIuOG8AZ3on3vuEKiPDwFE/lG1snN2eUB9IhF7EyF7K1hmTcLztNIsA==", + "version": "5.50.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.50.0.tgz", + "integrity": "sha512-Gq4zapso+OtIZlv8YNAStFtT6d05zyVCK7Fx3h5inlLBx2hWuc/0465C2mg/EQDDU2LKe52+/jN4f0g9bd+kow==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.49.0", - "@typescript-eslint/visitor-keys": "5.49.0", + "@typescript-eslint/types": "5.50.0", + "@typescript-eslint/visitor-keys": "5.50.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -4881,16 +4881,16 @@ } }, "node_modules/@typescript-eslint/experimental-utils/node_modules/@typescript-eslint/utils": { - "version": "5.49.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.49.0.tgz", - "integrity": "sha512-cPJue/4Si25FViIb74sHCLtM4nTSBXtLx1d3/QT6mirQ/c65bV8arBEebBJJizfq8W2YyMoPI/WWPFWitmNqnQ==", + "version": "5.50.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.50.0.tgz", + "integrity": "sha512-v/AnUFImmh8G4PH0NDkf6wA8hujNNcrwtecqW4vtQ1UOSNBaZl49zP1SHoZ/06e+UiwzHpgb5zP5+hwlYYWYAw==", "dev": true, "dependencies": { "@types/json-schema": "^7.0.9", "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.49.0", - "@typescript-eslint/types": "5.49.0", - "@typescript-eslint/typescript-estree": "5.49.0", + "@typescript-eslint/scope-manager": "5.50.0", + "@typescript-eslint/types": "5.50.0", + "@typescript-eslint/typescript-estree": "5.50.0", "eslint-scope": "^5.1.1", "eslint-utils": "^3.0.0", "semver": "^7.3.7" @@ -4907,12 +4907,12 @@ } }, "node_modules/@typescript-eslint/experimental-utils/node_modules/@typescript-eslint/visitor-keys": { - "version": "5.49.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.49.0.tgz", - "integrity": "sha512-v9jBMjpNWyn8B6k/Mjt6VbUS4J1GvUlR4x3Y+ibnP1z7y7V4n0WRz+50DY6+Myj0UaXVSuUlHohO+eZ8IJEnkg==", + "version": "5.50.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.50.0.tgz", + "integrity": "sha512-cdMeD9HGu6EXIeGOh2yVW6oGf9wq8asBgZx7nsR/D36gTfQ0odE5kcRYe5M81vjEFAcPeugXrHg78Imu55F6gg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.49.0", + "@typescript-eslint/types": "5.50.0", "eslint-visitor-keys": "^3.3.0" }, "engines": { @@ -6354,9 +6354,9 @@ } }, "node_modules/browserslist": { - "version": "4.21.4", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz", - "integrity": "sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==", + "version": "4.21.5", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz", + "integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==", "funding": [ { "type": "opencollective", @@ -6368,10 +6368,10 @@ } ], "dependencies": { - "caniuse-lite": "^1.0.30001400", - "electron-to-chromium": "^1.4.251", - "node-releases": "^2.0.6", - "update-browserslist-db": "^1.0.9" + "caniuse-lite": "^1.0.30001449", + "electron-to-chromium": "^1.4.284", + "node-releases": "^2.0.8", + "update-browserslist-db": "^1.0.10" }, "bin": { "browserslist": "cli.js" @@ -6490,9 +6490,9 @@ } }, "node_modules/camel-case/node_modules/tslib": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", - "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", "dev": true }, "node_modules/camelcase": { @@ -6529,9 +6529,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001448", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001448.tgz", - "integrity": "sha512-tq2YI+MJnooG96XpbTRYkBxLxklZPOdLmNIOdIhvf7SNJan6u5vCKum8iT7ZfCt70m1GPkuC7P3TtX6UuhupuA==", + "version": "1.0.30001450", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001450.tgz", + "integrity": "sha512-qMBmvmQmFXaSxexkjjfMvD5rnDL0+m+dUMZKoDYsGG8iZN29RuYh9eRoMvKsT6uMAWlyUUGDEQGJJYjzCIO9ew==", "funding": [ { "type": "opencollective", @@ -6953,9 +6953,9 @@ } }, "node_modules/content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", "dev": true, "engines": { "node": ">= 0.6" @@ -7783,9 +7783,9 @@ "dev": true }, "node_modules/deepmerge": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", - "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.0.tgz", + "integrity": "sha512-z2wJZXrmeHdvYJp/Ux55wIjqo81G5Bp4c+oELTW+7ar6SogWHajt5a9gO3s3IDaGSAXjDk0vlQKN3rms8ab3og==", "dev": true, "engines": { "node": ">=0.10.0" @@ -8149,9 +8149,9 @@ } }, "node_modules/dot-case/node_modules/tslib": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", - "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", "dev": true }, "node_modules/dotenv": { @@ -8913,13 +8913,13 @@ } }, "node_modules/eslint-plugin-testing-library/node_modules/@typescript-eslint/scope-manager": { - "version": "5.49.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.49.0.tgz", - "integrity": "sha512-clpROBOiMIzpbWNxCe1xDK14uPZh35u4QaZO1GddilEzoCLAEz4szb51rBpdgurs5k2YzPtJeTEN3qVbG+LRUQ==", + "version": "5.50.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.50.0.tgz", + "integrity": "sha512-rt03kaX+iZrhssaT974BCmoUikYtZI24Vp/kwTSy841XhiYShlqoshRFDvN1FKKvU2S3gK+kcBW1EA7kNUrogg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.49.0", - "@typescript-eslint/visitor-keys": "5.49.0" + "@typescript-eslint/types": "5.50.0", + "@typescript-eslint/visitor-keys": "5.50.0" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -8930,9 +8930,9 @@ } }, "node_modules/eslint-plugin-testing-library/node_modules/@typescript-eslint/types": { - "version": "5.49.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.49.0.tgz", - "integrity": "sha512-7If46kusG+sSnEpu0yOz2xFv5nRz158nzEXnJFCGVEHWnuzolXKwrH5Bsf9zsNlOQkyZuk0BZKKoJQI+1JPBBg==", + "version": "5.50.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.50.0.tgz", + "integrity": "sha512-atruOuJpir4OtyNdKahiHZobPKFvZnBnfDiyEaBf6d9vy9visE7gDjlmhl+y29uxZ2ZDgvXijcungGFjGGex7w==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -8943,13 +8943,13 @@ } }, "node_modules/eslint-plugin-testing-library/node_modules/@typescript-eslint/typescript-estree": { - "version": "5.49.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.49.0.tgz", - "integrity": "sha512-PBdx+V7deZT/3GjNYPVQv1Nc0U46dAHbIuOG8AZ3on3vuEKiPDwFE/lG1snN2eUB9IhF7EyF7K1hmTcLztNIsA==", + "version": "5.50.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.50.0.tgz", + "integrity": "sha512-Gq4zapso+OtIZlv8YNAStFtT6d05zyVCK7Fx3h5inlLBx2hWuc/0465C2mg/EQDDU2LKe52+/jN4f0g9bd+kow==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.49.0", - "@typescript-eslint/visitor-keys": "5.49.0", + "@typescript-eslint/types": "5.50.0", + "@typescript-eslint/visitor-keys": "5.50.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -8970,16 +8970,16 @@ } }, "node_modules/eslint-plugin-testing-library/node_modules/@typescript-eslint/utils": { - "version": "5.49.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.49.0.tgz", - "integrity": "sha512-cPJue/4Si25FViIb74sHCLtM4nTSBXtLx1d3/QT6mirQ/c65bV8arBEebBJJizfq8W2YyMoPI/WWPFWitmNqnQ==", + "version": "5.50.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.50.0.tgz", + "integrity": "sha512-v/AnUFImmh8G4PH0NDkf6wA8hujNNcrwtecqW4vtQ1UOSNBaZl49zP1SHoZ/06e+UiwzHpgb5zP5+hwlYYWYAw==", "dev": true, "dependencies": { "@types/json-schema": "^7.0.9", "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.49.0", - "@typescript-eslint/types": "5.49.0", - "@typescript-eslint/typescript-estree": "5.49.0", + "@typescript-eslint/scope-manager": "5.50.0", + "@typescript-eslint/types": "5.50.0", + "@typescript-eslint/typescript-estree": "5.50.0", "eslint-scope": "^5.1.1", "eslint-utils": "^3.0.0", "semver": "^7.3.7" @@ -8996,12 +8996,12 @@ } }, "node_modules/eslint-plugin-testing-library/node_modules/@typescript-eslint/visitor-keys": { - "version": "5.49.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.49.0.tgz", - "integrity": "sha512-v9jBMjpNWyn8B6k/Mjt6VbUS4J1GvUlR4x3Y+ibnP1z7y7V4n0WRz+50DY6+Myj0UaXVSuUlHohO+eZ8IJEnkg==", + "version": "5.50.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.50.0.tgz", + "integrity": "sha512-cdMeD9HGu6EXIeGOh2yVW6oGf9wq8asBgZx7nsR/D36gTfQ0odE5kcRYe5M81vjEFAcPeugXrHg78Imu55F6gg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.49.0", + "@typescript-eslint/types": "5.50.0", "eslint-visitor-keys": "^3.3.0" }, "engines": { @@ -9281,9 +9281,9 @@ } }, "node_modules/eslint/node_modules/globals": { - "version": "13.19.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.19.0.tgz", - "integrity": "sha512-dkQ957uSRWHw7CFXLUtUHQI3g3aWApYhfNR2O6jn/907riyTYKVBmxYVROkBcY614FSSeSJh7Xm7SrUWCxvJMQ==", + "version": "13.20.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", + "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", "dev": true, "dependencies": { "type-fest": "^0.20.2" @@ -11061,9 +11061,9 @@ } }, "node_modules/immer": { - "version": "9.0.18", - "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.18.tgz", - "integrity": "sha512-eAPNpsj7Ax1q6Y/3lm2PmlwRcFzpON7HSNQ3ru5WQH1/PSpnyed/HpNOELl2CxLKoj4r+bAHgdyKqW5gc2Se1A==", + "version": "9.0.19", + "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.19.tgz", + "integrity": "sha512-eY+Y0qcsB4TZKwgQzLaE/lqYMlKhv5J9dyd2RhhtGhNo2njPXDqU9XPfcNfa3MIDsdtZt5KlkIsirlo4dHsWdQ==", "dev": true, "funding": { "type": "opencollective", @@ -13612,9 +13612,9 @@ } }, "node_modules/jest-watch-typeahead/node_modules/@types/yargs": { - "version": "17.0.20", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.20.tgz", - "integrity": "sha512-eknWrTHofQuPk2iuqDm1waA7V6xPlbgBoaaXEgYkClhLOnB0TtbW+srJaOToAgawPxPlHQzwypFA2bhZaUGP5A==", + "version": "17.0.22", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.22.tgz", + "integrity": "sha512-pet5WJ9U8yPVRhkwuEIp5ktAeAqRZOq4UdAyWLWzxbtpyXnzbtLdKiXAjJzi/KLmPGS9wk86lUFWZFN6sISo4g==", "dev": true, "dependencies": { "@types/yargs-parser": "*" @@ -14709,9 +14709,9 @@ } }, "node_modules/lower-case/node_modules/tslib": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", - "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", "dev": true }, "node_modules/lru-cache": { @@ -15030,9 +15030,9 @@ } }, "node_modules/no-case/node_modules/tslib": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", - "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", "dev": true }, "node_modules/node-forge": { @@ -15051,9 +15051,9 @@ "dev": true }, "node_modules/node-releases": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.8.tgz", - "integrity": "sha512-dFSmB8fFHEH/s81Xi+Y/15DQY6VHW81nXRj86EMSL3lmuTmK1e+aT4wrFCkTbm+gSwkw4KpX+rT/pMM2c1mF+A==" + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.9.tgz", + "integrity": "sha512-2xfmOrRkGogbTK9R6Leda0DGiXeY3p2NJpy4+gNCffdUvV6mdEJnaDEic1i3Ec2djAo8jWYoJMR5PB0MSMpxUA==" }, "node_modules/normalize-path": { "version": "3.0.0", @@ -15414,9 +15414,9 @@ } }, "node_modules/param-case/node_modules/tslib": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", - "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", "dev": true }, "node_modules/parent-module": { @@ -15473,9 +15473,9 @@ } }, "node_modules/pascal-case/node_modules/tslib": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", - "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", "dev": true }, "node_modules/path-exists": { @@ -18156,9 +18156,9 @@ } }, "node_modules/rxjs/node_modules/tslib": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", - "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", "dev": true }, "node_modules/safe-buffer": { @@ -18485,9 +18485,9 @@ } }, "node_modules/shell-quote": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.4.tgz", - "integrity": "sha512-8o/QEhSSRb1a5i7TFR0iM4G16Z0vYB2OQVs4G3aAFXjn3T6yEx8AZxy1PgDF7I00LZHYA3WxaSYIf5e5sAX8Rw==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.0.tgz", + "integrity": "sha512-QHsz8GgQIGKlRi24yFc6a6lN69Idnx634w49ay6+jA5yFh7a1UY+4Rp6HPx/L/1zcEDPEij8cIsiqR6bQsE5VQ==", "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -19192,9 +19192,9 @@ } }, "node_modules/terser": { - "version": "5.16.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.16.1.tgz", - "integrity": "sha512-xvQfyfA1ayT0qdK47zskQgRZeWLoOQ8JQ6mIgRGVNwZKdQMU+5FkCBjmv4QjcrTzyZquRw2FVtlJSRUmMKQslw==", + "version": "5.16.2", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.16.2.tgz", + "integrity": "sha512-JKuM+KvvWVqT7muHVyrwv7FVRPnmHDwF6XwoIxdbF5Witi0vu99RYpxDexpJndXt3jbZZmmWr2/mQa6HvSNdSg==", "dev": true, "dependencies": { "@jridgewell/source-map": "^0.3.2", @@ -20833,9 +20833,9 @@ } }, "@babel/compat-data": { - "version": "7.20.10", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.20.10.tgz", - "integrity": "sha512-sEnuDPpOJR/fcafHMjpcpGN5M2jbUGUHwmuWKM/YdPzeEDJg8bgmbcWQFUfE32MQjti1koACvoPVsDe8Uq+idg==" + "version": "7.20.14", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.20.14.tgz", + "integrity": "sha512-0YpKHD6ImkWMEINCyDAD0HLLUH/lPCefG8ld9it8DJB2wnApraKuhgYTvTY1z7UFIfBTGy5LwncZ+5HWWGbhFw==" }, "@babel/core": { "version": "7.20.12", @@ -20879,9 +20879,9 @@ } }, "@babel/generator": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.7.tgz", - "integrity": "sha512-7wqMOJq8doJMZmP4ApXTzLxSr7+oO2jroJURrVEp6XShrQUObV8Tq/D0NCcoYg2uHqUrjzO0zwBjoYzelxK+sw==", + "version": "7.20.14", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.14.tgz", + "integrity": "sha512-AEmuXHdcD3A52HHXxaTmYlb8q/xMEhoRP67B3T4Oq7lbmSoqroMZzjnGj3+i1io3pdnF8iBYVu4Ilj+c4hBxYg==", "requires": { "@babel/types": "^7.20.7", "@jridgewell/gen-mapping": "^0.3.2", @@ -21561,9 +21561,9 @@ } }, "@babel/plugin-transform-block-scoping": { - "version": "7.20.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.20.11.tgz", - "integrity": "sha512-tA4N427a7fjf1P0/2I4ScsHGc5jcHPbb30xMbaTke2gxDuWpUfXDuX1FEymJwKk4tuGUvGcejAR6HdZVqmmPyw==", + "version": "7.20.14", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.20.14.tgz", + "integrity": "sha512-sMPepQtsOs5fM1bwNvuJJHvaCfOEQfmc01FGw0ELlTpTJj5Ql/zuNRRldYhAPys4ghXdBIQJbRVYi44/7QflQQ==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.20.2" @@ -22246,9 +22246,9 @@ "requires": {} }, "@csstools/selector-specificity": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-2.1.0.tgz", - "integrity": "sha512-zJ6hb3FDgBbO8d2e83vg6zq7tNvDqSq9RwdwfzJ8tdm9JHNvANq2fqwyRn6mlpUb7CwTs5ILdUrGwi9Gk4vY5w==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-2.1.1.tgz", + "integrity": "sha512-jwx+WCqszn53YHOfvFMJJRd/B2GqkCBt+1MJSG6o5/s8+ytHMvDZXsJgUEWLk12UnLd7HYKac4BYU5i/Ron1Cw==", "dev": true, "requires": {} }, @@ -22444,9 +22444,9 @@ }, "dependencies": { "globals": { - "version": "13.19.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.19.0.tgz", - "integrity": "sha512-dkQ957uSRWHw7CFXLUtUHQI3g3aWApYhfNR2O6jn/907riyTYKVBmxYVROkBcY614FSSeSJh7Xm7SrUWCxvJMQ==", + "version": "13.20.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", + "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", "dev": true, "requires": { "type-fest": "^0.20.2" @@ -23128,9 +23128,9 @@ } }, "@mui/core-downloads-tracker": { - "version": "5.11.6", - "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.11.6.tgz", - "integrity": "sha512-lbD3qdafBOf2dlqKhOcVRxaPAujX+9UlPC6v8iMugMeAXe0TCgU3QbGXY3zrJsu6ex64WYDpH4y1+WOOBmWMuA==" + "version": "5.11.7", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.11.7.tgz", + "integrity": "sha512-lZgX7XQTk0zVcpwEa80r+T4y09dosnUxWvFPSikU/2Hh5wnyNOek8WfJwGCNsaRiXJHMi5eHY+z8oku4u5lgNw==" }, "@mui/icons-material": { "version": "5.4.1", @@ -23160,12 +23160,12 @@ } }, "@mui/private-theming": { - "version": "5.11.2", - "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.11.2.tgz", - "integrity": "sha512-qZwMaqRFPwlYmqwVKblKBGKtIjJRAj3nsvX93pOmatsXyorW7N/0IPE/swPgz1VwChXhHO75DwBEx8tB+aRMNg==", + "version": "5.11.7", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.11.7.tgz", + "integrity": "sha512-XzRTSZdc8bhuUdjablTNv3kFkZ/XIMlKkOqqJCU0G8W3tWGXpau2DXkafPd1ddjPhF9zF3qLKNGgKCChYItjgA==", "requires": { "@babel/runtime": "^7.20.7", - "@mui/utils": "^5.11.2", + "@mui/utils": "^5.11.7", "prop-types": "^15.8.1" } }, @@ -23226,9 +23226,9 @@ "requires": {} }, "@mui/utils": { - "version": "5.11.2", - "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.11.2.tgz", - "integrity": "sha512-AyizuHHlGdAtH5hOOXBW3kriuIwUIKUIgg0P7LzMvzf6jPhoQbENYqY6zJqfoZ7fAWMNNYT8mgN5EftNGzwE2w==", + "version": "5.11.7", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.11.7.tgz", + "integrity": "sha512-8uyNDeVHZA804Ego20Erv8TpxlbqTe/EbhTI2H1UYr4/RiIbBprat8W4Qqr2UQIsC/b3DLz+0RQ6R/E5BxEcLA==", "requires": { "@babel/runtime": "^7.20.7", "@types/prop-types": "^15.7.5", @@ -24123,38 +24123,38 @@ } }, "@typescript-eslint/experimental-utils": { - "version": "5.49.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-5.49.0.tgz", - "integrity": "sha512-veLpCJLYn44Fru7mSvi2doxQMzMCOFSDYdMUQhAzaH1vFYq2RVNpecZ8d18Wh6UMv07yahXkiv/aShWE48iE9Q==", + "version": "5.50.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-5.50.0.tgz", + "integrity": "sha512-gZIhzNRivy0RVqcxjKnQ+ipGc0qolilhBeNmvH+Dvu7Vymug+IfiYxTj2zM7mIlHsw6Q5aH7L7WmuTE3tZyzag==", "dev": true, "requires": { - "@typescript-eslint/utils": "5.49.0" + "@typescript-eslint/utils": "5.50.0" }, "dependencies": { "@typescript-eslint/scope-manager": { - "version": "5.49.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.49.0.tgz", - "integrity": "sha512-clpROBOiMIzpbWNxCe1xDK14uPZh35u4QaZO1GddilEzoCLAEz4szb51rBpdgurs5k2YzPtJeTEN3qVbG+LRUQ==", + "version": "5.50.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.50.0.tgz", + "integrity": "sha512-rt03kaX+iZrhssaT974BCmoUikYtZI24Vp/kwTSy841XhiYShlqoshRFDvN1FKKvU2S3gK+kcBW1EA7kNUrogg==", "dev": true, "requires": { - "@typescript-eslint/types": "5.49.0", - "@typescript-eslint/visitor-keys": "5.49.0" + "@typescript-eslint/types": "5.50.0", + "@typescript-eslint/visitor-keys": "5.50.0" } }, "@typescript-eslint/types": { - "version": "5.49.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.49.0.tgz", - "integrity": "sha512-7If46kusG+sSnEpu0yOz2xFv5nRz158nzEXnJFCGVEHWnuzolXKwrH5Bsf9zsNlOQkyZuk0BZKKoJQI+1JPBBg==", + "version": "5.50.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.50.0.tgz", + "integrity": "sha512-atruOuJpir4OtyNdKahiHZobPKFvZnBnfDiyEaBf6d9vy9visE7gDjlmhl+y29uxZ2ZDgvXijcungGFjGGex7w==", "dev": true }, "@typescript-eslint/typescript-estree": { - "version": "5.49.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.49.0.tgz", - "integrity": "sha512-PBdx+V7deZT/3GjNYPVQv1Nc0U46dAHbIuOG8AZ3on3vuEKiPDwFE/lG1snN2eUB9IhF7EyF7K1hmTcLztNIsA==", + "version": "5.50.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.50.0.tgz", + "integrity": "sha512-Gq4zapso+OtIZlv8YNAStFtT6d05zyVCK7Fx3h5inlLBx2hWuc/0465C2mg/EQDDU2LKe52+/jN4f0g9bd+kow==", "dev": true, "requires": { - "@typescript-eslint/types": "5.49.0", - "@typescript-eslint/visitor-keys": "5.49.0", + "@typescript-eslint/types": "5.50.0", + "@typescript-eslint/visitor-keys": "5.50.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -24163,28 +24163,28 @@ } }, "@typescript-eslint/utils": { - "version": "5.49.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.49.0.tgz", - "integrity": "sha512-cPJue/4Si25FViIb74sHCLtM4nTSBXtLx1d3/QT6mirQ/c65bV8arBEebBJJizfq8W2YyMoPI/WWPFWitmNqnQ==", + "version": "5.50.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.50.0.tgz", + "integrity": "sha512-v/AnUFImmh8G4PH0NDkf6wA8hujNNcrwtecqW4vtQ1UOSNBaZl49zP1SHoZ/06e+UiwzHpgb5zP5+hwlYYWYAw==", "dev": true, "requires": { "@types/json-schema": "^7.0.9", "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.49.0", - "@typescript-eslint/types": "5.49.0", - "@typescript-eslint/typescript-estree": "5.49.0", + "@typescript-eslint/scope-manager": "5.50.0", + "@typescript-eslint/types": "5.50.0", + "@typescript-eslint/typescript-estree": "5.50.0", "eslint-scope": "^5.1.1", "eslint-utils": "^3.0.0", "semver": "^7.3.7" } }, "@typescript-eslint/visitor-keys": { - "version": "5.49.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.49.0.tgz", - "integrity": "sha512-v9jBMjpNWyn8B6k/Mjt6VbUS4J1GvUlR4x3Y+ibnP1z7y7V4n0WRz+50DY6+Myj0UaXVSuUlHohO+eZ8IJEnkg==", + "version": "5.50.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.50.0.tgz", + "integrity": "sha512-cdMeD9HGu6EXIeGOh2yVW6oGf9wq8asBgZx7nsR/D36gTfQ0odE5kcRYe5M81vjEFAcPeugXrHg78Imu55F6gg==", "dev": true, "requires": { - "@typescript-eslint/types": "5.49.0", + "@typescript-eslint/types": "5.50.0", "eslint-visitor-keys": "^3.3.0" } }, @@ -25273,14 +25273,14 @@ } }, "browserslist": { - "version": "4.21.4", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz", - "integrity": "sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==", + "version": "4.21.5", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz", + "integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==", "requires": { - "caniuse-lite": "^1.0.30001400", - "electron-to-chromium": "^1.4.251", - "node-releases": "^2.0.6", - "update-browserslist-db": "^1.0.9" + "caniuse-lite": "^1.0.30001449", + "electron-to-chromium": "^1.4.284", + "node-releases": "^2.0.8", + "update-browserslist-db": "^1.0.10" } }, "bser": { @@ -25358,9 +25358,9 @@ }, "dependencies": { "tslib": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", - "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", "dev": true } } @@ -25390,9 +25390,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001448", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001448.tgz", - "integrity": "sha512-tq2YI+MJnooG96XpbTRYkBxLxklZPOdLmNIOdIhvf7SNJan6u5vCKum8iT7ZfCt70m1GPkuC7P3TtX6UuhupuA==" + "version": "1.0.30001450", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001450.tgz", + "integrity": "sha512-qMBmvmQmFXaSxexkjjfMvD5rnDL0+m+dUMZKoDYsGG8iZN29RuYh9eRoMvKsT6uMAWlyUUGDEQGJJYjzCIO9ew==" }, "case-sensitive-paths-webpack-plugin": { "version": "2.4.0", @@ -25717,9 +25717,9 @@ } }, "content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", "dev": true }, "convert-source-map": { @@ -26320,9 +26320,9 @@ "dev": true }, "deepmerge": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", - "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.0.tgz", + "integrity": "sha512-z2wJZXrmeHdvYJp/Ux55wIjqo81G5Bp4c+oELTW+7ar6SogWHajt5a9gO3s3IDaGSAXjDk0vlQKN3rms8ab3og==", "dev": true }, "default-gateway": { @@ -26596,9 +26596,9 @@ }, "dependencies": { "tslib": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", - "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", "dev": true } } @@ -27009,9 +27009,9 @@ } }, "globals": { - "version": "13.19.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.19.0.tgz", - "integrity": "sha512-dkQ957uSRWHw7CFXLUtUHQI3g3aWApYhfNR2O6jn/907riyTYKVBmxYVROkBcY614FSSeSJh7Xm7SrUWCxvJMQ==", + "version": "13.20.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", + "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", "dev": true, "requires": { "type-fest": "^0.20.2" @@ -27268,29 +27268,29 @@ }, "dependencies": { "@typescript-eslint/scope-manager": { - "version": "5.49.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.49.0.tgz", - "integrity": "sha512-clpROBOiMIzpbWNxCe1xDK14uPZh35u4QaZO1GddilEzoCLAEz4szb51rBpdgurs5k2YzPtJeTEN3qVbG+LRUQ==", + "version": "5.50.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.50.0.tgz", + "integrity": "sha512-rt03kaX+iZrhssaT974BCmoUikYtZI24Vp/kwTSy841XhiYShlqoshRFDvN1FKKvU2S3gK+kcBW1EA7kNUrogg==", "dev": true, "requires": { - "@typescript-eslint/types": "5.49.0", - "@typescript-eslint/visitor-keys": "5.49.0" + "@typescript-eslint/types": "5.50.0", + "@typescript-eslint/visitor-keys": "5.50.0" } }, "@typescript-eslint/types": { - "version": "5.49.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.49.0.tgz", - "integrity": "sha512-7If46kusG+sSnEpu0yOz2xFv5nRz158nzEXnJFCGVEHWnuzolXKwrH5Bsf9zsNlOQkyZuk0BZKKoJQI+1JPBBg==", + "version": "5.50.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.50.0.tgz", + "integrity": "sha512-atruOuJpir4OtyNdKahiHZobPKFvZnBnfDiyEaBf6d9vy9visE7gDjlmhl+y29uxZ2ZDgvXijcungGFjGGex7w==", "dev": true }, "@typescript-eslint/typescript-estree": { - "version": "5.49.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.49.0.tgz", - "integrity": "sha512-PBdx+V7deZT/3GjNYPVQv1Nc0U46dAHbIuOG8AZ3on3vuEKiPDwFE/lG1snN2eUB9IhF7EyF7K1hmTcLztNIsA==", + "version": "5.50.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.50.0.tgz", + "integrity": "sha512-Gq4zapso+OtIZlv8YNAStFtT6d05zyVCK7Fx3h5inlLBx2hWuc/0465C2mg/EQDDU2LKe52+/jN4f0g9bd+kow==", "dev": true, "requires": { - "@typescript-eslint/types": "5.49.0", - "@typescript-eslint/visitor-keys": "5.49.0", + "@typescript-eslint/types": "5.50.0", + "@typescript-eslint/visitor-keys": "5.50.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -27299,28 +27299,28 @@ } }, "@typescript-eslint/utils": { - "version": "5.49.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.49.0.tgz", - "integrity": "sha512-cPJue/4Si25FViIb74sHCLtM4nTSBXtLx1d3/QT6mirQ/c65bV8arBEebBJJizfq8W2YyMoPI/WWPFWitmNqnQ==", + "version": "5.50.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.50.0.tgz", + "integrity": "sha512-v/AnUFImmh8G4PH0NDkf6wA8hujNNcrwtecqW4vtQ1UOSNBaZl49zP1SHoZ/06e+UiwzHpgb5zP5+hwlYYWYAw==", "dev": true, "requires": { "@types/json-schema": "^7.0.9", "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.49.0", - "@typescript-eslint/types": "5.49.0", - "@typescript-eslint/typescript-estree": "5.49.0", + "@typescript-eslint/scope-manager": "5.50.0", + "@typescript-eslint/types": "5.50.0", + "@typescript-eslint/typescript-estree": "5.50.0", "eslint-scope": "^5.1.1", "eslint-utils": "^3.0.0", "semver": "^7.3.7" } }, "@typescript-eslint/visitor-keys": { - "version": "5.49.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.49.0.tgz", - "integrity": "sha512-v9jBMjpNWyn8B6k/Mjt6VbUS4J1GvUlR4x3Y+ibnP1z7y7V4n0WRz+50DY6+Myj0UaXVSuUlHohO+eZ8IJEnkg==", + "version": "5.50.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.50.0.tgz", + "integrity": "sha512-cdMeD9HGu6EXIeGOh2yVW6oGf9wq8asBgZx7nsR/D36gTfQ0odE5kcRYe5M81vjEFAcPeugXrHg78Imu55F6gg==", "dev": true, "requires": { - "@typescript-eslint/types": "5.49.0", + "@typescript-eslint/types": "5.50.0", "eslint-visitor-keys": "^3.3.0" } }, @@ -28770,9 +28770,9 @@ "dev": true }, "immer": { - "version": "9.0.18", - "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.18.tgz", - "integrity": "sha512-eAPNpsj7Ax1q6Y/3lm2PmlwRcFzpON7HSNQ3ru5WQH1/PSpnyed/HpNOELl2CxLKoj4r+bAHgdyKqW5gc2Se1A==", + "version": "9.0.19", + "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.19.tgz", + "integrity": "sha512-eY+Y0qcsB4TZKwgQzLaE/lqYMlKhv5J9dyd2RhhtGhNo2njPXDqU9XPfcNfa3MIDsdtZt5KlkIsirlo4dHsWdQ==", "dev": true }, "import-fresh": { @@ -30641,9 +30641,9 @@ } }, "@types/yargs": { - "version": "17.0.20", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.20.tgz", - "integrity": "sha512-eknWrTHofQuPk2iuqDm1waA7V6xPlbgBoaaXEgYkClhLOnB0TtbW+srJaOToAgawPxPlHQzwypFA2bhZaUGP5A==", + "version": "17.0.22", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.22.tgz", + "integrity": "sha512-pet5WJ9U8yPVRhkwuEIp5ktAeAqRZOq4UdAyWLWzxbtpyXnzbtLdKiXAjJzi/KLmPGS9wk86lUFWZFN6sISo4g==", "dev": true, "requires": { "@types/yargs-parser": "*" @@ -31492,9 +31492,9 @@ }, "dependencies": { "tslib": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", - "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", "dev": true } } @@ -31739,9 +31739,9 @@ }, "dependencies": { "tslib": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", - "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", "dev": true } } @@ -31759,9 +31759,9 @@ "dev": true }, "node-releases": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.8.tgz", - "integrity": "sha512-dFSmB8fFHEH/s81Xi+Y/15DQY6VHW81nXRj86EMSL3lmuTmK1e+aT4wrFCkTbm+gSwkw4KpX+rT/pMM2c1mF+A==" + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.9.tgz", + "integrity": "sha512-2xfmOrRkGogbTK9R6Leda0DGiXeY3p2NJpy4+gNCffdUvV6mdEJnaDEic1i3Ec2djAo8jWYoJMR5PB0MSMpxUA==" }, "normalize-path": { "version": "3.0.0", @@ -32017,9 +32017,9 @@ }, "dependencies": { "tslib": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", - "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", "dev": true } } @@ -32066,9 +32066,9 @@ }, "dependencies": { "tslib": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", - "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", "dev": true } } @@ -33892,9 +33892,9 @@ }, "dependencies": { "tslib": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", - "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", "dev": true } } @@ -34144,9 +34144,9 @@ "dev": true }, "shell-quote": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.4.tgz", - "integrity": "sha512-8o/QEhSSRb1a5i7TFR0iM4G16Z0vYB2OQVs4G3aAFXjn3T6yEx8AZxy1PgDF7I00LZHYA3WxaSYIf5e5sAX8Rw==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.0.tgz", + "integrity": "sha512-QHsz8GgQIGKlRi24yFc6a6lN69Idnx634w49ay6+jA5yFh7a1UY+4Rp6HPx/L/1zcEDPEij8cIsiqR6bQsE5VQ==", "dev": true }, "side-channel": { @@ -34690,9 +34690,9 @@ } }, "terser": { - "version": "5.16.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.16.1.tgz", - "integrity": "sha512-xvQfyfA1ayT0qdK47zskQgRZeWLoOQ8JQ6mIgRGVNwZKdQMU+5FkCBjmv4QjcrTzyZquRw2FVtlJSRUmMKQslw==", + "version": "5.16.2", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.16.2.tgz", + "integrity": "sha512-JKuM+KvvWVqT7muHVyrwv7FVRPnmHDwF6XwoIxdbF5Witi0vu99RYpxDexpJndXt3jbZZmmWr2/mQa6HvSNdSg==", "dev": true, "requires": { "@jridgewell/source-map": "^0.3.2", diff --git a/src/App.tsx b/src/App.tsx index 264d136..e4ff133 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -265,6 +265,13 @@ export default function App() component: , }); + routeList.push({ + name: `${app.label}`, + key: app.name, + route: `${path}/savedFilter/:id`, + component: , + }); + routeList.push({ name: `${app.label} Create`, key: `${app.name}.create`, diff --git a/src/qqq/components/buttons/DefaultButtons.tsx b/src/qqq/components/buttons/DefaultButtons.tsx index 8550964..44d2459 100644 --- a/src/qqq/components/buttons/DefaultButtons.tsx +++ b/src/qqq/components/buttons/DefaultButtons.tsx @@ -28,13 +28,17 @@ import MDButton from "qqq/components/legacy/MDButton"; // eslint-disable import/prefer-default-export -const standardWidth = "150px"; +export const standardWidth = "150px"; -export function QCreateNewButton(): JSX.Element +interface QCreateNewButtonProps +{ + tablePath: string; +} +export function QCreateNewButton({tablePath}: QCreateNewButtonProps): JSX.Element { return ( - + add}> Create New @@ -116,6 +120,23 @@ export function QActionsMenuButton({isOpen, onClickHandler}: QActionsMenuButtonP ); } +export function QSavedFiltersMenuButton({isOpen, onClickHandler}: QActionsMenuButtonProps): JSX.Element +{ + return ( + + + saved filters  + keyboard_arrow_down + + + ); +} + interface QCancelButtonProps { onClickHandler: any; diff --git a/src/qqq/components/horseshoe/Breadcrumbs.tsx b/src/qqq/components/horseshoe/Breadcrumbs.tsx index 4aa2840..d116d06 100644 --- a/src/qqq/components/horseshoe/Breadcrumbs.tsx +++ b/src/qqq/components/horseshoe/Breadcrumbs.tsx @@ -67,6 +67,11 @@ function QBreadcrumbs({icon, title, route, light}: Props): JSX.Element let accumulatedPath = ""; for (let i = 0; i < routes.length; i++) { + if(routes[i] === "savedFilter") + { + continue; + } + accumulatedPath = `${accumulatedPath}/${routes[i]}`; fullRoutes.push(accumulatedPath); pageTitle = `${routeToLabel(routes[i])} | ${pageTitle}`; diff --git a/src/qqq/components/misc/SavedFilters.tsx b/src/qqq/components/misc/SavedFilters.tsx new file mode 100644 index 0000000..258c4f2 --- /dev/null +++ b/src/qqq/components/misc/SavedFilters.tsx @@ -0,0 +1,526 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2022. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import {QController} from "@kingsrook/qqq-frontend-core/lib/controllers/QController"; +import {QInstance} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QInstance"; +import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData"; +import {QJobComplete} from "@kingsrook/qqq-frontend-core/lib/model/processes/QJobComplete"; +import {QJobError} from "@kingsrook/qqq-frontend-core/lib/model/processes/QJobError"; +import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord"; +import {KeyboardArrowDown} from "@mui/icons-material"; +import {Alert, ClickAwayListener, Grow, MenuList, Paper, Popper} from "@mui/material"; +import Box from "@mui/material/Box"; +import Button from "@mui/material/Button"; +import ButtonGroup from "@mui/material/ButtonGroup"; +import Dialog from "@mui/material/Dialog"; +import DialogActions from "@mui/material/DialogActions"; +import DialogContent from "@mui/material/DialogContent"; +import DialogTitle from "@mui/material/DialogTitle"; +import Menu from "@mui/material/Menu"; +import MenuItem from "@mui/material/MenuItem"; +import TextField from "@mui/material/TextField"; +import Typography from "@mui/material/Typography"; +import {GridFilterModel, GridSortItem} from "@mui/x-data-grid-pro"; +import FormData from "form-data"; +import React, {useEffect, useRef, useState} from "react"; +import {useLocation, useNavigate} from "react-router-dom"; +import {QCancelButton, QDeleteButton, QSaveButton, QSavedFiltersMenuButton} from "qqq/components/buttons/DefaultButtons"; +import FilterUtils from "qqq/utils/qqq/FilterUtils"; + +interface Props +{ + qController: QController; + metaData: QInstance; + tableMetaData: QTableMetaData; + currentSavedFilter: QRecord; + filterModel?: GridFilterModel; + columnSortModel?: GridSortItem[]; + filterOnChangeCallback?: (selectedSavedFilterId: number) => void; +} + +function SavedFilters({qController, metaData, tableMetaData, currentSavedFilter, filterModel, columnSortModel, filterOnChangeCallback}: Props): JSX.Element +{ + const navigate = useNavigate(); + + const [savedFilters, setSavedFilters] = useState([] as QRecord[]); + const [savedFiltersMenu, setSavedFiltersMenu] = useState(null); + const [savedFiltersHaveLoaded, setSavedFiltersHaveLoaded] = useState(false); + const [filterIsModified, setFilterIsModified] = useState(false); + + const [saveFilterPopupOpen, setSaveFilterPopupOpen] = useState(false); + const [isSaveFilterAs, setIsSaveFilterAs] = useState(false); + const [isRenameFilter, setIsRenameFilter] = useState(false); + const [isDeleteFilter, setIsDeleteFilter] = useState(false); + const [savedFilterNameInputValue, setSavedFilterNameInputValue] = useState(null as string); + const [popupAlertContent, setPopupAlertContent] = useState(""); + + const anchorRef = useRef(null); + const location = useLocation(); + const [saveOptionsOpen, setSaveOptionsOpen] = useState(false); + + const SAVE_OPTION = "Save Filter..."; + const SAVE_AS_OPTION = "Create A New Filter From This Filter..."; + const RENAME_OPTION = "Rename This Filter..."; + const CLEAR_OPTION = "Clear This Filter"; + const DELETE_OPTION = "Delete This Filter..."; + const dropdownOptions = [SAVE_AS_OPTION, RENAME_OPTION, CLEAR_OPTION, DELETE_OPTION]; + + const openSavedFiltersMenu = (event: any) => setSavedFiltersMenu(event.currentTarget); + const closeSavedFiltersMenu = () => setSavedFiltersMenu(null); + + ////////////////////////////////////////////////////////////////////////// + // load filters on first run, then monitor location or metadata changes // + ////////////////////////////////////////////////////////////////////////// + useEffect(() => + { + loadSavedFilters() + .then(() => + { + if (currentSavedFilter != null) + { + let qFilter = FilterUtils.buildQFilterFromGridFilter(filterModel, columnSortModel); + setFilterIsModified(JSON.stringify(qFilter) !== currentSavedFilter.values.get("filterJson")); + } + + setSavedFiltersHaveLoaded(true); + }); + }, [location , tableMetaData, currentSavedFilter, filterModel, columnSortModel]) + + + + /******************************************************************************* + ** make request to load all saved filters from backend + *******************************************************************************/ + async function loadSavedFilters() + { + if (! tableMetaData) + { + return; + } + + const formData = new FormData(); + formData.append("tableName", tableMetaData.name); + + let savedFilters = await makeSavedFilterRequest("querySavedFilter", formData); + setSavedFilters(savedFilters); + } + + + + /******************************************************************************* + ** fired when a saved record is clicked from the dropdown + *******************************************************************************/ + const handleSavedFilterRecordOnClick = async (record: QRecord) => + { + setSaveFilterPopupOpen(false); + closeSavedFiltersMenu(); + filterOnChangeCallback(record.values.get("id")); + navigate(`${metaData.getTablePathByName(tableMetaData.name)}/savedFilter/${record.values.get("id")}`); + }; + + + + /******************************************************************************* + ** fired when a save option is selected from the save... button/dropdown combo + *******************************************************************************/ + const handleDropdownOptionClick = (optionName: string) => + { + setSaveOptionsOpen(false); + setPopupAlertContent(null); + closeSavedFiltersMenu(); + setSavedFilterNameInputValue(null); + setSaveFilterPopupOpen(true); + setIsSaveFilterAs(false); + setIsRenameFilter(false); + setIsDeleteFilter(false) + + switch(optionName) + { + case SAVE_OPTION: + break; + case SAVE_AS_OPTION: + setIsSaveFilterAs(true); + break; + case CLEAR_OPTION: + setSaveFilterPopupOpen(false) + filterOnChangeCallback(null); + navigate(metaData.getTablePathByName(tableMetaData.name)); + break; + case RENAME_OPTION: + setIsRenameFilter(true); + break; + case DELETE_OPTION: + setIsDeleteFilter(true) + break; + } + } + + + + /******************************************************************************* + ** fired when save or delete button saved on confirmation dialogs + *******************************************************************************/ + async function handleFilterDialogButtonOnClick() + { + try + { + const formData = new FormData(); + if (isDeleteFilter) + { + formData.append("id", currentSavedFilter.values.get("id")); + await makeSavedFilterRequest("deleteSavedFilter", formData); + await(async() => + { + handleDropdownOptionClick(CLEAR_OPTION); + })(); + } + else + { + formData.append("tableName", tableMetaData.name); + formData.append("filterJson", JSON.stringify(FilterUtils.buildQFilterFromGridFilter(filterModel, columnSortModel))); + + if (isSaveFilterAs || isRenameFilter || currentSavedFilter == null) + { + formData.append("label", savedFilterNameInputValue); + if(currentSavedFilter != null && isRenameFilter) + { + formData.append("id", currentSavedFilter.values.get("id")); + } + } + else + { + formData.append("id", currentSavedFilter.values.get("id")); + formData.append("label", currentSavedFilter?.values.get("label")); + } + const recordList = await makeSavedFilterRequest("storeSavedFilter", formData); + await(async() => + { + if (recordList && recordList.length > 0) + { + setSavedFiltersHaveLoaded(false); + loadSavedFilters(); + handleSavedFilterRecordOnClick(recordList[0]); + } + })(); + } + } + catch (e: any) + { + setPopupAlertContent(JSON.stringify(e.message)); + } + } + + + + /******************************************************************************* + ** hides/shows the save options + *******************************************************************************/ + const handleToggleSaveOptions = () => + { + setSaveOptionsOpen((prevOpen) => !prevOpen); + }; + + + + /******************************************************************************* + ** closes save options menu (on clickaway) + *******************************************************************************/ + const handleSaveOptionsMenuClose = (event: Event) => + { + if (anchorRef.current && anchorRef.current.contains(event.target as HTMLElement)) + { + return; + } + + setSaveOptionsOpen(false); + }; + + + + /******************************************************************************* + ** stores the current dialog input text to state + *******************************************************************************/ + const handleSaveFilterInputChange = (event: React.ChangeEvent) => + { + setSavedFilterNameInputValue(event.target.value); + }; + + + + /******************************************************************************* + ** closes current dialog + *******************************************************************************/ + const handleSaveFilterPopupClose = () => + { + setSaveFilterPopupOpen(false); + }; + + + + /******************************************************************************* + ** make a request to the backend for various savedFilter processes + *******************************************************************************/ + async function makeSavedFilterRequest(processName: string, formData: FormData): Promise + { + ///////////////////////// + // fetch saved filters // + ///////////////////////// + let savedFilters = [] as QRecord[] + try + { + ////////////////////////////////////////////////////////////////// + // we don't want this job to go async, so, pass a large timeout // + ////////////////////////////////////////////////////////////////// + formData.append("_qStepTimeoutMillis", 60 * 1000); + + const formDataHeaders = { + "content-type": "multipart/form-data; boundary=--------------------------320289315924586491558366", + }; + + const processResult = await qController.processInit(processName, formData, formDataHeaders); + if (processResult instanceof QJobError) + { + const jobError = processResult as QJobError; + throw(jobError.error); + } + else + { + const result = processResult as QJobComplete; + if(result.values.savedFilterList) + { + for (let i = 0; i < result.values.savedFilterList.length; i++) + { + const qRecord = new QRecord(result.values.savedFilterList[i]); + savedFilters.push(qRecord); + } + } + } + } + catch (e) + { + throw(e); + } + + return (savedFilters); + } + + const renderSavedFiltersMenu = tableMetaData && ( + + { + savedFilters && savedFilters.length > 0 ? ( + savedFilters.map((record: QRecord, index: number) => + handleSavedFilterRecordOnClick(record)}> + {record.values.get("label")} + + ) + ): ( + + No filters have been saved for this table. + + ) + } + + ); + + + const hasStorePermission = metaData?.processes.has("storeSavedFilter"); + const hasDeletePermission = metaData?.processes.has("deleteSavedFilter"); + const hasQueryPermission = metaData?.processes.has("querySavedFilter"); + + return ( + hasQueryPermission && tableMetaData ? ( + + + {renderSavedFiltersMenu} + + + + { + savedFiltersHaveLoaded && ( + currentSavedFilter ? ( + Current Filter:  + + {currentSavedFilter.values.get("label")} + { + filterIsModified && ( +  * + + ) + } + + + ) : ( +   +   + + ) + ) + } + + + + { + hasStorePermission && ( + + ) + } + { + currentSavedFilter && ( + + ) + } + + + {({TransitionProps, placement}) => ( + + + + + {dropdownOptions.map((option, index) => ( + (option === CLEAR_OPTION || ((option !== DELETE_OPTION || hasDeletePermission) && (option !== SAVE_AS_OPTION || hasStorePermission))) && ( + handleDropdownOptionClick(option)} + > + {option} + + ) + ))} + + + + + )} + + + + { + + { + currentSavedFilter ? ( + isDeleteFilter ? ( + Delete Filter + ) : ( + isSaveFilterAs ? ( + Save Filter As + ):( + isRenameFilter ? ( + Rename Filter + ):( + Update Existing Filter + ) + ) + ) + ):( + Save New Filter + ) + } + + { + (! currentSavedFilter || isSaveFilterAs || isRenameFilter) && ! isDeleteFilter ? ( + + { + isSaveFilterAs ? ( + Enter a name for this new saved filter. + ):( + Enter a new name for this saved filter. + ) + } + + + ):( + isDeleteFilter ? ( + Are you sure you want to delete the filter {`'${currentSavedFilter?.values.get("label")}'`}? + + ):( + Are you sure you want to update the filter {`'${currentSavedFilter?.values.get("label")}'`} with the current filter criteria? + ) + ) + } + {popupAlertContent ? ( + + {popupAlertContent} + + ) : ("")} + + + + { + isDeleteFilter ? + + : + + } + + + } + + ) : null + ); +} + +export default SavedFilters; diff --git a/src/qqq/pages/records/query/RecordQuery.tsx b/src/qqq/pages/records/query/RecordQuery.tsx index b55c486..1502b25 100644 --- a/src/qqq/pages/records/query/RecordQuery.tsx +++ b/src/qqq/pages/records/query/RecordQuery.tsx @@ -20,10 +20,12 @@ */ import {Capability} from "@kingsrook/qqq-frontend-core/lib/model/metaData/Capability"; +import {QInstance} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QInstance"; import {QProcessMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QProcessMetaData"; import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData"; -import {QFilterCriteria} from "@kingsrook/qqq-frontend-core/lib/model/query/QFilterCriteria"; -import {QFilterOrderBy} from "@kingsrook/qqq-frontend-core/lib/model/query/QFilterOrderBy"; +import {QJobComplete} from "@kingsrook/qqq-frontend-core/lib/model/processes/QJobComplete"; +import {QJobError} from "@kingsrook/qqq-frontend-core/lib/model/processes/QJobError"; +import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord"; import {QQueryFilter} from "@kingsrook/qqq-frontend-core/lib/model/query/QQueryFilter"; import {Alert, TablePagination} from "@mui/material"; import Box from "@mui/material/Box"; @@ -42,21 +44,23 @@ import Menu from "@mui/material/Menu"; import MenuItem from "@mui/material/MenuItem"; import Modal from "@mui/material/Modal"; import Tooltip from "@mui/material/Tooltip"; -import {DataGridPro, GridCallbackDetails, GridColDef, GridColumnOrderChangeParams, GridColumnVisibilityModel, GridDensity, GridEventListener, GridExportMenuItemProps, GridFilterModel, GridLinkOperator, GridPinnedColumns, gridPreferencePanelStateSelector, GridRowId, GridRowParams, GridRowsProp, GridSelectionModel, GridSortItem, GridSortModel, GridState, GridToolbarColumnsButton, GridToolbarContainer, GridToolbarDensitySelector, GridToolbarExportContainer, GridToolbarFilterButton, MuiEvent, useGridApiContext, useGridApiEventHandler, useGridSelector} from "@mui/x-data-grid-pro"; +import {DataGridPro, GridCallbackDetails, GridColDef, GridColumnOrderChangeParams, GridColumnVisibilityModel, GridDensity, GridEventListener, GridExportMenuItemProps, GridFilterModel, GridPinnedColumns, gridPreferencePanelStateSelector, GridRowId, GridRowParams, GridRowsProp, GridSelectionModel, GridSortItem, GridSortModel, GridState, GridToolbarColumnsButton, GridToolbarContainer, GridToolbarDensitySelector, GridToolbarExportContainer, GridToolbarFilterButton, MuiEvent, useGridApiContext, useGridApiEventHandler, useGridSelector} from "@mui/x-data-grid-pro"; +import FormData from "form-data"; import React, {useContext, useEffect, useReducer, useRef, useState} from "react"; import {useLocation, useNavigate, useSearchParams} from "react-router-dom"; import QContext from "QContext"; import {QActionsMenuButton, QCreateNewButton} from "qqq/components/buttons/DefaultButtons"; import Footer from "qqq/components/horseshoe/Footer"; import NavBar from "qqq/components/horseshoe/NavBar"; +import SavedFilters from "qqq/components/misc/SavedFilters"; import DashboardLayout from "qqq/layouts/DashboardLayout"; import ProcessRun from "qqq/pages/processes/ProcessRun"; import DataGridUtils from "qqq/utils/DataGridUtils"; import Client from "qqq/utils/qqq/Client"; import FilterUtils from "qqq/utils/qqq/FilterUtils"; import ProcessUtils from "qqq/utils/qqq/ProcessUtils"; -import ValueUtils from "qqq/utils/qqq/ValueUtils"; +const CURRENT_SAVED_FILTER_ID_LOCAL_STORAGE_KEY_ROOT = "qqq.currentSavedFilterId"; const COLUMN_VISIBILITY_LOCAL_STORAGE_KEY_ROOT = "qqq.columnVisibility"; const COLUMN_SORT_LOCAL_STORAGE_KEY_ROOT = "qqq.columnSort"; const FILTER_LOCAL_STORAGE_KEY_ROOT = "qqq.filter"; @@ -77,86 +81,6 @@ RecordQuery.defaultProps = { const qController = Client.getInstance(); -/******************************************************************************* - ** Get the default filter to use on the page - either from query string, or - ** local storage, or a default (empty). - *******************************************************************************/ -async function getDefaultFilter(tableMetaData: QTableMetaData, searchParams: URLSearchParams, filterLocalStorageKey: string): Promise -{ - if (tableMetaData.fields !== undefined) - { - if (searchParams.has("filter")) - { - try - { - const qQueryFilter = JSON.parse(searchParams.get("filter")) as QQueryFilter; - - ////////////////////////////////////////////////////////////////// - // translate from a qqq-style filter to one that the grid wants // - ////////////////////////////////////////////////////////////////// - const defaultFilter = {items: []} as GridFilterModel; - let id = 1; - - for (let i = 0; i < qQueryFilter.criteria.length; i++) - { - const criteria = qQueryFilter.criteria[i]; - const field = tableMetaData.fields.get(criteria.fieldName); - let values = criteria.values; - if (field.possibleValueSourceName) - { - ////////////////////////////////////////////////////////////////////////////////// - // possible-values in query-string are expected to only be their id values. // - // e.g., ...values=[1]... // - // but we need them to be possibleValue objects (w/ id & label) so the label // - // can be shown in the filter dropdown. So, make backend call to look them up. // - ////////////////////////////////////////////////////////////////////////////////// - if (values && values.length > 0) - { - values = await qController.possibleValues(tableMetaData.name, field.name, "", values); - } - - //////////////////////////////////////////// - // log message if no values were returned // - //////////////////////////////////////////// - if (! values || values.length === 0) - { - console.warn("WARNING: No possible values were returned for [" + field.possibleValueSourceName + "] for values [" + criteria.values + "]."); - } - } - - defaultFilter.items.push({ - columnField: criteria.fieldName, - operatorValue: FilterUtils.qqqCriteriaOperatorToGrid(criteria.operator, field, values), - value: FilterUtils.qqqCriteriaValuesToGrid(criteria.operator, values, field), - id: id++, // not sure what this id is!! - }); - } - - defaultFilter.linkOperator = GridLinkOperator.And; - if (qQueryFilter.booleanOperator === "OR") - { - defaultFilter.linkOperator = GridLinkOperator.Or; - } - - return (defaultFilter); - } - catch (e) - { - console.warn("Error parsing filter from query string", e); - } - } - - if (localStorage.getItem(filterLocalStorageKey)) - { - const defaultFilter = JSON.parse(localStorage.getItem(filterLocalStorageKey)); - console.log(`Got default from LS: ${JSON.stringify(defaultFilter)}`); - return (defaultFilter); - } - } - - return ({items: []}); -} - function RecordQuery({table, launchProcess}: Props): JSX.Element { const tableName = table.name; @@ -170,6 +94,7 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element //////////////////////////////////////////// // look for defaults in the local storage // //////////////////////////////////////////// + const currentSavedFilterLocalStorageKey = `${CURRENT_SAVED_FILTER_ID_LOCAL_STORAGE_KEY_ROOT}.${tableName}`; const sortLocalStorageKey = `${COLUMN_SORT_LOCAL_STORAGE_KEY_ROOT}.${tableName}`; const rowsPerPageLocalStorageKey = `${ROWS_PER_PAGE_LOCAL_STORAGE_KEY_ROOT}.${tableName}`; const pinnedColumnsLocalStorageKey = `${PINNED_COLUMNS_LOCAL_STORAGE_KEY_ROOT}.${tableName}`; @@ -198,8 +123,6 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element { defaultPinnedColumns = JSON.parse(localStorage.getItem(pinnedColumnsLocalStorageKey)); } - - if (localStorage.getItem(rowsPerPageLocalStorageKey)) { defaultRowsPerPage = JSON.parse(localStorage.getItem(rowsPerPageLocalStorageKey)); @@ -217,6 +140,7 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element const [pinnedColumns, setPinnedColumns] = useState(defaultPinnedColumns); const [tableState, setTableState] = useState(""); + const [metaData, setMetaData] = useState(null as QInstance); const [tableMetaData, setTableMetaData] = useState(null as QTableMetaData); const [defaultFilterLoaded, setDefaultFilterLoaded] = useState(false); const [actionsMenu, setActionsMenu] = useState(null); @@ -236,6 +160,7 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element const [gridPreferencesWindow, setGridPreferencesWindow] = useState(undefined); const [showClearFiltersWarning, setShowClearFiltersWarning] = useState(false); const [hasValidFilters, setHasValidFilters] = useState(false); + const [currentSavedFilter, setCurrentSavedFilter] = useState(null as QRecord); const [activeModalProcess, setActiveModalProcess] = useState(null as QProcessMetaData); const [launchingProcess, setLaunchingProcess] = useState(launchProcess); @@ -285,6 +210,60 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element console.log(`Couldn't find process named ${processName}`); } } + + ///////////////////////////////////////////////////////////////////// + // the path for a savedFilter looks like: .../table/savedFilter/32 // + // so if path has '/savedFilter/' get last parsed string // + ///////////////////////////////////////////////////////////////////// + let currentSavedFilterId = null as number; + if(location.pathname.indexOf("/savedFilter/") != -1) + { + const parts = location.pathname.split("/"); + currentSavedFilterId = Number.parseInt(parts[parts.length - 1]); + } + else + { + if (localStorage.getItem(currentSavedFilterLocalStorageKey)) + { + currentSavedFilterId = Number.parseInt(localStorage.getItem(currentSavedFilterLocalStorageKey)); + navigate(`${metaData.getTablePathByName(tableName)}/savedFilter/${currentSavedFilterId}`); + } + else + { + setCurrentSavedFilter(null); + } + } + + if(currentSavedFilterId != null) + { + (async () => + { + const formData = new FormData(); + formData.append("id", currentSavedFilterId); + + ////////////////////////////////////////////////////////////////// + // we don't want this job to go async, so, pass a large timeout // + ////////////////////////////////////////////////////////////////// + formData.append("_qStepTimeoutMillis", 60 * 1000); + + const formDataHeaders = { + "content-type": "multipart/form-data; boundary=--------------------------320289315924586491558366", + }; + + const processResult = await qController.processInit("querySavedFilter", formData, formDataHeaders); + if (processResult instanceof QJobError) + { + const jobError = processResult as QJobError; + console.error("Could not retrieve saved filter: " + jobError.userFacingError); + } + else + { + const result = processResult as QJobComplete; + const qRecord = new QRecord(result.values.savedFilterList[0]); + setCurrentSavedFilter(qRecord); + } + })(); + } } catch (e) { @@ -294,69 +273,19 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element //////////////////////////////////////////////////////////////////////////////////// // if we didn't open a process... not sure what we do in the table/query use-case // //////////////////////////////////////////////////////////////////////////////////// - setActiveModalProcess(null); - }, [ location ]); + }, [ location , tableMetaData]); + + const buildQFilter = (filterModel: GridFilterModel) => { - console.log("Building q filter with model:"); - console.log(filterModel); - - const qFilter = new QQueryFilter(); - if (columnSortModel) - { - columnSortModel.forEach((gridSortItem) => - { - qFilter.addOrderBy(new QFilterOrderBy(gridSortItem.field, gridSortItem.sort === "asc")); - }); - } - - if (filterModel) - { - let foundFilter = false; - filterModel.items.forEach((item) => - { - ///////////////////////////////////////////////////////////////////////// - // set the values for these operators that otherwise don't have values // - ///////////////////////////////////////////////////////////////////////// - if(item.operatorValue === "isTrue") - { - item.value = [true]; - } - else if(item.operatorValue === "isFalse") - { - item.value = [false]; - } - - //////////////////////////////////////////////////////////////////////////////// - // if no value set and not 'empty' or 'not empty' operators, skip this filter // - //////////////////////////////////////////////////////////////////////////////// - if((! item.value || item.value.length == 0) && item.operatorValue !== "isEmpty" && item.operatorValue !== "isNotEmpty") - { - return; - } - - const operator = FilterUtils.gridCriteriaOperatorToQQQ(item.operatorValue); - const values = FilterUtils.gridCriteriaValueToQQQ(operator, item.value, item.operatorValue); - qFilter.addCriteria(new QFilterCriteria(item.columnField, operator, values)); - foundFilter = true; - }); - setHasValidFilters(foundFilter); - - qFilter.booleanOperator = "AND"; - if (filterModel.linkOperator == "or") - { - /////////////////////////////////////////////////////////////////////////////////////////// - // by default qFilter uses AND - so only if we see linkOperator=or do we need to set it // - /////////////////////////////////////////////////////////////////////////////////////////// - qFilter.booleanOperator = "OR"; - } - } - - return qFilter; + const filter = FilterUtils.buildQFilterFromGridFilter(filterModel, columnSortModel); + setHasValidFilters(filter.criteria && filter.criteria.length > 0); + return(filter); }; + const getTableMetaData = async (): Promise => { if(tableMetaData !== null) @@ -388,10 +317,13 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element if (!defaultFilterLoaded) { setDefaultFilterLoaded(true); - localFilterModel = await getDefaultFilter(tableMetaData, searchParams, filterLocalStorageKey); - setFilterModel(localFilterModel); + + let models = await FilterUtils.determineFilterAndSortModels(qController, tableMetaData, null, searchParams, filterLocalStorageKey, sortLocalStorageKey); + setFilterModel(models.filter); + setColumnSortModel(models.sort); return; } + setTableMetaData(tableMetaData); setTableLabel(tableMetaData.label); if (columnSortModel.length === 0) @@ -588,7 +520,7 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element clearTimeout(instance.current.timer); instance.current.timer = setTimeout(() => { - navigate(`${params.id}`); + navigate(`${metaData.getTablePathByName(tableName)}/${params.id}`); }, 100); } else @@ -663,7 +595,7 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element { setTableState(tableName); const metaData = await qController.loadMetaData(); - ValueUtils.qInstance = metaData; + setMetaData(metaData); setTableProcesses(ProcessUtils.getProcessesForTable(metaData, tableName)); // these are the ones to show in the dropdown setAllTableProcesses(ProcessUtils.getProcessesForTable(metaData, tableName, true)); // these include hidden ones (e.g., to find the bulks) @@ -813,7 +745,7 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element setRecordIdsForProcess(""); } - navigate(`${process.name}${getRecordsQueryString()}`); + navigate(`${metaData?.getTablePathByName(tableName)}/${process.name}${getRecordsQueryString()}`); closeActionsMenu(); }; @@ -922,7 +854,7 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element component="div" // note - passing null here makes the 'to' param in the defaultLabelDisplayedRows also be null, // so pass some sentinel value... - count={totalRecords === null ? -1 : totalRecords} + count={totalRecords === null || totalRecords === undefined ? -1 : totalRecords} page={pageNumber} rowsPerPageOptions={[ 10, 25, 50, 100, 250 ]} rowsPerPage={rowsPerPage} @@ -940,6 +872,54 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element ); } + async function handleSavedFilterChange(selectedSavedFilterId: number) + { + if(selectedSavedFilterId != null) + { + const qRecord = await fetchSavedFilter(selectedSavedFilterId); + const models = await FilterUtils.determineFilterAndSortModels(qController, tableMetaData, qRecord.values.get("filterJson"), null, null,null); + handleFilterChange(models.filter); + handleSortChange(models.sort); + localStorage.setItem(currentSavedFilterLocalStorageKey, selectedSavedFilterId.toString()); + } + else + { + handleFilterChange({items: []} as GridFilterModel); + handleSortChange([{field: tableMetaData.primaryKeyField, sort: "desc"}]); + localStorage.removeItem(currentSavedFilterLocalStorageKey); + } + } + + async function fetchSavedFilter(filterId: number):Promise + { + let qRecord = null; + const formData = new FormData(); + formData.append("id", filterId); + + ////////////////////////////////////////////////////////////////// + // we don't want this job to go async, so, pass a large timeout // + ////////////////////////////////////////////////////////////////// + formData.append("_qStepTimeoutMillis", 60 * 1000); + + const formDataHeaders = { + "content-type": "multipart/form-data; boundary=--------------------------320289315924586491558366", + }; + + const processResult = await qController.processInit("querySavedFilter", formData, formDataHeaders); + if (processResult instanceof QJobError) + { + const jobError = processResult as QJobError; + console.error("Could not retrieve saved filter: " + jobError.userFacingError); + } + else + { + const result = processResult as QJobComplete; + qRecord = new QRecord(result.values.savedFilterList[0]); + } + + return(qRecord); + } + function CustomToolbar() { const handleMouseDown: GridEventListener<"cellMouseDown"> = ( @@ -1003,6 +983,7 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element @@ -1120,7 +1101,7 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element //////////////////////////////////////////////////////////////////////////////////////// updateTable(); } - }, [ pageNumber, rowsPerPage, columnSortModel ]); + }, [ pageNumber, rowsPerPage, columnSortModel, currentSavedFilter ]); /////////////////////////////////////////////////////////////////////////////////////////////////////////////// // for state changes that DO change the filter, call to update the table - and DO clear out the totalRecords // @@ -1185,15 +1166,17 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element ) : null } + + + {renderActionsMenu} - { table.capabilities.has(Capability.TABLE_INSERT) && table.insertPermission && - + } @@ -1215,7 +1198,7 @@ function RecordQuery({table, launchProcess}: Props): JSX.Element // getRowHeight={() => "auto"} // maybe nice? wraps values in cells... columns={columnsModel} rowBuffer={10} - rowCount={totalRecords === null ? 0 : totalRecords} + rowCount={totalRecords === null || totalRecords === undefined ? 0 : totalRecords} onPageSizeChange={handleRowsPerPageChange} onRowClick={handleRowClick} onStateChange={handleStateChange} diff --git a/src/qqq/utils/qqq/FilterUtils.ts b/src/qqq/utils/qqq/FilterUtils.ts index 65db5eb..8f04970 100644 --- a/src/qqq/utils/qqq/FilterUtils.ts +++ b/src/qqq/utils/qqq/FilterUtils.ts @@ -19,9 +19,15 @@ * along with this program. If not, see . */ +import {QController} from "@kingsrook/qqq-frontend-core/lib/controllers/QController"; import {QFieldMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldMetaData"; import {QFieldType} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldType"; +import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData"; import {QCriteriaOperator} from "@kingsrook/qqq-frontend-core/lib/model/query/QCriteriaOperator"; +import {QFilterCriteria} from "@kingsrook/qqq-frontend-core/lib/model/query/QFilterCriteria"; +import {QFilterOrderBy} from "@kingsrook/qqq-frontend-core/lib/model/query/QFilterOrderBy"; +import {QQueryFilter} from "@kingsrook/qqq-frontend-core/lib/model/query/QQueryFilter"; +import {GridFilterModel, GridLinkOperator, GridSortItem} from "@mui/x-data-grid-pro"; import ValueUtils from "qqq/utils/qqq/ValueUtils"; /******************************************************************************* @@ -301,7 +307,7 @@ class FilterUtils const fieldType = field.type; if (operator === QCriteriaOperator.IS_BLANK || operator === QCriteriaOperator.IS_NOT_BLANK) { - return (null); + return null; } else if (operator === QCriteriaOperator.IN || operator === QCriteriaOperator.NOT_IN || operator === QCriteriaOperator.BETWEEN || operator === QCriteriaOperator.NOT_BETWEEN) { @@ -321,6 +327,171 @@ class FilterUtils return (values[0]); }; + + + /******************************************************************************* + ** Get the default filter to use on the page - either from given filter string, query string param, or + ** local storage, or a default (empty). + *******************************************************************************/ + public static async determineFilterAndSortModels(qController: QController, tableMetaData: QTableMetaData, filterString: string, searchParams: URLSearchParams, filterLocalStorageKey: string, sortLocalStorageKey: string): Promise<{ filter: GridFilterModel, sort: GridSortItem[] }> + { + let defaultFilter = {items: []} as GridFilterModel; + let defaultSort = [] as GridSortItem[]; + + if (tableMetaData.fields !== undefined) + { + if (filterString != null || (searchParams && searchParams.has("filter"))) + { + try + { + const qQueryFilter = (filterString !== null) ? JSON.parse(filterString) : JSON.parse(searchParams.get("filter")) as QQueryFilter; + + ////////////////////////////////////////////////////////////////// + // translate from a qqq-style filter to one that the grid wants // + ////////////////////////////////////////////////////////////////// + let id = 1; + for (let i = 0; i < qQueryFilter?.criteria?.length; i++) + { + const criteria = qQueryFilter.criteria[i]; + const field = tableMetaData.fields.get(criteria.fieldName); + let values = criteria.values; + if (field.possibleValueSourceName) + { + ////////////////////////////////////////////////////////////////////////////////// + // possible-values in query-string are expected to only be their id values. // + // e.g., ...values=[1]... // + // but we need them to be possibleValue objects (w/ id & label) so the label // + // can be shown in the filter dropdown. So, make backend call to look them up. // + ////////////////////////////////////////////////////////////////////////////////// + if (values && values.length > 0) + { + values = await qController.possibleValues(tableMetaData.name, field.name, "", values); + } + + //////////////////////////////////////////// + // log message if no values were returned // + //////////////////////////////////////////// + if (!values || values.length === 0) + { + console.warn("WARNING: No possible values were returned for [" + field.possibleValueSourceName + "] for values [" + criteria.values + "]."); + } + } + + defaultFilter.items.push({ + columnField: criteria.fieldName, + operatorValue: FilterUtils.qqqCriteriaOperatorToGrid(criteria.operator, field, values), + value: FilterUtils.qqqCriteriaValuesToGrid(criteria.operator, values, field), + id: id++, // not sure what this id is!! + }); + } + + defaultFilter.linkOperator = GridLinkOperator.And; + if (qQueryFilter.booleanOperator === "OR") + { + defaultFilter.linkOperator = GridLinkOperator.Or; + } + + ////////////////////////////////////////////////////////////////// + // translate from a qqq-style filter to one that the grid wants // + ////////////////////////////////////////////////////////////////// + if (qQueryFilter.orderBys && qQueryFilter.orderBys.length > 0) + { + for (let i = 0; i < qQueryFilter.orderBys.length; i++) + { + const orderBy = qQueryFilter.orderBys[i]; + defaultSort.push({ + field: orderBy.fieldName, + sort: orderBy.isAscending ? "asc" : "desc" + }); + } + } + + return ({filter: defaultFilter, sort: defaultSort}); + } + catch (e) + { + console.warn("Error parsing filter from query string", e); + } + } + + if (localStorage.getItem(filterLocalStorageKey)) + { + defaultFilter = JSON.parse(localStorage.getItem(filterLocalStorageKey)); + console.log(`Got default from LS: ${JSON.stringify(defaultFilter)}`); + } + + if (localStorage.getItem(sortLocalStorageKey)) + { + defaultSort = JSON.parse(localStorage.getItem(sortLocalStorageKey)); + console.log(`Got default from LS: ${JSON.stringify(defaultSort)}`); + } + } + + return ({filter: defaultFilter, sort: defaultSort}); + } + + + /******************************************************************************* + ** build a qqq filter from a grid and column sort model + *******************************************************************************/ + public static buildQFilterFromGridFilter(filterModel: GridFilterModel, columnSortModel: GridSortItem[]): QQueryFilter + { + console.log("Building q filter with model:"); + console.log(filterModel); + + const qFilter = new QQueryFilter(); + if (columnSortModel) + { + columnSortModel.forEach((gridSortItem) => + { + qFilter.addOrderBy(new QFilterOrderBy(gridSortItem.field, gridSortItem.sort === "asc")); + }); + } + + if (filterModel) + { + let foundFilter = false; + filterModel.items.forEach((item) => + { + ///////////////////////////////////////////////////////////////////////// + // set the values for these operators that otherwise don't have values // + ///////////////////////////////////////////////////////////////////////// + if (item.operatorValue === "isTrue") + { + item.value = [true]; + } + else if (item.operatorValue === "isFalse") + { + item.value = [false]; + } + + //////////////////////////////////////////////////////////////////////////////// + // if no value set and not 'empty' or 'not empty' operators, skip this filter // + //////////////////////////////////////////////////////////////////////////////// + if ((!item.value || item.value.length == 0) && item.operatorValue !== "isEmpty" && item.operatorValue !== "isNotEmpty") + { + return; + } + + const operator = FilterUtils.gridCriteriaOperatorToQQQ(item.operatorValue); + const values = FilterUtils.gridCriteriaValueToQQQ(operator, item.value, item.operatorValue); + qFilter.addCriteria(new QFilterCriteria(item.columnField, operator, values)); + foundFilter = true; + }); + + qFilter.booleanOperator = "AND"; + if (filterModel.linkOperator == "or") + { + /////////////////////////////////////////////////////////////////////////////////////////// + // by default qFilter uses AND - so only if we see linkOperator=or do we need to set it // + /////////////////////////////////////////////////////////////////////////////////////////// + qFilter.booleanOperator = "OR"; + } + } + + return qFilter; + }; + } export default FilterUtils;