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 && (
+
+ );
+
+
+ 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}) => (
+
+
+
+
+
+
+
+ )}
+
+
+
+ {
+
+ }
+
+ ) : 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;