Parcourir la source

job selector (#177) (#199)

* feat: Job Selector initial UI commit

* feat: Job selector update, row select, and some table functionality

* feat: Job selector update, fix sorting, code cleanup.

* fix: Cleanup and bug fixes

* fix: PR comment fixes, code cleanup

* fix: PR comment fixes and code cleanup

* Job selector backend

* feat: Filtering implementation

* fix: Change location of functions

* fix: Fix for job_list and corechange useEffect

* feat: Integrating other parameters

* feat: Job selector update, row select, and some table functionality

* feat: Job selector update, fix sorting, code cleanup.

* fix: Cleanup and bug fixes

* fix: PR comment fixes, code cleanup

* fix: PR comment fixes and code cleanup

* Job selector backend

* feat: Filtering implementation

* fix: Change location of functions

* fix: Fix for job_list and corechange useEffect

* feat: Integrating other parameters

* Merge GuiCore

* various little fixes

* mypy

* fix: Date format fix

* fix: JobSelector fixes, merge issues, bug fix.

* fix merge issues. removed unused entityType

* fix: PR comments and code clean-up

* fix: lint issues

* #177 connect and finalize job  selector

* format

---------

Co-authored-by: Fred Lefévère-Laoide <Fred.Lefevere-Laoide@Taipy.io>
pavle-avaiga il y a 1 an
Parent
commit
9091f1b7b2

+ 256 - 226
gui/package-lock.json

@@ -1,12 +1,12 @@
 {
   "name": "taipy-gui-core",
-  "version": "2.3.0",
+  "version": "3.0.0",
   "lockfileVersion": 3,
   "requires": true,
   "packages": {
     "": {
       "name": "taipy-gui-core",
-      "version": "2.3.0",
+      "version": "3.0.0",
       "hasInstallScript": true,
       "dependencies": {
         "@emotion/react": "^11.10.6",
@@ -40,53 +40,61 @@
       }
     },
     "../../../.virtualenvs/taipy-OW_uNObx/Lib/site-packages/taipy/gui/webapp": {
-      "name": "taipy-gui",
       "version": "2.3.0"
     },
+    "node_modules/@aashutoshrathi/word-wrap": {
+      "version": "1.2.6",
+      "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz",
+      "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
     "node_modules/@babel/code-frame": {
-      "version": "7.21.4",
-      "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.21.4.tgz",
-      "integrity": "sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g==",
+      "version": "7.22.5",
+      "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.5.tgz",
+      "integrity": "sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ==",
       "dependencies": {
-        "@babel/highlight": "^7.18.6"
+        "@babel/highlight": "^7.22.5"
       },
       "engines": {
         "node": ">=6.9.0"
       }
     },
     "node_modules/@babel/helper-module-imports": {
-      "version": "7.21.4",
-      "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.21.4.tgz",
-      "integrity": "sha512-orajc5T2PsRYUN3ZryCEFeMDYwyw09c/pZeaQEZPH0MpKzSvn3e0uXsDBu3k03VI+9DBiRo+l22BfKTpKwa/Wg==",
+      "version": "7.22.5",
+      "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.5.tgz",
+      "integrity": "sha512-8Dl6+HD/cKifutF5qGd/8ZJi84QeAKh+CEe1sBzz8UayBBGg1dAIJrdHOcOM5b2MpzWL2yuotJTtGjETq0qjXg==",
       "dependencies": {
-        "@babel/types": "^7.21.4"
+        "@babel/types": "^7.22.5"
       },
       "engines": {
         "node": ">=6.9.0"
       }
     },
     "node_modules/@babel/helper-string-parser": {
-      "version": "7.21.5",
-      "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.21.5.tgz",
-      "integrity": "sha512-5pTUx3hAJaZIdW99sJ6ZUUgWq/Y+Hja7TowEnLNMm1VivRgZQL3vpBY3qUACVsvw+yQU6+YgfBVmcbLaZtrA1w==",
+      "version": "7.22.5",
+      "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz",
+      "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==",
       "engines": {
         "node": ">=6.9.0"
       }
     },
     "node_modules/@babel/helper-validator-identifier": {
-      "version": "7.19.1",
-      "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz",
-      "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==",
+      "version": "7.22.5",
+      "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz",
+      "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==",
       "engines": {
         "node": ">=6.9.0"
       }
     },
     "node_modules/@babel/highlight": {
-      "version": "7.18.6",
-      "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz",
-      "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==",
+      "version": "7.22.5",
+      "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.5.tgz",
+      "integrity": "sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw==",
       "dependencies": {
-        "@babel/helper-validator-identifier": "^7.18.6",
+        "@babel/helper-validator-identifier": "^7.22.5",
         "chalk": "^2.0.0",
         "js-tokens": "^4.0.0"
       },
@@ -159,9 +167,9 @@
       }
     },
     "node_modules/@babel/runtime": {
-      "version": "7.22.3",
-      "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.3.tgz",
-      "integrity": "sha512-XsDuspWKLUsxwCp6r7EhsExHtYfbe5oAGQ19kqngTdCPUoPQzOPdUbD/pB9PJiwb2ptYKQDjSJT3R6dC+EPqfQ==",
+      "version": "7.22.5",
+      "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.5.tgz",
+      "integrity": "sha512-ecjvYlnAaZ/KVneE/OdKYBYfgXV3Ptu6zQWmgEF7vwKhQnvVS6bjMD2XYgj+SNvQ1GfK/pjgokfPkC/2CO8CuA==",
       "dependencies": {
         "regenerator-runtime": "^0.13.11"
       },
@@ -170,12 +178,12 @@
       }
     },
     "node_modules/@babel/types": {
-      "version": "7.22.4",
-      "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.4.tgz",
-      "integrity": "sha512-Tx9x3UBHTTsMSW85WB2kphxYQVvrZ/t1FxD88IpSgIjiUJlCm9z+xWIDwyo1vffTwSqteqyznB8ZE9vYYk16zA==",
+      "version": "7.22.5",
+      "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.5.tgz",
+      "integrity": "sha512-zo3MIHGOkPOfoRXitsgHLjEXmlDaD/5KU1Uzuc9GNiZPhSqVxVRtxuPaSBZDsYZ9qV88AjtMtWW7ww98loJ9KA==",
       "dependencies": {
-        "@babel/helper-string-parser": "^7.21.5",
-        "@babel/helper-validator-identifier": "^7.19.1",
+        "@babel/helper-string-parser": "^7.22.5",
+        "@babel/helper-validator-identifier": "^7.22.5",
         "to-fast-properties": "^2.0.0"
       },
       "engines": {
@@ -372,9 +380,9 @@
       }
     },
     "node_modules/@eslint/js": {
-      "version": "8.42.0",
-      "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.42.0.tgz",
-      "integrity": "sha512-6SWlXpWU5AvId8Ac7zjzmIOqMOba/JWY8XZ4A7q7Gn1Vlfg/SFFIlrtHXt9nPn4op9ZPAkl91Jao+QQv3r/ukw==",
+      "version": "8.43.0",
+      "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.43.0.tgz",
+      "integrity": "sha512-s2UHCoiXfxMvmfzqoN+vrQ84ahUSYde9qNO1MdxmoEhyHWsfmwOpFlwYV+ePJEVc7gFnATGUi376WowX1N7tFg==",
       "dev": true,
       "engines": {
         "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
@@ -442,20 +450,6 @@
         "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
       }
     },
-    "node_modules/@jridgewell/gen-mapping": {
-      "version": "0.3.3",
-      "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz",
-      "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==",
-      "dev": true,
-      "dependencies": {
-        "@jridgewell/set-array": "^1.0.1",
-        "@jridgewell/sourcemap-codec": "^1.4.10",
-        "@jridgewell/trace-mapping": "^0.3.9"
-      },
-      "engines": {
-        "node": ">=6.0.0"
-      }
-    },
     "node_modules/@jridgewell/resolve-uri": {
       "version": "3.1.0",
       "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz",
@@ -465,24 +459,11 @@
         "node": ">=6.0.0"
       }
     },
-    "node_modules/@jridgewell/set-array": {
-      "version": "1.1.2",
-      "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz",
-      "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==",
-      "dev": true,
-      "engines": {
-        "node": ">=6.0.0"
-      }
-    },
     "node_modules/@jridgewell/source-map": {
-      "version": "0.3.3",
-      "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.3.tgz",
-      "integrity": "sha512-b+fsZXeLYi9fEULmfBrhxn4IrPlINf8fiNarzTof004v3lFdntdwa9PF7vFJqm3mg7s+ScJMxXaE3Acp1irZcg==",
-      "dev": true,
-      "dependencies": {
-        "@jridgewell/gen-mapping": "^0.3.0",
-        "@jridgewell/trace-mapping": "^0.3.9"
-      }
+      "version": "0.3.4",
+      "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.4.tgz",
+      "integrity": "sha512-KE/SxsDqNs3rrWwFHcRh15ZLVFrI0YoZtgAdIyIq9k5hUNmiWRXXThPomIxHuL20sLdgzbDFyvkUMna14bvtrw==",
+      "dev": true
     },
     "node_modules/@jridgewell/sourcemap-codec": {
       "version": "1.4.14",
@@ -532,14 +513,14 @@
       }
     },
     "node_modules/@mui/base": {
-      "version": "5.0.0-beta.4",
-      "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.4.tgz",
-      "integrity": "sha512-ejhtqYJpjDgHGEljjMBQWZ22yEK0OzIXNa7toJmmXsP4TT3W7xVy8bTJ0TniPDf+JNjrsgfgiFTDGdlEhV1E+g==",
+      "version": "5.0.0-beta.5",
+      "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.5.tgz",
+      "integrity": "sha512-vy3TWLQYdGNecTaufR4wDNQFV2WEg6wRPi6BVbx6q1vP3K1mbxIn1+XOqOzfYBXjFHvMx0gZAo2TgWbaqfgvAA==",
       "dependencies": {
-        "@babel/runtime": "^7.21.0",
+        "@babel/runtime": "^7.22.5",
         "@emotion/is-prop-valid": "^1.2.1",
         "@mui/types": "^7.2.4",
-        "@mui/utils": "^5.13.1",
+        "@mui/utils": "^5.13.6",
         "@popperjs/core": "^2.11.8",
         "clsx": "^1.2.1",
         "prop-types": "^15.8.1",
@@ -598,13 +579,13 @@
       }
     },
     "node_modules/@mui/lab": {
-      "version": "5.0.0-alpha.133",
-      "resolved": "https://registry.npmjs.org/@mui/lab/-/lab-5.0.0-alpha.133.tgz",
-      "integrity": "sha512-pPL5/f6si8eCXlsnOZrO+/zg5Yv6qKa9OpI6nGP77Mpn7iYHm9qrcsWFIBs6YjgxqJf6dA2IqtHaSNOSndrXDw==",
+      "version": "5.0.0-alpha.134",
+      "resolved": "https://registry.npmjs.org/@mui/lab/-/lab-5.0.0-alpha.134.tgz",
+      "integrity": "sha512-GhvuM2dNOi6hzjbeGEocWVozgyyeUn7RBmZhLFtniROauxmPCZMcTsEU+GAxmpyYppqHuI8flP6tGKgMuEAK/g==",
       "dependencies": {
         "@babel/runtime": "^7.21.0",
         "@mui/base": "5.0.0-beta.4",
-        "@mui/system": "^5.13.2",
+        "@mui/system": "^5.13.5",
         "@mui/types": "^7.2.4",
         "@mui/utils": "^5.13.1",
         "clsx": "^1.2.1",
@@ -638,17 +619,49 @@
         }
       }
     },
