diff --git a/package-lock.json b/package-lock.json index 5e1b58ad..b4883096 100755 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "@emotion/styled": "^11.10.5", "@mui/icons-material": "^5.10.14", "@mui/material": "^5.10.14", + "@mui/x-date-pickers": "^6.16.1", "@testing-library/jest-dom": "^5.14.1", "@testing-library/react": "^13.0.0", "@testing-library/user-event": "^13.2.1", @@ -23,9 +24,11 @@ "@types/react-dnd": "^3.0.2", "@types/react-dom": "^18.0.0", "axios": "^1.5.1", + "dayjs": "^1.11.10", "emoji-mart": "^5.5.2", "file-saver": "^2.0.5", "html-to-image": "^1.11.11", + "immer": "^10.0.3", "jszip": "^3.10.1", "notistack": "^3.0.1", "react": "^18.2.0", @@ -36,6 +39,7 @@ "react-easy-crop": "^5.0.0", "react-image-crop": "^10.1.5", "react-image-file-resizer": "^0.4.8", + "react-rnd": "^10.4.1", "react-router-dom": "^6.6.2", "react-scripts": "5.0.1", "typescript": "^4.4.2", @@ -1775,11 +1779,11 @@ } }, "node_modules/@babel/runtime": { - "version": "7.20.6", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.6.tgz", - "integrity": "sha512-Q+8MqP7TiHMWzSfwiJwXCjyf4GYA4Dgw3emg/7xmwsdLJOZUp+nMqcOwOzzYheuM1rhDu8FSj2l0aoMygEuXuA==", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.2.tgz", + "integrity": "sha512-mM8eg4yl5D6i3lu2QKPuPH4FArvJ8KhTofbE7jwMUv9KX5mBvwPAqnV3MlyBNqdp9RyRKP6Yck8TrfYrPvX3bg==", "dependencies": { - "regenerator-runtime": "^0.13.11" + "regenerator-runtime": "^0.14.0" }, "engines": { "node": ">=6.9.0" @@ -1797,6 +1801,11 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/runtime/node_modules/regenerator-runtime": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz", + "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==" + }, "node_modules/@babel/template": { "version": "7.20.7", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz", @@ -2416,6 +2425,40 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@floating-ui/core": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.5.0.tgz", + "integrity": "sha512-kK1h4m36DQ0UHGj5Ah4db7R0rHemTqqO0QLvUqi1/mUUp3LuAWbWxdxSIf/XsnH9VS6rRVPLJCncjRzUvyCLXg==", + "dependencies": { + "@floating-ui/utils": "^0.1.3" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.5.3.tgz", + "integrity": "sha512-ClAbQnEqJAKCJOEbbLo5IUlZHkNszqhuxS4fHAVxRPXPya6Ysf2G8KypnYcOTpx6I8xcgF9bbHb6g/2KpbV8qA==", + "dependencies": { + "@floating-ui/core": "^1.4.2", + "@floating-ui/utils": "^0.1.3" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.2.tgz", + "integrity": "sha512-5qhlDvjaLmAst/rKb3VdlCinwTF4EYMiVxuuc/HVUjs46W0zgtbMmAZ1UTsDrRTxRmUEzl92mOtWbeeXL26lSQ==", + "dependencies": { + "@floating-ui/dom": "^1.5.1" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.1.6.tgz", + "integrity": "sha512-OfX7E2oUDYxtBvsuS4e/jSn4Q9Qb6DzgeYtsAdkPZ47znpoNsMgZw0+tVijiv3uGNR6dgNlty6r9rzIzHjtd/A==" + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.7", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.7.tgz", @@ -3393,11 +3436,11 @@ } }, "node_modules/@mui/types": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.2.tgz", - "integrity": "sha512-siex8cZDtWeC916cXOoUOnEQQejuMYmHtc4hM6VkKVYaBICz3VIiqyiAomRboTQHt2jchxQ5Q5ATlbcDekTxDA==", + "version": "7.2.6", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.6.tgz", + "integrity": "sha512-7sjLQrUmBwufm/M7jw/quNiPK/oor2+pGUQP2CULRcFCArYTq78oJ3D5esTaL0UMkXKJvDqXn6Ike69yAOBQng==", "peerDependencies": { - "@types/react": "*" + "@types/react": "^17.0.0 || ^18.0.0" }, "peerDependenciesMeta": { "@types/react": { @@ -3406,13 +3449,12 @@ } }, "node_modules/@mui/utils": { - "version": "5.10.16", - "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.10.16.tgz", - "integrity": "sha512-3MB/SGsgiiu9Z55CFmAfiONUoR7AAue/H4F6w3mc2LnhFQCsoVvXhioDPcsiRpUMIQr34jDPzGXdCuqWooPCXQ==", + "version": "5.14.13", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.14.13.tgz", + "integrity": "sha512-2AFpyXWw7uDCIqRu7eU2i/EplZtks5LAMzQvIhC79sPV9IhOZU2qwOWVnPtdctRXiQJOAaXulg+A37pfhEueQw==", "dependencies": { - "@babel/runtime": "^7.20.1", - "@types/prop-types": "^15.7.5", - "@types/react-is": "^16.7.1 || ^17.0.0", + "@babel/runtime": "^7.23.1", + "@types/prop-types": "^15.7.7", "prop-types": "^15.8.1", "react-is": "^18.2.0" }, @@ -3424,7 +3466,117 @@ "url": "https://opencollective.com/mui" }, "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0", "react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/x-date-pickers": { + "version": "6.16.2", + "resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-6.16.2.tgz", + "integrity": "sha512-JFrDUeBkiKtfJ0WqwyPBICEP1U+Ujfsily3ZQ/Hv4zAOleG/5769EgS7TOO4cVgnuhtvQ/pqx2gmuCn8/gcC5w==", + "dependencies": { + "@babel/runtime": "^7.23.1", + "@mui/base": "^5.0.0-beta.17", + "@mui/utils": "^5.14.11", + "@types/react-transition-group": "^4.4.7", + "clsx": "^2.0.0", + "prop-types": "^15.8.1", + "react-transition-group": "^4.4.5" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui" + }, + "peerDependencies": { + "@emotion/react": "^11.9.0", + "@emotion/styled": "^11.8.1", + "@mui/material": "^5.8.6", + "@mui/system": "^5.8.0", + "date-fns": "^2.25.0", + "date-fns-jalali": "^2.13.0-0", + "dayjs": "^1.10.7", + "luxon": "^3.0.2", + "moment": "^2.29.4", + "moment-hijri": "^2.1.2", + "moment-jalaali": "^0.7.4 || ^0.8.0 || ^0.9.0 || ^0.10.0", + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "date-fns": { + "optional": true + }, + "date-fns-jalali": { + "optional": true + }, + "dayjs": { + "optional": true + }, + "luxon": { + "optional": true + }, + "moment": { + "optional": true + }, + "moment-hijri": { + "optional": true + }, + "moment-jalaali": { + "optional": true + } + } + }, + "node_modules/@mui/x-date-pickers/node_modules/@mui/base": { + "version": "5.0.0-beta.19", + "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.19.tgz", + "integrity": "sha512-maNBgAscddyPNzFZQUJDF/puxM27Li+NqSBsr/lAP8TLns2VvWS2SoL3OKFOIoRnAMKGY/Ic6Aot6gCYeQnssA==", + "dependencies": { + "@babel/runtime": "^7.23.1", + "@floating-ui/react-dom": "^2.0.2", + "@mui/types": "^7.2.6", + "@mui/utils": "^5.14.13", + "@popperjs/core": "^2.11.8", + "clsx": "^2.0.0", + "prop-types": "^15.8.1" + }, + "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/x-date-pickers/node_modules/clsx": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz", + "integrity": "sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==", + "engines": { + "node": ">=6" } }, "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { @@ -3545,9 +3697,9 @@ } }, "node_modules/@popperjs/core": { - "version": "2.11.6", - "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.6.tgz", - "integrity": "sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==", + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", "funding": { "type": "opencollective", "url": "https://opencollective.com/popperjs" @@ -4342,9 +4494,9 @@ "integrity": "sha512-ri0UmynRRvZiiUJdiz38MmIblKK+oH30MztdBVR95dv/Ubw6neWSb8u1XpRb72L4qsZOhz+L+z9JD40SJmfWow==" }, "node_modules/@types/prop-types": { - "version": "15.7.5", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", - "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==" + "version": "15.7.8", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.8.tgz", + "integrity": "sha512-kMpQpfZKSCBqltAJwskgePRaYRFukDkm1oItcAbC3gNELR20XIBcN9VRgg4+m8DKsTfkWeA4m4Imp4DDuWy7FQ==" }, "node_modules/@types/q": { "version": "1.5.5", @@ -4397,14 +4549,6 @@ "@types/react": "*" } }, - "node_modules/@types/react-is": { - "version": "17.0.3", - "resolved": "https://registry.npmjs.org/@types/react-is/-/react-is-17.0.3.tgz", - "integrity": "sha512-aBTIWg1emtu95bLTLx0cpkxwGW3ueZv71nE2YFBpL8k/z5czEW8yYpOo8Dp+UUAFAtKwNaOsh/ioSeQnWlZcfw==", - "dependencies": { - "@types/react": "*" - } - }, "node_modules/@types/react-redux": { "version": "7.1.27", "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.27.tgz", @@ -4417,9 +4561,9 @@ } }, "node_modules/@types/react-transition-group": { - "version": "4.4.5", - "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.5.tgz", - "integrity": "sha512-juKD/eiSM3/xZYzjuzH6ZwpP+/lejltmiS3QEzV/vmb/Q8+HfDmxu+Baga8UEMGBqV88Nbg4l2hY/K2DkyaLLA==", + "version": "4.4.7", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.7.tgz", + "integrity": "sha512-ICCyBl5mvyqYp8Qeq9B5G/fyBSRC0zx3XM3sCC6KkcMsNeAHqXBKkmat4GqdJET5jtYUpZXrxI5flve5qhi2Eg==", "dependencies": { "@types/react": "*" } @@ -6853,6 +6997,11 @@ "node": ">=10" } }, + "node_modules/dayjs": { + "version": "1.11.10", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz", + "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==" + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -8347,6 +8496,11 @@ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==" }, + "node_modules/fast-memoize": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/fast-memoize/-/fast-memoize-2.5.2.tgz", + "integrity": "sha512-Ue0LwpDYErFbmNnZSF0UH6eImUwDmogUO1jyE+JbN2gsQz/jICm1Ve7t9QT0rNSsfJt+Hs4/S3GnsDVjL4HVrw==" + }, "node_modules/fastq": { "version": "1.14.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.14.0.tgz", @@ -9430,9 +9584,9 @@ "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==" }, "node_modules/immer": { - "version": "9.0.16", - "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.16.tgz", - "integrity": "sha512-qenGE7CstVm1NrHQbMh8YaSzTZTFNP3zPqr3YU0S0UY441j4bJTg4A2Hh5KAhwgaiU6ZZ1Ar6y/2f4TblnMReQ==", + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/immer/-/immer-10.0.3.tgz", + "integrity": "sha512-pwupu3eWfouuaowscykeckFmVTpqbzW+rXFCX8rQLkZzM9ftBmU/++Ra+o+L27mz03zJTlyV4UUr+fdKNffo4A==", "funding": { "type": "opencollective", "url": "https://opencollective.com/immer" @@ -14761,6 +14915,18 @@ "node": ">=0.10.0" } }, + "node_modules/re-resizable": { + "version": "6.9.6", + "resolved": "https://registry.npmjs.org/re-resizable/-/re-resizable-6.9.6.tgz", + "integrity": "sha512-0xYKS5+Z0zk+vICQlcZW+g54CcJTTmHluA7JUUgvERDxnKAnytylcyPsA+BSFi759s5hPlHmBRegFrwXs2FuBQ==", + "dependencies": { + "fast-memoize": "^2.5.1" + }, + "peerDependencies": { + "react": "^16.13.1 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.13.1 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/react": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", @@ -14893,6 +15059,15 @@ "node": ">=8" } }, + "node_modules/react-dev-utils/node_modules/immer": { + "version": "9.0.21", + "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz", + "integrity": "sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, "node_modules/react-dev-utils/node_modules/loader-utils": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-3.2.1.tgz", @@ -14961,6 +15136,19 @@ "react": "^18.2.0" } }, + "node_modules/react-draggable": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-draggable/-/react-draggable-4.4.5.tgz", + "integrity": "sha512-OMHzJdyJbYTZo4uQE393fHcqqPYsEtkjfMgvCHr6rejT+Ezn4OZbNyGH50vv+SunC1RMvwOTSWkEODQLzw1M9g==", + "dependencies": { + "clsx": "^1.1.1", + "prop-types": "^15.8.1" + }, + "peerDependencies": { + "react": ">= 16.3.0", + "react-dom": ">= 16.3.0" + } + }, "node_modules/react-easy-crop": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/react-easy-crop/-/react-easy-crop-5.0.2.tgz", @@ -15039,6 +15227,25 @@ "node": ">=0.10.0" } }, + "node_modules/react-rnd": { + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/react-rnd/-/react-rnd-10.4.1.tgz", + "integrity": "sha512-0m887AjQZr6p2ADLNnipquqsDq4XJu/uqVqI3zuoGD19tRm6uB83HmZWydtkilNp5EWsOHbLGF4IjWMdd5du8Q==", + "dependencies": { + "re-resizable": "6.9.6", + "react-draggable": "4.4.5", + "tslib": "2.3.1" + }, + "peerDependencies": { + "react": ">=16.3.0", + "react-dom": ">=16.3.0" + } + }, + "node_modules/react-rnd/node_modules/tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" + }, "node_modules/react-router": { "version": "6.8.0", "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.8.0.tgz", @@ -19302,11 +19509,18 @@ } }, "@babel/runtime": { - "version": "7.20.6", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.6.tgz", - "integrity": "sha512-Q+8MqP7TiHMWzSfwiJwXCjyf4GYA4Dgw3emg/7xmwsdLJOZUp+nMqcOwOzzYheuM1rhDu8FSj2l0aoMygEuXuA==", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.2.tgz", + "integrity": "sha512-mM8eg4yl5D6i3lu2QKPuPH4FArvJ8KhTofbE7jwMUv9KX5mBvwPAqnV3MlyBNqdp9RyRKP6Yck8TrfYrPvX3bg==", "requires": { - "regenerator-runtime": "^0.13.11" + "regenerator-runtime": "^0.14.0" + }, + "dependencies": { + "regenerator-runtime": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz", + "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==" + } } }, "@babel/runtime-corejs3": { @@ -19714,6 +19928,36 @@ } } }, + "@floating-ui/core": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.5.0.tgz", + "integrity": "sha512-kK1h4m36DQ0UHGj5Ah4db7R0rHemTqqO0QLvUqi1/mUUp3LuAWbWxdxSIf/XsnH9VS6rRVPLJCncjRzUvyCLXg==", + "requires": { + "@floating-ui/utils": "^0.1.3" + } + }, + "@floating-ui/dom": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.5.3.tgz", + "integrity": "sha512-ClAbQnEqJAKCJOEbbLo5IUlZHkNszqhuxS4fHAVxRPXPya6Ysf2G8KypnYcOTpx6I8xcgF9bbHb6g/2KpbV8qA==", + "requires": { + "@floating-ui/core": "^1.4.2", + "@floating-ui/utils": "^0.1.3" + } + }, + "@floating-ui/react-dom": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.2.tgz", + "integrity": "sha512-5qhlDvjaLmAst/rKb3VdlCinwTF4EYMiVxuuc/HVUjs46W0zgtbMmAZ1UTsDrRTxRmUEzl92mOtWbeeXL26lSQ==", + "requires": { + "@floating-ui/dom": "^1.5.1" + } + }, + "@floating-ui/utils": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.1.6.tgz", + "integrity": "sha512-OfX7E2oUDYxtBvsuS4e/jSn4Q9Qb6DzgeYtsAdkPZ47znpoNsMgZw0+tVijiv3uGNR6dgNlty6r9rzIzHjtd/A==" + }, "@humanwhocodes/config-array": { "version": "0.11.7", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.7.tgz", @@ -20369,23 +20613,57 @@ } }, "@mui/types": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.2.tgz", - "integrity": "sha512-siex8cZDtWeC916cXOoUOnEQQejuMYmHtc4hM6VkKVYaBICz3VIiqyiAomRboTQHt2jchxQ5Q5ATlbcDekTxDA==", + "version": "7.2.6", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.6.tgz", + "integrity": "sha512-7sjLQrUmBwufm/M7jw/quNiPK/oor2+pGUQP2CULRcFCArYTq78oJ3D5esTaL0UMkXKJvDqXn6Ike69yAOBQng==", "requires": {} }, "@mui/utils": { - "version": "5.10.16", - "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.10.16.tgz", - "integrity": "sha512-3MB/SGsgiiu9Z55CFmAfiONUoR7AAue/H4F6w3mc2LnhFQCsoVvXhioDPcsiRpUMIQr34jDPzGXdCuqWooPCXQ==", + "version": "5.14.13", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.14.13.tgz", + "integrity": "sha512-2AFpyXWw7uDCIqRu7eU2i/EplZtks5LAMzQvIhC79sPV9IhOZU2qwOWVnPtdctRXiQJOAaXulg+A37pfhEueQw==", "requires": { - "@babel/runtime": "^7.20.1", - "@types/prop-types": "^15.7.5", - "@types/react-is": "^16.7.1 || ^17.0.0", + "@babel/runtime": "^7.23.1", + "@types/prop-types": "^15.7.7", "prop-types": "^15.8.1", "react-is": "^18.2.0" } }, + "@mui/x-date-pickers": { + "version": "6.16.2", + "resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-6.16.2.tgz", + "integrity": "sha512-JFrDUeBkiKtfJ0WqwyPBICEP1U+Ujfsily3ZQ/Hv4zAOleG/5769EgS7TOO4cVgnuhtvQ/pqx2gmuCn8/gcC5w==", + "requires": { + "@babel/runtime": "^7.23.1", + "@mui/base": "^5.0.0-beta.17", + "@mui/utils": "^5.14.11", + "@types/react-transition-group": "^4.4.7", + "clsx": "^2.0.0", + "prop-types": "^15.8.1", + "react-transition-group": "^4.4.5" + }, + "dependencies": { + "@mui/base": { + "version": "5.0.0-beta.19", + "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.19.tgz", + "integrity": "sha512-maNBgAscddyPNzFZQUJDF/puxM27Li+NqSBsr/lAP8TLns2VvWS2SoL3OKFOIoRnAMKGY/Ic6Aot6gCYeQnssA==", + "requires": { + "@babel/runtime": "^7.23.1", + "@floating-ui/react-dom": "^2.0.2", + "@mui/types": "^7.2.6", + "@mui/utils": "^5.14.13", + "@popperjs/core": "^2.11.8", + "clsx": "^2.0.0", + "prop-types": "^15.8.1" + } + }, + "clsx": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz", + "integrity": "sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==" + } + } + }, "@nicolo-ribaudo/eslint-scope-5-internals": { "version": "5.1.1-v1", "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", @@ -20457,9 +20735,9 @@ } }, "@popperjs/core": { - "version": "2.11.6", - "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.6.tgz", - "integrity": "sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==" + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==" }, "@react-dnd/asap": { "version": "5.0.2", @@ -21060,9 +21338,9 @@ "integrity": "sha512-ri0UmynRRvZiiUJdiz38MmIblKK+oH30MztdBVR95dv/Ubw6neWSb8u1XpRb72L4qsZOhz+L+z9JD40SJmfWow==" }, "@types/prop-types": { - "version": "15.7.5", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", - "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==" + "version": "15.7.8", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.8.tgz", + "integrity": "sha512-kMpQpfZKSCBqltAJwskgePRaYRFukDkm1oItcAbC3gNELR20XIBcN9VRgg4+m8DKsTfkWeA4m4Imp4DDuWy7FQ==" }, "@types/q": { "version": "1.5.5", @@ -21114,14 +21392,6 @@ "@types/react": "*" } }, - "@types/react-is": { - "version": "17.0.3", - "resolved": "https://registry.npmjs.org/@types/react-is/-/react-is-17.0.3.tgz", - "integrity": "sha512-aBTIWg1emtu95bLTLx0cpkxwGW3ueZv71nE2YFBpL8k/z5czEW8yYpOo8Dp+UUAFAtKwNaOsh/ioSeQnWlZcfw==", - "requires": { - "@types/react": "*" - } - }, "@types/react-redux": { "version": "7.1.27", "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.27.tgz", @@ -21134,9 +21404,9 @@ } }, "@types/react-transition-group": { - "version": "4.4.5", - "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.5.tgz", - "integrity": "sha512-juKD/eiSM3/xZYzjuzH6ZwpP+/lejltmiS3QEzV/vmb/Q8+HfDmxu+Baga8UEMGBqV88Nbg4l2hY/K2DkyaLLA==", + "version": "4.4.7", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.7.tgz", + "integrity": "sha512-ICCyBl5mvyqYp8Qeq9B5G/fyBSRC0zx3XM3sCC6KkcMsNeAHqXBKkmat4GqdJET5jtYUpZXrxI5flve5qhi2Eg==", "requires": { "@types/react": "*" } @@ -22933,6 +23203,11 @@ "whatwg-url": "^8.0.0" } }, + "dayjs": { + "version": "1.11.10", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz", + "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==" + }, "debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -24050,6 +24325,11 @@ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==" }, + "fast-memoize": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/fast-memoize/-/fast-memoize-2.5.2.tgz", + "integrity": "sha512-Ue0LwpDYErFbmNnZSF0UH6eImUwDmogUO1jyE+JbN2gsQz/jICm1Ve7t9QT0rNSsfJt+Hs4/S3GnsDVjL4HVrw==" + }, "fastq": { "version": "1.14.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.14.0.tgz", @@ -24838,9 +25118,9 @@ "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==" }, "immer": { - "version": "9.0.16", - "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.16.tgz", - "integrity": "sha512-qenGE7CstVm1NrHQbMh8YaSzTZTFNP3zPqr3YU0S0UY441j4bJTg4A2Hh5KAhwgaiU6ZZ1Ar6y/2f4TblnMReQ==" + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/immer/-/immer-10.0.3.tgz", + "integrity": "sha512-pwupu3eWfouuaowscykeckFmVTpqbzW+rXFCX8rQLkZzM9ftBmU/++Ra+o+L27mz03zJTlyV4UUr+fdKNffo4A==" }, "import-fresh": { "version": "3.3.0", @@ -28507,6 +28787,14 @@ } } }, + "re-resizable": { + "version": "6.9.6", + "resolved": "https://registry.npmjs.org/re-resizable/-/re-resizable-6.9.6.tgz", + "integrity": "sha512-0xYKS5+Z0zk+vICQlcZW+g54CcJTTmHluA7JUUgvERDxnKAnytylcyPsA+BSFi759s5hPlHmBRegFrwXs2FuBQ==", + "requires": { + "fast-memoize": "^2.5.1" + } + }, "react": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", @@ -28608,6 +28896,11 @@ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" }, + "immer": { + "version": "9.0.21", + "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz", + "integrity": "sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==" + }, "loader-utils": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-3.2.1.tgz", @@ -28652,6 +28945,15 @@ "scheduler": "^0.23.0" } }, + "react-draggable": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-draggable/-/react-draggable-4.4.5.tgz", + "integrity": "sha512-OMHzJdyJbYTZo4uQE393fHcqqPYsEtkjfMgvCHr6rejT+Ezn4OZbNyGH50vv+SunC1RMvwOTSWkEODQLzw1M9g==", + "requires": { + "clsx": "^1.1.1", + "prop-types": "^15.8.1" + } + }, "react-easy-crop": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/react-easy-crop/-/react-easy-crop-5.0.2.tgz", @@ -28714,6 +29016,23 @@ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz", "integrity": "sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A==" }, + "react-rnd": { + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/react-rnd/-/react-rnd-10.4.1.tgz", + "integrity": "sha512-0m887AjQZr6p2ADLNnipquqsDq4XJu/uqVqI3zuoGD19tRm6uB83HmZWydtkilNp5EWsOHbLGF4IjWMdd5du8Q==", + "requires": { + "re-resizable": "6.9.6", + "react-draggable": "4.4.5", + "tslib": "2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" + } + } + }, "react-router": { "version": "6.8.0", "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.8.0.tgz", diff --git a/package.json b/package.json index dbd9cd52..226b9f59 100755 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "@emotion/styled": "^11.10.5", "@mui/icons-material": "^5.10.14", "@mui/material": "^5.10.14", + "@mui/x-date-pickers": "^6.16.1", "@testing-library/jest-dom": "^5.14.1", "@testing-library/react": "^13.0.0", "@testing-library/user-event": "^13.2.1", @@ -18,9 +19,11 @@ "@types/react-dnd": "^3.0.2", "@types/react-dom": "^18.0.0", "axios": "^1.5.1", + "dayjs": "^1.11.10", "emoji-mart": "^5.5.2", "file-saver": "^2.0.5", "html-to-image": "^1.11.11", + "immer": "^10.0.3", "jszip": "^3.10.1", "notistack": "^3.0.1", "react": "^18.2.0", @@ -31,6 +34,7 @@ "react-easy-crop": "^5.0.0", "react-image-crop": "^10.1.5", "react-image-file-resizer": "^0.4.8", + "react-rnd": "^10.4.1", "react-router-dom": "^6.6.2", "react-scripts": "5.0.1", "typescript": "^4.4.2", diff --git a/src/assets/icons/CalendarIcon.tsx b/src/assets/icons/CalendarIcon.tsx new file mode 100644 index 00000000..a3c91293 --- /dev/null +++ b/src/assets/icons/CalendarIcon.tsx @@ -0,0 +1,39 @@ +import { Box } from "@mui/material"; + +export default function CalendarIcon() { + return ( + + + + + + + + + + + + + + + ); +} diff --git a/src/assets/icons/CrossedEyeIcon.tsx b/src/assets/icons/CrossedEyeIcon.tsx index f2db60c7..c9021ca9 100644 --- a/src/assets/icons/CrossedEyeIcon.tsx +++ b/src/assets/icons/CrossedEyeIcon.tsx @@ -1,7 +1,16 @@ -export default function CrossedEyeIcon() { +import { FC, SVGProps } from "react"; + +export const CrossedEyeIcon: FC> = (props) => { return ( - - + + ); -} +}; diff --git a/src/assets/icons/questionsPage/OneIcon.tsx b/src/assets/icons/questionsPage/OneIcon.tsx index e7763088..64853a84 100755 --- a/src/assets/icons/questionsPage/OneIcon.tsx +++ b/src/assets/icons/questionsPage/OneIcon.tsx @@ -2,11 +2,17 @@ import { Box } from "@mui/material"; import { FC, SVGProps } from "react"; export const OneIcon: FC> = (props) => ( - - + + ); diff --git a/src/assets/icons/questionsPage/addAnswer.tsx b/src/assets/icons/questionsPage/addAnswer.tsx new file mode 100644 index 00000000..3bb950df --- /dev/null +++ b/src/assets/icons/questionsPage/addAnswer.tsx @@ -0,0 +1,34 @@ +import { Box, SxProps, Theme } from "@mui/material"; + +interface Props { + color: string; + sx?: SxProps; +} +export default function AddAnswer({ color, sx }: Props) { + return ( + + + + + + + ); +} diff --git a/src/assets/icons/questionsPage/addEmoji.tsx b/src/assets/icons/questionsPage/addEmoji.tsx index 40914338..cc8bc4d4 100644 --- a/src/assets/icons/questionsPage/addEmoji.tsx +++ b/src/assets/icons/questionsPage/addEmoji.tsx @@ -8,8 +8,8 @@ export default function AddEmoji() { return ( diff --git a/src/assets/icons/questionsPage/answerGroup.tsx b/src/assets/icons/questionsPage/answerGroup.tsx new file mode 100644 index 00000000..4b17fcb9 --- /dev/null +++ b/src/assets/icons/questionsPage/answerGroup.tsx @@ -0,0 +1,43 @@ +import { Box, SxProps, Theme } from "@mui/material"; + +interface Props { + color: string; + sx?: SxProps; +} +export default function AnswerGroup({ color, sx }: Props) { + return ( + + + + + + + + + ); +} diff --git a/src/constants/base.ts b/src/constants/base.ts new file mode 100644 index 00000000..feae09e3 --- /dev/null +++ b/src/constants/base.ts @@ -0,0 +1,29 @@ +import type { QuizQuestionInitial } from "../model/questionTypes/shared"; + +export const QUIZ_QUESTION_BASE: Omit = { + title: "", + type: "nonselected", + expanded: false, + required: false, + deleted: false, + deleteTimeoutId: 0, + content: { + hint: { + text: "", + video: "", + }, + rule: { + or: true, + show: true, + title: "", + reqs: [ + { + id: "", + vars: [], + }, + ], + }, + back: "", + autofill: false, + }, +}; diff --git a/src/constants/date.ts b/src/constants/date.ts new file mode 100644 index 00000000..d97bd30c --- /dev/null +++ b/src/constants/date.ts @@ -0,0 +1,16 @@ +import { QUIZ_QUESTION_BASE } from "./base"; + +import type { QuizQuestionDate } from "../model/questionTypes/date"; + +export const QUIZ_QUESTION_DATE: Omit = { + ...QUIZ_QUESTION_BASE, + type: "date", + content: { + ...QUIZ_QUESTION_BASE.content, + required: false, + innerNameCheck: false, + innerName: "", + dateRange: false, + time: false, + }, +}; diff --git a/src/constants/emoji.ts b/src/constants/emoji.ts new file mode 100644 index 00000000..48811534 --- /dev/null +++ b/src/constants/emoji.ts @@ -0,0 +1,23 @@ +import { QUIZ_QUESTION_BASE } from "./base"; + +import type { QuizQuestionEmoji } from "../model/questionTypes/emoji"; + +export const QUIZ_QUESTION_EMOJI: Omit = { + ...QUIZ_QUESTION_BASE, + type: "emoji", + content: { + ...QUIZ_QUESTION_BASE.content, + multi: false, + own: false, + innerNameCheck: false, + innerName: "", + required: false, + variants: [ + { + answer: "", + hints: "", + extendedText: "", + }, + ], + }, +}; diff --git a/src/constants/file.ts b/src/constants/file.ts new file mode 100644 index 00000000..425b28e7 --- /dev/null +++ b/src/constants/file.ts @@ -0,0 +1,15 @@ +import { QUIZ_QUESTION_BASE } from "./base"; + +import type { QuizQuestionFile } from "../model/questionTypes/file"; + +export const QUIZ_QUESTION_FILE: Omit = { + ...QUIZ_QUESTION_BASE, + type: "file", + content: { + ...QUIZ_QUESTION_BASE.content, + required: false, + innerNameCheck: false, + innerName: "", + type: "all", + }, +}; diff --git a/src/constants/images.ts b/src/constants/images.ts new file mode 100644 index 00000000..ed338d07 --- /dev/null +++ b/src/constants/images.ts @@ -0,0 +1,27 @@ +import { QUIZ_QUESTION_BASE } from "./base"; + +import type { QuizQuestionImages } from "../model/questionTypes/images"; + +export const QUIZ_QUESTION_IMAGES: Omit = { + ...QUIZ_QUESTION_BASE, + type: "images", + content: { + ...QUIZ_QUESTION_BASE.content, + own: false, + multi: false, + xy: "1:1", + innerNameCheck: false, + innerName: "", + large: false, + format: "carousel", + required: false, + variants: [ + { + answer: "", + hints: "", + extendedText: "", + }, + ], + largeCheck: false, + }, +}; diff --git a/src/constants/number.ts b/src/constants/number.ts new file mode 100644 index 00000000..57460fcb --- /dev/null +++ b/src/constants/number.ts @@ -0,0 +1,21 @@ +import { QUIZ_QUESTION_BASE } from "./base"; + +import type { QuizQuestionNumber } from "../model/questionTypes/number"; + +export const QUIZ_QUESTION_NUMBER: Omit = { + ...QUIZ_QUESTION_BASE, + type: "number", + content: { + ...QUIZ_QUESTION_BASE.content, + required: false, + innerNameCheck: false, + innerName: "", + range: "1—100", + defaultValue: 0, + step: 1, + steps: 5, + start: 50, + chooseRange: false, + form: "star", + }, +}; diff --git a/src/constants/page.ts b/src/constants/page.ts new file mode 100644 index 00000000..c33ad659 --- /dev/null +++ b/src/constants/page.ts @@ -0,0 +1,16 @@ +import { QUIZ_QUESTION_BASE } from "./base"; + +import type { QuizQuestionPage } from "../model/questionTypes/page"; + +export const QUIZ_QUESTION_PAGE: Omit = { + ...QUIZ_QUESTION_BASE, + type: "page", + content: { + ...QUIZ_QUESTION_BASE.content, + innerNameCheck: false, + innerName: "", + text: "", + picture: "", + video: "", + }, +}; diff --git a/src/constants/rating.ts b/src/constants/rating.ts new file mode 100644 index 00000000..a72e5363 --- /dev/null +++ b/src/constants/rating.ts @@ -0,0 +1,19 @@ +import { QUIZ_QUESTION_BASE } from "./base"; + +import type { QuizQuestionRating } from "../model/questionTypes/rating"; + +export const QUIZ_QUESTION_RATING: Omit = { + ...QUIZ_QUESTION_BASE, + type: "rating", + content: { + ...QUIZ_QUESTION_BASE.content, + required: false, + innerNameCheck: false, + innerName: "", + steps: 5, + ratingExpanded: false, + form: "star", + ratingNegativeDescription: "", + ratingPositiveDescription: "", + }, +}; diff --git a/src/constants/select.ts b/src/constants/select.ts new file mode 100644 index 00000000..cf1873bb --- /dev/null +++ b/src/constants/select.ts @@ -0,0 +1,17 @@ +import { QUIZ_QUESTION_BASE } from "./base"; + +import type { QuizQuestionSelect } from "../model/questionTypes/select"; + +export const QUIZ_QUESTION_SELECT: Omit = { + ...QUIZ_QUESTION_BASE, + type: "select", + content: { + ...QUIZ_QUESTION_BASE.content, + multi: false, + required: false, + innerNameCheck: false, + innerName: "", + default: "", + variants: [{ answer: "", hints: "", extendedText: "" }], + }, +}; diff --git a/src/constants/text.ts b/src/constants/text.ts new file mode 100644 index 00000000..d589fe98 --- /dev/null +++ b/src/constants/text.ts @@ -0,0 +1,16 @@ +import { QUIZ_QUESTION_BASE } from "./base"; + +import type { QuizQuestionText } from "../model/questionTypes/text"; + +export const QUIZ_QUESTION_TEXT: Omit = { + ...QUIZ_QUESTION_BASE, + type: "text", + content: { + ...QUIZ_QUESTION_BASE.content, + placeholder: "", + innerNameCheck: false, + innerName: "", + required: false, + answerType: "single", + }, +}; diff --git a/src/constants/variant.ts b/src/constants/variant.ts new file mode 100644 index 00000000..b16a2a7a --- /dev/null +++ b/src/constants/variant.ts @@ -0,0 +1,18 @@ +import { QUIZ_QUESTION_BASE } from "./base"; + +import type { QuizQuestionVariant } from "../model/questionTypes/variant"; + +export const QUIZ_QUESTION_VARIANT: Omit = { + ...QUIZ_QUESTION_BASE, + type: "variant", + content: { + ...QUIZ_QUESTION_BASE.content, + largeCheck: false, + multi: false, + own: false, + innerNameCheck: false, + required: false, + innerName: "", + variants: [{ answer: "", hints: "", extendedText: "" }], + }, +}; diff --git a/src/constants/varimg.ts b/src/constants/varimg.ts new file mode 100644 index 00000000..6f98bdae --- /dev/null +++ b/src/constants/varimg.ts @@ -0,0 +1,18 @@ +import { QUIZ_QUESTION_BASE } from "./base"; + +import type { QuizQuestionVarImg } from "../model/questionTypes/varimg"; + +export const QUIZ_QUESTION_VARIMG: Omit = { + ...QUIZ_QUESTION_BASE, + type: "varimg", + content: { + ...QUIZ_QUESTION_BASE.content, + own: false, + innerNameCheck: false, + innerName: "", + required: false, + variants: [{ answer: "", hints: "", extendedText: "" }], + largeCheck: false, + replText: "", + }, +}; diff --git a/src/index.tsx b/src/index.tsx index 562547b9..5836465a 100755 --- a/src/index.tsx +++ b/src/index.tsx @@ -6,7 +6,6 @@ import "./index.css"; import { BrowserRouter, Route, Routes } from "react-router-dom"; import lightTheme from "./utils/themes/light"; import { ThemeProvider } from "@mui/material"; - import StartPage from "./pages/startPage/StartPage"; import Main from "./pages/main"; import QuestionsPage from "./pages/Questions/QuestionsPage"; @@ -19,12 +18,22 @@ import ContactFormModal from "@ui_kit/ContactForm"; import ImageCrop from "@ui_kit/Modal/ImageCrop"; import Landing from "./pages/Landing/Landing"; import { SnackbarProvider } from 'notistack' +import { LocalizationProvider } from "@mui/x-date-pickers"; +import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs"; +import "dayjs/locale/ru"; +import dayjs from "dayjs"; +import { ruRU } from '@mui/x-date-pickers/locales'; + + +dayjs.locale("ru"); + +const localeText = ruRU.components.MuiLocalizationProvider.defaultProps.localeText; const routeslink: { - path: string; - page: JSX.Element; - header: boolean; - sidebar: boolean; + path: string; + page: JSX.Element; + header: boolean; + sidebar: boolean; }[] = [ { path: "/list", page: , header: false, sidebar: false }, { path: "/questions/:quizId", page: , header: true, sidebar: true,}, @@ -37,21 +46,23 @@ const routeslink: { const root = createRoot(document.getElementById("root")!); root.render( - - - - - - - {routeslink.map((e, i) => ( - } /> - ))} - } /> - } /> - }/> - - - - - + + + + + + + + {routeslink.map((e, i) => ( + } /> + ))} + } /> + } /> + }/> + + + + + + ); diff --git a/src/model/questionTypes/date.ts b/src/model/questionTypes/date.ts new file mode 100644 index 00000000..ea1c8dd4 --- /dev/null +++ b/src/model/questionTypes/date.ts @@ -0,0 +1,23 @@ +import type { + QuizQuestionBase, + QuestionHint, + QuestionBranchingRule, +} from "./shared"; + +export interface QuizQuestionDate extends QuizQuestionBase { + type: "date"; + content: { + /** Чекбокс "Необязательный вопрос" */ + required: boolean; + /** Чекбокс "Внутреннее название вопроса" */ + innerNameCheck: boolean; + /** Поле "Внутреннее название вопроса" */ + innerName: string; + dateRange: boolean; + time: boolean; + hint: QuestionHint; + rule: QuestionBranchingRule; + back: string; + autofill: boolean; + }; +} diff --git a/src/model/questionTypes/emoji.ts b/src/model/questionTypes/emoji.ts new file mode 100644 index 00000000..84193a43 --- /dev/null +++ b/src/model/questionTypes/emoji.ts @@ -0,0 +1,27 @@ +import type { + QuizQuestionBase, + QuestionVariant, + QuestionHint, + QuestionBranchingRule, +} from "./shared"; + +export interface QuizQuestionEmoji extends QuizQuestionBase { + type: "emoji"; + content: { + /** Чекбокс "Можно несколько" */ + multi: boolean; + /** Чекбокс "Вариант "свой ответ"" */ + own: boolean; + /** Чекбокс "Внутреннее название вопроса" */ + innerNameCheck: boolean; + /** Поле "Внутреннее название вопроса" */ + innerName: string; + /** Чекбокс "Необязательный вопрос" */ + required: boolean; + variants: QuestionVariant[]; + hint: QuestionHint; + rule: QuestionBranchingRule; + back: string; + autofill: boolean; + }; +} diff --git a/src/model/questionTypes/file.ts b/src/model/questionTypes/file.ts new file mode 100644 index 00000000..514424f2 --- /dev/null +++ b/src/model/questionTypes/file.ts @@ -0,0 +1,33 @@ +import type { + QuizQuestionBase, + QuestionHint, + QuestionBranchingRule, +} from "./shared"; + +export const uploadFileTypesMap = { + all: "Все типы файлов", + picture: "Изображения", + video: "Видео", + audio: "Аудио", + document: "Документ", +} as const; + +export type UploadFileType = keyof typeof uploadFileTypesMap; + +export interface QuizQuestionFile extends QuizQuestionBase { + type: "file"; + content: { + /** Чекбокс "Необязательный вопрос" */ + required: boolean; + /** Чекбокс "Внутреннее название вопроса" */ + innerNameCheck: boolean; + /** Поле "Внутреннее название вопроса" */ + innerName: string; + /** Чекбокс "Автозаполнение адреса" */ + autofill: boolean; + type: UploadFileType; + hint: QuestionHint; + rule: QuestionBranchingRule; + back: string; + }; +} diff --git a/src/model/questionTypes/images.ts b/src/model/questionTypes/images.ts new file mode 100644 index 00000000..d555b7e7 --- /dev/null +++ b/src/model/questionTypes/images.ts @@ -0,0 +1,35 @@ +import type { + QuizQuestionBase, + QuestionVariant, + QuestionHint, + QuestionBranchingRule, +} from "./shared"; + +export interface QuizQuestionImages extends QuizQuestionBase { + type: "images"; + content: { + /** Чекбокс "Вариант "свой ответ"" */ + own: boolean; + /** Чекбокс "Можно несколько" */ + multi: boolean; + /** Пропорции */ + xy: "1:1" | "1:2" | "2:1"; + /** Чекбокс "Внутреннее название вопроса" */ + innerNameCheck: boolean; + /** Поле "Внутреннее название вопроса" */ + innerName: string; + /** Чекбокс "Большие картинки" */ + large: boolean; + /** Форма */ + format: "carousel" | "masonry"; + /** Чекбокс "Необязательный вопрос" */ + required: boolean; + /** Варианты (картинки) */ + variants: QuestionVariant[]; + hint: QuestionHint; + rule: QuestionBranchingRule; + back: string; + autofill: boolean; + largeCheck: boolean; + }; +} diff --git a/src/model/questionTypes/number.ts b/src/model/questionTypes/number.ts new file mode 100644 index 00000000..2beb1f5c --- /dev/null +++ b/src/model/questionTypes/number.ts @@ -0,0 +1,33 @@ +import type { + QuizQuestionBase, + QuestionHint, + QuestionBranchingRule, +} from "./shared"; + +export interface QuizQuestionNumber extends QuizQuestionBase { + type: "number"; + content: { + /** Чекбокс "Необязательный вопрос" */ + required: boolean; + /** Чекбокс "Внутреннее название вопроса" */ + innerNameCheck: boolean; + /** Поле "Внутреннее название вопроса" */ + innerName: string; + /** Диапазон */ + range: string; + /** Начальное значение */ + start: number; + /** Начальное значение */ + defaultValue: number; + /** Шаг */ + step: number; + steps: number; + /** Чекбокс "Выбор диапазона (два ползунка)" */ + chooseRange: boolean; + hint: QuestionHint; + rule: QuestionBranchingRule; + back: string; + autofill: boolean; + form: "star" | "trophie" | "flag" | "heart" | "like" | "bubble" | "hashtag"; + }; +} diff --git a/src/model/questionTypes/page.ts b/src/model/questionTypes/page.ts new file mode 100644 index 00000000..d994ff6b --- /dev/null +++ b/src/model/questionTypes/page.ts @@ -0,0 +1,22 @@ +import type { + QuizQuestionBase, + QuestionHint, + QuestionBranchingRule, +} from "./shared"; + +export interface QuizQuestionPage extends QuizQuestionBase { + type: "page"; + content: { + /** Чекбокс "Внутреннее название вопроса" */ + innerNameCheck: boolean; + /** Поле "Внутреннее название вопроса" */ + innerName: string; + text: string; + picture: string; + video: string; + hint: QuestionHint; + rule: QuestionBranchingRule; + back: string; + autofill: boolean; + }; +} diff --git a/src/model/questionTypes/rating.ts b/src/model/questionTypes/rating.ts new file mode 100644 index 00000000..7799dff6 --- /dev/null +++ b/src/model/questionTypes/rating.ts @@ -0,0 +1,29 @@ +import type { + QuizQuestionBase, + QuestionHint, + QuestionBranchingRule, +} from "./shared"; + +export interface QuizQuestionRating extends QuizQuestionBase { + type: "rating"; + content: { + /** Чекбокс "Необязательный вопрос" */ + required: boolean; + /** Чекбокс "Внутреннее название вопроса" */ + innerNameCheck: boolean; + /** Поле "Внутреннее название вопроса" */ + innerName: string; + steps: number; + ratingExpanded: boolean; + /** Форма иконки */ + form: string; + hint: QuestionHint; + rule: QuestionBranchingRule; + back: string; + autofill: boolean; + /** Позитивное описание рейтинга */ + ratingPositiveDescription: string; + /** Негативное описание рейтинга */ + ratingNegativeDescription: string; + }; +} diff --git a/src/model/questionTypes/select.ts b/src/model/questionTypes/select.ts new file mode 100644 index 00000000..6b734a90 --- /dev/null +++ b/src/model/questionTypes/select.ts @@ -0,0 +1,27 @@ +import type { + QuizQuestionBase, + QuestionVariant, + QuestionHint, + QuestionBranchingRule, +} from "./shared"; + +export interface QuizQuestionSelect extends QuizQuestionBase { + type: "select"; + content: { + /** Чекбокс "Можно несколько" */ + multi: boolean; + /** Чекбокс "Необязательный вопрос" */ + required: boolean; + /** Чекбокс "Внутреннее название вопроса" */ + innerNameCheck: boolean; + /** Поле "Внутреннее название вопроса" */ + innerName: string; + /** Поле "Текст в выпадающем списке" */ + default: string; + variants: QuestionVariant[]; + rule: QuestionBranchingRule; + hint: QuestionHint; + back: string; + autofill: boolean; + }; +} diff --git a/src/model/questionTypes/shared.ts b/src/model/questionTypes/shared.ts new file mode 100644 index 00000000..7789694b --- /dev/null +++ b/src/model/questionTypes/shared.ts @@ -0,0 +1,77 @@ +import type { QuizQuestionDate } from "./date"; +import type { QuizQuestionEmoji } from "./emoji"; +import type { QuizQuestionFile } from "./file"; +import type { QuizQuestionImages } from "./images"; +import type { QuizQuestionNumber } from "./number"; +import type { QuizQuestionPage } from "./page"; +import type { QuizQuestionRating } from "./rating"; +import type { QuizQuestionSelect } from "./select"; +import type { QuizQuestionText } from "./text"; +import type { QuizQuestionVariant } from "./variant"; +import type { QuizQuestionVarImg } from "./varimg"; + +export interface QuestionBranchingRule { + /** Радиокнопка "Все условия обязательны" */ + or: boolean; + show: boolean; + title: string; + reqs: { + id: string; + /** Список выбранных вариантов */ + vars: number[]; + }[]; +} + +export interface QuestionHint { + /** Текст подсказки */ + text: string; + /** URL видео подсказки */ + video: string; +} + +export type QuestionVariant = { + /** Текст */ + answer: string; + /** Текст подсказки */ + hints: string; + /** Дополнительное поле для текста, emoji, ссылки на картинку */ + extendedText: string; +}; + +export interface QuizQuestionBase { + id: number; + title: string; + type: string; + expanded: boolean; + required: boolean; + deleted: boolean; + deleteTimeoutId: number; + content: { + hint: QuestionHint; + rule: QuestionBranchingRule; + back: string; + autofill: boolean; + }; +} + +export interface QuizQuestionInitial extends QuizQuestionBase { + type: "nonselected"; +} + +export type AnyQuizQuestion = + | QuizQuestionVariant + | QuizQuestionImages + | QuizQuestionVarImg + | QuizQuestionEmoji + | QuizQuestionText + | QuizQuestionSelect + | QuizQuestionDate + | QuizQuestionNumber + | QuizQuestionFile + | QuizQuestionPage + | QuizQuestionRating + | QuizQuestionInitial; + +export type QuizQuestionType = AnyQuizQuestion["type"]; + +export type DefiniteQuestionType = Exclude; diff --git a/src/model/questionTypes/text.ts b/src/model/questionTypes/text.ts new file mode 100644 index 00000000..43f35417 --- /dev/null +++ b/src/model/questionTypes/text.ts @@ -0,0 +1,24 @@ +import type { + QuizQuestionBase, + QuestionHint, + QuestionBranchingRule, +} from "./shared"; + +export interface QuizQuestionText extends QuizQuestionBase { + type: "text"; + content: { + placeholder: string; + /** Чекбокс "Внутреннее название вопроса" */ + innerNameCheck: boolean; + /** Поле "Внутреннее название вопроса" */ + innerName: string; + /** Чекбокс "Необязательный вопрос" */ + required: boolean; + /** Чекбокс "Автозаполнение адреса" */ + autofill: boolean; + answerType: "single" | "multi" | "number"; + hint: QuestionHint; + rule: QuestionBranchingRule; + back: string; + }; +} diff --git a/src/model/questionTypes/variant.ts b/src/model/questionTypes/variant.ts new file mode 100644 index 00000000..9a7f3c9b --- /dev/null +++ b/src/model/questionTypes/variant.ts @@ -0,0 +1,30 @@ +import type { + QuizQuestionBase, + QuestionVariant, + QuestionHint, + QuestionBranchingRule, +} from "./shared"; + +export interface QuizQuestionVariant extends QuizQuestionBase { + type: "variant"; + content: { + /** Чекбокс "Длинный текстовый ответ" */ + largeCheck: boolean; + /** Чекбокс "Можно несколько" */ + multi: boolean; + /** Чекбокс "Вариант "свой ответ"" */ + own: boolean; + /** Чекбокс "Внутреннее название вопроса" */ + innerNameCheck: boolean; + /** Чекбокс "Необязательный вопрос" */ + required: boolean; + /** Поле "Внутреннее название вопроса" */ + innerName: string; + /** Варианты ответов */ + variants: QuestionVariant[]; + hint: QuestionHint; + rule: QuestionBranchingRule; + back: string; + autofill: boolean; + }; +} diff --git a/src/model/questionTypes/varimg.ts b/src/model/questionTypes/varimg.ts new file mode 100644 index 00000000..2e5ebd43 --- /dev/null +++ b/src/model/questionTypes/varimg.ts @@ -0,0 +1,27 @@ +import type { + QuizQuestionBase, + QuestionVariant, + QuestionHint, + QuestionBranchingRule, +} from "./shared"; + +export interface QuizQuestionVarImg extends QuizQuestionBase { + type: "varimg"; + content: { + /** Чекбокс "Вариант "свой ответ"" */ + own: boolean; + /** Чекбокс "Внутреннее название вопроса" */ + innerNameCheck: boolean; + /** Поле "Внутреннее название вопроса" */ + innerName: string; + /** Чекбокс "Необязательный вопрос" */ + required: boolean; + variants: QuestionVariant[]; + hint: QuestionHint; + rule: QuestionBranchingRule; + back: string; + autofill: boolean; + largeCheck: boolean; + replText: string; + }; +} diff --git a/src/pages/ContactFormPage/ContactFormPage.tsx b/src/pages/ContactFormPage/ContactFormPage.tsx index 1e16d3a7..28da8dcd 100644 --- a/src/pages/ContactFormPage/ContactFormPage.tsx +++ b/src/pages/ContactFormPage/ContactFormPage.tsx @@ -1,5 +1,4 @@ import React from "react"; -import Stepper from "@ui_kit/Stepper"; import { Box, Button, IconButton, Typography, Paper, useTheme, Link, SxProps, Theme, TextField } from "@mui/material"; import ExpandMoreIcon from "@mui/icons-material/ExpandMore"; import CustomTextField from "@ui_kit/CustomTextField"; @@ -41,7 +40,6 @@ export default function ContactFormPage() { const theme = useTheme(); return ( <> - { - setActiveStep((prevActiveStep) => prevActiveStep + 1); - }; - - const handleBack = () => { - setActiveStep((prevActiveStep) => prevActiveStep - 1); - }; - const [display, setDisplay] = React.useState("1"); + const quizId = Number(useParams().quizId); const handleChange = (event: SelectChangeEvent) => { setDisplay(event.target.value); }; @@ -96,9 +88,14 @@ export default function InstallQuiz() { const [backgroundType, setBackgroundType] = useState("text"); const theme = useTheme(); + const { listQuizes, updateQuizesList } = quizStore(); + + const handleNext = () => { + updateQuizesList(quizId, { step: listQuizes[quizId].step + 1 }); + }; + return ( <> - - + { const quizId = Number(useParams().quizId); const { listQuestions } = questionStore(); const theme = useTheme(); + const question = listQuestions[quizId][totalIndex] as QuizQuestionVariant; const isTablet = useMediaQuery(theme.breakpoints.down(790)); const debounced = useDebouncedCallback((value) => { const answerNew = variants.slice(); answerNew[index].answer = value; - updateQuestionsList(quizId, totalIndex, { + updateQuestionsList(quizId, totalIndex, { content: { - ...listQuestions[quizId][totalIndex].content, + ...question.content, variants: answerNew, }, }); @@ -72,13 +73,10 @@ export const AnswerItem = ({ const addNewAnswer = () => { const answerNew = variants.slice(); - answerNew.push({ answer: "", hints: "", emoji: "", image: "" }); + answerNew.push({ answer: "", hints: "", extendedText: "" }); - updateQuestionsList(quizId, totalIndex, { - content: { - ...listQuestions[quizId][totalIndex].content, - variants: answerNew, - }, + updateQuestionsList(quizId, totalIndex, { + content: { ...question.content, variants: answerNew }, }); }; @@ -86,11 +84,8 @@ export const AnswerItem = ({ const answerNew = variants.slice(); answerNew.splice(index, 1); - updateQuestionsList(quizId, totalIndex, { - content: { - ...listQuestions[quizId][totalIndex].content, - variants: answerNew, - }, + updateQuestionsList(quizId, totalIndex, { + content: { ...question.content, variants: answerNew }, }); }; @@ -98,11 +93,8 @@ export const AnswerItem = ({ const answerNew = variants.slice(); answerNew[index].hints = event.target.value; - updateQuestionsList(quizId, totalIndex, { - content: { - ...listQuestions[quizId][totalIndex].content, - variants: answerNew, - }, + updateQuestionsList(quizId, totalIndex, { + content: { ...question.content, variants: answerNew }, }); }; @@ -126,13 +118,10 @@ export const AnswerItem = ({ fullWidth focused={false} placeholder={"Добавьте ответ"} - multiline={listQuestions[quizId][totalIndex].content.largeCheck} + multiline={question.content.largeCheck} onChange={({ target }) => debounced(target.value)} onKeyDown={(event: KeyboardEvent) => { - if ( - event.code === "Enter" && - !listQuestions[quizId][totalIndex].content.largeCheck - ) { + if (event.code === "Enter" && !question.content.largeCheck) { addNewAnswer(); } }} @@ -166,13 +155,11 @@ export const AnswerItem = ({ /> ReactNode; - additionalMobile?: (variant: Variants, index: number) => ReactNode; + additionalContent?: (variant: QuestionVariant, index: number) => ReactNode; + additionalMobile?: (variant: QuestionVariant, index: number) => ReactNode; }; export const AnswerDraggableList = ({ diff --git a/src/pages/Questions/ButtonsOptions.tsx b/src/pages/Questions/ButtonsOptions.tsx index 40f41b8f..f83973a1 100644 --- a/src/pages/Questions/ButtonsOptions.tsx +++ b/src/pages/Questions/ButtonsOptions.tsx @@ -4,20 +4,39 @@ import { useParams } from "react-router-dom"; import SettingIcon from "../../assets/icons/questionsPage/settingIcon"; import Clue from "../../assets/icons/questionsPage/clue"; import Branching from "../../assets/icons/questionsPage/branching"; -import { Box, Typography, Tooltip, IconButton, useTheme, useMediaQuery } from "@mui/material"; +import { + Box, + Typography, + Tooltip, + IconButton, + useTheme, + useMediaQuery, +} from "@mui/material"; import { HideIcon } from "../../assets/icons/questionsPage/hideIcon"; import { CopyIcon } from "../../assets/icons/questionsPage/CopyIcon"; -import { questionStore, resetSomeField, copyQuestion, removeQuestionForce, updateQuestionsList, removeQuestion } from "@root/questions"; +import { + questionStore, + resetSomeField, + copyQuestion, + removeQuestionForce, + updateQuestionsList, + removeQuestion, +} from "@root/questions"; +import { quizStore } from "@root/quizes"; import { DoubleTick } from "@icons/questionsPage/DoubleTick"; import { DoubleArrowRight } from "@icons/questionsPage/DoubleArrowRight"; import { VectorQuestions } from "@icons/questionsPage/VectorQuestions"; import { DeleteIcon } from "@icons/questionsPage/deleteIcon"; +import type { SxProps } from "@mui/material"; +import type { QuizQuestionBase } from "../../model/questionTypes/shared"; + interface Props { switchState: string; SSHC: (data: string) => void; totalIndex: number; + sx?: SxProps; } export default function ButtonsOptions({ @@ -27,12 +46,15 @@ export default function ButtonsOptions({ }: Props) { const quizId = Number(useParams().quizId); const { openedModalSettings, listQuestions } = questionStore(); + const { listQuizes } = quizStore(); const [openedReallyChangingModal, setOpenedReallyChangingModal] = useState(false); + const quize = listQuizes[quizId]; + const question = listQuestions[quizId][totalIndex] as QuizQuestionBase; useEffect(() => { - if (listQuestions[quizId][totalIndex].deleteTimeoutId) { - clearTimeout(listQuestions[quizId][totalIndex].deleteTimeoutId); + if (question.deleteTimeoutId) { + clearTimeout(question.deleteTimeoutId); } }, [listQuestions]); @@ -110,6 +132,19 @@ export default function ButtonsOptions({ {icon} - {isWrappMiniButtonSetting ? null : title} ) : ( - { - SSHC(value); - myFunc(); - }} - sx={{ - backgroundColor: - switchState === value - ? theme.palette.brightPurple.main - : "transparent", - color: - switchState === value - ? "#ffffff" - : theme.palette.grey3.main, - minWidth: isWrappMiniButtonSetting ? "30px" : "64px", - height: "30px", - }} - > - {icon} - {isWrappMiniButtonSetting ? null : title} - + <> + { + SSHC(value); + myFunc(); + }} + sx={{ + backgroundColor: + switchState === value + ? theme.palette.brightPurple.main + : "transparent", + color: + switchState === value + ? "#ffffff" + : theme.palette.grey3.main, + minWidth: isWrappMiniButtonSetting ? "30px" : "64px", + height: "30px", + "&:hover": { + color: theme.palette.grey3.main, + "& path": { stroke: theme.palette.grey3.main }, + }, + }} + > + {icon} + {isWrappMiniButtonSetting ? null : title} + + )} ))} - setOpenedReallyChangingModal(true)} - sx={{ - minWidth: "30px", - height: "30px", - backgroundColor: "#FEDFD0", - }} - > - - - setOpenedReallyChangingModal(true)} - sx={{ - minWidth: "30px", - height: "30px", - backgroundColor: "#FEDFD0", - }} - > - - - setOpenedReallyChangingModal(true)} - sx={{ - minWidth: "30px", - height: "30px", - backgroundColor: "#FEDFD0", - }} - > - - + <> + setOpenedReallyChangingModal(true)} + sx={{ + minWidth: "30px", + height: "30px", + backgroundColor: "#FEDFD0", + }} + > + + + setOpenedReallyChangingModal(true)} + sx={{ + minWidth: "30px", + height: "30px", + backgroundColor: "#FEDFD0", + }} + > + + + setOpenedReallyChangingModal(true)} + sx={{ + minWidth: "30px", + height: "30px", + backgroundColor: "#FEDFD0", + }} + > + + + { - const removedId = listQuestions[quizId][totalIndex].id; - if (listQuestions[quizId][totalIndex].deleteTimeoutId) { - clearTimeout(listQuestions[quizId][totalIndex].deleteTimeoutId); + const removedId = question.id; + if (question.deleteTimeoutId) { + clearTimeout(question.deleteTimeoutId); } removeQuestion(quizId, totalIndex); @@ -250,8 +296,8 @@ export default function ButtonsOptions({ removeQuestionForce(quizId, removedId); }, 5000); - updateQuestionsList(quizId, totalIndex, { - ...listQuestions[quizId][totalIndex], + updateQuestionsList(quizId, totalIndex, { + ...question, deleteTimeoutId: newTimeoutId, }); }} @@ -261,4 +307,4 @@ export default function ButtonsOptions({ ); -} \ No newline at end of file +} diff --git a/src/pages/Questions/ButtonsOptionsAndPict.css b/src/pages/Questions/ButtonsOptionsAndPict.css deleted file mode 100644 index 250e1922..00000000 --- a/src/pages/Questions/ButtonsOptionsAndPict.css +++ /dev/null @@ -1,10 +0,0 @@ -.MuiTooltip-popper > .MuiTooltip-tooltip { - background: #fff; - border-radius: 6px; - color: #9A9AAF; - box-shadow: 0px 8px 24px rgba(210, 208, 225, 0.4); -} - -.MuiTooltip-popper > .MuiTooltip-tooltip .MuiTooltip-arrow::before { - background: #fff; -} \ No newline at end of file diff --git a/src/pages/Questions/ButtonsOptionsAndPict.tsx b/src/pages/Questions/ButtonsOptionsAndPict.tsx index 1f4d714d..fc59b983 100644 --- a/src/pages/Questions/ButtonsOptionsAndPict.tsx +++ b/src/pages/Questions/ButtonsOptionsAndPict.tsx @@ -3,12 +3,20 @@ import MiniButtonSetting from "@ui_kit/MiniButtonSetting"; import SettingIcon from "../../assets/icons/questionsPage/settingIcon"; import Clue from "../../assets/icons/questionsPage/clue"; import Branching from "../../assets/icons/questionsPage/branching"; -import { Box, IconButton, Tooltip, Typography, useMediaQuery, useTheme } from "@mui/material"; +import { + Box, + IconButton, + Tooltip, + Typography, + useMediaQuery, + useTheme, +} from "@mui/material"; import { HideIcon } from "../../assets/icons/questionsPage/hideIcon"; import { CopyIcon } from "../../assets/icons/questionsPage/CopyIcon"; import { DeleteIcon } from "../../assets/icons/questionsPage/deleteIcon"; import ImgIcon from "../../assets/icons/questionsPage/imgIcon"; import { useParams } from "react-router-dom"; +import { quizStore } from "@root/quizes"; import { questionStore, copyQuestion, @@ -22,7 +30,7 @@ import { DoubleTick } from "@icons/questionsPage/DoubleTick"; import { VectorQuestions } from "@icons/questionsPage/VectorQuestions"; import { ReallyChangingModal } from "@ui_kit/Modal/ReallyChangingModal/ReallyChangingModal"; -import "./ButtonsOptionsAndPict.css"; +import type { QuizQuestionBase } from "../../model/questionTypes/shared"; interface Props { switchState: string; @@ -30,19 +38,26 @@ interface Props { totalIndex: number; } -export default function ButtonsOptionsAndPict({ SSHC, switchState, totalIndex }: Props) { +export default function ButtonsOptionsAndPict({ + SSHC, + switchState, + totalIndex, +}: Props) { const [buttonHover, setButtonHover] = useState(""); const quizId = Number(useParams().quizId); + const { listQuizes } = quizStore(); const { listQuestions } = questionStore(); const [openedReallyChangingModal, setOpenedReallyChangingModal] = useState(false); const theme = useTheme(); const isMobile = useMediaQuery(theme.breakpoints.down(790)); const isIconMobile = useMediaQuery(theme.breakpoints.down(1050)); + const quize = listQuizes[quizId]; + const question = listQuestions[quizId][totalIndex] as QuizQuestionBase; useEffect(() => { - if (listQuestions[quizId][totalIndex].deleteTimeoutId) { - clearTimeout(listQuestions[quizId][totalIndex].deleteTimeoutId); + if (question.deleteTimeoutId) { + clearTimeout(question.deleteTimeoutId); } }, [listQuestions]); @@ -79,10 +94,15 @@ export default function ButtonsOptionsAndPict({ SSHC, switchState, totalIndex }: maxWidth: "104px", minWidth: isIconMobile ? "30px" : "64px", height: "30px", - backgroundColor: switchState === "setting" ? theme.palette.brightPurple.main : "transparent", - color: switchState === "setting" ? "#ffffff" : theme.palette.grey3.main, + backgroundColor: + switchState === "setting" + ? theme.palette.brightPurple.main + : "transparent", + color: + switchState === "setting" ? "#ffffff" : theme.palette.grey3.main, "&:hover": { - color: switchState === "setting" ? theme.palette.grey3.main : null, + color: + switchState === "setting" ? theme.palette.grey3.main : null, }, }} > @@ -107,8 +127,12 @@ export default function ButtonsOptionsAndPict({ SSHC, switchState, totalIndex }: minWidth: isIconMobile ? "30px" : "64px", maxWidth: "102px", height: "30px", - backgroundColor: switchState === "help" ? theme.palette.brightPurple.main : "transparent", - color: switchState === "help" ? "#ffffff" : theme.palette.grey3.main, + backgroundColor: + switchState === "help" + ? theme.palette.brightPurple.main + : "transparent", + color: + switchState === "help" ? "#ffffff" : theme.palette.grey3.main, "&:hover": { color: switchState === "help" ? theme.palette.grey3.main : null, }, @@ -125,123 +149,156 @@ export default function ButtonsOptionsAndPict({ SSHC, switchState, totalIndex }: /> {isIconMobile ? null : "Подсказка"} - - - Будет показан при условии - - Название - - Условие 1, Условие 2 - - Все условия обязательны - - } - > + <> + + + Будет показан при условии + + + Название + + + Условие 1, Условие 2 + + + Все условия обязательны + + + } + > + setButtonHover("branching")} + onMouseLeave={() => setButtonHover("")} + onClick={() => { + SSHC("branching"); + openedModal(); + }} + sx={{ + height: "30px", + maxWidth: "103px", + minWidth: isIconMobile ? "30px" : "64px", + backgroundColor: + switchState === "branching" + ? theme.palette.brightPurple.main + : "transparent", + color: + switchState === "branching" + ? "#ffffff" + : theme.palette.grey3.main, + "&:hover": { + color: + switchState === "branching" + ? theme.palette.grey3.main + : null, + }, + }} + > + + {isIconMobile ? null : "Ветвление"} + + setButtonHover("branching")} + onMouseEnter={() => setButtonHover("image")} onMouseLeave={() => setButtonHover("")} onClick={() => { - SSHC("branching"); - openedModal(); + SSHC("image"); }} sx={{ height: "30px", - maxWidth: "103px", + maxWidth: "123px", minWidth: isIconMobile ? "30px" : "64px", - backgroundColor: switchState === "branching" ? theme.palette.brightPurple.main : "transparent", - color: switchState === "branching" ? "#ffffff" : theme.palette.grey3.main, + backgroundColor: + switchState === "image" + ? theme.palette.brightPurple.main + : "transparent", + color: + switchState === "image" ? "#ffffff" : theme.palette.grey3.main, "&:hover": { - color: switchState === "branching" ? theme.palette.grey3.main : null, + color: + switchState === "image" ? theme.palette.grey3.main : null, }, }} > - - {isIconMobile ? null : "Ветвление"} + {isIconMobile ? null : "Изображение"} - - setButtonHover("image")} - onMouseLeave={() => setButtonHover("")} - onClick={() => { - SSHC("image"); - }} - sx={{ - height: "30px", - maxWidth: "123px", - minWidth: isIconMobile ? "30px" : "64px", - backgroundColor: switchState === "image" ? theme.palette.brightPurple.main : "transparent", - color: switchState === "image" ? "#ffffff" : theme.palette.grey3.main, - "&:hover": { - color: switchState === "image" ? theme.palette.grey3.main : null, - }, - }} - > - - {isIconMobile ? null : "Изображение"} - - setOpenedReallyChangingModal(true)} - sx={{ - minWidth: "30px", - height: "30px", - backgroundColor: "#FEDFD0", - }} - > - - - setOpenedReallyChangingModal(true)} - sx={{ - minWidth: "30px", - height: "30px", - backgroundColor: "#FEDFD0", - }} - > - - - setOpenedReallyChangingModal(true)} - sx={{ - minWidth: "30px", - height: "30px", - backgroundColor: "#FEDFD0", - }} - > - - + setOpenedReallyChangingModal(true)} + sx={{ + minWidth: "30px", + height: "30px", + backgroundColor: "#FEDFD0", + }} + > + + + setOpenedReallyChangingModal(true)} + sx={{ + minWidth: "30px", + height: "30px", + backgroundColor: "#FEDFD0", + }} + > + + + setOpenedReallyChangingModal(true)} + sx={{ + minWidth: "30px", + height: "30px", + backgroundColor: "#FEDFD0", + }} + > + + + - copyQuestion(quizId, totalIndex)}> - - copyQuestion(quizId, totalIndex)} > - + { - const removedId = listQuestions[quizId][totalIndex].id; - if (listQuestions[quizId][totalIndex].deleteTimeoutId) { - clearTimeout(listQuestions[quizId][totalIndex].deleteTimeoutId); + const removedId = question.id; + if (question.deleteTimeoutId) { + clearTimeout(question.deleteTimeoutId); } removeQuestion(quizId, totalIndex); @@ -274,8 +328,8 @@ export default function ButtonsOptionsAndPict({ SSHC, switchState, totalIndex }: removeQuestionForce(quizId, removedId); }, 5000); - updateQuestionsList(quizId, totalIndex, { - ...listQuestions[quizId][totalIndex], + updateQuestionsList(quizId, totalIndex, { + ...question, deleteTimeoutId: newTimeoutId, }); }} diff --git a/src/pages/Questions/DataOptions/DataOptions.tsx b/src/pages/Questions/DataOptions/DataOptions.tsx index a25b260f..2830d34d 100644 --- a/src/pages/Questions/DataOptions/DataOptions.tsx +++ b/src/pages/Questions/DataOptions/DataOptions.tsx @@ -1,11 +1,9 @@ -import { useParams } from "react-router-dom"; -import { Box, Typography, Tooltip, useMediaQuery, useTheme } from "@mui/material"; +import { Box, Tooltip, Typography, useMediaQuery, useTheme } from "@mui/material"; +import { useState } from "react"; +import InfoIcon from "../../../assets/icons/InfoIcon"; import ButtonsOptions from "../ButtonsOptions"; import SwitchData from "./switchData"; -import { useState, useEffect } from "react"; -import InfoIcon from "../../../assets/icons/InfoIcon"; -import SelectableButton from "@ui_kit/SelectableButton"; -import { questionStore, updateQuestionsList } from "@root/questions"; + interface Props { totalIndex: number; @@ -13,8 +11,6 @@ interface Props { export default function DataOptions({ totalIndex }: Props) { const [switchState, setSwitchState] = useState("setting"); - const quizId = Number(useParams().quizId); - const { listQuestions } = questionStore(); const theme = useTheme(); const isMobile = useMediaQuery(theme.breakpoints.down(790)); @@ -22,14 +18,6 @@ export default function DataOptions({ totalIndex }: Props) { setSwitchState(data); }; - useEffect(() => { - if (listQuestions[quizId][totalIndex].content.type !== "mask") { - const clonContent = listQuestions[quizId][totalIndex].content; - clonContent.type = "calendar"; - updateQuestionsList(quizId, totalIndex, { content: clonContent }); - } - }, []); - return ( <> - - { - const clonContent = listQuestions[quizId][totalIndex].content; - clonContent.type = "calendar"; - updateQuestionsList(quizId, totalIndex, { content: clonContent }); - }} - sx={{ maxWidth: "257px", height: "48px", whiteSpace: "nowrap" }} - > - Использовать календарь - - { - const clonContent = listQuestions[quizId][totalIndex].content; - clonContent.type = "mask"; - updateQuestionsList(quizId, totalIndex, { content: clonContent }); - }} - sx={{ maxWidth: "211px", height: "48px", whiteSpace: "nowrap" }} - > - Использовать маску - - - + { - let clonContent = listQuestions[quizId][totalIndex].content; - clonContent.innerName = value; - updateQuestionsList(quizId, totalIndex, { content: clonContent }); + updateQuestionsList(quizId, totalIndex, { + content: { ...question.content, innerName: value }, + }); }, 1000); return ( @@ -34,7 +37,7 @@ export default function SettingsData({ totalIndex }: SettingsDataProps) { > { - const clonContent = listQuestions[quizId][totalIndex].content; - clonContent.dateRange = target.checked; - updateQuestionsList(quizId, totalIndex, { content: clonContent }); + updateQuestionsList(quizId, totalIndex, { + content: { ...question.content, dateRange: target.checked }, + }); }} /> { - const clonContent = listQuestions[quizId][totalIndex].content; - clonContent.time = target.checked; - updateQuestionsList(quizId, totalIndex, { content: clonContent }); + updateQuestionsList(quizId, totalIndex, { + content: { ...question.content, time: target.checked }, + }); }} /> @@ -76,7 +79,7 @@ export default function SettingsData({ totalIndex }: SettingsDataProps) { display: "flex", flexDirection: "column", gap: isMobile ? "13px" : "14px", - width: "100%", + width: isMobile ? "auto" : "100%", }} > @@ -85,27 +88,36 @@ export default function SettingsData({ totalIndex }: SettingsDataProps) { { - updateQuestionsList(quizId, totalIndex, { - required: !e.target.checked, + checked={!question.required} + handleChange={({ target }) => { + updateQuestionsList(quizId, totalIndex, { + required: !target.checked, }); }} /> - + { - let clonContent = listQuestions[quizId][totalIndex].content; - clonContent.innerNameCheck = e.target.checked; - - if (!e.target.checked) { - clonContent.innerName = ""; - } - - updateQuestionsList(quizId, totalIndex, { content: clonContent }); + checked={question.content.innerNameCheck} + handleChange={({ target }) => { + updateQuestionsList(quizId, totalIndex, { + content: { + ...question.content, + innerNameCheck: target.checked, + innerName: target.checked ? question.content.innerName : "", + }, + }); }} /> @@ -114,10 +126,10 @@ export default function SettingsData({ totalIndex }: SettingsDataProps) { - {listQuestions[quizId][totalIndex].content.innerNameCheck && ( + {question.content.innerNameCheck && ( debounced(target.value)} /> )} diff --git a/src/pages/Questions/DraggableList/ChooseAnswerModal.tsx b/src/pages/Questions/DraggableList/ChooseAnswerModal.tsx index 17d70e6b..44150618 100644 --- a/src/pages/Questions/DraggableList/ChooseAnswerModal.tsx +++ b/src/pages/Questions/DraggableList/ChooseAnswerModal.tsx @@ -17,11 +17,16 @@ import { import { questionStore, updateQuestionsList, - DEFAULT_QUESTION, + removeQuestionForce, + createQuestion, } from "@root/questions"; import { BUTTON_TYPE_QUESTIONS } from "../TypeQuestions"; import type { RefObject } from "react"; +import type { + QuizQuestionType, + QuizQuestionBase, +} from "../../../model/questionTypes/shared"; type ChooseAnswerModalProps = { open: boolean; @@ -39,7 +44,7 @@ export const ChooseAnswerModal = ({ switchState, }: ChooseAnswerModalProps) => { const [openModal, setOpenModal] = useState(false); - const [selectedValue, setSelectedValue] = useState(""); + const [selectedValue, setSelectedValue] = useState("text"); const quizId = Number(useParams().quizId); const { listQuestions } = questionStore(); const theme = useTheme(); @@ -124,11 +129,12 @@ export const ChooseAnswerModal = ({ onClick={() => { setOpenModal(false); - const question = listQuestions[quizId][totalIndex]; - updateQuestionsList(quizId, totalIndex, { - ...DEFAULT_QUESTION, + const question = { ...listQuestions[quizId][totalIndex] }; + + removeQuestionForce(quizId, question.id); + createQuestion(quizId, selectedValue, totalIndex); + updateQuestionsList(quizId, totalIndex, { expanded: question.expanded, - type: selectedValue, }); }} > diff --git a/src/pages/Questions/DraggableList/DraggableListItem.tsx b/src/pages/Questions/DraggableList/DraggableListItem.tsx index de222151..750621c0 100644 --- a/src/pages/Questions/DraggableList/DraggableListItem.tsx +++ b/src/pages/Questions/DraggableList/DraggableListItem.tsx @@ -5,84 +5,82 @@ import { Box, ListItem, Typography, useTheme } from "@mui/material"; import QuestionsPageCard from "./QuestionPageCard"; -import { questionStore, updateQuestionsList } from "@root/questions"; +import { updateQuestionsList } from "@root/questions"; -import {Question} from "@root/questions" +import { QuizQuestionBase } from "../../../model/questionTypes/shared"; type DraggableListItemProps = { index: number; isDragging: boolean; - questionData: Question; + questionData: QuizQuestionBase; }; -export default memo(({ - index, - isDragging, - questionData -}: DraggableListItemProps) => { - const quizId = Number(useParams().quizId); - const theme = useTheme(); - console.log("Мой индекс "+index) - console.log(questionData) +export default memo( + ({ index, isDragging, questionData }: DraggableListItemProps) => { + const quizId = Number(useParams().quizId); + const theme = useTheme(); + console.log("Мой индекс " + index); + console.log(questionData); - return ( - - {(provided) => ( - - {questionData.deleted ? ( - - + {(provided) => ( + + {questionData.deleted ? ( + - Вопрос удалён. - - { - updateQuestionsList(quizId, index, { - ...questionData, - deleted: false, - }); - }} - sx={{ - cursor: "pointer", - fontSize: "16px", - textDecoration: "underline", - color: theme.palette.brightPurple.main, - textDecorationColor: theme.palette.brightPurple.main, - }} - > - Восстановить? - - - ) : ( - - - - )} - - )} - - ); -}); + + Вопрос удалён. + + { + updateQuestionsList(quizId, index, { + ...questionData, + deleted: false, + }); + }} + sx={{ + cursor: "pointer", + fontSize: "16px", + textDecoration: "underline", + color: theme.palette.brightPurple.main, + textDecorationColor: theme.palette.brightPurple.main, + }} + > + Восстановить? + + + ) : ( + + + + )} + + )} + + ); + } +); diff --git a/src/pages/Questions/DraggableList/QuestionPageCard.tsx b/src/pages/Questions/DraggableList/QuestionPageCard.tsx index ddcf5acb..078c96e6 100644 --- a/src/pages/Questions/DraggableList/QuestionPageCard.tsx +++ b/src/pages/Questions/DraggableList/QuestionPageCard.tsx @@ -32,7 +32,7 @@ import { DeleteIcon } from "@icons/questionsPage/deleteIcon"; import { OneIcon } from "@icons/questionsPage/OneIcon"; import { PointsIcon } from "@icons/questionsPage/PointsIcon"; import { CopyIcon } from "@icons/questionsPage/CopyIcon"; -import CrossedEyeIcon from "@icons/CrossedEyeIcon"; +import { CrossedEyeIcon } from "@icons/CrossedEyeIcon"; import { HideIcon } from "@icons/questionsPage/hideIcon"; import Answer from "@icons/questionsPage/answer"; import OptionsPict from "@icons/questionsPage/options_pict"; @@ -45,10 +45,11 @@ import Slider from "@icons/questionsPage/slider"; import Download from "@icons/questionsPage/download"; import Page from "@icons/questionsPage/page"; import RatingIcon from "@icons/questionsPage/rating"; +import { ArrowDownIcon } from "@icons/questionsPage/ArrowDownIcon"; import { ReactComponent as PlusIcon } from "../../../assets/icons/plus.svg"; import type { DraggableProvidedDragHandleProps } from "react-beautiful-dnd"; -import { ArrowDownIcon } from "@icons/questionsPage/ArrowDownIcon"; +import type { AnyQuizQuestion, QuizQuestionInitial } from "../../../model/questionTypes/shared"; interface Props { totalIndex: number; @@ -59,32 +60,91 @@ interface Props { const IconAndrom = (isExpanded: boolean, switchState: string) => { switch (switchState) { case "variant": - return ; + return ( + + ); case "images": - return ; + return ( + + ); case "varimg": - return ; + return ( + + ); case "emoji": - return ; + return ( + + ); case "text": - return ; + return ( + + ); case "select": - return ; + return ( + + ); case "date": - return ; + return ( + + ); case "number": - return ; + return ( + + ); case "file": - return ; + return ( + + ); case "page": - return ; + return ( + + ); case "rating": - return ; + return ( + + ); default: return <>; } }; -export default function QuestionsPageCard({ totalIndex, draggableProps, isDragging }: Props) { +export default function QuestionsPageCard({ + totalIndex, + draggableProps, + isDragging, +}: Props) { const [plusVisible, setPlusVisible] = useState(false); const [open, setOpen] = useState(false); const quizId = Number(useParams().quizId); @@ -92,17 +152,17 @@ export default function QuestionsPageCard({ totalIndex, draggableProps, isDraggi const isTablet = useMediaQuery(theme.breakpoints.down(1000)); const isMobile = useMediaQuery(theme.breakpoints.down(790)); const { listQuestions } = questionStore(); - const { type: switchState, expanded: isExpanded } = listQuestions[quizId][totalIndex]; + const question = listQuestions[quizId][totalIndex]; const anchorRef = useRef(null); const debounced = useDebouncedCallback((title) => { - updateQuestionsList(quizId, totalIndex, { title }); - }, 1000); + updateQuestionsList(quizId, totalIndex, { title }); + }, 200); useEffect(() => { - if (listQuestions[quizId][totalIndex].deleteTimeoutId) { - clearTimeout(listQuestions[quizId][totalIndex].deleteTimeoutId); + if (question.deleteTimeoutId) { + clearTimeout(question.deleteTimeoutId); } - }, [listQuestions]); + }, [question]); return ( <> @@ -111,17 +171,18 @@ export default function QuestionsPageCard({ totalIndex, draggableProps, isDraggi sx={{ maxWidth: "796px", width: "100%", - borderRadius: "8px", - backgroundColor: isExpanded ? "white" : "#333647", + borderRadius: "12px", + backgroundColor: question.expanded ? "white" : "#EEE4FC", + border: question.expanded ? "none" : "1px solid #9A9AAF", + boxShadow: "0px 10px 30px #e7e7e7", }} > debounced(target.value)} InputProps={{ @@ -146,26 +207,34 @@ export default function QuestionsPageCard({ totalIndex, draggableProps, isDraggi sx={{ cursor: "pointer" }} onClick={() => setOpen((isOpened) => !isOpened)} > - {IconAndrom(isExpanded, switchState)} + {IconAndrom(question.expanded, question.type)} setOpen(false)} anchorRef={anchorRef} totalIndex={totalIndex} - switchState={switchState} + switchState={question.type} /> ), }} sx={{ + margin: isMobile ? "10px 0" : 0, "& .MuiInputBase-root": { - color: isExpanded ? "#9A9AAF" : "white", - backgroundColor: isExpanded ? theme.palette.background.default : "transparent", + color: question.expanded ? "#9A9AAF" : "#4D4D4D", + backgroundColor: question.expanded + ? theme.palette.background.default + : "transparent", height: "48px", borderRadius: "10px", ".MuiOutlinedInput-notchedOutline": { borderWidth: "1px !important", + border: !question.expanded ? "none" : null, + }, + "& .MuiInputBase-input::placeholder": { + color: "#4D4D4D", + opacity: 0.8, }, }, }} @@ -174,7 +243,7 @@ export default function QuestionsPageCard({ totalIndex, draggableProps, isDraggi fontSize: "18px", lineHeight: "21px", py: 0, - paddingLeft: switchState.length === 0 ? 0 : "18px", + paddingLeft: question.type.length === 0 ? 0 : "18px", }, }} /> @@ -182,96 +251,132 @@ export default function QuestionsPageCard({ totalIndex, draggableProps, isDraggi - + updateQuestionsList(quizId, totalIndex, { + expanded: !question.expanded, + }) + } > - - updateQuestionsList(quizId, totalIndex, { - expanded: !isExpanded, - }) - } - > - {isExpanded ? ( - - ) : ( - - )} - - {isExpanded ? ( - <> + {question.expanded ? ( + ) : ( - - } checkedIcon={} /> - } - label={""} - sx={{ - color: theme.palette.grey2.main, - ml: "-9px", - mr: 0, - userSelect: "none", - }} - /> - copyQuestion(quizId, totalIndex)}> - - - { - const removedId = listQuestions[quizId][totalIndex].id; - if (listQuestions[quizId][totalIndex].deleteTimeoutId) { - clearTimeout( - listQuestions[quizId][totalIndex].deleteTimeoutId - ); - } - - removeQuestion(quizId, totalIndex); - - const newTimeoutId = window.setTimeout(() => { - removeQuestionForce(quizId, removedId); - }, 5000); - - updateQuestionsList(quizId, totalIndex, { - ...listQuestions[quizId][totalIndex], - deleteTimeoutId: newTimeoutId, - }); - }} - > - - - + )} + + {question.expanded ? ( + <> + ) : ( + + + } + checkedIcon={} + /> + } + label={""} + sx={{ + color: theme.palette.grey2.main, + ml: "-9px", + mr: 0, + userSelect: "none", + }} + /> + copyQuestion(quizId, totalIndex)} + > + + + { + const removedId = question.id; + if (question.deleteTimeoutId) { + clearTimeout(question.deleteTimeoutId); + } - - + removeQuestion(quizId, totalIndex); + + const newTimeoutId = window.setTimeout(() => { + removeQuestionForce(quizId, removedId); + }, 5000); + + updateQuestionsList(quizId, totalIndex, { + ...question, + deleteTimeoutId: newTimeoutId, + }); + }} + > + + + + )} + + - {isExpanded && ( + {question.expanded && ( - {switchState.length === 0 ? ( + {question.type === "nonselected" ? ( ) : ( @@ -310,7 +415,7 @@ export default function QuestionsPageCard({ totalIndex, draggableProps, isDraggi }} > createQuestion(quizId, totalIndex + 1)} + onClick={() => createQuestion(quizId, "nonselected", totalIndex + 1)} sx={{ display: plusVisible && !isDragging ? "flex" : "none", width: "100%", @@ -326,7 +431,8 @@ export default function QuestionsPageCard({ totalIndex, draggableProps, isDraggi backgroundPosition: "bottom", backgroundRepeat: "repeat-x", backgroundSize: "20px 1px", - backgroundImage: "radial-gradient(circle, #7E2AEA 6px, #F2F3F7 1px)", + backgroundImage: + "radial-gradient(circle, #7E2AEA 6px, #F2F3F7 1px)", }} /> diff --git a/src/pages/Questions/DropDown/DropDown.tsx b/src/pages/Questions/DropDown/DropDown.tsx index 5113ccca..7ab48826 100644 --- a/src/pages/Questions/DropDown/DropDown.tsx +++ b/src/pages/Questions/DropDown/DropDown.tsx @@ -9,6 +9,8 @@ import { EnterIcon } from "../../../assets/icons/questionsPage/enterIcon"; import SwitchDropDown from "./switchDropDown"; import ButtonsOptions from "../ButtonsOptions"; +import type { QuizQuestionSelect } from "../../../model/questionTypes/select"; + interface Props { totalIndex: number; } @@ -19,28 +21,29 @@ export default function DropDown({ totalIndex }: Props) { const { listQuestions } = questionStore(); const theme = useTheme(); const isMobile = useMediaQuery(theme.breakpoints.down(790)); - const variants = listQuestions[quizId][totalIndex].content.variants; + const question = listQuestions[quizId][totalIndex] as QuizQuestionSelect; const SSHC = (data: string) => { setSwitchState(data); }; const addNewAnswer = () => { - const answerNew = variants.slice(); - answerNew.push({ answer: "", hints: "", emoji: "", image: "" }); + const answerNew = question.content.variants.slice(); + answerNew.push({ answer: "", hints: "", extendedText: "" }); - updateQuestionsList(quizId, totalIndex, { - content: { - ...listQuestions[quizId][totalIndex].content, - variants: answerNew, - }, + updateQuestionsList(quizId, totalIndex, { + content: { ...question.content, variants: answerNew }, }); }; return ( <> - - {variants.length === 0 ? ( + + {question.content.variants.length === 0 ? ( ) : ( - + )} - + ); diff --git a/src/pages/Questions/DropDown/settingDropDown.tsx b/src/pages/Questions/DropDown/settingDropDown.tsx index 83062b31..cb6364fb 100644 --- a/src/pages/Questions/DropDown/settingDropDown.tsx +++ b/src/pages/Questions/DropDown/settingDropDown.tsx @@ -1,5 +1,11 @@ import { useParams } from "react-router-dom"; -import { Box, Typography, useMediaQuery, useTheme } from "@mui/material"; +import { + Box, + Typography, + Tooltip, + useMediaQuery, + useTheme, +} from "@mui/material"; import CustomCheckbox from "@ui_kit/CustomCheckbox"; import CustomTextField from "@ui_kit/CustomTextField"; import { useDebouncedCallback } from "use-debounce"; @@ -8,6 +14,8 @@ import { questionStore, updateQuestionsList } from "@root/questions"; import InfoIcon from "../../../assets/icons/InfoIcon"; +import type { QuizQuestionSelect } from "../../../model/questionTypes/select"; + type SettingDropDownProps = { totalIndex: number; }; @@ -21,15 +29,16 @@ export default function SettingDropDown({ totalIndex }: SettingDropDownProps) { const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990)); const isMobile = useMediaQuery(theme.breakpoints.down(790)); + const question = listQuestions[quizId][totalIndex] as QuizQuestionSelect; const debounced = useDebouncedCallback((value) => { - let clonContent = listQuestions[quizId][totalIndex].content; - clonContent.innerName = value; - updateQuestionsList(quizId, totalIndex, { content: clonContent }); + updateQuestionsList(quizId, totalIndex, { + content: { ...question.content, innerName: value }, + }); }, 1000); const debounceAnswer = useDebouncedCallback((value) => { - const clonContent = listQuestions[quizId][totalIndex].content; - clonContent.default = value; - updateQuestionsList(quizId, totalIndex, { content: clonContent }); + updateQuestionsList(quizId, totalIndex, { + content: { ...question.content, default: value }, + }); }, 1000); return ( @@ -38,6 +47,7 @@ export default function SettingDropDown({ totalIndex }: SettingDropDownProps) { sx={{ position: "relative", display: "flex", + gap: "20px", width: "100%", justifyContent: "space-between", flexDirection: isMobile ? "column" : null, @@ -56,23 +66,30 @@ export default function SettingDropDown({ totalIndex }: SettingDropDownProps) { }} > Настройки ответов - updateQuestionsList(quizId, totalIndex, { - content: { - ...listQuestions[quizId][totalIndex].content, - multi: target.checked, - }, + updateQuestionsList(quizId, totalIndex, { + content: { ...question.content, multi: target.checked }, }) } /> - + debounceAnswer(target.value)} /> @@ -99,52 +116,56 @@ export default function SettingDropDown({ totalIndex }: SettingDropDownProps) { pr: isFigmaTablte ? "19px" : "20px", display: "flex", flexDirection: "column", - gap: isMobile ? "13px" : "14px", - width: "100%", + gap: "14px", + width: isMobile ? "auto" : "100%", }} > Настройки вопросов { - updateQuestionsList(quizId, totalIndex, { + updateQuestionsList(quizId, totalIndex, { required: !e.target.checked, }); }} /> - + { - let clonContent = listQuestions[quizId][totalIndex].content; - clonContent.innerNameCheck = e.target.checked; - - if (!e.target.checked) { - clonContent.innerName = ""; - } - - updateQuestionsList(quizId, totalIndex, { - content: clonContent, + checked={question.content.innerNameCheck} + handleChange={({ target }) => { + updateQuestionsList(quizId, totalIndex, { + content: { + ...question.content, + innerNameCheck: target.checked, + innerName: target.checked ? question.content.innerName : "", + }, }); }} /> - + + + + + debounceAnswer(target.value)} /> - {listQuestions[quizId][totalIndex].content.innerNameCheck && ( + {question.content.innerNameCheck && ( debounced(target.value)} /> )} diff --git a/src/pages/Questions/Emoji/Emoji.tsx b/src/pages/Questions/Emoji/Emoji.tsx index b8bba723..1ac17595 100644 --- a/src/pages/Questions/Emoji/Emoji.tsx +++ b/src/pages/Questions/Emoji/Emoji.tsx @@ -14,149 +14,44 @@ import SwitchEmoji from "./switchEmoji"; import { AnswerDraggableList } from "../AnswerDraggableList"; import { EmojiPicker } from "@ui_kit/EmojiPicker"; import { EmojiIcons } from "@icons/EmojiIocns"; +import AddEmoji from "@icons/questionsPage/addEmoji"; +import PlusImage from "@icons/questionsPage/plus"; import { questionStore, updateQuestionsList } from "@root/questions"; -import AddEmoji from "@icons/questionsPage/addEmoji"; -import PlusImage from "@icons/questionsPage/plus"; +import type { QuizQuestionEmoji } from "../../../model/questionTypes/emoji"; interface Props { totalIndex: number; } + export default function Emoji({ totalIndex }: Props) { const [switchState, setSwitchState] = useState("setting"); const [open, setOpen] = useState(false); - const [anchorElement, setAnchorElement] = useState( - null - ); + const [anchorElement, setAnchorElement] = useState(null); const [currentIndex, setCurrentIndex] = useState(0); const { listQuestions } = questionStore(); const quizId = Number(useParams().quizId); const theme = useTheme(); + const question = listQuestions[quizId][totalIndex] as QuizQuestionEmoji; const isMobile = useMediaQuery(theme.breakpoints.down(790)); const isTablet = useMediaQuery(theme.breakpoints.down(1000)); + const SSHC = (data: string) => { setSwitchState(data); }; return ( <> - {/* - - - - - - {!isMobile && ( - - - - - - + - - - )} - - ), - endAdornment: ( - - - - - - - - - - - - ), - }} - sx={{ - "& .MuiInputBase-root": { - padding: "13.5px", - borderRadius: "10px", - background: "#ffffff", - height: "48px", - }, - "& .MuiOutlinedInput-notchedOutline": { - border: "none", - }, - }} - inputProps={{ - sx: { fontSize: "18px", lineHeight: "21px", py: 0 }, - }} - /> - - {isMobile && ( - - - - - + - - - )} - - */} ( <> {!isTablet && ( - + { setAnchorElement(currentTarget); @@ -172,19 +67,29 @@ export default function Emoji({ totalIndex }: Props) { gap: "5px", }} > - {variant.emoji ? ( + {variant.extendedText ? ( - {variant.emoji} - + + {variant.extendedText} + + @@ -220,7 +125,7 @@ export default function Emoji({ totalIndex }: Props) { height: "40px", }} /> - {variant.emoji ? ( + {variant.extendedText ? ( - {variant.emoji} + {variant.extendedText} ) : ( { setOpen(false); - const cloneVariants = [ - ...listQuestions[quizId][totalIndex].content.variants, - ]; + const cloneVariants = [...question.content.variants]; cloneVariants[currentIndex] = { ...cloneVariants[currentIndex], - emoji: native, + extendedText: native, }; - updateQuestionsList(quizId, totalIndex, { - content: { - ...listQuestions[quizId][totalIndex].content, - variants: cloneVariants, - }, + updateQuestionsList(quizId, totalIndex, { + content: { ...question.content, variants: cloneVariants }, }); }} /> - + { - const answerNew = - listQuestions[quizId][totalIndex].content.variants.slice(); - answerNew.push({ answer: "", hints: "", emoji: "", image: "" }); + const answerNew = question.content.variants.slice(); + answerNew.push({ answer: "", hints: "", extendedText: "" }); - updateQuestionsList(quizId, totalIndex, { - content: { - ...listQuestions[quizId][totalIndex].content, - variants: answerNew, - }, + updateQuestionsList(quizId, totalIndex, { + content: { ...question.content, variants: answerNew }, }); }} > @@ -341,11 +237,7 @@ export default function Emoji({ totalIndex }: Props) { )} - + ); diff --git a/src/pages/Questions/Emoji/settingEmoji.tsx b/src/pages/Questions/Emoji/settingEmoji.tsx index 6732d8b4..5a14bc01 100644 --- a/src/pages/Questions/Emoji/settingEmoji.tsx +++ b/src/pages/Questions/Emoji/settingEmoji.tsx @@ -6,6 +6,8 @@ import CustomTextField from "@ui_kit/CustomTextField"; import InfoIcon from "../../../assets/icons/InfoIcon"; import { questionStore, updateQuestionsList } from "@root/questions"; +import type { QuizQuestionEmoji } from "../../../model/questionTypes/emoji"; + type SettingEmojiProps = { totalIndex: number; }; @@ -19,10 +21,11 @@ export default function SettingEmoji({ totalIndex }: SettingEmojiProps) { const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990)); const isMobile = useMediaQuery(theme.breakpoints.down(790)); + const question = listQuestions[quizId][totalIndex] as QuizQuestionEmoji; const debounced = useDebouncedCallback((value) => { - let clonContent = listQuestions[quizId][totalIndex].content; - clonContent.innerName = value; - updateQuestionsList(quizId, totalIndex, { content: clonContent }); + updateQuestionsList(quizId, totalIndex, { + content: { ...question.content, innerName: value }, + }); }, 1000); return ( @@ -51,21 +54,21 @@ export default function SettingEmoji({ totalIndex }: SettingEmojiProps) { { - let clonContent = listQuestions[quizId][totalIndex].content; - clonContent.multi = e.target.checked; - updateQuestionsList(quizId, totalIndex, { content: clonContent }); + checked={question.content.multi} + handleChange={({ target }) => { + updateQuestionsList(quizId, totalIndex, { + content: { ...question.content, multi: target.checked }, + }); }} /> { - let clonContent = listQuestions[quizId][totalIndex].content; - clonContent.own = e.target.checked; - updateQuestionsList(quizId, totalIndex, { content: clonContent }); + checked={question.content.own} + handleChange={({ target }) => { + updateQuestionsList(quizId, totalIndex, { + content: { ...question.content, own: target.checked }, + }); }} /> @@ -77,8 +80,8 @@ export default function SettingEmoji({ totalIndex }: SettingEmojiProps) { pr: isFigmaTablte ? "30px" : "20px", display: "flex", flexDirection: "column", - gap: isMobile ? "13px" : "14px", - width: "100%", + gap: "14px", + width: isMobile ? "auto" : "100%", }} > @@ -87,27 +90,36 @@ export default function SettingEmoji({ totalIndex }: SettingEmojiProps) { { - updateQuestionsList(quizId, totalIndex, { + updateQuestionsList(quizId, totalIndex, { required: !e.target.checked, }); }} /> - + { - let clonContent = listQuestions[quizId][totalIndex].content; - clonContent.innerNameCheck = e.target.checked; - - if (!e.target.checked) { - clonContent.innerName = ""; - } - - updateQuestionsList(quizId, totalIndex, { content: clonContent }); + checked={question.content.innerNameCheck} + handleChange={({ target }) => { + updateQuestionsList(quizId, totalIndex, { + content: { + ...question.content, + innerNameCheck: target.checked, + innerName: target.checked ? question.content.innerName : "", + }, + }); }} /> @@ -116,10 +128,10 @@ export default function SettingEmoji({ totalIndex }: SettingEmojiProps) { - {listQuestions[quizId][totalIndex].content.innerNameCheck && ( + {question.content.innerNameCheck && ( debounced(target.value)} /> )} diff --git a/src/pages/Questions/Form/FormDraggableList/ChooseAnswerModal.tsx b/src/pages/Questions/Form/FormDraggableList/ChooseAnswerModal.tsx new file mode 100644 index 00000000..38a15a48 --- /dev/null +++ b/src/pages/Questions/Form/FormDraggableList/ChooseAnswerModal.tsx @@ -0,0 +1,148 @@ +import { useState } from "react"; +import { useParams } from "react-router-dom"; +import { + Box, + Typography, + Popper, + Grow, + Paper, + MenuList, + MenuItem, + ClickAwayListener, + Modal, + Button, + useTheme, +} from "@mui/material"; + +import { + questionStore, + updateQuestionsList, + removeQuestionForce, + createQuestion, +} from "@root/questions"; +import { BUTTON_TYPE_QUESTIONS } from "../../TypeQuestions"; + +import type { RefObject } from "react"; +import type { + QuizQuestionType, + QuizQuestionBase, +} from "../../../../model/questionTypes/shared"; + +type ChooseAnswerModalProps = { + open: boolean; + onClose: () => void; + anchorRef: RefObject; + totalIndex: number; + switchState: string; +}; + +export const ChooseAnswerModal = ({ + open, + onClose, + anchorRef, + totalIndex, + switchState, +}: ChooseAnswerModalProps) => { + const [openModal, setOpenModal] = useState(false); + const [selectedValue, setSelectedValue] = useState("text"); + const quizId = Number(useParams().quizId); + const { listQuestions } = questionStore(); + const theme = useTheme(); + + return ( + <> + + {({ TransitionProps }) => ( + + + + + {BUTTON_TYPE_QUESTIONS.map(({ icon, title, value }) => ( + { + onClose(); + setOpenModal(true); + setSelectedValue(value); + }, + })} + > + {icon} + + {title} + + + ))} + + + + + )} + + setOpenModal(false)}> + + + Все настройки, кроме заголовка вопроса будут сброшены + + + + + + + + + ); +}; diff --git a/src/pages/Questions/Form/FormDraggableList/FormDraggableListItem.tsx b/src/pages/Questions/Form/FormDraggableList/FormDraggableListItem.tsx new file mode 100644 index 00000000..b625b316 --- /dev/null +++ b/src/pages/Questions/Form/FormDraggableList/FormDraggableListItem.tsx @@ -0,0 +1,92 @@ +import { memo } from "react"; +import { useParams } from "react-router-dom"; +import { Draggable } from "react-beautiful-dnd"; +import { Box, ListItem, Typography, useTheme } from "@mui/material"; + +import QuestionsPageCard from "./QuestionPageCard"; + +import { updateQuestionsList } from "@root/questions"; + +import { QuizQuestionBase } from "../../../../model/questionTypes/shared"; + +type FormDraggableListItemProps = { + index: number; + isDragging: boolean; + questionData: QuizQuestionBase; +}; + +export default memo( + ({ index, isDragging, questionData }: FormDraggableListItemProps) => { + const quizId = Number(useParams().quizId); + const theme = useTheme(); + console.log("Мой индекс " + index); + console.log(questionData); + + return ( + + {(provided) => ( + + {questionData.deleted ? ( + + + Вопрос удалён. + + { + updateQuestionsList(quizId, index, { + ...questionData, + deleted: false, + }); + }} + sx={{ + cursor: "pointer", + fontSize: "16px", + textDecoration: "underline", + color: theme.palette.brightPurple.main, + textDecorationColor: theme.palette.brightPurple.main, + }} + > + Восстановить? + + + ) : ( + + + + )} + + )} + + ); + } +); diff --git a/src/pages/Questions/Form/FormDraggableList/QuestionPageCard.tsx b/src/pages/Questions/Form/FormDraggableList/QuestionPageCard.tsx new file mode 100644 index 00000000..0c5c8476 --- /dev/null +++ b/src/pages/Questions/Form/FormDraggableList/QuestionPageCard.tsx @@ -0,0 +1,168 @@ +import { useState, useRef, useEffect } from "react"; +import { useParams } from "react-router-dom"; +import { Box, InputAdornment, Paper } from "@mui/material"; +import { useDebouncedCallback } from "use-debounce"; + +import CustomTextField from "@ui_kit/CustomTextField"; +import { ChooseAnswerModal } from "./ChooseAnswerModal"; +import FormTypeQuestions from "../FormTypeQuestions"; +import SwitchQuestionsPage from "../../SwitchQuestionsPage"; + +import { questionStore, updateQuestionsList } from "@root/questions"; + +import { PointsIcon } from "@icons/questionsPage/PointsIcon"; +import Answer from "@icons/questionsPage/answer"; +import OptionsPict from "@icons/questionsPage/options_pict"; +import OptionsAndPict from "@icons/questionsPage/options_and_pict"; +import Emoji from "@icons/questionsPage/emoji"; +import Input from "@icons/questionsPage/input"; +import DropDown from "@icons/questionsPage/drop_down"; +import Date from "@icons/questionsPage/date"; +import Slider from "@icons/questionsPage/slider"; +import Download from "@icons/questionsPage/download"; +import Page from "@icons/questionsPage/page"; +import RatingIcon from "@icons/questionsPage/rating"; +import AnswerGroup from "@icons/questionsPage/answerGroup"; + +import type { DraggableProvidedDragHandleProps } from "react-beautiful-dnd"; +import type { QuizQuestionBase } from "../../../../model/questionTypes/shared"; + +interface Props { + totalIndex: number; + draggableProps: DraggableProvidedDragHandleProps | null | undefined; + isDragging: boolean; +} + +const IconAndrom = (switchState: string) => { + switch (switchState) { + case "variant": + return ; + case "images": + return ( + + ); + case "varimg": + return ( + + ); + case "emoji": + return ; + case "text": + return ; + case "select": + return ( + + ); + case "date": + return ; + case "number": + return ; + case "file": + return ( + + ); + case "page": + return ; + case "rating": + return ( + + ); + default: + return ( + + ); + } +}; +export default function QuestionsPageCard({ + totalIndex, + draggableProps, + isDragging, +}: Props) { + const [open, setOpen] = useState(false); + const quizId = Number(useParams().quizId); + const { listQuestions } = questionStore(); + const question = listQuestions[quizId][totalIndex]; + const anchorRef = useRef(null); + const debounced = useDebouncedCallback((title) => { + updateQuestionsList(quizId, totalIndex, { title }); + }, 1000); + + useEffect(() => { + if (question.deleteTimeoutId) { + clearTimeout(question.deleteTimeoutId); + } + }, [question]); + + return ( + <> + + + debounced(target.value)} + sx={{ margin: "20px", width: "auto" }} + InputProps={{ + startAdornment: ( + + setOpen((isOpened) => !isOpened)} + > + {IconAndrom(question.type)} + + setOpen(false)} + anchorRef={anchorRef} + totalIndex={totalIndex} + switchState={question.type} + /> + + ), + endAdornment: ( + <> + + + + + ), + }} + /> + {question.type === "nonselected" ? ( + + ) : ( + + )} + + + + ); +} diff --git a/src/pages/Questions/Form/FormDraggableList/helper.ts b/src/pages/Questions/Form/FormDraggableList/helper.ts new file mode 100644 index 00000000..203ee13b --- /dev/null +++ b/src/pages/Questions/Form/FormDraggableList/helper.ts @@ -0,0 +1,11 @@ +export const reorder = ( + list: T[], + startIndex: number, + endIndex: number +): T[] => { + const result = Array.from(list); + const [removed] = result.splice(startIndex, 1); + result.splice(endIndex, 0, removed); + + return result; +}; diff --git a/src/pages/Questions/Form/FormDraggableList/index.tsx b/src/pages/Questions/Form/FormDraggableList/index.tsx new file mode 100644 index 00000000..894a43ad --- /dev/null +++ b/src/pages/Questions/Form/FormDraggableList/index.tsx @@ -0,0 +1,48 @@ +import { useParams } from "react-router-dom"; +import { Box } from "@mui/material"; +import { DragDropContext, Droppable } from "react-beautiful-dnd"; + +import FormDraggableListItem from "./FormDraggableListItem"; + +import { questionStore, updateQuestionsListDragAndDrop } from "@root/questions"; + +import { reorder } from "./helper"; + +import type { DropResult } from "react-beautiful-dnd"; + +export const FormDraggableList = () => { + const quizId = Number(useParams().quizId); + const { listQuestions } = questionStore(); + + const onDragEnd = ({ destination, source }: DropResult) => { + if (destination) { + const newItems = reorder( + listQuestions[quizId], + source.index, + destination.index + ); + + updateQuestionsListDragAndDrop(quizId, newItems); + } + }; + + return ( + + + {(provided, snapshot) => ( + + {listQuestions[quizId]?.map((question, index) => ( + + ))} + {provided.placeholder} + + )} + + + ); +}; diff --git a/src/pages/Questions/Form/FormQuestionsPage.tsx b/src/pages/Questions/Form/FormQuestionsPage.tsx new file mode 100644 index 00000000..e119cd53 --- /dev/null +++ b/src/pages/Questions/Form/FormQuestionsPage.tsx @@ -0,0 +1,142 @@ +import { Box, Button, Typography, useTheme } from "@mui/material"; +import { useParams } from "react-router-dom"; + +import { FormDraggableList } from "./FormDraggableList"; + +import { + questionStore, + createQuestion, + updateQuestionsList, +} from "@root/questions"; +import { quizStore } from "@root/quizes"; + +import ArrowLeft from "../../../assets/icons/questionsPage/arrowLeft"; +import AddAnswer from "../../../assets/icons/questionsPage/addAnswer"; + +import type { + AnyQuizQuestion, + QuizQuestionBase, +} from "../../../model/questionTypes/shared"; +import QuizPreview from "@ui_kit/QuizPreview/QuizPreview"; +import { createPortal } from "react-dom"; + +export default function FormQuestionsPage() { + const { listQuizes, updateQuizesList } = quizStore(); + const quizId = Number(useParams().quizId); + const { listQuestions } = questionStore(); + const handleNext = () => { + updateQuizesList(quizId, { step: listQuizes[quizId].step + 1 }); + }; + + const collapseEverything = () => { + listQuestions[quizId].forEach((item, index) => { + updateQuestionsList(quizId, index, { + ...item, + expanded: false, + }); + }); + }; + + const theme = useTheme(); + + return ( + <> + + Заголовок анкеты + + + + + { + createQuestion(quizId); + updateQuestionsList( + quizId, + listQuestions[quizId].length - 1 || 0, + { + expanded: true, + } + ); + }} + > + + + Добавить еще один вопрос + + + + + + + {createPortal(, document.body)} + + + ); +} diff --git a/src/pages/Questions/Form/FormTypeQuestions.tsx b/src/pages/Questions/Form/FormTypeQuestions.tsx new file mode 100644 index 00000000..f7da06d3 --- /dev/null +++ b/src/pages/Questions/Form/FormTypeQuestions.tsx @@ -0,0 +1,143 @@ +import { useState } from "react"; +import { useParams } from "react-router-dom"; +import { Box } from "@mui/material"; + +import QuestionsMiniButton from "@ui_kit/QuestionsMiniButton"; +import ButtonsOptions from "../ButtonsOptions"; +import SwitchAnswerOptions from "../answerOptions/switchAnswerOptions"; + +import Answer from "../../../assets/icons/questionsPage/answer"; +import OptionsPict from "../../../assets/icons/questionsPage/options_pict"; +import OptionsAndPict from "../../../assets/icons/questionsPage/options_and_pict"; +import Emoji from "../../../assets/icons/questionsPage/emoji"; +import Input from "../../../assets/icons/questionsPage/input"; +import DropDown from "../../../assets/icons/questionsPage/drop_down"; +import Date from "../../../assets/icons/questionsPage/date"; +import Slider from "../../../assets/icons/questionsPage/slider"; +import Download from "../../../assets/icons/questionsPage/download"; +import Page from "../../../assets/icons/questionsPage/page"; +import RatingIcon from "../../../assets/icons/questionsPage/rating"; + +import { + questionStore, + updateQuestionsList, + createQuestion, + removeQuestionForce, +} from "@root/questions"; + +import type { + QuizQuestionType, + QuizQuestionBase, +} from "../../../model/questionTypes/shared"; + +interface Props { + totalIndex: number; +} + +type ButtonTypeQuestion = { + icon: JSX.Element; + title: string; + value: QuizQuestionType; +}; + +export const BUTTON_TYPE_QUESTIONS: ButtonTypeQuestion[] = [ + { + icon: , + title: "Варианты ответов", + value: "variant", + }, + { + icon: , + title: "Варианты с картинками", + value: "images", + }, + { + icon: , + title: "Варианты и картинка", + value: "varimg", + }, + { + icon: , + title: "Эмоджи", + value: "emoji", + }, + { + icon: , + title: "Своё поле для ввода", + value: "text", + }, + { + icon: , + title: "Выпадающий список", + value: "select", + }, + { + icon: , + title: "Дата", + value: "date", + }, + { + icon: , + title: "Ползунок", + value: "number", + }, + { + icon: , + title: "Загрузка файла", + value: "file", + }, + { + icon: , + title: "Страница", + value: "page", + }, + { + icon: , + title: "Рейтинг", + value: "rating", + }, +]; + +export default function FormTypeQuestions({ totalIndex }: Props) { + const [switchState, setSwitchState] = useState(""); + const quizId = Number(useParams().quizId); + const { listQuestions } = questionStore(); + const question = listQuestions[quizId][totalIndex] as QuizQuestionBase; + + return ( + + + {BUTTON_TYPE_QUESTIONS.map(({ icon, title, value }) => ( + { + const clonedQuestion = { ...question }; + + removeQuestionForce(quizId, clonedQuestion.id); + createQuestion(quizId, value, totalIndex); + updateQuestionsList(quizId, totalIndex, { + expanded: clonedQuestion.expanded, + type: value, + }); + }} + icon={icon} + text={title} + /> + ))} + + + + + ); +} diff --git a/src/pages/Questions/OptionsAndPicture/OptionsAndPicture.tsx b/src/pages/Questions/OptionsAndPicture/OptionsAndPicture.tsx index aea79644..e9ffe26e 100644 --- a/src/pages/Questions/OptionsAndPicture/OptionsAndPicture.tsx +++ b/src/pages/Questions/OptionsAndPicture/OptionsAndPicture.tsx @@ -1,16 +1,16 @@ import { useState } from "react"; import { - Box, - Link, - Typography, - useTheme, - useMediaQuery, - InputAdornment, - IconButton, - Button, - Popover, - TextareaAutosize, - TextField, + Box, + Link, + Typography, + useTheme, + useMediaQuery, + InputAdornment, + IconButton, + Button, + Popover, + TextareaAutosize, + TextField, } from "@mui/material"; import { useParams } from "react-router-dom"; @@ -31,403 +31,427 @@ import { MessageIcon } from "@icons/messagIcon"; import { DeleteIcon } from "@icons/questionsPage/deleteIcon"; import PlusImage from "@icons/questionsPage/plus"; +import type { QuizQuestionVarImg } from "../../../model/questionTypes/varimg"; +import { produce } from "immer"; + interface Props { - totalIndex: number; + totalIndex: number; } export default function OptionsAndPicture({ totalIndex }: Props) { - const [open, setOpen] = useState(false); - const [opened, setOpened] = useState(false); - const [switchState, setSwitchState] = useState("setting"); - const [currentIndex, setCurrentIndex] = useState(0); - const theme = useTheme(); - const isTablet = useMediaQuery(theme.breakpoints.down(1000)); - const isMobile = useMediaQuery(theme.breakpoints.down(790)); - const quizId = Number(useParams().quizId); - const { listQuestions } = questionStore(); + const [open, setOpen] = useState(false); + const [opened, setOpened] = useState(false); + const [switchState, setSwitchState] = useState("setting"); + const [currentIndex, setCurrentIndex] = useState(0); + const quizId = Number(useParams().quizId); + const { listQuestions } = questionStore(); + const theme = useTheme(); + const isTablet = useMediaQuery(theme.breakpoints.down(1000)); + const isMobile = useMediaQuery(theme.breakpoints.down(790)); + const question = listQuestions[quizId][totalIndex] as QuizQuestionVarImg; - const SSHC = (data: string) => { - setSwitchState(data); - }; + const SSHC = (data: string) => { + setSwitchState(data); + }; - const uploadImage = (files: FileList | null) => { - if (files?.length) { - const [file] = Array.from(files); + const uploadImage = (files: FileList | null) => { + if (files?.length) { + const [file] = Array.from(files); - const clonContent = { ...listQuestions[quizId][totalIndex].content }; - clonContent.variants[currentIndex].image = URL.createObjectURL(file); - updateQuestionsList(quizId, totalIndex, { content: clonContent }); + const clonedContent = { ...question.content }; + clonedContent.variants[currentIndex].extendedText = + URL.createObjectURL(file); + updateQuestionsList(quizId, totalIndex, { + content: clonedContent, + }); - setOpen(false); - setOpened(true); - } - }; + setOpen(false); + setOpened(true); + } + }; - const addNewAnswer = () => { - const answerNew = - listQuestions[quizId][totalIndex].content.variants.slice(); - answerNew.push({ answer: "", hints: "", emoji: "", image: "" }); - - updateQuestionsList(quizId, totalIndex, { - content: { - ...listQuestions[quizId][totalIndex].content, - variants: answerNew, - }, - }); - }; - - return ( - <> - - ( - <> - {!isMobile && ( + return ( + <> + + ( + <> + {!isMobile && ( + { + setCurrentIndex(index); + setOpen(true); + }} + > + {variant.extendedText ? ( + + + + + + + ) : ( + + )} + + )} + + )} + additionalMobile={(variant, index) => ( + <> + {isMobile && ( + { + setCurrentIndex(index); + setOpen(true); + }} + sx={{ + overflow: "hidden", + display: "flex", + alignItems: "center", + m: "8px", + position: "relative", + borderRadius: "3px", + }} + > + + {variant.extendedText ? ( + + + + ) : ( + + )} + + + + + + + )} + + )} + /> + setOpen(false)} + imgHC={uploadImage} + /> + setOpened(false)} + picture={question.content.variants[currentIndex]?.extendedText} + onCropPress={url => { + const content = produce(question.content, draft => { + draft.variants[currentIndex].extendedText = url; + }); + updateQuestionsList(quizId, totalIndex, { + content, + }); + }} + /> { - setCurrentIndex(index); - setOpen(true); - }} + sx={{ + width: "100%", + border: "1px solid #9A9AAF", + borderRadius: "8px", + display: isTablet ? "block" : "none", + }} > - {variant.image ? ( - + + + + {!isMobile && ( + + + + + + + + + + )} + + ), + endAdornment: ( + + + + + + + + + + + + ), + }} + sx={{ + "& .MuiInputBase-root": { + padding: "13.5px", + borderRadius: "10px", + background: "#ffffff", + height: "48px", + }, + "& .MuiOutlinedInput-notchedOutline": { + border: "none", + }, + }} + inputProps={{ + sx: { fontSize: "18px", lineHeight: "21px", py: 0 }, + }} + /> + + {isMobile && ( + + + + + + + + + + + + + )} + + - - - - - - ) : ( - - )} - - )} - - )} - additionalMobile={(variant, index) => ( - <> - {isMobile && ( - { - setCurrentIndex(index); - setOpen(true); - }} - sx={{ - overflow: "hidden", - display: "flex", - alignItems: "center", - m: "8px", - position: "relative", - borderRadius: "3px", - }} + marginBottom: "17px", + }} > - - {variant.image ? ( - { + const clonedContent = { ...question.content }; + clonedContent.variants.push({ + answer: "", + hints: "", + extendedText: "", + }); + updateQuestionsList(quizId, totalIndex, { + content: clonedContent, + }); }} - > - - - ) : ( - - )} - - - + - - - )} - - )} - /> - setOpen(false)} - imgHC={uploadImage} - /> - setOpened(false)} - picture={ - listQuestions[quizId][totalIndex].content.variants[currentIndex] - .image - } - /> - - - - - - {!isMobile && ( - - - - - - + - - - )} - - ), - endAdornment: ( - - - - - - - - - - - - ), - }} - sx={{ - "& .MuiInputBase-root": { - padding: "13.5px", - borderRadius: "10px", - background: "#ffffff", - height: "48px", - }, - "& .MuiOutlinedInput-notchedOutline": { - border: "none", - }, - }} - inputProps={{ - sx: { fontSize: "18px", lineHeight: "21px", py: 0 }, - }} - /> - - {isMobile && ( - - - - - + - + Добавьте ответ + + {isMobile ? null : ( + <> + + или нажмите Enter + + + + )} + - )} - - - { - const clonContent = listQuestions[quizId][totalIndex].content; - clonContent.variants.push({ - answer: "", - hints: "", - emoji: "", - image: "", - }); - - updateQuestionsList(quizId, totalIndex, { content: clonContent }); - }} - > - Добавьте ответ - - {isMobile ? null : ( - <> - - или нажмите Enter - - - - )} - - - - - - ); + + + + ); } diff --git a/src/pages/Questions/OptionsAndPicture/SettingOptionsAndPict.tsx b/src/pages/Questions/OptionsAndPicture/SettingOptionsAndPict.tsx index 74fe9b1a..b6b12cfd 100644 --- a/src/pages/Questions/OptionsAndPicture/SettingOptionsAndPict.tsx +++ b/src/pages/Questions/OptionsAndPicture/SettingOptionsAndPict.tsx @@ -7,6 +7,8 @@ import InfoIcon from "../../../assets/icons/InfoIcon"; import { questionStore, updateQuestionsList } from "@root/questions"; +import type { QuizQuestionVarImg } from "../../../model/questionTypes/varimg"; + type SettingOptionsAndPictProps = { totalIndex: number; }; @@ -19,19 +21,15 @@ export default function SettingOptionsAndPict({ totalIndex }: SettingOptionsAndP const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990)); const isMobile = useMediaQuery(theme.breakpoints.down(680)); + const question = listQuestions[quizId][totalIndex] as QuizQuestionVarImg; const debounced = useDebouncedCallback((replText) => { - updateQuestionsList(quizId, totalIndex, { - content: { - ...listQuestions[quizId][totalIndex].content, - replText, - }, + updateQuestionsList(quizId, totalIndex, { + content: { ...question.content, replText }, }); }, 1000); const debounceDescription = useDebouncedCallback((value) => { - let clonContent = listQuestions[quizId][totalIndex].content; - clonContent.innerName = value; - updateQuestionsList(quizId, totalIndex, { - content: clonContent, + updateQuestionsList(quizId, totalIndex, { + content: { ...question.content, innerName: value }, }); }, 1000); @@ -46,7 +44,7 @@ export default function SettingOptionsAndPict({ totalIndex }: SettingOptionsAndP > { - updateQuestionsList(quizId, totalIndex, { - content: { - ...listQuestions[quizId][totalIndex].content, - own: target.checked, - }, + updateQuestionsList(quizId, totalIndex, { + content: { ...question.content, own: target.checked }, }); }} /> @@ -88,9 +83,13 @@ export default function SettingOptionsAndPict({ totalIndex }: SettingOptionsAndP Текст-заглушка на картинке debounced(target.value)} /> @@ -105,7 +104,7 @@ export default function SettingOptionsAndPict({ totalIndex }: SettingOptionsAndP display: "flex", flexDirection: "column", gap: isMobile ? "13px" : "14px", - width: "100%", + width: isMobile ? "auto" : "100%", }} > { - updateQuestionsList(quizId, totalIndex, { - content: { - ...listQuestions[quizId][totalIndex].content, - required: target.checked, - }, + updateQuestionsList(quizId, totalIndex, { + content: { ...question.content, required: target.checked }, }); }} /> { - updateQuestionsList(quizId, totalIndex, { + updateQuestionsList(quizId, totalIndex, { content: { - ...listQuestions[quizId][totalIndex].content, + ...question.content, innerNameCheck: target.checked, innerName: "", }, @@ -147,10 +146,10 @@ export default function SettingOptionsAndPict({ totalIndex }: SettingOptionsAndP - {listQuestions[quizId][totalIndex].content.innerNameCheck && ( + {question.content.innerNameCheck && ( debounceDescription(target.value)} /> )} @@ -164,7 +163,7 @@ export default function SettingOptionsAndPict({ totalIndex }: SettingOptionsAndP debounced(target.value)} /> diff --git a/src/pages/Questions/OptionsPicture/OptionsPicture.tsx b/src/pages/Questions/OptionsPicture/OptionsPicture.tsx index 2d824220..1be84281 100644 --- a/src/pages/Questions/OptionsPicture/OptionsPicture.tsx +++ b/src/pages/Questions/OptionsPicture/OptionsPicture.tsx @@ -1,12 +1,12 @@ import { useState } from "react"; import { useParams } from "react-router-dom"; import { - Box, - Link, - Typography, - Button, - useTheme, - useMediaQuery, + Box, + Link, + Typography, + Button, + useTheme, + useMediaQuery } from "@mui/material"; import ButtonsOptions from "../ButtonsOptions"; @@ -21,229 +21,231 @@ import Image from "../../../assets/icons/questionsPage/image"; import SwitchAnswerOptionsPict from "./switchOptionsPict"; import PlusImage from "@icons/questionsPage/plus"; +import type { QuizQuestionImages } from "../../../model/questionTypes/images"; +import { produce } from "immer"; + interface Props { - totalIndex: number; + totalIndex: number; } export default function OptionsPicture({ totalIndex }: Props) { - const [open, setOpen] = useState(false); - const [opened, setOpened] = useState(false); - const [currentIndex, setCurrentIndex] = useState(0); - const theme = useTheme(); - const isMobile = useMediaQuery(theme.breakpoints.down(790)); - const quizId = Number(useParams().quizId); - const [switchState, setSwitchState] = useState("setting"); - const { listQuestions } = questionStore(); + const [open, setOpen] = useState(false); + const [opened, setOpened] = useState(false); + const [currentIndex, setCurrentIndex] = useState(0); + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down(790)); + const isTablet = useMediaQuery(theme.breakpoints.down(790)); + const quizId = Number(useParams().quizId); + const [switchState, setSwitchState] = useState("setting"); + const { listQuestions } = questionStore(); + const question = listQuestions[quizId][totalIndex] as QuizQuestionImages; - const SSHC = (data: string) => { - setSwitchState(data); - }; + const SSHC = (data: string) => { + setSwitchState(data); + }; - const uploadImage = (files: FileList | null) => { - if (files?.length) { - const [file] = Array.from(files); + const uploadImage = (files: FileList | null) => { + if (files?.length) { + const [file] = Array.from(files); - const clonContent = { ...listQuestions[quizId][totalIndex].content }; - clonContent.variants[currentIndex].image = URL.createObjectURL(file); - updateQuestionsList(quizId, totalIndex, { content: clonContent }); + const clonedContent = { ...question.content }; + clonedContent.variants[currentIndex].extendedText = + URL.createObjectURL(file); + updateQuestionsList(quizId, totalIndex, { + content: clonedContent, + }); - setOpen(false); - setOpened(true); - } - }; + setOpen(false); + setOpened(true); + } + }; - const addNewAnswer = () => { - const answerNew = - listQuestions[quizId][totalIndex].content.variants.slice(); - answerNew.push({ answer: "", hints: "", emoji: "", image: "" }); + const addNewAnswer = () => { + const answerNew = question.content.variants.slice(); + answerNew.push({ answer: "", hints: "", extendedText: "" }); - updateQuestionsList(quizId, totalIndex, { - content: { - ...listQuestions[quizId][totalIndex].content, - variants: answerNew, - }, - }); - }; + updateQuestionsList(quizId, totalIndex, { + content: { ...question.content, variants: answerNew }, + }); + }; - return ( - <> - - ( - <> - {!isMobile && ( - { - setCurrentIndex(index); - setOpen(true); - }} - > - {variant.image ? ( - - - - - - - ) : ( - - )} - - )} - - )} - additionalMobile={(variant, index) => ( - <> - {isMobile && ( - { - setCurrentIndex(index); - setOpen(true); - }} - sx={{ - overflow: "hidden", - display: "flex", - alignItems: "center", - m: "8px", - position: "relative", - borderRadius: "3px", - }} - > - - {variant.image ? ( - - - - ) : ( - + return ( + <> + + ( + <> + {!isMobile && ( + { + setCurrentIndex(index); + setOpen(true); + }} + > + {variant.extendedText ? ( + + + + + + + ) : ( + + )} + + )} + )} - - ( + <> + {isMobile && ( + { + setCurrentIndex(index); + setOpen(true); + }} + sx={{ + overflow: "hidden", + display: "flex", + alignItems: "center", + m: "8px", + position: "relative", + borderRadius: "3px", + }} + > + + {variant.extendedText ? ( + + + + ) : ( + + )} + + + + + + + )} + + )} + /> + setOpen(false)} + imgHC={uploadImage} + /> + setOpened(false)} + picture={question.content.variants[currentIndex]?.extendedText} + onCropPress={url => { + const content = produce(question.content, draft => { + draft.variants[currentIndex].extendedText = url; + }); + updateQuestionsList(quizId, totalIndex, { + content, + }); }} - > - + - + /> + + + Добавьте ответ + + {isMobile ? null : ( + <> + + или нажмите Enter + + + + )} - )} - - )} - /> - setOpen(false)} - imgHC={uploadImage} - /> - setOpened(false)} - picture={ - listQuestions[quizId][totalIndex].content.variants[currentIndex] - .image - } - /> - - - Добавьте ответ - - {isMobile ? null : ( - <> - - или нажмите Enter - - - - )} - - - - - - ); + + + + + ); } diff --git a/src/pages/Questions/OptionsPicture/settingOpytionsPict.tsx b/src/pages/Questions/OptionsPicture/settingOpytionsPict.tsx index 3463c4e8..8ecf2380 100644 --- a/src/pages/Questions/OptionsPicture/settingOpytionsPict.tsx +++ b/src/pages/Questions/OptionsPicture/settingOpytionsPict.tsx @@ -1,6 +1,13 @@ import { useEffect } from "react"; import { useParams } from "react-router-dom"; -import { Box, Button, Typography, Tooltip, useMediaQuery, useTheme } from "@mui/material"; +import { + Box, + Button, + Typography, + Tooltip, + useMediaQuery, + useTheme, +} from "@mui/material"; import CustomCheckbox from "@ui_kit/CustomCheckbox"; import CustomTextField from "@ui_kit/CustomTextField"; import { useDebouncedCallback } from "use-debounce"; @@ -14,8 +21,11 @@ import ProportionsIcon11 from "../../../assets/icons/questionsPage/ProportionsIc import ProportionsIcon21 from "../../../assets/icons/questionsPage/ProportionsIcon21"; import ProportionsIcon12 from "../../../assets/icons/questionsPage/ProportionsIcon12"; +import type { QuizQuestionImages } from "../../../model/questionTypes/images"; + interface Props { - Icon: React.ElementType; + Icon: (props: { color: string }) => JSX.Element; + // Icon: React.ElementType; isActive?: boolean; onClick: () => void; } @@ -24,7 +34,14 @@ type SettingOpytionsPictProps = { totalIndex: number; }; -const PROPORTIONS = [ +type Proportion = "1:1" | "2:1" | "1:2"; + +type ProportionItem = { + value: Proportion; + icon: (props: { color: string }) => JSX.Element; +}; + +const PROPORTIONS: ProportionItem[] = [ { value: "1:1", icon: ProportionsIcon11 }, { value: "2:1", icon: ProportionsIcon21 }, { value: "1:2", icon: ProportionsIcon12 }, @@ -37,13 +54,23 @@ export function SelectIconButton({ Icon, isActive = false, onClick }: Props) { + {createPortal(, document.body)} ); } diff --git a/src/pages/Questions/RatingOptions/RatingOptions.tsx b/src/pages/Questions/RatingOptions/RatingOptions.tsx index c36cffa3..a447d84c 100644 --- a/src/pages/Questions/RatingOptions/RatingOptions.tsx +++ b/src/pages/Questions/RatingOptions/RatingOptions.tsx @@ -20,12 +20,14 @@ import LightbulbIcon from "../../../assets/icons/questionsPage/lightbulbIcon"; import HashtagIcon from "../../../assets/icons/questionsPage/hashtagIcon"; import StarIconMini from "../../../assets/icons/questionsPage/StarIconMini"; +import type { QuizQuestionRating } from "../../../model/questionTypes/rating"; + interface Props { totalIndex: number; } export type ButtonRatingFrom = { - name: string; + name: "star" | "trophie" | "flag" | "heart" | "like" | "bubble" | "hashtag"; icon: JSX.Element; }; @@ -39,32 +41,29 @@ export default function RatingOptions({ totalIndex }: Props) { const { listQuestions } = questionStore(); const theme = useTheme(); const isMobile = useMediaQuery(theme.breakpoints.down(790)); + const question = listQuestions[quizId][totalIndex] as QuizQuestionRating; const negativeRef = useRef(null); const positiveRef = useRef(null); const debounceNegativeDescription = useDebouncedCallback((value) => { - updateQuestionsList(quizId, totalIndex, { + updateQuestionsList(quizId, totalIndex, { content: { - ...listQuestions[quizId][totalIndex].content, + ...question.content, ratingNegativeDescription: value.substring(0, 15), }, }); }, 500); const debouncePositiveDescription = useDebouncedCallback((value) => { - updateQuestionsList(quizId, totalIndex, { + updateQuestionsList(quizId, totalIndex, { content: { - ...listQuestions[quizId][totalIndex].content, + ...question.content, ratingPositiveDescription: value.substring(0, 15), }, }); }, 500); useEffect(() => { - setNegativeText( - listQuestions[quizId][totalIndex].content.ratingNegativeDescription - ); - setPositiveText( - listQuestions[quizId][totalIndex].content.ratingPositiveDescription - ); + setNegativeText(question.content.ratingNegativeDescription); + setPositiveText(question.content.ratingPositiveDescription); }, []); useEffect(() => { @@ -76,7 +75,10 @@ export default function RatingOptions({ totalIndex }: Props) { }, [positiveText]); const buttonRatingForm: ButtonRatingFrom[] = [ - { name: "star", icon: }, + { + name: "star", + icon: , + }, { name: "trophie", icon: }, { name: "flag", icon: }, { name: "heart", icon: }, @@ -96,24 +98,17 @@ export default function RatingOptions({ totalIndex }: Props) { <> - negativeTextWidth + positiveTextWidth - ? listQuestions[quizId][totalIndex].content.steps * 44 - : negativeTextWidth + positiveTextWidth + 20 - }px`, - minWidth: "300px", - maxWidth: "440px", display: "flex", px: "20px", flexDirection: "column", gap: "20px", + marginTop: isMobile ? "20px" : 0, }} > {Array.from( - { length: listQuestions[quizId][totalIndex].content.steps }, + { length: question.content.steps }, (_, index) => index ).map((itemNumber) => ( - + { + updateQuestionsList( + quizId, + totalIndex, + { + content: { + ...question.content, + ratingExpanded: true, + }, + } + ); + }, + sx: { + cursor: "pointer", + transform: "scale(1.5)", + ":hover": { + transform: "scale(1.7)", + transition: "0.2s", + }, + }, + } + : { sx: { transform: "scale(1.5)" } })} + > { buttonRatingForm.find( - ({ name }) => - listQuestions[quizId][totalIndex].content.form === name + ({ name }) => question.content.form === name )?.icon } @@ -140,113 +160,118 @@ export default function RatingOptions({ totalIndex }: Props) { display: "flex", alignItems: "center", justifyContent: "space-between", - maxWidth: isMobile ? "303px" : "290px", + maxWidth: "410px", width: "100%", }} > - - {negativeText} - - { - if (target.value.length <= 15) { - setNegativeText(target.value); - debounceNegativeDescription(target.value); - } - }} - onBlur={({ target }) => debounceNegativeDescription(target.value)} - sx={{ - width: negativeTextWidth + 10 + "px", - background: "transparent", - fontSize: "18px", - minWidth: "95px", - maxWidth: "230px", - transition: "0.2s", - "& .MuiInputBase-root": { - "& .MuiInputBase-input": { - color: theme.palette.grey2.main, - fontSize: "16px", - padding: "0 3px", - borderRadius: "3px", - border: "1px solid", - borderColor: "transparent", - "&:hover, &:focus": { - borderColor: theme.palette.grey2.main, + + + {negativeText} + + { + if (target.value.length <= 15) { + setNegativeText(target.value); + debounceNegativeDescription(target.value); + } + }} + onBlur={({ target }) => debounceNegativeDescription(target.value)} + sx={{ + width: negativeTextWidth + 10 + "px", + maxWidth: isMobile ? "140px" : "230px", + background: "transparent", + fontSize: "18px", + minWidth: "95px", + transition: "0.2s", + "& .MuiInputBase-root": { + "& .MuiInputBase-input": { + color: theme.palette.grey2.main, + fontSize: "16px", + padding: "0 3px", + borderRadius: "3px", + border: "1px solid", + borderColor: "transparent", + "&:hover, &:focus": { + borderColor: theme.palette.grey2.main, + }, + }, + "& .MuiOutlinedInput-notchedOutline": { + outline: "none", + border: "none", }, }, - "& .MuiOutlinedInput-notchedOutline": { - outline: "none", - border: "none", - }, - }, - }} - /> - - {positiveText} - - { - if (target.value.length <= 15) { - setPositiveText(target.value); - debouncePositiveDescription(target.value); - } - }} - onBlur={({ target }) => debouncePositiveDescription(target.value)} - sx={{ - width: positiveTextWidth + 10 + "px", - background: "transparent", - fontSize: "18px", - minWidth: "95px", - maxWidth: "230px", - transition: "0.2s", - "& .MuiInputBase-root": { - "& .MuiInputBase-input": { - color: theme.palette.grey2.main, - fontSize: "16px", - padding: "0 3px", - borderRadius: "3px", - border: "1px solid", - borderColor: "transparent", - "&:hover, &:focus": { - borderColor: theme.palette.grey2.main, + }} + /> + + + + {positiveText} + + { + if (target.value.length <= 15) { + setPositiveText(target.value); + debouncePositiveDescription(target.value); + } + }} + onBlur={({ target }) => debouncePositiveDescription(target.value)} + sx={{ + width: positiveTextWidth + 10 + "px", + maxWidth: isMobile ? "140px" : "230px", + background: "transparent", + fontSize: "18px", + minWidth: "95px", + transition: "0.2s", + "& .MuiInputBase-root": { + "& .MuiInputBase-input": { + color: theme.palette.grey2.main, + fontSize: "16px", + padding: "0 3px", + borderRadius: "3px", + border: "1px solid", + borderColor: "transparent", + "&:hover, &:focus": { + borderColor: theme.palette.grey2.main, + }, + }, + "& .MuiOutlinedInput-notchedOutline": { + outline: "none", + border: "none", }, }, - "& .MuiOutlinedInput-notchedOutline": { - outline: "none", - border: "none", - }, - }, - }} - /> + }} + /> + - + ); diff --git a/src/pages/Questions/RatingOptions/settingRating.tsx b/src/pages/Questions/RatingOptions/settingRating.tsx index b46ba1ac..272b5fc0 100644 --- a/src/pages/Questions/RatingOptions/settingRating.tsx +++ b/src/pages/Questions/RatingOptions/settingRating.tsx @@ -16,6 +16,7 @@ import HashtagIcon from "../../../assets/icons/questionsPage/hashtagIcon"; import StarIconMini from "../../../assets/icons/questionsPage/StarIconMini"; import type { ButtonRatingFrom } from "./RatingOptions"; +import type { QuizQuestionNumber } from "../../../model/questionTypes/number"; type SettingSliderProps = { totalIndex: number; @@ -28,10 +29,11 @@ export default function SettingSlider({ totalIndex }: SettingSliderProps) { const isMobile = useMediaQuery(theme.breakpoints.down(790)); const isWrappColumn = useMediaQuery(theme.breakpoints.down(980)); const { listQuestions } = questionStore(); + const question = listQuestions[quizId][totalIndex] as QuizQuestionNumber; const debounced = useDebouncedCallback((value) => { - let clonContent = listQuestions[quizId][totalIndex].content; - clonContent.innerName = value; - updateQuestionsList(quizId, totalIndex, { content: clonContent }); + updateQuestionsList(quizId, totalIndex, { + content: { ...question.content, innerName: value }, + }); }, 1000); const buttonRatingForm: ButtonRatingFrom[] = [ @@ -70,43 +72,41 @@ export default function SettingSlider({ totalIndex }: SettingSliderProps) { Настройки рейтинга - - - Форма - - - {buttonRatingForm.map(({ name, icon }, index) => ( - { - const clonContent = listQuestions[quizId][totalIndex].content; - clonContent.form = name; - updateQuestionsList(quizId, totalIndex, { - content: clonContent, - }); - }} - sx={{ - backgroundColor: - listQuestions[quizId][totalIndex].content.form === name - ? theme.palette.brightPurple.main - : "transparent", - color: listQuestions[quizId][totalIndex].content.form === name ? "#ffffff" : theme.palette.grey3.main, - width: "40px", - height: "40px", - borderRadius: "4px", - }} - > - {icon} - - ))} - + + Форма + + + {buttonRatingForm.map(({ name, icon }, index) => ( + { + updateQuestionsList(quizId, totalIndex, { + content: { ...question.content, form: name }, + }); + }} + sx={{ + backgroundColor: + question.content.form === name + ? theme.palette.brightPurple.main + : "transparent", + color: + question.content.form === name + ? "#ffffff" + : theme.palette.grey3.main, + width: "40px", + height: "40px", + borderRadius: "4px", + }} + > + {icon} + + ))} { - updateQuestionsList(quizId, totalIndex, { - content: { - ...listQuestions[quizId][totalIndex].content, - steps: Number(value) || 1, - }, + updateQuestionsList(quizId, totalIndex, { + content: { ...question.content, steps: Number(value) || 1 }, }); }} /> { - updateQuestionsList(quizId, totalIndex, { + updateQuestionsList(quizId, totalIndex, { required: !e.target.checked, }); }} @@ -170,16 +168,15 @@ export default function SettingSlider({ totalIndex }: SettingSliderProps) { { - let clonContent = listQuestions[quizId][totalIndex].content; - clonContent.innerNameCheck = e.target.checked; - - if (!e.target.checked) { - clonContent.innerName = ""; - } - - updateQuestionsList(quizId, totalIndex, { content: clonContent }); + checked={question.content.innerNameCheck} + handleChange={({ target }) => { + updateQuestionsList(quizId, totalIndex, { + content: { + ...question.content, + innerNameCheck: target.checked, + innerName: target.checked ? question.content.innerName : "", + }, + }); }} /> @@ -188,10 +185,10 @@ export default function SettingSlider({ totalIndex }: SettingSliderProps) { - {listQuestions[quizId][totalIndex].content.innerNameCheck && ( + {question.content.innerNameCheck && ( debounced(target.value)} /> )} diff --git a/src/pages/Questions/SliderOptions/SliderOptions.tsx b/src/pages/Questions/SliderOptions/SliderOptions.tsx index d4e27796..89ec3600 100644 --- a/src/pages/Questions/SliderOptions/SliderOptions.tsx +++ b/src/pages/Questions/SliderOptions/SliderOptions.tsx @@ -6,6 +6,8 @@ import CustomNumberField from "@ui_kit/CustomNumberField"; import SwitchSlider from "./switchSlider"; import { questionStore, updateQuestionsList } from "@root/questions"; +import type { QuizQuestionNumber } from "../../../model/questionTypes/number"; + interface Props { totalIndex: number; } @@ -18,6 +20,7 @@ export default function SliderOptions({ totalIndex }: Props) { const [stepError, setStepError] = useState(""); const quizId = Number(useParams().quizId); const { listQuestions } = questionStore(); + const question = listQuestions[quizId][totalIndex] as QuizQuestionNumber; const SSHC = (data: string) => { setSwitchState(data); @@ -31,59 +34,60 @@ export default function SliderOptions({ totalIndex }: Props) { maxWidth: "673.8px", display: "flex", pl: "20px", - pr: "20px", + pr: isMobile ? "13px" : "20px", pb: isMobile ? "30px" : "20px", flexDirection: "column", - gap: "20px", + gap: isMobile ? "25px" : "20px", }} > - + Выбор значения из диапазона - + { - const clonContent = listQuestions[quizId][totalIndex].content; - clonContent.range = `${target.value}—${ - listQuestions[quizId][totalIndex].content.range.split("—")[1] - }`; - updateQuestionsList(quizId, totalIndex, { - content: clonContent, + updateQuestionsList(quizId, totalIndex, { + content: { + ...question.content, + range: `${target.value}—${ + question.content.range.split("—")[1] + }`, + }, }); }} onBlur={({ target }) => { - const start = listQuestions[quizId][totalIndex].content.start; + const start = question.content.start; const min = Number(target.value); - const max = Number( - listQuestions[quizId][totalIndex].content.range.split("—")[1] - ); + const max = Number(question.content.range.split("—")[1]); if (min >= max) { - const clonContent = listQuestions[quizId][totalIndex].content; - clonContent.range = `${max - 1 >= 0 ? max - 1 : 0}—${ - listQuestions[quizId][totalIndex].content.range.split( - "—" - )[1] - }`; - updateQuestionsList(quizId, totalIndex, { - content: clonContent, + updateQuestionsList(quizId, totalIndex, { + content: { + ...question.content, + range: `${max - 1 >= 0 ? max - 1 : 0}—${ + question.content.range.split("—")[1] + }`, + }, }); } if (start < min) { - updateQuestionsList(quizId, totalIndex, { - content: { - ...listQuestions[quizId][totalIndex].content, - start: min, - }, + updateQuestionsList(quizId, totalIndex, { + content: { ...question.content, start: min }, }); } }} @@ -94,62 +98,48 @@ export default function SliderOptions({ totalIndex }: Props) { placeholder={"100"} min={0} max={100} - value={ - listQuestions[quizId][totalIndex].content.range.split("—")[1] - } + value={question.content.range.split("—")[1]} onChange={({ target }) => { - const clonContent = listQuestions[quizId][totalIndex].content; - clonContent.range = `${ - listQuestions[quizId][totalIndex].content.range.split("—")[0] - }—${target.value}`; - updateQuestionsList(quizId, totalIndex, { - content: clonContent, + updateQuestionsList(quizId, totalIndex, { + content: { + ...question.content, + range: `${question.content.range.split("—")[0]}—${ + target.value + }`, + }, }); }} onBlur={({ target }) => { - const start = listQuestions[quizId][totalIndex].content.start; - const step = listQuestions[quizId][totalIndex].content.step; - const min = Number( - listQuestions[quizId][totalIndex].content.range.split("—")[0] - ); + const start = question.content.start; + const step = question.content.step; + const min = Number(question.content.range.split("—")[0]); const max = Number(target.value); const range = max - min; if (max <= min) { - const clonContent = listQuestions[quizId][totalIndex].content; - clonContent.range = `${ - listQuestions[quizId][totalIndex].content.range.split( - "—" - )[0] - }—${min + 1 >= 100 ? 100 : min + 1}`; - updateQuestionsList(quizId, totalIndex, { - content: clonContent, + updateQuestionsList(quizId, totalIndex, { + content: { + ...question.content, + range: `${question.content.range.split("—")[0]}—${ + min + 1 >= 100 ? 100 : min + 1 + }`, + }, }); } if (start > max) { - updateQuestionsList(quizId, totalIndex, { - content: { - ...listQuestions[quizId][totalIndex].content, - start: max, - }, + updateQuestionsList(quizId, totalIndex, { + content: { ...question.content, start: max }, }); } if (step > max) { - updateQuestionsList(quizId, totalIndex, { - content: { - ...listQuestions[quizId][totalIndex].content, - step: max, - }, + updateQuestionsList(quizId, totalIndex, { + content: { ...question.content, step: max }, }); if (range % step) { - setStepError( - `Шаг должен делить без остатка диапазон ${max} - ${min} = ${ - max - min - }` - ); + setStepError(`Шаг должен делить без остатка диапазон ${max} - ${min} = ${max - min}`); } else { setStepError(""); } @@ -168,24 +158,18 @@ export default function SliderOptions({ totalIndex }: Props) { }} > - + Начальное значение { - const clonContent = listQuestions[quizId][totalIndex].content; - clonContent.start = Number(target.value); - updateQuestionsList(quizId, totalIndex, { - content: clonContent, + updateQuestionsList(quizId, totalIndex, { + content: { ...question.content, start: Number(target.value) }, }); }} /> @@ -207,39 +191,26 @@ export default function SliderOptions({ totalIndex }: Props) { max={100} placeholder={"1"} error={stepError} - value={String(listQuestions[quizId][totalIndex].content.step)} + value={String(question.content.step)} onChange={({ target }) => { - const clonContent = listQuestions[quizId][totalIndex].content; - clonContent.step = Number(target.value); - updateQuestionsList(quizId, totalIndex, { - content: clonContent, + updateQuestionsList(quizId, totalIndex, { + content: { ...question.content, step: Number(target.value) }, }); }} onBlur={({ target }) => { - const min = Number( - listQuestions[quizId][totalIndex].content.range.split("—")[0] - ); - const max = Number( - listQuestions[quizId][totalIndex].content.range.split("—")[1] - ); + const min = Number(question.content.range.split("—")[0]); + const max = Number(question.content.range.split("—")[1]); const range = max - min; const step = Number(target.value); if (step > max) { - updateQuestionsList(quizId, totalIndex, { - content: { - ...listQuestions[quizId][totalIndex].content, - step: max, - }, + updateQuestionsList(quizId, totalIndex, { + content: { ...question.content, step: max }, }); } if (range % step) { - setStepError( - `Шаг должен делить без остатка диапазон ${max} - ${min} = ${ - max - min - }` - ); + setStepError(`Шаг должен делить без остатка диапазон ${max} - ${min} = ${max - min}`); } else { setStepError(""); } @@ -248,11 +219,7 @@ export default function SliderOptions({ totalIndex }: Props) { - + ); diff --git a/src/pages/Questions/SliderOptions/settingSlider.tsx b/src/pages/Questions/SliderOptions/settingSlider.tsx index e4bf757e..b0a89cee 100644 --- a/src/pages/Questions/SliderOptions/settingSlider.tsx +++ b/src/pages/Questions/SliderOptions/settingSlider.tsx @@ -6,6 +6,8 @@ import CustomTextField from "@ui_kit/CustomTextField"; import InfoIcon from "../../../assets/icons/InfoIcon"; import { questionStore, updateQuestionsList } from "@root/questions"; +import type { QuizQuestionNumber } from "../../../model/questionTypes/number"; + type SettingSliderProps = { totalIndex: number; }; @@ -17,10 +19,11 @@ export default function SettingSlider({ totalIndex }: SettingSliderProps) { const isWrappColumn = useMediaQuery(theme.breakpoints.down(980)); const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990)); const isMobile = useMediaQuery(theme.breakpoints.down(790)); + const question = listQuestions[quizId][totalIndex] as QuizQuestionNumber; const debounced = useDebouncedCallback((value) => { - let clonContent = listQuestions[quizId][totalIndex].content; - clonContent.innerName = value; - updateQuestionsList(quizId, totalIndex, { content: clonContent }); + updateQuestionsList(quizId, totalIndex, { + content: { ...question.content, innerName: value }, + }); }, 1000); return ( @@ -34,25 +37,29 @@ export default function SettingSlider({ totalIndex }: SettingSliderProps) { Настройки ползунка { - let clonContent = listQuestions[quizId][totalIndex].content; - clonContent.chooseRange = e.target.checked; - updateQuestionsList(quizId, totalIndex, { content: clonContent }); + checked={question.content.chooseRange} + handleChange={({ target }) => { + updateQuestionsList(quizId, totalIndex, { + content: { ...question.content, chooseRange: target.checked }, + }); }} /> @@ -64,19 +71,19 @@ export default function SettingSlider({ totalIndex }: SettingSliderProps) { pr: isFigmaTablte ? (isMobile ? "20px" : "12px") : "20px", display: "flex", flexDirection: "column", - gap: isMobile ? "13px" : "14px", - width: "100%", + gap: "14px", + width: isMobile ? "auto" : "100%", }} > Настройки вопросов { - updateQuestionsList(quizId, totalIndex, { + updateQuestionsList(quizId, totalIndex, { required: !e.target.checked, }); }} @@ -85,22 +92,25 @@ export default function SettingSlider({ totalIndex }: SettingSliderProps) { sx={{ width: isMobile ? "90%" : "auto", display: "flex", - alignItems: "center", + alignItems: "flex-start", }} > { - let clonContent = listQuestions[quizId][totalIndex].content; - clonContent.innerNameCheck = e.target.checked; - - if (!e.target.checked) { - clonContent.innerName = ""; - } - - updateQuestionsList(quizId, totalIndex, { content: clonContent }); + checked={question.content.innerNameCheck} + handleChange={({ target }) => { + updateQuestionsList(quizId, totalIndex, { + content: { + ...question.content, + innerNameCheck: target.checked, + innerName: target.checked ? question.content.innerName : "", + }, + }); }} /> @@ -109,10 +119,10 @@ export default function SettingSlider({ totalIndex }: SettingSliderProps) { - {listQuestions[quizId][totalIndex].content.innerNameCheck && ( + {question.content.innerNameCheck && ( debounced(target.value)} /> )} diff --git a/src/pages/Questions/TypeQuestions.tsx b/src/pages/Questions/TypeQuestions.tsx index fe5a9989..7c29886f 100755 --- a/src/pages/Questions/TypeQuestions.tsx +++ b/src/pages/Questions/TypeQuestions.tsx @@ -13,7 +13,18 @@ import RatingIcon from "../../assets/icons/questionsPage/rating"; import { Box } from "@mui/material"; import React from "react"; import { useParams } from "react-router-dom"; -import { questionStore, updateQuestionsList } from "@root/questions"; + +import { + questionStore, + updateQuestionsList, + createQuestion, + removeQuestionForce, +} from "@root/questions"; + +import type { + QuizQuestionType, + QuizQuestionBase, +} from "../../model/questionTypes/shared"; interface Props { totalIndex: number; @@ -22,7 +33,7 @@ interface Props { type ButtonTypeQuestion = { icon: JSX.Element; title: string; - value: string; + value: QuizQuestionType; }; export const BUTTON_TYPE_QUESTIONS: ButtonTypeQuestion[] = [ @@ -86,7 +97,6 @@ export const BUTTON_TYPE_QUESTIONS: ButtonTypeQuestion[] = [ export default function TypeQuestions({ totalIndex }: Props) { const quizId = Number(useParams().quizId); const { listQuestions } = questionStore(); - const switchState = listQuestions[quizId][totalIndex].type; return ( { - updateQuestionsList(quizId, totalIndex, { type: value }); + const question = { ...listQuestions[quizId][totalIndex] }; + + removeQuestionForce(quizId, question.id); + createQuestion(quizId, value, totalIndex); + updateQuestionsList(quizId, totalIndex, { + expanded: question.expanded, + type: value, + }); }} icon={icon} text={title} diff --git a/src/pages/Questions/UploadFile/UploadFile.tsx b/src/pages/Questions/UploadFile/UploadFile.tsx index 6652d8db..e6abb06e 100644 --- a/src/pages/Questions/UploadFile/UploadFile.tsx +++ b/src/pages/Questions/UploadFile/UploadFile.tsx @@ -7,6 +7,7 @@ import { Select, SelectChangeEvent, Typography, + Tooltip, useMediaQuery, useTheme, } from "@mui/material"; @@ -18,11 +19,22 @@ import InfoIcon from "../../../assets/icons/InfoIcon"; import ArrowDown from "../../../assets/icons/ArrowDownIcon"; import SwitchUpload from "./switchUpload"; +import type { AnyQuizQuestion } from "../../../model/questionTypes/shared"; +import type { + QuizQuestionFile, + UploadFileType, +} from "../../../model/questionTypes/file"; + interface Props { totalIndex: number; } -const DESIGN_TYPES = [ +type DesignItem = { + name: string; + value: UploadFileType; +}; + +const DESIGN_TYPES: DesignItem[] = [ { name: "Все типы файлов", value: "all" }, { name: "Изображения", value: "picture" }, { name: "Видео", value: "video" }, @@ -36,6 +48,7 @@ export default function UploadFile({ totalIndex }: Props) { const { listQuestions } = questionStore(); const theme = useTheme(); const isTablet = useMediaQuery(theme.breakpoints.down(980)); + const question = listQuestions[quizId][totalIndex] as QuizQuestionFile; const isFigmaTablet = useMediaQuery(theme.breakpoints.down(990)); const isMobile = useMediaQuery(theme.breakpoints.down(790)); @@ -44,18 +57,20 @@ export default function UploadFile({ totalIndex }: Props) { }; const handleChange = ({ target }: SelectChangeEvent) => { - const clonContent = listQuestions[quizId][totalIndex].content; - clonContent.type = target.value; - updateQuestionsList(quizId, totalIndex, { content: clonContent }); + updateQuestionsList(quizId, totalIndex, { + content: { ...question.content, type: target.value as UploadFileType }, + }); }; useEffect(() => { - const isTypeSetted = DESIGN_TYPES.find(({ value }) => value === listQuestions[quizId][totalIndex].content.type); + const isTypeSetted = DESIGN_TYPES.find( + ({ value }) => value === question.content.type + ); if (!isTypeSetted) { - const clonContent = listQuestions[quizId][totalIndex].content; - clonContent.type = DESIGN_TYPES[0].value; - updateQuestionsList(quizId, totalIndex, { content: clonContent }); + updateQuestionsList(quizId, totalIndex, { + content: { ...question.content, type: DESIGN_TYPES[0].value }, + }); } }, []); @@ -70,7 +85,7 @@ export default function UploadFile({ totalIndex }: Props) { flexDirection: "column", }} > - + Пользователь может загрузить любой собственный файл - + + + + + - + ); diff --git a/src/pages/Questions/UploadFile/settingUpload.tsx b/src/pages/Questions/UploadFile/settingUpload.tsx index 7ad321ce..77680e36 100644 --- a/src/pages/Questions/UploadFile/settingUpload.tsx +++ b/src/pages/Questions/UploadFile/settingUpload.tsx @@ -1,5 +1,11 @@ import { useParams } from "react-router-dom"; -import { Box, Typography, useMediaQuery, useTheme, Tooltip } from "@mui/material"; +import { + Box, + Typography, + useMediaQuery, + useTheme, + Tooltip, +} from "@mui/material"; import CustomCheckbox from "@ui_kit/CustomCheckbox"; import CustomTextField from "@ui_kit/CustomTextField"; import { useDebouncedCallback } from "use-debounce"; @@ -8,6 +14,8 @@ import { questionStore, updateQuestionsList } from "@root/questions"; import InfoIcon from "../../../assets/icons/InfoIcon"; +import type { QuizQuestionFile } from "../../../model/questionTypes/file"; + type SettingsUploadProps = { totalIndex: number; }; @@ -16,75 +24,95 @@ export default function SettingsUpload({ totalIndex }: SettingsUploadProps) { const theme = useTheme(); const quizId = Number(useParams().quizId); const { listQuestions } = questionStore(); + const question = listQuestions[quizId][totalIndex] as QuizQuestionFile; const debounced = useDebouncedCallback((value) => { - let clonContent = listQuestions[quizId][totalIndex].content; - clonContent.innerName = value; - updateQuestionsList(quizId, totalIndex, { content: clonContent }); + updateQuestionsList(quizId, totalIndex, { + content: { ...question.content, innerName: value }, + }); }, 1000); const isMobile = useMediaQuery(theme.breakpoints.down(790)); return ( - - Настройки вопроса - + Настройки вопроса { - const clonContent = listQuestions[quizId][totalIndex].content; - clonContent.autofill = target.checked; - updateQuestionsList(quizId, totalIndex, { content: clonContent }); + updateQuestionsList(quizId, totalIndex, { + content: { ...question.content, autofill: target.checked }, + }); }} /> { - updateQuestionsList(quizId, totalIndex, { + updateQuestionsList(quizId, totalIndex, { required: !e.target.checked, }); }} /> - + { - let clonContent = listQuestions[quizId][totalIndex].content; - clonContent.innerNameCheck = e.target.checked; - - if (!e.target.checked) { - clonContent.innerName = ""; - } - - updateQuestionsList(quizId, totalIndex, { content: clonContent }); + checked={question.content.innerNameCheck} + handleChange={({ target }) => { + updateQuestionsList(quizId, totalIndex, { + content: { + ...question.content, + innerNameCheck: target.checked, + innerName: target.checked ? question.content.innerName : "", + }, + }); }} /> - + - {listQuestions[quizId][totalIndex].content.innerNameCheck && ( + {question.content.innerNameCheck && ( debounced(target.value)} + sx={{ paddingRight: "20px" }} /> )} diff --git a/src/pages/Questions/UploadImage/index.tsx b/src/pages/Questions/UploadImage/index.tsx index 1148dd96..dbfaee3c 100644 --- a/src/pages/Questions/UploadImage/index.tsx +++ b/src/pages/Questions/UploadImage/index.tsx @@ -9,6 +9,7 @@ import { questionStore, updateQuestionsList } from "@root/questions"; import { UploadImageModal } from "./UploadImageModal"; import type { DragEvent } from "react"; +import type { QuizQuestionImages } from "../../../model/questionTypes/images"; type UploadImageProps = { totalIndex: number; @@ -19,6 +20,7 @@ export default function UploadImage({ totalIndex }: UploadImageProps) { const theme = useTheme(); const [open, setOpen] = React.useState(false); const { listQuestions } = questionStore(); + const question = listQuestions[quizId][totalIndex] as QuizQuestionImages; const handleOpen = () => setOpen(true); const handleClose = () => setOpen(false); @@ -26,9 +28,12 @@ export default function UploadImage({ totalIndex }: UploadImageProps) { if (files?.length) { const [file] = Array.from(files); - let clonContent = listQuestions[quizId][totalIndex].content; - clonContent.back = URL.createObjectURL(file); - updateQuestionsList(quizId, totalIndex, { content: clonContent }); + updateQuestionsList(quizId, totalIndex, { + content: { + ...question.content, + back: URL.createObjectURL(file), + }, + }); handleClose(); setOpened(true); @@ -70,7 +75,7 @@ export default function UploadImage({ totalIndex }: UploadImageProps) { setOpened(false)} - picture={listQuestions[quizId][totalIndex].content.back} + picture={question.content.back} /> ); diff --git a/src/pages/Questions/answerOptions/AnswerOptions.tsx b/src/pages/Questions/answerOptions/AnswerOptions.tsx index 7c04e8d5..ed01472f 100755 --- a/src/pages/Questions/answerOptions/AnswerOptions.tsx +++ b/src/pages/Questions/answerOptions/AnswerOptions.tsx @@ -7,6 +7,8 @@ import { AnswerDraggableList } from "../AnswerDraggableList"; import ButtonsOptionsAndPict from "../ButtonsOptionsAndPict"; import { questionStore, updateQuestionsList } from "@root/questions"; +import type { QuizQuestionVariant } from "../../../model/questionTypes/variant"; + interface Props { totalIndex: number; } @@ -15,7 +17,7 @@ export default function AnswerOptions({ totalIndex }: Props) { const [switchState, setSwitchState] = useState("setting"); const quizId = Number(useParams().quizId); const { listQuestions } = questionStore(); - const variants = listQuestions[quizId][totalIndex].content.variants; + const question = listQuestions[quizId][totalIndex] as QuizQuestionVariant; const theme = useTheme(); const isMobile = useMediaQuery(theme.breakpoints.down(790)); @@ -24,21 +26,18 @@ export default function AnswerOptions({ totalIndex }: Props) { }; const addNewAnswer = () => { - const answerNew = variants.slice(); - answerNew.push({ answer: "", hints: "", emoji: "", image: "" }); + const answerNew = question.content.variants.slice(); + answerNew.push({ answer: "", hints: "", extendedText: "" }); - updateQuestionsList(quizId, totalIndex, { - content: { - ...listQuestions[quizId][totalIndex].content, - variants: answerNew, - }, + updateQuestionsList(quizId, totalIndex, { + content: { ...question.content, variants: answerNew }, }); }; return ( <> - - {variants.length === 0 ? ( + + {question.content.variants.length === 0 ? ( ) : ( - + )} - + ); diff --git a/src/pages/Questions/answerOptions/responseSettings.tsx b/src/pages/Questions/answerOptions/responseSettings.tsx index e56e236b..00550e65 100644 --- a/src/pages/Questions/answerOptions/responseSettings.tsx +++ b/src/pages/Questions/answerOptions/responseSettings.tsx @@ -1,11 +1,19 @@ import { useParams } from "react-router-dom"; -import { Box, Typography, Tooltip, useMediaQuery, useTheme } from "@mui/material"; +import { + Box, + Typography, + Tooltip, + useMediaQuery, + useTheme, +} from "@mui/material"; import { useDebouncedCallback } from "use-debounce"; import CustomCheckbox from "@ui_kit/CustomCheckbox"; import InfoIcon from "../../../assets/icons/InfoIcon"; import CustomTextField from "@ui_kit/CustomTextField"; import { questionStore, updateQuestionsList } from "@root/questions"; +import type { QuizQuestionVariant } from "../../../model/questionTypes/variant"; + interface Props { totalIndex: number; } @@ -15,12 +23,14 @@ export default function ResponseSettings({ totalIndex }: Props) { const { listQuestions } = questionStore(); const theme = useTheme(); const isTablet = useMediaQuery(theme.breakpoints.down(900)); + const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990)); const isMobile = useMediaQuery(theme.breakpoints.down(790)); + const question = listQuestions[quizId][totalIndex] as QuizQuestionVariant; const debounced = useDebouncedCallback((value) => { - let clonContent = listQuestions[quizId][totalIndex].content; - clonContent.innerName = value; - updateQuestionsList(quizId, totalIndex, { content: clonContent }); + updateQuestionsList(quizId, totalIndex, { + content: { ...question.content, innerName: value }, + }); }, 1000); return ( @@ -43,102 +53,124 @@ export default function ResponseSettings({ totalIndex }: Props) { width: "100%", }} > - + Настройки ответов { - let clonContent = listQuestions[quizId][totalIndex].content; - clonContent.largeCheck = e.target.checked; - - if (!e.target.checked) { - clonContent.large = ""; - } - - updateQuestionsList(quizId, totalIndex, { content: clonContent }); + checked={question.content.largeCheck} + handleChange={({ target }) => { + updateQuestionsList(quizId, totalIndex, { + content: { + ...question.content, + largeCheck: target.checked, + }, + }); }} /> { - let clonContent = listQuestions[quizId][totalIndex].content; - clonContent.multi = e.target.checked; - updateQuestionsList(quizId, totalIndex, { content: clonContent }); + checked={question.content.multi} + handleChange={({ target }) => { + updateQuestionsList(quizId, totalIndex, { + content: { ...question.content, multi: target.checked }, + }); }} /> { - let clonContent = listQuestions[quizId][totalIndex].content; - clonContent.own = e.target.checked; - updateQuestionsList(quizId, totalIndex, { content: clonContent }); + checked={question.content.own} + handleChange={({ target }) => { + updateQuestionsList(quizId, totalIndex, { + content: { ...question.content, own: target.checked }, + }); }} /> - + Настройки вопросов { - updateQuestionsList(quizId, totalIndex, { - required: !e.target.checked, + checked={!question.required} + handleChange={({ target }) => { + updateQuestionsList(quizId, totalIndex, { + required: !target.checked, }); }} /> - + { - let clonContent = listQuestions[quizId][totalIndex].content; - clonContent.innerNameCheck = e.target.checked; - - if (!e.target.checked) { - clonContent.innerName = ""; - } - - updateQuestionsList(quizId, totalIndex, { - content: clonContent, + checked={question.content.innerNameCheck} + handleChange={({ target }) => { + updateQuestionsList(quizId, totalIndex, { + content: { + ...question.content, + innerNameCheck: target.checked, + innerName: target.checked ? question.content.innerName : "", + }, }); }} /> - {isMobile || - (!isFigmaTablte && ( - - - - - - ))} + {isMobile && ( + + + + + + )} - {listQuestions[quizId][totalIndex].content.innerNameCheck && ( + {question.content.innerNameCheck && ( debounced(target.value)} /> )} diff --git a/src/pages/Questions/branchingQuestions.tsx b/src/pages/Questions/branchingQuestions.tsx index 7db88e1c..7578d2a2 100644 --- a/src/pages/Questions/branchingQuestions.tsx +++ b/src/pages/Questions/branchingQuestions.tsx @@ -11,10 +11,15 @@ import { Modal, Radio, RadioGroup, + Tooltip, Typography, useTheme, } from "@mui/material"; -import { questionStore, resetSomeField, updateQuestionsList } from "@root/questions"; +import { + questionStore, + resetSomeField, + updateQuestionsList, +} from "@root/questions"; import { Select } from "./Select"; import RadioCheck from "@ui_kit/RadioCheck"; @@ -22,6 +27,8 @@ import RadioIcon from "@ui_kit/RadioIcon"; import InfoIcon from "@icons/Info"; import { DeleteIcon } from "@icons/questionsPage/deleteIcon"; +import type { QuizQuestionBase } from "../../model/questionTypes/shared"; + type BranchingQuestionsProps = { totalIndex: number; }; @@ -29,15 +36,21 @@ type BranchingQuestionsProps = { const ACTIONS = ["Показать", "Скрыть"]; const STIPULATIONS = ["Условие 1", "Условие 2", "Условие 3"]; const ANSWERS = ["Ответ 1", "Ответ 2", "Ответ 3"]; -const CONDITIONS = ["Все условия обязательны", "Обязательно хотя бы одно условие"]; +const CONDITIONS = [ + "Все условия обязательны", + "Обязательно хотя бы одно условие", +]; -export default function BranchingQuestions({ totalIndex }: BranchingQuestionsProps) { +export default function BranchingQuestions({ + totalIndex, +}: BranchingQuestionsProps) { const theme = useTheme(); + const [title, setTitle] = useState(""); const [titleInputWidth, setTitleInputWidth] = useState(0); const quizId = Number(useParams().quizId); const { openedModalSettings, listQuestions } = questionStore(); const titleRef = useRef(null); - const [title, setTitle] = useState(listQuestions[quizId][totalIndex].title) + const question = listQuestions[quizId][totalIndex] as QuizQuestionBase; useEffect(() => { setTitleInputWidth(titleRef.current?.offsetWidth || 0); @@ -74,9 +87,10 @@ export default function BranchingQuestions({ totalIndex }: BranchingQuestionsPro sx={{ boxSizing: "border-box", background: "#F2F3F7", - padding: "25px", height: "70px", + padding: "0 25px", display: "flex", + alignItems: "center", }} > @@ -113,7 +127,14 @@ export default function BranchingQuestions({ totalIndex }: BranchingQuestionsPro ) - + + + + + { - const clonContent = listQuestions[quizId][totalIndex].content; + const clonedContent = { ...question.content }; - clonContent.rule.reqs[index] = { - id: String(STIPULATIONS.findIndex((item) => item.includes(stipulation))), + clonedContent.rule.reqs[index] = { + id: String( + STIPULATIONS.findIndex((item) => + item.includes(stipulation) + ) + ), vars: request.vars, }; - updateQuestionsList(quizId, totalIndex, { - content: clonContent, + updateQuestionsList(quizId, totalIndex, { + content: clonedContent, }); }} sx={{ marginBottom: "15px" }} @@ -204,7 +240,9 @@ export default function BranchingQuestions({ totalIndex }: BranchingQuestionsPro pb: "10px", }} > - Дан ответ + + Дан ответ + (Укажите один или несколько вариантов) @@ -214,16 +252,28 @@ export default function BranchingQuestions({ totalIndex }: BranchingQuestionsPro activeItemIndex={-1} items={ANSWERS} onChange={(answer) => { - const clonContent = listQuestions[quizId][totalIndex].content; - const answerItemIndex = ANSWERS.findIndex((answerItem) => answerItem === answer); + const clonedContent = { ...question.content }; + const answerItemIndex = ANSWERS.findIndex( + (answerItem) => answerItem === answer + ); - if (!clonContent.rule.reqs[index].vars.includes(answerItemIndex)) { - listQuestions[quizId][totalIndex].content.rule.reqs[index].vars.push(answerItemIndex); + if ( + !clonedContent.rule.reqs[index].vars.includes( + answerItemIndex + ) + ) { + question.content.rule.reqs[index].vars.push( + answerItemIndex + ); } - updateQuestionsList(quizId, totalIndex, { - content: clonContent, - }); + updateQuestionsList( + quizId, + totalIndex, + { + content: clonedContent, + } + ); }} sx={{ marginBottom: "10px", @@ -238,25 +288,34 @@ export default function BranchingQuestions({ totalIndex }: BranchingQuestionsPro gap: "10px", }} > - {listQuestions[quizId][totalIndex].content.rule.reqs[index].vars.map((item, varIndex) => ( - { - const clonContent = listQuestions[quizId][totalIndex].content; - const removedItemIndex = clonContent.rule.reqs[index].vars.findIndex( - (varItem) => varItem === item - ); + {question.content.rule.reqs[index].vars.map( + (item, varIndex) => ( + { + const clonedContent = { ...question.content }; + const removedItemIndex = clonedContent.rule.reqs[ + index + ].vars.findIndex((varItem) => varItem === item); - clonContent.rule.reqs[index].vars.splice(removedItemIndex, 1); + clonedContent.rule.reqs[index].vars.splice( + removedItemIndex, + 1 + ); - updateQuestionsList(quizId, totalIndex, { - content: clonContent, - }); - }} - /> - ))} + updateQuestionsList( + quizId, + totalIndex, + { + content: clonedContent, + } + ); + }} + /> + ) + )} )} @@ -276,10 +335,10 @@ export default function BranchingQuestions({ totalIndex }: BranchingQuestionsPro marginBottom: "10px", }} onClick={() => { - const clonContent = listQuestions[quizId][totalIndex].content; - clonContent.rule.reqs.push({ id: "", vars: [] }); - updateQuestionsList(quizId, totalIndex, { - content: clonContent, + const clonedContent = { ...question.content }; + clonedContent.rule.reqs.push({ id: "", vars: [] }); + updateQuestionsList(quizId, totalIndex, { + content: clonedContent, }); }} > @@ -288,12 +347,16 @@ export default function BranchingQuestions({ totalIndex }: BranchingQuestionsPro { - const clonContent = listQuestions[quizId][totalIndex].content; - clonContent.rule.or = Boolean(Number(value)); - updateQuestionsList(quizId, totalIndex, { - content: clonContent, + updateQuestionsList(quizId, totalIndex, { + content: { + ...question.content, + rule: { + ...question.content.rule, + or: Boolean(Number(value)), + }, + }, }); }} > @@ -302,7 +365,12 @@ export default function BranchingQuestions({ totalIndex }: BranchingQuestionsPro key={index} sx={{ color: theme.palette.grey2.main }} value={index} - control={} icon={} />} + control={ + } + icon={} + /> + } label={condition} /> ))} @@ -310,10 +378,18 @@ export default function BranchingQuestions({ totalIndex }: BranchingQuestionsPro - - diff --git a/src/pages/Questions/helpQuestions.tsx b/src/pages/Questions/helpQuestions.tsx index c04205da..a988ea46 100644 --- a/src/pages/Questions/helpQuestions.tsx +++ b/src/pages/Questions/helpQuestions.tsx @@ -9,6 +9,8 @@ import UploadBox from "@ui_kit/UploadBox"; import { questionStore, updateQuestionsList } from "@root/questions"; import { UploadVideoModal } from "./UploadVideoModal"; +import type { QuizQuestionBase } from "../../model/questionTypes/shared"; + type BackgroundType = "text" | "video"; type HelpQuestionsProps = { @@ -20,16 +22,25 @@ export default function HelpQuestions({ totalIndex }: HelpQuestionsProps) { const [backgroundType, setBackgroundType] = useState("text"); const quizId = Number(useParams().quizId); const { listQuestions } = questionStore(); + const question = listQuestions[quizId][totalIndex] as QuizQuestionBase; const debounced = useDebouncedCallback((value) => { - let clonContent = listQuestions[quizId][totalIndex].content; - clonContent.hint.text = value; - updateQuestionsList(quizId, totalIndex, { content: clonContent }); + updateQuestionsList(quizId, totalIndex, { + content: { + ...question.content, + hint: { text: value, video: question.content.hint.video }, + }, + }); }, 1000); const videoHC = (url: string) => { - const clonContent = listQuestions[quizId][totalIndex].content; - clonContent.hint.video = url; - updateQuestionsList(quizId, totalIndex, { content: clonContent }); + const clonedContent = { ...question.content }; + clonedContent.hint.video = url; + updateQuestionsList(quizId, totalIndex, { + content: { + ...question.content, + hint: { video: url, text: question.content.hint.text }, + }, + }); }; return ( @@ -67,7 +78,7 @@ export default function HelpQuestions({ totalIndex }: HelpQuestionsProps) { <> debounced(target.value)} /> @@ -80,12 +91,8 @@ export default function HelpQuestions({ totalIndex }: HelpQuestionsProps) { onClick={() => setOpen(true)} sx={{ justifyContent: "flex-start" }} > - {listQuestions[quizId][totalIndex].content.hint.video ? ( - diff --git a/src/pages/Result/Result.tsx b/src/pages/Result/Result.tsx index 88e38081..621a73d5 100644 --- a/src/pages/Result/Result.tsx +++ b/src/pages/Result/Result.tsx @@ -1,18 +1,18 @@ -import {Link, useParams} from "react-router-dom"; -import {Box, Button, useTheme} from "@mui/material"; +import { Link, useParams } from "react-router-dom"; +import { Box, Button, useTheme } from "@mui/material"; import CreationFullCard from "./CreationFullCard"; import Info from "../../assets/icons/Info"; import image from "../../assets/Rectangle 110.png"; -import {quizStore} from "@root/quizes"; +import { quizStore } from "@root/quizes"; export const Result = () => { - const {listQuizes, updateQuizesList} = quizStore(); - const params = Number(useParams().quizId); - const handleNext = () => { - updateQuizesList(params, {step: listQuizes[params].step + 1}) - } + const { listQuizes, updateQuizesList } = quizStore(); + const params = Number(useParams().quizId); + const handleNext = () => { + updateQuizesList(params, { createResult: true }); + }; const theme = useTheme(); return ( @@ -22,16 +22,16 @@ export const Result = () => { image={image} /> - + diff --git a/src/pages/createQuize/FirstQuiz.tsx b/src/pages/createQuize/FirstQuiz.tsx index 85b67121..4dbcc5e8 100755 --- a/src/pages/createQuize/FirstQuiz.tsx +++ b/src/pages/createQuize/FirstQuiz.tsx @@ -35,7 +35,7 @@ export default function FirstQuiz() { @@ -62,7 +62,7 @@ export default function MyQuizzesFull({outerContainerSx: sx, children}: Props) { removeQuiz(e.id) }} onClickEdit={() => - navigate(`/quize-setting/${e.id}`) + navigate(`/setting/${e.id}`) } /> ) diff --git a/src/pages/startPage/StartPage.tsx b/src/pages/startPage/StartPage.tsx index a4037374..d4944a5d 100755 --- a/src/pages/startPage/StartPage.tsx +++ b/src/pages/startPage/StartPage.tsx @@ -2,7 +2,17 @@ import Stepper from "@ui_kit/Stepper"; import SwitchStepPages from "@ui_kit/switchStepPages"; import React, { useState } from "react"; import PenaLogo from "@ui_kit/PenaLogo"; -import { Box, Button, Container, FormControl, IconButton, TextField, useMediaQuery, useTheme } from "@mui/material"; +import { + Box, + Button, + Container, + FormControl, + IconButton, + TextField, + useMediaQuery, + useTheme, +} from "@mui/material"; +import { Link } from "react-router-dom"; import BackArrowIcon from "@icons/BackArrowIcon"; import NavMenuItem from "@ui_kit/Header/NavMenuItem"; import EyeIcon from "@icons/EyeIcon"; @@ -14,6 +24,15 @@ import { Burger } from "@icons/Burger"; import { PenaLogoIcon } from "@icons/PenaLogoIcon"; import { SidebarMobile } from "./Sidebar/SidebarMobile"; +const DESCRIPTIONS = [ + "Настройка стартовой страницы", + "Задайте вопросы", + "Настройте авторезультаты", + "Настройте форму контактов", + "Установите квиз", + "Запустите рекламу", +] as const; + export default function StartPage() { const { listQuizes, updateQuizesList, removeQuiz, createBlank } = quizStore(); const params = Number(useParams().quizId); @@ -24,10 +43,6 @@ export default function StartPage() { const [mobileSidebar, setMobileSidebar] = useState(false); - const handleNext = () => { - updateQuizesList(params, { step: listQuizes[params].step + 1 }); - }; - const handleBack = () => { let result = listQuizes[params].step - 1; updateQuizesList(params, { step: result ? result : 1 }); @@ -50,7 +65,13 @@ export default function StartPage() { zIndex: theme.zIndex.drawer + 1, }} > - {isMobile ? : } + + {isMobile ? ( + + ) : ( + + )} + ) : ( )} @@ -157,7 +183,12 @@ export default function StartPage() { Опубликовать @@ -180,8 +211,16 @@ export default function StartPage() { boxSizing: "border-box", }} > - - + + diff --git a/src/pages/startPage/StartPageSettings.tsx b/src/pages/startPage/StartPageSettings.tsx index ec1239a1..cfa1b465 100755 --- a/src/pages/startPage/StartPageSettings.tsx +++ b/src/pages/startPage/StartPageSettings.tsx @@ -36,10 +36,6 @@ import AlignCenterIcon from "@icons/AlignCenterIcon"; import DropFav from "./dropfavicon"; import { createQuestion } from "@root/questions"; -interface HandleNext { - handleNext: () => void; -} - const designTypes = [ [ "standard", @@ -62,7 +58,7 @@ const designTypes = [ type BackgroundType = "image" | "video"; type AlignType = "left" | "right" | "center"; -export default function StartPageSettings({ handleNext }: HandleNext) { +export default function StartPageSettings() { const { listQuizes, updateQuizesList, removeQuiz, createBlank } = quizStore(); const params = Number(useParams().quizId); const theme = useTheme(); @@ -73,6 +69,10 @@ export default function StartPageSettings({ handleNext }: HandleNext) { ); const [alignType, setAlignType] = useState("left"); + const handleNext = () => { + updateQuizesList(params, { step: listQuizes[params].step + 1 }); + }; + const videoHC = (videoInp: HTMLInputElement) => { const file = videoInp.files?.[0]; diff --git a/src/pages/startPage/stepOne.tsx b/src/pages/startPage/stepOne.tsx index bf13c9c0..5163a984 100755 --- a/src/pages/startPage/stepOne.tsx +++ b/src/pages/startPage/stepOne.tsx @@ -2,19 +2,15 @@ import { Box, Button, Typography, useTheme } from "@mui/material"; import CreationCard from "@ui_kit/CreationCard"; import quizCreationImage1 from "../../assets/quiz-creation-1.png"; import quizCreationImage2 from "../../assets/quiz-creation-2.png"; -import {useParams} from "react-router-dom"; -import {quizStore} from "@root/quizes"; +import { useParams } from "react-router-dom"; +import { quizStore } from "@root/quizes"; -interface HandleNext { - handleNext: () => void; -} - -export default function StepOne({ handleNext }: HandleNext) { +export default function StepOne() { const theme = useTheme(); const params = Number(useParams().quizId); - const {listQuizes, updateQuizesList,} = quizStore() + const { listQuizes, updateQuizesList } = quizStore(); return ( <> - - diff --git a/src/pages/startPage/steptwo.tsx b/src/pages/startPage/steptwo.tsx index ecb4c6d4..15dd2fb5 100755 --- a/src/pages/startPage/steptwo.tsx +++ b/src/pages/startPage/steptwo.tsx @@ -6,11 +6,7 @@ import cardImage3 from "../../assets/card-3.png"; import {quizStore} from "@root/quizes"; import {useParams} from "react-router-dom"; -interface HandleNext { - handleNext: () => void -} - -export default function Steptwo ({handleNext}:HandleNext) { +export default function Steptwo () { const params = Number(useParams().quizId); const {listQuizes, updateQuizesList} = quizStore() return ( @@ -27,7 +23,6 @@ export default function Steptwo ({handleNext}:HandleNext) { + + + + + {completedCrop && ( +
+ +
)} - - - - {crop?.width ? Math.round(crop.width) + "px" : ""} - - - {crop?.height ? Math.round(crop.height) + "px" : ""} - - - - - - - - Размер - - { - setWidth(newValue as number); - }} - /> - - - - Затемнение - - setDarken(newValue as number)} - /> - - - - - - - - - - {completedCrop && ( -
- -
- )} -
- - Hidden download - -
- - ); +
+ + Hidden download + +
+ + ); }; diff --git a/src/ui_kit/QuizPreview/QuizPreview.tsx b/src/ui_kit/QuizPreview/QuizPreview.tsx new file mode 100644 index 00000000..f4cf528c --- /dev/null +++ b/src/ui_kit/QuizPreview/QuizPreview.tsx @@ -0,0 +1,130 @@ +import { PointsIcon } from "@icons/questionsPage/PointsIcon"; +import { Box, IconButton } from "@mui/material"; +import { toggleQuizPreview, useQuizPreviewStore } from "@root/quizPreview"; +import { useLayoutEffect, useRef } from "react"; +import { Rnd } from "react-rnd"; +import QuizPreviewLayout from "./QuizPreviewLayout"; +import ResizeIcon from "./ResizeIcon"; +import VisibilityIcon from '@mui/icons-material/Visibility'; + + +const DRAG_PARENT_MARGIN = 25; +const NAVBAR_HEIGHT = 81; +const DRAG_PARENT_BOTTOM_MARGIN = 65; + +interface RndPositionAndSize { + x: number; + y: number; + width: string; + height: string; +} + +export default function QuizPreview() { + const isPreviewShown = useQuizPreviewStore(state => state.isPreviewShown); + const rndParentRef = useRef(null); + const rndRef = useRef(null); + const rndPositionAndSizeRef = useRef({ x: 0, y: 0, width: "340", height: "480" }); + const isFirstShowRef = useRef(true); + + useLayoutEffect(function stickPreviewToBottomRight() { + const rnd = rndRef.current; + const rndSelfElement = rnd?.getSelfElement(); + if (!rnd || !rndSelfElement || !rndParentRef.current || !isFirstShowRef.current) return; + + const rndParentRect = rndParentRef.current.getBoundingClientRect(); + const rndRect = rndSelfElement.getBoundingClientRect(); + + const x = rndParentRect.width - rndRect.width; + const y = rndParentRect.height - rndRect.height; + + rnd.updatePosition({ x, y }); + rndPositionAndSizeRef.current.x = x; + rndPositionAndSizeRef.current.y = y; + + isFirstShowRef.current = false; + }, [isPreviewShown]); + + return ( + + {isPreviewShown && + { + rndPositionAndSizeRef.current.x = position.x; + rndPositionAndSizeRef.current.y = position.y; + rndPositionAndSizeRef.current.width = ref.style.width; + rndPositionAndSizeRef.current.height = ref.style.height; + }} + onDragStop={(e, d) => { + rndPositionAndSizeRef.current.x = d.x; + rndPositionAndSizeRef.current.y = d.y; + }} + onDragStart={(e, d) => { + e.preventDefault(); + }} + enableResizing={{ + topLeft: isPreviewShown, + }} + resizeHandleComponent={{ + topLeft: + }} + resizeHandleStyles={{ + topLeft: { + top: "-1px", + left: "-1px", + } + }} + style={{ + pointerEvents: "auto", + }} + > + + + + + + } + + + + + ); +} diff --git a/src/ui_kit/QuizPreview/QuizPreviewLayout.tsx b/src/ui_kit/QuizPreview/QuizPreviewLayout.tsx new file mode 100644 index 00000000..0c1eed29 --- /dev/null +++ b/src/ui_kit/QuizPreview/QuizPreviewLayout.tsx @@ -0,0 +1,133 @@ +import { Box, Button, LinearProgress, Paper, Typography } from "@mui/material"; +import { questionStore } from "@root/questions"; +import { decrementCurrentQuestionIndex, incrementCurrentQuestionIndex, useQuizPreviewStore } from "@root/quizPreview"; +import { AnyQuizQuestion, DefiniteQuestionType } from "model/questionTypes/shared"; +import { FC, useEffect } from "react"; +import { useParams } from "react-router-dom"; +import ArrowLeft from "../../assets/icons/questionsPage/arrowLeft"; +import Date from "./QuizPreviewQuestionTypes/Date"; +import Emoji from "./QuizPreviewQuestionTypes/Emoji"; +import File from "./QuizPreviewQuestionTypes/File"; +import Images from "./QuizPreviewQuestionTypes/Images"; +import Number from "./QuizPreviewQuestionTypes/Number"; +import Page from "./QuizPreviewQuestionTypes/Page"; +import Rating from "./QuizPreviewQuestionTypes/Rating"; +import Select from "./QuizPreviewQuestionTypes/Select"; +import Text from "./QuizPreviewQuestionTypes/Text"; +import Variant from "./QuizPreviewQuestionTypes/Variant"; +import Varimg from "./QuizPreviewQuestionTypes/Varimg"; + + +const QuestionPreviewComponentByType: Record> = { + variant: Variant, + images: Images, + varimg: Varimg, + emoji: Emoji, + text: Text, + select: Select, + date: Date, + number: Number, + file: File, + page: Page, + rating: Rating, +}; + +export default function QuizPreviewLayout() { + const quizId = useParams().quizId ?? 0; + const listQuestions = questionStore(state => state.listQuestions); + const currentQuizStep = useQuizPreviewStore(state => state.currentQuestionIndex); + + const quizQuestions: AnyQuizQuestion[] | undefined = listQuestions[quizId]; + const nonDeletedQuizQuestions = quizQuestions?.filter(question => !question.deleted); + const maxCurrentQuizStep = nonDeletedQuizQuestions?.length > 0 ? nonDeletedQuizQuestions.length - 1 : 0; + const currentProgress = Math.floor((currentQuizStep / maxCurrentQuizStep) * 100); + + const currentQuestion = nonDeletedQuizQuestions[currentQuizStep]; + const QuestionComponent = currentQuestion + ? QuestionPreviewComponentByType[currentQuestion.type as DefiniteQuestionType] + : null; + + const questionElement = QuestionComponent + ? + : null; + + useEffect(function resetCurrentQuizStep() { + if (currentQuizStep > maxCurrentQuizStep) { + decrementCurrentQuestionIndex(); + } + }, [currentQuizStep, maxCurrentQuizStep]); + + return ( + + + {questionElement} + + + + + {nonDeletedQuizQuestions.length > 0 + ? `Вопрос ${currentQuizStep + 1} из ${nonDeletedQuizQuestions.length}` + : "Нет вопросов" + } + + {nonDeletedQuizQuestions.length > 0 && + + } + + + + + + + + ); +} diff --git a/src/ui_kit/QuizPreview/QuizPreviewQuestionTypes/Date.tsx b/src/ui_kit/QuizPreview/QuizPreviewQuestionTypes/Date.tsx new file mode 100644 index 00000000..2cf95a08 --- /dev/null +++ b/src/ui_kit/QuizPreview/QuizPreviewQuestionTypes/Date.tsx @@ -0,0 +1,22 @@ +import { Box, Typography } from "@mui/material"; +import LabeledDatePicker from "@ui_kit/LabeledDatePicker"; +import { QuizQuestionDate } from "model/questionTypes/date"; + + +interface Props { + question: QuizQuestionDate; +} + +export default function Date({ question }: Props) { + + return ( + + {question.title} + + + ); +} diff --git a/src/ui_kit/QuizPreview/QuizPreviewQuestionTypes/Emoji.tsx b/src/ui_kit/QuizPreview/QuizPreviewQuestionTypes/Emoji.tsx new file mode 100644 index 00000000..38021c64 --- /dev/null +++ b/src/ui_kit/QuizPreview/QuizPreviewQuestionTypes/Emoji.tsx @@ -0,0 +1,39 @@ +import InfoIcon from "@icons/InfoIcon"; +import { Box, FormControl, FormControlLabel, FormLabel, Radio, RadioGroup, Tooltip, Typography } from "@mui/material"; +import { QuizQuestionEmoji } from "model/questionTypes/emoji"; +import { useState, ChangeEvent } from "react"; + + +interface Props { + question: QuizQuestionEmoji; +} + +export default function Emoji({ question }: Props) { + const [value, setValue] = useState(null); + + const handleChange = (event: ChangeEvent) => { + setValue((event.target as HTMLInputElement).value); + }; + + return ( + + {question.title} + + {question.content.variants.map((variant, index) => ( + } label={ + + {`${variant.extendedText} ${variant.answer}`} + + + + + } /> + ))} + + + ); +} diff --git a/src/ui_kit/QuizPreview/QuizPreviewQuestionTypes/File.tsx b/src/ui_kit/QuizPreview/QuizPreviewQuestionTypes/File.tsx new file mode 100644 index 00000000..72de0682 --- /dev/null +++ b/src/ui_kit/QuizPreview/QuizPreviewQuestionTypes/File.tsx @@ -0,0 +1,44 @@ +import { Box, Button, Typography } from "@mui/material"; +import { QuizQuestionFile } from "model/questionTypes/file"; +import { ChangeEvent, useRef, useState } from "react"; + + +interface Props { + question: QuizQuestionFile; +} + +export default function File({ question }: Props) { + const fileInputRef = useRef(null); + const [file, setFile] = useState(null); + + function handleFileChange(event: ChangeEvent) { + if (!event.target.files?.[0]) return setFile(null); + setFile(event.target.files[0]); + } + + return ( + + {question.title} + + {file && Выбран файл: {file.name}} + + ); +} diff --git a/src/ui_kit/QuizPreview/QuizPreviewQuestionTypes/Images.tsx b/src/ui_kit/QuizPreview/QuizPreviewQuestionTypes/Images.tsx new file mode 100644 index 00000000..d1bde9fc --- /dev/null +++ b/src/ui_kit/QuizPreview/QuizPreviewQuestionTypes/Images.tsx @@ -0,0 +1,90 @@ +import InfoIcon from "@icons/InfoIcon"; +import { Box, ButtonBase, Divider, Tooltip, Typography, useTheme } from "@mui/material"; +import { QuizQuestionImages } from "model/questionTypes/images"; +import { useEffect, useState } from "react"; + + +interface Props { + question: QuizQuestionImages; +} + +export default function Images({ question }: Props) { + const theme = useTheme(); + const [selectedVariants, setSelectedVariants] = useState([]); + + function handleVariantClick(index: number) { + if (!question.content.multi) return setSelectedVariants([index]); + + const newSelectedVariants = [...selectedVariants]; + if (newSelectedVariants.includes(index)) { + newSelectedVariants.splice(newSelectedVariants.indexOf(index), 1); + } else { + newSelectedVariants.push(index); + } + setSelectedVariants(newSelectedVariants); + } + + useEffect(function resetSelectedVariants() { + setSelectedVariants([]); + }, [question.content.multi]); + + return ( + + {question.title} + + {question.content.variants.map((variant, index) => ( + handleVariantClick(index)} + sx={{ + display: "flex", + flexDirection: "column", + borderRadius: "8px", + overflow: "hidden", + border: "1px solid", + borderColor: selectedVariants.includes(index) ? theme.palette.brightPurple.main : "#E3E3E3", + }} + > + {variant.extendedText ? + question variant + : + Картинка отсутствует + } + + + {variant.answer} + + + + + + + + ))} + + + ); +} diff --git a/src/ui_kit/QuizPreview/QuizPreviewQuestionTypes/Number.tsx b/src/ui_kit/QuizPreview/QuizPreviewQuestionTypes/Number.tsx new file mode 100644 index 00000000..57665991 --- /dev/null +++ b/src/ui_kit/QuizPreview/QuizPreviewQuestionTypes/Number.tsx @@ -0,0 +1,47 @@ +import { Box, Typography } from "@mui/material"; +import { CustomSlider } from "@ui_kit/CustomSlider"; +import { QuizQuestionNumber } from "model/questionTypes/number"; +import { useLayoutEffect, useState } from "react"; + + +interface Props { + question: QuizQuestionNumber; +} + +export default function Number({ question }: Props) { + const [sliderValues, setSliderValues] = useState(0); + + const start = question.content.start; + const min = parseInt(question.content.range.split("—")[0]); + const max = parseInt(question.content.range.split("—")[1]); + + useLayoutEffect(() => { + if (question.content.chooseRange) { + setSliderValues([start, start + (max - start) / 2]); + } else { + setSliderValues(start); + } + }, [max, question.content.chooseRange, start]); + + return ( + + {question.title} + + setSliderValues(value)} + min={min} + max={max} + defaultValue={start} + step={question.content.step} + /> + + + ); +} diff --git a/src/ui_kit/QuizPreview/QuizPreviewQuestionTypes/Page.tsx b/src/ui_kit/QuizPreview/QuizPreviewQuestionTypes/Page.tsx new file mode 100644 index 00000000..576a7333 --- /dev/null +++ b/src/ui_kit/QuizPreview/QuizPreviewQuestionTypes/Page.tsx @@ -0,0 +1,34 @@ +import { Box, Typography } from "@mui/material"; +import { QuizQuestionPage } from "model/questionTypes/page"; + + +interface Props { + question: QuizQuestionPage; +} + +export default function Page({ question }: Props) { + + return ( + + {question.title} + {question.content.text} + {question.content.picture && + + } + + ); +} diff --git a/src/ui_kit/QuizPreview/QuizPreviewQuestionTypes/Rating.tsx b/src/ui_kit/QuizPreview/QuizPreviewQuestionTypes/Rating.tsx new file mode 100644 index 00000000..3ea7e408 --- /dev/null +++ b/src/ui_kit/QuizPreview/QuizPreviewQuestionTypes/Rating.tsx @@ -0,0 +1,91 @@ +import { Box, Typography, useMediaQuery, useTheme } from "@mui/material"; +import { FC, useState } from "react"; +import FlagIcon from "../../../assets/icons/questionsPage/FlagIcon"; +import StarIconMini from "../../../assets/icons/questionsPage/StarIconMini"; +import HashtagIcon from "../../../assets/icons/questionsPage/hashtagIcon"; +import HeartIcon from "../../../assets/icons/questionsPage/heartIcon"; +import LightbulbIcon from "../../../assets/icons/questionsPage/lightbulbIcon"; +import LikeIcon from "../../../assets/icons/questionsPage/likeIcon"; +import TropfyIcon from "../../../assets/icons/questionsPage/tropfyIcon"; +import { QuizQuestionRating } from "model/questionTypes/rating"; + + +type RatingIconType = "star" | "trophie" | "flag" | "heart" | "like" | "bubble" | "hashtag"; + +const ratingIconComponentByType: Record> = { + "star": StarIconMini, + "trophie": TropfyIcon, + "flag": FlagIcon, + "heart": HeartIcon, + "like": LikeIcon, + "bubble": LightbulbIcon, + "hashtag": HashtagIcon, +}; + +interface Props { + question: QuizQuestionRating; +} + +export default function Rating({ question }: Props) { + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down(790)); + const [selectedRating, setSelectedRating] = useState(0); + + console.log(question); + + const RatingIconComponent = ratingIconComponentByType[question.content.form as RatingIconType]; + + return ( + + {question.title} + + + {Array.from( + { length: question.content.steps }, + (_, index) => index + ).map((itemNumber) => ( + setSelectedRating(itemNumber + 1)} + sx={{ + cursor: "pointer", + transform: "scale(1.5)", + ":hover": { + transform: "scale(1.7)", + transition: "0.2s", + }, + }} + > + itemNumber + ? theme.palette.brightPurple.main + : theme.palette.grey2.main + } /> + + ))} + + + {question.content.ratingNegativeDescription} + {question.content.ratingPositiveDescription} + + + + ); +} diff --git a/src/ui_kit/QuizPreview/QuizPreviewQuestionTypes/Select.tsx b/src/ui_kit/QuizPreview/QuizPreviewQuestionTypes/Select.tsx new file mode 100644 index 00000000..136d82aa --- /dev/null +++ b/src/ui_kit/QuizPreview/QuizPreviewQuestionTypes/Select.tsx @@ -0,0 +1,102 @@ +import ArrowDownIcon from "@icons/ArrowDownIcon"; +import { Box, FormControl, MenuItem, Select, SelectChangeEvent, Typography, useTheme } from "@mui/material"; +import { QuizQuestionSelect } from "model/questionTypes/select"; +import { useState } from "react"; + + +interface Props { + question: QuizQuestionSelect; +} + +export default function Text({ question }: Props) { + const theme = useTheme(); + const [selectValue, setSelectValue] = useState(""); + + function handleChange(event: SelectChangeEvent) { + setSelectValue((event.target as HTMLInputElement).value); + } + + return ( + + {question.title} + + + + + ); +} diff --git a/src/ui_kit/QuizPreview/QuizPreviewQuestionTypes/Text.tsx b/src/ui_kit/QuizPreview/QuizPreviewQuestionTypes/Text.tsx new file mode 100644 index 00000000..9e7f9cfe --- /dev/null +++ b/src/ui_kit/QuizPreview/QuizPreviewQuestionTypes/Text.tsx @@ -0,0 +1,24 @@ +import { Box, Typography } from "@mui/material"; +import CustomTextField from "@ui_kit/CustomTextField"; +import { QuizQuestionText } from "model/questionTypes/text"; + + +interface Props { + question: QuizQuestionText; +} + +export default function Text({ question }: Props) { + + return ( + + {question.title} + + + ); +} diff --git a/src/ui_kit/QuizPreview/QuizPreviewQuestionTypes/Variant.tsx b/src/ui_kit/QuizPreview/QuizPreviewQuestionTypes/Variant.tsx new file mode 100644 index 00000000..cded6e77 --- /dev/null +++ b/src/ui_kit/QuizPreview/QuizPreviewQuestionTypes/Variant.tsx @@ -0,0 +1,41 @@ +import InfoIcon from "@icons/InfoIcon"; +import { Box, FormControl, FormControlLabel, FormLabel, Radio, RadioGroup, Tooltip, Typography } from "@mui/material"; +import { QuizQuestionVariant } from "model/questionTypes/variant"; +import { ChangeEvent, useState } from "react"; + + +interface Props { + question: QuizQuestionVariant; +} + +export default function Variant({ question }: Props) { + const [value, setValue] = useState(null); + + const handleChange = (event: ChangeEvent) => { + setValue((event.target as HTMLInputElement).value); + }; + + return ( + + {question.title} + + {question.content.variants.map((variant, index) => ( + } label={ + + {variant.answer} + + + + + + + } /> + ))} + + + ); +} diff --git a/src/ui_kit/QuizPreview/QuizPreviewQuestionTypes/Varimg.tsx b/src/ui_kit/QuizPreview/QuizPreviewQuestionTypes/Varimg.tsx new file mode 100644 index 00000000..1383be3a --- /dev/null +++ b/src/ui_kit/QuizPreview/QuizPreviewQuestionTypes/Varimg.tsx @@ -0,0 +1,80 @@ +import InfoIcon from "@icons/InfoIcon"; +import { Box, FormControl, FormControlLabel, FormLabel, Radio, RadioGroup, Tooltip, Typography } from "@mui/material"; +import { QuestionVariant } from "model/questionTypes/shared"; +import { QuizQuestionVarImg } from "model/questionTypes/varimg"; +import { useState, ChangeEvent } from "react"; + + +interface Props { + question: QuizQuestionVarImg; +} + +export default function Varimg({ question }: Props) { + const [selectedVariantIndex, setSelectedVariantIndex] = useState(-1); + + const handleChange = (event: ChangeEvent) => { + setSelectedVariantIndex(question.content.variants.findIndex( + variant => variant.answer === event.target.value + )); + }; + + const currentVariant: QuestionVariant | undefined = question.content.variants[selectedVariantIndex]; + + return ( + + + {question.title} + + {question.content.variants.map((variant, index) => ( + } + label={ + + {variant.answer} + + + + + + + } + /> + ))} + + + + {currentVariant?.extendedText ? + question variant + : + {selectedVariantIndex === -1 ? "Выберите вариант" : "Картинка отсутствует"} + } + + + ); +} diff --git a/src/ui_kit/QuizPreview/ResizeIcon.tsx b/src/ui_kit/QuizPreview/ResizeIcon.tsx new file mode 100644 index 00000000..3d5895d6 --- /dev/null +++ b/src/ui_kit/QuizPreview/ResizeIcon.tsx @@ -0,0 +1,26 @@ +import { Box, SxProps, Theme } from "@mui/material"; + + +interface Props { + sx?: SxProps; +} + +export default function ResizeIcon({ sx }: Props) { + + return ( + + + + + + ); +} diff --git a/src/ui_kit/Sidebar.tsx b/src/ui_kit/Sidebar.tsx index c84968b8..318604f4 100755 --- a/src/ui_kit/Sidebar.tsx +++ b/src/ui_kit/Sidebar.tsx @@ -1,5 +1,15 @@ import { useState } from "react"; -import { Container, Box, useTheme, List, Typography, IconButton } from "@mui/material"; +import { useParams } from "react-router-dom"; +import { + Container, + Box, + useTheme, + List, + Typography, + IconButton, +} from "@mui/material"; + +import { quizStore } from "@root/quizes"; import MegaphoneIcon from "../assets/icons/MegaphoneIcon"; import QuestionIcon from "../assets/icons/QuestionIcon"; @@ -33,8 +43,9 @@ const quizSettingsMenuItems = [ export default function Sidebar() { const theme = useTheme(); const [isMenuCollapsed, setIsMenuCollapsed] = useState(false); - const [activeMenuItemIndex, setActiveMenuItemIndex] = useState(0); const [progress, setProgress] = useState(1 / 6); + const quizId = Number(useParams().quizId); + const { listQuizes, updateQuizesList } = quizStore(); const handleMenuCollapseToggle = () => setIsMenuCollapsed((prev) => !prev); return ( @@ -76,7 +87,10 @@ export default function Sidebar() { Создание квиза )} - + setActiveMenuItemIndex(index)} + onClick={() => { + updateQuizesList(quizId, { step: index + 1 }); + }} key={menuItem[1]} text={menuItem[1]} isCollapsed={isMenuCollapsed} - isActive={activeMenuItemIndex === index} + isActive={listQuizes[quizId].step === index + 1} icon={ { const Icon = menuItem[0]; const totalIndex = index + createQuizMenuItems.length; - const isActive = activeMenuItemIndex === totalIndex; + const isActive = listQuizes[quizId].step === totalIndex + 1; return ( setActiveMenuItemIndex(totalIndex)} + onClick={() => updateQuizesList(quizId, { step: totalIndex + 1 })} key={menuItem[1]} text={menuItem[1]} isActive={isActive} @@ -142,7 +158,11 @@ export default function Sidebar() { icon={ void; + quizType: string; + startpage: string; + createResult: boolean; } -export default function SwitchStepPages({ activeStep = 1, handleNext }: Props) { +export default function SwitchStepPages({ + activeStep = 1, + quizType, + startpage, + createResult, +}: Props) { switch (activeStep) { case 1: - return ; + if (!quizType) return ; + if (quizType === "form") return <>; + if (!startpage) return ; + return ; case 2: - return ; - case 3: - return ; - case 4: + if (quizType === "form") return ; return ; - case 5: - return ; - case 6: + case 3: + if (!createResult) return ; return ; - case 7: + case 4: return ; - case 8: + case 5: return ; + case 6: + return <>Реклама; + default: return <>; } diff --git a/yarn.lock b/yarn.lock index 9d0545b3..66325392 100755 --- a/yarn.lock +++ b/yarn.lock @@ -1034,12 +1034,12 @@ core-js-pure "^3.25.1" regenerator-runtime "^0.13.11" -"@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.15.4", "@babel/runtime@^7.16.3", "@babel/runtime@^7.18.3", "@babel/runtime@^7.18.9", "@babel/runtime@^7.20.1", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": - version "7.20.6" - resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.6.tgz" - integrity sha512-Q+8MqP7TiHMWzSfwiJwXCjyf4GYA4Dgw3emg/7xmwsdLJOZUp+nMqcOwOzzYheuM1rhDu8FSj2l0aoMygEuXuA== +"@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.15.4", "@babel/runtime@^7.16.3", "@babel/runtime@^7.18.3", "@babel/runtime@^7.18.9", "@babel/runtime@^7.20.1", "@babel/runtime@^7.23.1", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": + version "7.23.2" + resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.2.tgz" + integrity sha512-mM8eg4yl5D6i3lu2QKPuPH4FArvJ8KhTofbE7jwMUv9KX5mBvwPAqnV3MlyBNqdp9RyRKP6Yck8TrfYrPvX3bg== dependencies: - regenerator-runtime "^0.13.11" + regenerator-runtime "^0.14.0" "@babel/template@^7.18.10", "@babel/template@^7.20.7", "@babel/template@^7.3.3": version "7.20.7" @@ -1267,7 +1267,7 @@ resolved "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.0.tgz" integrity sha512-G/YwXTkv7Den9mXDO7AhLWkE3q+I92B+VqAE+dYG4NGPaHZGvt3G8Q0p9vmE+sq7rTGphUbAvmQ9YpbfMQGGlA== -"@emotion/react@^11.0.0-rc.0", "@emotion/react@^11.10.5", "@emotion/react@^11.4.1", "@emotion/react@^11.5.0": +"@emotion/react@^11.0.0-rc.0", "@emotion/react@^11.10.5", "@emotion/react@^11.4.1", "@emotion/react@^11.5.0", "@emotion/react@^11.9.0": version "11.10.5" resolved "https://registry.npmjs.org/@emotion/react/-/react-11.10.5.tgz" integrity sha512-TZs6235tCJ/7iF6/rvTaOH4oxQg2gMAcdHemjwLKIjKz4rRuYe1HJ2TQJKnAcRAfOUDdU8XoDadCe1rl72iv8A== @@ -1297,7 +1297,7 @@ resolved "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.2.1.tgz" integrity sha512-zxRBwl93sHMsOj4zs+OslQKg/uhF38MB+OMKoCrVuS0nyTkqnau+BM3WGEoOptg9Oz45T/aIGs1qbVAsEFo3nA== -"@emotion/styled@^11.10.5", "@emotion/styled@^11.3.0": +"@emotion/styled@^11.10.5", "@emotion/styled@^11.3.0", "@emotion/styled@^11.8.1": version "11.10.5" resolved "https://registry.npmjs.org/@emotion/styled/-/styled-11.10.5.tgz" integrity sha512-8EP6dD7dMkdku2foLoruPCNkRevzdcBaY6q0l0OsbyJK+x8D9HWjX27ARiSIKNF634hY9Zdoedh8bJCiva8yZw== @@ -1344,6 +1344,33 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" +"@floating-ui/core@^1.4.2": + version "1.5.0" + resolved "https://registry.npmjs.org/@floating-ui/core/-/core-1.5.0.tgz" + integrity sha512-kK1h4m36DQ0UHGj5Ah4db7R0rHemTqqO0QLvUqi1/mUUp3LuAWbWxdxSIf/XsnH9VS6rRVPLJCncjRzUvyCLXg== + dependencies: + "@floating-ui/utils" "^0.1.3" + +"@floating-ui/dom@^1.5.1": + version "1.5.3" + resolved "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.5.3.tgz" + integrity sha512-ClAbQnEqJAKCJOEbbLo5IUlZHkNszqhuxS4fHAVxRPXPya6Ysf2G8KypnYcOTpx6I8xcgF9bbHb6g/2KpbV8qA== + dependencies: + "@floating-ui/core" "^1.4.2" + "@floating-ui/utils" "^0.1.3" + +"@floating-ui/react-dom@^2.0.2": + version "2.0.2" + resolved "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.2.tgz" + integrity sha512-5qhlDvjaLmAst/rKb3VdlCinwTF4EYMiVxuuc/HVUjs46W0zgtbMmAZ1UTsDrRTxRmUEzl92mOtWbeeXL26lSQ== + dependencies: + "@floating-ui/dom" "^1.5.1" + +"@floating-ui/utils@^0.1.3": + version "0.1.6" + resolved "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.1.6.tgz" + integrity sha512-OfX7E2oUDYxtBvsuS4e/jSn4Q9Qb6DzgeYtsAdkPZ47znpoNsMgZw0+tVijiv3uGNR6dgNlty6r9rzIzHjtd/A== + "@humanwhocodes/config-array@^0.11.6": version "0.11.7" resolved "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.7.tgz" @@ -1659,6 +1686,19 @@ resolved "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz" integrity sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A== +"@mui/base@^5.0.0-beta.17": + version "5.0.0-beta.19" + resolved "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.19.tgz" + integrity sha512-maNBgAscddyPNzFZQUJDF/puxM27Li+NqSBsr/lAP8TLns2VvWS2SoL3OKFOIoRnAMKGY/Ic6Aot6gCYeQnssA== + dependencies: + "@babel/runtime" "^7.23.1" + "@floating-ui/react-dom" "^2.0.2" + "@mui/types" "^7.2.6" + "@mui/utils" "^5.14.13" + "@popperjs/core" "^2.11.8" + clsx "^2.0.0" + prop-types "^15.8.1" + "@mui/base@5.0.0-alpha.108": version "5.0.0-alpha.108" resolved "https://registry.npmjs.org/@mui/base/-/base-5.0.0-alpha.108.tgz" @@ -1685,7 +1725,7 @@ dependencies: "@babel/runtime" "^7.20.1" -"@mui/material@^5.0.0", "@mui/material@^5.10.14": +"@mui/material@^5.0.0", "@mui/material@^5.10.14", "@mui/material@^5.8.6": version "5.10.16" resolved "https://registry.npmjs.org/@mui/material/-/material-5.10.16.tgz" integrity sha512-JSHcDQQ+k30NKkCM/0KX6jq4F5LOrbFKZpS+cEl7scZWOCJpUPH5ccAT5a7O8wzrgNZ8Y9PnwzNvWBrfShpJFw== @@ -1722,7 +1762,7 @@ csstype "^3.1.1" prop-types "^15.8.1" -"@mui/system@^5.10.16": +"@mui/system@^5.10.16", "@mui/system@^5.8.0": version "5.10.16" resolved "https://registry.npmjs.org/@mui/system/-/system-5.10.16.tgz" integrity sha512-OqI9B1jZ9zQ/dmoqseku4CzdEs9DbLiiMOaWxC3WeAJxM1UavlCgXz0encqm93LIlmSL7TjuHN1/rW8BJCnU8A== @@ -1736,22 +1776,34 @@ csstype "^3.1.1" prop-types "^15.8.1" -"@mui/types@^7.2.2": - version "7.2.2" - resolved "https://registry.npmjs.org/@mui/types/-/types-7.2.2.tgz" - integrity sha512-siex8cZDtWeC916cXOoUOnEQQejuMYmHtc4hM6VkKVYaBICz3VIiqyiAomRboTQHt2jchxQ5Q5ATlbcDekTxDA== +"@mui/types@^7.2.2", "@mui/types@^7.2.6": + version "7.2.6" + resolved "https://registry.npmjs.org/@mui/types/-/types-7.2.6.tgz" + integrity sha512-7sjLQrUmBwufm/M7jw/quNiPK/oor2+pGUQP2CULRcFCArYTq78oJ3D5esTaL0UMkXKJvDqXn6Ike69yAOBQng== -"@mui/utils@^5.10.16": - version "5.10.16" - resolved "https://registry.npmjs.org/@mui/utils/-/utils-5.10.16.tgz" - integrity sha512-3MB/SGsgiiu9Z55CFmAfiONUoR7AAue/H4F6w3mc2LnhFQCsoVvXhioDPcsiRpUMIQr34jDPzGXdCuqWooPCXQ== +"@mui/utils@^5.10.16", "@mui/utils@^5.14.11", "@mui/utils@^5.14.13": + version "5.14.13" + resolved "https://registry.npmjs.org/@mui/utils/-/utils-5.14.13.tgz" + integrity sha512-2AFpyXWw7uDCIqRu7eU2i/EplZtks5LAMzQvIhC79sPV9IhOZU2qwOWVnPtdctRXiQJOAaXulg+A37pfhEueQw== dependencies: - "@babel/runtime" "^7.20.1" - "@types/prop-types" "^15.7.5" - "@types/react-is" "^16.7.1 || ^17.0.0" + "@babel/runtime" "^7.23.1" + "@types/prop-types" "^15.7.7" prop-types "^15.8.1" react-is "^18.2.0" +"@mui/x-date-pickers@^6.16.1": + version "6.16.2" + resolved "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-6.16.2.tgz" + integrity sha512-JFrDUeBkiKtfJ0WqwyPBICEP1U+Ujfsily3ZQ/Hv4zAOleG/5769EgS7TOO4cVgnuhtvQ/pqx2gmuCn8/gcC5w== + dependencies: + "@babel/runtime" "^7.23.1" + "@mui/base" "^5.0.0-beta.17" + "@mui/utils" "^5.14.11" + "@types/react-transition-group" "^4.4.7" + clsx "^2.0.0" + prop-types "^15.8.1" + react-transition-group "^4.4.5" + "@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1": version "5.1.1-v1" resolved "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz" @@ -1795,10 +1847,10 @@ schema-utils "^3.0.0" source-map "^0.7.3" -"@popperjs/core@^2.11.6": - version "2.11.6" - resolved "https://registry.npmjs.org/@popperjs/core/-/core-2.11.6.tgz" - integrity sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw== +"@popperjs/core@^2.11.6", "@popperjs/core@^2.11.8": + version "2.11.8" + resolved "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz" + integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A== "@react-dnd/asap@^5.0.1": version "5.0.2" @@ -2276,10 +2328,10 @@ resolved "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.1.tgz" integrity sha512-ri0UmynRRvZiiUJdiz38MmIblKK+oH30MztdBVR95dv/Ubw6neWSb8u1XpRb72L4qsZOhz+L+z9JD40SJmfWow== -"@types/prop-types@*", "@types/prop-types@^15.7.5": - version "15.7.5" - resolved "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz" - integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w== +"@types/prop-types@*", "@types/prop-types@^15.7.7": + version "15.7.8" + resolved "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.8.tgz" + integrity sha512-kMpQpfZKSCBqltAJwskgePRaYRFukDkm1oItcAbC3gNELR20XIBcN9VRgg4+m8DKsTfkWeA4m4Imp4DDuWy7FQ== "@types/q@^1.5.1": version "1.5.5" @@ -2317,13 +2369,6 @@ dependencies: "@types/react" "*" -"@types/react-is@^16.7.1 || ^17.0.0": - version "17.0.3" - resolved "https://registry.npmjs.org/@types/react-is/-/react-is-17.0.3.tgz" - integrity sha512-aBTIWg1emtu95bLTLx0cpkxwGW3ueZv71nE2YFBpL8k/z5czEW8yYpOo8Dp+UUAFAtKwNaOsh/ioSeQnWlZcfw== - dependencies: - "@types/react" "*" - "@types/react-redux@^7.1.20": version "7.1.27" resolved "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.27.tgz" @@ -2334,10 +2379,10 @@ hoist-non-react-statics "^3.3.0" redux "^4.0.0" -"@types/react-transition-group@^4.4.5": - version "4.4.5" - resolved "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.5.tgz" - integrity sha512-juKD/eiSM3/xZYzjuzH6ZwpP+/lejltmiS3QEzV/vmb/Q8+HfDmxu+Baga8UEMGBqV88Nbg4l2hY/K2DkyaLLA== +"@types/react-transition-group@^4.4.5", "@types/react-transition-group@^4.4.7": + version "4.4.7" + resolved "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.7.tgz" + integrity sha512-ICCyBl5mvyqYp8Qeq9B5G/fyBSRC0zx3XM3sCC6KkcMsNeAHqXBKkmat4GqdJET5jtYUpZXrxI5flve5qhi2Eg== dependencies: "@types/react" "*" @@ -3467,11 +3512,16 @@ clone-deep@^4.0.1: kind-of "^6.0.2" shallow-clone "^3.0.0" -clsx@^1.1.0, clsx@^1.2.1: +clsx@^1.1.0, clsx@^1.1.1, clsx@^1.2.1: version "1.2.1" resolved "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz" integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg== +clsx@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz" + integrity sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q== + co@^4.6.0: version "4.6.0" resolved "https://registry.npmjs.org/co/-/co-4.6.0.tgz" @@ -3929,6 +3979,11 @@ data-urls@^2.0.0: whatwg-mimetype "^2.3.0" whatwg-url "^8.0.0" +dayjs@^1.10.7, dayjs@^1.11.10: + version "1.11.10" + resolved "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz" + integrity sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ== + debug@^2.6.0: version "2.6.9" resolved "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz" @@ -4805,6 +4860,11 @@ fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.6: resolved "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz" integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== +fast-memoize@^2.5.1: + version "2.5.2" + resolved "https://registry.npmjs.org/fast-memoize/-/fast-memoize-2.5.2.tgz" + integrity sha512-Ue0LwpDYErFbmNnZSF0UH6eImUwDmogUO1jyE+JbN2gsQz/jICm1Ve7t9QT0rNSsfJt+Hs4/S3GnsDVjL4HVrw== + fastq@^1.6.0: version "1.14.0" resolved "https://registry.npmjs.org/fastq/-/fastq-1.14.0.tgz" @@ -5456,10 +5516,15 @@ immediate@~3.0.5: resolved "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz" integrity sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ== -immer@^9.0.7, immer@>=9.0: - version "9.0.16" - resolved "https://registry.npmjs.org/immer/-/immer-9.0.16.tgz" - integrity sha512-qenGE7CstVm1NrHQbMh8YaSzTZTFNP3zPqr3YU0S0UY441j4bJTg4A2Hh5KAhwgaiU6ZZ1Ar6y/2f4TblnMReQ== +immer@^10.0.3, immer@>=9.0: + version "10.0.3" + resolved "https://registry.npmjs.org/immer/-/immer-10.0.3.tgz" + integrity sha512-pwupu3eWfouuaowscykeckFmVTpqbzW+rXFCX8rQLkZzM9ftBmU/++Ra+o+L27mz03zJTlyV4UUr+fdKNffo4A== + +immer@^9.0.7: + version "9.0.21" + resolved "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz" + integrity sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA== import-fresh@^3.0.0, import-fresh@^3.1.0, import-fresh@^3.2.1: version "3.3.0" @@ -7928,6 +7993,13 @@ raw-body@2.5.1: iconv-lite "0.4.24" unpipe "1.0.0" +re-resizable@6.9.6: + version "6.9.6" + resolved "https://registry.npmjs.org/re-resizable/-/re-resizable-6.9.6.tgz" + integrity sha512-0xYKS5+Z0zk+vICQlcZW+g54CcJTTmHluA7JUUgvERDxnKAnytylcyPsA+BSFi759s5hPlHmBRegFrwXs2FuBQ== + dependencies: + fast-memoize "^2.5.1" + react-app-polyfill@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/react-app-polyfill/-/react-app-polyfill-3.0.0.tgz" @@ -8001,7 +8073,7 @@ react-dnd@*, react-dnd@^16.0.1: fast-deep-equal "^3.1.3" hoist-non-react-statics "^3.3.2" -"react-dom@^16.8.0 || ^17.0.0 || ^18.0.0", "react-dom@^16.8.5 || ^17.0.0 || ^18.0.0", "react-dom@^17.0.0 || ^18.0.0", react-dom@^18.0.0, react-dom@^18.2.0, react-dom@>=16.4.0, react-dom@>=16.6.0, react-dom@>=16.8: +"react-dom@^16.13.1 || ^17.0.0 || ^18.0.0", "react-dom@^16.8.0 || ^17.0.0 || ^18.0.0", "react-dom@^16.8.5 || ^17.0.0 || ^18.0.0", "react-dom@^17.0.0 || ^18.0.0", react-dom@^18.0.0, react-dom@^18.2.0, "react-dom@>= 16.3.0", react-dom@>=16.3.0, react-dom@>=16.4.0, react-dom@>=16.6.0, react-dom@>=16.8, react-dom@>=16.8.0: version "18.2.0" resolved "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz" integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g== @@ -8009,6 +8081,14 @@ react-dnd@*, react-dnd@^16.0.1: loose-envify "^1.1.0" scheduler "^0.23.0" +react-draggable@4.4.5: + version "4.4.5" + resolved "https://registry.npmjs.org/react-draggable/-/react-draggable-4.4.5.tgz" + integrity sha512-OMHzJdyJbYTZo4uQE393fHcqqPYsEtkjfMgvCHr6rejT+Ezn4OZbNyGH50vv+SunC1RMvwOTSWkEODQLzw1M9g== + dependencies: + clsx "^1.1.1" + prop-types "^15.8.1" + react-easy-crop@^5.0.0: version "5.0.2" resolved "https://registry.npmjs.org/react-easy-crop/-/react-easy-crop-5.0.2.tgz" @@ -8074,6 +8154,15 @@ react-refresh@^0.11.0, "react-refresh@>=0.10.0 <1.0.0": resolved "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz" integrity sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A== +react-rnd@^10.4.1: + version "10.4.1" + resolved "https://registry.npmjs.org/react-rnd/-/react-rnd-10.4.1.tgz" + integrity sha512-0m887AjQZr6p2ADLNnipquqsDq4XJu/uqVqI3zuoGD19tRm6uB83HmZWydtkilNp5EWsOHbLGF4IjWMdd5du8Q== + dependencies: + re-resizable "6.9.6" + react-draggable "4.4.5" + tslib "2.3.1" + react-router-dom@^6.6.2: version "6.8.0" resolved "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.8.0.tgz" @@ -8154,7 +8243,7 @@ react-transition-group@^4.4.5: loose-envify "^1.4.0" prop-types "^15.6.2" -"react@^16.8 || ^17 || ^18", "react@^16.8.0 || ^17.0.0 || ^18.0.0", "react@^16.8.3 || ^17 || ^18", "react@^16.8.5 || ^17.0.0 || ^18.0.0", "react@^17.0.0 || ^18.0.0", react@^18.0.0, react@^18.2.0, "react@>= 16", "react@>= 16.14", react@>=16.13.1, react@>=16.4.0, react@>=16.6.0, react@>=16.8, react@>=16.8.0: +"react@^16.13.1 || ^17.0.0 || ^18.0.0", "react@^16.8 || ^17 || ^18", "react@^16.8.0 || ^17.0.0 || ^18.0.0", "react@^16.8.3 || ^17 || ^18", "react@^16.8.5 || ^17.0.0 || ^18.0.0", "react@^17.0.0 || ^18.0.0", react@^18.0.0, react@^18.2.0, "react@>= 16", "react@>= 16.14", "react@>= 16.3.0", react@>=16.13.1, react@>=16.3.0, react@>=16.4.0, react@>=16.6.0, react@>=16.8, react@>=16.8.0: version "18.2.0" resolved "https://registry.npmjs.org/react/-/react-18.2.0.tgz" integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ== @@ -8249,6 +8338,11 @@ regenerator-runtime@^0.13.11, regenerator-runtime@^0.13.9: resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz" integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg== +regenerator-runtime@^0.14.0: + version "0.14.0" + resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz" + integrity sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA== + regenerator-transform@^0.15.1: version "0.15.1" resolved "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.1.tgz" @@ -9271,6 +9365,11 @@ tslib@2.0.1: resolved "https://registry.npmjs.org/tslib/-/tslib-2.0.1.tgz" integrity sha512-SgIkNheinmEBgx1IUNirK0TUD4X9yjjBRTqqjggWCU3pUEqIk3/Uwl3yRixYKT6WjQuGiwDv4NomL3wqRCj+CQ== +tslib@2.3.1: + version "2.3.1" + resolved "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz" + integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw== + tsutils@^3.21.0: version "3.21.0" resolved "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz"