merge adapter and quiz-preview
This commit is contained in:
commit
32e0a9f73b
453
package-lock.json
generated
453
package-lock.json
generated
@ -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",
|
||||
|
@ -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",
|
||||
|
39
src/assets/icons/CalendarIcon.tsx
Normal file
39
src/assets/icons/CalendarIcon.tsx
Normal file
@ -0,0 +1,39 @@
|
||||
import { Box } from "@mui/material";
|
||||
|
||||
export default function CalendarIcon() {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
flexShrink: 0,
|
||||
"&:hover path": {
|
||||
stroke: "#581CA7",
|
||||
},
|
||||
"&:active path": {
|
||||
stroke: "#FB5607",
|
||||
},
|
||||
"&:hover rect": {
|
||||
stroke: "#581CA7",
|
||||
},
|
||||
"&:active rect": {
|
||||
stroke: "#FB5607",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<svg width="20" height="22" viewBox="0 0 20 22" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="1" y="2.5" width="18" height="18" rx="5" stroke="#7E2AEA" strokeWidth="1.5" />
|
||||
<path d="M1 7.5H19" stroke="#7E2AEA" strokeWidth="1.5" strokeLinejoin="round" />
|
||||
<path d="M14.5 1L14.5 4" stroke="#7E2AEA" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
||||
<path d="M5.5 1L5.5 4" stroke="#7E2AEA" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
||||
<path d="M4.5 11.5H5.5" stroke="#7E2AEA" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
||||
<path d="M9.5 11.5H10.5" stroke="#7E2AEA" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
||||
<path d="M14.5 11.5H15.5" stroke="#7E2AEA" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
||||
<path d="M4.5 15.5H5.5" stroke="#7E2AEA" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
||||
<path d="M9.5 15.5H10.5" stroke="#7E2AEA" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
||||
<path d="M14.5 15.5H15.5" stroke="#7E2AEA" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</svg>
|
||||
</Box>
|
||||
);
|
||||
}
|
@ -1,7 +1,16 @@
|
||||
export default function CrossedEyeIcon() {
|
||||
import { FC, SVGProps } from "react";
|
||||
|
||||
export const CrossedEyeIcon: FC<SVGProps<SVGSVGElement>> = (props) => {
|
||||
return (
|
||||
<svg width="30" height="31" viewBox="0 0 30 31" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect y="0.8125" width="30" height="30" rx="6" fill="#252734" />
|
||||
<svg
|
||||
{...props}
|
||||
width="30"
|
||||
height="30"
|
||||
viewBox="0 0 30 30"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<rect y="0.8125" width="30" height="30" rx="6" fill="#FFF" />
|
||||
<path
|
||||
d="M7.5 7.5625L22.5 24.0625"
|
||||
stroke="#7E2AEA"
|
||||
@ -39,4 +48,4 @@ export default function CrossedEyeIcon() {
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
@ -2,11 +2,17 @@ import { Box } from "@mui/material";
|
||||
import { FC, SVGProps } from "react";
|
||||
|
||||
export const OneIcon: FC<SVGProps<SVGSVGElement>> = (props) => (
|
||||
<svg {...props} height="1em" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="30" height="30" rx="15" fill="#EEE4FC" />
|
||||
<svg
|
||||
fill="#7E2AEA"
|
||||
height="1em"
|
||||
viewBox="0 0 30 30"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<rect width="30" height="30" rx="15" />
|
||||
<path
|
||||
d="M15.864 19.5C15.7707 19.5 15.6913 19.472 15.626 19.416C15.57 19.3507 15.542 19.2713 15.542 19.178V11.338L13.218 13.13C13.1433 13.186 13.064 13.2093 12.98 13.2C12.896 13.1907 12.826 13.1487 12.77 13.074L12.406 12.612C12.35 12.528 12.3267 12.444 12.336 12.36C12.3547 12.276 12.4013 12.206 12.476 12.15L15.528 9.798C15.5933 9.75133 15.654 9.72333 15.71 9.714C15.766 9.70467 15.8267 9.7 15.892 9.7H16.606C16.6993 9.7 16.774 9.73267 16.83 9.798C16.886 9.854 16.914 9.92867 16.914 10.022V19.178C16.914 19.2713 16.886 19.3507 16.83 19.416C16.774 19.472 16.6993 19.5 16.606 19.5H15.864Z"
|
||||
fill="#7E2AEA"
|
||||
fill={props.style?.color || "#7E2AEA"}
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
34
src/assets/icons/questionsPage/addAnswer.tsx
Normal file
34
src/assets/icons/questionsPage/addAnswer.tsx
Normal file
@ -0,0 +1,34 @@
|
||||
import { Box, SxProps, Theme } from "@mui/material";
|
||||
|
||||
interface Props {
|
||||
color: string;
|
||||
sx?: SxProps<Theme>;
|
||||
}
|
||||
export default function AddAnswer({ color, sx }: Props) {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
height: "38px",
|
||||
width: "45px",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
...sx,
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
width="32"
|
||||
height="32"
|
||||
viewBox="0 0 32 32"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<rect width="32" height="32" rx="8" fill={color} />
|
||||
<path
|
||||
d="M15.518 20.612C15.398 20.612 15.296 20.576 15.212 20.504C15.14 20.42 15.104 20.318 15.104 20.198V16.454H11.414C11.294 16.454 11.192 16.418 11.108 16.346C11.036 16.262 11 16.16 11 16.04V15.464C11 15.344 11.036 15.248 11.108 15.176C11.192 15.092 11.294 15.05 11.414 15.05H15.104V11.414C15.104 11.294 15.14 11.198 15.212 11.126C15.296 11.042 15.398 11 15.518 11H16.148C16.268 11 16.364 11.042 16.436 11.126C16.52 11.198 16.562 11.294 16.562 11.414V15.05H20.27C20.39 15.05 20.486 15.092 20.558 15.176C20.642 15.248 20.684 15.344 20.684 15.464V16.04C20.684 16.16 20.642 16.262 20.558 16.346C20.486 16.418 20.39 16.454 20.27 16.454H16.562V20.198C16.562 20.318 16.52 20.42 16.436 20.504C16.364 20.576 16.268 20.612 16.148 20.612H15.518Z"
|
||||
fill="#7E2AEA"
|
||||
/>
|
||||
</svg>
|
||||
</Box>
|
||||
);
|
||||
}
|
@ -8,8 +8,8 @@ export default function AddEmoji() {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
height: "38px",
|
||||
width: "45px",
|
||||
height: "40px",
|
||||
width: "60px",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
@ -37,24 +37,24 @@ export default function AddEmoji() {
|
||||
<path
|
||||
d="M20 31C21.0949 30.9993 22.1837 30.8371 23.2313 30.5187L31 20C31 17.8244 30.3549 15.6977 29.1462 13.8887C27.9375 12.0798 26.2195 10.6699 24.2095 9.83733C22.1995 9.00477 19.9878 8.78693 17.854 9.21137C15.7202 9.6358 13.7602 10.6835 12.2218 12.2218C10.6835 13.7602 9.6358 15.7202 9.21137 17.854C8.78693 19.9878 9.00477 22.1995 9.83733 24.2095C10.6699 26.2195 12.0798 27.9375 13.8887 29.1462C15.6977 30.3549 17.8244 31 20 31Z"
|
||||
stroke="#7E2AEA"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M15.875 19.083C16.6344 19.083 17.25 18.4674 17.25 17.708C17.25 16.9486 16.6344 16.333 15.875 16.333C15.1156 16.333 14.5 16.9486 14.5 17.708C14.5 18.4674 15.1156 19.083 15.875 19.083Z"
|
||||
d="M15.875 19.084C16.6344 19.084 17.25 18.4684 17.25 17.709C17.25 16.9496 16.6344 16.334 15.875 16.334C15.1156 16.334 14.5 16.9496 14.5 17.709C14.5 18.4684 15.1156 19.084 15.875 19.084Z"
|
||||
fill="#7E2AEA"
|
||||
/>
|
||||
<path
|
||||
d="M24.125 19.083C24.8844 19.083 25.5 18.4674 25.5 17.708C25.5 16.9486 24.8844 16.333 24.125 16.333C23.3656 16.333 22.75 16.9486 22.75 17.708C22.75 18.4674 23.3656 19.083 24.125 19.083Z"
|
||||
d="M24.125 19.084C24.8844 19.084 25.5 18.4684 25.5 17.709C25.5 16.9496 24.8844 16.334 24.125 16.334C23.3656 16.334 22.75 16.9496 22.75 17.709C22.75 18.4684 23.3656 19.084 24.125 19.084Z"
|
||||
fill="#7E2AEA"
|
||||
/>
|
||||
<path
|
||||
d="M24.7677 22.75C24.2831 23.5849 23.5878 24.2778 22.7512 24.7595C21.9147 25.2412 20.9663 25.4947 20.001 25.4947C19.0357 25.4947 18.0874 25.2412 17.2508 24.7595C16.4143 24.2778 15.719 23.5849 15.2344 22.75"
|
||||
stroke="#7E2AEA"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</Box>
|
||||
|
43
src/assets/icons/questionsPage/answerGroup.tsx
Normal file
43
src/assets/icons/questionsPage/answerGroup.tsx
Normal file
@ -0,0 +1,43 @@
|
||||
import { Box, SxProps, Theme } from "@mui/material";
|
||||
|
||||
interface Props {
|
||||
color: string;
|
||||
sx?: SxProps<Theme>;
|
||||
}
|
||||
export default function AnswerGroup({ color, sx }: Props) {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
height: "38px",
|
||||
width: "45px",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
...sx,
|
||||
}}
|
||||
>
|
||||
<svg viewBox="0 0 24 22" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M1 20V2C1 1.44772 1.44772 1 2 1H22C22.5523 1 23 1.44772 23 2V20C23 20.5523 22.5523 21 22 21H2C1.44772 21 1 20.5523 1 20Z"
|
||||
stroke={color}
|
||||
stroke-width="1.5"
|
||||
/>
|
||||
<path
|
||||
d="M3 6V4.5C3 3.94772 3.44772 3.5 4 3.5H10C10.5523 3.5 11 3.94772 11 4.5V6C11 6.55228 10.5523 7 10 7H4C3.44772 7 3 6.55228 3 6Z"
|
||||
stroke={color}
|
||||
stroke-width="1.5"
|
||||
/>
|
||||
<path
|
||||
d="M13 6V4.5C13 3.94772 13.4477 3.5 14 3.5H20C20.5523 3.5 21 3.94772 21 4.5V6C21 6.55228 20.5523 7 20 7H14C13.4477 7 13 6.55228 13 6Z"
|
||||
stroke={color}
|
||||
stroke-width="1.5"
|
||||
/>
|
||||
<path
|
||||
d="M3 12.5V11C3 10.4477 3.44772 10 4 10H20C20.5523 10 21 10.4477 21 11V12.5C21 13.0523 20.5523 13.5 20 13.5H4C3.44772 13.5 3 13.0523 3 12.5Z"
|
||||
stroke={color}
|
||||
stroke-width="1.5"
|
||||
/>
|
||||
</svg>
|
||||
</Box>
|
||||
);
|
||||
}
|
29
src/constants/base.ts
Normal file
29
src/constants/base.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import type { QuizQuestionInitial } from "../model/questionTypes/shared";
|
||||
|
||||
export const QUIZ_QUESTION_BASE: Omit<QuizQuestionInitial, "id"> = {
|
||||
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,
|
||||
},
|
||||
};
|
16
src/constants/date.ts
Normal file
16
src/constants/date.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { QUIZ_QUESTION_BASE } from "./base";
|
||||
|
||||
import type { QuizQuestionDate } from "../model/questionTypes/date";
|
||||
|
||||
export const QUIZ_QUESTION_DATE: Omit<QuizQuestionDate, "id"> = {
|
||||
...QUIZ_QUESTION_BASE,
|
||||
type: "date",
|
||||
content: {
|
||||
...QUIZ_QUESTION_BASE.content,
|
||||
required: false,
|
||||
innerNameCheck: false,
|
||||
innerName: "",
|
||||
dateRange: false,
|
||||
time: false,
|
||||
},
|
||||
};
|
23
src/constants/emoji.ts
Normal file
23
src/constants/emoji.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { QUIZ_QUESTION_BASE } from "./base";
|
||||
|
||||
import type { QuizQuestionEmoji } from "../model/questionTypes/emoji";
|
||||
|
||||
export const QUIZ_QUESTION_EMOJI: Omit<QuizQuestionEmoji, "id"> = {
|
||||
...QUIZ_QUESTION_BASE,
|
||||
type: "emoji",
|
||||
content: {
|
||||
...QUIZ_QUESTION_BASE.content,
|
||||
multi: false,
|
||||
own: false,
|
||||
innerNameCheck: false,
|
||||
innerName: "",
|
||||
required: false,
|
||||
variants: [
|
||||
{
|
||||
answer: "",
|
||||
hints: "",
|
||||
extendedText: "",
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
15
src/constants/file.ts
Normal file
15
src/constants/file.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { QUIZ_QUESTION_BASE } from "./base";
|
||||
|
||||
import type { QuizQuestionFile } from "../model/questionTypes/file";
|
||||
|
||||
export const QUIZ_QUESTION_FILE: Omit<QuizQuestionFile, "id"> = {
|
||||
...QUIZ_QUESTION_BASE,
|
||||
type: "file",
|
||||
content: {
|
||||
...QUIZ_QUESTION_BASE.content,
|
||||
required: false,
|
||||
innerNameCheck: false,
|
||||
innerName: "",
|
||||
type: "all",
|
||||
},
|
||||
};
|
27
src/constants/images.ts
Normal file
27
src/constants/images.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import { QUIZ_QUESTION_BASE } from "./base";
|
||||
|
||||
import type { QuizQuestionImages } from "../model/questionTypes/images";
|
||||
|
||||
export const QUIZ_QUESTION_IMAGES: Omit<QuizQuestionImages, "id"> = {
|
||||
...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,
|
||||
},
|
||||
};
|
21
src/constants/number.ts
Normal file
21
src/constants/number.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { QUIZ_QUESTION_BASE } from "./base";
|
||||
|
||||
import type { QuizQuestionNumber } from "../model/questionTypes/number";
|
||||
|
||||
export const QUIZ_QUESTION_NUMBER: Omit<QuizQuestionNumber, "id"> = {
|
||||
...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",
|
||||
},
|
||||
};
|
16
src/constants/page.ts
Normal file
16
src/constants/page.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { QUIZ_QUESTION_BASE } from "./base";
|
||||
|
||||
import type { QuizQuestionPage } from "../model/questionTypes/page";
|
||||
|
||||
export const QUIZ_QUESTION_PAGE: Omit<QuizQuestionPage, "id"> = {
|
||||
...QUIZ_QUESTION_BASE,
|
||||
type: "page",
|
||||
content: {
|
||||
...QUIZ_QUESTION_BASE.content,
|
||||
innerNameCheck: false,
|
||||
innerName: "",
|
||||
text: "",
|
||||
picture: "",
|
||||
video: "",
|
||||
},
|
||||
};
|
19
src/constants/rating.ts
Normal file
19
src/constants/rating.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { QUIZ_QUESTION_BASE } from "./base";
|
||||
|
||||
import type { QuizQuestionRating } from "../model/questionTypes/rating";
|
||||
|
||||
export const QUIZ_QUESTION_RATING: Omit<QuizQuestionRating, "id"> = {
|
||||
...QUIZ_QUESTION_BASE,
|
||||
type: "rating",
|
||||
content: {
|
||||
...QUIZ_QUESTION_BASE.content,
|
||||
required: false,
|
||||
innerNameCheck: false,
|
||||
innerName: "",
|
||||
steps: 5,
|
||||
ratingExpanded: false,
|
||||
form: "star",
|
||||
ratingNegativeDescription: "",
|
||||
ratingPositiveDescription: "",
|
||||
},
|
||||
};
|
17
src/constants/select.ts
Normal file
17
src/constants/select.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { QUIZ_QUESTION_BASE } from "./base";
|
||||
|
||||
import type { QuizQuestionSelect } from "../model/questionTypes/select";
|
||||
|
||||
export const QUIZ_QUESTION_SELECT: Omit<QuizQuestionSelect, "id"> = {
|
||||
...QUIZ_QUESTION_BASE,
|
||||
type: "select",
|
||||
content: {
|
||||
...QUIZ_QUESTION_BASE.content,
|
||||
multi: false,
|
||||
required: false,
|
||||
innerNameCheck: false,
|
||||
innerName: "",
|
||||
default: "",
|
||||
variants: [{ answer: "", hints: "", extendedText: "" }],
|
||||
},
|
||||
};
|
16
src/constants/text.ts
Normal file
16
src/constants/text.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { QUIZ_QUESTION_BASE } from "./base";
|
||||
|
||||
import type { QuizQuestionText } from "../model/questionTypes/text";
|
||||
|
||||
export const QUIZ_QUESTION_TEXT: Omit<QuizQuestionText, "id"> = {
|
||||
...QUIZ_QUESTION_BASE,
|
||||
type: "text",
|
||||
content: {
|
||||
...QUIZ_QUESTION_BASE.content,
|
||||
placeholder: "",
|
||||
innerNameCheck: false,
|
||||
innerName: "",
|
||||
required: false,
|
||||
answerType: "single",
|
||||
},
|
||||
};
|
18
src/constants/variant.ts
Normal file
18
src/constants/variant.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { QUIZ_QUESTION_BASE } from "./base";
|
||||
|
||||
import type { QuizQuestionVariant } from "../model/questionTypes/variant";
|
||||
|
||||
export const QUIZ_QUESTION_VARIANT: Omit<QuizQuestionVariant, "id"> = {
|
||||
...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: "" }],
|
||||
},
|
||||
};
|
18
src/constants/varimg.ts
Normal file
18
src/constants/varimg.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { QUIZ_QUESTION_BASE } from "./base";
|
||||
|
||||
import type { QuizQuestionVarImg } from "../model/questionTypes/varimg";
|
||||
|
||||
export const QUIZ_QUESTION_VARIMG: Omit<QuizQuestionVarImg, "id"> = {
|
||||
...QUIZ_QUESTION_BASE,
|
||||
type: "varimg",
|
||||
content: {
|
||||
...QUIZ_QUESTION_BASE.content,
|
||||
own: false,
|
||||
innerNameCheck: false,
|
||||
innerName: "",
|
||||
required: false,
|
||||
variants: [{ answer: "", hints: "", extendedText: "" }],
|
||||
largeCheck: false,
|
||||
replText: "",
|
||||
},
|
||||
};
|
@ -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: <MyQuizzesFull />, header: false, sidebar: false },
|
||||
{ path: "/questions/:quizId", page: <QuestionsPage />, header: true, sidebar: true,},
|
||||
@ -37,21 +46,23 @@ const routeslink: {
|
||||
const root = createRoot(document.getElementById("root")!);
|
||||
|
||||
root.render(
|
||||
<DndProvider backend={HTML5Backend}>
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
<SnackbarProvider>
|
||||
<ContactFormModal />
|
||||
<BrowserRouter>
|
||||
<Routes>
|
||||
{routeslink.map((e, i) => (
|
||||
<Route key={i} path={e.path} element={<Main page={e.page} header={e.header} sidebar={e.sidebar} />} />
|
||||
))}
|
||||
<Route path="quize-setting/:quizId" element={<StartPage />} />
|
||||
<Route path="crop" element={<ImageCrop />} />
|
||||
<Route path="/" element={<Landing/>}/>
|
||||
</Routes>
|
||||
</BrowserRouter>
|
||||
</SnackbarProvider>
|
||||
</ThemeProvider>
|
||||
</DndProvider>
|
||||
<DndProvider backend={HTML5Backend}>
|
||||
<LocalizationProvider dateAdapter={AdapterDayjs} adapterLocale="ru" localeText={localeText}>
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
<SnackbarProvider>
|
||||
<ContactFormModal />
|
||||
<BrowserRouter>
|
||||
<Routes>
|
||||
{routeslink.map((e, i) => (
|
||||
<Route key={i} path={e.path} element={<Main page={e.page} header={e.header} sidebar={e.sidebar} />} />
|
||||
))}
|
||||
<Route path="setting/:quizId" element={<StartPage />} />
|
||||
<Route path="crop" element={<ImageCrop />} />
|
||||
<Route path="/" element={<Landing/>}/>
|
||||
</Routes>
|
||||
</BrowserRouter>
|
||||
</SnackbarProvider>
|
||||
</ThemeProvider>
|
||||
</LocalizationProvider>
|
||||
</DndProvider>
|
||||
);
|
||||
|
23
src/model/questionTypes/date.ts
Normal file
23
src/model/questionTypes/date.ts
Normal file
@ -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;
|
||||
};
|
||||
}
|
27
src/model/questionTypes/emoji.ts
Normal file
27
src/model/questionTypes/emoji.ts
Normal file
@ -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;
|
||||
};
|
||||
}
|
33
src/model/questionTypes/file.ts
Normal file
33
src/model/questionTypes/file.ts
Normal file
@ -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;
|
||||
};
|
||||
}
|
35
src/model/questionTypes/images.ts
Normal file
35
src/model/questionTypes/images.ts
Normal file
@ -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;
|
||||
};
|
||||
}
|
33
src/model/questionTypes/number.ts
Normal file
33
src/model/questionTypes/number.ts
Normal file
@ -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";
|
||||
};
|
||||
}
|
22
src/model/questionTypes/page.ts
Normal file
22
src/model/questionTypes/page.ts
Normal file
@ -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;
|
||||
};
|
||||
}
|
29
src/model/questionTypes/rating.ts
Normal file
29
src/model/questionTypes/rating.ts
Normal file
@ -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;
|
||||
};
|
||||
}
|
27
src/model/questionTypes/select.ts
Normal file
27
src/model/questionTypes/select.ts
Normal file
@ -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;
|
||||
};
|
||||
}
|
77
src/model/questionTypes/shared.ts
Normal file
77
src/model/questionTypes/shared.ts
Normal file
@ -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<QuizQuestionType, "nonselected">;
|
24
src/model/questionTypes/text.ts
Normal file
24
src/model/questionTypes/text.ts
Normal file
@ -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;
|
||||
};
|
||||
}
|
30
src/model/questionTypes/variant.ts
Normal file
30
src/model/questionTypes/variant.ts
Normal file
@ -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;
|
||||
};
|
||||
}
|
27
src/model/questionTypes/varimg.ts
Normal file
27
src/model/questionTypes/varimg.ts
Normal file
@ -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;
|
||||
};
|
||||
}
|
@ -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 (
|
||||
<>
|
||||
<Stepper activeStep={activeStep} desc={"Настройте форму контактов"} />
|
||||
<Box sx={{ display: "flex", alignItems: "center", gap: "10px" }}>
|
||||
<Link
|
||||
sx={{
|
||||
|
@ -1,4 +1,5 @@
|
||||
import React, { useState } from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
@ -18,7 +19,7 @@ import {
|
||||
Tooltip,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
import Stepper from "@ui_kit/Stepper";
|
||||
import { quizStore } from "@root/quizes";
|
||||
import LinkIcon from "../../assets/icons/LinkIcon";
|
||||
import InfoIcon from "../../assets/icons/InfoIcon";
|
||||
import ArrowDown from "../../assets/icons/ArrowDownIcon";
|
||||
@ -70,17 +71,8 @@ export default function InstallQuiz() {
|
||||
},
|
||||
];
|
||||
|
||||
const [activeStep, setActiveStep] = React.useState(5);
|
||||
|
||||
const handleNext = () => {
|
||||
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<BackgroundType>("text");
|
||||
|
||||
const theme = useTheme();
|
||||
const { listQuizes, updateQuizesList } = quizStore();
|
||||
|
||||
const handleNext = () => {
|
||||
updateQuizesList(quizId, { step: listQuizes[quizId].step + 1 });
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Stepper activeStep={activeStep} desc={"Установите квиз"} />
|
||||
<Box sx={{ marginTop: "60px", display: "flex", gap: "40px" }}>
|
||||
<Paper
|
||||
sx={{
|
||||
@ -416,7 +413,7 @@ export default function InstallQuiz() {
|
||||
>
|
||||
<ArrowLeft />
|
||||
</Button>
|
||||
<Button variant="contained">Запустить рекламу</Button>
|
||||
<Button variant="contained" onClick={handleNext}>Запустить рекламу</Button>
|
||||
</Box>
|
||||
|
||||
<Modal
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { useState, useRef } from "react";
|
||||
import { useState } from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { Draggable } from "react-beautiful-dnd";
|
||||
import {
|
||||
@ -21,13 +21,14 @@ import { MessageIcon } from "@icons/messagIcon";
|
||||
import TextareaAutosize from "@mui/base/TextareaAutosize";
|
||||
|
||||
import type { ChangeEvent, KeyboardEvent, ReactNode } from "react";
|
||||
import type { Variants } from "@root/questions";
|
||||
import type { QuizQuestionVariant } from "../../../model/questionTypes/variant";
|
||||
import type { QuestionVariant } from "../../../model/questionTypes/shared";
|
||||
|
||||
type AnswerItemProps = {
|
||||
index: number;
|
||||
totalIndex: number;
|
||||
variants: Variants[];
|
||||
variant: Variants;
|
||||
variants: QuestionVariant[];
|
||||
variant: QuestionVariant;
|
||||
additionalContent?: ReactNode;
|
||||
additionalMobile?: ReactNode;
|
||||
icon?: ReactNode;
|
||||
@ -40,19 +41,19 @@ export const AnswerItem = ({
|
||||
variant,
|
||||
additionalContent,
|
||||
additionalMobile,
|
||||
icon,
|
||||
}: AnswerItemProps) => {
|
||||
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<QuizQuestionVariant>(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<QuizQuestionVariant>(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<QuizQuestionVariant>(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<QuizQuestionVariant>(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<HTMLInputElement>) => {
|
||||
if (
|
||||
event.code === "Enter" &&
|
||||
!listQuestions[quizId][totalIndex].content.largeCheck
|
||||
) {
|
||||
if (event.code === "Enter" && !question.content.largeCheck) {
|
||||
addNewAnswer();
|
||||
}
|
||||
}}
|
||||
@ -166,13 +155,11 @@ export const AnswerItem = ({
|
||||
/>
|
||||
</IconButton>
|
||||
<Popover
|
||||
id="my-popover-id"
|
||||
open={isOpen}
|
||||
anchorEl={anchorEl}
|
||||
onClose={handleClose}
|
||||
anchorOrigin={{
|
||||
vertical: "bottom",
|
||||
horizontal: "left",
|
||||
}}
|
||||
anchorOrigin={{ vertical: "bottom", horizontal: "left" }}
|
||||
>
|
||||
<TextareaAutosize
|
||||
style={{ margin: "10px" }}
|
||||
@ -197,10 +184,15 @@ export const AnswerItem = ({
|
||||
}}
|
||||
sx={{
|
||||
"& .MuiInputBase-root": {
|
||||
padding: icon ? "5px 13.5px" : "13.5px 13.5px 13.5px 9px",
|
||||
borderRadius: "8px",
|
||||
height: "48px",
|
||||
padding: additionalContent ? "5px 13px" : "13px",
|
||||
borderRadius: "10px",
|
||||
background: "#ffffff",
|
||||
"& input.MuiInputBase-input": {
|
||||
height: "22px",
|
||||
},
|
||||
"& textarea.MuiInputBase-input": {
|
||||
marginTop: "1px",
|
||||
},
|
||||
"& .MuiOutlinedInput-notchedOutline": {
|
||||
border: "none",
|
||||
},
|
||||
|
@ -10,13 +10,13 @@ import { reorder } from "./helper";
|
||||
|
||||
import type { ReactNode } from "react";
|
||||
import type { DropResult } from "react-beautiful-dnd";
|
||||
import type { Variants } from "@root/questions";
|
||||
import type { QuestionVariant } from "../../../model/questionTypes/shared";
|
||||
|
||||
type AnswerDraggableListProps = {
|
||||
variants: Variants[];
|
||||
variants: QuestionVariant[];
|
||||
totalIndex: number;
|
||||
additionalContent?: (variant: Variants, index: number) => ReactNode;
|
||||
additionalMobile?: (variant: Variants, index: number) => ReactNode;
|
||||
additionalContent?: (variant: QuestionVariant, index: number) => ReactNode;
|
||||
additionalMobile?: (variant: QuestionVariant, index: number) => ReactNode;
|
||||
};
|
||||
|
||||
export const AnswerDraggableList = ({
|
||||
|
@ -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<boolean>(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({
|
||||
<Tooltip
|
||||
arrow
|
||||
placement="right"
|
||||
componentsProps={{
|
||||
tooltip: {
|
||||
sx: {
|
||||
background: "#fff",
|
||||
borderRadius: "6px",
|
||||
color: "#9A9AAF",
|
||||
boxShadow: "0px 8px 24px rgba(210, 208, 225, 0.4)",
|
||||
"& .MuiTooltip-arrow": {
|
||||
color: "#FFF",
|
||||
},
|
||||
},
|
||||
},
|
||||
}}
|
||||
title={
|
||||
<Box>
|
||||
<Typography
|
||||
@ -157,69 +192,80 @@ export default function ButtonsOptions({
|
||||
: 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}
|
||||
</MiniButtonSetting>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<MiniButtonSetting
|
||||
key={title}
|
||||
onClick={() => {
|
||||
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}
|
||||
</MiniButtonSetting>
|
||||
<>
|
||||
<MiniButtonSetting
|
||||
key={title}
|
||||
onClick={() => {
|
||||
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}
|
||||
</MiniButtonSetting>
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
))}
|
||||
<MiniButtonSetting
|
||||
onClick={() => setOpenedReallyChangingModal(true)}
|
||||
sx={{
|
||||
minWidth: "30px",
|
||||
height: "30px",
|
||||
backgroundColor: "#FEDFD0",
|
||||
}}
|
||||
>
|
||||
<DoubleTick style={{ color: "#FC712F", fontSize: "9px" }} />
|
||||
</MiniButtonSetting>
|
||||
<MiniButtonSetting
|
||||
onClick={() => setOpenedReallyChangingModal(true)}
|
||||
sx={{
|
||||
minWidth: "30px",
|
||||
height: "30px",
|
||||
backgroundColor: "#FEDFD0",
|
||||
}}
|
||||
>
|
||||
<DoubleArrowRight style={{ color: "#FC712F", fontSize: "9px" }} />
|
||||
</MiniButtonSetting>
|
||||
<MiniButtonSetting
|
||||
onClick={() => setOpenedReallyChangingModal(true)}
|
||||
sx={{
|
||||
minWidth: "30px",
|
||||
height: "30px",
|
||||
backgroundColor: "#FEDFD0",
|
||||
}}
|
||||
>
|
||||
<VectorQuestions style={{ color: "#FC712F", fontSize: "9px" }} />
|
||||
</MiniButtonSetting>
|
||||
<>
|
||||
<MiniButtonSetting
|
||||
onClick={() => setOpenedReallyChangingModal(true)}
|
||||
sx={{
|
||||
minWidth: "30px",
|
||||
height: "30px",
|
||||
backgroundColor: "#FEDFD0",
|
||||
}}
|
||||
>
|
||||
<DoubleTick style={{ color: "#FC712F", fontSize: "9px" }} />
|
||||
</MiniButtonSetting>
|
||||
<MiniButtonSetting
|
||||
onClick={() => setOpenedReallyChangingModal(true)}
|
||||
sx={{
|
||||
minWidth: "30px",
|
||||
height: "30px",
|
||||
backgroundColor: "#FEDFD0",
|
||||
}}
|
||||
>
|
||||
<DoubleArrowRight style={{ color: "#FC712F", fontSize: "9px" }} />
|
||||
</MiniButtonSetting>
|
||||
<MiniButtonSetting
|
||||
onClick={() => setOpenedReallyChangingModal(true)}
|
||||
sx={{
|
||||
minWidth: "30px",
|
||||
height: "30px",
|
||||
backgroundColor: "#FEDFD0",
|
||||
}}
|
||||
>
|
||||
<VectorQuestions style={{ color: "#FC712F", fontSize: "9px" }} />
|
||||
</MiniButtonSetting>
|
||||
</>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
@ -239,9 +285,9 @@ export default function ButtonsOptions({
|
||||
<IconButton
|
||||
sx={{ borderRadius: "6px", padding: "2px" }}
|
||||
onClick={() => {
|
||||
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<QuizQuestionBase>(quizId, totalIndex, {
|
||||
...question,
|
||||
deleteTimeoutId: newTimeoutId,
|
||||
});
|
||||
}}
|
||||
@ -261,4 +307,4 @@ export default function ButtonsOptions({
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
@ -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<string>("");
|
||||
const quizId = Number(useParams().quizId);
|
||||
const { listQuizes } = quizStore();
|
||||
const { listQuestions } = questionStore();
|
||||
const [openedReallyChangingModal, setOpenedReallyChangingModal] =
|
||||
useState<boolean>(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 : "Подсказка"}
|
||||
</MiniButtonSetting>
|
||||
<Tooltip
|
||||
arrow
|
||||
placement="right"
|
||||
title={
|
||||
<Box>
|
||||
<Typography
|
||||
sx={{
|
||||
color: "#4D4D4D",
|
||||
fontWeight: "bold",
|
||||
fontSize: "14px",
|
||||
marginBottom: "10px",
|
||||
}}
|
||||
>
|
||||
Будет показан при условии
|
||||
</Typography>
|
||||
<Typography sx={{ fontWeight: "bold", fontSize: "12px" }}>Название</Typography>
|
||||
<Typography
|
||||
sx={{
|
||||
fontWeight: "bold",
|
||||
fontSize: "12px",
|
||||
marginBottom: "10px",
|
||||
}}
|
||||
>
|
||||
Условие 1, Условие 2
|
||||
</Typography>
|
||||
<Typography sx={{ color: "#7E2AEA", fontSize: "12px" }}>Все условия обязательны</Typography>
|
||||
</Box>
|
||||
}
|
||||
>
|
||||
<>
|
||||
<Tooltip
|
||||
arrow
|
||||
placement="right"
|
||||
componentsProps={{
|
||||
tooltip: {
|
||||
sx: {
|
||||
background: "#fff",
|
||||
borderRadius: "6px",
|
||||
color: "#9A9AAF",
|
||||
boxShadow: "0px 8px 24px rgba(210, 208, 225, 0.4)",
|
||||
"& .MuiTooltip-arrow": {
|
||||
color: "#FFF",
|
||||
},
|
||||
},
|
||||
},
|
||||
}}
|
||||
title={
|
||||
<Box>
|
||||
<Typography
|
||||
sx={{
|
||||
color: "#4D4D4D",
|
||||
fontWeight: "bold",
|
||||
fontSize: "14px",
|
||||
marginBottom: "10px",
|
||||
}}
|
||||
>
|
||||
Будет показан при условии
|
||||
</Typography>
|
||||
<Typography sx={{ fontWeight: "bold", fontSize: "12px" }}>
|
||||
Название
|
||||
</Typography>
|
||||
<Typography
|
||||
sx={{
|
||||
fontWeight: "bold",
|
||||
fontSize: "12px",
|
||||
marginBottom: "10px",
|
||||
}}
|
||||
>
|
||||
Условие 1, Условие 2
|
||||
</Typography>
|
||||
<Typography sx={{ color: "#7E2AEA", fontSize: "12px" }}>
|
||||
Все условия обязательны
|
||||
</Typography>
|
||||
</Box>
|
||||
}
|
||||
>
|
||||
<MiniButtonSetting
|
||||
onMouseEnter={() => 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,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Branching
|
||||
color={
|
||||
buttonHover === "branching"
|
||||
? theme.palette.grey3.main
|
||||
: switchState === "branching"
|
||||
? "#ffffff"
|
||||
: theme.palette.grey3.main
|
||||
}
|
||||
/>
|
||||
{isIconMobile ? null : "Ветвление"}
|
||||
</MiniButtonSetting>
|
||||
</Tooltip>
|
||||
<MiniButtonSetting
|
||||
onMouseEnter={() => 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,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Branching
|
||||
<ImgIcon
|
||||
color={
|
||||
buttonHover === "branching"
|
||||
buttonHover === "image"
|
||||
? theme.palette.grey3.main
|
||||
: switchState === "branching"
|
||||
: switchState === "image"
|
||||
? "#ffffff"
|
||||
: theme.palette.grey3.main
|
||||
}
|
||||
/>
|
||||
{isIconMobile ? null : "Ветвление"}
|
||||
{isIconMobile ? null : "Изображение"}
|
||||
</MiniButtonSetting>
|
||||
</Tooltip>
|
||||
<MiniButtonSetting
|
||||
onMouseEnter={() => 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,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<ImgIcon
|
||||
color={
|
||||
buttonHover === "image"
|
||||
? theme.palette.grey3.main
|
||||
: switchState === "image"
|
||||
? "#ffffff"
|
||||
: theme.palette.grey3.main
|
||||
}
|
||||
/>
|
||||
{isIconMobile ? null : "Изображение"}
|
||||
</MiniButtonSetting>
|
||||
<MiniButtonSetting
|
||||
onClick={() => setOpenedReallyChangingModal(true)}
|
||||
sx={{
|
||||
minWidth: "30px",
|
||||
height: "30px",
|
||||
backgroundColor: "#FEDFD0",
|
||||
}}
|
||||
>
|
||||
<DoubleTick style={{ color: "#FC712F", fontSize: "9px" }} />
|
||||
</MiniButtonSetting>
|
||||
<MiniButtonSetting
|
||||
onClick={() => setOpenedReallyChangingModal(true)}
|
||||
sx={{
|
||||
minWidth: "30px",
|
||||
height: "30px",
|
||||
backgroundColor: "#FEDFD0",
|
||||
}}
|
||||
>
|
||||
<DoubleArrowRight style={{ color: "#FC712F", fontSize: "9px" }} />
|
||||
</MiniButtonSetting>
|
||||
<MiniButtonSetting
|
||||
onClick={() => setOpenedReallyChangingModal(true)}
|
||||
sx={{
|
||||
minWidth: "30px",
|
||||
height: "30px",
|
||||
backgroundColor: "#FEDFD0",
|
||||
}}
|
||||
>
|
||||
<VectorQuestions style={{ color: "#FC712F", fontSize: "9px" }} />
|
||||
</MiniButtonSetting>
|
||||
<MiniButtonSetting
|
||||
onClick={() => setOpenedReallyChangingModal(true)}
|
||||
sx={{
|
||||
minWidth: "30px",
|
||||
height: "30px",
|
||||
backgroundColor: "#FEDFD0",
|
||||
}}
|
||||
>
|
||||
<DoubleTick style={{ color: "#FC712F", fontSize: "9px" }} />
|
||||
</MiniButtonSetting>
|
||||
<MiniButtonSetting
|
||||
onClick={() => setOpenedReallyChangingModal(true)}
|
||||
sx={{
|
||||
minWidth: "30px",
|
||||
height: "30px",
|
||||
backgroundColor: "#FEDFD0",
|
||||
}}
|
||||
>
|
||||
<DoubleArrowRight style={{ color: "#FC712F", fontSize: "9px" }} />
|
||||
</MiniButtonSetting>
|
||||
<MiniButtonSetting
|
||||
onClick={() => setOpenedReallyChangingModal(true)}
|
||||
sx={{
|
||||
minWidth: "30px",
|
||||
height: "30px",
|
||||
backgroundColor: "#FEDFD0",
|
||||
}}
|
||||
>
|
||||
<VectorQuestions style={{ color: "#FC712F", fontSize: "9px" }} />
|
||||
</MiniButtonSetting>
|
||||
</>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
@ -251,21 +308,18 @@ export default function ButtonsOptionsAndPict({ SSHC, switchState, totalIndex }:
|
||||
<IconButton sx={{ borderRadius: "6px", padding: "0px 2px" }}>
|
||||
<HideIcon style={{ color: "#4D4D4D" }} />
|
||||
</IconButton>
|
||||
<IconButton sx={{ borderRadius: "6px" }} onClick={() => copyQuestion(quizId, totalIndex)}>
|
||||
<CopyIcon style={{ color: "#4D4D4D" }} />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
sx={{ borderRadius: "6px", padding: " 0px 2px" }}
|
||||
sx={{ borderRadius: "6px" }}
|
||||
onClick={() => copyQuestion(quizId, totalIndex)}
|
||||
>
|
||||
<CopyIcon color={"#4D4D4D"} />
|
||||
<CopyIcon style={{ color: "#4D4D4D" }} />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
sx={{ borderRadius: "6px", padding: "2px" }}
|
||||
onClick={() => {
|
||||
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<QuizQuestionBase>(quizId, totalIndex, {
|
||||
...question,
|
||||
deleteTimeoutId: newTimeoutId,
|
||||
});
|
||||
}}
|
||||
|
@ -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 (
|
||||
<>
|
||||
<Box
|
||||
@ -43,31 +31,7 @@ export default function DataOptions({ totalIndex }: Props) {
|
||||
gap: isMobile ? "18px" : "20px",
|
||||
}}
|
||||
>
|
||||
<Box sx={{ gap: "10px", display: "flex", flexWrap: "wrap" }}>
|
||||
<SelectableButton
|
||||
isSelected={listQuestions[quizId][totalIndex].content.type === "calendar"}
|
||||
onClick={() => {
|
||||
const clonContent = listQuestions[quizId][totalIndex].content;
|
||||
clonContent.type = "calendar";
|
||||
updateQuestionsList(quizId, totalIndex, { content: clonContent });
|
||||
}}
|
||||
sx={{ maxWidth: "257px", height: "48px", whiteSpace: "nowrap" }}
|
||||
>
|
||||
Использовать календарь
|
||||
</SelectableButton>
|
||||
<SelectableButton
|
||||
isSelected={listQuestions[quizId][totalIndex].content.type === "mask"}
|
||||
onClick={() => {
|
||||
const clonContent = listQuestions[quizId][totalIndex].content;
|
||||
clonContent.type = "mask";
|
||||
updateQuestionsList(quizId, totalIndex, { content: clonContent });
|
||||
}}
|
||||
sx={{ maxWidth: "211px", height: "48px", whiteSpace: "nowrap" }}
|
||||
>
|
||||
Использовать маску
|
||||
</SelectableButton>
|
||||
</Box>
|
||||
<Box sx={{ display: "flex", alignItems: "center", gap: "12px", mb: "20px" }}>
|
||||
<Box sx={{ display: "flex", alignItems: "flex-start", gap: "12px", mb: "20px" }}>
|
||||
<Typography
|
||||
sx={{
|
||||
fontWeight: 400,
|
||||
|
@ -6,6 +6,8 @@ import CustomTextField from "@ui_kit/CustomTextField";
|
||||
import InfoIcon from "../../../assets/icons/InfoIcon";
|
||||
import { questionStore, updateQuestionsList } from "@root/questions";
|
||||
|
||||
import type { QuizQuestionDate } from "../../../model/questionTypes/date";
|
||||
|
||||
type SettingsDataProps = {
|
||||
totalIndex: number;
|
||||
};
|
||||
@ -16,12 +18,13 @@ export default function SettingsData({ totalIndex }: SettingsDataProps) {
|
||||
const theme = useTheme();
|
||||
const isWrappColumn = useMediaQuery(theme.breakpoints.down(980));
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||
const question = listQuestions[quizId][totalIndex] as QuizQuestionDate;
|
||||
const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990));
|
||||
|
||||
const debounced = useDebouncedCallback((value) => {
|
||||
let clonContent = listQuestions[quizId][totalIndex].content;
|
||||
clonContent.innerName = value;
|
||||
updateQuestionsList(quizId, totalIndex, { content: clonContent });
|
||||
updateQuestionsList<QuizQuestionDate>(quizId, totalIndex, {
|
||||
content: { ...question.content, innerName: value },
|
||||
});
|
||||
}, 1000);
|
||||
|
||||
return (
|
||||
@ -34,7 +37,7 @@ export default function SettingsData({ totalIndex }: SettingsDataProps) {
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
pt: isMobile ? "25px" : "20px",
|
||||
pt: "20px",
|
||||
pb: isMobile ? "25px" : "20px",
|
||||
pl: "20px",
|
||||
display: "flex",
|
||||
@ -49,21 +52,21 @@ export default function SettingsData({ totalIndex }: SettingsDataProps) {
|
||||
<CustomCheckbox
|
||||
sx={{ display: "block", mr: isMobile ? "0px" : "16px" }}
|
||||
label={"Выбор диапазона дат"}
|
||||
checked={listQuestions[quizId][totalIndex].content.dateRange}
|
||||
checked={question.content.dateRange}
|
||||
handleChange={({ target }) => {
|
||||
const clonContent = listQuestions[quizId][totalIndex].content;
|
||||
clonContent.dateRange = target.checked;
|
||||
updateQuestionsList(quizId, totalIndex, { content: clonContent });
|
||||
updateQuestionsList<QuizQuestionDate>(quizId, totalIndex, {
|
||||
content: { ...question.content, dateRange: target.checked },
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<CustomCheckbox
|
||||
sx={{ display: "block", mr: isMobile ? "0px" : "16px" }}
|
||||
label={"Выбор времени"}
|
||||
checked={listQuestions[quizId][totalIndex].content.time}
|
||||
checked={question.content.time}
|
||||
handleChange={({ target }) => {
|
||||
const clonContent = listQuestions[quizId][totalIndex].content;
|
||||
clonContent.time = target.checked;
|
||||
updateQuestionsList(quizId, totalIndex, { content: clonContent });
|
||||
updateQuestionsList<QuizQuestionDate>(quizId, totalIndex, {
|
||||
content: { ...question.content, time: target.checked },
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
@ -76,7 +79,7 @@ export default function SettingsData({ totalIndex }: SettingsDataProps) {
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: isMobile ? "13px" : "14px",
|
||||
width: "100%",
|
||||
width: isMobile ? "auto" : "100%",
|
||||
}}
|
||||
>
|
||||
<Typography sx={{ height: isMobile ? "18px" : "auto", fontWeight: "500", fontSize: "18px", color: " #4D4D4D" }}>
|
||||
@ -85,27 +88,36 @@ export default function SettingsData({ totalIndex }: SettingsDataProps) {
|
||||
<CustomCheckbox
|
||||
sx={{ mr: isMobile ? "0px" : "16px" }}
|
||||
label={"Необязательный вопрос"}
|
||||
checked={!listQuestions[quizId][totalIndex].required}
|
||||
handleChange={(e) => {
|
||||
updateQuestionsList(quizId, totalIndex, {
|
||||
required: !e.target.checked,
|
||||
checked={!question.required}
|
||||
handleChange={({ target }) => {
|
||||
updateQuestionsList<QuizQuestionDate>(quizId, totalIndex, {
|
||||
required: !target.checked,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<Box sx={{ width: isMobile ? "93%" : "auto", display: "flex", alignItems: "center" }}>
|
||||
<Box
|
||||
sx={{
|
||||
width: isMobile ? "93%" : "auto",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<CustomCheckbox
|
||||
sx={{ mr: isMobile ? "0px" : "16px" }}
|
||||
sx={{
|
||||
mr: isMobile ? "0px" : "16px",
|
||||
height: isMobile ? "100%" : "26px",
|
||||
alignItems: isMobile ? "flex-start" : "center",
|
||||
}}
|
||||
label={"Внутреннее название вопроса"}
|
||||
checked={listQuestions[quizId][totalIndex].content.innerNameCheck}
|
||||
handleChange={(e) => {
|
||||
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<QuizQuestionDate>(quizId, totalIndex, {
|
||||
content: {
|
||||
...question.content,
|
||||
innerNameCheck: target.checked,
|
||||
innerName: target.checked ? question.content.innerName : "",
|
||||
},
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<Tooltip title="Будет отображаться как заголовок вопроса в приходящих заявках." placement="top">
|
||||
@ -114,10 +126,10 @@ export default function SettingsData({ totalIndex }: SettingsDataProps) {
|
||||
</Box>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
{listQuestions[quizId][totalIndex].content.innerNameCheck && (
|
||||
{question.content.innerNameCheck && (
|
||||
<CustomTextField
|
||||
placeholder={"Развёрнутое описание вопроса"}
|
||||
text={listQuestions[quizId][totalIndex].content.innerName}
|
||||
text={question.content.innerName}
|
||||
onChange={({ target }) => debounced(target.value)}
|
||||
/>
|
||||
)}
|
||||
|
@ -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<boolean>(false);
|
||||
const [selectedValue, setSelectedValue] = useState<string>("");
|
||||
const [selectedValue, setSelectedValue] = useState<QuizQuestionType>("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<QuizQuestionBase>(quizId, totalIndex, {
|
||||
expanded: question.expanded,
|
||||
type: selectedValue,
|
||||
});
|
||||
}}
|
||||
>
|
||||
|
@ -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 (
|
||||
<Draggable draggableId={String(index)} index={index}>
|
||||
{(provided) => (
|
||||
<ListItem
|
||||
ref={provided.innerRef}
|
||||
{...provided.draggableProps}
|
||||
sx={{ userSelect: "none", padding: 0 }}
|
||||
>
|
||||
{questionData.deleted ? (
|
||||
<Box
|
||||
{...provided.dragHandleProps}
|
||||
sx={{
|
||||
width: "100%",
|
||||
maxWidth: "800px",
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
marginBottom: "40px",
|
||||
gap: "5px",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
return (
|
||||
<Draggable draggableId={String(index)} index={index}>
|
||||
{(provided) => (
|
||||
<ListItem
|
||||
ref={provided.innerRef}
|
||||
{...provided.draggableProps}
|
||||
sx={{ userSelect: "none", padding: 0 }}
|
||||
>
|
||||
{questionData.deleted ? (
|
||||
<Box
|
||||
{...provided.dragHandleProps}
|
||||
sx={{
|
||||
fontSize: "16px",
|
||||
color: theme.palette.grey2.main,
|
||||
width: "100%",
|
||||
maxWidth: "800px",
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
marginBottom: "40px",
|
||||
gap: "5px",
|
||||
}}
|
||||
>
|
||||
Вопрос удалён.
|
||||
</Typography>
|
||||
<Typography
|
||||
onClick={() => {
|
||||
updateQuestionsList(quizId, index, {
|
||||
...questionData,
|
||||
deleted: false,
|
||||
});
|
||||
}}
|
||||
sx={{
|
||||
cursor: "pointer",
|
||||
fontSize: "16px",
|
||||
textDecoration: "underline",
|
||||
color: theme.palette.brightPurple.main,
|
||||
textDecorationColor: theme.palette.brightPurple.main,
|
||||
}}
|
||||
>
|
||||
Восстановить?
|
||||
</Typography>
|
||||
</Box>
|
||||
) : (
|
||||
<Box sx={{ width: "100%", position: "relative" }}>
|
||||
<QuestionsPageCard
|
||||
key={index}
|
||||
totalIndex={index}
|
||||
draggableProps={provided.dragHandleProps}
|
||||
isDragging={isDragging}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
</ListItem>
|
||||
)}
|
||||
</Draggable>
|
||||
);
|
||||
});
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: "16px",
|
||||
color: theme.palette.grey2.main,
|
||||
}}
|
||||
>
|
||||
Вопрос удалён.
|
||||
</Typography>
|
||||
<Typography
|
||||
onClick={() => {
|
||||
updateQuestionsList<QuizQuestionBase>(quizId, index, {
|
||||
...questionData,
|
||||
deleted: false,
|
||||
});
|
||||
}}
|
||||
sx={{
|
||||
cursor: "pointer",
|
||||
fontSize: "16px",
|
||||
textDecoration: "underline",
|
||||
color: theme.palette.brightPurple.main,
|
||||
textDecorationColor: theme.palette.brightPurple.main,
|
||||
}}
|
||||
>
|
||||
Восстановить?
|
||||
</Typography>
|
||||
</Box>
|
||||
) : (
|
||||
<Box sx={{ width: "100%", position: "relative" }}>
|
||||
<QuestionsPageCard
|
||||
key={index}
|
||||
totalIndex={index}
|
||||
draggableProps={provided.dragHandleProps}
|
||||
isDragging={isDragging}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
</ListItem>
|
||||
)}
|
||||
</Draggable>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
@ -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 <Answer color={isExpanded ? "#9A9AAF" : "white"} sx={{ height: "22px", width: "20px" }} />;
|
||||
return (
|
||||
<Answer
|
||||
color={isExpanded ? "#9A9AAF" : "#7E2AEA"}
|
||||
sx={{ height: "22px", width: "20px" }}
|
||||
/>
|
||||
);
|
||||
case "images":
|
||||
return <OptionsPict color={isExpanded ? "#9A9AAF" : "white"} sx={{ height: "22px", width: "20px" }} />;
|
||||
return (
|
||||
<OptionsPict
|
||||
color={isExpanded ? "#9A9AAF" : "#7E2AEA"}
|
||||
sx={{ height: "22px", width: "20px" }}
|
||||
/>
|
||||
);
|
||||
case "varimg":
|
||||
return <OptionsAndPict color={isExpanded ? "#9A9AAF" : "white"} sx={{ height: "22px", width: "20px" }} />;
|
||||
return (
|
||||
<OptionsAndPict
|
||||
color={isExpanded ? "#9A9AAF" : "#7E2AEA"}
|
||||
sx={{ height: "22px", width: "20px" }}
|
||||
/>
|
||||
);
|
||||
case "emoji":
|
||||
return <Emoji color={isExpanded ? "#9A9AAF" : "white"} sx={{ height: "22px", width: "20px" }} />;
|
||||
return (
|
||||
<Emoji
|
||||
color={isExpanded ? "#9A9AAF" : "#7E2AEA"}
|
||||
sx={{ height: "22px", width: "20px" }}
|
||||
/>
|
||||
);
|
||||
case "text":
|
||||
return <Input color={isExpanded ? "#9A9AAF" : "white"} sx={{ height: "22px", width: "20px" }} />;
|
||||
return (
|
||||
<Input
|
||||
color={isExpanded ? "#9A9AAF" : "#7E2AEA"}
|
||||
sx={{ height: "22px", width: "20px" }}
|
||||
/>
|
||||
);
|
||||
case "select":
|
||||
return <DropDown color={isExpanded ? "#9A9AAF" : "white"} sx={{ height: "22px", width: "20px" }} />;
|
||||
return (
|
||||
<DropDown
|
||||
color={isExpanded ? "#9A9AAF" : "#7E2AEA"}
|
||||
sx={{ height: "22px", width: "20px" }}
|
||||
/>
|
||||
);
|
||||
case "date":
|
||||
return <Date color={isExpanded ? "#9A9AAF" : "white"} sx={{ height: "22px", width: "20px" }} />;
|
||||
return (
|
||||
<Date
|
||||
color={isExpanded ? "#9A9AAF" : "#7E2AEA"}
|
||||
sx={{ height: "22px", width: "20px" }}
|
||||
/>
|
||||
);
|
||||
case "number":
|
||||
return <Slider color={isExpanded ? "#9A9AAF" : "white"} sx={{ height: "22px", width: "20px" }} />;
|
||||
return (
|
||||
<Slider
|
||||
color={isExpanded ? "#9A9AAF" : "#7E2AEA"}
|
||||
sx={{ height: "22px", width: "20px" }}
|
||||
/>
|
||||
);
|
||||
case "file":
|
||||
return <Download color={isExpanded ? "#9A9AAF" : "white"} sx={{ height: "22px", width: "20px" }} />;
|
||||
return (
|
||||
<Download
|
||||
color={isExpanded ? "#9A9AAF" : "#7E2AEA"}
|
||||
sx={{ height: "22px", width: "20px" }}
|
||||
/>
|
||||
);
|
||||
case "page":
|
||||
return <Page color={isExpanded ? "#9A9AAF" : "white"} sx={{ height: "22px", width: "20px" }} />;
|
||||
return (
|
||||
<Page
|
||||
color={isExpanded ? "#9A9AAF" : "#7E2AEA"}
|
||||
sx={{ height: "22px", width: "20px" }}
|
||||
/>
|
||||
);
|
||||
case "rating":
|
||||
return <RatingIcon color={isExpanded ? "#9A9AAF" : "white"} sx={{ height: "22px", width: "20px" }} />;
|
||||
return (
|
||||
<RatingIcon
|
||||
color={isExpanded ? "#9A9AAF" : "#7E2AEA"}
|
||||
sx={{ height: "22px", width: "20px" }}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return <></>;
|
||||
}
|
||||
};
|
||||
export default function QuestionsPageCard({ totalIndex, draggableProps, isDragging }: Props) {
|
||||
export default function QuestionsPageCard({
|
||||
totalIndex,
|
||||
draggableProps,
|
||||
isDragging,
|
||||
}: Props) {
|
||||
const [plusVisible, setPlusVisible] = useState<boolean>(false);
|
||||
const [open, setOpen] = useState<boolean>(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<QuizQuestionInitial>(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",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
padding: isTablet ? (isMobile ? "20px 20px 0px 20px" : "20px 10px 20px 20px ") : "20px 10px 20px 20px",
|
||||
flexDirection: isMobile ? "column-reverse" : null,
|
||||
padding: isMobile ? "10px" : "20px 10px 20px 20px",
|
||||
flexDirection: isMobile ? "column" : null,
|
||||
}}
|
||||
>
|
||||
<FormControl
|
||||
@ -134,7 +195,7 @@ export default function QuestionsPageCard({ totalIndex, draggableProps, isDraggi
|
||||
}}
|
||||
>
|
||||
<TextField
|
||||
defaultValue={listQuestions[quizId][totalIndex].title}
|
||||
defaultValue={question.title}
|
||||
placeholder={"Заголовок вопроса"}
|
||||
onChange={({ target }) => 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)}
|
||||
</InputAdornment>
|
||||
<ChooseAnswerModal
|
||||
open={open}
|
||||
onClose={() => setOpen(false)}
|
||||
anchorRef={anchorRef}
|
||||
totalIndex={totalIndex}
|
||||
switchState={switchState}
|
||||
switchState={question.type}
|
||||
/>
|
||||
</Box>
|
||||
),
|
||||
}}
|
||||
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
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "flex-start",
|
||||
alignItems: "center",
|
||||
justifyContent: "flex-end",
|
||||
width: isMobile ? "100%" : "auto",
|
||||
position: "relative",
|
||||
height: "30px",
|
||||
marginBottom: "10px",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
}}
|
||||
<IconButton
|
||||
sx={{ padding: "0", margin: "5px" }}
|
||||
disableRipple
|
||||
onClick={() =>
|
||||
updateQuestionsList<QuizQuestionInitial>(quizId, totalIndex, {
|
||||
expanded: !question.expanded,
|
||||
})
|
||||
}
|
||||
>
|
||||
<IconButton
|
||||
sx={{ paddingBottom: isMobile ? "0" : "" }}
|
||||
disableRipple
|
||||
onClick={() =>
|
||||
updateQuestionsList(quizId, totalIndex, {
|
||||
expanded: !isExpanded,
|
||||
})
|
||||
}
|
||||
>
|
||||
{isExpanded ? (
|
||||
<ArrowDownIcon
|
||||
style={{
|
||||
width: "18px",
|
||||
color: "#4D4D4D",
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<ExpandLessIcon fill="#7E2AEA" />
|
||||
)}
|
||||
</IconButton>
|
||||
{isExpanded ? (
|
||||
<></>
|
||||
{question.expanded ? (
|
||||
<ArrowDownIcon
|
||||
style={{
|
||||
width: "18px",
|
||||
color: "#4D4D4D",
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Box sx={{ display: "flex", borderRight: "solid 1px white" }}>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox icon={<HideIcon style={{ color: "#7E2AEA" }} />} checkedIcon={<CrossedEyeIcon />} />
|
||||
}
|
||||
label={""}
|
||||
sx={{
|
||||
color: theme.palette.grey2.main,
|
||||
ml: "-9px",
|
||||
mr: 0,
|
||||
userSelect: "none",
|
||||
}}
|
||||
/>
|
||||
<IconButton onClick={() => copyQuestion(quizId, totalIndex)}>
|
||||
<CopyIcon style={{ color: "white" }} />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
sx={{
|
||||
cursor: "pointer",
|
||||
borderRadius: "6px",
|
||||
padding: "2px",
|
||||
}}
|
||||
onClick={() => {
|
||||
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,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<DeleteIcon style={{ color: "white" }} />
|
||||
</IconButton>
|
||||
</Box>
|
||||
<ExpandLessIcon
|
||||
sx={{
|
||||
boxSizing: "border-box",
|
||||
fill: theme.palette.brightPurple.main,
|
||||
background: "#FFF",
|
||||
borderRadius: "6px",
|
||||
height: "30px",
|
||||
width: "30px",
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</IconButton>
|
||||
{question.expanded ? (
|
||||
<></>
|
||||
) : (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
height: "30px",
|
||||
borderRight: "solid 1px #4D4D4D",
|
||||
}}
|
||||
>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
icon={
|
||||
<HideIcon
|
||||
style={{
|
||||
boxSizing: "border-box",
|
||||
color: "#7E2AEA",
|
||||
background: "#FFF",
|
||||
borderRadius: "6px",
|
||||
height: "30px",
|
||||
width: "30px",
|
||||
padding: "3px",
|
||||
}}
|
||||
/>
|
||||
}
|
||||
checkedIcon={<CrossedEyeIcon />}
|
||||
/>
|
||||
}
|
||||
label={""}
|
||||
sx={{
|
||||
color: theme.palette.grey2.main,
|
||||
ml: "-9px",
|
||||
mr: 0,
|
||||
userSelect: "none",
|
||||
}}
|
||||
/>
|
||||
<IconButton
|
||||
sx={{ padding: "0" }}
|
||||
onClick={() => copyQuestion(quizId, totalIndex)}
|
||||
>
|
||||
<CopyIcon
|
||||
style={{ color: theme.palette.brightPurple.main }}
|
||||
/>
|
||||
</IconButton>
|
||||
<IconButton
|
||||
sx={{
|
||||
cursor: "pointer",
|
||||
borderRadius: "6px",
|
||||
padding: "0",
|
||||
margin: "0 5px 0 10px",
|
||||
}}
|
||||
onClick={() => {
|
||||
const removedId = question.id;
|
||||
if (question.deleteTimeoutId) {
|
||||
clearTimeout(question.deleteTimeoutId);
|
||||
}
|
||||
|
||||
<OneIcon style={{ fontSize: "30px", marginRight: "-2px" }} />
|
||||
</Box>
|
||||
removeQuestion(quizId, totalIndex);
|
||||
|
||||
const newTimeoutId = window.setTimeout(() => {
|
||||
removeQuestionForce(quizId, removedId);
|
||||
}, 5000);
|
||||
|
||||
updateQuestionsList<AnyQuizQuestion>(quizId, totalIndex, {
|
||||
...question,
|
||||
deleteTimeoutId: newTimeoutId,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<DeleteIcon
|
||||
style={{ color: theme.palette.brightPurple.main }}
|
||||
/>
|
||||
</IconButton>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<OneIcon
|
||||
style={{
|
||||
fontSize: "30px",
|
||||
marginLeft: "3px",
|
||||
color: !question.expanded ? "#FFF" : "",
|
||||
fill: question.expanded
|
||||
? "#EEE4FC"
|
||||
: theme.palette.brightPurple.main,
|
||||
}}
|
||||
/>
|
||||
<IconButton
|
||||
disableRipple
|
||||
sx={{
|
||||
padding: isMobile ? "0" : "5px 10px 8px 8px",
|
||||
position: isMobile ? "absolute" : "relative",
|
||||
padding: isMobile ? "0" : "0 5px",
|
||||
right: isMobile ? "0" : null,
|
||||
bottom: isMobile ? "0" : null,
|
||||
}}
|
||||
@ -281,7 +386,7 @@ export default function QuestionsPageCard({ totalIndex, draggableProps, isDraggi
|
||||
</IconButton>
|
||||
</Box>
|
||||
</Box>
|
||||
{isExpanded && (
|
||||
{question.expanded && (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
@ -290,7 +395,7 @@ export default function QuestionsPageCard({ totalIndex, draggableProps, isDraggi
|
||||
borderRadius: "12px",
|
||||
}}
|
||||
>
|
||||
{switchState.length === 0 ? (
|
||||
{question.type === "nonselected" ? (
|
||||
<TypeQuestions totalIndex={totalIndex} />
|
||||
) : (
|
||||
<SwitchQuestionsPage totalIndex={totalIndex} />
|
||||
@ -310,7 +415,7 @@ export default function QuestionsPageCard({ totalIndex, draggableProps, isDraggi
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
onClick={() => 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)",
|
||||
}}
|
||||
/>
|
||||
<PlusIcon />
|
||||
|
@ -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<QuizQuestionSelect>(quizId, totalIndex, {
|
||||
content: { ...question.content, variants: answerNew },
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box sx={{ pl: "20px", pr: "20px" }}>
|
||||
{variants.length === 0 ? (
|
||||
<Box
|
||||
sx={{
|
||||
padding: isMobile ? "15px 20px 20px 20px" : "20px 20px 20px 20px ",
|
||||
}}
|
||||
>
|
||||
{question.content.variants.length === 0 ? (
|
||||
<Typography
|
||||
sx={{
|
||||
padding: "0 0 33px 80px",
|
||||
@ -53,13 +56,16 @@ export default function DropDown({ totalIndex }: Props) {
|
||||
Добавьте ответ
|
||||
</Typography>
|
||||
) : (
|
||||
<AnswerDraggableList variants={variants} totalIndex={totalIndex} />
|
||||
<AnswerDraggableList
|
||||
variants={question.content.variants}
|
||||
totalIndex={totalIndex}
|
||||
/>
|
||||
)}
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
marginBottom: "17px",
|
||||
marginBottom: "20px",
|
||||
}}
|
||||
>
|
||||
<Link
|
||||
@ -93,11 +99,7 @@ export default function DropDown({ totalIndex }: Props) {
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
<ButtonsOptions
|
||||
switchState={switchState}
|
||||
SSHC={SSHC}
|
||||
totalIndex={totalIndex}
|
||||
/>
|
||||
<ButtonsOptions switchState={switchState} SSHC={SSHC} totalIndex={totalIndex} />
|
||||
<SwitchDropDown switchState={switchState} totalIndex={totalIndex} />
|
||||
</>
|
||||
);
|
||||
|
@ -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<QuizQuestionSelect>(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<QuizQuestionSelect>(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) {
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{ height: isMobile ? "18px" : "auto", fontWeight: "500", fontSize: "18px", color: " #4D4D4D" }}
|
||||
sx={{
|
||||
height: isMobile ? "18px" : "auto",
|
||||
fontWeight: "500",
|
||||
fontSize: "18px",
|
||||
color: " #4D4D4D",
|
||||
}}
|
||||
>
|
||||
Настройки ответов
|
||||
</Typography>
|
||||
<CustomCheckbox
|
||||
label={"Можно несколько"}
|
||||
checked={listQuestions[quizId][totalIndex].content.multi}
|
||||
checked={question.content.multi}
|
||||
handleChange={({ target }) =>
|
||||
updateQuestionsList(quizId, totalIndex, {
|
||||
content: {
|
||||
...listQuestions[quizId][totalIndex].content,
|
||||
multi: target.checked,
|
||||
},
|
||||
updateQuestionsList<QuizQuestionSelect>(quizId, totalIndex, {
|
||||
content: { ...question.content, multi: target.checked },
|
||||
})
|
||||
}
|
||||
/>
|
||||
<Box sx={{ display: isMobile ? "none" : "block", mt: isMobile ? "11px" : "6px" }}>
|
||||
<Box
|
||||
sx={{
|
||||
display: isMobile ? "none" : "block",
|
||||
mt: isMobile ? "11px" : "6px",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
height: isMobile ? "18px" : "auto",
|
||||
@ -86,7 +103,7 @@ export default function SettingDropDown({ totalIndex }: SettingDropDownProps) {
|
||||
</Typography>
|
||||
<CustomTextField
|
||||
placeholder={"Выберите вариант"}
|
||||
text={listQuestions[quizId][totalIndex].content.default}
|
||||
text={question.content.default}
|
||||
onChange={({ target }) => debounceAnswer(target.value)}
|
||||
/>
|
||||
</Box>
|
||||
@ -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%",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{ height: isMobile ? "18px" : "auto", fontWeight: "500", fontSize: "18px", color: " #4D4D4D" }}
|
||||
sx={{
|
||||
height: isMobile ? "18px" : "auto",
|
||||
fontWeight: "500",
|
||||
fontSize: "18px",
|
||||
color: " #4D4D4D",
|
||||
}}
|
||||
>
|
||||
Настройки вопросов
|
||||
</Typography>
|
||||
<CustomCheckbox
|
||||
label={"Необязательный вопрос"}
|
||||
checked={!listQuestions[quizId][totalIndex].required}
|
||||
checked={!question.required}
|
||||
handleChange={(e) => {
|
||||
updateQuestionsList(quizId, totalIndex, {
|
||||
updateQuestionsList<QuizQuestionSelect>(quizId, totalIndex, {
|
||||
required: !e.target.checked,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<Box sx={{ position: "relative", display: "flex", alignItems: "center" }}>
|
||||
<Box sx={{ position: "relative", display: "flex", alignItems: "flex-start" }}>
|
||||
<CustomCheckbox
|
||||
sx={{ height: isMobile ? "100%" : "26px", alignItems: isMobile ? "flex-start" : "center" }}
|
||||
label={"Внутреннее название вопроса"}
|
||||
checked={listQuestions[quizId][totalIndex].content.innerNameCheck}
|
||||
handleChange={(e) => {
|
||||
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<QuizQuestionSelect>(quizId, totalIndex, {
|
||||
content: {
|
||||
...question.content,
|
||||
innerNameCheck: target.checked,
|
||||
innerName: target.checked ? question.content.innerName : "",
|
||||
},
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<InfoIcon
|
||||
sx={{
|
||||
position: isMobile ? "absolute" : null,
|
||||
display: "block",
|
||||
right: isMobile ? "30px" : null,
|
||||
}}
|
||||
/>
|
||||
<Tooltip
|
||||
title="Будет отображаться как заголовок вопроса в приходящих заявках."
|
||||
placement="top"
|
||||
>
|
||||
<Box>
|
||||
<InfoIcon />
|
||||
</Box>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
width: "85%",
|
||||
width: "100%",
|
||||
pt: "20px",
|
||||
display: isMobile ? "block" : "none",
|
||||
}}
|
||||
@ -162,14 +183,14 @@ export default function SettingDropDown({ totalIndex }: SettingDropDownProps) {
|
||||
</Typography>
|
||||
<CustomTextField
|
||||
placeholder={"Выберите вариант"}
|
||||
text={listQuestions[quizId][totalIndex].content.default}
|
||||
text={question.content.default}
|
||||
onChange={({ target }) => debounceAnswer(target.value)}
|
||||
/>
|
||||
</Box>
|
||||
{listQuestions[quizId][totalIndex].content.innerNameCheck && (
|
||||
{question.content.innerNameCheck && (
|
||||
<CustomTextField
|
||||
placeholder={"Развёрнутое описание вопроса"}
|
||||
text={listQuestions[quizId][totalIndex].content.innerName}
|
||||
text={question.content.innerName}
|
||||
onChange={({ target }) => debounced(target.value)}
|
||||
/>
|
||||
)}
|
||||
|
@ -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<string>("setting");
|
||||
const [open, setOpen] = useState<boolean>(false);
|
||||
const [anchorElement, setAnchorElement] = useState<HTMLDivElement | null>(
|
||||
null
|
||||
);
|
||||
const [anchorElement, setAnchorElement] = useState<HTMLDivElement | null>(null);
|
||||
const [currentIndex, setCurrentIndex] = useState<number>(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 (
|
||||
<>
|
||||
{/* <Box sx={{ padding: "0 20px 0px 20px" }}>
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
border: "1px solid #9A9AAF",
|
||||
borderRadius: "8px",
|
||||
marginBottom: "15px",
|
||||
}}
|
||||
>
|
||||
<TextField
|
||||
fullWidth
|
||||
focused={false}
|
||||
placeholder={"Добавьте ответ"}
|
||||
multiline={listQuestions[quizId][totalIndex].content.largeCheck}
|
||||
InputProps={{
|
||||
startAdornment: (
|
||||
<>
|
||||
<InputAdornment position="start">
|
||||
<PointsIcon style={{ color: "#9A9AAF", fontSize: "30px" }} />
|
||||
</InputAdornment>
|
||||
{!isMobile && (
|
||||
<Box
|
||||
sx={{
|
||||
width: "60px",
|
||||
height: "40px",
|
||||
background: "#EEE4FC",
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
marginRight: "20px",
|
||||
marginLeft: "12px",
|
||||
}}
|
||||
>
|
||||
<Box sx={{ display: "flex", alignItems: "center", justifyContent: "center", width: "40px" }}>
|
||||
<EmojiIcons fontSize="22px" color="#7E2AEA" />
|
||||
</Box>
|
||||
<span
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
background: "#7E2AEA",
|
||||
height: "100%",
|
||||
width: "20px",
|
||||
color: "white",
|
||||
fontSize: "15px",
|
||||
}}
|
||||
>
|
||||
+
|
||||
</span>
|
||||
</Box>
|
||||
)}
|
||||
</>
|
||||
),
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
<IconButton sx={{ padding: "0" }} aria-describedby="my-popover-id">
|
||||
<MessageIcon style={{ color: "#9A9AAF", fontSize: "30px", marginRight: "6.5px" }} />
|
||||
</IconButton>
|
||||
<Popover id="my-popover-id" anchorOrigin={{ vertical: "bottom", horizontal: "left" }} open={false}>
|
||||
<TextareaAutosize style={{ margin: "10px" }} placeholder="Подсказка для этого ответа" />
|
||||
</Popover>
|
||||
<IconButton sx={{ padding: "0" }}>
|
||||
<DeleteIcon style={{ color: theme.palette.grey2.main, marginRight: "-1px" }} />
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
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 && (
|
||||
<Box sx={{ display: "flex", alignItems: "center", m: "8px", position: "relative" }}>
|
||||
<Box sx={{ width: "100%", background: "#EEE4FC", height: "40px" }} />
|
||||
<ImageAddIcons
|
||||
style={{ position: "absolute", color: "#7E2AEA", fontSize: "20px", left: "45%", right: "55%" }}
|
||||
/>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
width: "20px",
|
||||
background: "#EEE4FC",
|
||||
height: "40px",
|
||||
color: "white",
|
||||
backgroundColor: "#7E2AEA",
|
||||
}}
|
||||
>
|
||||
+
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
<Box sx={{ display: "flex", alignItems: "center", marginBottom: "20px" }}> */}
|
||||
<Box sx={{ padding: "20px" }}>
|
||||
<AnswerDraggableList
|
||||
variants={listQuestions[quizId][totalIndex].content.variants}
|
||||
variants={question.content.variants}
|
||||
totalIndex={totalIndex}
|
||||
additionalContent={(variant, index) => (
|
||||
<>
|
||||
{!isTablet && (
|
||||
<Box sx={{ cursor: "pointer", margin: "0 15px 0 5px" }}>
|
||||
<Box sx={{ cursor: "pointer" }}>
|
||||
<Box
|
||||
onClick={({ currentTarget }) => {
|
||||
setAnchorElement(currentTarget);
|
||||
@ -172,19 +67,29 @@ export default function Emoji({ totalIndex }: Props) {
|
||||
gap: "5px",
|
||||
}}
|
||||
>
|
||||
{variant.emoji ? (
|
||||
{variant.extendedText ? (
|
||||
<Box
|
||||
sx={{
|
||||
width: "30px",
|
||||
height: "40px",
|
||||
width: "60px",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
background: "#EEE4FC",
|
||||
borderRadius: "3px",
|
||||
marginRight: "15px",
|
||||
}}
|
||||
>
|
||||
<Box sx={{ marginLeft: "3px" }}>{variant.emoji}</Box>
|
||||
<Box sx={{ marginLeft: "-3px" }}>
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
{variant.extendedText}
|
||||
</Box>
|
||||
<Box>
|
||||
<PlusImage />
|
||||
</Box>
|
||||
</Box>
|
||||
@ -220,7 +125,7 @@ export default function Emoji({ totalIndex }: Props) {
|
||||
height: "40px",
|
||||
}}
|
||||
/>
|
||||
{variant.emoji ? (
|
||||
{variant.extendedText ? (
|
||||
<Box
|
||||
sx={{
|
||||
position: "absolute",
|
||||
@ -230,7 +135,7 @@ export default function Emoji({ totalIndex }: Props) {
|
||||
right: "55%",
|
||||
}}
|
||||
>
|
||||
{variant.emoji}
|
||||
{variant.extendedText}
|
||||
</Box>
|
||||
) : (
|
||||
<EmojiIcons
|
||||
@ -280,39 +185,30 @@ export default function Emoji({ totalIndex }: Props) {
|
||||
<EmojiPicker
|
||||
onEmojiSelect={({ native }) => {
|
||||
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<QuizQuestionEmoji>(quizId, totalIndex, {
|
||||
content: { ...question.content, variants: cloneVariants },
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Popover>
|
||||
<Box sx={{ display: "flex", alignItems: "center", gap: "10px" }}>
|
||||
<Box sx={{ display: "flex", alignItems: "center", gap: "10px", marginBottom: isMobile ? "17px" : "20px" }}>
|
||||
<Link
|
||||
component="button"
|
||||
variant="body2"
|
||||
sx={{ color: theme.palette.brightPurple.main }}
|
||||
onClick={() => {
|
||||
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<QuizQuestionEmoji>(quizId, totalIndex, {
|
||||
content: { ...question.content, variants: answerNew },
|
||||
});
|
||||
}}
|
||||
>
|
||||
@ -341,11 +237,7 @@ export default function Emoji({ totalIndex }: Props) {
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
<ButtonsOptions
|
||||
switchState={switchState}
|
||||
SSHC={SSHC}
|
||||
totalIndex={totalIndex}
|
||||
/>
|
||||
<ButtonsOptions switchState={switchState} SSHC={SSHC} totalIndex={totalIndex} />
|
||||
<SwitchEmoji switchState={switchState} totalIndex={totalIndex} />
|
||||
</>
|
||||
);
|
||||
|
@ -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<QuizQuestionEmoji>(quizId, totalIndex, {
|
||||
content: { ...question.content, innerName: value },
|
||||
});
|
||||
}, 1000);
|
||||
|
||||
return (
|
||||
@ -51,21 +54,21 @@ export default function SettingEmoji({ totalIndex }: SettingEmojiProps) {
|
||||
<CustomCheckbox
|
||||
sx={{ display: "block", mr: isMobile ? "0px" : "16px" }}
|
||||
label={"Можно несколько"}
|
||||
checked={listQuestions[quizId][totalIndex].content.multi}
|
||||
handleChange={(e) => {
|
||||
let clonContent = listQuestions[quizId][totalIndex].content;
|
||||
clonContent.multi = e.target.checked;
|
||||
updateQuestionsList(quizId, totalIndex, { content: clonContent });
|
||||
checked={question.content.multi}
|
||||
handleChange={({ target }) => {
|
||||
updateQuestionsList<QuizQuestionEmoji>(quizId, totalIndex, {
|
||||
content: { ...question.content, multi: target.checked },
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<CustomCheckbox
|
||||
sx={{ display: "block", mr: isMobile ? "0px" : "16px" }}
|
||||
label={'Вариант "свой ответ"'}
|
||||
checked={listQuestions[quizId][totalIndex].content.own}
|
||||
handleChange={(e) => {
|
||||
let clonContent = listQuestions[quizId][totalIndex].content;
|
||||
clonContent.own = e.target.checked;
|
||||
updateQuestionsList(quizId, totalIndex, { content: clonContent });
|
||||
checked={question.content.own}
|
||||
handleChange={({ target }) => {
|
||||
updateQuestionsList<QuizQuestionEmoji>(quizId, totalIndex, {
|
||||
content: { ...question.content, own: target.checked },
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
@ -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%",
|
||||
}}
|
||||
>
|
||||
<Typography sx={{ height: isMobile ? "18px" : "auto", fontWeight: "500", fontSize: "18px", color: " #4D4D4D" }}>
|
||||
@ -87,27 +90,36 @@ export default function SettingEmoji({ totalIndex }: SettingEmojiProps) {
|
||||
<CustomCheckbox
|
||||
sx={{ mr: isMobile ? "0px" : "16px" }}
|
||||
label={"Необязательный вопрос"}
|
||||
checked={!listQuestions[quizId][totalIndex].required}
|
||||
checked={!question.required}
|
||||
handleChange={(e) => {
|
||||
updateQuestionsList(quizId, totalIndex, {
|
||||
updateQuestionsList<QuizQuestionEmoji>(quizId, totalIndex, {
|
||||
required: !e.target.checked,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<Box sx={{ width: isMobile ? "90%" : "auto", display: "flex", alignItems: "center" }}>
|
||||
<Box
|
||||
sx={{
|
||||
width: isMobile ? "90%" : "auto",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<CustomCheckbox
|
||||
sx={{ mr: isMobile ? "0px" : "16px" }}
|
||||
sx={{
|
||||
mr: isMobile ? "0px" : "16px",
|
||||
height: isMobile ? "100%" : "26px",
|
||||
alignItems: isMobile ? "flex-start" : "center",
|
||||
}}
|
||||
label={"Внутреннее название вопроса"}
|
||||
checked={listQuestions[quizId][totalIndex].content.innerNameCheck}
|
||||
handleChange={(e) => {
|
||||
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<QuizQuestionEmoji>(quizId, totalIndex, {
|
||||
content: {
|
||||
...question.content,
|
||||
innerNameCheck: target.checked,
|
||||
innerName: target.checked ? question.content.innerName : "",
|
||||
},
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<Tooltip title="Будет отображаться как заголовок вопроса в приходящих заявках." placement="top">
|
||||
@ -116,10 +128,10 @@ export default function SettingEmoji({ totalIndex }: SettingEmojiProps) {
|
||||
</Box>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
{listQuestions[quizId][totalIndex].content.innerNameCheck && (
|
||||
{question.content.innerNameCheck && (
|
||||
<CustomTextField
|
||||
placeholder={"Развёрнутое описание вопроса"}
|
||||
text={listQuestions[quizId][totalIndex].content.innerName}
|
||||
text={question.content.innerName}
|
||||
onChange={({ target }) => debounced(target.value)}
|
||||
/>
|
||||
)}
|
||||
|
148
src/pages/Questions/Form/FormDraggableList/ChooseAnswerModal.tsx
Normal file
148
src/pages/Questions/Form/FormDraggableList/ChooseAnswerModal.tsx
Normal file
@ -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<HTMLDivElement>;
|
||||
totalIndex: number;
|
||||
switchState: string;
|
||||
};
|
||||
|
||||
export const ChooseAnswerModal = ({
|
||||
open,
|
||||
onClose,
|
||||
anchorRef,
|
||||
totalIndex,
|
||||
switchState,
|
||||
}: ChooseAnswerModalProps) => {
|
||||
const [openModal, setOpenModal] = useState<boolean>(false);
|
||||
const [selectedValue, setSelectedValue] = useState<QuizQuestionType>("text");
|
||||
const quizId = Number(useParams().quizId);
|
||||
const { listQuestions } = questionStore();
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Popper
|
||||
placement="right-start"
|
||||
open={open}
|
||||
anchorEl={anchorRef.current}
|
||||
transition
|
||||
>
|
||||
{({ TransitionProps }) => (
|
||||
<Grow {...TransitionProps}>
|
||||
<Paper>
|
||||
<ClickAwayListener onClickAway={onClose}>
|
||||
<MenuList autoFocusItem={open}>
|
||||
{BUTTON_TYPE_QUESTIONS.map(({ icon, title, value }) => (
|
||||
<MenuItem
|
||||
key={value}
|
||||
sx={{ display: "flex", gap: "10px" }}
|
||||
{...(value !== switchState && {
|
||||
onClick: () => {
|
||||
onClose();
|
||||
setOpenModal(true);
|
||||
setSelectedValue(value);
|
||||
},
|
||||
})}
|
||||
>
|
||||
<Box>{icon}</Box>
|
||||
<Typography
|
||||
sx={{
|
||||
color:
|
||||
value === switchState
|
||||
? theme.palette.brightPurple.main
|
||||
: theme.palette.grey2.main,
|
||||
}}
|
||||
>
|
||||
{title}
|
||||
</Typography>
|
||||
</MenuItem>
|
||||
))}
|
||||
</MenuList>
|
||||
</ClickAwayListener>
|
||||
</Paper>
|
||||
</Grow>
|
||||
)}
|
||||
</Popper>
|
||||
<Modal open={openModal} onClose={() => setOpenModal(false)}>
|
||||
<Box
|
||||
sx={{
|
||||
position: "absolute",
|
||||
top: "50%",
|
||||
left: "50%",
|
||||
transform: "translate(-50%, -50%)",
|
||||
padding: "30px",
|
||||
borderRadius: "10px",
|
||||
background: "#FFFFFF",
|
||||
}}
|
||||
>
|
||||
<Typography variant="h6">
|
||||
Все настройки, кроме заголовка вопроса будут сброшены
|
||||
</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
marginTop: "30px",
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
gap: "15px",
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
variant="contained"
|
||||
sx={{ minWidth: "150px" }}
|
||||
onClick={() => setOpenModal(false)}
|
||||
>
|
||||
Отмена
|
||||
</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
sx={{ minWidth: "150px" }}
|
||||
onClick={() => {
|
||||
setOpenModal(false);
|
||||
|
||||
const question = { ...listQuestions[quizId][totalIndex] };
|
||||
|
||||
removeQuestionForce(quizId, question.id);
|
||||
createQuestion(quizId, selectedValue, totalIndex);
|
||||
updateQuestionsList<QuizQuestionBase>(quizId, totalIndex, {
|
||||
expanded: question.expanded,
|
||||
});
|
||||
}}
|
||||
>
|
||||
Подтвердить
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
@ -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 (
|
||||
<Draggable draggableId={String(index)} index={index}>
|
||||
{(provided) => (
|
||||
<ListItem
|
||||
ref={provided.innerRef}
|
||||
{...provided.draggableProps}
|
||||
sx={{ userSelect: "none", padding: 0 }}
|
||||
>
|
||||
{questionData.deleted ? (
|
||||
<Box
|
||||
{...provided.dragHandleProps}
|
||||
sx={{
|
||||
width: "100%",
|
||||
maxWidth: "800px",
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
marginTop: "30px",
|
||||
gap: "5px",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: "16px",
|
||||
color: theme.palette.grey2.main,
|
||||
}}
|
||||
>
|
||||
Вопрос удалён.
|
||||
</Typography>
|
||||
<Typography
|
||||
onClick={() => {
|
||||
updateQuestionsList<QuizQuestionBase>(quizId, index, {
|
||||
...questionData,
|
||||
deleted: false,
|
||||
});
|
||||
}}
|
||||
sx={{
|
||||
cursor: "pointer",
|
||||
fontSize: "16px",
|
||||
textDecoration: "underline",
|
||||
color: theme.palette.brightPurple.main,
|
||||
textDecorationColor: theme.palette.brightPurple.main,
|
||||
}}
|
||||
>
|
||||
Восстановить?
|
||||
</Typography>
|
||||
</Box>
|
||||
) : (
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
position: "relative",
|
||||
borderBottom: "1px solid rgba(0, 0, 0, 0.23)",
|
||||
}}
|
||||
>
|
||||
<QuestionsPageCard
|
||||
key={index}
|
||||
totalIndex={index}
|
||||
draggableProps={provided.dragHandleProps}
|
||||
isDragging={isDragging}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
</ListItem>
|
||||
)}
|
||||
</Draggable>
|
||||
);
|
||||
}
|
||||
);
|
168
src/pages/Questions/Form/FormDraggableList/QuestionPageCard.tsx
Normal file
168
src/pages/Questions/Form/FormDraggableList/QuestionPageCard.tsx
Normal file
@ -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 <Answer color="#9A9AAF" sx={{ height: "22px", width: "20px" }} />;
|
||||
case "images":
|
||||
return (
|
||||
<OptionsPict color="#9A9AAF" sx={{ height: "22px", width: "20px" }} />
|
||||
);
|
||||
case "varimg":
|
||||
return (
|
||||
<OptionsAndPict
|
||||
color="#9A9AAF"
|
||||
sx={{ height: "22px", width: "20px" }}
|
||||
/>
|
||||
);
|
||||
case "emoji":
|
||||
return <Emoji color="#9A9AAF" sx={{ height: "22px", width: "20px" }} />;
|
||||
case "text":
|
||||
return <Input color="#9A9AAF" sx={{ height: "22px", width: "20px" }} />;
|
||||
case "select":
|
||||
return (
|
||||
<DropDown color="#9A9AAF" sx={{ height: "22px", width: "20px" }} />
|
||||
);
|
||||
case "date":
|
||||
return <Date color="#9A9AAF" sx={{ height: "22px", width: "20px" }} />;
|
||||
case "number":
|
||||
return <Slider color="#9A9AAF" sx={{ height: "22px", width: "20px" }} />;
|
||||
case "file":
|
||||
return (
|
||||
<Download color="#9A9AAF" sx={{ height: "22px", width: "20px" }} />
|
||||
);
|
||||
case "page":
|
||||
return <Page color="#9A9AAF" sx={{ height: "22px", width: "20px" }} />;
|
||||
case "rating":
|
||||
return (
|
||||
<RatingIcon color="#9A9AAF" sx={{ height: "22px", width: "20px" }} />
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<AnswerGroup color="#9A9AAF" sx={{ height: "22px", width: "20px" }} />
|
||||
);
|
||||
}
|
||||
};
|
||||
export default function QuestionsPageCard({
|
||||
totalIndex,
|
||||
draggableProps,
|
||||
isDragging,
|
||||
}: Props) {
|
||||
const [open, setOpen] = useState<boolean>(false);
|
||||
const quizId = Number(useParams().quizId);
|
||||
const { listQuestions } = questionStore();
|
||||
const question = listQuestions[quizId][totalIndex];
|
||||
const anchorRef = useRef(null);
|
||||
const debounced = useDebouncedCallback((title) => {
|
||||
updateQuestionsList<QuizQuestionBase>(quizId, totalIndex, { title });
|
||||
}, 1000);
|
||||
|
||||
useEffect(() => {
|
||||
if (question.deleteTimeoutId) {
|
||||
clearTimeout(question.deleteTimeoutId);
|
||||
}
|
||||
}, [question]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Paper
|
||||
id={String(totalIndex)}
|
||||
sx={{
|
||||
overflow: "hidden",
|
||||
maxWidth: "796px",
|
||||
width: "100%",
|
||||
backgroundColor: "white",
|
||||
border: "none",
|
||||
boxShadow: "none",
|
||||
paddingBottom: "20px",
|
||||
borderRadius: "0",
|
||||
borderTopLeftRadius: "12px",
|
||||
borderTopRightRadius: "12px",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
padding: 0,
|
||||
}}
|
||||
>
|
||||
<CustomTextField
|
||||
placeholder={`Заголовок ${totalIndex + 1} вопроса`}
|
||||
text={question.title}
|
||||
onChange={({ target }) => debounced(target.value)}
|
||||
sx={{ margin: "20px", width: "auto" }}
|
||||
InputProps={{
|
||||
startAdornment: (
|
||||
<Box>
|
||||
<InputAdornment
|
||||
ref={anchorRef}
|
||||
position="start"
|
||||
sx={{ cursor: "pointer" }}
|
||||
onClick={() => setOpen((isOpened) => !isOpened)}
|
||||
>
|
||||
{IconAndrom(question.type)}
|
||||
</InputAdornment>
|
||||
<ChooseAnswerModal
|
||||
open={open}
|
||||
onClose={() => setOpen(false)}
|
||||
anchorRef={anchorRef}
|
||||
totalIndex={totalIndex}
|
||||
switchState={question.type}
|
||||
/>
|
||||
</Box>
|
||||
),
|
||||
endAdornment: (
|
||||
<>
|
||||
<InputAdornment {...draggableProps} position="start">
|
||||
<PointsIcon
|
||||
style={{ color: "#9A9AAF", fontSize: "30px" }}
|
||||
/>
|
||||
</InputAdornment>
|
||||
</>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
{question.type === "nonselected" ? (
|
||||
<FormTypeQuestions totalIndex={totalIndex} />
|
||||
) : (
|
||||
<SwitchQuestionsPage totalIndex={totalIndex} />
|
||||
)}
|
||||
</Box>
|
||||
</Paper>
|
||||
</>
|
||||
);
|
||||
}
|
11
src/pages/Questions/Form/FormDraggableList/helper.ts
Normal file
11
src/pages/Questions/Form/FormDraggableList/helper.ts
Normal file
@ -0,0 +1,11 @@
|
||||
export const reorder = <T>(
|
||||
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;
|
||||
};
|
48
src/pages/Questions/Form/FormDraggableList/index.tsx
Normal file
48
src/pages/Questions/Form/FormDraggableList/index.tsx
Normal file
@ -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 (
|
||||
<DragDropContext onDragEnd={onDragEnd}>
|
||||
<Droppable droppableId="droppable-list">
|
||||
{(provided, snapshot) => (
|
||||
<Box ref={provided.innerRef} {...provided.droppableProps}>
|
||||
{listQuestions[quizId]?.map((question, index) => (
|
||||
<FormDraggableListItem
|
||||
key={index}
|
||||
index={index}
|
||||
isDragging={snapshot.isDraggingOver}
|
||||
questionData={question}
|
||||
/>
|
||||
))}
|
||||
{provided.placeholder}
|
||||
</Box>
|
||||
)}
|
||||
</Droppable>
|
||||
</DragDropContext>
|
||||
);
|
||||
};
|
142
src/pages/Questions/Form/FormQuestionsPage.tsx
Normal file
142
src/pages/Questions/Form/FormQuestionsPage.tsx
Normal file
@ -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<AnyQuizQuestion>(quizId, index, {
|
||||
...item,
|
||||
expanded: false,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box
|
||||
sx={{
|
||||
maxWidth: "796px",
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
margin: "60px 0 40px 0",
|
||||
}}
|
||||
>
|
||||
<Typography variant={"h5"}>Заголовок анкеты</Typography>
|
||||
<Button
|
||||
sx={{
|
||||
fontSize: "16px",
|
||||
lineHeight: "19px",
|
||||
padding: 0,
|
||||
textDecoration: "underline",
|
||||
color: theme.palette.brightPurple.main,
|
||||
textDecorationColor: theme.palette.brightPurple.main,
|
||||
}}
|
||||
onClick={collapseEverything}
|
||||
>
|
||||
Свернуть всё
|
||||
</Button>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
maxWidth: "796px",
|
||||
boxShadow: "0px 10px 30px #e7e7e7",
|
||||
borderRadius: "12px",
|
||||
marginBottom: "40px",
|
||||
borderTop: "1px solid transparent",
|
||||
borderBottom: "1px solid transparent",
|
||||
background: "#FFFFFF",
|
||||
}}
|
||||
>
|
||||
<FormDraggableList />
|
||||
<Box
|
||||
sx={{
|
||||
cursor: "pointer",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "15px",
|
||||
padding: "4px",
|
||||
margin: "15px",
|
||||
border: "1px solid transparent",
|
||||
borderRadius: "8px",
|
||||
"&:hover": {
|
||||
border: "1px solid #9A9AAF",
|
||||
},
|
||||
}}
|
||||
onClick={() => {
|
||||
createQuestion(quizId);
|
||||
updateQuestionsList<QuizQuestionBase>(
|
||||
quizId,
|
||||
listQuestions[quizId].length - 1 || 0,
|
||||
{
|
||||
expanded: true,
|
||||
}
|
||||
);
|
||||
}}
|
||||
>
|
||||
<AddAnswer color="#EEE4FC" />
|
||||
<Typography sx={{ color: "#9A9AAF" }}>
|
||||
Добавить еще один вопрос
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "flex-end",
|
||||
gap: "8px",
|
||||
maxWidth: "796px",
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
variant="outlined"
|
||||
sx={{ padding: "10px 20px", borderRadius: "8px", height: "44px" }}
|
||||
>
|
||||
<ArrowLeft />
|
||||
</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
sx={{
|
||||
height: "44px",
|
||||
padding: "10px 20px",
|
||||
borderRadius: "8px",
|
||||
background: theme.palette.brightPurple.main,
|
||||
fontSize: "18px",
|
||||
}}
|
||||
onClick={handleNext}
|
||||
>
|
||||
Следующий шаг
|
||||
</Button>
|
||||
{createPortal(<QuizPreview />, document.body)}
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
}
|
143
src/pages/Questions/Form/FormTypeQuestions.tsx
Normal file
143
src/pages/Questions/Form/FormTypeQuestions.tsx
Normal file
@ -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: <Answer color="#9A9AAF" />,
|
||||
title: "Варианты ответов",
|
||||
value: "variant",
|
||||
},
|
||||
{
|
||||
icon: <OptionsPict color="#9A9AAF" />,
|
||||
title: "Варианты с картинками",
|
||||
value: "images",
|
||||
},
|
||||
{
|
||||
icon: <OptionsAndPict color="#9A9AAF" />,
|
||||
title: "Варианты и картинка",
|
||||
value: "varimg",
|
||||
},
|
||||
{
|
||||
icon: <Emoji color="#9A9AAF" />,
|
||||
title: "Эмоджи",
|
||||
value: "emoji",
|
||||
},
|
||||
{
|
||||
icon: <Input color="#9A9AAF" />,
|
||||
title: "Своё поле для ввода",
|
||||
value: "text",
|
||||
},
|
||||
{
|
||||
icon: <DropDown color="#9A9AAF" />,
|
||||
title: "Выпадающий список",
|
||||
value: "select",
|
||||
},
|
||||
{
|
||||
icon: <Date color="#9A9AAF" />,
|
||||
title: "Дата",
|
||||
value: "date",
|
||||
},
|
||||
{
|
||||
icon: <Slider color="#9A9AAF" />,
|
||||
title: "Ползунок",
|
||||
value: "number",
|
||||
},
|
||||
{
|
||||
icon: <Download color="#9A9AAF" />,
|
||||
title: "Загрузка файла",
|
||||
value: "file",
|
||||
},
|
||||
{
|
||||
icon: <Page color="#9A9AAF" />,
|
||||
title: "Страница",
|
||||
value: "page",
|
||||
},
|
||||
{
|
||||
icon: <RatingIcon color="#9A9AAF" />,
|
||||
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 (
|
||||
<Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexWrap: "wrap",
|
||||
gap: "20px",
|
||||
margin: "20px",
|
||||
}}
|
||||
>
|
||||
{BUTTON_TYPE_QUESTIONS.map(({ icon, title, value }) => (
|
||||
<QuestionsMiniButton
|
||||
key={title}
|
||||
onClick={() => {
|
||||
const clonedQuestion = { ...question };
|
||||
|
||||
removeQuestionForce(quizId, clonedQuestion.id);
|
||||
createQuestion(quizId, value, totalIndex);
|
||||
updateQuestionsList<QuizQuestionBase>(quizId, totalIndex, {
|
||||
expanded: clonedQuestion.expanded,
|
||||
type: value,
|
||||
});
|
||||
}}
|
||||
icon={icon}
|
||||
text={title}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
<ButtonsOptions
|
||||
switchState={switchState}
|
||||
SSHC={setSwitchState}
|
||||
totalIndex={totalIndex}
|
||||
/>
|
||||
<SwitchAnswerOptions switchState={switchState} totalIndex={totalIndex} />
|
||||
</Box>
|
||||
);
|
||||
}
|
@ -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<boolean>(false);
|
||||
const [switchState, setSwitchState] = useState("setting");
|
||||
const [currentIndex, setCurrentIndex] = useState<number>(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<boolean>(false);
|
||||
const [switchState, setSwitchState] = useState("setting");
|
||||
const [currentIndex, setCurrentIndex] = useState<number>(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<QuizQuestionVarImg>(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 (
|
||||
<>
|
||||
<Box sx={{ pl: "20px", pr: "20px" }}>
|
||||
<AnswerDraggableList
|
||||
variants={listQuestions[quizId][totalIndex].content.variants}
|
||||
totalIndex={totalIndex}
|
||||
additionalContent={(variant, index) => (
|
||||
<>
|
||||
{!isMobile && (
|
||||
return (
|
||||
<>
|
||||
<Box sx={{ pl: "20px", pr: "20px" }}>
|
||||
<AnswerDraggableList
|
||||
variants={question.content.variants}
|
||||
totalIndex={totalIndex}
|
||||
additionalContent={(variant, index) => (
|
||||
<>
|
||||
{!isMobile && (
|
||||
<Box
|
||||
sx={{ cursor: "pointer" }}
|
||||
onClick={() => {
|
||||
setCurrentIndex(index);
|
||||
setOpen(true);
|
||||
}}
|
||||
>
|
||||
{variant.extendedText ? (
|
||||
<Box
|
||||
sx={{
|
||||
overflow: "hidden",
|
||||
width: "60px",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
background: "#EEE4FC",
|
||||
borderRadius: "3px",
|
||||
margin: "0 10px",
|
||||
height: "40px",
|
||||
}}
|
||||
>
|
||||
<Box sx={{ display: "flex", width: "40px" }}>
|
||||
<img
|
||||
src={variant.extendedText}
|
||||
alt=""
|
||||
style={{ width: "100%" }}
|
||||
/>
|
||||
</Box>
|
||||
<PlusImage />
|
||||
</Box>
|
||||
) : (
|
||||
<Button component="label" sx={{ padding: "0px" }}>
|
||||
<AddImage
|
||||
sx={{
|
||||
height: "40px",
|
||||
width: "60px",
|
||||
margin: "0 10px",
|
||||
}}
|
||||
/>
|
||||
</Button>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
additionalMobile={(variant, index) => (
|
||||
<>
|
||||
{isMobile && (
|
||||
<Box
|
||||
onClick={() => {
|
||||
setCurrentIndex(index);
|
||||
setOpen(true);
|
||||
}}
|
||||
sx={{
|
||||
overflow: "hidden",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
m: "8px",
|
||||
position: "relative",
|
||||
borderRadius: "3px",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
background: "#EEE4FC",
|
||||
height: "40px",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
{variant.extendedText ? (
|
||||
<Box
|
||||
sx={{
|
||||
overflow: "hidden",
|
||||
width: "40px",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
background: "#EEE4FC",
|
||||
height: "30px",
|
||||
borderRadius: "3px",
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src={variant.extendedText}
|
||||
alt=""
|
||||
style={{ width: "100%" }}
|
||||
/>
|
||||
</Box>
|
||||
) : (
|
||||
<Button component="label" sx={{ padding: "0px" }}>
|
||||
<Image
|
||||
sx={{
|
||||
height: "40px",
|
||||
width: "60px",
|
||||
margin: "0 10px",
|
||||
}}
|
||||
/>
|
||||
</Button>
|
||||
)}
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
width: "20px",
|
||||
background: "#EEE4FC",
|
||||
height: "40px",
|
||||
color: "white",
|
||||
backgroundColor: "#7E2AEA",
|
||||
}}
|
||||
>
|
||||
+
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
<UploadImageModal
|
||||
open={open}
|
||||
onClose={() => setOpen(false)}
|
||||
imgHC={uploadImage}
|
||||
/>
|
||||
<CropModal
|
||||
opened={opened}
|
||||
onClose={() => setOpened(false)}
|
||||
picture={question.content.variants[currentIndex]?.extendedText}
|
||||
onCropPress={url => {
|
||||
const content = produce(question.content, draft => {
|
||||
draft.variants[currentIndex].extendedText = url;
|
||||
});
|
||||
updateQuestionsList<QuizQuestionVarImg>(quizId, totalIndex, {
|
||||
content,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<Box
|
||||
sx={{ cursor: "pointer" }}
|
||||
onClick={() => {
|
||||
setCurrentIndex(index);
|
||||
setOpen(true);
|
||||
}}
|
||||
sx={{
|
||||
width: "100%",
|
||||
border: "1px solid #9A9AAF",
|
||||
borderRadius: "8px",
|
||||
display: isTablet ? "block" : "none",
|
||||
}}
|
||||
>
|
||||
{variant.image ? (
|
||||
<Box
|
||||
sx={{
|
||||
overflow: "hidden",
|
||||
width: "60px",
|
||||
<TextField
|
||||
fullWidth
|
||||
focused={false}
|
||||
placeholder={"Добавьте ответ"}
|
||||
multiline={question.content.largeCheck}
|
||||
InputProps={{
|
||||
startAdornment: (
|
||||
<>
|
||||
<InputAdornment position="start">
|
||||
<PointsIcon
|
||||
style={{ color: "#9A9AAF", fontSize: "30px" }}
|
||||
/>
|
||||
</InputAdornment>
|
||||
{!isMobile && (
|
||||
<Box
|
||||
sx={{
|
||||
width: "60px",
|
||||
height: "40px",
|
||||
background: "#EEE4FC",
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
marginRight: "20px",
|
||||
marginLeft: "12px",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<ImageAddIcons fontSize="22px" color="#7E2AEA" />
|
||||
</Box>
|
||||
<span
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
background: "#7E2AEA",
|
||||
height: "100%",
|
||||
width: "25px",
|
||||
color: "white",
|
||||
fontSize: "15px",
|
||||
}}
|
||||
>
|
||||
+
|
||||
</span>
|
||||
</Box>
|
||||
)}
|
||||
</>
|
||||
),
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
<IconButton
|
||||
sx={{ padding: "0" }}
|
||||
aria-describedby="my-popover-id"
|
||||
>
|
||||
<MessageIcon
|
||||
style={{
|
||||
color: "#9A9AAF",
|
||||
fontSize: "30px",
|
||||
marginRight: "6.5px",
|
||||
}}
|
||||
/>
|
||||
</IconButton>
|
||||
<Popover
|
||||
id="my-popover-id"
|
||||
anchorOrigin={{ vertical: "bottom", horizontal: "left" }}
|
||||
open={false}
|
||||
>
|
||||
<TextareaAutosize
|
||||
style={{ margin: "10px" }}
|
||||
placeholder="Подсказка для этого ответа"
|
||||
/>
|
||||
</Popover>
|
||||
<IconButton sx={{ padding: "0" }}>
|
||||
<DeleteIcon
|
||||
style={{
|
||||
color: theme.palette.grey2.main,
|
||||
marginRight: "-1px",
|
||||
}}
|
||||
/>
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
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 && (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
m: "8px",
|
||||
position: "relative",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{ width: "100%", background: "#EEE4FC", height: "40px" }}
|
||||
/>
|
||||
<ImageAddIcons
|
||||
style={{
|
||||
position: "absolute",
|
||||
color: "#7E2AEA",
|
||||
fontSize: "20px",
|
||||
left: "45%",
|
||||
right: "55%",
|
||||
}}
|
||||
/>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
width: "20px",
|
||||
background: "#EEE4FC",
|
||||
height: "40px",
|
||||
color: "white",
|
||||
backgroundColor: "#7E2AEA",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{ width: "100%", background: "#EEE4FC", height: "40px" }}
|
||||
/>
|
||||
<ImageAddIcons
|
||||
style={{
|
||||
position: "absolute",
|
||||
color: "#7E2AEA",
|
||||
fontSize: "20px",
|
||||
left: "45%",
|
||||
right: "55%",
|
||||
}}
|
||||
/>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
width: "20px",
|
||||
background: "#EEE4FC",
|
||||
height: "40px",
|
||||
color: "white",
|
||||
backgroundColor: "#7E2AEA",
|
||||
}}
|
||||
>
|
||||
+
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
background: "#EEE4FC",
|
||||
borderRadius: "3px",
|
||||
margin: "0 10px",
|
||||
height: "40px",
|
||||
}}
|
||||
>
|
||||
<Box sx={{ display: "flex", width: "40px" }}>
|
||||
<img
|
||||
src={variant.image}
|
||||
alt=""
|
||||
style={{ width: "100%" }}
|
||||
/>
|
||||
</Box>
|
||||
<PlusImage />
|
||||
</Box>
|
||||
) : (
|
||||
<Button component="label" sx={{ padding: "0px" }}>
|
||||
<AddImage
|
||||
sx={{
|
||||
height: "40px",
|
||||
width: "60px",
|
||||
margin: "0 10px",
|
||||
}}
|
||||
/>
|
||||
</Button>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
additionalMobile={(variant, index) => (
|
||||
<>
|
||||
{isMobile && (
|
||||
<Box
|
||||
onClick={() => {
|
||||
setCurrentIndex(index);
|
||||
setOpen(true);
|
||||
}}
|
||||
sx={{
|
||||
overflow: "hidden",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
m: "8px",
|
||||
position: "relative",
|
||||
borderRadius: "3px",
|
||||
}}
|
||||
marginBottom: "17px",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
background: "#EEE4FC",
|
||||
height: "40px",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
{variant.image ? (
|
||||
<Box
|
||||
<Link
|
||||
component="button"
|
||||
variant="body2"
|
||||
sx={{
|
||||
overflow: "hidden",
|
||||
width: "40px",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
background: "#EEE4FC",
|
||||
height: "30px",
|
||||
borderRadius: "3px",
|
||||
color: theme.palette.brightPurple.main,
|
||||
fontWeight: "400",
|
||||
fontSize: "16px",
|
||||
mr: "4px",
|
||||
height: "19px",
|
||||
}}
|
||||
onClick={() => {
|
||||
const clonedContent = { ...question.content };
|
||||
clonedContent.variants.push({
|
||||
answer: "",
|
||||
hints: "",
|
||||
extendedText: "",
|
||||
});
|
||||
updateQuestionsList<QuizQuestionVarImg>(quizId, totalIndex, {
|
||||
content: clonedContent,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src={variant.image}
|
||||
alt=""
|
||||
style={{ width: "100%" }}
|
||||
/>
|
||||
</Box>
|
||||
) : (
|
||||
<Button component="label" sx={{ padding: "0px" }}>
|
||||
<Image
|
||||
sx={{
|
||||
height: "40px",
|
||||
width: "60px",
|
||||
margin: "0 10px",
|
||||
}}
|
||||
/>
|
||||
</Button>
|
||||
)}
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
width: "20px",
|
||||
background: "#EEE4FC",
|
||||
height: "40px",
|
||||
color: "white",
|
||||
backgroundColor: "#7E2AEA",
|
||||
}}
|
||||
>
|
||||
+
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
<UploadImageModal
|
||||
open={open}
|
||||
onClose={() => setOpen(false)}
|
||||
imgHC={uploadImage}
|
||||
/>
|
||||
<CropModal
|
||||
opened={opened}
|
||||
onClose={() => setOpened(false)}
|
||||
picture={
|
||||
listQuestions[quizId][totalIndex].content.variants[currentIndex]
|
||||
.image
|
||||
}
|
||||
/>
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
border: "1px solid #9A9AAF",
|
||||
borderRadius: "8px",
|
||||
display: isTablet ? "block" : "none",
|
||||
}}
|
||||
>
|
||||
<TextField
|
||||
fullWidth
|
||||
focused={false}
|
||||
placeholder={"Добавьте ответ"}
|
||||
multiline={listQuestions[quizId][totalIndex].content.largeCheck}
|
||||
InputProps={{
|
||||
startAdornment: (
|
||||
<>
|
||||
<InputAdornment position="start">
|
||||
<PointsIcon
|
||||
style={{ color: "#9A9AAF", fontSize: "30px" }}
|
||||
/>
|
||||
</InputAdornment>
|
||||
{!isMobile && (
|
||||
<Box
|
||||
sx={{
|
||||
width: "60px",
|
||||
height: "40px",
|
||||
background: "#EEE4FC",
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
marginRight: "20px",
|
||||
marginLeft: "12px",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<ImageAddIcons fontSize="22px" color="#7E2AEA" />
|
||||
</Box>
|
||||
<span
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
background: "#7E2AEA",
|
||||
height: "100%",
|
||||
width: "25px",
|
||||
color: "white",
|
||||
fontSize: "15px",
|
||||
}}
|
||||
>
|
||||
+
|
||||
</span>
|
||||
</Box>
|
||||
)}
|
||||
</>
|
||||
),
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
<IconButton
|
||||
sx={{ padding: "0" }}
|
||||
aria-describedby="my-popover-id"
|
||||
>
|
||||
<MessageIcon
|
||||
style={{
|
||||
color: "#9A9AAF",
|
||||
fontSize: "30px",
|
||||
marginRight: "6.5px",
|
||||
}}
|
||||
/>
|
||||
</IconButton>
|
||||
<Popover
|
||||
id="my-popover-id"
|
||||
anchorOrigin={{ vertical: "bottom", horizontal: "left" }}
|
||||
open={false}
|
||||
>
|
||||
<TextareaAutosize
|
||||
style={{ margin: "10px" }}
|
||||
placeholder="Подсказка для этого ответа"
|
||||
/>
|
||||
</Popover>
|
||||
<IconButton sx={{ padding: "0" }}>
|
||||
<DeleteIcon
|
||||
style={{
|
||||
color: theme.palette.grey2.main,
|
||||
marginRight: "-1px",
|
||||
}}
|
||||
/>
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
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 && (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
m: "8px",
|
||||
position: "relative",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{ width: "100%", background: "#EEE4FC", height: "40px" }}
|
||||
/>
|
||||
<ImageAddIcons
|
||||
style={{
|
||||
position: "absolute",
|
||||
color: "#7E2AEA",
|
||||
fontSize: "20px",
|
||||
left: "45%",
|
||||
right: "55%",
|
||||
}}
|
||||
/>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
width: "20px",
|
||||
background: "#EEE4FC",
|
||||
height: "40px",
|
||||
color: "white",
|
||||
backgroundColor: "#7E2AEA",
|
||||
}}
|
||||
>
|
||||
+
|
||||
</Box>
|
||||
Добавьте ответ
|
||||
</Link>
|
||||
{isMobile ? null : (
|
||||
<>
|
||||
<Typography
|
||||
sx={{
|
||||
fontWeight: 400,
|
||||
lineHeight: "21.33px",
|
||||
color: theme.palette.grey2.main,
|
||||
fontSize: "16px",
|
||||
}}
|
||||
>
|
||||
или нажмите Enter
|
||||
</Typography>
|
||||
<EnterIcon
|
||||
style={{
|
||||
color: "#7E2AEA",
|
||||
fontSize: "24px",
|
||||
marginLeft: "6px",
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
marginBottom: "17px",
|
||||
}}
|
||||
>
|
||||
<Link
|
||||
component="button"
|
||||
variant="body2"
|
||||
sx={{
|
||||
color: theme.palette.brightPurple.main,
|
||||
fontWeight: "400",
|
||||
fontSize: "16px",
|
||||
mr: "4px",
|
||||
height: "19px",
|
||||
}}
|
||||
onClick={() => {
|
||||
const clonContent = listQuestions[quizId][totalIndex].content;
|
||||
clonContent.variants.push({
|
||||
answer: "",
|
||||
hints: "",
|
||||
emoji: "",
|
||||
image: "",
|
||||
});
|
||||
|
||||
updateQuestionsList(quizId, totalIndex, { content: clonContent });
|
||||
}}
|
||||
>
|
||||
Добавьте ответ
|
||||
</Link>
|
||||
{isMobile ? null : (
|
||||
<>
|
||||
<Typography
|
||||
sx={{
|
||||
fontWeight: 400,
|
||||
lineHeight: "21.33px",
|
||||
color: theme.palette.grey2.main,
|
||||
fontSize: "16px",
|
||||
}}
|
||||
>
|
||||
или нажмите Enter
|
||||
</Typography>
|
||||
<EnterIcon
|
||||
style={{
|
||||
color: "#7E2AEA",
|
||||
fontSize: "24px",
|
||||
marginLeft: "6px",
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
<ButtonsOptionsAndPict
|
||||
switchState={switchState}
|
||||
SSHC={SSHC}
|
||||
totalIndex={totalIndex}
|
||||
/>
|
||||
<SwitchOptionsAndPict switchState={switchState} totalIndex={totalIndex} />
|
||||
</>
|
||||
);
|
||||
<ButtonsOptionsAndPict
|
||||
switchState={switchState}
|
||||
SSHC={SSHC}
|
||||
totalIndex={totalIndex}
|
||||
/>
|
||||
<SwitchOptionsAndPict switchState={switchState} totalIndex={totalIndex} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -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<QuizQuestionVarImg>(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<QuizQuestionVarImg>(quizId, totalIndex, {
|
||||
content: { ...question.content, innerName: value },
|
||||
});
|
||||
}, 1000);
|
||||
|
||||
@ -46,7 +44,7 @@ export default function SettingOptionsAndPict({ totalIndex }: SettingOptionsAndP
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
pt: isMobile ? "25px" : "20px",
|
||||
pt: isMobile ? "30px" : "20px",
|
||||
pb: isMobile ? "25px" : "20px",
|
||||
pl: "20px",
|
||||
display: "flex",
|
||||
@ -64,13 +62,10 @@ export default function SettingOptionsAndPict({ totalIndex }: SettingOptionsAndP
|
||||
<CustomCheckbox
|
||||
sx={{ mr: isMobile ? "0px" : "16px" }}
|
||||
label={'Вариант "свой ответ"'}
|
||||
checked={listQuestions[quizId][totalIndex].content.own}
|
||||
checked={question.content.own}
|
||||
handleChange={({ target }) => {
|
||||
updateQuestionsList(quizId, totalIndex, {
|
||||
content: {
|
||||
...listQuestions[quizId][totalIndex].content,
|
||||
own: target.checked,
|
||||
},
|
||||
updateQuestionsList<QuizQuestionVarImg>(quizId, totalIndex, {
|
||||
content: { ...question.content, own: target.checked },
|
||||
});
|
||||
}}
|
||||
/>
|
||||
@ -88,9 +83,13 @@ export default function SettingOptionsAndPict({ totalIndex }: SettingOptionsAndP
|
||||
Текст-заглушка на картинке
|
||||
</Typography>
|
||||
<CustomTextField
|
||||
sx={{ maxWidth: "360px", width: "100%", mr: isMobile ? "0px" : "16px" }}
|
||||
sx={{
|
||||
maxWidth: "330px",
|
||||
width: "100%",
|
||||
mr: isMobile ? "0px" : "16px",
|
||||
}}
|
||||
placeholder={"Пример текста"}
|
||||
text={listQuestions[quizId][totalIndex].content.replText}
|
||||
text={question.content.replText}
|
||||
onChange={({ target }) => debounced(target.value)}
|
||||
/>
|
||||
</Box>
|
||||
@ -105,7 +104,7 @@ export default function SettingOptionsAndPict({ totalIndex }: SettingOptionsAndP
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: isMobile ? "13px" : "14px",
|
||||
width: "100%",
|
||||
width: isMobile ? "auto" : "100%",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
@ -116,25 +115,25 @@ export default function SettingOptionsAndPict({ totalIndex }: SettingOptionsAndP
|
||||
<CustomCheckbox
|
||||
sx={{ mr: isMobile ? "0px" : "16px" }}
|
||||
label={"Необязательный вопрос"}
|
||||
checked={listQuestions[quizId][totalIndex].content.required}
|
||||
checked={question.content.required}
|
||||
handleChange={({ target }) => {
|
||||
updateQuestionsList(quizId, totalIndex, {
|
||||
content: {
|
||||
...listQuestions[quizId][totalIndex].content,
|
||||
required: target.checked,
|
||||
},
|
||||
updateQuestionsList<QuizQuestionVarImg>(quizId, totalIndex, {
|
||||
content: { ...question.content, required: target.checked },
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<Box sx={{ display: "flex", alignItems: "center" }}>
|
||||
<CustomCheckbox
|
||||
sx={{ width: isMobile ? "90%" : "auto", mr: isMobile ? "0px" : "16px" }}
|
||||
sx={{
|
||||
width: isMobile ? "90%" : "auto",
|
||||
mr: isMobile ? "0px" : "16px",
|
||||
}}
|
||||
label={"Внутреннее название вопроса"}
|
||||
checked={listQuestions[quizId][totalIndex].content.innerNameCheck}
|
||||
checked={question.content.innerNameCheck}
|
||||
handleChange={({ target }) => {
|
||||
updateQuestionsList(quizId, totalIndex, {
|
||||
updateQuestionsList<QuizQuestionVarImg>(quizId, totalIndex, {
|
||||
content: {
|
||||
...listQuestions[quizId][totalIndex].content,
|
||||
...question.content,
|
||||
innerNameCheck: target.checked,
|
||||
innerName: "",
|
||||
},
|
||||
@ -147,10 +146,10 @@ export default function SettingOptionsAndPict({ totalIndex }: SettingOptionsAndP
|
||||
</Box>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
{listQuestions[quizId][totalIndex].content.innerNameCheck && (
|
||||
{question.content.innerNameCheck && (
|
||||
<CustomTextField
|
||||
placeholder={"Развёрнутое описание вопроса"}
|
||||
text={listQuestions[quizId][totalIndex].content.innerName}
|
||||
text={question.content.innerName}
|
||||
onChange={({ target }) => debounceDescription(target.value)}
|
||||
/>
|
||||
)}
|
||||
@ -164,7 +163,7 @@ export default function SettingOptionsAndPict({ totalIndex }: SettingOptionsAndP
|
||||
<CustomTextField
|
||||
sx={{ maxWidth: "360px", width: "100%" }}
|
||||
placeholder={"Пример текста"}
|
||||
text={listQuestions[quizId][totalIndex].content.replText}
|
||||
text={question.content.replText}
|
||||
onChange={({ target }) => debounced(target.value)}
|
||||
/>
|
||||
</>
|
||||
|
@ -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<boolean>(false);
|
||||
const [currentIndex, setCurrentIndex] = useState<number>(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<boolean>(false);
|
||||
const [currentIndex, setCurrentIndex] = useState<number>(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<QuizQuestionImages>(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<QuizQuestionImages>(quizId, totalIndex, {
|
||||
content: { ...question.content, variants: answerNew },
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box sx={{ padding: "20px" }}>
|
||||
<AnswerDraggableList
|
||||
variants={listQuestions[quizId][totalIndex].content.variants}
|
||||
totalIndex={totalIndex}
|
||||
additionalContent={(variant, index) => (
|
||||
<>
|
||||
{!isMobile && (
|
||||
<Box
|
||||
sx={{ cursor: "pointer" }}
|
||||
onClick={() => {
|
||||
setCurrentIndex(index);
|
||||
setOpen(true);
|
||||
}}
|
||||
>
|
||||
{variant.image ? (
|
||||
<Box
|
||||
sx={{
|
||||
overflow: "hidden",
|
||||
width: "60px",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
background: "#EEE4FC",
|
||||
borderRadius: "3px",
|
||||
margin: "0 10px",
|
||||
height: "40px",
|
||||
}}
|
||||
>
|
||||
<Box sx={{ display: "flex", width: "40px" }}>
|
||||
<img
|
||||
src={variant.image}
|
||||
alt=""
|
||||
style={{ width: "100%" }}
|
||||
/>
|
||||
</Box>
|
||||
<PlusImage />
|
||||
</Box>
|
||||
) : (
|
||||
<Button component="label" sx={{ padding: "0px" }}>
|
||||
<AddImage
|
||||
sx={{ height: "40px", width: "60px", margin: "0 10px" }}
|
||||
/>
|
||||
</Button>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
additionalMobile={(variant, index) => (
|
||||
<>
|
||||
{isMobile && (
|
||||
<Box
|
||||
onClick={() => {
|
||||
setCurrentIndex(index);
|
||||
setOpen(true);
|
||||
}}
|
||||
sx={{
|
||||
overflow: "hidden",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
m: "8px",
|
||||
position: "relative",
|
||||
borderRadius: "3px",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
background: "#EEE4FC",
|
||||
height: "40px",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
{variant.image ? (
|
||||
<Box
|
||||
sx={{
|
||||
overflow: "hidden",
|
||||
width: "40px",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
background: "#EEE4FC",
|
||||
height: "30px",
|
||||
borderRadius: "3px",
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src={variant.image}
|
||||
alt=""
|
||||
style={{ width: "100%" }}
|
||||
/>
|
||||
</Box>
|
||||
) : (
|
||||
<Button component="label" sx={{ padding: "0px" }}>
|
||||
<Image
|
||||
sx={{
|
||||
height: "40px",
|
||||
width: "60px",
|
||||
margin: "0 10px",
|
||||
}}
|
||||
/>
|
||||
</Button>
|
||||
return (
|
||||
<>
|
||||
<Box sx={{ padding: "20px" }}>
|
||||
<AnswerDraggableList
|
||||
variants={question.content.variants}
|
||||
totalIndex={totalIndex}
|
||||
additionalContent={(variant, index) => (
|
||||
<>
|
||||
{!isMobile && (
|
||||
<Box
|
||||
sx={{ cursor: "pointer" }}
|
||||
onClick={() => {
|
||||
setCurrentIndex(index);
|
||||
setOpen(true);
|
||||
}}
|
||||
>
|
||||
{variant.extendedText ? (
|
||||
<Box
|
||||
sx={{
|
||||
overflow: "hidden",
|
||||
width: "60px",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
background: "#EEE4FC",
|
||||
borderRadius: "3px",
|
||||
margin: "0 10px",
|
||||
height: "40px",
|
||||
}}
|
||||
>
|
||||
<Box sx={{ display: "flex", width: "40px" }}>
|
||||
<img
|
||||
src={variant.extendedText}
|
||||
alt=""
|
||||
style={{ width: "100%" }}
|
||||
/>
|
||||
</Box>
|
||||
<PlusImage />
|
||||
</Box>
|
||||
) : (
|
||||
<Button component="label" sx={{ padding: "0px" }}>
|
||||
<AddImage
|
||||
sx={{ height: "40px", width: "60px", margin: "0 10px" }}
|
||||
/>
|
||||
</Button>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
width: "20px",
|
||||
background: "#EEE4FC",
|
||||
height: "40px",
|
||||
color: "white",
|
||||
backgroundColor: "#7E2AEA",
|
||||
additionalMobile={(variant, index) => (
|
||||
<>
|
||||
{isMobile && (
|
||||
<Box
|
||||
onClick={() => {
|
||||
setCurrentIndex(index);
|
||||
setOpen(true);
|
||||
}}
|
||||
sx={{
|
||||
overflow: "hidden",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
m: "8px",
|
||||
position: "relative",
|
||||
borderRadius: "3px",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
background: "#EEE4FC",
|
||||
height: "40px",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
{variant.extendedText ? (
|
||||
<Box
|
||||
sx={{
|
||||
overflow: "hidden",
|
||||
width: "40px",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
background: "#EEE4FC",
|
||||
height: "30px",
|
||||
borderRadius: "3px",
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src={variant.extendedText}
|
||||
alt=""
|
||||
style={{ width: "100%" }}
|
||||
/>
|
||||
</Box>
|
||||
) : (
|
||||
<Button component="label" sx={{ padding: "0px" }}>
|
||||
<Image
|
||||
sx={{
|
||||
height: "40px",
|
||||
width: "60px",
|
||||
margin: "0 10px",
|
||||
}}
|
||||
/>
|
||||
</Button>
|
||||
)}
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
width: "20px",
|
||||
background: "#EEE4FC",
|
||||
height: "40px",
|
||||
color: "white",
|
||||
backgroundColor: "#7E2AEA",
|
||||
}}
|
||||
>
|
||||
+
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
<UploadImageModal
|
||||
open={open}
|
||||
onClose={() => setOpen(false)}
|
||||
imgHC={uploadImage}
|
||||
/>
|
||||
<CropModal
|
||||
opened={opened}
|
||||
onClose={() => setOpened(false)}
|
||||
picture={question.content.variants[currentIndex]?.extendedText}
|
||||
onCropPress={url => {
|
||||
const content = produce(question.content, draft => {
|
||||
draft.variants[currentIndex].extendedText = url;
|
||||
});
|
||||
updateQuestionsList<QuizQuestionImages>(quizId, totalIndex, {
|
||||
content,
|
||||
});
|
||||
}}
|
||||
>
|
||||
+
|
||||
</Box>
|
||||
/>
|
||||
<Box sx={{ display: "flex", alignItems: "center", gap: "10px" }}>
|
||||
<Link
|
||||
component="button"
|
||||
variant="body2"
|
||||
sx={{ color: theme.palette.brightPurple.main }}
|
||||
onClick={addNewAnswer}
|
||||
>
|
||||
Добавьте ответ
|
||||
</Link>
|
||||
{isMobile ? null : (
|
||||
<>
|
||||
<Typography
|
||||
sx={{
|
||||
fontWeight: 400,
|
||||
lineHeight: "21.33px",
|
||||
color: theme.palette.grey2.main,
|
||||
fontSize: "16px",
|
||||
}}
|
||||
>
|
||||
или нажмите Enter
|
||||
</Typography>
|
||||
<EnterIcon
|
||||
style={{
|
||||
color: "#7E2AEA",
|
||||
fontSize: "24px",
|
||||
marginLeft: "6px",
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
<UploadImageModal
|
||||
open={open}
|
||||
onClose={() => setOpen(false)}
|
||||
imgHC={uploadImage}
|
||||
/>
|
||||
<CropModal
|
||||
opened={opened}
|
||||
onClose={() => setOpened(false)}
|
||||
picture={
|
||||
listQuestions[quizId][totalIndex].content.variants[currentIndex]
|
||||
.image
|
||||
}
|
||||
/>
|
||||
<Box sx={{ display: "flex", alignItems: "center", gap: "10px" }}>
|
||||
<Link
|
||||
component="button"
|
||||
variant="body2"
|
||||
sx={{ color: theme.palette.brightPurple.main }}
|
||||
onClick={addNewAnswer}
|
||||
>
|
||||
Добавьте ответ
|
||||
</Link>
|
||||
{isMobile ? null : (
|
||||
<>
|
||||
<Typography
|
||||
sx={{
|
||||
fontWeight: 400,
|
||||
lineHeight: "21.33px",
|
||||
color: theme.palette.grey2.main,
|
||||
fontSize: "16px",
|
||||
}}
|
||||
>
|
||||
или нажмите Enter
|
||||
</Typography>
|
||||
<EnterIcon
|
||||
style={{
|
||||
color: "#7E2AEA",
|
||||
fontSize: "24px",
|
||||
marginLeft: "6px",
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
<ButtonsOptions
|
||||
switchState={switchState}
|
||||
SSHC={SSHC}
|
||||
totalIndex={totalIndex}
|
||||
/>
|
||||
<SwitchAnswerOptionsPict
|
||||
switchState={switchState}
|
||||
totalIndex={totalIndex}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
</Box>
|
||||
<ButtonsOptions switchState={switchState} SSHC={SSHC} totalIndex={totalIndex} />
|
||||
<SwitchAnswerOptionsPict switchState={switchState} totalIndex={totalIndex} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -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) {
|
||||
<Button
|
||||
onClick={onClick}
|
||||
variant="outlined"
|
||||
startIcon={<Icon color={isActive ? theme.palette.navbarbg.main : theme.palette.brightPurple.main} />}
|
||||
startIcon={
|
||||
<Icon
|
||||
color={
|
||||
isActive
|
||||
? theme.palette.navbarbg.main
|
||||
: theme.palette.brightPurple.main
|
||||
}
|
||||
/>
|
||||
}
|
||||
sx={{
|
||||
backgroundColor: isActive ? theme.palette.brightPurple.main : "#eee4fc",
|
||||
|
||||
borderRadius: 0,
|
||||
border: "none",
|
||||
color: isActive ? theme.palette.brightPurple.main : theme.palette.grey2.main,
|
||||
color: isActive
|
||||
? theme.palette.brightPurple.main
|
||||
: theme.palette.grey2.main,
|
||||
p: "7px",
|
||||
width: "40px",
|
||||
height: "40px",
|
||||
@ -54,39 +81,42 @@ export function SelectIconButton({ Icon, isActive = false, onClick }: Props) {
|
||||
},
|
||||
"&:hover": {
|
||||
border: "none",
|
||||
borderColor: isActive ? theme.palette.brightPurple.main : theme.palette.grey2.main,
|
||||
borderColor: isActive
|
||||
? theme.palette.brightPurple.main
|
||||
: theme.palette.grey2.main,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default function SettingOpytionsPict({ totalIndex }: SettingOpytionsPictProps) {
|
||||
export default function SettingOpytionsPict({
|
||||
totalIndex,
|
||||
}: SettingOpytionsPictProps) {
|
||||
const quizId = Number(useParams().quizId);
|
||||
const { listQuestions } = questionStore();
|
||||
const theme = useTheme();
|
||||
const isTablet = useMediaQuery(theme.breakpoints.down(985));
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||
const question = listQuestions[quizId][totalIndex] as QuizQuestionImages;
|
||||
const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990));
|
||||
|
||||
const debounced = useDebouncedCallback((value) => {
|
||||
let clonContent = listQuestions[quizId][totalIndex].content;
|
||||
clonContent.innerName = value;
|
||||
updateQuestionsList(quizId, totalIndex, {
|
||||
content: clonContent,
|
||||
updateQuestionsList<QuizQuestionImages>(quizId, totalIndex, {
|
||||
content: { ...question.content, innerName: value },
|
||||
});
|
||||
}, 1000);
|
||||
|
||||
useEffect(() => {
|
||||
if (!listQuestions[quizId][totalIndex].content.xy) {
|
||||
if (!question.content.xy) {
|
||||
updateProportions("1:1");
|
||||
}
|
||||
}, []);
|
||||
|
||||
const updateProportions = (proportions: string) => {
|
||||
const clonContent = listQuestions[quizId][totalIndex].content;
|
||||
clonContent.xy = proportions;
|
||||
updateQuestionsList(quizId, totalIndex, { content: clonContent });
|
||||
const updateProportions = (proportions: Proportion) => {
|
||||
updateQuestionsList<QuizQuestionImages>(quizId, totalIndex, {
|
||||
content: { ...question.content, xy: proportions },
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
@ -112,7 +142,14 @@ export default function SettingOpytionsPict({ totalIndex }: SettingOpytionsPictP
|
||||
}}
|
||||
>
|
||||
<Box sx={{ pb: isMobile ? "11px" : "6px" }}>
|
||||
<Typography sx={{ fontWeight: "500", fontSize: "18px", color: " #4D4D4D", mb: isMobile ? "10px" : "14px" }}>
|
||||
<Typography
|
||||
sx={{
|
||||
fontWeight: "500",
|
||||
fontSize: "18px",
|
||||
color: " #4D4D4D",
|
||||
mb: isMobile ? "10px" : "14px",
|
||||
}}
|
||||
>
|
||||
Пропорции
|
||||
</Typography>
|
||||
<Box
|
||||
@ -125,38 +162,39 @@ export default function SettingOpytionsPict({ totalIndex }: SettingOpytionsPictP
|
||||
<SelectIconButton
|
||||
key={index}
|
||||
onClick={() => updateProportions(value)}
|
||||
isActive={listQuestions[quizId][totalIndex].content.xy === value}
|
||||
isActive={question.content.xy === value}
|
||||
Icon={icon}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Typography sx={{ fontWeight: "500", fontSize: "18px", color: " #4D4D4D" }}>Настройки ответов</Typography>
|
||||
<Typography
|
||||
sx={{ fontWeight: "500", fontSize: "18px", color: " #4D4D4D" }}
|
||||
>
|
||||
Настройки ответов
|
||||
</Typography>
|
||||
|
||||
<CustomCheckbox
|
||||
sx={{ display: "block", mr: isMobile ? "0px" : "16px" }}
|
||||
label={"Можно несколько"}
|
||||
checked={listQuestions[quizId][totalIndex].content.multi}
|
||||
checked={question.content.multi}
|
||||
handleChange={({ target }) =>
|
||||
updateQuestionsList(quizId, totalIndex, {
|
||||
content: {
|
||||
...listQuestions[quizId][totalIndex].content,
|
||||
multi: target.checked,
|
||||
},
|
||||
updateQuestionsList<QuizQuestionImages>(quizId, totalIndex, {
|
||||
content: { ...question.content, multi: target.checked },
|
||||
})
|
||||
}
|
||||
/>
|
||||
{listQuestions[quizId][totalIndex].content.xy !== "1:1" &&
|
||||
listQuestions[quizId][totalIndex].content.format !== "masonry" && (
|
||||
{question.content.xy !== "1:1" &&
|
||||
question.content.format !== "masonry" && (
|
||||
<CustomCheckbox
|
||||
sx={{ display: "block", mr: isMobile ? "0px" : "16px" }}
|
||||
label={"Большие картинки"}
|
||||
checked={listQuestions[quizId][totalIndex].content.largeCheck}
|
||||
checked={question.content.largeCheck}
|
||||
handleChange={({ target }) =>
|
||||
updateQuestionsList(quizId, totalIndex, {
|
||||
updateQuestionsList<QuizQuestionImages>(quizId, totalIndex, {
|
||||
content: {
|
||||
...listQuestions[quizId][totalIndex].content,
|
||||
...question.content,
|
||||
largeCheck: target.checked,
|
||||
},
|
||||
})
|
||||
@ -166,75 +204,78 @@ export default function SettingOpytionsPict({ totalIndex }: SettingOpytionsPictP
|
||||
<CustomCheckbox
|
||||
sx={{ display: "block", mr: isMobile ? "0px" : "16px" }}
|
||||
label={'Вариант "свой ответ"'}
|
||||
checked={listQuestions[quizId][totalIndex].content.own}
|
||||
checked={question.content.own}
|
||||
handleChange={({ target }) =>
|
||||
updateQuestionsList(quizId, totalIndex, {
|
||||
content: {
|
||||
...listQuestions[quizId][totalIndex].content,
|
||||
own: target.checked,
|
||||
},
|
||||
updateQuestionsList<QuizQuestionImages>(quizId, totalIndex, {
|
||||
content: { ...question.content, own: target.checked },
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
pt: isMobile ? "0px" : "20px",
|
||||
pb: "20px",
|
||||
pl: isTablet ? "20px" : "",
|
||||
pr: isFigmaTablte ? "30px" : "20px",
|
||||
pt: isMobile ? "25px" : "20px",
|
||||
pb: isMobile ? "25px" : "20px",
|
||||
pl: "20px",
|
||||
pr: isFigmaTablte ? (isMobile ? "20px" : "0px") : "28px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: isMobile ? "13px" : "14px",
|
||||
gap: "14px",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<Box sx={{ pb: isMobile ? "11px" : "6px" }}>
|
||||
<Typography sx={{ fontWeight: "500", fontSize: "18px", color: " #4D4D4D", mb: isMobile ? "10px" : "14px" }}>
|
||||
Формат
|
||||
</Typography>
|
||||
<Box
|
||||
<Box
|
||||
sx={{
|
||||
marginBottom: "5px",
|
||||
opacity: question.content.xy !== "1:1" ? 1 : 0,
|
||||
display: isTablet
|
||||
? question.content.xy === "1:1"
|
||||
? "none"
|
||||
: "block"
|
||||
: "block",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
display: "flex",
|
||||
gap: "10px",
|
||||
marginBottom: "15px",
|
||||
fontWeight: "500",
|
||||
fontSize: "18px",
|
||||
color: " #4D4D4D",
|
||||
}}
|
||||
>
|
||||
<SelectIconButton
|
||||
onClick={() =>
|
||||
updateQuestionsList(quizId, totalIndex, {
|
||||
content: {
|
||||
...listQuestions[quizId][totalIndex].content,
|
||||
format: "carousel",
|
||||
},
|
||||
})
|
||||
}
|
||||
isActive={listQuestions[quizId][totalIndex].content.format === "carousel"}
|
||||
Icon={FormatIcon2}
|
||||
/>
|
||||
<SelectIconButton
|
||||
onClick={() =>
|
||||
updateQuestionsList(quizId, totalIndex, {
|
||||
content: {
|
||||
...listQuestions[quizId][totalIndex].content,
|
||||
format: "masonry",
|
||||
},
|
||||
})
|
||||
}
|
||||
isActive={listQuestions[quizId][totalIndex].content.format === "masonry"}
|
||||
Icon={FormatIcon1}
|
||||
/>
|
||||
</Box>
|
||||
Формат
|
||||
</Typography>
|
||||
<SelectIconButton
|
||||
onClick={() =>
|
||||
updateQuestionsList<QuizQuestionImages>(quizId, totalIndex, {
|
||||
content: { ...question.content, format: "carousel" },
|
||||
})
|
||||
}
|
||||
isActive={question.content.format === "carousel"}
|
||||
Icon={FormatIcon2}
|
||||
/>
|
||||
<SelectIconButton
|
||||
onClick={() =>
|
||||
updateQuestionsList<QuizQuestionImages>(quizId, totalIndex, {
|
||||
content: { ...question.content, format: "masonry" },
|
||||
})
|
||||
}
|
||||
isActive={question.content.format === "masonry"}
|
||||
Icon={FormatIcon1}
|
||||
/>
|
||||
</Box>
|
||||
<Typography sx={{ fontWeight: "500", fontSize: "18px", color: " #4D4D4D" }}>Настройки вопросов</Typography>
|
||||
<Typography
|
||||
sx={{ fontWeight: "500", fontSize: "18px", color: " #4D4D4D" }}
|
||||
>
|
||||
Настройки вопросов
|
||||
</Typography>
|
||||
<CustomCheckbox
|
||||
sx={{ alignItems: isMobile ? "flex-start" : "" }}
|
||||
label={"Необязательный вопрос"}
|
||||
checked={listQuestions[quizId][totalIndex].content.required}
|
||||
checked={question.content.required}
|
||||
handleChange={({ target }) =>
|
||||
updateQuestionsList(quizId, totalIndex, {
|
||||
content: {
|
||||
...listQuestions[quizId][totalIndex].content,
|
||||
required: target.checked,
|
||||
},
|
||||
updateQuestionsList<QuizQuestionImages>(quizId, totalIndex, {
|
||||
content: { ...question.content, required: target.checked },
|
||||
})
|
||||
}
|
||||
/>
|
||||
@ -246,28 +287,35 @@ export default function SettingOpytionsPict({ totalIndex }: SettingOpytionsPictP
|
||||
}}
|
||||
>
|
||||
<CustomCheckbox
|
||||
sx={{
|
||||
height: isMobile ? "100%" : "26px",
|
||||
alignItems: isMobile ? "flex-start" : "center",
|
||||
}}
|
||||
label={"Внутреннее название вопроса"}
|
||||
checked={listQuestions[quizId][totalIndex].content.innerNameCheck}
|
||||
checked={question.content.innerNameCheck}
|
||||
handleChange={({ target }) =>
|
||||
updateQuestionsList(quizId, totalIndex, {
|
||||
updateQuestionsList<QuizQuestionImages>(quizId, totalIndex, {
|
||||
content: {
|
||||
...listQuestions[quizId][totalIndex].content,
|
||||
...question.content,
|
||||
innerNameCheck: target.checked,
|
||||
innerName: "",
|
||||
},
|
||||
})
|
||||
}
|
||||
/>
|
||||
<Tooltip title="Будет отображаться как заголовок вопроса в приходящих заявках." placement="top">
|
||||
<Tooltip
|
||||
title="Будет отображаться как заголовок вопроса в приходящих заявках."
|
||||
placement="top"
|
||||
>
|
||||
<Box>
|
||||
<InfoIcon />
|
||||
</Box>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
{listQuestions[quizId][totalIndex].content.innerNameCheck && (
|
||||
{question.content.innerNameCheck && (
|
||||
<CustomTextField
|
||||
placeholder={"Внутреннее описание вопроса"}
|
||||
text={listQuestions[quizId][totalIndex].content.innerName}
|
||||
text={question.content.innerName}
|
||||
onChange={({ target }) => debounced(target.value)}
|
||||
/>
|
||||
)}
|
||||
|
@ -11,6 +11,8 @@ import { questionStore, updateQuestionsList } from "@root/questions";
|
||||
|
||||
import InfoIcon from "../../../assets/icons/InfoIcon";
|
||||
|
||||
import type { QuizQuestionText } from "../../../model/questionTypes/text";
|
||||
|
||||
interface Props {
|
||||
totalIndex: number;
|
||||
}
|
||||
@ -19,13 +21,13 @@ export default function OwnTextField({ totalIndex }: Props) {
|
||||
const quizId = Number(useParams().quizId);
|
||||
const { listQuestions } = questionStore();
|
||||
const theme = useTheme();
|
||||
const isWrapperColumn = useMediaQuery(theme.breakpoints.down(980));
|
||||
const question = listQuestions[quizId][totalIndex] as QuizQuestionText;
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||
const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990));
|
||||
const debounced = useDebouncedCallback((value) => {
|
||||
const clonContent = listQuestions[quizId][totalIndex].content;
|
||||
clonContent.placeholder = value;
|
||||
updateQuestionsList(quizId, totalIndex, { content: clonContent });
|
||||
updateQuestionsList<QuizQuestionText>(quizId, totalIndex, {
|
||||
content: { ...question.content, placeholder: value },
|
||||
});
|
||||
}, 1000);
|
||||
|
||||
const SSHC = (data: string) => {
|
||||
@ -39,7 +41,7 @@ export default function OwnTextField({ totalIndex }: Props) {
|
||||
width: "auto",
|
||||
maxWidth: "745px",
|
||||
display: "flex",
|
||||
pb: "15px",
|
||||
pb: "20px",
|
||||
pl: "20px",
|
||||
pr: "20px",
|
||||
flexDirection: "column",
|
||||
@ -48,11 +50,11 @@ export default function OwnTextField({ totalIndex }: Props) {
|
||||
>
|
||||
<CustomTextField
|
||||
placeholder={"Пример ответа"}
|
||||
text={listQuestions[quizId][totalIndex].content.placeholder}
|
||||
text={question.content.placeholder}
|
||||
onChange={({ target }) => debounced(target.value)}
|
||||
sx={{ maxWidth: isFigmaTablte ? "549px" : "640px", width: "100%" }}
|
||||
sx={{ maxWidth: isFigmaTablte ? "549px" : "640px", width: "100%", mt: isMobile ? "15px" : "0px" }}
|
||||
/>
|
||||
<Box sx={{ display: "flex", alignItems: "center", gap: "12px" }}>
|
||||
<Box sx={{ display: "flex", alignItems: isMobile ? "flex-start" : "center", gap: "12px" }}>
|
||||
<Typography
|
||||
sx={{
|
||||
fontWeight: 400,
|
||||
|
@ -20,6 +20,8 @@ import CheckedIcon from "@ui_kit/RadioCheck";
|
||||
import CheckIcon from "@ui_kit/RadioIcon";
|
||||
import InfoIcon from "../../../assets/icons/InfoIcon";
|
||||
|
||||
import type { QuizQuestionText } from "../../../model/questionTypes/text";
|
||||
|
||||
type SettingTextFieldProps = {
|
||||
totalIndex: number;
|
||||
};
|
||||
@ -41,12 +43,13 @@ export default function SettingTextField({ totalIndex }: SettingTextFieldProps)
|
||||
const theme = useTheme();
|
||||
const isWrappColumn = useMediaQuery(theme.breakpoints.down(980));
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||
const question = listQuestions[quizId][totalIndex] as QuizQuestionText;
|
||||
const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990));
|
||||
|
||||
const debounced = useDebouncedCallback((value) => {
|
||||
let clonContent = listQuestions[quizId][totalIndex].content;
|
||||
clonContent.innerName = value;
|
||||
updateQuestionsList(quizId, totalIndex, { content: clonContent });
|
||||
updateQuestionsList<QuizQuestionText>(quizId, totalIndex, {
|
||||
content: { ...question.content, innerName: value },
|
||||
});
|
||||
}, 1000);
|
||||
|
||||
return (
|
||||
@ -73,17 +76,16 @@ export default function SettingTextField({ totalIndex }: SettingTextFieldProps)
|
||||
sx={{ display: "flex", flexDirection: "column", gap: "14px", width: "100%" }}
|
||||
aria-labelledby="demo-controlled-radio-buttons-group"
|
||||
name="controlled-radio-buttons-group"
|
||||
value={ANSWER_TYPES.findIndex(({ value }) => listQuestions[quizId][totalIndex].content[value])}
|
||||
value={ANSWER_TYPES.findIndex(
|
||||
({ value }) => question.content.answerType === value
|
||||
)}
|
||||
onChange={({ target }: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const clonContent = {
|
||||
...listQuestions[quizId][totalIndex].content,
|
||||
single: false,
|
||||
multi: false,
|
||||
number: false,
|
||||
[ANSWER_TYPES[Number(target.value)].value]: true,
|
||||
};
|
||||
|
||||
updateQuestionsList(quizId, totalIndex, { content: clonContent });
|
||||
updateQuestionsList<QuizQuestionText>(quizId, totalIndex, {
|
||||
content: {
|
||||
...question.content,
|
||||
answerType: ANSWER_TYPES[Number(target.value)].value,
|
||||
},
|
||||
});
|
||||
}}
|
||||
>
|
||||
{ANSWER_TYPES.map(({ name }, index) => (
|
||||
@ -107,49 +109,67 @@ export default function SettingTextField({ totalIndex }: SettingTextFieldProps)
|
||||
pt: isMobile ? "0px" : "20px",
|
||||
pb: "20px",
|
||||
pl: isFigmaTablte ? (isWrappColumn ? "20px" : "34px") : "20px",
|
||||
pr: isFigmaTablte ? "14px" : "20px",
|
||||
pr: "20px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: isMobile ? "13px" : "14px",
|
||||
gap: "14px",
|
||||
}}
|
||||
>
|
||||
<Typography sx={{ height: isMobile ? "18px" : "auto", fontWeight: "500", fontSize: "18px", color: " #4D4D4D" }}>
|
||||
Настройки вопросов
|
||||
</Typography>
|
||||
<CustomCheckbox
|
||||
sx={{ display: "block", mr: isMobile ? "0px" : "16px" }}
|
||||
sx={{
|
||||
display: isMobile ? "flex" : "block",
|
||||
mr: isMobile ? "0px" : "16px",
|
||||
alignItems: isMobile ? "flex-end" : "center",
|
||||
}}
|
||||
label={"Автозаполнение адреса"}
|
||||
checked={listQuestions[quizId][totalIndex].content.autofill}
|
||||
checked={question.content.autofill}
|
||||
handleChange={({ target }) => {
|
||||
const clonContent = listQuestions[quizId][totalIndex].content;
|
||||
clonContent.autofill = target.checked;
|
||||
updateQuestionsList(quizId, totalIndex, { content: clonContent });
|
||||
updateQuestionsList<QuizQuestionText>(quizId, totalIndex, {
|
||||
content: { ...question.content, autofill: target.checked },
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<CustomCheckbox
|
||||
sx={{ display: "block", mr: isMobile ? "0px" : "16px" }}
|
||||
sx={{
|
||||
display: isMobile ? "flex" : "block",
|
||||
mr: isMobile ? "0px" : "16px",
|
||||
alignItems: isMobile ? "flex-end" : "center",
|
||||
}}
|
||||
label={"Необязательный вопрос"}
|
||||
checked={!listQuestions[quizId][totalIndex].required}
|
||||
checked={!question.required}
|
||||
handleChange={(e) => {
|
||||
updateQuestionsList(quizId, totalIndex, {
|
||||
updateQuestionsList<QuizQuestionText>(quizId, totalIndex, {
|
||||
required: !e.target.checked,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<Box sx={{ width: isMobile ? "90%" : "auto", display: "flex", alignItems: "center" }}>
|
||||
<Box
|
||||
sx={{
|
||||
width: isMobile ? "90%" : "auto",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<CustomCheckbox
|
||||
sx={{ display: "block", mr: isMobile ? "0px" : "16px" }}
|
||||
sx={{
|
||||
alignItems: isMobile ? "flex-start" : "center",
|
||||
display: isMobile ? "flex" : "block",
|
||||
mr: isMobile ? "0px" : "16px",
|
||||
height: isMobile ? "100%" : "26px",
|
||||
}}
|
||||
label={"Внутреннее название вопроса"}
|
||||
checked={listQuestions[quizId][totalIndex].content.innerNameCheck}
|
||||
handleChange={(e) => {
|
||||
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<QuizQuestionText>(quizId, totalIndex, {
|
||||
content: {
|
||||
...question.content,
|
||||
innerNameCheck: target.checked,
|
||||
innerName: target.checked ? question.content.innerName : "",
|
||||
},
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<Tooltip title="Будет отображаться как заголовок вопроса в приходящих заявках." placement="top">
|
||||
@ -158,10 +178,10 @@ export default function SettingTextField({ totalIndex }: SettingTextFieldProps)
|
||||
</Box>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
{listQuestions[quizId][totalIndex].content.innerNameCheck && (
|
||||
{question.content.innerNameCheck && (
|
||||
<CustomTextField
|
||||
placeholder={"Развёрнутое описание вопроса"}
|
||||
text={listQuestions[quizId][totalIndex].content.innerName}
|
||||
text={question.content.innerName}
|
||||
onChange={({ target }) => debounced(target.value)}
|
||||
/>
|
||||
)}
|
||||
|
@ -15,6 +15,8 @@ import { AddPlusVideo } from "@icons/questionsPage/addPlusVideo";
|
||||
import { ImageAddIcons } from "@icons/ImageAddIcons";
|
||||
import { VideofileIcon } from "@icons/questionsPage/VideofileIcon";
|
||||
|
||||
import type { QuizQuestionPage } from "../../../model/questionTypes/page";
|
||||
|
||||
type Props = {
|
||||
disableInput?: boolean;
|
||||
totalIndex: number;
|
||||
@ -30,10 +32,11 @@ export default function PageOptions({ disableInput, totalIndex }: Props) {
|
||||
const isTablet = useMediaQuery(theme.breakpoints.down(980));
|
||||
const isFigmaTablet = useMediaQuery(theme.breakpoints.down(990));
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(780));
|
||||
const question = listQuestions[quizId][totalIndex] as QuizQuestionPage;
|
||||
const debounced = useDebouncedCallback((value) => {
|
||||
const clonContent = listQuestions[quizId][totalIndex].content;
|
||||
clonContent.text = value;
|
||||
updateQuestionsList(quizId, totalIndex, { content: clonContent });
|
||||
updateQuestionsList<QuizQuestionPage>(quizId, totalIndex, {
|
||||
content: { ...question.content, text: value },
|
||||
});
|
||||
}, 1000);
|
||||
|
||||
const SSHC = (data: string) => {
|
||||
@ -52,13 +55,14 @@ export default function PageOptions({ disableInput, totalIndex }: Props) {
|
||||
gap: isMobile ? "25px" : "20px",
|
||||
}}
|
||||
>
|
||||
<Box sx={{ display: disableInput ? "none" : "" }}>
|
||||
<Box sx={{ display: disableInput ? "none" : "", mt: isMobile ? "15px" : "0px" }}>
|
||||
<CustomTextField
|
||||
placeholder={"Можно добавить текст"}
|
||||
text={listQuestions[quizId][totalIndex].content.text}
|
||||
text={question.content.text}
|
||||
onChange={({ target }) => debounced(target.value)}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
mb: "20px",
|
||||
@ -79,7 +83,50 @@ export default function PageOptions({ disableInput, totalIndex }: Props) {
|
||||
}}
|
||||
>
|
||||
{isMobile ? (
|
||||
<AddPlusImage />
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
width: "120px",
|
||||
position: "relative",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
background: "#EEE4FC",
|
||||
height: "40px",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
borderTopLeftRadius: "4px",
|
||||
borderBottomLeftRadius: "4px",
|
||||
}}
|
||||
>
|
||||
<ImageAddIcons
|
||||
style={{
|
||||
color: "#7E2AEA",
|
||||
fontSize: "20px",
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
width: "20px",
|
||||
background: "#EEE4FC",
|
||||
height: "40px",
|
||||
color: "white",
|
||||
backgroundColor: "#7E2AEA",
|
||||
borderTopRightRadius: "4px",
|
||||
borderBottomRightRadius: "4px",
|
||||
}}
|
||||
>
|
||||
+
|
||||
</Box>
|
||||
</Box>
|
||||
) : (
|
||||
<Box
|
||||
sx={{
|
||||
@ -127,10 +174,11 @@ export default function PageOptions({ disableInput, totalIndex }: Props) {
|
||||
onClose={() => setOpenImageModal(false)}
|
||||
imgHC={(fileList) => {
|
||||
if (fileList?.length) {
|
||||
const clonContent = listQuestions[quizId][totalIndex].content;
|
||||
clonContent.picture = URL.createObjectURL(fileList[0]);
|
||||
updateQuestionsList(quizId, totalIndex, {
|
||||
content: clonContent,
|
||||
updateQuestionsList<QuizQuestionPage>(quizId, totalIndex, {
|
||||
content: {
|
||||
...question.content,
|
||||
picture: URL.createObjectURL(fileList[0]),
|
||||
},
|
||||
});
|
||||
}
|
||||
}}
|
||||
@ -146,7 +194,50 @@ export default function PageOptions({ disableInput, totalIndex }: Props) {
|
||||
}}
|
||||
>
|
||||
{isMobile ? (
|
||||
<AddPlusVideo />
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
width: "120px",
|
||||
position: "relative",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
background: "#EEE4FC",
|
||||
height: "40px",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
borderTopLeftRadius: "4px",
|
||||
borderBottomLeftRadius: "4px",
|
||||
}}
|
||||
>
|
||||
<VideofileIcon
|
||||
style={{
|
||||
color: "#7E2AEA",
|
||||
fontSize: "20px",
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
width: "20px",
|
||||
background: "#EEE4FC",
|
||||
height: "40px",
|
||||
color: "white",
|
||||
backgroundColor: "#7E2AEA",
|
||||
borderTopRightRadius: "4px",
|
||||
borderBottomRightRadius: "4px",
|
||||
}}
|
||||
>
|
||||
+
|
||||
</Box>
|
||||
</Box>
|
||||
) : (
|
||||
<Box
|
||||
sx={{
|
||||
@ -192,11 +283,11 @@ export default function PageOptions({ disableInput, totalIndex }: Props) {
|
||||
<UploadVideoModal
|
||||
open={openVideoModal}
|
||||
onClose={() => setOpenVideoModal(false)}
|
||||
video={listQuestions[quizId][totalIndex].content.video}
|
||||
video={question.content.video}
|
||||
onUpload={(url) => {
|
||||
const clonContent = listQuestions[quizId][totalIndex].content;
|
||||
clonContent.video = url;
|
||||
updateQuestionsList(quizId, totalIndex, { content: clonContent });
|
||||
updateQuestionsList<QuizQuestionPage>(quizId, totalIndex, {
|
||||
content: { ...question.content, video: url },
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
|
@ -14,63 +14,81 @@ import { questionStore, updateQuestionsList } from "@root/questions";
|
||||
|
||||
import InfoIcon from "../../../assets/icons/InfoIcon";
|
||||
|
||||
import type { QuizQuestionPage } from "../../../model/questionTypes/page";
|
||||
|
||||
type SettingPageOptionsProps = {
|
||||
totalIndex: number;
|
||||
};
|
||||
|
||||
export default function SettingPageOptions({ totalIndex }: SettingPageOptionsProps) {
|
||||
export default function SettingPageOptions({
|
||||
totalIndex,
|
||||
}: SettingPageOptionsProps) {
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||
const quizId = Number(useParams().quizId);
|
||||
const { listQuestions } = questionStore();
|
||||
const question = listQuestions[quizId][totalIndex] as QuizQuestionPage;
|
||||
const debounced = useDebouncedCallback((value) => {
|
||||
let clonContent = listQuestions[quizId][totalIndex].content;
|
||||
clonContent.innerName = value;
|
||||
updateQuestionsList(quizId, totalIndex, {
|
||||
content: clonContent,
|
||||
updateQuestionsList<QuizQuestionPage>(quizId, totalIndex, {
|
||||
content: { ...question.content, innerName: value },
|
||||
});
|
||||
}, 1000);
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
boxSizing: "border-box",
|
||||
pt: isMobile ? "25px" : "20px",
|
||||
pb: isMobile ? "25px" : "20px",
|
||||
pl: "20px",
|
||||
pr: "20px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "14px",
|
||||
width: "100%",
|
||||
width: isMobile ? "auto" : "100%",
|
||||
}}
|
||||
>
|
||||
<Typography sx={{ height: isMobile ? "18px" : "auto", fontWeight: "500", fontSize: "18px", color: " #4D4D4D" }}>
|
||||
<Typography
|
||||
sx={{
|
||||
height: isMobile ? "18px" : "auto",
|
||||
fontWeight: "500",
|
||||
fontSize: "18px",
|
||||
color: " #4D4D4D",
|
||||
}}
|
||||
>
|
||||
Настройки вопроса
|
||||
</Typography>
|
||||
<Box sx={{ width: isMobile ? "90%" : "auto", display: "flex", alignItems: "center" }}>
|
||||
<Box sx={{ display: "flex", alignItems: "flex-start" }}>
|
||||
<CustomCheckbox
|
||||
sx={{ mr: isMobile ? "0px" : "16px" }}
|
||||
sx={{
|
||||
mr: isMobile ? "0px" : "16px",
|
||||
height: isMobile ? "100%" : "26px",
|
||||
}}
|
||||
label={"Внутреннее название вопроса"}
|
||||
checked={listQuestions[quizId][totalIndex].content.innerNameCheck}
|
||||
checked={question.content.innerNameCheck}
|
||||
handleChange={({ target }) =>
|
||||
updateQuestionsList(quizId, totalIndex, {
|
||||
updateQuestionsList<QuizQuestionPage>(quizId, totalIndex, {
|
||||
content: {
|
||||
...listQuestions[quizId][totalIndex].content,
|
||||
...question.content,
|
||||
innerNameCheck: target.checked,
|
||||
innerName: "",
|
||||
},
|
||||
})
|
||||
}
|
||||
/>
|
||||
<Tooltip title="Будет отображаться как заголовок вопроса в приходящих заявках." placement="top">
|
||||
<Tooltip
|
||||
title="Будет отображаться как заголовок вопроса в приходящих заявках."
|
||||
placement="top"
|
||||
>
|
||||
<Box>
|
||||
<InfoIcon />
|
||||
</Box>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
{listQuestions[quizId][totalIndex].content.innerNameCheck && (
|
||||
{question.content.innerNameCheck && (
|
||||
<CustomTextField
|
||||
placeholder={"Внутреннее описание вопроса"}
|
||||
text={listQuestions[quizId][totalIndex].content.innerName}
|
||||
text={question.content.innerName}
|
||||
onChange={({ target }) => debounced(target.value)}
|
||||
/>
|
||||
)}
|
||||
|
@ -1,43 +1,50 @@
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
IconButton,
|
||||
Typography,
|
||||
useMediaQuery,
|
||||
useTheme,
|
||||
Box,
|
||||
Button,
|
||||
IconButton,
|
||||
Typography,
|
||||
useMediaQuery,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
import AddPlus from "../../assets/icons/questionsPage/addPlus";
|
||||
import ArrowLeft from "../../assets/icons/questionsPage/arrowLeft";
|
||||
import { quizStore } from "@root/quizes";
|
||||
import { useParams } from "react-router-dom";
|
||||
import {
|
||||
questionStore,
|
||||
createQuestion,
|
||||
updateQuestionsList,
|
||||
questionStore,
|
||||
createQuestion,
|
||||
updateQuestionsList,
|
||||
} from "@root/questions";
|
||||
import { DraggableList } from "./DraggableList";
|
||||
|
||||
export default function QuestionsPage() {
|
||||
const { listQuizes, updateQuizesList } = quizStore();
|
||||
const quizId = Number(useParams().quizId);
|
||||
const { listQuestions } = questionStore();
|
||||
const handleNext = () => {
|
||||
updateQuizesList(quizId, { step: listQuizes[quizId].step + 1 });
|
||||
};
|
||||
import type { AnyQuizQuestion } from "../../model/questionTypes/shared";
|
||||
import QuizPreview from "@ui_kit/QuizPreview/QuizPreview";
|
||||
import { createPortal } from "react-dom";
|
||||
|
||||
const handleBack = () => {
|
||||
let result = listQuizes[quizId].step - 1;
|
||||
updateQuizesList(quizId, { step: result ? result : 1 });
|
||||
};
|
||||
export default function QuestionsPage() {
|
||||
const { listQuizes, updateQuizesList } = quizStore();
|
||||
const quizId = Number(useParams().quizId);
|
||||
const { listQuestions } = questionStore();
|
||||
const handleNext = () => {
|
||||
updateQuizesList(quizId, { step: listQuizes[quizId].step + 1 });
|
||||
};
|
||||
|
||||
const handleBack = () => {
|
||||
let result = listQuizes[quizId].step - 1;
|
||||
updateQuizesList(quizId, { step: result ? result : 1 });
|
||||
};
|
||||
|
||||
const collapseEverything = () => {
|
||||
listQuestions[quizId].forEach((item, index) => {
|
||||
updateQuestionsList(quizId, index, { ...item, expanded: false });
|
||||
updateQuestionsList<AnyQuizQuestion>(quizId, index, {
|
||||
...item,
|
||||
expanded: false,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const theme = useTheme();
|
||||
const isTablet = useMediaQuery(theme.breakpoints.up(1000));
|
||||
const theme = useTheme();
|
||||
const isTablet = useMediaQuery(theme.breakpoints.up(1000));
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -103,6 +110,7 @@ export default function QuestionsPage() {
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
{createPortal(<QuizPreview />, document.body)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -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<HTMLDivElement>(null);
|
||||
const positiveRef = useRef<HTMLDivElement>(null);
|
||||
const debounceNegativeDescription = useDebouncedCallback((value) => {
|
||||
updateQuestionsList(quizId, totalIndex, {
|
||||
updateQuestionsList<QuizQuestionRating>(quizId, totalIndex, {
|
||||
content: {
|
||||
...listQuestions[quizId][totalIndex].content,
|
||||
...question.content,
|
||||
ratingNegativeDescription: value.substring(0, 15),
|
||||
},
|
||||
});
|
||||
}, 500);
|
||||
const debouncePositiveDescription = useDebouncedCallback((value) => {
|
||||
updateQuestionsList(quizId, totalIndex, {
|
||||
updateQuestionsList<QuizQuestionRating>(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: <StarIconMini width={"50px"} color={theme.palette.grey2.main} /> },
|
||||
{
|
||||
name: "star",
|
||||
icon: <StarIconMini width={"50px"} color={theme.palette.grey2.main} />,
|
||||
},
|
||||
{ name: "trophie", icon: <TropfyIcon color={theme.palette.grey2.main} /> },
|
||||
{ name: "flag", icon: <FlagIcon color={theme.palette.grey2.main} /> },
|
||||
{ name: "heart", icon: <HeartIcon color={theme.palette.grey2.main} /> },
|
||||
@ -96,24 +98,17 @@ export default function RatingOptions({ totalIndex }: Props) {
|
||||
<>
|
||||
<Box
|
||||
sx={{
|
||||
width: isMobile
|
||||
? "auto"
|
||||
: `${
|
||||
listQuestions[quizId][totalIndex].content.steps * 44 >
|
||||
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,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
width: isMobile ? "auto" : `${question.content.steps * 44}px`,
|
||||
maxWidth: "440px",
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
padding: "0 10px",
|
||||
@ -121,14 +116,39 @@ export default function RatingOptions({ totalIndex }: Props) {
|
||||
}}
|
||||
>
|
||||
{Array.from(
|
||||
{ length: listQuestions[quizId][totalIndex].content.steps },
|
||||
{ length: question.content.steps },
|
||||
(_, index) => index
|
||||
).map((itemNumber) => (
|
||||
<Box key={itemNumber} sx={{ transform: "scale(1.5)" }}>
|
||||
<Box
|
||||
key={itemNumber}
|
||||
{...(itemNumber === 0 || itemNumber === question.content.steps - 1
|
||||
? {
|
||||
onClick: () => {
|
||||
updateQuestionsList<QuizQuestionRating>(
|
||||
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
|
||||
}
|
||||
</Box>
|
||||
@ -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%",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
ref={negativeRef}
|
||||
sx={{
|
||||
position: "absolute",
|
||||
opacity: 0,
|
||||
zIndex: "-100",
|
||||
whiteSpace: "nowrap",
|
||||
fontSize: "16px",
|
||||
}}
|
||||
>
|
||||
{negativeText}
|
||||
</Typography>
|
||||
<TextField
|
||||
defaultValue={
|
||||
listQuestions[quizId][totalIndex].content
|
||||
.ratingNegativeDescription
|
||||
}
|
||||
value={negativeText}
|
||||
placeholder="Негативно"
|
||||
onChange={({ target }) => {
|
||||
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,
|
||||
<Box sx={{ minWidth: isMobile ? "140px" : "205px" }}>
|
||||
<Typography
|
||||
ref={negativeRef}
|
||||
sx={{
|
||||
position: "absolute",
|
||||
opacity: 0,
|
||||
zIndex: "-100",
|
||||
whiteSpace: "nowrap",
|
||||
fontSize: "16px",
|
||||
}}
|
||||
>
|
||||
{negativeText}
|
||||
</Typography>
|
||||
<TextField
|
||||
defaultValue={question.content.ratingNegativeDescription}
|
||||
value={negativeText}
|
||||
placeholder="Негативно"
|
||||
onChange={({ target }) => {
|
||||
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",
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<Typography
|
||||
ref={positiveRef}
|
||||
sx={{
|
||||
position: "absolute",
|
||||
opacity: 0,
|
||||
zIndex: "-100",
|
||||
whiteSpace: "nowrap",
|
||||
fontSize: "16px",
|
||||
}}
|
||||
>
|
||||
{positiveText}
|
||||
</Typography>
|
||||
<TextField
|
||||
value={positiveText}
|
||||
placeholder="Позитивно"
|
||||
onChange={({ target }) => {
|
||||
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,
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<Box sx={{ minWidth: isMobile ? "140px" : "205px" }}>
|
||||
<Typography
|
||||
ref={positiveRef}
|
||||
sx={{
|
||||
position: "absolute",
|
||||
opacity: 0,
|
||||
zIndex: "-100",
|
||||
whiteSpace: "nowrap",
|
||||
fontSize: "16px",
|
||||
}}
|
||||
>
|
||||
{positiveText}
|
||||
</Typography>
|
||||
<TextField
|
||||
value={positiveText}
|
||||
placeholder="Позитивно"
|
||||
onChange={({ target }) => {
|
||||
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",
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
<ButtonsOptions switchState={switchState} SSHC={SSHC} totalIndex={totalIndex} />
|
||||
<ButtonsOptions
|
||||
switchState={switchState}
|
||||
SSHC={SSHC}
|
||||
totalIndex={totalIndex}
|
||||
/>
|
||||
<SwitchRating switchState={switchState} totalIndex={totalIndex} />
|
||||
</>
|
||||
);
|
||||
|
@ -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<QuizQuestionNumber>(quizId, totalIndex, {
|
||||
content: { ...question.content, innerName: value },
|
||||
});
|
||||
}, 1000);
|
||||
|
||||
const buttonRatingForm: ButtonRatingFrom[] = [
|
||||
@ -70,43 +72,41 @@ export default function SettingSlider({ totalIndex }: SettingSliderProps) {
|
||||
<Typography sx={{ height: isMobile ? "18px" : "auto", fontWeight: "500", fontSize: "18px", color: " #4D4D4D" }}>
|
||||
Настройки рейтинга
|
||||
</Typography>
|
||||
<Box>
|
||||
<Typography
|
||||
sx={{
|
||||
color: theme.palette.grey2.main,
|
||||
fontSize: "16px",
|
||||
fontWeight: 400,
|
||||
mb: "8px",
|
||||
}}
|
||||
>
|
||||
Форма
|
||||
</Typography>
|
||||
<Box sx={{ display: "flex" }}>
|
||||
{buttonRatingForm.map(({ name, icon }, index) => (
|
||||
<ButtonBase
|
||||
key={index}
|
||||
onClick={() => {
|
||||
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}
|
||||
</ButtonBase>
|
||||
))}
|
||||
</Box>
|
||||
<Typography
|
||||
sx={{
|
||||
color: theme.palette.grey2.main,
|
||||
fontSize: "16px",
|
||||
fontWeight: 400,
|
||||
}}
|
||||
>
|
||||
Форма
|
||||
</Typography>
|
||||
<Box sx={{ display: "flex", marginBottom: "15px" }}>
|
||||
{buttonRatingForm.map(({ name, icon }, index) => (
|
||||
<ButtonBase
|
||||
key={index}
|
||||
onClick={() => {
|
||||
updateQuestionsList<QuizQuestionNumber>(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}
|
||||
</ButtonBase>
|
||||
))}
|
||||
</Box>
|
||||
|
||||
<Typography
|
||||
@ -119,24 +119,22 @@ export default function SettingSlider({ totalIndex }: SettingSliderProps) {
|
||||
Количество
|
||||
</Typography>
|
||||
<Slider
|
||||
value={listQuestions[quizId][totalIndex].content.steps}
|
||||
value={question.content.steps}
|
||||
min={2}
|
||||
max={10}
|
||||
aria-label="Default"
|
||||
valueLabelDisplay="auto"
|
||||
sx={{ color: theme.palette.brightPurple.main, padding: "0" }}
|
||||
onChange={(_, value) => {
|
||||
updateQuestionsList(quizId, totalIndex, {
|
||||
content: {
|
||||
...listQuestions[quizId][totalIndex].content,
|
||||
steps: Number(value) || 1,
|
||||
},
|
||||
updateQuestionsList<QuizQuestionNumber>(quizId, totalIndex, {
|
||||
content: { ...question.content, steps: Number(value) || 1 },
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
boxSizing: "border-box",
|
||||
pt: isMobile ? "30px" : "20px",
|
||||
pb: "20px",
|
||||
pl: isFigmaTablte ? (isMobile ? "20px" : "34px") : "40px",
|
||||
@ -153,9 +151,9 @@ export default function SettingSlider({ totalIndex }: SettingSliderProps) {
|
||||
<CustomCheckbox
|
||||
sx={{ display: "block", mr: isMobile ? "0px" : "16px" }}
|
||||
label={"Необязательный вопрос"}
|
||||
checked={!listQuestions[quizId][totalIndex].required}
|
||||
checked={!question.required}
|
||||
handleChange={(e) => {
|
||||
updateQuestionsList(quizId, totalIndex, {
|
||||
updateQuestionsList<QuizQuestionNumber>(quizId, totalIndex, {
|
||||
required: !e.target.checked,
|
||||
});
|
||||
}}
|
||||
@ -170,16 +168,15 @@ export default function SettingSlider({ totalIndex }: SettingSliderProps) {
|
||||
<CustomCheckbox
|
||||
sx={{ mr: isMobile ? "0px" : "16px" }}
|
||||
label={"Внутреннее название вопроса"}
|
||||
checked={listQuestions[quizId][totalIndex].content.innerNameCheck}
|
||||
handleChange={(e) => {
|
||||
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<QuizQuestionNumber>(quizId, totalIndex, {
|
||||
content: {
|
||||
...question.content,
|
||||
innerNameCheck: target.checked,
|
||||
innerName: target.checked ? question.content.innerName : "",
|
||||
},
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<Tooltip title="Будет отображаться как заголовок вопроса в приходящих заявках." placement="top">
|
||||
@ -188,10 +185,10 @@ export default function SettingSlider({ totalIndex }: SettingSliderProps) {
|
||||
</Box>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
{listQuestions[quizId][totalIndex].content.innerNameCheck && (
|
||||
{question.content.innerNameCheck && (
|
||||
<CustomTextField
|
||||
placeholder={"Развёрнутое описание вопроса"}
|
||||
text={listQuestions[quizId][totalIndex].content.innerName}
|
||||
text={question.content.innerName}
|
||||
onChange={({ target }) => debounced(target.value)}
|
||||
/>
|
||||
)}
|
||||
|
@ -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",
|
||||
}}
|
||||
>
|
||||
<Box sx={{ gap: "14px", display: "flex", flexDirection: "column" }}>
|
||||
<Box
|
||||
sx={{
|
||||
gap: isMobile ? "10px" : "14px",
|
||||
mt: isMobile ? "25px" : "0px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
marginRight: isMobile ? "10px" : "0px",
|
||||
}}
|
||||
>
|
||||
<Typography sx={{ fontWeight: "500", fontSize: "18px", color: "#4D4D4D" }}>
|
||||
Выбор значения из диапазона
|
||||
</Typography>
|
||||
<Box sx={{ width: "100%", display: "flex", alignItems: "center", gap: "20px" }}>
|
||||
<Box sx={{ width: "100%", display: "flex", alignItems: "center", gap: isMobile ? "9px" : "20px" }}>
|
||||
<CustomNumberField
|
||||
sx={{ maxWidth: "310px", width: "100%" }}
|
||||
placeholder={"0"}
|
||||
min={0}
|
||||
max={99}
|
||||
value={
|
||||
listQuestions[quizId][totalIndex].content.range.split("—")[0]
|
||||
}
|
||||
value={question.content.range.split("—")[0]}
|
||||
onChange={({ target }) => {
|
||||
const clonContent = listQuestions[quizId][totalIndex].content;
|
||||
clonContent.range = `${target.value}—${
|
||||
listQuestions[quizId][totalIndex].content.range.split("—")[1]
|
||||
}`;
|
||||
updateQuestionsList(quizId, totalIndex, {
|
||||
content: clonContent,
|
||||
updateQuestionsList<QuizQuestionNumber>(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<QuizQuestionNumber>(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<QuizQuestionNumber>(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<QuizQuestionNumber>(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<QuizQuestionNumber>(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<QuizQuestionNumber>(quizId, totalIndex, {
|
||||
content: { ...question.content, start: max },
|
||||
});
|
||||
}
|
||||
|
||||
if (step > max) {
|
||||
updateQuestionsList(quizId, totalIndex, {
|
||||
content: {
|
||||
...listQuestions[quizId][totalIndex].content,
|
||||
step: max,
|
||||
},
|
||||
updateQuestionsList<QuizQuestionNumber>(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) {
|
||||
}}
|
||||
>
|
||||
<Box sx={{ width: "100%" }}>
|
||||
<Typography sx={{ fontWeight: "500", fontSize: "18px", color: "#4D4D4D", mb: "14px" }}>
|
||||
<Typography sx={{ fontWeight: "500", fontSize: "18px", color: "#4D4D4D", mb: isMobile ? "10px" : "14px" }}>
|
||||
Начальное значение
|
||||
</Typography>
|
||||
<CustomNumberField
|
||||
sx={{ maxWidth: "310px", width: "100%" }}
|
||||
placeholder={"50"}
|
||||
min={Number(
|
||||
listQuestions[quizId][totalIndex].content.range.split("—")[0]
|
||||
)}
|
||||
max={Number(
|
||||
listQuestions[quizId][totalIndex].content.range.split("—")[1]
|
||||
)}
|
||||
value={String(listQuestions[quizId][totalIndex].content.start)}
|
||||
min={Number(question.content.range.split("—")[0])}
|
||||
max={Number(question.content.range.split("—")[1])}
|
||||
value={String(question.content.start)}
|
||||
onChange={({ target }) => {
|
||||
const clonContent = listQuestions[quizId][totalIndex].content;
|
||||
clonContent.start = Number(target.value);
|
||||
updateQuestionsList(quizId, totalIndex, {
|
||||
content: clonContent,
|
||||
updateQuestionsList<QuizQuestionNumber>(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<QuizQuestionNumber>(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<QuizQuestionNumber>(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) {
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
<ButtonsOptions
|
||||
switchState={switchState}
|
||||
SSHC={SSHC}
|
||||
totalIndex={totalIndex}
|
||||
/>
|
||||
<ButtonsOptions switchState={switchState} SSHC={SSHC} totalIndex={totalIndex} />
|
||||
<SwitchSlider switchState={switchState} totalIndex={totalIndex} />
|
||||
</>
|
||||
);
|
||||
|
@ -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<QuizQuestionNumber>(quizId, totalIndex, {
|
||||
content: { ...question.content, innerName: value },
|
||||
});
|
||||
}, 1000);
|
||||
|
||||
return (
|
||||
@ -34,25 +37,29 @@ export default function SettingSlider({ totalIndex }: SettingSliderProps) {
|
||||
<Box
|
||||
sx={{
|
||||
pt: isMobile ? "25px" : "20px",
|
||||
pb: isMobile ? "25px" : "20px",
|
||||
pb: isMobile ? "30px" : "20px",
|
||||
pl: "20px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "14px",
|
||||
width: "100%",
|
||||
width: isMobile ? "auto" : "100%",
|
||||
}}
|
||||
>
|
||||
<Typography sx={{ height: isMobile ? "18px" : "auto", fontWeight: "500", fontSize: "18px", color: " #4D4D4D" }}>
|
||||
Настройки ползунка
|
||||
</Typography>
|
||||
<CustomCheckbox
|
||||
sx={{ mr: isMobile ? "0px" : "16px" }}
|
||||
sx={{
|
||||
mr: isMobile ? "0px" : "16px",
|
||||
height: isMobile ? "100%" : "auto",
|
||||
alignItems: isMobile ? "flex-start" : "center",
|
||||
}}
|
||||
label={"Выбор диапозона (два ползунка)"}
|
||||
checked={listQuestions[quizId][totalIndex].content.chooseRange}
|
||||
handleChange={(e) => {
|
||||
let clonContent = listQuestions[quizId][totalIndex].content;
|
||||
clonContent.chooseRange = e.target.checked;
|
||||
updateQuestionsList(quizId, totalIndex, { content: clonContent });
|
||||
checked={question.content.chooseRange}
|
||||
handleChange={({ target }) => {
|
||||
updateQuestionsList<QuizQuestionNumber>(quizId, totalIndex, {
|
||||
content: { ...question.content, chooseRange: target.checked },
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
@ -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%",
|
||||
}}
|
||||
>
|
||||
<Typography sx={{ height: isMobile ? "18px" : "auto", fontWeight: "500", fontSize: "18px", color: " #4D4D4D" }}>
|
||||
Настройки вопросов
|
||||
</Typography>
|
||||
<CustomCheckbox
|
||||
sx={{ mr: isMobile ? "0px" : "16px" }}
|
||||
sx={{ mr: isMobile ? "0px" : "16px", alignItems: isMobile ? "flex-end" : "center" }}
|
||||
label={"Необязательный вопрос"}
|
||||
checked={!listQuestions[quizId][totalIndex].required}
|
||||
checked={!question.required}
|
||||
handleChange={(e) => {
|
||||
updateQuestionsList(quizId, totalIndex, {
|
||||
updateQuestionsList<QuizQuestionNumber>(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",
|
||||
}}
|
||||
>
|
||||
<CustomCheckbox
|
||||
sx={{ mr: isMobile ? "0px" : "16px", whiteSpace: isFigmaTablte ? "nowrap" : "" }}
|
||||
sx={{
|
||||
mr: isMobile ? "0px" : "16px",
|
||||
height: isMobile ? "100%" : "auto",
|
||||
alignItems: isMobile ? "flex-start" : "center",
|
||||
}}
|
||||
label={"Внутреннее название вопроса"}
|
||||
checked={listQuestions[quizId][totalIndex].content.innerNameCheck}
|
||||
handleChange={(e) => {
|
||||
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<QuizQuestionNumber>(quizId, totalIndex, {
|
||||
content: {
|
||||
...question.content,
|
||||
innerNameCheck: target.checked,
|
||||
innerName: target.checked ? question.content.innerName : "",
|
||||
},
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<Tooltip title="Будет отображаться как заголовок вопроса в приходящих заявках." placement="top">
|
||||
@ -109,10 +119,10 @@ export default function SettingSlider({ totalIndex }: SettingSliderProps) {
|
||||
</Box>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
{listQuestions[quizId][totalIndex].content.innerNameCheck && (
|
||||
{question.content.innerNameCheck && (
|
||||
<CustomTextField
|
||||
placeholder={"Развёрнутое описание вопроса"}
|
||||
text={listQuestions[quizId][totalIndex].content.innerName}
|
||||
text={question.content.innerName}
|
||||
onChange={({ target }) => debounced(target.value)}
|
||||
/>
|
||||
)}
|
||||
|
@ -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 (
|
||||
<Box
|
||||
@ -101,7 +111,14 @@ export default function TypeQuestions({ totalIndex }: Props) {
|
||||
<QuestionsMiniButton
|
||||
key={title}
|
||||
onClick={() => {
|
||||
updateQuestionsList(quizId, totalIndex, { type: value });
|
||||
const question = { ...listQuestions[quizId][totalIndex] };
|
||||
|
||||
removeQuestionForce(quizId, question.id);
|
||||
createQuestion(quizId, value, totalIndex);
|
||||
updateQuestionsList<QuizQuestionBase>(quizId, totalIndex, {
|
||||
expanded: question.expanded,
|
||||
type: value,
|
||||
});
|
||||
}}
|
||||
icon={icon}
|
||||
text={title}
|
||||
|
@ -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<AnyQuizQuestion>(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<AnyQuizQuestion>(quizId, totalIndex, {
|
||||
content: { ...question.content, type: DESIGN_TYPES[0].value },
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
@ -70,7 +85,7 @@ export default function UploadFile({ totalIndex }: Props) {
|
||||
flexDirection: "column",
|
||||
}}
|
||||
>
|
||||
<Box sx={{ gap: "10px", display: "flex" }}>
|
||||
<Box sx={{ gap: "10px", display: "flex", mt: isMobile ? "15px" : "0px" }}>
|
||||
<FormControl
|
||||
fullWidth
|
||||
size="small"
|
||||
@ -84,7 +99,7 @@ export default function UploadFile({ totalIndex }: Props) {
|
||||
<Select
|
||||
id="category-select"
|
||||
variant="outlined"
|
||||
value={listQuestions[quizId][totalIndex].content.type}
|
||||
value={question.content.type}
|
||||
displayEmpty
|
||||
onChange={handleChange}
|
||||
sx={{
|
||||
@ -151,9 +166,9 @@ export default function UploadFile({ totalIndex }: Props) {
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
alignItems: "flex-start",
|
||||
marginBottom: "20px",
|
||||
marginTop: isMobile ? "18px" : "15px",
|
||||
marginTop: "15px",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
@ -166,10 +181,21 @@ export default function UploadFile({ totalIndex }: Props) {
|
||||
>
|
||||
Пользователь может загрузить любой собственный файл
|
||||
</Typography>
|
||||
<InfoIcon />
|
||||
<Tooltip
|
||||
title="Можно загрузить файл в желаемом формате."
|
||||
placement="top"
|
||||
>
|
||||
<Box>
|
||||
<InfoIcon />
|
||||
</Box>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
</Box>
|
||||
<ButtonsOptions switchState={switchState} SSHC={SSHC} totalIndex={totalIndex} />
|
||||
<ButtonsOptions
|
||||
switchState={switchState}
|
||||
SSHC={SSHC}
|
||||
totalIndex={totalIndex}
|
||||
/>
|
||||
<SwitchUpload switchState={switchState} totalIndex={totalIndex} />
|
||||
</>
|
||||
);
|
||||
|
@ -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<QuizQuestionFile>(quizId, totalIndex, {
|
||||
content: { ...question.content, innerName: value },
|
||||
});
|
||||
}, 1000);
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
pt: isMobile ? "25px" : "20px",
|
||||
pb: isMobile ? "25px" : "20px",
|
||||
boxSizing: "border-box",
|
||||
pt: isMobile ? "30px" : "20px",
|
||||
pb: "20px",
|
||||
pl: "20px",
|
||||
pr: isMobile ? "20px" : "0px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "14px",
|
||||
width: "100%",
|
||||
width: isMobile ? "auto" : "100%",
|
||||
}}
|
||||
>
|
||||
<Typography sx={{ height: isMobile ? "18px" : "auto", fontWeight: "500", fontSize: "18px", color: " #4D4D4D" }}>
|
||||
Настройки вопроса
|
||||
</Typography>
|
||||
<Typography>Настройки вопроса</Typography>
|
||||
<CustomCheckbox
|
||||
sx={{ display: "block", mr: isMobile ? "0px" : "16px" }}
|
||||
sx={{
|
||||
display: isMobile ? "flex" : "block",
|
||||
mr: isMobile ? "0px" : "16px",
|
||||
}}
|
||||
label={"Автозаполнение адреса"}
|
||||
checked={listQuestions[quizId][totalIndex].content.autofill}
|
||||
checked={question.content.autofill}
|
||||
handleChange={({ target }) => {
|
||||
const clonContent = listQuestions[quizId][totalIndex].content;
|
||||
clonContent.autofill = target.checked;
|
||||
updateQuestionsList(quizId, totalIndex, { content: clonContent });
|
||||
updateQuestionsList<QuizQuestionFile>(quizId, totalIndex, {
|
||||
content: { ...question.content, autofill: target.checked },
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<CustomCheckbox
|
||||
sx={{ display: "block", mr: isMobile ? "0px" : "16px" }}
|
||||
sx={{
|
||||
display: isMobile ? "flex" : "block",
|
||||
mr: isMobile ? "0px" : "16px",
|
||||
}}
|
||||
label={"Необязательный вопрос"}
|
||||
checked={!listQuestions[quizId][totalIndex].required}
|
||||
checked={!question.required}
|
||||
handleChange={(e) => {
|
||||
updateQuestionsList(quizId, totalIndex, {
|
||||
updateQuestionsList<QuizQuestionFile>(quizId, totalIndex, {
|
||||
required: !e.target.checked,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<Box sx={{ width: isMobile ? "90%" : "auto", display: "flex", alignItems: "center" }}>
|
||||
<Box
|
||||
sx={{
|
||||
width: isMobile ? "90%" : "auto",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<CustomCheckbox
|
||||
sx={{ mr: isMobile ? "0px" : "16px" }}
|
||||
sx={{
|
||||
mr: isMobile ? "0px" : "16px",
|
||||
height: isMobile ? "100%" : "26px",
|
||||
alignItems: "flex-start",
|
||||
}}
|
||||
label={"Внутреннее название вопроса"}
|
||||
checked={listQuestions[quizId][totalIndex].content.innerNameCheck}
|
||||
handleChange={(e) => {
|
||||
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<QuizQuestionFile>(quizId, totalIndex, {
|
||||
content: {
|
||||
...question.content,
|
||||
innerNameCheck: target.checked,
|
||||
innerName: target.checked ? question.content.innerName : "",
|
||||
},
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<Tooltip title="Будет отображаться как заголовок вопроса в приходящих заявках." placement="top">
|
||||
<Tooltip
|
||||
title="Будет отображаться как заголовок вопроса в приходящих заявках."
|
||||
placement="top"
|
||||
>
|
||||
<Box>
|
||||
<InfoIcon />
|
||||
</Box>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
{listQuestions[quizId][totalIndex].content.innerNameCheck && (
|
||||
{question.content.innerNameCheck && (
|
||||
<CustomTextField
|
||||
placeholder={"Развёрнутое описание вопроса"}
|
||||
text={listQuestions[quizId][totalIndex].content.innerName}
|
||||
text={question.content.innerName}
|
||||
onChange={({ target }) => debounced(target.value)}
|
||||
sx={{ paddingRight: "20px" }}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
|
@ -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<QuizQuestionImages>(quizId, totalIndex, {
|
||||
content: {
|
||||
...question.content,
|
||||
back: URL.createObjectURL(file),
|
||||
},
|
||||
});
|
||||
|
||||
handleClose();
|
||||
setOpened(true);
|
||||
@ -70,7 +75,7 @@ export default function UploadImage({ totalIndex }: UploadImageProps) {
|
||||
<CropModal
|
||||
opened={opened}
|
||||
onClose={() => setOpened(false)}
|
||||
picture={listQuestions[quizId][totalIndex].content.back}
|
||||
picture={question.content.back}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
|
@ -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<QuizQuestionVariant>(quizId, totalIndex, {
|
||||
content: { ...question.content, variants: answerNew },
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box sx={{ padding: "0 20px 0px 20px" }}>
|
||||
{variants.length === 0 ? (
|
||||
<Box sx={{ padding: "0 20px 20px 20px" }}>
|
||||
{question.content.variants.length === 0 ? (
|
||||
<Typography
|
||||
sx={{
|
||||
padding: "0 0 33px 80px",
|
||||
@ -51,7 +50,10 @@ export default function AnswerOptions({ totalIndex }: Props) {
|
||||
Добавьте ответ
|
||||
</Typography>
|
||||
) : (
|
||||
<AnswerDraggableList variants={variants} totalIndex={totalIndex} />
|
||||
<AnswerDraggableList
|
||||
variants={question.content.variants}
|
||||
totalIndex={totalIndex}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Box
|
||||
@ -92,11 +94,7 @@ export default function AnswerOptions({ totalIndex }: Props) {
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
<ButtonsOptionsAndPict
|
||||
switchState={switchState}
|
||||
SSHC={SSHC}
|
||||
totalIndex={totalIndex}
|
||||
/>
|
||||
<ButtonsOptionsAndPict switchState={switchState} SSHC={SSHC} totalIndex={totalIndex} />
|
||||
<SwitchAnswerOptions switchState={switchState} totalIndex={totalIndex} />
|
||||
</>
|
||||
);
|
||||
|
@ -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<QuizQuestionVariant>(quizId, totalIndex, {
|
||||
content: { ...question.content, innerName: value },
|
||||
});
|
||||
}, 1000);
|
||||
|
||||
return (
|
||||
@ -43,102 +53,124 @@ export default function ResponseSettings({ totalIndex }: Props) {
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<Typography sx={{ height: isMobile ? "18px" : "auto", fontWeight: "500", fontSize: "18px", color: " #4D4D4D" }}>
|
||||
<Typography
|
||||
sx={{
|
||||
height: isMobile ? "18px" : "auto",
|
||||
fontWeight: "500",
|
||||
fontSize: "18px",
|
||||
color: " #4D4D4D",
|
||||
}}
|
||||
>
|
||||
Настройки ответов
|
||||
</Typography>
|
||||
<CustomCheckbox
|
||||
sx={{ mr: isMobile ? "0px" : "16px" }}
|
||||
label={"Длинный текстовый ответ"}
|
||||
checked={listQuestions[quizId][totalIndex].content.largeCheck}
|
||||
handleChange={(e) => {
|
||||
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<QuizQuestionVariant>(quizId, totalIndex, {
|
||||
content: {
|
||||
...question.content,
|
||||
largeCheck: target.checked,
|
||||
},
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<CustomCheckbox
|
||||
sx={{ mr: isMobile ? "0px" : "16px" }}
|
||||
label={"Можно несколько"}
|
||||
checked={listQuestions[quizId][totalIndex].content.multi}
|
||||
handleChange={(e) => {
|
||||
let clonContent = listQuestions[quizId][totalIndex].content;
|
||||
clonContent.multi = e.target.checked;
|
||||
updateQuestionsList(quizId, totalIndex, { content: clonContent });
|
||||
checked={question.content.multi}
|
||||
handleChange={({ target }) => {
|
||||
updateQuestionsList<QuizQuestionVariant>(quizId, totalIndex, {
|
||||
content: { ...question.content, multi: target.checked },
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<CustomCheckbox
|
||||
sx={{ mr: isMobile ? "0px" : "16px" }}
|
||||
label={'Вариант "свой ответ"'}
|
||||
checked={listQuestions[quizId][totalIndex].content.own}
|
||||
handleChange={(e) => {
|
||||
let clonContent = listQuestions[quizId][totalIndex].content;
|
||||
clonContent.own = e.target.checked;
|
||||
updateQuestionsList(quizId, totalIndex, { content: clonContent });
|
||||
checked={question.content.own}
|
||||
handleChange={({ target }) => {
|
||||
updateQuestionsList<QuizQuestionVariant>(quizId, totalIndex, {
|
||||
content: { ...question.content, own: target.checked },
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
boxSizing: "border-box",
|
||||
pt: isMobile ? "0px" : "20px",
|
||||
pb: "20px",
|
||||
pl: isFigmaTablte ? (isMobile ? "20px" : "34px") : "28px",
|
||||
pl: isFigmaTablte ? (isTablet ? "20px" : "34px") : "28px",
|
||||
pr: isFigmaTablte ? "19px" : "20px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: isMobile ? "13px" : "14px",
|
||||
gap: "14px",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<Typography sx={{ height: isMobile ? "18px" : "auto", fontWeight: "500", fontSize: "18px", color: " #4D4D4D" }}>
|
||||
<Typography
|
||||
sx={{
|
||||
height: isMobile ? "18px" : "auto",
|
||||
fontWeight: "500",
|
||||
fontSize: "18px",
|
||||
color: " #4D4D4D",
|
||||
}}
|
||||
>
|
||||
Настройки вопросов
|
||||
</Typography>
|
||||
<CustomCheckbox
|
||||
sx={{ mr: isMobile ? "0px" : "16px" }}
|
||||
label={"Необязательный вопрос"}
|
||||
checked={!listQuestions[quizId][totalIndex].required}
|
||||
handleChange={(e) => {
|
||||
updateQuestionsList(quizId, totalIndex, {
|
||||
required: !e.target.checked,
|
||||
checked={!question.required}
|
||||
handleChange={({ target }) => {
|
||||
updateQuestionsList<QuizQuestionVariant>(quizId, totalIndex, {
|
||||
required: !target.checked,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<Box sx={{ width: isMobile ? "90%" : "auto", display: "flex" }}>
|
||||
<Box
|
||||
sx={{
|
||||
width: isMobile ? "90%" : "auto",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<CustomCheckbox
|
||||
sx={{ mr: isMobile ? "0px" : "9px", height: isMobile ? "42px" : "26px", alignItems: "start" }}
|
||||
sx={{
|
||||
mr: isMobile ? "0px" : "9px",
|
||||
height: isMobile ? "42px" : "26px",
|
||||
alignItems: "start",
|
||||
}}
|
||||
label={"Внутреннее название вопроса"}
|
||||
checked={listQuestions[quizId][totalIndex].content.innerNameCheck}
|
||||
handleChange={(e) => {
|
||||
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<QuizQuestionVariant>(quizId, totalIndex, {
|
||||
content: {
|
||||
...question.content,
|
||||
innerNameCheck: target.checked,
|
||||
innerName: target.checked ? question.content.innerName : "",
|
||||
},
|
||||
});
|
||||
}}
|
||||
/>
|
||||
{isMobile ||
|
||||
(!isFigmaTablte && (
|
||||
<Tooltip title="Будет отображаться как заголовок вопроса в приходящих заявках." placement="top">
|
||||
<Box>
|
||||
<InfoIcon />
|
||||
</Box>
|
||||
</Tooltip>
|
||||
))}
|
||||
{isMobile && (
|
||||
<Tooltip
|
||||
title="Будет отображаться как заголовок вопроса в приходящих заявках."
|
||||
placement="top"
|
||||
>
|
||||
<Box>
|
||||
<InfoIcon />
|
||||
</Box>
|
||||
</Tooltip>
|
||||
)}
|
||||
</Box>
|
||||
{listQuestions[quizId][totalIndex].content.innerNameCheck && (
|
||||
{question.content.innerNameCheck && (
|
||||
<CustomTextField
|
||||
sx={{ mr: isMobile ? "0px" : "16px" }}
|
||||
placeholder={"Развёрнутое описание вопроса"}
|
||||
text={listQuestions[quizId][totalIndex].content.innerName}
|
||||
text={question.content.innerName}
|
||||
onChange={({ target }) => debounced(target.value)}
|
||||
/>
|
||||
)}
|
||||
|
@ -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<string>("");
|
||||
const [titleInputWidth, setTitleInputWidth] = useState<number>(0);
|
||||
const quizId = Number(useParams().quizId);
|
||||
const { openedModalSettings, listQuestions } = questionStore();
|
||||
const titleRef = useRef<HTMLDivElement>(null);
|
||||
const [title, setTitle] = useState<string>(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",
|
||||
}}
|
||||
>
|
||||
<Box sx={{ color: "#9A9AAF" }}>
|
||||
@ -113,7 +127,14 @@ export default function BranchingQuestions({ totalIndex }: BranchingQuestionsPro
|
||||
</Box>
|
||||
<Typography component="span">)</Typography>
|
||||
</Box>
|
||||
<InfoIcon width={24} height={24} />
|
||||
<Tooltip
|
||||
title="Настройте условия, при которых данный вопрос будет отображаться в квизе."
|
||||
placement="top"
|
||||
>
|
||||
<Box>
|
||||
<InfoIcon />
|
||||
</Box>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
@ -132,19 +153,25 @@ export default function BranchingQuestions({ totalIndex }: BranchingQuestionsPro
|
||||
>
|
||||
<Select
|
||||
items={ACTIONS}
|
||||
activeItemIndex={listQuestions[quizId][totalIndex].content.rule.show ? 0 : 1}
|
||||
activeItemIndex={question.content.rule.show ? 0 : 1}
|
||||
sx={{ maxWidth: "140px" }}
|
||||
onChange={(action) => {
|
||||
const clonContent = listQuestions[quizId][totalIndex].content;
|
||||
clonContent.rule.show = action === ACTIONS[0];
|
||||
updateQuestionsList(quizId, totalIndex, {
|
||||
content: clonContent,
|
||||
updateQuestionsList<QuizQuestionBase>(quizId, totalIndex, {
|
||||
content: {
|
||||
...question.content,
|
||||
rule: {
|
||||
...question.content.rule,
|
||||
show: action === ACTIONS[0],
|
||||
},
|
||||
},
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<Typography sx={{ color: theme.palette.grey2.main }}>если в ответе на вопрос</Typography>
|
||||
<Typography sx={{ color: theme.palette.grey2.main }}>
|
||||
если в ответе на вопрос
|
||||
</Typography>
|
||||
</Box>
|
||||
{listQuestions[quizId][totalIndex].content.rule.reqs.map((request, index) => (
|
||||
{question.content.rule.reqs.map((request, index) => (
|
||||
<Box
|
||||
key={index}
|
||||
sx={{
|
||||
@ -162,19 +189,24 @@ export default function BranchingQuestions({ totalIndex }: BranchingQuestionsPro
|
||||
pb: "5px",
|
||||
}}
|
||||
>
|
||||
<Typography sx={{ color: "#4D4D4D", fontWeight: "500" }}>Условие 1</Typography>
|
||||
<Typography sx={{ color: "#4D4D4D", fontWeight: "500" }}>
|
||||
Условие 1
|
||||
</Typography>
|
||||
<IconButton
|
||||
sx={{ borderRadius: "6px", padding: "2px" }}
|
||||
onClick={() => {
|
||||
const clonContent = listQuestions[quizId][totalIndex].content;
|
||||
|
||||
clonContent.rule.reqs.splice(index, 1);
|
||||
updateQuestionsList(quizId, totalIndex, {
|
||||
content: clonContent,
|
||||
});
|
||||
const clonedContent = { ...question.content };
|
||||
clonedContent.rule.reqs.splice(index, 1);
|
||||
updateQuestionsList<QuizQuestionBase>(
|
||||
quizId,
|
||||
totalIndex,
|
||||
{
|
||||
content: clonedContent,
|
||||
}
|
||||
);
|
||||
}}
|
||||
>
|
||||
<DeleteIcon style={{ color: "#4D4D4D" }} />
|
||||
<DeleteIcon color={"#4D4D4D"} />
|
||||
</IconButton>
|
||||
</Box>
|
||||
<Select
|
||||
@ -182,15 +214,19 @@ export default function BranchingQuestions({ totalIndex }: BranchingQuestionsPro
|
||||
activeItemIndex={request.id ? Number(request.id) : -1}
|
||||
items={STIPULATIONS}
|
||||
onChange={(stipulation) => {
|
||||
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<QuizQuestionBase>(quizId, totalIndex, {
|
||||
content: clonedContent,
|
||||
});
|
||||
}}
|
||||
sx={{ marginBottom: "15px" }}
|
||||
@ -204,7 +240,9 @@ export default function BranchingQuestions({ totalIndex }: BranchingQuestionsPro
|
||||
pb: "10px",
|
||||
}}
|
||||
>
|
||||
<Typography sx={{ color: "#4D4D4D", fontWeight: "500" }}>Дан ответ</Typography>
|
||||
<Typography sx={{ color: "#4D4D4D", fontWeight: "500" }}>
|
||||
Дан ответ
|
||||
</Typography>
|
||||
<Typography sx={{ color: "#7E2AEA", pl: "10px" }}>
|
||||
(Укажите один или несколько вариантов)
|
||||
</Typography>
|
||||
@ -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<QuizQuestionBase>(
|
||||
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) => (
|
||||
<Chip
|
||||
key={varIndex}
|
||||
label={ANSWERS[item]}
|
||||
variant="outlined"
|
||||
onDelete={() => {
|
||||
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) => (
|
||||
<Chip
|
||||
key={varIndex}
|
||||
label={ANSWERS[item]}
|
||||
variant="outlined"
|
||||
onDelete={() => {
|
||||
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<QuizQuestionBase>(
|
||||
quizId,
|
||||
totalIndex,
|
||||
{
|
||||
content: clonedContent,
|
||||
}
|
||||
);
|
||||
}}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
@ -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<QuizQuestionBase>(quizId, totalIndex, {
|
||||
content: clonedContent,
|
||||
});
|
||||
}}
|
||||
>
|
||||
@ -288,12 +347,16 @@ export default function BranchingQuestions({ totalIndex }: BranchingQuestionsPro
|
||||
<FormControl>
|
||||
<RadioGroup
|
||||
aria-labelledby="demo-controlled-radio-buttons-group"
|
||||
value={listQuestions[quizId][totalIndex].content.rule.or ? 1 : 0}
|
||||
value={question.content.rule.or ? 1 : 0}
|
||||
onChange={(_, value) => {
|
||||
const clonContent = listQuestions[quizId][totalIndex].content;
|
||||
clonContent.rule.or = Boolean(Number(value));
|
||||
updateQuestionsList(quizId, totalIndex, {
|
||||
content: clonContent,
|
||||
updateQuestionsList<QuizQuestionBase>(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={<Radio checkedIcon={<RadioCheck />} icon={<RadioIcon />} />}
|
||||
control={
|
||||
<Radio
|
||||
checkedIcon={<RadioCheck />}
|
||||
icon={<RadioIcon />}
|
||||
/>
|
||||
}
|
||||
label={condition}
|
||||
/>
|
||||
))}
|
||||
@ -310,10 +378,18 @@ export default function BranchingQuestions({ totalIndex }: BranchingQuestionsPro
|
||||
</FormControl>
|
||||
</Box>
|
||||
<Box sx={{ display: "flex", justifyContent: "end", gap: "10px" }}>
|
||||
<Button variant="outlined" onClick={handleClose} sx={{ width: "100%", maxWidth: "130px" }}>
|
||||
<Button
|
||||
variant="outlined"
|
||||
onClick={handleClose}
|
||||
sx={{ width: "100%", maxWidth: "130px" }}
|
||||
>
|
||||
Отмена
|
||||
</Button>
|
||||
<Button variant="contained" sx={{ width: "100%", maxWidth: "130px" }} onClick={handleClose}>
|
||||
<Button
|
||||
variant="contained"
|
||||
sx={{ width: "100%", maxWidth: "130px" }}
|
||||
onClick={handleClose}
|
||||
>
|
||||
Готово
|
||||
</Button>
|
||||
</Box>
|
||||
|
@ -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<BackgroundType>("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<QuizQuestionBase>(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<QuizQuestionBase>(quizId, totalIndex, {
|
||||
content: {
|
||||
...question.content,
|
||||
hint: { video: url, text: question.content.hint.text },
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
@ -67,7 +78,7 @@ export default function HelpQuestions({ totalIndex }: HelpQuestionsProps) {
|
||||
<>
|
||||
<CustomTextField
|
||||
placeholder={"Текст консультанта"}
|
||||
text={listQuestions[quizId][totalIndex].content.hint.text}
|
||||
text={question.content.hint.text}
|
||||
onChange={({ target }) => 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 ? (
|
||||
<video
|
||||
src={listQuestions[quizId][totalIndex].content.hint.video}
|
||||
width="400"
|
||||
controls
|
||||
/>
|
||||
{question.content.hint.video ? (
|
||||
<video src={question.content.hint.video} width="400" controls />
|
||||
) : (
|
||||
<>
|
||||
<UploadBox
|
||||
@ -101,7 +108,7 @@ export default function HelpQuestions({ totalIndex }: HelpQuestionsProps) {
|
||||
<UploadVideoModal
|
||||
open={open}
|
||||
onClose={() => setOpen(false)}
|
||||
video={listQuestions[quizId][totalIndex].content.hint.video}
|
||||
video={question.content.hint.video}
|
||||
onUpload={videoHC}
|
||||
/>
|
||||
</Box>
|
||||
|
@ -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 (
|
||||
<Box component="section">
|
||||
@ -22,16 +22,16 @@ export const Result = () => {
|
||||
image={image}
|
||||
/>
|
||||
<Box sx={{ mt: "30px", alignItems: "center" }}>
|
||||
<Button
|
||||
variant="contained"
|
||||
sx={{
|
||||
mr: "23px",
|
||||
minWidth: "258px",
|
||||
}}
|
||||
onClick={handleNext}
|
||||
>
|
||||
Создать результаты
|
||||
</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
sx={{
|
||||
mr: "23px",
|
||||
minWidth: "258px",
|
||||
}}
|
||||
onClick={handleNext}
|
||||
>
|
||||
Создать результаты
|
||||
</Button>
|
||||
|
||||
<Info />
|
||||
</Box>
|
||||
|
@ -35,7 +35,7 @@ export default function FirstQuiz() {
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={() => {
|
||||
navigate(`/quize-setting/${createBlank()}`);
|
||||
navigate(`/setting/${createBlank()}`);
|
||||
}}
|
||||
>
|
||||
Создать +
|
||||
|
@ -40,7 +40,7 @@ export default function MyQuizzesFull({outerContainerSx: sx, children}: Props) {
|
||||
variant="contained"
|
||||
sx={{padding: "10px 47px"}}
|
||||
onClick={() => {
|
||||
navigate(`/quize-setting/${createBlank()}`);
|
||||
navigate(`/setting/${createBlank()}`);
|
||||
}}
|
||||
>Создать +</Button>
|
||||
</Box>
|
||||
@ -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}`)
|
||||
}
|
||||
/>
|
||||
)
|
||||
|
@ -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<boolean>(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 ? <PenaLogoIcon style={{ fontSize: "39px", color: "white" }} /> : <PenaLogo width={124} />}
|
||||
<Link to="/" style={{ display: "flex" }}>
|
||||
{isMobile ? (
|
||||
<PenaLogoIcon style={{ fontSize: "39px", color: "white" }} />
|
||||
) : (
|
||||
<PenaLogo width={124} />
|
||||
)}
|
||||
</Link>
|
||||
<Box
|
||||
sx={{
|
||||
display: isMobile ? "none" : "flex",
|
||||
@ -104,7 +125,12 @@ export default function StartPage() {
|
||||
/>
|
||||
) : (
|
||||
<CustomAvatar
|
||||
sx={{ ml: "11px", backgroundColor: theme.palette.orange.main, height: "36px", width: "36px" }}
|
||||
sx={{
|
||||
ml: "11px",
|
||||
backgroundColor: theme.palette.orange.main,
|
||||
height: "36px",
|
||||
width: "36px",
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
@ -157,7 +183,12 @@ export default function StartPage() {
|
||||
Опубликовать
|
||||
</Button>
|
||||
<CustomAvatar
|
||||
sx={{ ml: "11px", backgroundColor: theme.palette.orange.main, height: "36px", width: "36px" }}
|
||||
sx={{
|
||||
ml: "11px",
|
||||
backgroundColor: theme.palette.orange.main,
|
||||
height: "36px",
|
||||
width: "36px",
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</>
|
||||
@ -180,8 +211,16 @@ export default function StartPage() {
|
||||
boxSizing: "border-box",
|
||||
}}
|
||||
>
|
||||
<Stepper activeStep={activeStep} desc={"Настройка стартовой страницы"} />
|
||||
<SwitchStepPages activeStep={activeStep} handleNext={handleNext} />
|
||||
<Stepper
|
||||
activeStep={activeStep}
|
||||
desc={DESCRIPTIONS[activeStep - 1]}
|
||||
/>
|
||||
<SwitchStepPages
|
||||
activeStep={activeStep}
|
||||
quizType={listQuizes[params].config.type}
|
||||
startpage={listQuizes[params].startpage}
|
||||
createResult={listQuizes[params].createResult}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
</>
|
||||
|
@ -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<AlignType>("left");
|
||||
|
||||
const handleNext = () => {
|
||||
updateQuizesList(params, { step: listQuizes[params].step + 1 });
|
||||
};
|
||||
|
||||
const videoHC = (videoInp: HTMLInputElement) => {
|
||||
const file = videoInp.files?.[0];
|
||||
|
||||
|
@ -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 (
|
||||
<>
|
||||
<Box
|
||||
@ -24,32 +20,45 @@ export default function StepOne({ handleNext }: HandleNext) {
|
||||
mt: "60px",
|
||||
}}
|
||||
>
|
||||
<Button variant="text" onClick={() => {
|
||||
let SPageClone = listQuizes[params].config
|
||||
SPageClone.type = "quize"
|
||||
updateQuizesList(params, {config: SPageClone })
|
||||
handleNext()
|
||||
}
|
||||
}>
|
||||
<Button
|
||||
variant="text"
|
||||
onClick={() => {
|
||||
let SPageClone = listQuizes[params].config;
|
||||
SPageClone.type = "quize";
|
||||
updateQuizesList(params, { config: SPageClone });
|
||||
}}
|
||||
>
|
||||
<CreationCard
|
||||
header="Создание квиз-опроса"
|
||||
text="У стартовой страницы одна ключевая задача - заинтересовать посетителя пройти квиз. С ней сложно ошибиться, сформулируйте суть предложения и подберите живую фотографию, остальное мы сделаем за вас"
|
||||
image={quizCreationImage1}
|
||||
border={listQuizes[params].config.type === "quize" ? "1px solid #7E2AEA" : "none"}
|
||||
border={
|
||||
listQuizes[params].config.type === "quize"
|
||||
? "1px solid #7E2AEA"
|
||||
: "none"
|
||||
}
|
||||
/>
|
||||
</Button>
|
||||
<Button variant="text" onClick={() => {
|
||||
let SPageClone = listQuizes[params].config
|
||||
SPageClone.type = "form"
|
||||
updateQuizesList(params, {config: SPageClone })
|
||||
handleNext()
|
||||
}
|
||||
}>
|
||||
<Button
|
||||
variant="text"
|
||||
onClick={() => {
|
||||
let SPageClone = listQuizes[params].config;
|
||||
SPageClone.type = "form";
|
||||
updateQuizesList(params, {
|
||||
config: SPageClone,
|
||||
step: listQuizes[params].step + 1,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<CreationCard
|
||||
header="Создание анкеты"
|
||||
text="У стартовой страницы одна ключевая задача - заинтересовать посетителя пройти квиз. С ней сложно ошибиться, сформулируйте суть предложения и подберите живую фотографию, остальное мы сделаем за вас"
|
||||
image={quizCreationImage2}
|
||||
border={listQuizes[params].config.type === "form" ? "1px solid #7E2AEA" : "none"}
|
||||
border={
|
||||
listQuizes[params].config.type === "form"
|
||||
? "1px solid #7E2AEA"
|
||||
: "none"
|
||||
}
|
||||
/>
|
||||
</Button>
|
||||
</Box>
|
||||
|
@ -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) {
|
||||
<Button variant='text'
|
||||
onClick={() => {
|
||||
updateQuizesList(params, {startpage: "standard"})
|
||||
handleNext()
|
||||
}}
|
||||
>
|
||||
<CardWithImage image={cardImage1} text="Standard" border={listQuizes[params].startpage === "standard" ? "1px solid #7E2AEA" : "none"} />
|
||||
@ -35,7 +30,6 @@ export default function Steptwo ({handleNext}:HandleNext) {
|
||||
<Button variant='text'
|
||||
onClick={() => {
|
||||
updateQuizesList(params, {startpage: "expanded"})
|
||||
handleNext()
|
||||
}}
|
||||
>
|
||||
<CardWithImage image={cardImage2} text="Expanded" border={listQuizes[params].startpage === "expanded" ? "1px solid #7E2AEA" : "none"}/>
|
||||
@ -43,7 +37,6 @@ export default function Steptwo ({handleNext}:HandleNext) {
|
||||
<Button variant='text'
|
||||
onClick={() => {
|
||||
updateQuizesList(params, {startpage: "centered"})
|
||||
handleNext()
|
||||
}}
|
||||
>
|
||||
<CardWithImage image={cardImage3} text="Centered" border={listQuizes[params].startpage === "centered" ? "1px solid #7E2AEA" : "none"}/>
|
||||
|
@ -1,154 +1,39 @@
|
||||
import { create } from "zustand";
|
||||
import { persist } from "zustand/middleware";
|
||||
|
||||
export type Variants = {
|
||||
answer: string;
|
||||
hints: string;
|
||||
emoji: string;
|
||||
image: string;
|
||||
};
|
||||
import { QuizQuestionEmoji } from "../model/questionTypes/emoji";
|
||||
import { QuizQuestionImages } from "../model/questionTypes/images";
|
||||
import { QuizQuestionVariant } from "../model/questionTypes/variant";
|
||||
import { QuizQuestionVarImg } from "../model/questionTypes/varimg";
|
||||
|
||||
type Hint = {
|
||||
text: string;
|
||||
video: string;
|
||||
};
|
||||
import type {
|
||||
AnyQuizQuestion,
|
||||
QuizQuestionType,
|
||||
QuestionVariant,
|
||||
} from "../model/questionTypes/shared";
|
||||
|
||||
type Rule = {
|
||||
or: boolean;
|
||||
show: boolean;
|
||||
reqs: [
|
||||
{
|
||||
id: string;
|
||||
vars: number[];
|
||||
}
|
||||
];
|
||||
};
|
||||
import { QUIZ_QUESTION_BASE } from "../constants/base";
|
||||
import { QUIZ_QUESTION_DATE } from "../constants/date";
|
||||
import { QUIZ_QUESTION_EMOJI } from "../constants/emoji";
|
||||
import { QUIZ_QUESTION_FILE } from "../constants/file";
|
||||
import { QUIZ_QUESTION_IMAGES } from "../constants/images";
|
||||
import { QUIZ_QUESTION_NUMBER } from "../constants/number";
|
||||
import { QUIZ_QUESTION_PAGE } from "../constants/page";
|
||||
import { QUIZ_QUESTION_RATING } from "../constants/rating";
|
||||
import { QUIZ_QUESTION_SELECT } from "../constants/select";
|
||||
import { QUIZ_QUESTION_TEXT } from "../constants/text";
|
||||
import { QUIZ_QUESTION_VARIANT } from "../constants/variant";
|
||||
import { QUIZ_QUESTION_VARIMG } from "../constants/varimg";
|
||||
import { setAutoFreeze } from "immer";
|
||||
|
||||
export interface Question {
|
||||
id: number;
|
||||
title: string;
|
||||
description: string;
|
||||
type: string;
|
||||
required: boolean;
|
||||
deleted: boolean;
|
||||
deleteTimeoutId: number;
|
||||
page: number;
|
||||
content: {
|
||||
variants: Variants[];
|
||||
hint: Hint;
|
||||
rule: Rule;
|
||||
largeCheck: boolean;
|
||||
large: string;
|
||||
multi: boolean;
|
||||
own: boolean;
|
||||
innerNameCheck: boolean;
|
||||
innerName: string;
|
||||
back: string;
|
||||
placeholder: string;
|
||||
type: string;
|
||||
autofill: boolean;
|
||||
default: string;
|
||||
single: boolean;
|
||||
number: boolean;
|
||||
xy: string;
|
||||
format: "carousel" | "masonry";
|
||||
text: string;
|
||||
picture: string;
|
||||
video: string;
|
||||
dateRange: boolean;
|
||||
time: boolean;
|
||||
form: string;
|
||||
steps: number;
|
||||
range: string;
|
||||
start: number;
|
||||
step: number;
|
||||
chooseRange: boolean;
|
||||
required: boolean;
|
||||
replText: string;
|
||||
ratingNegativeDescription: string;
|
||||
ratingPositiveDescription: string;
|
||||
};
|
||||
version: number;
|
||||
parent_ids: number[];
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
expanded: boolean;
|
||||
}
|
||||
|
||||
setAutoFreeze(false);
|
||||
|
||||
interface QuestionStore {
|
||||
listQuestions: Record<string, Question[]>;
|
||||
listQuestions: Record<string, AnyQuizQuestion[]>;
|
||||
openedModalSettings: string;
|
||||
}
|
||||
|
||||
export const DEFAULT_QUESTION: Omit<Question, "id"> = {
|
||||
title: "",
|
||||
description: "",
|
||||
type: "",
|
||||
required: true,
|
||||
deleted: false,
|
||||
deleteTimeoutId: 0,
|
||||
page: 0,
|
||||
content: {
|
||||
largeCheck: false,
|
||||
large: "",
|
||||
multi: false,
|
||||
own: false,
|
||||
innerNameCheck: false,
|
||||
innerName: "",
|
||||
back: "",
|
||||
placeholder: "",
|
||||
type: "all",
|
||||
autofill: true,
|
||||
default: "",
|
||||
number: false,
|
||||
single: false,
|
||||
xy: "",
|
||||
format: "carousel",
|
||||
text: "",
|
||||
picture: "",
|
||||
video: "",
|
||||
dateRange: false,
|
||||
time: false,
|
||||
form: "star",
|
||||
steps: 5,
|
||||
range: "0—100",
|
||||
start: 50,
|
||||
step: 1,
|
||||
chooseRange: false,
|
||||
required: false,
|
||||
replText: "",
|
||||
ratingNegativeDescription: "",
|
||||
ratingPositiveDescription: "",
|
||||
variants: [
|
||||
{
|
||||
answer: "",
|
||||
hints: "",
|
||||
emoji: "",
|
||||
image: "",
|
||||
},
|
||||
],
|
||||
hint: {
|
||||
text: "",
|
||||
video: "",
|
||||
},
|
||||
rule: {
|
||||
or: true,
|
||||
show: true,
|
||||
reqs: [
|
||||
{
|
||||
id: "",
|
||||
vars: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
version: 0,
|
||||
parent_ids: [0],
|
||||
created_at: "",
|
||||
updated_at: "",
|
||||
expanded: false,
|
||||
};
|
||||
|
||||
let isFirstPartialize = true;
|
||||
|
||||
export const questionStore = create<QuestionStore>()(
|
||||
@ -178,13 +63,44 @@ export const questionStore = create<QuestionStore>()(
|
||||
|
||||
return state;
|
||||
},
|
||||
merge: (persistedState, currentState) => {
|
||||
const state = persistedState as QuestionStore;
|
||||
|
||||
// replace blob urls with ""
|
||||
Object.values(state.listQuestions).forEach(questions => {
|
||||
questions.forEach(question => {
|
||||
if (question.type === "page" && question.content.picture.startsWith("blob:")) {
|
||||
question.content.picture = "";
|
||||
}
|
||||
if (question.type === "images") {
|
||||
question.content.variants.forEach(variant => {
|
||||
if (variant.extendedText.startsWith("blob:")) {
|
||||
variant.extendedText = "";
|
||||
}
|
||||
});
|
||||
}
|
||||
if (question.type === "varimg") {
|
||||
question.content.variants.forEach(variant => {
|
||||
if (variant.extendedText.startsWith("blob:")) {
|
||||
variant.extendedText = "";
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
...currentState,
|
||||
...state,
|
||||
};
|
||||
},
|
||||
}
|
||||
)
|
||||
);
|
||||
export const updateQuestionsList = (
|
||||
export const updateQuestionsList = <T = AnyQuizQuestion>(
|
||||
quizId: number,
|
||||
index: number,
|
||||
data: Partial<Question>
|
||||
data: Partial<T>
|
||||
) => {
|
||||
const questionListClone = { ...questionStore.getState()["listQuestions"] };
|
||||
questionListClone[quizId][index] = {
|
||||
@ -196,7 +112,7 @@ export const updateQuestionsList = (
|
||||
|
||||
export const updateQuestionsListDragAndDrop = (
|
||||
quizId: number,
|
||||
updatedQuestions: Question[]
|
||||
updatedQuestions: AnyQuizQuestion[]
|
||||
) => {
|
||||
const questionListClone = { ...questionStore.getState()["listQuestions"] };
|
||||
questionStore.setState({
|
||||
@ -207,15 +123,38 @@ export const updateQuestionsListDragAndDrop = (
|
||||
export const updateVariants = (
|
||||
quizId: number,
|
||||
index: number,
|
||||
variants: Variants[]
|
||||
variants: QuestionVariant[]
|
||||
) => {
|
||||
const listQuestions = { ...questionStore.getState()["listQuestions"] };
|
||||
listQuestions[quizId][index].content.variants = variants;
|
||||
|
||||
questionStore.setState({ listQuestions });
|
||||
switch (listQuestions[quizId][index].type) {
|
||||
case "emoji":
|
||||
const emojiState = listQuestions[quizId][index] as QuizQuestionEmoji;
|
||||
emojiState.content.variants = variants;
|
||||
return questionStore.setState({ listQuestions });
|
||||
|
||||
case "images":
|
||||
const imagesState = listQuestions[quizId][index] as QuizQuestionImages;
|
||||
imagesState.content.variants = variants;
|
||||
return questionStore.setState({ listQuestions });
|
||||
|
||||
case "variant":
|
||||
const variantState = listQuestions[quizId][index] as QuizQuestionVariant;
|
||||
variantState.content.variants = variants;
|
||||
return questionStore.setState({ listQuestions });
|
||||
|
||||
case "varimg":
|
||||
const varImgState = listQuestions[quizId][index] as QuizQuestionVarImg;
|
||||
varImgState.content.variants = variants;
|
||||
return questionStore.setState({ listQuestions });
|
||||
}
|
||||
};
|
||||
|
||||
export const createQuestion = (quizId: number, placeIndex = -1) => {
|
||||
export const createQuestion = (
|
||||
quizId: number,
|
||||
questionType: QuizQuestionType = "nonselected",
|
||||
placeIndex = -1
|
||||
) => {
|
||||
const id = getRandom(1000000, 10000000);
|
||||
const newData = { ...questionStore.getState()["listQuestions"] };
|
||||
|
||||
@ -223,13 +162,30 @@ export const createQuestion = (quizId: number, placeIndex = -1) => {
|
||||
newData[quizId] = [];
|
||||
}
|
||||
|
||||
newData[quizId].splice(
|
||||
placeIndex < 0 ? newData[quizId].length : placeIndex,
|
||||
0,
|
||||
{ ...DEFAULT_QUESTION, id }
|
||||
);
|
||||
const defaultObject = [
|
||||
QUIZ_QUESTION_BASE,
|
||||
QUIZ_QUESTION_DATE,
|
||||
QUIZ_QUESTION_EMOJI,
|
||||
QUIZ_QUESTION_FILE,
|
||||
QUIZ_QUESTION_IMAGES,
|
||||
QUIZ_QUESTION_NUMBER,
|
||||
QUIZ_QUESTION_PAGE,
|
||||
QUIZ_QUESTION_RATING,
|
||||
QUIZ_QUESTION_SELECT,
|
||||
QUIZ_QUESTION_TEXT,
|
||||
QUIZ_QUESTION_VARIANT,
|
||||
QUIZ_QUESTION_VARIMG,
|
||||
].find((defaultObjectItem) => defaultObjectItem.type === questionType);
|
||||
|
||||
questionStore.setState({ listQuestions: newData });
|
||||
if (defaultObject) {
|
||||
newData[quizId].splice(
|
||||
placeIndex < 0 ? newData[quizId].length : placeIndex,
|
||||
0,
|
||||
{ ...defaultObject, id }
|
||||
);
|
||||
|
||||
questionStore.setState({ listQuestions: newData });
|
||||
}
|
||||
};
|
||||
|
||||
export const copyQuestion = (quizId: number, copiedQuestionIndex: number) => {
|
||||
@ -267,7 +223,7 @@ export const findQuestionById = (quizId: number) => {
|
||||
let found = null;
|
||||
questionStore
|
||||
.getState()
|
||||
["listQuestions"][quizId].some((quiz: Question, index: number) => {
|
||||
["listQuestions"][quizId].some((quiz: AnyQuizQuestion, index: number) => {
|
||||
if (quiz.id === quizId) {
|
||||
found = { quiz, index };
|
||||
return true;
|
||||
|
37
src/stores/quizPreview.ts
Normal file
37
src/stores/quizPreview.ts
Normal file
@ -0,0 +1,37 @@
|
||||
import { create } from "zustand";
|
||||
import { devtools } from "zustand/middleware";
|
||||
|
||||
|
||||
interface QuizPreviewStore {
|
||||
isPreviewShown: boolean;
|
||||
currentQuestionIndex: number;
|
||||
}
|
||||
|
||||
export const useQuizPreviewStore = create<QuizPreviewStore>()(
|
||||
devtools(
|
||||
(set, get) => ({
|
||||
isPreviewShown: true,
|
||||
currentQuestionIndex: 0,
|
||||
}),
|
||||
{
|
||||
name: "quizPreview",
|
||||
enabled: process.env.NODE_ENV !== "production",
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
export const showQuizPreview = () => useQuizPreviewStore.setState({ isPreviewShown: true });
|
||||
|
||||
export const hideQuizPreview = () => useQuizPreviewStore.setState({ isPreviewShown: false });
|
||||
|
||||
export const toggleQuizPreview = () => useQuizPreviewStore.setState(
|
||||
state => ({ isPreviewShown: !state.isPreviewShown })
|
||||
);
|
||||
|
||||
export const incrementCurrentQuestionIndex = (maxStep: number) => useQuizPreviewStore.setState(
|
||||
state => ({ currentQuestionIndex: Math.min(state.currentQuestionIndex + 1, maxStep) })
|
||||
);
|
||||
|
||||
export const decrementCurrentQuestionIndex = () => useQuizPreviewStore.setState(
|
||||
state => ({ currentQuestionIndex: Math.max(state.currentQuestionIndex - 1, 0) })
|
||||
);
|
@ -36,6 +36,7 @@ export interface Quizes {
|
||||
group_id: number,
|
||||
step: number,
|
||||
startpage: string,
|
||||
createResult: boolean,
|
||||
config: {
|
||||
type: string,
|
||||
logo: string,
|
||||
@ -118,6 +119,7 @@ export const quizStore = create<QuizStore>()(
|
||||
"group_id": 0,
|
||||
"step": 1,
|
||||
"startpage": "",
|
||||
"createResult": false,
|
||||
"config": {
|
||||
"noStartPage": false,
|
||||
"type": "", // quiz или form
|
||||
|
61
src/ui_kit/CustomSlider.tsx
Normal file
61
src/ui_kit/CustomSlider.tsx
Normal file
@ -0,0 +1,61 @@
|
||||
import { Slider } from "@mui/material";
|
||||
|
||||
type CustomSliderProps = {
|
||||
defaultValue?: number;
|
||||
value?: number | number[];
|
||||
min?: number;
|
||||
max?: number;
|
||||
step?: number;
|
||||
onChange?: (value: number | number[]) => void;
|
||||
};
|
||||
|
||||
export const CustomSlider = ({
|
||||
defaultValue,
|
||||
value,
|
||||
min = 0,
|
||||
max = 100,
|
||||
step,
|
||||
onChange,
|
||||
}: CustomSliderProps) => {
|
||||
const handleChange = ({ type }: Event, newValue: number | number[]) => {
|
||||
// Для корректной работы слайдера в FireFox
|
||||
if (type !== "change") {
|
||||
onChange?.(newValue);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Slider
|
||||
value={value}
|
||||
defaultValue={defaultValue}
|
||||
min={min}
|
||||
max={max}
|
||||
step={step}
|
||||
onChange={handleChange}
|
||||
valueLabelDisplay="auto"
|
||||
sx={{
|
||||
color: "#7E2AEA",
|
||||
height: "12px",
|
||||
"& .MuiSlider-track": {
|
||||
border: "none",
|
||||
},
|
||||
"& .MuiSlider-rail": {
|
||||
backgroundColor: "#F2F3F7",
|
||||
border: `1px solid #9A9AAF`,
|
||||
},
|
||||
"& .MuiSlider-thumb": {
|
||||
height: 32,
|
||||
width: 32,
|
||||
border: `6px solid "#7E2AEA`,
|
||||
backgroundColor: "white",
|
||||
boxShadow: `0px 0px 0px 3px white,
|
||||
0px 4px 4px 3px #C3C8DD`,
|
||||
"&:focus, &:hover, &.Mui-active, &.Mui-focusVisible": {
|
||||
boxShadow: `0px 0px 0px 3px white,
|
||||
0px 4px 4px 3px #C3C8DD`,
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
@ -7,6 +7,7 @@ import {
|
||||
} from "@mui/material";
|
||||
|
||||
import type { ChangeEvent, KeyboardEvent, FocusEvent } from "react";
|
||||
import type { InputProps } from "@mui/material";
|
||||
|
||||
interface CustomTextFieldProps {
|
||||
placeholder: string;
|
||||
@ -17,6 +18,7 @@ interface CustomTextFieldProps {
|
||||
onBlur?: (event: FocusEvent<HTMLInputElement>) => void;
|
||||
text?: string;
|
||||
sx?: SxProps<Theme>;
|
||||
InputProps?: Partial<InputProps>;
|
||||
}
|
||||
|
||||
export default function CustomTextField({
|
||||
@ -28,6 +30,7 @@ export default function CustomTextField({
|
||||
onChange,
|
||||
onKeyDown,
|
||||
onBlur,
|
||||
InputProps,
|
||||
}: CustomTextFieldProps) {
|
||||
const theme = useTheme();
|
||||
|
||||
@ -55,6 +58,7 @@ export default function CustomTextField({
|
||||
},
|
||||
...sx,
|
||||
}}
|
||||
InputProps={InputProps}
|
||||
inputProps={{
|
||||
sx: {
|
||||
borderRadius: "10px",
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { useState } from "react";
|
||||
import { Box, IconButton, SxProps, Theme, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||
|
||||
import CrossedEyeIcon from "@icons/CrossedEyeIcon";
|
||||
import { CrossedEyeIcon } from "@icons/CrossedEyeIcon";
|
||||
import CopyIcon from "@icons/CopyIcon";
|
||||
import TrashIcon from "@icons/TrashIcon";
|
||||
import CountIcon from "@icons/CountIcon";
|
||||
|
68
src/ui_kit/LabeledDatePicker.tsx
Normal file
68
src/ui_kit/LabeledDatePicker.tsx
Normal file
@ -0,0 +1,68 @@
|
||||
import CalendarIcon from "@icons/CalendarIcon";
|
||||
import { Typography, Box, SxProps, Theme, useMediaQuery, useTheme } from "@mui/material";
|
||||
import { DatePicker } from "@mui/x-date-pickers";
|
||||
import { Dayjs } from "dayjs";
|
||||
|
||||
|
||||
interface Props {
|
||||
label?: string;
|
||||
sx?: SxProps<Theme>;
|
||||
value?: Dayjs | null;
|
||||
onChange?: (value: Dayjs | null) => void;
|
||||
}
|
||||
|
||||
export default function LabeledDatePicker({ label, value, onChange, sx }: Props) {
|
||||
const theme = useTheme();
|
||||
const upLg = useMediaQuery(theme.breakpoints.up("md"));
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
...sx,
|
||||
}}
|
||||
>
|
||||
{label && (
|
||||
<Typography
|
||||
sx={{
|
||||
fontWeight: 500,
|
||||
fontSize: "16px",
|
||||
lineHeight: "20px",
|
||||
color: "#4D4D4D",
|
||||
mb: "10px",
|
||||
}}
|
||||
>
|
||||
{label}
|
||||
</Typography>
|
||||
)}
|
||||
<DatePicker
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
slots={{
|
||||
openPickerIcon: () => <CalendarIcon />,
|
||||
}}
|
||||
slotProps={{
|
||||
openPickerButton: {
|
||||
sx: {
|
||||
p: 0,
|
||||
},
|
||||
},
|
||||
}}
|
||||
sx={{
|
||||
"& .MuiInputBase-root": {
|
||||
backgroundColor: "#F2F3F7",
|
||||
borderRadius: "10px",
|
||||
pr: "22px",
|
||||
"& input": {
|
||||
py: "11px",
|
||||
pl: upLg ? "20px" : "13px",
|
||||
lineHeight: "19px",
|
||||
},
|
||||
"& fieldset": {
|
||||
borderColor: "#9A9AAF",
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
@ -1,13 +1,13 @@
|
||||
import React, { useState, useRef, useEffect, FC } from "react";
|
||||
import ReactCrop, { Crop, PixelCrop } from "react-image-crop";
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Modal,
|
||||
Slider,
|
||||
Typography,
|
||||
useMediaQuery,
|
||||
useTheme,
|
||||
Box,
|
||||
Button,
|
||||
Modal,
|
||||
Slider,
|
||||
Typography,
|
||||
useMediaQuery,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
|
||||
import { canvasPreview } from "./utils/canvasPreview";
|
||||
@ -19,334 +19,336 @@ import "react-image-crop/dist/ReactCrop.css";
|
||||
import { CropIcon } from "@icons/CropIcon";
|
||||
|
||||
interface Iprops {
|
||||
opened: boolean;
|
||||
onClose: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
picture?: string;
|
||||
opened: boolean;
|
||||
onClose: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
picture?: string;
|
||||
onCropPress?: (imageUrl: string) => void;
|
||||
}
|
||||
|
||||
export const CropModal: FC<Iprops> = ({ opened, onClose, picture }) => {
|
||||
const [imgSrc, setImgSrc] = useState("");
|
||||
export const CropModal: FC<Iprops> = ({ opened, onClose, picture, onCropPress }) => {
|
||||
const [imgSrc, setImgSrc] = useState("");
|
||||
|
||||
const imgRef = useRef<HTMLImageElement>(null);
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
const previewCanvasRef = useRef<HTMLCanvasElement>(null);
|
||||
const hiddenAnchorRef = useRef<HTMLAnchorElement>(null);
|
||||
const blobUrlRef = useRef("");
|
||||
const [crop, setCrop] = useState<Crop>();
|
||||
const [completedCrop, setCompletedCrop] = useState<PixelCrop>();
|
||||
const [rotate, setRotate] = useState(0);
|
||||
const [darken, setDarken] = useState(0);
|
||||
const imgRef = useRef<HTMLImageElement>(null);
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
const previewCanvasRef = useRef<HTMLCanvasElement>(null);
|
||||
const hiddenAnchorRef = useRef<HTMLAnchorElement>(null);
|
||||
const blobUrlRef = useRef("");
|
||||
const [crop, setCrop] = useState<Crop>();
|
||||
const [completedCrop, setCompletedCrop] = useState<PixelCrop>();
|
||||
const [rotate, setRotate] = useState(0);
|
||||
const [darken, setDarken] = useState(0);
|
||||
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(786));
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(786));
|
||||
|
||||
useEffect(() => {
|
||||
if (picture) {
|
||||
setImgSrc(picture);
|
||||
}
|
||||
}, [picture]);
|
||||
useEffect(() => {
|
||||
if (picture) {
|
||||
setImgSrc(picture);
|
||||
}
|
||||
}, [picture]);
|
||||
|
||||
const styleModal = {
|
||||
position: "absolute",
|
||||
top: "50%",
|
||||
left: "50%",
|
||||
transform: "translate(-50%, -50%)",
|
||||
width: isMobile ? "343px" : "620px",
|
||||
bgcolor: "background.paper",
|
||||
boxShadow: 24,
|
||||
padding: "20px",
|
||||
borderRadius: "8px",
|
||||
};
|
||||
const styleModal = {
|
||||
position: "absolute",
|
||||
top: "50%",
|
||||
left: "50%",
|
||||
transform: "translate(-50%, -50%)",
|
||||
width: isMobile ? "343px" : "620px",
|
||||
bgcolor: "background.paper",
|
||||
boxShadow: 24,
|
||||
padding: "20px",
|
||||
borderRadius: "8px",
|
||||
};
|
||||
|
||||
const styleSlider = {
|
||||
width: isMobile ? "350px" : "250px",
|
||||
color: "#7E2AEA",
|
||||
height: "12px",
|
||||
"& .MuiSlider-track": {
|
||||
border: "none",
|
||||
},
|
||||
"& .MuiSlider-rail": {
|
||||
backgroundColor: "#F2F3F7",
|
||||
border: `1px solid #9A9AAF`,
|
||||
},
|
||||
"& .MuiSlider-thumb": {
|
||||
height: 26,
|
||||
width: 26,
|
||||
border: `6px solid #7E2AEA`,
|
||||
backgroundColor: "white",
|
||||
boxShadow: `0px 0px 0px 3px white,
|
||||
const styleSlider = {
|
||||
width: isMobile ? "350px" : "250px",
|
||||
color: "#7E2AEA",
|
||||
height: "12px",
|
||||
"& .MuiSlider-track": {
|
||||
border: "none",
|
||||
},
|
||||
"& .MuiSlider-rail": {
|
||||
backgroundColor: "#F2F3F7",
|
||||
border: `1px solid #9A9AAF`,
|
||||
},
|
||||
"& .MuiSlider-thumb": {
|
||||
height: 26,
|
||||
width: 26,
|
||||
border: `6px solid #7E2AEA`,
|
||||
backgroundColor: "white",
|
||||
boxShadow: `0px 0px 0px 3px white,
|
||||
0px 4px 4px 3px #C3C8DD`,
|
||||
"&:focus, &:hover, &.Mui-active, &.Mui-focusVisible": {
|
||||
boxShadow: `0px 0px 0px 3px white,
|
||||
"&:focus, &:hover, &.Mui-active, &.Mui-focusVisible": {
|
||||
boxShadow: `0px 0px 0px 3px white,
|
||||
0px 4px 4px 3px #C3C8DD`,
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const rotateImage = () => {
|
||||
const newRotation = (rotate + 90) % 360;
|
||||
setRotate(newRotation);
|
||||
};
|
||||
const rotateImage = () => {
|
||||
const newRotation = (rotate + 90) % 360;
|
||||
setRotate(newRotation);
|
||||
};
|
||||
|
||||
const onSelectFile = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (event.target.files && event.target.files.length > 0) {
|
||||
setCrop(undefined);
|
||||
const reader = new FileReader();
|
||||
reader.addEventListener("load", () =>
|
||||
setImgSrc(reader.result?.toString() || "")
|
||||
);
|
||||
reader.readAsDataURL(event.target.files[0]);
|
||||
}
|
||||
};
|
||||
const onSelectFile = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (event.target.files && event.target.files.length > 0) {
|
||||
setCrop(undefined);
|
||||
const reader = new FileReader();
|
||||
reader.addEventListener("load", () =>
|
||||
setImgSrc(reader.result?.toString() || "")
|
||||
);
|
||||
reader.readAsDataURL(event.target.files[0]);
|
||||
}
|
||||
};
|
||||
|
||||
const onDownloadCropClick = () => {
|
||||
if (!previewCanvasRef.current) {
|
||||
throw new Error("Crop canvas does not exist");
|
||||
}
|
||||
const onDownloadCropClick = () => {
|
||||
if (!previewCanvasRef.current) {
|
||||
throw new Error("Crop canvas does not exist");
|
||||
}
|
||||
|
||||
const canvasCopy = document.createElement("canvas");
|
||||
const ctx = canvasCopy.getContext("2d");
|
||||
canvasCopy.width = previewCanvasRef.current.width;
|
||||
canvasCopy.height = previewCanvasRef.current.height;
|
||||
ctx!.filter = `brightness(${100 - darken}%)`;
|
||||
ctx!.drawImage(previewCanvasRef.current, 0, 0);
|
||||
const canvasCopy = document.createElement("canvas");
|
||||
const ctx = canvasCopy.getContext("2d");
|
||||
canvasCopy.width = previewCanvasRef.current.width;
|
||||
canvasCopy.height = previewCanvasRef.current.height;
|
||||
ctx!.filter = `brightness(${100 - darken}%)`;
|
||||
ctx!.drawImage(previewCanvasRef.current, 0, 0);
|
||||
|
||||
canvasCopy.toBlob((blob) => {
|
||||
if (!blob) {
|
||||
throw new Error("Failed to create blob");
|
||||
}
|
||||
if (blobUrlRef.current) {
|
||||
URL.revokeObjectURL(blobUrlRef.current);
|
||||
}
|
||||
blobUrlRef.current = URL.createObjectURL(blob);
|
||||
hiddenAnchorRef.current!.href = blobUrlRef.current;
|
||||
hiddenAnchorRef.current!.click();
|
||||
canvasCopy.toBlob((blob) => {
|
||||
if (!blob) {
|
||||
throw new Error("Failed to create blob");
|
||||
}
|
||||
if (blobUrlRef.current) {
|
||||
URL.revokeObjectURL(blobUrlRef.current);
|
||||
}
|
||||
blobUrlRef.current = URL.createObjectURL(blob);
|
||||
hiddenAnchorRef.current!.href = blobUrlRef.current;
|
||||
hiddenAnchorRef.current!.click();
|
||||
|
||||
setImgSrc(blobUrlRef.current);
|
||||
});
|
||||
};
|
||||
setImgSrc(blobUrlRef.current);
|
||||
onCropPress?.(blobUrlRef.current);
|
||||
});
|
||||
};
|
||||
|
||||
useDebounceEffect(
|
||||
async () => {
|
||||
if (
|
||||
completedCrop?.width &&
|
||||
completedCrop?.height &&
|
||||
imgRef.current &&
|
||||
previewCanvasRef.current
|
||||
) {
|
||||
canvasPreview(
|
||||
imgRef.current,
|
||||
previewCanvasRef.current,
|
||||
completedCrop,
|
||||
rotate
|
||||
);
|
||||
}
|
||||
},
|
||||
100,
|
||||
[completedCrop, rotate]
|
||||
);
|
||||
useDebounceEffect(
|
||||
async () => {
|
||||
if (
|
||||
completedCrop?.width &&
|
||||
completedCrop?.height &&
|
||||
imgRef.current &&
|
||||
previewCanvasRef.current
|
||||
) {
|
||||
canvasPreview(
|
||||
imgRef.current,
|
||||
previewCanvasRef.current,
|
||||
completedCrop,
|
||||
rotate
|
||||
);
|
||||
}
|
||||
},
|
||||
100,
|
||||
[completedCrop, rotate]
|
||||
);
|
||||
|
||||
const [width, setWidth] = useState<number>(0);
|
||||
const [width, setWidth] = useState<number>(0);
|
||||
|
||||
const getImageSize = () => {
|
||||
if (imgRef.current) {
|
||||
const imageWidth = imgRef.current.naturalWidth;
|
||||
const imageHeight = imgRef.current.naturalHeight;
|
||||
const getImageSize = () => {
|
||||
if (imgRef.current) {
|
||||
const imageWidth = imgRef.current.naturalWidth;
|
||||
const imageHeight = imgRef.current.naturalHeight;
|
||||
|
||||
const aspect = imageWidth / imageHeight;
|
||||
const aspect = imageWidth / imageHeight;
|
||||
|
||||
|
||||
if (aspect <= 1.333) {
|
||||
setWidth(240);
|
||||
}
|
||||
if (aspect >= 1.5) {
|
||||
setWidth(580);
|
||||
}
|
||||
if (aspect >= 1.778) {
|
||||
setWidth(580);
|
||||
}
|
||||
}
|
||||
};
|
||||
if (aspect <= 1.333) {
|
||||
setWidth(240);
|
||||
}
|
||||
if (aspect >= 1.5) {
|
||||
setWidth(580);
|
||||
}
|
||||
if (aspect >= 1.778) {
|
||||
setWidth(580);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal
|
||||
open={opened}
|
||||
onClose={onClose}
|
||||
aria-labelledby="modal-modal-title"
|
||||
aria-describedby="modal-modal-description"
|
||||
>
|
||||
<Box sx={styleModal}>
|
||||
<Box
|
||||
sx={{
|
||||
height: "320px",
|
||||
padding: "10px",
|
||||
backgroundSize: "cover",
|
||||
backgroundRepeat: "no-repeat",
|
||||
return (
|
||||
<>
|
||||
<Modal
|
||||
open={opened}
|
||||
onClose={onClose}
|
||||
aria-labelledby="modal-modal-title"
|
||||
aria-describedby="modal-modal-description"
|
||||
>
|
||||
<Box sx={styleModal}>
|
||||
<Box
|
||||
sx={{
|
||||
height: "320px",
|
||||
padding: "10px",
|
||||
backgroundSize: "cover",
|
||||
backgroundRepeat: "no-repeat",
|
||||
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
{imgSrc && (
|
||||
<ReactCrop
|
||||
crop={crop}
|
||||
onChange={(_, percentCrop) => setCrop(percentCrop)}
|
||||
onComplete={(c) => setCompletedCrop(c)}
|
||||
maxWidth={500}
|
||||
minWidth={50}
|
||||
maxHeight={320}
|
||||
minHeight={50}
|
||||
>
|
||||
<img
|
||||
onLoad={getImageSize}
|
||||
ref={imgRef}
|
||||
alt="Crop me"
|
||||
src={imgSrc}
|
||||
style={{
|
||||
filter: `brightness(${100 - darken}%)`,
|
||||
transform: ` rotate(${rotate}deg)`,
|
||||
maxWidth: "580px",
|
||||
maxHeight: "320px",
|
||||
}}
|
||||
width={width}
|
||||
/>
|
||||
</ReactCrop>
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
{imgSrc && (
|
||||
<ReactCrop
|
||||
crop={crop}
|
||||
onChange={(_, percentCrop) => setCrop(percentCrop)}
|
||||
onComplete={(c) => setCompletedCrop(c)}
|
||||
maxWidth={500}
|
||||
minWidth={50}
|
||||
maxHeight={320}
|
||||
minHeight={50}
|
||||
>
|
||||
<img
|
||||
onLoad={getImageSize}
|
||||
ref={imgRef}
|
||||
alt="Crop me"
|
||||
src={imgSrc}
|
||||
style={{
|
||||
filter: `brightness(${100 - darken}%)`,
|
||||
transform: ` rotate(${rotate}deg)`,
|
||||
maxWidth: "580px",
|
||||
maxHeight: "320px",
|
||||
}}
|
||||
width={width}
|
||||
/>
|
||||
</ReactCrop>
|
||||
)}
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
color: "#7E2AEA",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
fontSize: "16xp",
|
||||
fontWeight: "600",
|
||||
marginBottom: "50px",
|
||||
}}
|
||||
>
|
||||
<Typography sx={{ color: "#7E2AEA", lineHeight: "0px" }}>
|
||||
{crop?.width ? Math.round(crop.width) + "px" : ""}
|
||||
</Typography>
|
||||
<Typography sx={{ color: "#7E2AEA", lineHeight: "0px" }}>
|
||||
{crop?.height ? Math.round(crop.height) + "px" : ""}
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
display: isMobile ? "block" : "flex",
|
||||
alignItems: "end",
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<ResetIcon
|
||||
onClick={rotateImage}
|
||||
style={{ marginBottom: "10px", cursor: "pointer" }}
|
||||
/>
|
||||
<Box>
|
||||
<Typography sx={{ color: "#9A9AAF", fontSize: "16px" }}>
|
||||
Размер
|
||||
</Typography>
|
||||
<Slider
|
||||
sx={styleSlider}
|
||||
value={width}
|
||||
min={50}
|
||||
max={580}
|
||||
step={1}
|
||||
onChange={(_, newValue) => {
|
||||
setWidth(newValue as number);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<Box>
|
||||
<Typography sx={{ color: "#9A9AAF", fontSize: "16px" }}>
|
||||
Затемнение
|
||||
</Typography>
|
||||
<Slider
|
||||
sx={styleSlider}
|
||||
value={darken}
|
||||
min={0}
|
||||
max={100}
|
||||
step={1}
|
||||
onChange={(_, newValue) => setDarken(newValue as number)}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
marginTop: "40px",
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
justifyContent: "end",
|
||||
}}
|
||||
>
|
||||
<input
|
||||
style={{ display: "none", zIndex: "-999" }}
|
||||
ref={fileInputRef}
|
||||
type="file"
|
||||
accept="image/*"
|
||||
onChange={onSelectFile}
|
||||
/>
|
||||
<Button
|
||||
onClick={() => fileInputRef.current?.click()}
|
||||
disableRipple
|
||||
sx={{
|
||||
width: "215px",
|
||||
height: "48px",
|
||||
color: "#7E2AEA",
|
||||
borderRadius: "8px",
|
||||
border: "1px solid #7E2AEA",
|
||||
marginRight: "10px",
|
||||
}}
|
||||
>
|
||||
Загрузить оригинал
|
||||
</Button>
|
||||
<Button
|
||||
onClick={onDownloadCropClick}
|
||||
disableRipple
|
||||
variant="contained"
|
||||
sx={{
|
||||
padding: "10px 20px",
|
||||
borderRadius: "8px",
|
||||
background: theme.palette.brightPurple.main,
|
||||
fontSize: "18px",
|
||||
}}
|
||||
>
|
||||
<CropIcon />
|
||||
Обрезать
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
</Modal>
|
||||
{completedCrop && (
|
||||
<div>
|
||||
<canvas
|
||||
ref={previewCanvasRef}
|
||||
style={{
|
||||
display: "none",
|
||||
zIndex: "-999",
|
||||
border: "1px solid black",
|
||||
objectFit: "contain",
|
||||
width: completedCrop.width,
|
||||
height: completedCrop.height,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
color: "#7E2AEA",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
fontSize: "16xp",
|
||||
fontWeight: "600",
|
||||
marginBottom: "50px",
|
||||
}}
|
||||
>
|
||||
<Typography sx={{ color: "#7E2AEA", lineHeight: "0px" }}>
|
||||
{crop?.width ? Math.round(crop.width) + "px" : ""}
|
||||
</Typography>
|
||||
<Typography sx={{ color: "#7E2AEA", lineHeight: "0px" }}>
|
||||
{crop?.height ? Math.round(crop.height) + "px" : ""}
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
display: isMobile ? "block" : "flex",
|
||||
alignItems: "end",
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<ResetIcon
|
||||
onClick={rotateImage}
|
||||
style={{ marginBottom: "10px", cursor: "pointer" }}
|
||||
/>
|
||||
<Box>
|
||||
<Typography sx={{ color: "#9A9AAF", fontSize: "16px" }}>
|
||||
Размер
|
||||
</Typography>
|
||||
<Slider
|
||||
sx={styleSlider}
|
||||
value={width}
|
||||
min={50}
|
||||
max={580}
|
||||
step={1}
|
||||
onChange={(_, newValue) => {
|
||||
setWidth(newValue as number);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<Box>
|
||||
<Typography sx={{ color: "#9A9AAF", fontSize: "16px" }}>
|
||||
Затемнение
|
||||
</Typography>
|
||||
<Slider
|
||||
sx={styleSlider}
|
||||
value={darken}
|
||||
min={0}
|
||||
max={100}
|
||||
step={1}
|
||||
onChange={(_, newValue) => setDarken(newValue as number)}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
marginTop: "40px",
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
justifyContent: "end",
|
||||
}}
|
||||
>
|
||||
<input
|
||||
style={{ display: "none", zIndex: "-999" }}
|
||||
ref={fileInputRef}
|
||||
type="file"
|
||||
accept="image/*"
|
||||
onChange={onSelectFile}
|
||||
/>
|
||||
<Button
|
||||
onClick={() => fileInputRef.current?.click()}
|
||||
disableRipple
|
||||
sx={{
|
||||
width: "215px",
|
||||
height: "48px",
|
||||
color: "#7E2AEA",
|
||||
borderRadius: "8px",
|
||||
border: "1px solid #7E2AEA",
|
||||
marginRight: "10px",
|
||||
}}
|
||||
>
|
||||
Загрузить оригинал
|
||||
</Button>
|
||||
<Button
|
||||
onClick={onDownloadCropClick}
|
||||
disableRipple
|
||||
variant="contained"
|
||||
sx={{
|
||||
padding: "10px 20px",
|
||||
borderRadius: "8px",
|
||||
background: theme.palette.brightPurple.main,
|
||||
fontSize: "18px",
|
||||
}}
|
||||
>
|
||||
<CropIcon />
|
||||
Обрезать
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
</Modal>
|
||||
{completedCrop && (
|
||||
<div>
|
||||
<canvas
|
||||
ref={previewCanvasRef}
|
||||
style={{
|
||||
display: "none",
|
||||
zIndex: "-999",
|
||||
border: "1px solid black",
|
||||
objectFit: "contain",
|
||||
width: completedCrop.width,
|
||||
height: completedCrop.height,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<a
|
||||
href="#hidden"
|
||||
ref={hiddenAnchorRef}
|
||||
download
|
||||
style={{
|
||||
display: "none",
|
||||
}}
|
||||
>
|
||||
Hidden download
|
||||
</a>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
<div>
|
||||
<a
|
||||
href="#hidden"
|
||||
ref={hiddenAnchorRef}
|
||||
download
|
||||
style={{
|
||||
display: "none",
|
||||
}}
|
||||
>
|
||||
Hidden download
|
||||
</a>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
130
src/ui_kit/QuizPreview/QuizPreview.tsx
Normal file
130
src/ui_kit/QuizPreview/QuizPreview.tsx
Normal file
@ -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<HTMLDivElement>(null);
|
||||
const rndRef = useRef<Rnd | null>(null);
|
||||
const rndPositionAndSizeRef = useRef<RndPositionAndSize>({ x: 0, y: 0, width: "340", height: "480" });
|
||||
const isFirstShowRef = useRef<boolean>(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 (
|
||||
<Box
|
||||
ref={rndParentRef}
|
||||
sx={{
|
||||
position: "fixed",
|
||||
top: NAVBAR_HEIGHT + DRAG_PARENT_MARGIN,
|
||||
left: DRAG_PARENT_MARGIN,
|
||||
bottom: DRAG_PARENT_BOTTOM_MARGIN,
|
||||
right: DRAG_PARENT_MARGIN,
|
||||
// backgroundColor: "rgba(0, 100, 0, 0.2)",
|
||||
pointerEvents: "none",
|
||||
zIndex: 100,
|
||||
}}
|
||||
>
|
||||
{isPreviewShown &&
|
||||
<Rnd
|
||||
minHeight={300}
|
||||
minWidth={340}
|
||||
bounds="parent"
|
||||
ref={rndRef}
|
||||
dragHandleClassName="quiz-preview-draghandle"
|
||||
default={{
|
||||
x: rndPositionAndSizeRef.current.x,
|
||||
y: rndPositionAndSizeRef.current.y,
|
||||
width: rndPositionAndSizeRef.current.width,
|
||||
height: rndPositionAndSizeRef.current.height
|
||||
}}
|
||||
onResizeStop={(e, direction, ref, delta, position) => {
|
||||
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: <ResizeIcon />
|
||||
}}
|
||||
resizeHandleStyles={{
|
||||
topLeft: {
|
||||
top: "-1px",
|
||||
left: "-1px",
|
||||
}
|
||||
}}
|
||||
style={{
|
||||
pointerEvents: "auto",
|
||||
}}
|
||||
>
|
||||
<QuizPreviewLayout />
|
||||
<IconButton
|
||||
className="quiz-preview-draghandle"
|
||||
sx={{
|
||||
position: "absolute",
|
||||
bottom: -54,
|
||||
right: 46,
|
||||
cursor: "move",
|
||||
}}
|
||||
>
|
||||
<PointsIcon style={{ color: "#4D4D4D", fontSize: "30px" }} />
|
||||
</IconButton>
|
||||
</Rnd>
|
||||
}
|
||||
<IconButton
|
||||
onClick={toggleQuizPreview}
|
||||
sx={{
|
||||
position: "absolute",
|
||||
right: 0,
|
||||
bottom: -54,
|
||||
pointerEvents: "auto",
|
||||
}}
|
||||
>
|
||||
<VisibilityIcon sx={{ height: "30px", width: "30px" }} />
|
||||
</IconButton>
|
||||
</Box>
|
||||
);
|
||||
}
|
133
src/ui_kit/QuizPreview/QuizPreviewLayout.tsx
Normal file
133
src/ui_kit/QuizPreview/QuizPreviewLayout.tsx
Normal file
@ -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<DefiniteQuestionType, FC<any>> = {
|
||||
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
|
||||
? <QuestionComponent key={currentQuestion.id} question={currentQuestion} />
|
||||
: null;
|
||||
|
||||
useEffect(function resetCurrentQuizStep() {
|
||||
if (currentQuizStep > maxCurrentQuizStep) {
|
||||
decrementCurrentQuestionIndex();
|
||||
}
|
||||
}, [currentQuizStep, maxCurrentQuizStep]);
|
||||
|
||||
return (
|
||||
<Paper sx={{
|
||||
height: "100%",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
flexGrow: 1,
|
||||
borderRadius: "12px",
|
||||
pointerEvents: "auto",
|
||||
}}>
|
||||
<Box sx={{
|
||||
p: "16px",
|
||||
whiteSpace: "break-spaces",
|
||||
overflowY: "auto",
|
||||
flexGrow: 1,
|
||||
}}>
|
||||
{questionElement}
|
||||
</Box>
|
||||
<Box sx={{
|
||||
mt: "auto",
|
||||
p: "16px",
|
||||
display: "flex",
|
||||
borderTop: "1px solid #E3E3E3",
|
||||
alignItems: "center",
|
||||
}}>
|
||||
<Box sx={{
|
||||
flexGrow: 1,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: 1,
|
||||
}}>
|
||||
<Typography>
|
||||
{nonDeletedQuizQuestions.length > 0
|
||||
? `Вопрос ${currentQuizStep + 1} из ${nonDeletedQuizQuestions.length}`
|
||||
: "Нет вопросов"
|
||||
}
|
||||
</Typography>
|
||||
{nonDeletedQuizQuestions.length > 0 &&
|
||||
<LinearProgress
|
||||
variant="determinate"
|
||||
value={currentProgress}
|
||||
sx={{
|
||||
"&.MuiLinearProgress-colorPrimary": {
|
||||
backgroundColor: "fadePurple.main",
|
||||
},
|
||||
"& .MuiLinearProgress-barColorPrimary": {
|
||||
backgroundColor: "brightPurple.main",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
}
|
||||
</Box>
|
||||
<Box sx={{
|
||||
ml: 2,
|
||||
display: "flex",
|
||||
gap: 1,
|
||||
}}>
|
||||
<Button
|
||||
variant="outlined"
|
||||
onClick={decrementCurrentQuestionIndex}
|
||||
disabled={currentQuizStep === 0}
|
||||
sx={{ px: 1, minWidth: 0 }}
|
||||
>
|
||||
<ArrowLeft />
|
||||
</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={() => incrementCurrentQuestionIndex(maxCurrentQuizStep)}
|
||||
disabled={currentQuizStep >= maxCurrentQuizStep}
|
||||
>Далее</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
</Paper>
|
||||
);
|
||||
}
|
22
src/ui_kit/QuizPreview/QuizPreviewQuestionTypes/Date.tsx
Normal file
22
src/ui_kit/QuizPreview/QuizPreviewQuestionTypes/Date.tsx
Normal file
@ -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 (
|
||||
<Box sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: 1,
|
||||
}}>
|
||||
<Typography variant="h6">{question.title}</Typography>
|
||||
<LabeledDatePicker />
|
||||
</Box>
|
||||
);
|
||||
}
|
39
src/ui_kit/QuizPreview/QuizPreviewQuestionTypes/Emoji.tsx
Normal file
39
src/ui_kit/QuizPreview/QuizPreviewQuestionTypes/Emoji.tsx
Normal file
@ -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<string | null>(null);
|
||||
|
||||
const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
setValue((event.target as HTMLInputElement).value);
|
||||
};
|
||||
|
||||
return (
|
||||
<FormControl fullWidth>
|
||||
<FormLabel id="quiz-question-radio-group">{question.title}</FormLabel>
|
||||
<RadioGroup
|
||||
aria-labelledby="quiz-question-radio-group"
|
||||
value={value}
|
||||
onChange={handleChange}
|
||||
>
|
||||
{question.content.variants.map((variant, index) => (
|
||||
<FormControlLabel key={index} value={variant.answer} control={<Radio />} label={
|
||||
<Box sx={{ display: "flex", alignItems: "center", gap: 2 }}>
|
||||
<Typography>{`${variant.extendedText} ${variant.answer}`}</Typography>
|
||||
<Tooltip title={variant.hints} placement="right">
|
||||
<Box><InfoIcon /></Box>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
} />
|
||||
))}
|
||||
</RadioGroup>
|
||||
</FormControl>
|
||||
);
|
||||
}
|
44
src/ui_kit/QuizPreview/QuizPreviewQuestionTypes/File.tsx
Normal file
44
src/ui_kit/QuizPreview/QuizPreviewQuestionTypes/File.tsx
Normal file
@ -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<HTMLInputElement>(null);
|
||||
const [file, setFile] = useState<File | null>(null);
|
||||
|
||||
function handleFileChange(event: ChangeEvent<HTMLInputElement>) {
|
||||
if (!event.target.files?.[0]) return setFile(null);
|
||||
setFile(event.target.files[0]);
|
||||
}
|
||||
|
||||
return (
|
||||
<Box sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "start",
|
||||
gap: 1,
|
||||
}}>
|
||||
<Typography variant="h6">{question.title}</Typography>
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={() => fileInputRef.current?.click()}
|
||||
>
|
||||
Загрузить файл
|
||||
<input
|
||||
ref={fileInputRef}
|
||||
onChange={handleFileChange}
|
||||
type="file"
|
||||
style={{
|
||||
display: "none",
|
||||
}}
|
||||
/>
|
||||
</Button>
|
||||
{file && <Typography>Выбран файл: {file.name}</Typography>}
|
||||
</Box>
|
||||
);
|
||||
}
|
90
src/ui_kit/QuizPreview/QuizPreviewQuestionTypes/Images.tsx
Normal file
90
src/ui_kit/QuizPreview/QuizPreviewQuestionTypes/Images.tsx
Normal file
@ -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<number[]>([]);
|
||||
|
||||
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 (
|
||||
<Box sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: 1,
|
||||
}}>
|
||||
<Typography variant="h6">{question.title}</Typography>
|
||||
<Box sx={{
|
||||
display: "grid",
|
||||
gridTemplateColumns: "repeat(auto-fit, minmax(160px, 1fr))",
|
||||
gap: 1,
|
||||
}}>
|
||||
{question.content.variants.map((variant, index) => (
|
||||
<ButtonBase
|
||||
key={index}
|
||||
onClick={() => 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 ?
|
||||
<img
|
||||
src={variant.extendedText}
|
||||
alt="question variant"
|
||||
style={{
|
||||
width: "100%",
|
||||
display: "block",
|
||||
objectFit: "scale-down",
|
||||
flexGrow: 1,
|
||||
}}
|
||||
/>
|
||||
:
|
||||
<Typography p={2}>Картинка отсутствует</Typography>
|
||||
}
|
||||
<Divider sx={{ width: "100%" }} />
|
||||
<Box sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
gap: 2,
|
||||
p: 1,
|
||||
}}>
|
||||
<Typography>{variant.answer}</Typography>
|
||||
<Tooltip title={variant.hints} placement="right">
|
||||
<Box>
|
||||
<InfoIcon />
|
||||
</Box>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
</ButtonBase>
|
||||
))}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
47
src/ui_kit/QuizPreview/QuizPreviewQuestionTypes/Number.tsx
Normal file
47
src/ui_kit/QuizPreview/QuizPreviewQuestionTypes/Number.tsx
Normal file
@ -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<number | number[]>(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 (
|
||||
<Box sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: 1,
|
||||
}}>
|
||||
<Typography variant="h6">{question.title}</Typography>
|
||||
<Box sx={{
|
||||
px: 2,
|
||||
}}>
|
||||
<CustomSlider
|
||||
value={sliderValues}
|
||||
onChange={value => setSliderValues(value)}
|
||||
min={min}
|
||||
max={max}
|
||||
defaultValue={start}
|
||||
step={question.content.step}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
34
src/ui_kit/QuizPreview/QuizPreviewQuestionTypes/Page.tsx
Normal file
34
src/ui_kit/QuizPreview/QuizPreviewQuestionTypes/Page.tsx
Normal file
@ -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 (
|
||||
<Box sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "start",
|
||||
gap: 1,
|
||||
}}>
|
||||
<Typography variant="h6">{question.title}</Typography>
|
||||
<Typography>{question.content.text}</Typography>
|
||||
{question.content.picture &&
|
||||
<img
|
||||
src={question.content.picture}
|
||||
alt=""
|
||||
style={{
|
||||
width: "100%",
|
||||
display: "block",
|
||||
objectFit: "scale-down",
|
||||
flexGrow: 1,
|
||||
}}
|
||||
/>
|
||||
}
|
||||
</Box>
|
||||
);
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user