diff --git a/hwproj.front/package-lock.json b/hwproj.front/package-lock.json index a0882868d..fe2e3c324 100644 --- a/hwproj.front/package-lock.json +++ b/hwproj.front/package-lock.json @@ -20,6 +20,7 @@ "@mui/lab": "^5.0.0-alpha.99", "@mui/material": "^5.16.11", "@mui/x-charts": "^8.2.0", + "@reduxjs/toolkit": "^2.11.2", "@storybook/addon-knobs": "^6.3.0", "@types/bluebird": "^3.5.36", "@types/classnames": "^2.3.1", @@ -52,6 +53,7 @@ "react-drag-drop-files": "^3.1.0", "react-markdown": "^5.0.0", "react-query": "^3.21.1", + "react-redux": "^9.2.0", "react-router-dom": "^6.5.0", "react-social-login-buttons": "^3.5.1", "react-syntax-highlighter": "^15.5.0", @@ -4564,6 +4566,32 @@ "url": "https://opencollective.com/popperjs" } }, + "node_modules/@reduxjs/toolkit": { + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.11.2.tgz", + "integrity": "sha512-Kd6kAHTA6/nUpp8mySPqj3en3dm0tdMIgbttnQ1xFMVpufoj+ADi8pXLBsd4xzTRHQa7t/Jv8W5UnCuW4kuWMQ==", + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@standard-schema/utils": "^0.3.0", + "immer": "^11.0.0", + "redux": "^5.0.1", + "redux-thunk": "^3.1.0", + "reselect": "^5.1.0" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17.0.0 || ^18 || ^19", + "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-redux": { + "optional": true + } + } + }, "node_modules/@remix-run/router": { "version": "1.23.0", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.0.tgz", @@ -4874,6 +4902,18 @@ "dev": true, "license": "MIT" }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "license": "MIT" + }, + "node_modules/@standard-schema/utils": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz", + "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==", + "license": "MIT" + }, "node_modules/@storybook/addon-knobs": { "version": "6.4.0", "resolved": "https://registry.npmjs.org/@storybook/addon-knobs/-/addon-knobs-6.4.0.tgz", @@ -4977,6 +5017,7 @@ "version": "6.5.16", "resolved": "https://registry.npmjs.org/@storybook/addons/-/addons-6.5.16.tgz", "integrity": "sha512-p3DqQi+8QRL5k7jXhXmJZLsE/GqHqyY6PcoA1oNTJr0try48uhTGUOYkgzmqtDaa/qPFO5LP+xCPzZXckGtquQ==", + "dev": true, "license": "MIT", "dependencies": { "@storybook/api": "6.5.16", @@ -5004,12 +5045,14 @@ "version": "0.13.11", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "dev": true, "license": "MIT" }, "node_modules/@storybook/api": { "version": "6.5.16", "resolved": "https://registry.npmjs.org/@storybook/api/-/api-6.5.16.tgz", "integrity": "sha512-HOsuT8iomqeTMQJrRx5U8nsC7lJTwRr1DhdD0SzlqL4c80S/7uuCy4IZvOt4sYQjOzW5fOo/kamcoBXyLproTA==", + "dev": true, "license": "MIT", "dependencies": { "@storybook/channels": "6.5.16", @@ -5043,6 +5086,7 @@ "version": "0.13.11", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "dev": true, "license": "MIT" }, "node_modules/@storybook/builder-webpack4": { @@ -5441,6 +5485,7 @@ "version": "6.5.16", "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-6.5.16.tgz", "integrity": "sha512-VylzaWQZaMozEwZPJdyJoz+0jpDa8GRyaqu9TGG6QGv+KU5POoZaGLDkRE7TzWkyyP0KQLo80K99MssZCpgSeg==", + "dev": true, "license": "MIT", "dependencies": { "core-js": "^3.8.2", @@ -5500,6 +5545,7 @@ "version": "6.5.16", "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-6.5.16.tgz", "integrity": "sha512-pxcNaCj3ItDdicPTXTtmYJE3YC1SjxFrBmHcyrN+nffeNyiMuViJdOOZzzzucTUG0wcOOX8jaSyak+nnHg5H1Q==", + "dev": true, "license": "MIT", "dependencies": { "core-js": "^3.8.2", @@ -5514,6 +5560,7 @@ "version": "6.5.16", "resolved": "https://registry.npmjs.org/@storybook/components/-/components-6.5.16.tgz", "integrity": "sha512-LzBOFJKITLtDcbW9jXl0/PaG+4xAz25PK8JxPZpIALbmOpYWOAPcO6V9C2heX6e6NgWFMUxjplkULEk9RCQMNA==", + "dev": true, "license": "MIT", "dependencies": { "@storybook/client-logger": "6.5.16", @@ -5538,6 +5585,7 @@ "version": "0.13.11", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "dev": true, "license": "MIT" }, "node_modules/@storybook/core": { @@ -5704,6 +5752,7 @@ "version": "6.5.16", "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-6.5.16.tgz", "integrity": "sha512-qMZQwmvzpH5F2uwNUllTPg6eZXr2OaYZQRRN8VZJiuorZzDNdAFmiVWMWdkThwmyLEJuQKXxqCL8lMj/7PPM+g==", + "dev": true, "license": "MIT", "dependencies": { "core-js": "^3.8.2" @@ -5804,6 +5853,7 @@ "version": "0.0.2--canary.4566f4d.1", "resolved": "https://registry.npmjs.org/@storybook/csf/-/csf-0.0.2--canary.4566f4d.1.tgz", "integrity": "sha512-9OVvMVh3t9znYZwb0Svf/YQoxX2gVOeQTGe2bses2yj+a3+OJnCrUF3/hGv6Em7KujtOdL2LL+JnG49oMVGFgQ==", + "dev": true, "license": "MIT", "dependencies": { "lodash": "^4.17.15" @@ -6276,6 +6326,7 @@ "version": "6.5.16", "resolved": "https://registry.npmjs.org/@storybook/router/-/router-6.5.16.tgz", "integrity": "sha512-ZgeP8a5YV/iuKbv31V8DjPxlV4AzorRiR8OuSt/KqaiYXNXlOoQDz/qMmiNcrshrfLpmkzoq7fSo4T8lWo2UwQ==", + "dev": true, "license": "MIT", "dependencies": { "@storybook/client-logger": "6.5.16", @@ -6297,12 +6348,14 @@ "version": "0.13.11", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "dev": true, "license": "MIT" }, "node_modules/@storybook/semver": { "version": "7.3.2", "resolved": "https://registry.npmjs.org/@storybook/semver/-/semver-7.3.2.tgz", "integrity": "sha512-SWeszlsiPsMI0Ps0jVNtH64cI5c0UF3f7KgjVKJoNP30crQ6wUSddY2hsdeczZXEKVJGEn50Q60flcGsQGIcrg==", + "dev": true, "license": "ISC", "dependencies": { "core-js": "^3.6.5", @@ -6319,6 +6372,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, "license": "MIT", "dependencies": { "locate-path": "^5.0.0", @@ -6332,6 +6386,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, "license": "MIT", "dependencies": { "p-locate": "^4.1.0" @@ -6344,6 +6399,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, "license": "MIT", "dependencies": { "p-try": "^2.0.0" @@ -6359,6 +6415,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, "license": "MIT", "dependencies": { "p-limit": "^2.2.0" @@ -6442,6 +6499,7 @@ "version": "6.5.16", "resolved": "https://registry.npmjs.org/@storybook/theming/-/theming-6.5.16.tgz", "integrity": "sha512-hNLctkjaYLRdk1+xYTkC1mg4dYz2wSv6SqbLpcKMbkPHTE0ElhddGPHQqB362md/w9emYXNkt1LSMD8Xk9JzVQ==", + "dev": true, "license": "MIT", "dependencies": { "@storybook/client-logger": "6.5.16", @@ -6462,6 +6520,7 @@ "version": "0.13.11", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "dev": true, "license": "MIT" }, "node_modules/@storybook/ui": { @@ -7088,6 +7147,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/@types/is-function/-/is-function-1.0.3.tgz", "integrity": "sha512-/CLhCW79JUeLKznI6mbVieGbl4QU5Hfn+6udw1YHZoofASjbQ5zaP5LzAUZYDpRYEjS4/P+DhEgyJ/PQmGGTWw==", + "dev": true, "license": "MIT" }, "node_modules/@types/isomorphic-fetch": { @@ -7437,6 +7497,12 @@ "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", "license": "MIT" }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", + "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==", + "license": "MIT" + }, "node_modules/@types/webpack": { "version": "4.41.40", "resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.40.tgz", @@ -7456,6 +7522,7 @@ "version": "1.18.8", "resolved": "https://registry.npmjs.org/@types/webpack-env/-/webpack-env-1.18.8.tgz", "integrity": "sha512-G9eAoJRMLjcvN4I08wB5I7YofOb/kaJNd5uoCMX+LbKXTPCF+ZIHuqTnFaK9Jz1rgs035f9JUPUhNFtqgucy/A==", + "dev": true, "license": "MIT" }, "node_modules/@types/webpack-sources": { @@ -17528,6 +17595,16 @@ "node": ">= 4" } }, + "node_modules/immer": { + "version": "11.1.3", + "resolved": "https://registry.npmjs.org/immer/-/immer-11.1.3.tgz", + "integrity": "sha512-6jQTc5z0KJFtr1UgFpIL3N9XSC3saRaI9PwWtzM2pSqkNGtiNkYY2OSwkOGDK2XcTRcLb1pi/aNkKZz0nxVH4Q==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, "node_modules/import-fresh": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", @@ -18027,6 +18104,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.2.tgz", "integrity": "sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ==", + "dev": true, "license": "MIT" }, "node_modules/is-generator-function": { @@ -18149,6 +18227,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -18226,6 +18305,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -18697,13 +18777,6 @@ "node": ">= 10.13.0" } }, - "node_modules/jquery": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz", - "integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==", - "license": "MIT", - "peer": true - }, "node_modules/js-sha3": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", @@ -19343,6 +19416,7 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/map-or-similar/-/map-or-similar-1.5.0.tgz", "integrity": "sha512-0aF7ZmVon1igznGI4VS30yugpduQW3y3GkcgGJOp7d8x8QrizhigUxjI/m2UojsXXto+jLAH3KSz+xOJTiORjg==", + "dev": true, "license": "MIT" }, "node_modules/map-visit": { @@ -20535,6 +20609,7 @@ "version": "1.11.3", "resolved": "https://registry.npmjs.org/memoizerific/-/memoizerific-1.11.3.tgz", "integrity": "sha512-/EuHYwAPdLtXwAwSZkh/Gutery6pD2KYd44oQLhAvQp/50mpyduZh8Q7PYHXTCJ+wuXxt7oij2LXyIJOOYFPog==", + "dev": true, "license": "MIT", "dependencies": { "map-or-similar": "^1.5.0" @@ -20591,21 +20666,6 @@ "safe-buffer": "~5.1.0" } }, - "node_modules/meow": { - "version": "13.2.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz", - "integrity": "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/merge-descriptors": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", @@ -22516,6 +22576,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -22718,6 +22779,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -22870,18 +22932,6 @@ "node": ">=6" } }, - "node_modules/popper.js": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz", - "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==", - "deprecated": "You can find the new Popper v2 at @popperjs/core, this package is dedicated to the legacy v1", - "license": "MIT", - "peer": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/popperjs" - } - }, "node_modules/portable-fetch": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/portable-fetch/-/portable-fetch-3.0.0.tgz", @@ -23769,6 +23819,29 @@ } } }, + "node_modules/react-redux": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz", + "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==", + "license": "MIT", + "dependencies": { + "@types/use-sync-external-store": "^0.0.6", + "use-sync-external-store": "^1.4.0" + }, + "peerDependencies": { + "@types/react": "^18.2.25 || ^19", + "react": "^18.0 || ^19", + "redux": "^5.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, "node_modules/react-refresh": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz", @@ -24076,6 +24149,21 @@ "node": ">=8" } }, + "node_modules/redux": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", + "license": "MIT" + }, + "node_modules/redux-thunk": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", + "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==", + "license": "MIT", + "peerDependencies": { + "redux": "^5.0.0" + } + }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", @@ -27176,6 +27264,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -28052,6 +28141,7 @@ "version": "2.14.4", "resolved": "https://registry.npmjs.org/store2/-/store2-2.14.4.tgz", "integrity": "sha512-srTItn1GOvyvOycgxjAnPA63FZNwy0PTyUBFMHRM+hVFltAeoh0LmNBz9SZqUS9mMqGk8rfyWyXn3GH5ReJ8Zw==", + "dev": true, "license": "MIT" }, "node_modules/stream-browserify": { @@ -28685,6 +28775,7 @@ "version": "6.0.8", "resolved": "https://registry.npmjs.org/telejson/-/telejson-6.0.8.tgz", "integrity": "sha512-nerNXi+j8NK1QEfBHtZUN/aLdDcyupA//9kAboYLrtzZlPLpUfqbVGWb9zz91f/mIjRbAYhbgtnJHY8I1b5MBg==", + "dev": true, "license": "MIT", "dependencies": { "@types/is-function": "^1.0.0", @@ -28701,6 +28792,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/isobject/-/isobject-4.0.0.tgz", "integrity": "sha512-S/2fF5wH8SJA/kmwr6HYhK/RI/OkhD84k8ntalo0iJjZikgq1XFvR5M8NPT1x5F7fBwCG3qHfnzeP/Vh/ZxCUA==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -29222,6 +29314,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.2.0.tgz", "integrity": "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.10" @@ -30114,6 +30207,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, "license": "MIT" }, "node_modules/util.promisify": { @@ -31969,21 +32063,6 @@ "dev": true, "license": "ISC" }, - "node_modules/yaml": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.1.tgz", - "integrity": "sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ==", - "dev": true, - "license": "ISC", - "optional": true, - "peer": true, - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/yargs": { "version": "16.2.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", diff --git a/hwproj.front/package.json b/hwproj.front/package.json index 0ddb05582..9698a7bd2 100644 --- a/hwproj.front/package.json +++ b/hwproj.front/package.json @@ -16,6 +16,7 @@ "@mui/lab": "^5.0.0-alpha.99", "@mui/material": "^5.16.11", "@mui/x-charts": "^8.2.0", + "@reduxjs/toolkit": "^2.11.2", "@storybook/addon-knobs": "^6.3.0", "@types/bluebird": "^3.5.36", "@types/classnames": "^2.3.1", @@ -48,6 +49,7 @@ "react-drag-drop-files": "^3.1.0", "react-markdown": "^5.0.0", "react-query": "^3.21.1", + "react-redux": "^9.2.0", "react-router-dom": "^6.5.0", "react-social-login-buttons": "^3.5.1", "react-syntax-highlighter": "^15.5.0", diff --git a/hwproj.front/src/components/Common/MentorsList.tsx b/hwproj.front/src/components/Common/MentorsList.tsx index 68adeb04a..de10792f1 100644 --- a/hwproj.front/src/components/Common/MentorsList.tsx +++ b/hwproj.front/src/components/Common/MentorsList.tsx @@ -1,14 +1,18 @@ import {FC} from "react"; -import {AccountDataDto} from "@/api"; import {Typography} from "@material-ui/core"; import * as React from "react"; import {Stack, Tooltip} from "@mui/material"; +import { useAppSelector } from "@/store/hooks"; +import { AccountDataDto } from "@/api"; -const MentorsList: FC<{ - mentors: AccountDataDto[] -}> = (props) => { +interface MentorsListProps { + mentors?: AccountDataDto[]; +} + +const MentorsList: FC = ({ mentors: propMentors }) => { + const reduxMentors = useAppSelector(state => state.course.mentors); + const mentors = propMentors ?? reduxMentors; const count = 1 - const {mentors} = props const mentorsToShow = mentors.length > count ? mentors.slice(0, count) : mentors const mentorsToHide = mentors.length > count ? mentors.slice(count) : [] const fontSize = 18 diff --git a/hwproj.front/src/components/Courses/Course.tsx b/hwproj.front/src/components/Courses/Course.tsx index c2309e0fe..f9f482873 100644 --- a/hwproj.front/src/components/Courses/Course.tsx +++ b/hwproj.front/src/components/Courses/Course.tsx @@ -1,13 +1,6 @@ import * as React from "react"; import {useSearchParams} from "react-router-dom"; -import { - AccountDataDto, - CourseViewModel, - FileInfoDTO, - HomeworkViewModel, - ScopeDTO, - StatisticsCourseMatesModel -} from "@/api"; +import {FileInfoDTO,ScopeDTO,} from "@/api"; import StudentStats from "./StudentStats"; import NewCourseStudents from "./NewCourseStudents"; import ApiSingleton from "../../api/ApiSingleton"; @@ -40,6 +33,12 @@ import {MoreVert} from "@mui/icons-material"; import {DotLottieReact} from "@lottiefiles/dotlottie-react"; import {CourseUnitType} from "../Files/CourseUnitType"; import {FileStatus} from "../Files/FileStatus"; +import {useAppDispatch, useAppSelector} from "@/store/hooks"; +import {setCourse, setMentors, setAcceptedStudents, setNewStudents} from "@/store/slices/courseSlice"; +import {setHomeworks} from "@/store/slices/homeworkSlice"; +import {setStudentSolutions} from "@/store/slices/solutionSlice"; +import {setCourseFiles, updateCourseFiles, setProcessingLoading} from "@/store/slices/courseFileSlice"; +import {setAuth} from "@/store/slices/authSlice"; type TabValue = "homeworks" | "stats" | "applications" @@ -47,27 +46,6 @@ function isAcceptableTabValue(str: string): str is TabValue { return str === "homeworks" || str === "stats" || str === "applications"; } -interface ICourseState { - isFound: boolean; - course: CourseViewModel; - courseHomeworks: HomeworkViewModel[]; - mentors: AccountDataDto[]; - acceptedStudents: AccountDataDto[]; - newStudents: AccountDataDto[]; - studentSolutions: StatisticsCourseMatesModel[]; - showQrCode: boolean; -} - -interface ICourseFilesState { - processingFilesState: { - [homeworkId: number]: { - isLoading: boolean; - intervalId?: NodeJS.Timeout; - }; - }; - courseFiles: FileInfoDTO[]; -} - interface IPageState { tabValue: TabValue } @@ -78,53 +56,30 @@ const Course: React.FC = () => { const navigate = useNavigate() const {enqueueSnackbar} = useSnackbar() - const [courseState, setCourseState] = useState({ - isFound: false, - course: {}, - courseHomeworks: [], - mentors: [], - acceptedStudents: [], - newStudents: [], - studentSolutions: [], - showQrCode: false - }) - const [studentSolutions, setStudentSolutions] = useState(undefined) - const [courseFilesState, setCourseFilesState] = useState({ - processingFilesState: {}, - courseFiles: [] - }) + const dispatch = useAppDispatch(); + const course = useAppSelector(state => state.course.course); + const isFound = useAppSelector(state => state.course.isFound); + const mentors = useAppSelector(state => state.course.mentors); + const acceptedStudents = useAppSelector(state => state.course.acceptedStudents); + const newStudents = useAppSelector(state => state.course.newStudents); + const courseHomeworks = useAppSelector(state => state.homeworks.homeworks); + const studentSolutions = useAppSelector(state => state.solutions.studentSolutions); + const courseFiles = useAppSelector(state => state.courseFiles.courseFiles); + const processingFilesState = useAppSelector(state => state.courseFiles.processingFilesState); + const [showQrCode, setShowQrCode] = useState(false); const intervalsRef = React.useRef>({}); - const updateCourseFiles = (files: FileInfoDTO[], unitType: CourseUnitType, unitId: number) => { - setCourseFilesState(prev => ({ - ...prev, - courseFiles: [ - ...prev.courseFiles.filter( - f => !(f.courseUnitType === unitType && f.courseUnitId === unitId)), - ...files - ] - })); + const handleUpdateCourseFiles = (files: FileInfoDTO[], unitType: CourseUnitType, unitId: number) => { + dispatch(updateCourseFiles({ files, unitType, unitId })); }; const setCommonLoading = (homeworkId: number) => { - setCourseFilesState(prev => ({ - ...prev, - processingFilesState: { - ...prev.processingFilesState, - [homeworkId]: {isLoading: true} - } - })); + dispatch(setProcessingLoading({ homeworkId, isLoading: true })); } const unsetCommonLoading = (homeworkId: number) => { - setCourseFilesState(prev => ({ - ...prev, - processingFilesState: { - ...prev.processingFilesState, - [homeworkId]: {isLoading: false} - } - })); + dispatch(setProcessingLoading({ homeworkId, isLoading: false })); } const stopProcessing = (homeworkId: number) => { @@ -169,14 +124,14 @@ const Course: React.FC = () => { // Первый вариант для явного отображения всех файлов if (waitingNewFilesCount === 0 && files.filter(f => f.status === FileStatus.ReadyToUse).length === previouslyExistingFilesCount - deletingFilesIds.length) { - updateCourseFiles(files, scopeDto.courseUnitType as CourseUnitType, scopeDto.courseUnitId!) + handleUpdateCourseFiles(files, scopeDto.courseUnitType as CourseUnitType, scopeDto.courseUnitId!) unsetCommonLoading(homeworkId) } // Второй вариант для явного отображения всех файлов if (waitingNewFilesCount > 0 && files.filter(f => !deletingFilesIds.some(dfi => dfi === f.id)).length === previouslyExistingFilesCount - deletingFilesIds.length + waitingNewFilesCount) { - updateCourseFiles(files, scopeDto.courseUnitType as CourseUnitType, scopeDto.courseUnitId!) + handleUpdateCourseFiles(files, scopeDto.courseUnitType as CourseUnitType, scopeDto.courseUnitId!) unsetCommonLoading(homeworkId) } @@ -223,19 +178,16 @@ const Course: React.FC = () => { tabValue: "homeworks" }) - const { - isFound, - course, - mentors, - newStudents, - acceptedStudents, - courseHomeworks, - } = courseState - - const userId = ApiSingleton.authService.getUserId() + useEffect(() => { + const userId = ApiSingleton.authService.getUserId(); + const isLecturer = ApiSingleton.authService.isLecturer(); + const isExpert = ApiSingleton.authService.isExpert(); + dispatch(setAuth({ userId, isLecturer, isExpert })) + }, []) - const isLecturer = ApiSingleton.authService.isLecturer() - const isExpert = ApiSingleton.authService.isExpert() + const userId = useAppSelector(state => state.auth.userId); + const isLecturer = useAppSelector(state => state.auth.isLecturer); + const isExpert = useAppSelector(state => state.auth.isExpert); const isMentor = isLecturer || isExpert const isCourseMentor = mentors.some(t => t.userId === userId) const isSignedInCourse = newStudents!.some(cm => cm.userId === userId) @@ -272,16 +224,11 @@ const Course: React.FC = () => { return } - setCourseState(prevState => ({ - ...prevState, - isFound: true, - course: course, - courseHomeworks: course.homeworks!, - createHomework: false, - mentors: course.mentors!, - acceptedStudents: course.acceptedStudents!, - newStudents: course.newStudents!, - })) + dispatch(setCourse(course)); + dispatch(setMentors(course.mentors!)); + dispatch(setAcceptedStudents(course.acceptedStudents!)); + dispatch(setNewStudents(course.newStudents!)); + dispatch(setHomeworks(course.homeworks!)); } const getCourseFilesInfo = async () => { @@ -289,20 +236,17 @@ const Course: React.FC = () => { try { courseFilesInfo = isCourseMentor ? await ApiSingleton.filesApi.filesGetFilesInfo(+courseId!) - : await ApiSingleton.filesApi.filesGetUploadedFilesInfo(+courseId!) + : await ApiSingleton.filesApi.filesGetUploadedFilesInfo(+courseId!); } catch (e) { const responseErrors = await ErrorsHandler.getErrorMessages(e as Response) enqueueSnackbar(responseErrors[0], {variant: "warning", autoHideDuration: 1990}); } - setCourseFilesState(prevState => ({ - ...prevState, - courseFiles: courseFilesInfo - })) + dispatch(setCourseFiles(courseFilesInfo)); } useEffect(() => { setCurrentState() - }, []) + }, [courseId]) useEffect(() => { getCourseFilesInfo() @@ -310,7 +254,7 @@ const Course: React.FC = () => { useEffect(() => { ApiSingleton.statisticsApi.statisticsGetCourseStatistics(+courseId!) - .then(res => setStudentSolutions(res)) + .then(res => dispatch(setStudentSolutions(res))) }, [courseId]) useEffect(() => changeTab(tab || "homeworks"), [tab, courseId, isFound]) @@ -323,7 +267,7 @@ const Course: React.FC = () => { const {tabValue} = pageState const searchedHomeworkId = searchParams.get("homeworkId") - const unratedSolutionsCount = (studentSolutions || []) + const unratedSolutionsCount = studentSolutions .flatMap(x => x.homeworks) .flatMap(x => x!.tasks) .filter(t => t!.solution!.slice(-1)[0]?.state === 0) //last solution @@ -367,10 +311,7 @@ const Course: React.FC = () => { Управление } - setCourseState(prevState => ({ - ...prevState, - showQrCode: true - }))}> + setShowQrCode(true)}> @@ -391,8 +332,8 @@ const Course: React.FC = () => { return (
setCourseState(prevState => ({...prevState, showQrCode: false}))} + open={showQrCode} + onClose={() => setShowQrCode(false)} > Поделитесь ссылкой на курс с помощью QR-кода @@ -407,7 +348,7 @@ const Course: React.FC = () => { - {course.isCompleted && + {course?.isCompleted && Курс завершен! {isAcceptedStudent @@ -422,7 +363,7 @@ const Course: React.FC = () => { - {NameBuilder.getCourseFullName(course.name!, course.groupName)} + {NameBuilder.getCourseFullName(course?.name || "", course?.groupName || "")} @@ -430,11 +371,10 @@ const Course: React.FC = () => { - + {lecturerStatsState && setLecturerStatsState(false)} /> } @@ -488,68 +428,17 @@ const Course: React.FC = () => { }/>} {tabValue === "homeworks" && { - const homeworkIndex = courseState.courseHomeworks.findIndex(x => x.id === homework.id) - const homeworks = courseState.courseHomeworks - - if (isDeleted) homeworks.splice(homeworkIndex, 1) - else if (homeworkIndex === -1) homeworks.push(homework) - else homeworks[homeworkIndex] = homework - - setCourseState(prevState => ({ - ...prevState, - courseHomeworks: homeworks - })) - }} - onTaskUpdate={update => { - const task = update.task - const homeworks = courseState.courseHomeworks - const homework = homeworks.find(x => x.id === task.homeworkId)! - const tasks = [...homework.tasks!] - const taskIndex = tasks.findIndex(x => x!.id === task.id) - - if (update.isDeleted) tasks.splice(taskIndex, 1) - else if (taskIndex !== -1) tasks![taskIndex] = task - else tasks.push(task) - - homework.tasks = tasks - - setCourseState(prevState => ({ - ...prevState, - courseHomeworks: homeworks - })) - }} /> } {tabValue === "stats" && - + } {tabValue === "applications" && showApplicationsTab && - setCurrentState()} - course={courseState.course} - students={courseState.newStudents} - courseId={courseId!} - /> + }
@@ -564,4 +453,4 @@ const Course: React.FC = () => { } -export default Course +export default Course \ No newline at end of file diff --git a/hwproj.front/src/components/Courses/CourseExperimental.tsx b/hwproj.front/src/components/Courses/CourseExperimental.tsx index b5d1819fe..fe863c8b7 100644 --- a/hwproj.front/src/components/Courses/CourseExperimental.tsx +++ b/hwproj.front/src/components/Courses/CourseExperimental.tsx @@ -4,6 +4,8 @@ import { HomeworkTaskViewModel, HomeworkViewModel, Solution, StatisticsCourseMatesModel, } from "@/api"; +import {useAppDispatch, useAppSelector} from "@/store/hooks"; +import {updateOrInsertHomework, deleteHomework, updateTask, deleteTask} from "@/store/slices/homeworkSlice"; import { AlertTitle, Button, @@ -23,7 +25,7 @@ import TimelineContent from '@mui/lab/TimelineContent'; import TimelineDot from '@mui/lab/TimelineDot'; import TimelineOppositeContent from '@mui/lab/TimelineOppositeContent'; import {Alert, Card, CardActions, Chip, Paper, Stack, Tooltip} from "@mui/material"; -import {Link} from "react-router-dom"; +import {Link, useSearchParams} from "react-router-dom"; import StudentStatsUtils from "../../services/StudentStatsUtils"; import {BonusTag, DefaultTags, getTip, isBonusWork, isTestWork, TestTag} from "../Common/HomeworkTags"; import FileInfoConverter from "components/Utils/FileInfoConverter"; @@ -37,23 +39,6 @@ import SwitchAccessShortcutIcon from '@mui/icons-material/SwitchAccessShortcut'; import Lodash from "lodash"; interface ICourseExperimentalProps { - homeworks: HomeworkViewModel[] - courseFilesInfo: FileInfoDTO[] - studentSolutions: StatisticsCourseMatesModel[] - courseId: number - isMentor: boolean - isStudentAccepted: boolean - userId: string - selectedHomeworkId: number | undefined - onHomeworkUpdate: (update: { homework: HomeworkViewModel, fileInfos: FileInfoDTO[] | undefined } & { - isDeleted?: boolean - }) => void - onTaskUpdate: (update: { task: HomeworkTaskViewModel, isDeleted?: boolean }) => void, - processingFiles: { - [homeworkId: number]: { - isLoading: boolean; - }; - }; onStartProcessing: (homeworkId: number, previouslyExistingFilesCount: number, waitingNewFilesCount: number, deletingFilesIds: number[]) => void; } @@ -66,10 +51,23 @@ interface ICourseExperimentalState { } export const CourseExperimental: FC = (props) => { + const dispatch = useAppDispatch() + const allHomeworks = useAppSelector(state => state.homeworks.homeworks) + const studentSolutions = useAppSelector(state => state.solutions.studentSolutions) + const courseFilesInfo = useAppSelector(state => state.courseFiles.courseFiles) + const mentors = useAppSelector(state => state.course.mentors) + const course = useAppSelector(state => state.course.course) + const acceptedStudents = useAppSelector(state => state.course.acceptedStudents) + const userId = useAppSelector(state => state.auth.userId) + + const courseId = course?.id ?? 0 + const isAcceptedStudent = acceptedStudents.some(s => s.userId === userId) + const [hideDeferred, setHideDeferred] = useState(false) const [showOnlyGroupedTest, setShowOnlyGroupedTest] = useState(undefined) const filterAdded = hideDeferred || showOnlyGroupedTest !== undefined + const isMentor = mentors.some(m => m.userId === userId) // Определяем разрешение экрана пользователя const theme = useTheme(); const isMobile = useMediaQuery(theme.breakpoints.down('md')); @@ -77,13 +75,14 @@ export const CourseExperimental: FC = (props) => { // Состояние для кнопки "Наверх" const [showScrollButton, setShowScrollButton] = useState(false); - const homeworks = props.homeworks.slice().reverse().filter(x => { + const homeworks = allHomeworks.slice().reverse().filter(x => { if (hideDeferred) return !x.isDeferred if (showOnlyGroupedTest !== undefined) return x.tags!.includes(TestTag) && x.tags!.includes(showOnlyGroupedTest) return true }) - const {isMentor, studentSolutions, isStudentAccepted, userId, selectedHomeworkId, courseFilesInfo} = props + const [ searchParams ]= useSearchParams(); + const selectedHomeworkId = searchParams.get("homeworkId") ? +searchParams.get("homeworkId")! : undefined const [state, setState] = useState({ initialEditMode: false, @@ -162,7 +161,7 @@ export const CourseExperimental: FC = (props) => { const taskSolutionsMap = new Map() - if (!isMentor && isStudentAccepted) { + if (!isMentor && isAcceptedStudent) { studentSolutions .filter(t => t.id === userId) .flatMap(t => t.homeworks!) @@ -340,10 +339,28 @@ export const CourseExperimental: FC = (props) => { const [newTaskCounter, setNewTaskCounter] = useState(-1) + const handleHomeworkUpdate = (update: { homework: HomeworkViewModel, fileInfos?: FileInfoDTO[], isDeleted?: boolean }) => { + const { homework, isDeleted } = update; + if (isDeleted) { + dispatch(deleteHomework(homework.id!)); + } else { + dispatch(updateOrInsertHomework(homework)); + } + }; + + const handleTaskUpdate = (update: { task: HomeworkTaskViewModel, isDeleted?: boolean }) => { + const { task, isDeleted } = update; + if (isDeleted) { + dispatch(deleteTask({homeworkId: task.homeworkId!, taskId: task.id!})); + } else { + dispatch(updateTask(task)); + } + } + const addNewHomework = () => { - props.onHomeworkUpdate({ + handleHomeworkUpdate({ homework: { - courseId: props.courseId, + courseId: courseId, title: "Новое задание", publicationDateNotSet: false, publicationDate: undefined, @@ -398,7 +415,7 @@ export const CourseExperimental: FC = (props) => { id } - props.onTaskUpdate({task}) + handleTaskUpdate({task}) setState((prevState) => ({ ...prevState, selectedItem: { @@ -418,14 +435,12 @@ export const CourseExperimental: FC = (props) => { {isMentor && getDatesAlert(homework, true)} homeworks} homeworkAndFilesInfo={{homework, filesInfo}} - isMentor={isMentor} initialEditMode={initialEditMode || homeworkEditMode} onMount={onSelectedItemMount} onAddTask={addNewTask} onUpdate={update => { - props.onHomeworkUpdate(update) + handleHomeworkUpdate(update) setState((prevState) => ({ ...prevState, selectedItem: { @@ -434,7 +449,6 @@ export const CourseExperimental: FC = (props) => { } })) }} - isProcessing={props.processingFiles[homework.id!]?.isLoading || false} onStartProcessing={(homeworkId: number, previouslyExistingFilesCount: number, waitingNewFilesCount: number, deletingFilesIds: number[]) => props.onStartProcessing(homeworkId, previouslyExistingFilesCount, waitingNewFilesCount, deletingFilesIds)} /> @@ -450,11 +464,10 @@ export const CourseExperimental: FC = (props) => { key={task.id} task={task} homework={homework!} - isMentor={isMentor} initialEditMode={initialEditMode || taskEditMode} onMount={onSelectedItemMount} onUpdate={update => { - props.onTaskUpdate(update) + handleTaskUpdate(update) if (update.isDeleted) setState((prevState) => ({ ...prevState, @@ -464,8 +477,8 @@ export const CourseExperimental: FC = (props) => { } })) }} - toEditHomework={() => toEditHomework(homework!)} getAllHomeworks={() => homeworks}/> - {!props.isMentor && props.isStudentAccepted && < CardActions> + toEditHomework={() => toEditHomework(homework!)}/> + {!isMentor && isAcceptedStudent && < CardActions> @@ -516,7 +529,7 @@ export const CourseExperimental: FC = (props) => { borderRadius: 10 } }}> - {props.isMentor && filterAdded && + {isMentor && filterAdded &&