-    "node_modules/@mui/material": {
-      "version": "5.13.4",
-      "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.13.4.tgz",
-      "integrity": "sha512-Yq+4f1KLPa/Szd3xqra2hbOAf2Usl8GbubncArM6LIp40mBLtXIdPE29MNtHsbtuzz4g+eidrETgoi3wdbEYfQ==",
+    "node_modules/@mui/lab/node_modules/@mui/base": {
+      "version": "5.0.0-beta.4",
+      "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.4.tgz",
+      "integrity": "sha512-ejhtqYJpjDgHGEljjMBQWZ22yEK0OzIXNa7toJmmXsP4TT3W7xVy8bTJ0TniPDf+JNjrsgfgiFTDGdlEhV1E+g==",
       "dependencies": {
         "@babel/runtime": "^7.21.0",
-        "@mui/base": "5.0.0-beta.4",
-        "@mui/core-downloads-tracker": "^5.13.4",
-        "@mui/system": "^5.13.2",
+        "@emotion/is-prop-valid": "^1.2.1",
         "@mui/types": "^7.2.4",
         "@mui/utils": "^5.13.1",
+        "@popperjs/core": "^2.11.8",
+        "clsx": "^1.2.1",
+        "prop-types": "^15.8.1",
+        "react-is": "^18.2.0"
+      },
+      "engines": {
+        "node": ">=12.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/mui"
+      },
+      "peerDependencies": {
+        "@types/react": "^17.0.0 || ^18.0.0",
+        "react": "^17.0.0 || ^18.0.0",
+        "react-dom": "^17.0.0 || ^18.0.0"
+      },
+      "peerDependenciesMeta": {
+        "@types/react": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@mui/material": {
+      "version": "5.13.6",
+      "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.13.6.tgz",
+      "integrity": "sha512-/c2ZApeQm2sTYdQXjqEnldaBMBcUEiyu2VRS6bS39ZeNaAcCLBQbYocLR46R+f0S5dgpBzB0T4AsOABPOFYZ5Q==",
+      "dependencies": {
+        "@babel/runtime": "^7.22.5",
+        "@mui/base": "5.0.0-beta.5",
+        "@mui/core-downloads-tracker": "^5.13.4",
+        "@mui/system": "^5.13.6",
+        "@mui/types": "^7.2.4",
+        "@mui/utils": "^5.13.6",
         "@types/react-transition-group": "^4.4.6",
         "clsx": "^1.2.1",
         "csstype": "^3.1.2",
@@ -740,15 +753,15 @@
       }
     },
     "node_modules/@mui/system": {
-      "version": "5.13.2",
-      "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.13.2.tgz",
-      "integrity": "sha512-TPyWmRJPt0JPVxacZISI4o070xEJ7ftxpVtu6LWuYVOUOINlhoGOclam4iV8PDT3EMQEHuUrwU49po34UdWLlw==",
+      "version": "5.13.6",
+      "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.13.6.tgz",
+      "integrity": "sha512-G3Xr28uLqU3DyF6r2LQkHGw/ku4P0AHzlKVe7FGXOPl7X1u+hoe2xxj8Vdiq/69II/mh9OP21i38yBWgWb7WgQ==",
       "dependencies": {
-        "@babel/runtime": "^7.21.0",
+        "@babel/runtime": "^7.22.5",
         "@mui/private-theming": "^5.13.1",
         "@mui/styled-engine": "^5.13.2",
         "@mui/types": "^7.2.4",
-        "@mui/utils": "^5.13.1",
+        "@mui/utils": "^5.13.6",
         "clsx": "^1.2.1",
         "csstype": "^3.1.2",
         "prop-types": "^15.8.1"
@@ -792,11 +805,11 @@
       }
     },
     "node_modules/@mui/utils": {
-      "version": "5.13.1",
-      "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.13.1.tgz",
-      "integrity": "sha512-6lXdWwmlUbEU2jUI8blw38Kt+3ly7xkmV9ljzY4Q20WhsJMWiNry9CX8M+TaP/HbtuyR8XKsdMgQW7h7MM3n3A==",
+      "version": "5.13.6",
+      "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.13.6.tgz",
+      "integrity": "sha512-ggNlxl5NPSbp+kNcQLmSig6WVB0Id+4gOxhx644987v4fsji+CSXc+MFYLocFB/x4oHtzCUlSzbVHlJfP/fXoQ==",
       "dependencies": {
-        "@babel/runtime": "^7.21.0",
+        "@babel/runtime": "^7.22.5",
         "@types/prop-types": "^15.7.5",
         "@types/react-is": "^18.2.0",
         "prop-types": "^15.8.1",
@@ -814,11 +827,11 @@
       }
     },
     "node_modules/@mui/x-date-pickers": {
-      "version": "6.6.0",
-      "resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-6.6.0.tgz",
-      "integrity": "sha512-cF7Ijv0IgYi/tCQa2qdoCEHF5gtj/nn/gGdr3oOw/VI6QcKQkbLwA0baPfWEMS2weT9HR6JFerMQ5i57dWv+2A==",
+      "version": "6.9.0",
+      "resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-6.9.0.tgz",
+      "integrity": "sha512-aQ2FPQIf+Il85pkNdJ/vQaCEMJ/RiFor9qJzgSfm1NkCMlvAL8GguLxbvn3M0UdY3wSIdT23C4xyQRji7rV5Xg==",
       "dependencies": {
-        "@babel/runtime": "^7.21.0",
+        "@babel/runtime": "^7.22.5",
         "@mui/utils": "^5.13.1",
         "@types/react-transition-group": "^4.4.6",
         "clsx": "^1.2.1",
@@ -1003,9 +1016,9 @@
       "dev": true
     },
     "node_modules/@types/eslint": {
-      "version": "8.40.0",
-      "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.40.0.tgz",
-      "integrity": "sha512-nbq2mvc/tBrK9zQQuItvjJl++GTN5j06DaPtp3hZCpngmG6Q3xoyEmd0TwZI0gAy/G1X0zhGBbr2imsGFdFV0g==",
+      "version": "8.40.2",
+      "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.40.2.tgz",
+      "integrity": "sha512-PRVjQ4Eh9z9pmmtaq8nTjZjQwKFk7YIHIud3lRoKRBgUQjgjRmoGxxGEPXQkF+lH7QkHJRNr5F4aBgYCW0lqpQ==",
       "dev": true,
       "dependencies": {
         "@types/estree": "*",
@@ -1059,9 +1072,9 @@
       "dev": true
     },
     "node_modules/@types/node": {
-      "version": "20.2.5",
-      "resolved": "https://registry.npmjs.org/@types/node/-/node-20.2.5.tgz",
-      "integrity": "sha512-JJulVEQXmiY9Px5axXHeYGLSjhkZEnD+MDPDGbCbIAbMslkKwmygtZFy1X6s/075Yo94sf8GuSlFfPzysQrWZQ==",
+      "version": "20.3.2",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-20.3.2.tgz",
+      "integrity": "sha512-vOBLVQeCQfIcF/2Y7eKFTqrMnizK5lRNQ7ykML/5RuwVXVWxYkgwS7xbt4B6fKCUPgbSL5FSsjHQpaGQP/dQmw==",
       "dev": true
     },
     "node_modules/@types/parse-json": {
@@ -1075,9 +1088,9 @@
       "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w=="
     },
     "node_modules/@types/react": {
-      "version": "18.2.9",
-      "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.9.tgz",
-      "integrity": "sha512-pL3JAesUkF7PEQGxh5XOwdXGV907te6m1/Qe1ERJLgomojS6Ne790QiA7GUl434JEkFA2aAaB6qJ5z4e1zJn/w==",
+      "version": "18.2.14",
+      "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.14.tgz",
+      "integrity": "sha512-A0zjq+QN/O0Kpe30hA1GidzyFjatVvrpIvWLxD+xv67Vt91TWWgco9IvrJBkeyHm1trGaFS/FSGqPlhyeZRm0g==",
       "dependencies": {
         "@types/prop-types": "*",
         "@types/scheduler": "*",
@@ -1085,9 +1098,9 @@
       }
     },
     "node_modules/@types/react-is": {
-      "version": "18.2.0",
-      "resolved": "https://registry.npmjs.org/@types/react-is/-/react-is-18.2.0.tgz",
-      "integrity": "sha512-1vz2yObaQkLL7YFe/pme2cpvDsCwI1WXIfL+5eLz0MI9gFG24Re16RzUsI8t9XZn9ZWvgLNDrJBmrqXJO7GNQQ==",
+      "version": "18.2.1",
+      "resolved": "https://registry.npmjs.org/@types/react-is/-/react-is-18.2.1.tgz",
+      "integrity": "sha512-wyUkmaaSZEzFZivD8F2ftSyAfk6L+DfFliVj/mYdOXbVjRcS87fQJLTnhk6dRZPuJjI+9g6RZJO4PNCngUrmyw==",
       "dependencies": {
         "@types/react": "*"
       }
@@ -1127,15 +1140,15 @@
       "dev": true
     },
     "node_modules/@typescript-eslint/eslint-plugin": {
-      "version": "5.59.9",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.9.tgz",
-      "integrity": "sha512-4uQIBq1ffXd2YvF7MAvehWKW3zVv/w+mSfRAu+8cKbfj3nwzyqJLNcZJpQ/WZ1HLbJDiowwmQ6NO+63nCA+fqA==",
+      "version": "5.60.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.60.1.tgz",
+      "integrity": "sha512-KSWsVvsJsLJv3c4e73y/Bzt7OpqMCADUO846bHcuWYSYM19bldbAeDv7dYyV0jwkbMfJ2XdlzwjhXtuD7OY6bw==",
       "dev": true,
       "dependencies": {
         "@eslint-community/regexpp": "^4.4.0",
-        "@typescript-eslint/scope-manager": "5.59.9",
-        "@typescript-eslint/type-utils": "5.59.9",
-        "@typescript-eslint/utils": "5.59.9",
+        "@typescript-eslint/scope-manager": "5.60.1",
+        "@typescript-eslint/type-utils": "5.60.1",
+        "@typescript-eslint/utils": "5.60.1",
         "debug": "^4.3.4",
         "grapheme-splitter": "^1.0.4",
         "ignore": "^5.2.0",
@@ -1161,14 +1174,14 @@
       }
     },
     "node_modules/@typescript-eslint/parser": {
-      "version": "5.59.9",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.59.9.tgz",
-      "integrity": "sha512-FsPkRvBtcLQ/eVK1ivDiNYBjn3TGJdXy2fhXX+rc7czWl4ARwnpArwbihSOHI2Peg9WbtGHrbThfBUkZZGTtvQ==",
+      "version": "5.60.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.60.1.tgz",
+      "integrity": "sha512-pHWlc3alg2oSMGwsU/Is8hbm3XFbcrb6P5wIxcQW9NsYBfnrubl/GhVVD/Jm/t8HXhA2WncoIRfBtnCgRGV96Q==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/scope-manager": "5.59.9",
-        "@typescript-eslint/types": "5.59.9",
-        "@typescript-eslint/typescript-estree": "5.59.9",
+        "@typescript-eslint/scope-manager": "5.60.1",
+        "@typescript-eslint/types": "5.60.1",
+        "@typescript-eslint/typescript-estree": "5.60.1",
         "debug": "^4.3.4"
       },
       "engines": {
@@ -1188,13 +1201,13 @@
       }
     },
     "node_modules/@typescript-eslint/scope-manager": {
-      "version": "5.59.9",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.59.9.tgz",
-      "integrity": "sha512-8RA+E+w78z1+2dzvK/tGZ2cpGigBZ58VMEHDZtpE1v+LLjzrYGc8mMaTONSxKyEkz3IuXFM0IqYiGHlCsmlZxQ==",
+      "version": "5.60.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.60.1.tgz",
+      "integrity": "sha512-Dn/LnN7fEoRD+KspEOV0xDMynEmR3iSHdgNsarlXNLGGtcUok8L4N71dxUgt3YvlO8si7E+BJ5Fe3wb5yUw7DQ==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/types": "5.59.9",
-        "@typescript-eslint/visitor-keys": "5.59.9"
+        "@typescript-eslint/types": "5.60.1",
+        "@typescript-eslint/visitor-keys": "5.60.1"
       },
       "engines": {
         "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
@@ -1205,13 +1218,13 @@
       }
     },
     "node_modules/@typescript-eslint/type-utils": {
-      "version": "5.59.9",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.59.9.tgz",
-      "integrity": "sha512-ksEsT0/mEHg9e3qZu98AlSrONAQtrSTljL3ow9CGej8eRo7pe+yaC/mvTjptp23Xo/xIf2mLZKC6KPv4Sji26Q==",
+      "version": "5.60.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.60.1.tgz",
+      "integrity": "sha512-vN6UztYqIu05nu7JqwQGzQKUJctzs3/Hg7E2Yx8rz9J+4LgtIDFWjjl1gm3pycH0P3mHAcEUBd23LVgfrsTR8A==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/typescript-estree": "5.59.9",
-        "@typescript-eslint/utils": "5.59.9",
+        "@typescript-eslint/typescript-estree": "5.60.1",
+        "@typescript-eslint/utils": "5.60.1",
         "debug": "^4.3.4",
         "tsutils": "^3.21.0"
       },
@@ -1232,9 +1245,9 @@
       }
     },
     "node_modules/@typescript-eslint/types": {
-      "version": "5.59.9",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.59.9.tgz",
-      "integrity": "sha512-uW8H5NRgTVneSVTfiCVffBb8AbwWSKg7qcA4Ot3JI3MPCJGsB4Db4BhvAODIIYE5mNj7Q+VJkK7JxmRhk2Lyjw==",
+      "version": "5.60.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.60.1.tgz",
+      "integrity": "sha512-zDcDx5fccU8BA0IDZc71bAtYIcG9PowaOwaD8rjYbqwK7dpe/UMQl3inJ4UtUK42nOCT41jTSCwg76E62JpMcg==",
       "dev": true,
       "engines": {
         "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
@@ -1245,13 +1258,13 @@
       }
     },
     "node_modules/@typescript-eslint/typescript-estree": {
-      "version": "5.59.9",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.9.tgz",
-      "integrity": "sha512-pmM0/VQ7kUhd1QyIxgS+aRvMgw+ZljB3eDb+jYyp6d2bC0mQWLzUDF+DLwCTkQ3tlNyVsvZRXjFyV0LkU/aXjA==",
+      "version": "5.60.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.60.1.tgz",
+      "integrity": "sha512-hkX70J9+2M2ZT6fhti5Q2FoU9zb+GeZK2SLP1WZlvUDqdMbEKhexZODD1WodNRyO8eS+4nScvT0dts8IdaBzfw==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/types": "5.59.9",
-        "@typescript-eslint/visitor-keys": "5.59.9",
+        "@typescript-eslint/types": "5.60.1",
+        "@typescript-eslint/visitor-keys": "5.60.1",
         "debug": "^4.3.4",
         "globby": "^11.1.0",
         "is-glob": "^4.0.3",
@@ -1272,17 +1285,17 @@
       }
     },
     "node_modules/@typescript-eslint/utils": {
-      "version": "5.59.9",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.59.9.tgz",
-      "integrity": "sha512-1PuMYsju/38I5Ggblaeb98TOoUvjhRvLpLa1DoTOFaLWqaXl/1iQ1eGurTXgBY58NUdtfTXKP5xBq7q9NDaLKg==",
+      "version": "5.60.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.60.1.tgz",
+      "integrity": "sha512-tiJ7FFdFQOWssFa3gqb94Ilexyw0JVxj6vBzaSpfN/8IhoKkDuSAenUKvsSHw2A/TMpJb26izIszTXaqygkvpQ==",
       "dev": true,
       "dependencies": {
         "@eslint-community/eslint-utils": "^4.2.0",
         "@types/json-schema": "^7.0.9",
         "@types/semver": "^7.3.12",
-        "@typescript-eslint/scope-manager": "5.59.9",
-        "@typescript-eslint/types": "5.59.9",
-        "@typescript-eslint/typescript-estree": "5.59.9",
+        "@typescript-eslint/scope-manager": "5.60.1",
+        "@typescript-eslint/types": "5.60.1",
+        "@typescript-eslint/typescript-estree": "5.60.1",
         "eslint-scope": "^5.1.1",
         "semver": "^7.3.7"
       },
@@ -1298,12 +1311,12 @@
       }
     },
     "node_modules/@typescript-eslint/visitor-keys": {
-      "version": "5.59.9",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.9.tgz",
-      "integrity": "sha512-bT7s0td97KMaLwpEBckbzj/YohnvXtqbe2XgqNvTl6RJVakY5mvENOTPvw5u66nljfZxthESpDozs86U+oLY8Q==",
+      "version": "5.60.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.60.1.tgz",
+      "integrity": "sha512-xEYIxKcultP6E/RMKqube11pGjXH1DCo60mQoWhVYyKfLkwbIVVjYxmOenNMxILx0TjCujPTjjnTIVzm09TXIw==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/types": "5.59.9",
+        "@typescript-eslint/types": "5.60.1",
         "eslint-visitor-keys": "^3.3.0"
       },
       "engines": {
@@ -1517,9 +1530,9 @@
       "dev": true
     },
     "node_modules/acorn": {
-      "version": "8.8.2",
-      "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz",
-      "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==",
+      "version": "8.9.0",
+      "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.9.0.tgz",
+      "integrity": "sha512-jaVNAFBHNLXspO543WnNNPZFRtavh3skAkITqD0/2aeMkKZTN+254PyhwxFYrk3vQ1xfY+2wbesJMs/JC8/PwQ==",
       "dev": true,
       "bin": {
         "acorn": "bin/acorn"
@@ -1681,6 +1694,24 @@
         "node": ">=8"
       }
     },
+    "node_modules/array.prototype.flat": {
+      "version": "1.3.1",
+      "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz",
+      "integrity": "sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==",
+      "dev": true,
+      "dependencies": {
+        "call-bind": "^1.0.2",
+        "define-properties": "^1.1.4",
+        "es-abstract": "^1.20.4",
+        "es-shim-unscopables": "^1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
     "node_modules/array.prototype.flatmap": {
       "version": "1.3.1",
       "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz",
@@ -1767,9 +1798,9 @@
       }
     },
     "node_modules/browserslist": {
-      "version": "4.21.7",
-      "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.7.tgz",
-      "integrity": "sha512-BauCXrQ7I2ftSqd2mvKHGo85XR0u7Ru3C/Hxsy/0TkfCtjrmAbPdzLGasmoiBxplpDXlPvdjX9u7srIMfgasNA==",
+      "version": "4.21.9",
+      "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.9.tgz",
+      "integrity": "sha512-M0MFoZzbUrRU4KNfCrDLnvyE7gub+peetoTid3TBIqtunaDJyXlwhakT+/VkvSXcfIzFfK/nkCs4nmyTmxdNSg==",
       "dev": true,
       "funding": [
         {
@@ -1786,8 +1817,8 @@
         }
       ],
       "dependencies": {
-        "caniuse-lite": "^1.0.30001489",
-        "electron-to-chromium": "^1.4.411",
+        "caniuse-lite": "^1.0.30001503",
+        "electron-to-chromium": "^1.4.431",
         "node-releases": "^2.0.12",
         "update-browserslist-db": "^1.0.11"
       },
@@ -1826,9 +1857,9 @@
       }
     },
     "node_modules/caniuse-lite": {
-      "version": "1.0.30001495",
-      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001495.tgz",
-      "integrity": "sha512-F6x5IEuigtUfU5ZMQK2jsy5JqUUlEFRVZq8bO2a+ysq5K7jD6PPc9YXZj78xDNS3uNchesp1Jw47YXEqr+Viyg==",
+      "version": "1.0.30001509",
+      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001509.tgz",
+      "integrity": "sha512-2uDDk+TRiTX5hMcUYT/7CSyzMZxjfGu0vAUjS2g0LSD8UoXOv0LtpH4LxGMemsiPq6LCVIUjNwVM0erkOkGCDA==",
       "dev": true,
       "funding": [
         {
@@ -2093,9 +2124,9 @@
       }
     },
     "node_modules/dotenv": {
-      "version": "16.1.4",
-      "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.1.4.tgz",
-      "integrity": "sha512-m55RtE8AsPeJBpOIFKihEmqUcoVncQIwo7x9U8ZwLEZw9ZpXboz2c+rvog+jUaJvVrZ5kBOeYQBX5+8Aa/OZQw==",
+      "version": "16.3.1",
+      "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz",
+      "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==",
       "dev": true,
       "engines": {
         "node": ">=12"
@@ -2105,15 +2136,15 @@
       }
     },
     "node_modules/electron-to-chromium": {
-      "version": "1.4.425",
-      "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.425.tgz",
-      "integrity": "sha512-wv1NufHxu11zfDbY4fglYQApMswleE9FL/DSeyOyauVXDZ+Kco96JK/tPfBUaDqfRarYp2WH2hJ/5UnVywp9Jg==",
+      "version": "1.4.446",
+      "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.446.tgz",
+      "integrity": "sha512-4Gnw7ztEQ/E0eOt5JWfPn9jjeupfUlKoeW5ETKP9nLdWj+4spFoS3Stj19fqlKIaX28UQs0fNX+uKEyoLCBnkw==",
       "dev": true
     },
     "node_modules/enhanced-resolve": {
-      "version": "5.14.1",
-      "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.14.1.tgz",
-      "integrity": "sha512-Vklwq2vDKtl0y/vtwjSesgJ5MYS7Etuk5txS8VdKL4AOS1aUlD96zqIfsOSLQsdv3xgMRbtkWM8eG9XDfKUPow==",
+      "version": "5.15.0",
+      "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz",
+      "integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==",
       "dev": true,
       "dependencies": {
         "graceful-fs": "^4.2.4",
@@ -2124,9 +2155,9 @@
       }
     },
     "node_modules/envinfo": {
-      "version": "7.8.1",
-      "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.8.1.tgz",
-      "integrity": "sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw==",
+      "version": "7.10.0",
+      "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.10.0.tgz",
+      "integrity": "sha512-ZtUjZO6l5mwTHvc1L9+1q5p/R3wTopcfqMW8r5t8SJSKqeVI/LtajORwRFEKpEFuekjD0VBjwu1HMxL4UalIRw==",
       "dev": true,
       "bin": {
         "envinfo": "dist/cli.js"
@@ -2192,9 +2223,9 @@
       }
     },
     "node_modules/es-module-lexer": {
-      "version": "1.2.1",
-      "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.2.1.tgz",
-      "integrity": "sha512-9978wrXM50Y4rTMmW5kXIC09ZdXQZqkE4mxhwkd8VbzsGkXGPgV4zWuqQJgCEzYngdo2dYDa0l8xhX4fkSwJSg==",
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.3.0.tgz",
+      "integrity": "sha512-vZK7T0N2CBmBOixhmjdqx2gWVbFZ4DXZ/NyRMZVlJXPa7CyFS+/a4QQsDGDQy9ZfEzxFuNEsMLeQJnKP2p5/JA==",
       "dev": true
     },
     "node_modules/es-set-tostringtag": {
@@ -2258,15 +2289,15 @@
       }
     },
     "node_modules/eslint": {
-      "version": "8.42.0",
-      "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.42.0.tgz",
-      "integrity": "sha512-ulg9Ms6E1WPf67PHaEY4/6E2tEn5/f7FXGzr3t9cBMugOmf1INYvuUwwh1aXQN4MfJ6a5K2iNwP3w4AColvI9A==",
+      "version": "8.43.0",
+      "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.43.0.tgz",
+      "integrity": "sha512-aaCpf2JqqKesMFGgmRPessmVKjcGXqdlAYLLC3THM8t5nBRZRQ+st5WM/hoJXkdioEXLLbXgclUpM0TXo5HX5Q==",
       "dev": true,
       "dependencies": {
         "@eslint-community/eslint-utils": "^4.2.0",
         "@eslint-community/regexpp": "^4.4.0",
         "@eslint/eslintrc": "^2.0.3",
-        "@eslint/js": "8.42.0",
+        "@eslint/js": "8.43.0",
         "@humanwhocodes/config-array": "^0.11.10",
         "@humanwhocodes/module-importer": "^1.0.1",
         "@nodelib/fs.walk": "^1.2.8",
@@ -2682,9 +2713,9 @@
       }
     },
     "node_modules/formik": {
-      "version": "2.4.1",
-      "resolved": "https://registry.npmjs.org/formik/-/formik-2.4.1.tgz",
-      "integrity": "sha512-ajOB9EmFhXb4PACTlaooVEn7PLtLtBJEZ8fPs+wFZjL5KSGwgAoU+n9DHN8JcqNKcXkloEYYtn1lxrLav18ecQ==",
+      "version": "2.4.2",
+      "resolved": "https://registry.npmjs.org/formik/-/formik-2.4.2.tgz",
+      "integrity": "sha512-C6nx0hifW2uENP3M6HpPmnAE6HFWCcd8/sqBZEOHZY6lpHJ5qehsfAy43ktpFLEmkBmhiZDei726utcUB9leqg==",
       "funding": [
         {
           "type": "individual",
@@ -2698,7 +2729,7 @@
         "lodash-es": "^4.17.21",
         "react-fast-compare": "^2.0.1",
         "tiny-warning": "^1.0.2",
-        "tslib": "^1.10.0"
+        "tslib": "^2.0.0"
       },
       "peerDependencies": {
         "react": ">=16.8.0"
@@ -3443,13 +3474,15 @@
       "dev": true
     },
     "node_modules/jsx-ast-utils": {
-      "version": "3.3.3",
-      "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.3.tgz",
-      "integrity": "sha512-fYQHZTZ8jSfmWZ0iyzfwiU4WDX4HpHbMCZ3gPlWYiCl3BoeOTsqKBqnTVfH2rYT7eP5c3sVbeSPHnnJOaTrWiw==",
+      "version": "3.3.4",
+      "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.4.tgz",
+      "integrity": "sha512-fX2TVdCViod6HwKEtSWGHs57oFhVfCMwieb9PuRDgjDPh5XeqJiHFFFJCHxU5cnTc3Bu/GRL+kPiFmw8XWOfKw==",
       "dev": true,
       "dependencies": {
-        "array-includes": "^3.1.5",
-        "object.assign": "^4.1.3"
+        "array-includes": "^3.1.6",
+        "array.prototype.flat": "^1.3.1",
+        "object.assign": "^4.1.4",
+        "object.values": "^1.1.6"
       },
       "engines": {
         "node": ">=4.0"
@@ -3760,17 +3793,17 @@
       }
     },
     "node_modules/optionator": {
-      "version": "0.9.1",
-      "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz",
-      "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==",
+      "version": "0.9.3",
+      "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz",
+      "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==",
       "dev": true,
       "dependencies": {
+        "@aashutoshrathi/word-wrap": "^1.2.3",
         "deep-is": "^0.1.3",
         "fast-levenshtein": "^2.0.6",
         "levn": "^0.4.1",
         "prelude-ls": "^1.2.1",
-        "type-check": "^0.4.0",
-        "word-wrap": "^1.2.3"
+        "type-check": "^0.4.0"
       },
       "engines": {
         "node": ">= 0.8.0"
@@ -4275,9 +4308,9 @@
       }
     },
     "node_modules/schema-utils": {
-      "version": "4.1.0",
-      "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.1.0.tgz",
-      "integrity": "sha512-Jw+GZVbP5IggB2WAn6UHI02LBwGmsIeYN/lNbSMZyDziQ7jmtAUrqKqDja+W89YHVs+KL/3IkIMltAklqB1vAw==",
+      "version": "4.2.0",
+      "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz",
+      "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==",
       "dev": true,
       "dependencies": {
         "@types/json-schema": "^7.0.9",
@@ -4328,9 +4361,9 @@
       "dev": true
     },
     "node_modules/semver": {
-      "version": "7.5.1",
-      "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz",
-      "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==",
+      "version": "7.5.3",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz",
+      "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==",
       "dev": true,
       "dependencies": {
         "lru-cache": "^6.0.0"
@@ -4564,9 +4597,9 @@
       }
     },
     "node_modules/terser": {
-      "version": "5.17.7",
-      "resolved": "https://registry.npmjs.org/terser/-/terser-5.17.7.tgz",
-      "integrity": "sha512-/bi0Zm2C6VAexlGgLlVxA0P2lru/sdLyfCVaRMfKVo9nWxbmz7f/sD8VPybPeSUJaJcwmCJis9pBIhcVcG1QcQ==",
+      "version": "5.18.2",
+      "resolved": "https://registry.npmjs.org/terser/-/terser-5.18.2.tgz",
+      "integrity": "sha512-Ah19JS86ypbJzTzvUCX7KOsEIhDaRONungA4aYBjEP3JZRf4ocuDzTg4QWZnPn9DEMiMYGJPiSOy7aykoCc70w==",
       "dev": true,
       "dependencies": {
         "@jridgewell/source-map": "^0.3.3",
@@ -4630,9 +4663,9 @@
       }
     },
     "node_modules/terser-webpack-plugin/node_modules/schema-utils": {
-      "version": "3.2.0",
-      "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.2.0.tgz",
-      "integrity": "sha512-0zTyLGyDJYd/MBxG1AhJkKa6fpEBds4OQO2ut0w7OYG+ZGhGea09lijvzsqegYSik88zc7cUtIlnnO+/BvD6gQ==",
+      "version": "3.3.0",
+      "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz",
+      "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==",
       "dev": true,
       "dependencies": {
         "@types/json-schema": "^7.0.8",
@@ -4694,9 +4727,9 @@
       }
     },
     "node_modules/ts-loader": {
-      "version": "9.4.3",
-      "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.4.3.tgz",
-      "integrity": "sha512-n3hBnm6ozJYzwiwt5YRiJZkzktftRpMiBApHaJPoWLA+qetQBAXkHqCLM6nwSdRDimqVtA5ocIkcTRLMTt7yzA==",
+      "version": "9.4.4",
+      "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.4.4.tgz",
+      "integrity": "sha512-MLukxDHBl8OJ5Dk3y69IsKVFRA/6MwzEqBgh+OXMPB/OD01KQuWPFd1WAQP8a5PeSCAxfnkhiuWqfmFJzJQt9w==",
       "dev": true,
       "dependencies": {
         "chalk": "^4.1.0",
@@ -4713,9 +4746,9 @@
       }
     },
     "node_modules/tslib": {
-      "version": "1.14.1",
-      "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
-      "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
+      "version": "2.6.0",
+      "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz",
+      "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA=="
     },
     "node_modules/tsutils": {
       "version": "3.21.0",
@@ -4732,6 +4765,12 @@
         "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta"
       }
     },
+    "node_modules/tsutils/node_modules/tslib": {
+      "version": "1.14.1",
+      "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
+      "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
+      "dev": true
+    },
     "node_modules/type-check": {
       "version": "0.4.0",
       "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
@@ -4771,9 +4810,9 @@
       }
     },
     "node_modules/typescript": {
-      "version": "5.1.3",
-      "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.3.tgz",
-      "integrity": "sha512-XH627E9vkeqhlZFQuL+UsyAXEnibT0kWR2FWONlr4sTjvxyJYnyefgrkyECLzM5NenmKzRAy2rR/OlYLA1HkZw==",
+      "version": "5.1.6",
+      "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz",
+      "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==",
       "dev": true,
       "bin": {
         "tsc": "bin/tsc",
@@ -4851,9 +4890,9 @@
       }
     },
     "node_modules/webpack": {
-      "version": "5.86.0",
-      "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.86.0.tgz",
-      "integrity": "sha512-3BOvworZ8SO/D4GVP+GoRC3fVeg5MO4vzmq8TJJEkdmopxyazGDxN8ClqN12uzrZW9Tv8EED8v5VSb6Sqyi0pg==",
+      "version": "5.88.1",
+      "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.88.1.tgz",
+      "integrity": "sha512-FROX3TxQnC/ox4N+3xQoWZzvGXSuscxR32rbzjpXgEzWudJFEJBpdlkkob2ylrv5yzzufD1zph1OoFsLtm6stQ==",
       "dev": true,
       "dependencies": {
         "@types/eslint-scope": "^3.7.3",
@@ -4865,7 +4904,7 @@
         "acorn-import-assertions": "^1.9.0",
         "browserslist": "^4.14.5",
         "chrome-trace-event": "^1.0.2",
-        "enhanced-resolve": "^5.14.1",
+        "enhanced-resolve": "^5.15.0",
         "es-module-lexer": "^1.2.1",
         "eslint-scope": "5.1.1",
         "events": "^3.2.0",
@@ -4875,7 +4914,7 @@
         "loader-runner": "^4.2.0",
         "mime-types": "^2.1.27",
         "neo-async": "^2.6.2",
-        "schema-utils": "^3.1.2",
+        "schema-utils": "^3.2.0",
         "tapable": "^2.1.1",
         "terser-webpack-plugin": "^5.3.7",
         "watchpack": "^2.4.0",
@@ -4974,9 +5013,9 @@
       }
     },
     "node_modules/webpack/node_modules/schema-utils": {
-      "version": "3.2.0",
-      "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.2.0.tgz",
-      "integrity": "sha512-0zTyLGyDJYd/MBxG1AhJkKa6fpEBds4OQO2ut0w7OYG+ZGhGea09lijvzsqegYSik88zc7cUtIlnnO+/BvD6gQ==",
+      "version": "3.3.0",
+      "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz",
+      "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==",
       "dev": true,
       "dependencies": {
         "@types/json-schema": "^7.0.8",
@@ -5048,15 +5087,6 @@
       "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==",
       "dev": true
     },
-    "node_modules/word-wrap": {
-      "version": "1.2.3",
-      "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
-      "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
-      "dev": true,
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
     "node_modules/wrappy": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",

+ 809 - 0
gui/src/JobSelector.tsx

@@ -0,0 +1,809 @@
+/*
+ * Copyright 2023 Avaiga Private Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+
+import React, { useEffect, useState, useCallback, useMemo, MouseEvent } from "react";
+import { DeleteOutline, StopCircleOutlined, Add, FilterList } from "@mui/icons-material";
+import Box from "@mui/material/Box";
+import Button from "@mui/material/Button";
+import Checkbox from "@mui/material/Checkbox";
+import Chip from "@mui/material/Chip";
+import FormControl from "@mui/material/FormControl";
+import Grid from "@mui/material/Grid";
+import IconButton from "@mui/material/IconButton";
+import InputLabel from "@mui/material/InputLabel";
+import ListItemText from "@mui/material/ListItemText";
+import MenuItem from "@mui/material/MenuItem";
+import Paper from "@mui/material/Paper";
+import Popover, { PopoverOrigin } from "@mui/material/Popover";
+import Select from "@mui/material/Select";
+import Table from "@mui/material/Table";
+import TableBody from "@mui/material/TableBody";
+import TableCell from "@mui/material/TableCell";
+import TableContainer from "@mui/material/TableContainer";
+import TableHead from "@mui/material/TableHead";
+import TableRow from "@mui/material/TableRow";
+import TextField from "@mui/material/TextField";
+import Toolbar from "@mui/material/Toolbar";
+import Tooltip from "@mui/material/Tooltip";
+import Typography from "@mui/material/Typography";
+import { useFormik } from "formik";
+
+import {
+    createRequestUpdateAction,
+    createSendActionNameAction,
+    createSendUpdateAction,
+    getUpdateVar,
+    useDispatch,
+    useDispatchRequestUpdateOnFirstRender,
+    useModule,
+} from "taipy-gui";
+
+import { disableColor, useClassNames } from "./utils";
+
+interface JobSelectorProps {
+    updateVarName?: string;
+    coreChanged?: Record<string, unknown>;
+    error?: string;
+    jobs: Jobs;
+    onSelect?: string;
+    updateVars: string;
+    id?: string;
+    libClassName?: string;
+    className?: string;
+    dynamicClassName?: string;
+    height: string;
+    showJobId?: boolean;
+    showEntityLabel?: boolean;
+    showEntityId?: boolean;
+    showSubmitId?: boolean;
+    showDate?: boolean;
+    showCancel?: boolean;
+    showDelete?: boolean;
+    onJobAction: string;
+    onChange?: string;
+    value?: string;
+    defaultValue?: string;
+    propagate?: boolean;
+}
+
+// job id, job name, empty list, entity id, entity name, submit id, creation date, status
+type Job = [string, string, [], string, string, string, string, number];
+type Jobs = Array<Job>;
+
+enum JobProps {
+    id,
+    name,
+    _,
+    entity_id,
+    entity_name,
+    submit_id,
+    creation_date,
+    status,
+}
+const JobLength = Object.keys(JobProps).length / 2;
+
+enum JobStatus {
+    SUBMITTED = 1,
+    BLOCKED = 2,
+    PENDING = 3,
+    RUNNING = 4,
+    CANCELED = 5,
+    FAILED = 6,
+    COMPLETED = 7,
+    SKIPPED = 8,
+    ABANDONED = 9,
+}
+
+const containerSx = { width: "100%", mb: 2 };
+const selectSx = { height: 50 };
+const containerPopupSx = { width: "619px" };
+const tableWidthSx = { minWidth: 750 };
+const toolbarRightSx = { mr: 6 };
+
+const ChipStatus = ({ status }: { status: number }) => {
+    const statusText = JobStatus[status];
+    let colorFill: "warning" | "default" | "success" | "error" = "warning";
+
+    if (status === JobStatus.COMPLETED || status === JobStatus.SKIPPED) {
+        colorFill = "success";
+    } else if (status === JobStatus.FAILED) {
+        colorFill = "error";
+    } else if (status === JobStatus.CANCELED || status === JobStatus.ABANDONED) {
+        colorFill = "default";
+    }
+
+    const variant = status === JobStatus.FAILED || status === JobStatus.RUNNING ? "filled" : "outlined";
+
+    return <Chip label={statusText} variant={variant} color={colorFill} />;
+};
+
+type JobSelectorColumns = {
+    id: string;
+    primaryLabel: string;
+    showPrimaryLabel?: boolean;
+    secondaryLabel?: string;
+    showSecondayLabel?: boolean;
+    columnIndex: number;
+};
+
+type FilterData = {
+    data: number;
+    operator: string;
+    value: string;
+};
+
+const origin: PopoverOrigin = {
+    vertical: "bottom",
+    horizontal: "left",
+};
+interface FilterProps {
+    open: boolean;
+    anchorEl: HTMLButtonElement | null;
+    handleFilterClose: () => void;
+    handleApplyFilter: (filters: FilterData[]) => void;
+    columns: JobSelectorColumns[];
+}
+const Filter = ({ open, anchorEl, handleFilterClose, handleApplyFilter, columns }: FilterProps) => {
+    const form = useFormik<{filters: FilterData[], newData: "" | number, newOperator: "is" | "isnot", newValue: string}>({
+        initialValues: {
+            filters: [],
+            newData: "",
+            newOperator: "is",
+            newValue: "",
+        },
+        onSubmit: () => {
+            applyFilter();
+        },
+    });
+
+    const removeFilter = useCallback(
+        (e: MouseEvent<HTMLElement>) => {
+            const { idx } = e.currentTarget.dataset || {};
+            form.setFieldValue(
+                "filters",
+                form.values.filters.filter((_, i) => "" + i !== idx)
+            );
+        },
+        [form]
+    );
+
+    const addFilter = useCallback(() => {
+        const newFilters = [
+            ...form.values.filters,
+            {
+                data: form.values.newData,
+                operator: form.values.newOperator,
+                value: form.values.newValue,
+            },
+        ];
+        form.setFieldValue("filters", newFilters);
+        form.setFieldValue("newData", "");
+        form.setFieldValue("newOperator", "");
+        form.setFieldValue("newValue", "");
+    }, [form]);
+
+    const applyFilter = useCallback(() => {
+        handleApplyFilter([...form.values.filters]);
+        handleFilterClose();
+    }, [form, handleApplyFilter, handleFilterClose]);
+
+    const removeAllFilter = useCallback(() => {
+        const filters: FilterData[] = [];
+        form.setFieldValue("filters", filters);
+        handleApplyFilter(filters);
+        handleFilterClose();
+    }, [form, handleApplyFilter, handleFilterClose]);
+
+    return (
+        <Popover
+            id="filter-container"
+            open={open}
+            anchorEl={anchorEl}
+            onClose={handleFilterClose}
+            anchorOrigin={origin}
+        >
+            <form onSubmit={form.handleSubmit}>
+                <Grid container p={3} sx={containerPopupSx}>
+                    {form && form.values.filters && form.values.filters.length > 0
+                        ? form.values.filters.map((filter, index) => {
+                              return (
+                                  <Grid item xs={12} container spacing={2} mb={1} key={index}>
+                                      <Grid item xs={3}>
+                                          <FormControl fullWidth>
+                                              <InputLabel id="data">Column</InputLabel>
+                                              <Select
+                                                  labelId="data"
+                                                  sx={selectSx}
+                                                  label="Column"
+                                                  {...form.getFieldProps(`filters.${index}.data`)}
+                                              >
+                                                  {columns
+                                                      .filter(
+                                                          (item) =>
+                                                              item.columnIndex >= 0 &&
+                                                              (item.showPrimaryLabel || item.showSecondayLabel)
+                                                      )
+                                                      .map((item) => (
+                                                          <MenuItem key={item.id} value={item.columnIndex}>
+                                                              {item.primaryLabel}
+                                                          </MenuItem>
+                                                      ))}
+                                              </Select>
+                                          </FormControl>
+                                      </Grid>
+                                      <Grid item xs={3}>
+                                          <FormControl fullWidth>
+                                              <InputLabel id="operator">Operator</InputLabel>
+                                              <Select
+                                                  labelId="operator"
+                                                  sx={selectSx}
+                                                  label="Operator"
+                                                  {...form.getFieldProps(`filters.${index}.operator`)}
+                                              >
+                                                  <MenuItem value="is">is</MenuItem>
+                                                  <MenuItem value="isnot">is not</MenuItem>
+                                              </Select>
+                                          </FormControl>
+                                      </Grid>
+                                      <Grid item xs={5}>
+                                          <TextField
+                                              label="Value"
+                                              variant="outlined"
+                                              {...form.getFieldProps(`filters.${index}.value`)}
+                                          />
+                                      </Grid>
+                                      <Grid item xs={1}>
+                                          <IconButton data-idx={index} onClick={removeFilter}>
+                                              <DeleteOutline />
+                                          </IconButton>
+                                      </Grid>
+                                  </Grid>
+                              );
+                          })
+                        : null}
+                    <Grid item xs={12} container spacing={2} justifyContent="space-between">
+                        <Grid item xs={3}>
+                            <FormControl fullWidth>
+                                <InputLabel id="data-new">Column</InputLabel>
+                                <Select
+                                    labelId="data-new"
+                                    sx={selectSx}
+                                    label="Column"
+                                    {...form.getFieldProps(`newData`)}
+                                >
+                                    {columns
+                                        .filter(
+                                            (item) =>
+                                                item.columnIndex >= 0 &&
+                                                (item.showPrimaryLabel || item.showSecondayLabel)
+                                        )
+                                        .map((item) => (
+                                                <MenuItem key={item.id} value={item.columnIndex}>
+                                                    {item.primaryLabel}
+                                                </MenuItem>
+                                            ))}
+                                </Select>
+                            </FormControl>
+                        </Grid>
+                        <Grid item xs={3}>
+                            <FormControl fullWidth>
+                                <InputLabel id="operator-new">Operator</InputLabel>
+                                <Select
+                                    labelId="operator-new"
+                                    sx={selectSx}
+                                    label="Operator"
+                                    {...form.getFieldProps(`newOperator`)}
+                                >
+                                    <MenuItem value="is">is</MenuItem>
+                                    <MenuItem value="isnot">is not</MenuItem>
+                                </Select>
+                            </FormControl>
+                        </Grid>
+                        <Grid item xs={5}>
+                            <TextField label="Value" variant="outlined" {...form.getFieldProps(`newValue`)} />
+                        </Grid>
+                        <Grid item xs={1}>
+                            <IconButton onClick={addFilter} disabled={typeof form.values.newData === "string"}>
+                                <Add color={disableColor("primary", typeof form.values.newData === "string")} />
+                            </IconButton>
+                        </Grid>
+                    </Grid>
+                    <Grid item xs={12} container justifyContent="space-between" mt={2}>
+                        <Button variant="outlined" color="inherit" onClick={removeAllFilter} disabled={!form.values.filters.length}>
+                            Remove all filters
+                        </Button>
+                        <Button variant="contained" type="submit">
+                            Apply {form.values.filters.length} filter{form.values.filters.length > 1 ? "s": ""}
+                        </Button>
+                    </Grid>
+                </Grid>
+            </form>
+        </Popover>
+    );
+};
+
+interface JobSelectedTableHeadProps {
+    jobs: number;
+    selected: number;
+    handleSelectAllClick?: (event: React.ChangeEvent<HTMLInputElement>) => void;
+    columns: JobSelectorColumns[];
+}
+function JobSelectedTableHead({ jobs, selected, handleSelectAllClick, columns }: JobSelectedTableHeadProps) {
+    return (
+        <TableHead>
+            <TableRow>
+                <TableCell padding="checkbox">
+                    <Checkbox
+                        color="primary"
+                        checked={!!jobs && jobs == selected}
+                        indeterminate={!!jobs && !!selected && jobs != selected}
+                        onChange={handleSelectAllClick}
+                    />
+                </TableCell>
+                {columns
+                    .filter((i) => i.showPrimaryLabel || i.showSecondayLabel)
+                    .map((item) => (
+                        <TableCell key={item.id}>
+                            {item.secondaryLabel ? (
+                                <ListItemText primary={item.primaryLabel} secondary={item.secondaryLabel} />
+                            ) : (
+                                item.primaryLabel
+                            )}
+                        </TableCell>
+                    ))}
+            </TableRow>
+        </TableHead>
+    );
+}
+
+interface JobSelectedTableRowProps {
+    row: Job;
+    isSelected: boolean;
+    isItemSelected: boolean;
+    handleSelection: (event: React.MouseEvent<HTMLElement>) => void;
+    handleCheckboxClick: (event: React.MouseEvent<HTMLElement>) => void;
+    handleCancelJobs: (event: React.MouseEvent<HTMLElement>) => void;
+    handleDeleteJobs: (event: React.MouseEvent<HTMLElement>) => void;
+    showJobId?: boolean;
+    showEntityLabel?: boolean;
+    showEntityId?: boolean;
+    showSubmitId?: boolean;
+    showDate?: boolean;
+    showCancel?: boolean;
+    showDelete?: boolean;
+}
+const JobSelectedTableRow = ({
+    row,
+    isSelected,
+    isItemSelected,
+    handleSelection,
+    handleCheckboxClick,
+    handleCancelJobs,
+    handleDeleteJobs,
+    showJobId,
+    showEntityLabel,
+    showEntityId,
+    showSubmitId,
+    showDate,
+    showCancel,
+    showDelete,
+}: JobSelectedTableRowProps) => {
+    // eslint-disable-next-line @typescript-eslint/no-unused-vars
+    const [id, jobName, _, entityId, entityName, submitId, creationDate, status] = row;
+
+    return (
+        <TableRow hover role="checkbox" tabIndex={-1} key={id} data-id={id} onClick={handleSelection} selected={isSelected}>
+            <TableCell padding="checkbox">
+                <Checkbox color="primary" checked={isItemSelected} data-id={id} onClick={handleCheckboxClick} />
+            </TableCell>
+            {showJobId ? (
+                <TableCell component="th" scope="row" padding="none">
+                    <ListItemText primary={jobName} secondary={id} />
+                </TableCell>
+            ) : null}
+            {showSubmitId ? <TableCell>{submitId}</TableCell> : null}
+            {showEntityLabel || showEntityId ? (
+                <TableCell>
+                    {!showEntityLabel && showEntityId ? (
+                        entityId
+                    ) : !showEntityId && showEntityLabel ? (
+                        entityName
+                    ) : (
+                        <ListItemText primary={entityName} secondary={entityId} />
+                    )}
+                </TableCell>
+            ) : null}
+            {showDate ? <TableCell>{creationDate ? new Date(creationDate).toLocaleString() : ""}</TableCell> : null}
+            <TableCell>
+                <ChipStatus status={status} />
+            </TableCell>
+            {showCancel || showDelete ? (
+                <TableCell>
+                    {status === JobStatus.RUNNING ? null : status === JobStatus.BLOCKED ||
+                      status === JobStatus.PENDING ||
+                      status === JobStatus.SUBMITTED ? (
+                        showCancel ? (
+                            <Tooltip title="Cancel">
+                                <IconButton data-id={id} onClick={handleCancelJobs}>
+                                    <StopCircleOutlined />
+                                </IconButton>
+                            </Tooltip>
+                        ) : null
+                    ) : showDelete ? (
+                        <Tooltip title="Delete">
+                            <IconButton data-id={id} onClick={handleDeleteJobs}>
+                                <DeleteOutline color="primary" />
+                            </IconButton>
+                        </Tooltip>
+                    ) : null}
+                </TableCell>
+            ) : null}
+        </TableRow>
+    );
+};
+
+const JobSelector = (props: JobSelectorProps) => {
+    const {
+        id = "",
+        showJobId = true,
+        showEntityLabel = true,
+        showEntityId = true,
+        showSubmitId = true,
+        showDate = true,
+        showCancel = true,
+        showDelete = true,
+        propagate = true,
+    } = props;
+    const [checked, setChecked] = useState<string[]>([]);
+    const [selected, setSelected] = useState<string[]>([]);
+    const [jobRows, setJobRows] = useState<Jobs>([]);
+    const [filters, setFilters] = useState<FilterData[]>();
+    const [anchorEl, setAnchorEl] = React.useState<HTMLButtonElement | null>(null);
+
+    const dispatch = useDispatch();
+    const module = useModule();
+
+    const className = useClassNames(props.libClassName, props.dynamicClassName, props.className);
+
+    useDispatchRequestUpdateOnFirstRender(dispatch, id, module, props.updateVars);
+
+    const headerToolbarSx = useMemo(
+        () => ({
+            pl: { sm: 2 },
+            pr: { xs: 1, sm: 1 },
+            ...(checked.length > 0
+                ? {
+                      bgcolor: "rgba(0, 0, 0, 0.05)",
+                  }
+                : {}),
+        }),
+        [checked]
+    );
+
+    const jobSelectorColumns: JobSelectorColumns[] = useMemo(
+        () => [
+            {
+                id: "jobId",
+                primaryLabel: "Job",
+                showPrimaryLabel: showJobId,
+                secondaryLabel: "ID",
+                showSecondayLabel: showJobId,
+                columnIndex: JobProps.id,
+            },
+            {
+                id: "submitID",
+                primaryLabel: "Submit ID",
+                columnIndex: JobProps.submit_id,
+                showPrimaryLabel: showSubmitId,
+                showSecondayLabel: showSubmitId,
+            },
+            {
+                id: "submitEntity",
+                primaryLabel: "Submitted entity",
+                secondaryLabel: "ID",
+                columnIndex: JobProps.entity_id,
+                showPrimaryLabel: showEntityLabel,
+                showSecondayLabel: showEntityId,
+            },
+            {
+                id: "createdDt",
+                primaryLabel: "Creation date",
+                columnIndex: JobProps.creation_date,
+                showPrimaryLabel: showDate,
+                showSecondayLabel: showDate,
+            },
+            {
+                id: "status",
+                primaryLabel: "Status",
+                columnIndex: JobProps.status,
+                showPrimaryLabel: true,
+                showSecondayLabel: true,
+            },
+            {
+                id: "actions",
+                columnIndex: -1,
+                primaryLabel: "Actions",
+                showPrimaryLabel: showCancel,
+                showSecondayLabel: showDelete,
+            },
+        ],
+        [showDate, showEntityId, showEntityLabel, showJobId, showSubmitId, showCancel, showDelete]
+    );
+
+    const handleClick = useCallback((event: React.MouseEvent<HTMLElement>) => {
+        event.stopPropagation();
+        const { id = "" } = event.currentTarget?.dataset || {};
+        setChecked((oldChecked) => {
+            const newChecked = [...oldChecked];
+            const checkedIndex = newChecked.indexOf(id);
+
+            if (checkedIndex === -1) {
+                newChecked.push(id);
+            } else {
+                newChecked.splice(checkedIndex, 1);
+            }
+            return newChecked;
+        });
+    }, []);
+
+    const handleSelection = useCallback((event: React.MouseEvent<HTMLElement>) => {
+        const { id = "" } = event.currentTarget?.dataset || {};
+        setSelected((oldSelected) => {
+            const newSelected = [...oldSelected];
+            const selectedIndex = newSelected.indexOf(id);
+
+            if (selectedIndex === -1) {
+                newSelected.push(id);
+            } else {
+                newSelected.splice(selectedIndex, 1);
+            }
+            const jobsVar = getUpdateVar(props.updateVars, "jobs");
+            dispatch(
+                createSendUpdateAction(
+                    props.updateVarName,
+                    newSelected,
+                    module,
+                    props.onChange,
+                    propagate,
+                    jobsVar
+                )
+            );
+            return newSelected;
+        });
+    }, [dispatch, module, props.onChange, props.updateVars, props.updateVarName, propagate]);
+
+    const handleCheckAllClick = useCallback(
+        (event: React.ChangeEvent<HTMLInputElement>) =>
+            setChecked(event.target.checked ? jobRows.map((n) => n[JobProps.id]) : []),
+        [jobRows]
+    );
+
+    const handleCancelJobs = useCallback(
+        (event: React.MouseEvent<HTMLElement>) => {
+            event.stopPropagation();
+            const { id = "", multiple = false } = event.currentTarget?.dataset || {};
+            try {
+                dispatch(createSendActionNameAction(props.id, module, props.onJobAction, {id: multiple === false ? [id] : JSON.parse(id), action: "cancel"}));
+            } catch (e) {
+                console.warn("Error parsing ids for cancel.", e);
+            }
+        },
+        [dispatch, module, props.id, props.onJobAction]
+    );
+
+    const handleDeleteJobs = useCallback(
+        (event: React.MouseEvent<HTMLElement>) => {
+            event.stopPropagation();
+            const { id = "", multiple = false } = event.currentTarget?.dataset || {};
+            try {
+                dispatch(createSendActionNameAction(props.id, module, props.onJobAction, {id: multiple === false ? [id] : JSON.parse(id), action: "delete"}));
+            } catch (e) {
+                console.warn("Error parsing ids for delete.", e);
+            }
+        },
+        [dispatch, module, props.id, props.onJobAction]
+    );
+
+    const allowCancelJobs = useMemo(
+        () =>
+            !!checked.length &&
+            !jobRows.some(
+                (job) =>
+                    checked.includes(job[JobProps.id]) &&
+                    !(job[JobProps.status] === JobStatus.SUBMITTED || job[JobProps.status] === JobStatus.BLOCKED || job[JobProps.status] === JobStatus.PENDING)
+            ),
+        [jobRows, checked]
+    );
+
+    const allowDeleteJobs = useMemo(
+        () =>
+            !!checked.length &&
+            !jobRows.some(
+                (job) =>
+                    checked.includes(job[JobProps.id]) &&
+                    !(
+                        job[JobProps.status] === JobStatus.CANCELED ||
+                        job[JobProps.status] === JobStatus.FAILED ||
+                        job[JobProps.status] === JobStatus.SKIPPED ||
+                        job[JobProps.status] === JobStatus.ABANDONED
+                    )
+            ),
+        [jobRows, checked]
+    );
+
+    const handleFilterOpen = useCallback((event: React.MouseEvent<HTMLButtonElement>) => {
+        setAnchorEl(event.currentTarget);
+    }, []);
+
+    const handleFilterClose = useCallback(() => {
+        setAnchorEl(null);
+    }, []);
+
+    useEffect(() => {
+        if (props.jobs) {
+            if (filters) {
+                let filteredJobRows = [...props.jobs];
+                filteredJobRows.length &&
+                    filters
+                        .filter((filter) => filter.data && filter.operator)
+                        .forEach((filter) => {
+                            filteredJobRows = filteredJobRows.filter((job) => {
+                                let rowColumnValue = "";
+                                if (filter.data === JobProps.status) {
+                                    rowColumnValue = JobStatus[job[JobProps.status]].toLowerCase();
+                                } else if (filter.data === JobProps.id) {
+                                    rowColumnValue = `${job[JobProps.id].toLowerCase()}${job[JobProps.name].toLowerCase()}`;
+                                } else if (filter.data === JobProps.entity_id) {
+                                    rowColumnValue = `${job[JobProps.entity_id].toLowerCase()}${job[JobProps.entity_name].toLowerCase()}`;
+                                } else if (filter.data === JobProps.creation_date) {
+                                    rowColumnValue = new Date(job[JobProps.creation_date]).toLocaleString();
+                                } else if (filter.data < JobLength) {
+                                    rowColumnValue = job[filter.data].toString().toLowerCase();
+                                }
+                                const includes = rowColumnValue.includes(filter.value.toLowerCase());
+                                return filter.operator === "is" ? includes : !includes;
+                            });
+                        });
+                setJobRows(filteredJobRows);
+            } else {
+                setJobRows(props.jobs);
+            }
+        }
+    }, [filters, props.jobs]);
+
+    useEffect(() => {
+        if (props.value) {
+            setSelected(Array.isArray(props.value) ? props.value: [props.value]);
+        } else if (props.defaultValue) {
+            try {
+                const val = JSON.parse(props.defaultValue);
+                setSelected(Array.isArray(val) ? val: [val]);
+            } catch (e) {
+                console.warn("Error parsing jobs default value", e);
+            }
+        }
+    }, [props.value, props.defaultValue]);
+
+    useEffect(() => {
+        if (props.coreChanged?.jobs) {
+            const updateVar = getUpdateVar(props.updateVars, "jobs");
+            updateVar && dispatch(createRequestUpdateAction(id, module, [updateVar], true));
+        }
+    }, [props.coreChanged, props.updateVars, module, dispatch, id]);
+
+    const tableHeightSx = useMemo(() => ({ maxHeight: props.height || "50vh" }), [props.height]);
+
+    return (
+        <Box className={className}>
+            <Paper sx={containerSx}>
+                <Toolbar sx={headerToolbarSx}>
+                    <Grid container spacing={2} alignItems="center">
+                        <Grid item container xs={3} alignItems="center">
+                            <Tooltip title="Filter">
+                                <IconButton onClick={handleFilterOpen}>
+                                    <FilterList />
+                                </IconButton>
+                            </Tooltip>
+                            {filters && filters.length ? (
+                                <Typography component="div">
+                                    {filters.length} filter{filters.length > 1 ? "s" : ""}
+                                </Typography>
+                            ) : null}
+                        </Grid>
+                        {checked.length ? (
+                            <>
+                                <Grid item xs={7}>
+                                    <Typography variant="subtitle1">{checked.length} selected</Typography>
+                                </Grid>
+                                <Grid item container justifyContent="flex-end" spacing={1} xs={2}>
+                                    {showCancel ? (
+                                        <Tooltip title="Cancel">
+                                            <span>
+                                                <IconButton
+                                                    disabled={!allowCancelJobs}
+                                                    data-id={JSON.stringify(checked)}
+                                                    data-multiple
+                                                    onClick={handleCancelJobs}
+                                                >
+                                                    <StopCircleOutlined
+                                                        color={disableColor("inherit", !allowCancelJobs)}
+                                                    />
+                                                </IconButton>
+                                            </span>
+                                        </Tooltip>
+                                    ) : null}
+                                    {showDelete ? (
+                                        <Tooltip title="Delete">
+                                            <span>
+                                                <IconButton
+                                                    disabled={!allowDeleteJobs}
+                                                    data-id={JSON.stringify(checked)}
+                                                    data-multiple
+                                                    onClick={handleDeleteJobs}
+                                                >
+                                                    <DeleteOutline color={disableColor("primary", !allowDeleteJobs)} />
+                                                </IconButton>
+                                            </span>
+                                        </Tooltip>
+                                    ) : null}
+                                    <Box sx={toolbarRightSx} />
+                                </Grid>
+                            </>
+                        ) : null}
+
+                        <Filter
+                            open={!!anchorEl}
+                            anchorEl={anchorEl}
+                            handleFilterClose={handleFilterClose}
+                            handleApplyFilter={setFilters}
+                            columns={jobSelectorColumns}
+                        />
+                    </Grid>
+                </Toolbar>
+                <TableContainer sx={tableHeightSx}>
+                    <Table sx={tableWidthSx} aria-labelledby="tableTitle" size="medium">
+                        <JobSelectedTableHead
+                            jobs={jobRows.length}
+                            selected={checked.length}
+                            handleSelectAllClick={handleCheckAllClick}
+                            columns={jobSelectorColumns}
+                        />
+                        <TableBody>
+                            {jobRows.map((row) => (
+                                <JobSelectedTableRow
+                                    handleSelection={handleSelection}
+                                    handleCheckboxClick={handleClick}
+                                    row={row}
+                                    isSelected={selected.includes(row[JobProps.id])}
+                                    isItemSelected={checked.includes(row[JobProps.id])}
+                                    key={row[JobProps.id]}
+                                    handleDeleteJobs={handleDeleteJobs}
+                                    handleCancelJobs={handleCancelJobs}
+                                    showSubmitId={showSubmitId}
+                                    showJobId={showJobId}
+                                    showEntityLabel={showEntityLabel}
+                                    showEntityId={showEntityId}
+                                    showDate={showDate}
+                                    showCancel={showCancel}
+                                    showDelete={showDelete}
+                                />
+                            ))}
+                        </TableBody>
+                    </Table>
+                </TableContainer>
+            </Paper>
+        </Box>
+    );
+};
+
+export default JobSelector;

+ 10 - 5
gui/src/ScenarioViewer.tsx

@@ -35,7 +35,7 @@ import {
     useModule,
 } from "taipy-gui";
 
-import { FlagSx, ScFProps, ScenarioFull, ScenarioFullLength, useClassNames } from "./utils";
+import { FlagSx, ScFProps, ScenarioFull, ScenarioFullLength, disableColor, useClassNames } from "./utils";
 import ConfirmDialog from "./utils/ConfirmDialog";
 
 type Property = {
@@ -130,8 +130,6 @@ const AccordionSummarySx = {
     },
 };
 
-const disableColor = <T,>(color: T, disabled: boolean) => (disabled ? ("disabled" as T) : color);
-
 const PipelineRow = ({
     active,
     number,
@@ -208,7 +206,11 @@ const PipelineRow = ({
             </Grid>
             <Grid item xs={2} container alignContent="center" alignItems="center" justifyContent="center">
                 {submit ? (
-                    <IconButton size="small" onClick={onSubmitPipeline} disabled={!enableScenarioFields || !active || !submittable}>
+                    <IconButton
+                        size="small"
+                        onClick={onSubmitPipeline}
+                        disabled={!enableScenarioFields || !active || !submittable}
+                    >
                         <Send color={disableColor("info", !enableScenarioFields || !active || !submittable)} />
                     </IconButton>
                 ) : null}
@@ -515,7 +517,10 @@ const ScenarioViewer = (props: ScenarioViewerProps) => {
                                         onClick={submitScenario}
                                         disabled={!isScenario || !active || !scSubmittable}
                                     >
-                                        <Send fontSize="medium" color={disableColor("info", !isScenario || !active || !scSubmittable)} />
+                                        <Send
+                                            fontSize="medium"
+                                            color={disableColor("info", !isScenario || !active || !scSubmittable)}
+                                        />
                                     </IconButton>
                                 ) : null}
                             </Grid>

+ 2 - 1
gui/src/index.ts

@@ -2,5 +2,6 @@ import ScenarioSelector from "./ScenarioSelector";
 import ScenarioViewer from "./ScenarioViewer";
 import ScenarioDag from "./ScenarioDag";
 import NodeSelector from "./NodeSelector";
+import JobSelector from "./JobSelector";
 
-export { ScenarioSelector, ScenarioDag, ScenarioViewer as Scenario, NodeSelector as DataNodeSelector };
+export { ScenarioSelector, ScenarioDag, ScenarioViewer as Scenario, NodeSelector as DataNodeSelector, JobSelector };

+ 2 - 0
gui/src/utils.ts

@@ -106,3 +106,5 @@ export const ParentItemSx = {
 
 export const useClassNames = (libClassName?: string, dynamicClassName?: string, className?: string) =>
     ((libClassName || "") + " " + (useDynamicProperty(dynamicClassName, className, undefined) || "")).trim();
+
+export const disableColor = <T>(color: T, disabled: boolean) => (disabled ? ("disabled" as T) : color);

+ 90 - 2
src/taipy/gui_core/GuiCoreLib.py

@@ -18,10 +18,19 @@ from threading import Lock
 from dateutil import parser
 
 from taipy.config import Config
-from taipy.core import Cycle, DataNode, Pipeline, Scenario, create_scenario
+from taipy.core import Cycle, DataNode, Job, Pipeline, Scenario, cancel_job, create_scenario
 from taipy.core import delete as core_delete
+from taipy.core import delete_job
 from taipy.core import get as core_get
-from taipy.core import get_cycles_scenarios, get_data_nodes, is_deletable, is_promotable, is_submittable, set_primary
+from taipy.core import (
+    get_cycles_scenarios,
+    get_data_nodes,
+    get_jobs,
+    is_deletable,
+    is_promotable,
+    is_submittable,
+    set_primary,
+)
 from taipy.core import submit as core_submit
 from taipy.core.notification import CoreEventConsumerBase, EventEntityType
 from taipy.core.notification.event import Event
@@ -42,6 +51,7 @@ class _GCDoNotUpdate(_DoNotUpdate):
 
 Scenario.__bases__ += (_GCDoNotUpdate,)
 DataNode.__bases__ += (_GCDoNotUpdate,)
+Job.__bases__ += (_GCDoNotUpdate,)
 
 Config.configure_global_app(read_entity_retry=3)
 
@@ -132,16 +142,19 @@ class _GuiCoreContext(CoreEventConsumerBase):
     __PROP_SCENARIO_PRIMARY = "primary"
     __PROP_SCENARIO_TAGS = "tags"
     __SCENARIO_PROPS = (__PROP_SCENARIO_CONFIG_ID, __PROP_SCENARIO_DATE, __PROP_ENTITY_NAME)
+    __JOB_ACTION = "action"
     _CORE_CHANGED_NAME = "core_changed"
     _SCENARIO_SELECTOR_ERROR_VAR = "gui_core_sc_error"
     _SCENARIO_SELECTOR_ID_VAR = "gui_core_sc_id"
     _SCENARIO_VIZ_ERROR_VAR = "gui_core_sv_error"
+    _JOB_SELECTOR_ERROR_VAR = "gui_core_js_error"
 
     def __init__(self, gui: Gui) -> None:
         self.gui = gui
         self.scenario_by_cycle: t.Optional[t.Dict[t.Optional[Cycle], t.List[Scenario]]] = None
         self.data_nodes_by_owner: t.Optional[t.Dict[t.Optional[str], DataNode]] = None
         self.scenario_configs: t.Optional[t.List[t.Tuple[str, str]]] = None
+        self.jobs_list: t.Optional[t.List[Job]] = None
         # register to taipy core notification
         reg_id, reg_queue = Notifier.register()
         self.lock = Lock()
@@ -165,6 +178,10 @@ class _GuiCoreContext(CoreEventConsumerBase):
                     self.gui.broadcast(
                         _GuiCoreContext._CORE_CHANGED_NAME, {"scenario": [x for x in pipeline.parent_ids]}
                     )
+        elif event.entity_type == EventEntityType.JOB:
+            with self.lock:
+                self.jobs_list = None
+            self.gui.broadcast(_GuiCoreContext._CORE_CHANGED_NAME, {"jobs": True})
         elif event.entity_type == EventEntityType.DATA_NODE:
             with self.lock:
                 self.data_nodes_by_owner = None
@@ -365,12 +382,57 @@ class _GuiCoreContext(CoreEventConsumerBase):
                             return (data.id, data.get_simple_label(), datanodes, _EntityType.PIPELINE.value, False)
         return None
 
+    def get_jobs_list(self):
+        with self.lock:
+            if self.jobs_list is None:
+                self.jobs_list = get_jobs()
+            return self.jobs_list
+
+    def job_adapter(self, data):
+        if hasattr(data, "id") and core_get(data.id) is not None:
+            if isinstance(data, Job):
+                # entity = core_get(data.owner_id)
+                return (
+                    data.id,
+                    data.get_simple_label(),
+                    [],
+                    "",
+                    "",
+                    data.submit_id,
+                    data.creation_date,
+                    data.status.value,
+                )
+
+    def act_on_jobs(self, state: State, id: str, action: str, payload: t.Dict[str, str]):
+        args = payload.get("args")
+        if args is None or not isinstance(args, list) or len(args) < 1 or not isinstance(args[0], dict):
+            return
+        data = args[0]
+        job_ids = data.get(_GuiCoreContext.__PROP_ENTITY_ID)
+        job_action = data.get(_GuiCoreContext.__JOB_ACTION)
+        if job_action and isinstance(job_ids, list):
+            errs = []
+            if job_action == "delete":
+                for job_id in job_ids:
+                    try:
+                        delete_job(core_get(job_id))
+                    except Exception as e:
+                        errs.append(f"Error deleting job. {e}")
+            elif job_action == "cancel":
+                for job_id in job_ids:
+                    try:
+                        cancel_job(job_id)
+                    except Exception as e:
+                        errs.append(f"Error canceling job. {e}")
+            state.assign(_GuiCoreContext._JOB_SELECTOR_ERROR_VAR, "<br/>".join(errs) if errs else "")
+
 
 class _GuiCore(ElementLibrary):
     __LIB_NAME = "taipy_gui_core"
     __CTX_VAR_NAME = f"__{__LIB_NAME}_Ctx"
     __SCENARIO_ADAPTER = "tgc_scenario"
     __DATANODE_ADAPTER = "tgc_datanode"
+    __JOB_ADAPTER = "tgc_job"
 
     __elts = {
         "scenario_selector": Element(
@@ -457,6 +519,30 @@ class _GuiCore(ElementLibrary):
                 "type": ElementProperty(PropertyType.inner, __DATANODE_ADAPTER),
             },
         ),
+        "job_selector": Element(
+            "value",
+            {
+                "id": ElementProperty(PropertyType.string),
+                "class_name": ElementProperty(PropertyType.dynamic_string),
+                "value": ElementProperty(PropertyType.lov_value),
+                "show_job_id": ElementProperty(PropertyType.boolean, True),
+                "show_entity_label": ElementProperty(PropertyType.boolean, True),
+                "show_entity_id": ElementProperty(PropertyType.boolean, False),
+                "show_submit_id": ElementProperty(PropertyType.boolean, False),
+                "show_date": ElementProperty(PropertyType.boolean, True),
+                "show_cancel": ElementProperty(PropertyType.boolean, True),
+                "show_delete": ElementProperty(PropertyType.boolean, True),
+                "on_change": ElementProperty(PropertyType.function),
+                "height": ElementProperty(PropertyType.string, "50vh"),
+            },
+            inner_properties={
+                "jobs": ElementProperty(PropertyType.lov, f"{{{__CTX_VAR_NAME}.get_jobs_list()}}"),
+                "core_changed": ElementProperty(PropertyType.broadcast, _GuiCoreContext._CORE_CHANGED_NAME),
+                "type": ElementProperty(PropertyType.inner, __JOB_ADAPTER),
+                "on_job_action": ElementProperty(PropertyType.function, f"{{{__CTX_VAR_NAME}.act_on_jobs}}"),
+                "error": ElementProperty(PropertyType.dynamic_string, f"{{{_GuiCoreContext._JOB_SELECTOR_ERROR_VAR}}}"),
+            },
+        ),
     }
 
     def get_name(self) -> str:
@@ -472,6 +558,7 @@ class _GuiCore(ElementLibrary):
         ctx = _GuiCoreContext(gui)
         gui._add_adapter_for_type(_GuiCore.__SCENARIO_ADAPTER, ctx.scenario_adapter)
         gui._add_adapter_for_type(_GuiCore.__DATANODE_ADAPTER, ctx.data_node_adapter)
+        gui._add_adapter_for_type(_GuiCore.__JOB_ADAPTER, ctx.job_adapter)
         return _GuiCore.__CTX_VAR_NAME, ctx
 
     def on_user_init(self, state: State):
@@ -479,6 +566,7 @@ class _GuiCore(ElementLibrary):
             _GuiCoreContext._SCENARIO_SELECTOR_ERROR_VAR,
             _GuiCoreContext._SCENARIO_SELECTOR_ID_VAR,
             _GuiCoreContext._SCENARIO_VIZ_ERROR_VAR,
+            _GuiCoreContext._JOB_SELECTOR_ERROR_VAR,
         ]:
             state._add_attribute(var, "")
 

+ 84 - 1
src/taipy/gui_core/viselements.json

@@ -197,7 +197,7 @@
             "name": "display_cycles",
             "type": "bool",
             "default_value": "True",
-            "doc": "If False, the cycles are now shown in the selector."
+            "doc": "If False, the cycles are not shown in the selector."
           },
           {
             "name": "show_primary_flag",
@@ -232,6 +232,89 @@
           }
         ]
       }
+    ],
+    [
+      "job_selector",
+      {
+        "inherits": [
+          "core_gui_shared"
+        ],
+        "properties": [
+          {
+            "name": "value",
+            "default_property": true,
+            "type": "dynamic(Job|list[Job])",
+            "doc": "Bound to the selected <code>Job^</code>(s), or None if there is none."
+          },
+          {
+            "name": "show_job_id",
+            "type": "bool",
+            "default_value": "True",
+            "doc": "If False, the <code>Job^</code> id is not shown in the selector."
+          },
+          {
+            "name": "show_entity_label",
+            "type": "bool",
+            "default_value": "True",
+            "doc": "If False, the <code>Entity^</code> label is not shown in the selector."
+          },
+          {
+            "name": "show_entity_id",
+            "type": "bool",
+            "default_value": "False",
+            "doc": "If True, the <code>Entity^</code> id is shown in the selector."
+          },
+          {
+            "name": "show_submit_id",
+            "type": "bool",
+            "default_value": "False",
+            "doc": "If True, the <code>Submit^</code> id is shown in the selector."
+          },
+          {
+            "name": "show_date",
+            "type": "bool",
+            "default_value": "True",
+            "doc": "If False, the <code>Job^</code> date is not shown in the selector."
+          },
+          {
+            "name": "show_cancel",
+            "type": "bool",
+            "default_value": "True",
+            "doc": "If False, the cancel buttons are not shown in the selector."
+          },
+          {
+            "name": "show_delete",
+            "type": "bool",
+            "default_value": "True",
+            "doc": "If False, the delete button are not shown in the selector."
+          },
+          {
+            "name": "on_change",
+            "type": "callback",
+            "doc": "The name of a function that is triggered when the selection is updated.<br/>The parameters of that function are all optional:\n<ul>\n<li>state (<code>State^</code>): the state instance.</li>\n<li>var_name (str): the variable name.</li>\n<li>value (<code>Job^</code>): the selected job.</li>\n</ul>",
+            "signature": [
+              [
+                "state",
+                "State"
+              ],
+              [
+                "var_name",
+                "str"
+              ],
+              [
+                "value",
+                "optional[Job]"
+              ]
+            ]
+          },
+          {
+            "name": "height",
+            "type": "str",
+            "default_value": "50vh",
+            "doc": "The maximum height, in CSS units,of the tree."
+          }
+        ]
+      }
     ]
   ],
   "undocumented": [