mirror of
https://github.com/ets-cfuhrman-pfe/EvalueTonSavoir.git
synced 2025-08-11 21:23:54 -04:00
Affichage du tableau AVEC réponses longue devrait être adaptés
Fixes #228
This commit is contained in:
commit
744af1efc0
24 changed files with 1298 additions and 569 deletions
1
LICENSE
1
LICENSE
|
|
@ -3,6 +3,7 @@ MIT License
|
||||||
Copyright (c) 2023 ETS-PFE004-Plateforme-sondage-minitest
|
Copyright (c) 2023 ETS-PFE004-Plateforme-sondage-minitest
|
||||||
Copyright (c) 2024 Louis-Antoine Caron, Mathieu Roy, Mélanie St-Hilaire, Samy Waddah
|
Copyright (c) 2024 Louis-Antoine Caron, Mathieu Roy, Mélanie St-Hilaire, Samy Waddah
|
||||||
Copyright (c) 2024 Gabriel Moisan-Matte, Mathieu Sévigny-Lavallée, Jerry Kwok Hiu Fung, Bruno Roesner, Florent Serres
|
Copyright (c) 2024 Gabriel Moisan-Matte, Mathieu Sévigny-Lavallée, Jerry Kwok Hiu Fung, Bruno Roesner, Florent Serres
|
||||||
|
Copyright (c) 2025 Nouhaïla Aâter, Kendrick Chan Hing Wah, Philippe Côté, Edwin Stanley Lopez Andino, Ana Lucia Munteanu
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|
|
||||||
114
client/package-lock.json
generated
114
client/package-lock.json
generated
|
|
@ -14,7 +14,7 @@
|
||||||
"@fortawesome/fontawesome-svg-core": "^6.6.0",
|
"@fortawesome/fontawesome-svg-core": "^6.6.0",
|
||||||
"@fortawesome/free-solid-svg-icons": "^6.4.2",
|
"@fortawesome/free-solid-svg-icons": "^6.4.2",
|
||||||
"@fortawesome/react-fontawesome": "^0.2.0",
|
"@fortawesome/react-fontawesome": "^0.2.0",
|
||||||
"@mui/icons-material": "^6.1.0",
|
"@mui/icons-material": "^6.4.1",
|
||||||
"@mui/lab": "^5.0.0-alpha.153",
|
"@mui/lab": "^5.0.0-alpha.153",
|
||||||
"@mui/material": "^6.1.0",
|
"@mui/material": "^6.1.0",
|
||||||
"@types/uuid": "^9.0.7",
|
"@types/uuid": "^9.0.7",
|
||||||
|
|
@ -2093,15 +2093,15 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@emotion/serialize": {
|
"node_modules/@emotion/serialize": {
|
||||||
"version": "1.3.2",
|
"version": "1.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz",
|
||||||
"integrity": "sha512-grVnMvVPK9yUVE6rkKfAJlYZgo0cu3l9iMC77V7DW6E1DUIrU68pSEXRmFZFOFB1QFo57TncmOcvcbMDWsL4yA==",
|
"integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@emotion/hash": "^0.9.2",
|
"@emotion/hash": "^0.9.2",
|
||||||
"@emotion/memoize": "^0.9.0",
|
"@emotion/memoize": "^0.9.0",
|
||||||
"@emotion/unitless": "^0.10.0",
|
"@emotion/unitless": "^0.10.0",
|
||||||
"@emotion/utils": "^1.4.1",
|
"@emotion/utils": "^1.4.2",
|
||||||
"csstype": "^3.0.2"
|
"csstype": "^3.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -3355,9 +3355,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@mui/core-downloads-tracker": {
|
"node_modules/@mui/core-downloads-tracker": {
|
||||||
"version": "6.1.6",
|
"version": "6.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.1.6.tgz",
|
"resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.4.1.tgz",
|
||||||
"integrity": "sha512-nz1SlR9TdBYYPz4qKoNasMPRiGb4PaIHFkzLzhju0YVYS5QSuFF2+n7CsiHMIDcHv3piPu/xDWI53ruhOqvZwQ==",
|
"integrity": "sha512-SfDLWMV5b5oXgDf3NTa2hCTPC1d2defhDH2WgFKmAiejC4mSfXYbyi+AFCLzpizauXhgBm8OaZy9BHKnrSpahQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"funding": {
|
"funding": {
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
|
|
@ -3365,9 +3365,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@mui/icons-material": {
|
"node_modules/@mui/icons-material": {
|
||||||
"version": "6.1.6",
|
"version": "6.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-6.1.6.tgz",
|
"resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-6.4.1.tgz",
|
||||||
"integrity": "sha512-5r9urIL2lxXb/sPN3LFfFYEibsXJUb986HhhIeu1gOcte460pwdSiEhBSxkAuyT8Dj7jvu9MjqSBmSumQELo8A==",
|
"integrity": "sha512-wsxFcUTQxt4s+7Bg4GgobqRjyaHLmZGNOs+HJpbwrwmLbT6mhIJxhpqsKzzWq9aDY8xIe7HCjhpH7XI5UD6teA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.26.0"
|
"@babel/runtime": "^7.26.0"
|
||||||
|
|
@ -3380,7 +3380,7 @@
|
||||||
"url": "https://opencollective.com/mui-org"
|
"url": "https://opencollective.com/mui-org"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@mui/material": "^6.1.6",
|
"@mui/material": "^6.4.1",
|
||||||
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||||
"react": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
"react": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||||
},
|
},
|
||||||
|
|
@ -3464,22 +3464,22 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@mui/material": {
|
"node_modules/@mui/material": {
|
||||||
"version": "6.1.6",
|
"version": "6.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/@mui/material/-/material-6.1.6.tgz",
|
"resolved": "https://registry.npmjs.org/@mui/material/-/material-6.4.1.tgz",
|
||||||
"integrity": "sha512-1yvejiQ/601l5AK3uIdUlAVElyCxoqKnl7QA+2oFB/2qYPWfRwDgavW/MoywS5Y2gZEslcJKhe0s2F3IthgFgw==",
|
"integrity": "sha512-MFBfia6UiKxyoLeGkAh8M15bkeDmfnsUTMRJd/vTQue6YQ8AQ6lw9HqDthyYghzDEWIvZO/lQQzLrZE8XwNJLA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.26.0",
|
"@babel/runtime": "^7.26.0",
|
||||||
"@mui/core-downloads-tracker": "^6.1.6",
|
"@mui/core-downloads-tracker": "^6.4.1",
|
||||||
"@mui/system": "^6.1.6",
|
"@mui/system": "^6.4.1",
|
||||||
"@mui/types": "^7.2.19",
|
"@mui/types": "^7.2.21",
|
||||||
"@mui/utils": "^6.1.6",
|
"@mui/utils": "^6.4.1",
|
||||||
"@popperjs/core": "^2.11.8",
|
"@popperjs/core": "^2.11.8",
|
||||||
"@types/react-transition-group": "^4.4.11",
|
"@types/react-transition-group": "^4.4.12",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"csstype": "^3.1.3",
|
"csstype": "^3.1.3",
|
||||||
"prop-types": "^15.8.1",
|
"prop-types": "^15.8.1",
|
||||||
"react-is": "^18.3.1",
|
"react-is": "^19.0.0",
|
||||||
"react-transition-group": "^4.4.5"
|
"react-transition-group": "^4.4.5"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
|
|
@ -3492,7 +3492,7 @@
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@emotion/react": "^11.5.0",
|
"@emotion/react": "^11.5.0",
|
||||||
"@emotion/styled": "^11.3.0",
|
"@emotion/styled": "^11.3.0",
|
||||||
"@mui/material-pigment-css": "^6.1.6",
|
"@mui/material-pigment-css": "^6.4.1",
|
||||||
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||||
"react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
"react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||||
"react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
"react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||||
|
|
@ -3513,13 +3513,13 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@mui/material/node_modules/@mui/private-theming": {
|
"node_modules/@mui/material/node_modules/@mui/private-theming": {
|
||||||
"version": "6.1.6",
|
"version": "6.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-6.1.6.tgz",
|
"resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-6.4.1.tgz",
|
||||||
"integrity": "sha512-ioAiFckaD/fJSnTrUMWgjl9HYBWt7ixCh7zZw7gDZ+Tae7NuprNV6QJK95EidDT7K0GetR2rU3kAeIR61Myttw==",
|
"integrity": "sha512-DcT7mwK89owwgcEuiE7w458te4CIjHbYWW6Kn6PiR6eLtxBsoBYphA968uqsQAOBQDpbYxvkuFLwhgk4bxoN/Q==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.26.0",
|
"@babel/runtime": "^7.26.0",
|
||||||
"@mui/utils": "^6.1.6",
|
"@mui/utils": "^6.4.1",
|
||||||
"prop-types": "^15.8.1"
|
"prop-types": "^15.8.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
|
|
@ -3540,14 +3540,14 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@mui/material/node_modules/@mui/styled-engine": {
|
"node_modules/@mui/material/node_modules/@mui/styled-engine": {
|
||||||
"version": "6.1.6",
|
"version": "6.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-6.1.6.tgz",
|
"resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-6.4.0.tgz",
|
||||||
"integrity": "sha512-I+yS1cSuSvHnZDBO7e7VHxTWpj+R7XlSZvTC4lS/OIbUNJOMMSd3UDP6V2sfwzAdmdDNBi7NGCRv2SZ6O9hGDA==",
|
"integrity": "sha512-ek/ZrDujrger12P6o4luQIfRd2IziH7jQod2WMbLqGE03Iy0zUwYmckRTVhRQTLPNccpD8KXGcALJF+uaUQlbg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.26.0",
|
"@babel/runtime": "^7.26.0",
|
||||||
"@emotion/cache": "^11.13.1",
|
"@emotion/cache": "^11.13.5",
|
||||||
"@emotion/serialize": "^1.3.2",
|
"@emotion/serialize": "^1.3.3",
|
||||||
"@emotion/sheet": "^1.4.0",
|
"@emotion/sheet": "^1.4.0",
|
||||||
"csstype": "^3.1.3",
|
"csstype": "^3.1.3",
|
||||||
"prop-types": "^15.8.1"
|
"prop-types": "^15.8.1"
|
||||||
|
|
@ -3574,16 +3574,16 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@mui/material/node_modules/@mui/system": {
|
"node_modules/@mui/material/node_modules/@mui/system": {
|
||||||
"version": "6.1.6",
|
"version": "6.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/@mui/system/-/system-6.1.6.tgz",
|
"resolved": "https://registry.npmjs.org/@mui/system/-/system-6.4.1.tgz",
|
||||||
"integrity": "sha512-qOf1VUE9wK8syiB0BBCp82oNBAVPYdj4Trh+G1s+L+ImYiKlubWhhqlnvWt3xqMevR+D2h1CXzA1vhX2FvA+VQ==",
|
"integrity": "sha512-rgQzgcsHCTtzF9MZ+sL0tOhf2ZBLazpjrujClcb4Siju5lTrK0xX4PsiropActzCemNfM+mOu+0jezAVnfRK8g==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.26.0",
|
"@babel/runtime": "^7.26.0",
|
||||||
"@mui/private-theming": "^6.1.6",
|
"@mui/private-theming": "^6.4.1",
|
||||||
"@mui/styled-engine": "^6.1.6",
|
"@mui/styled-engine": "^6.4.0",
|
||||||
"@mui/types": "^7.2.19",
|
"@mui/types": "^7.2.21",
|
||||||
"@mui/utils": "^6.1.6",
|
"@mui/utils": "^6.4.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"csstype": "^3.1.3",
|
"csstype": "^3.1.3",
|
||||||
"prop-types": "^15.8.1"
|
"prop-types": "^15.8.1"
|
||||||
|
|
@ -3614,17 +3614,17 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@mui/material/node_modules/@mui/utils": {
|
"node_modules/@mui/material/node_modules/@mui/utils": {
|
||||||
"version": "6.1.6",
|
"version": "6.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.1.6.tgz",
|
"resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.4.1.tgz",
|
||||||
"integrity": "sha512-sBS6D9mJECtELASLM+18WUcXF6RH3zNxBRFeyCRg8wad6NbyNrdxLuwK+Ikvc38sTZwBzAz691HmSofLqHd9sQ==",
|
"integrity": "sha512-iQUDUeYh87SvR4lVojaRaYnQix8BbRV51MxaV6MBmqthecQoxwSbS5e2wnbDJUeFxY2ppV505CiqPLtd0OWkqw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.26.0",
|
"@babel/runtime": "^7.26.0",
|
||||||
"@mui/types": "^7.2.19",
|
"@mui/types": "^7.2.21",
|
||||||
"@types/prop-types": "^15.7.13",
|
"@types/prop-types": "^15.7.14",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"prop-types": "^15.8.1",
|
"prop-types": "^15.8.1",
|
||||||
"react-is": "^18.3.1"
|
"react-is": "^19.0.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=14.0.0"
|
"node": ">=14.0.0"
|
||||||
|
|
@ -3643,6 +3643,12 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@mui/material/node_modules/react-is": {
|
||||||
|
"version": "19.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-19.0.0.tgz",
|
||||||
|
"integrity": "sha512-H91OHcwjZsbq3ClIDHMzBShc1rotbfACdWENsmEf0IFvZ3FgGPtdHMcsv45bQ1hAbgdfiA8SnxTKfDS+x/8m2g==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@mui/private-theming": {
|
"node_modules/@mui/private-theming": {
|
||||||
"version": "5.16.14",
|
"version": "5.16.14",
|
||||||
"resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.16.14.tgz",
|
"resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.16.14.tgz",
|
||||||
|
|
@ -3743,9 +3749,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@mui/types": {
|
"node_modules/@mui/types": {
|
||||||
"version": "7.2.19",
|
"version": "7.2.21",
|
||||||
"resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.19.tgz",
|
"resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.21.tgz",
|
||||||
"integrity": "sha512-6XpZEM/Q3epK9RN8ENoXuygnqUQxE+siN/6rGRi2iwJPgBUR25mphYQ9ZI87plGh58YoZ5pp40bFvKYOCDJ3tA==",
|
"integrity": "sha512-6HstngiUxNqLU+/DPqlUJDIPbzUBxIVHb1MmXP0eTWDIROiCR2viugXpEif0PPe2mLqqakPzzRClWAnK+8UJww==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||||
|
|
@ -4682,9 +4688,9 @@
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@types/prop-types": {
|
"node_modules/@types/prop-types": {
|
||||||
"version": "15.7.13",
|
"version": "15.7.14",
|
||||||
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz",
|
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz",
|
||||||
"integrity": "sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==",
|
"integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@types/react": {
|
"node_modules/@types/react": {
|
||||||
|
|
@ -4718,11 +4724,11 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/react-transition-group": {
|
"node_modules/@types/react-transition-group": {
|
||||||
"version": "4.4.11",
|
"version": "4.4.12",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.11.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz",
|
||||||
"integrity": "sha512-RM05tAniPZ5DZPzzNFP+DmrcOdD0efDUxMy3145oljWSl3x9ZV5vhme98gTxFrj2lhXvmGNnUiuDyJgY9IKkNA==",
|
"integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"peerDependencies": {
|
||||||
"@types/react": "*"
|
"@types/react": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@
|
||||||
"@fortawesome/fontawesome-svg-core": "^6.6.0",
|
"@fortawesome/fontawesome-svg-core": "^6.6.0",
|
||||||
"@fortawesome/free-solid-svg-icons": "^6.4.2",
|
"@fortawesome/free-solid-svg-icons": "^6.4.2",
|
||||||
"@fortawesome/react-fontawesome": "^0.2.0",
|
"@fortawesome/react-fontawesome": "^0.2.0",
|
||||||
"@mui/icons-material": "^6.1.0",
|
"@mui/icons-material": "^6.4.1",
|
||||||
"@mui/lab": "^5.0.0-alpha.153",
|
"@mui/lab": "^5.0.0-alpha.153",
|
||||||
"@mui/material": "^6.1.0",
|
"@mui/material": "^6.1.0",
|
||||||
"@types/uuid": "^9.0.7",
|
"@types/uuid": "^9.0.7",
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,95 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { render, screen, fireEvent } from '@testing-library/react';
|
||||||
|
import '@testing-library/jest-dom';
|
||||||
|
import LiveResults from 'src/components/LiveResults/LiveResults';
|
||||||
|
import { QuestionType } from 'src/Types/QuestionType';
|
||||||
|
import { StudentType } from 'src/Types/StudentType';
|
||||||
|
import { BaseQuestion, parse } from 'gift-pegjs';
|
||||||
|
|
||||||
|
const mockGiftQuestions = parse(
|
||||||
|
`::Sample Question 1:: Sample Question 1 {=Answer 1 ~Answer 2}
|
||||||
|
|
||||||
|
::Sample Question 2:: Sample Question 2 {T}`);
|
||||||
|
|
||||||
|
const mockQuestions: QuestionType[] = mockGiftQuestions.map((question, index) => {
|
||||||
|
if (question.type !== "Category")
|
||||||
|
question.id = (index + 1).toString();
|
||||||
|
const newMockQuestion = question;
|
||||||
|
return {question : newMockQuestion as BaseQuestion};
|
||||||
|
});
|
||||||
|
|
||||||
|
const mockStudents: StudentType[] = [
|
||||||
|
{ id: "1", name: 'Student 1', answers: [{ idQuestion: 1, answer: 'Answer 1', isCorrect: true }] },
|
||||||
|
{ id: "2", name: 'Student 2', answers: [{ idQuestion: 2, answer: 'Answer 2', isCorrect: false }] },
|
||||||
|
];
|
||||||
|
|
||||||
|
const mockShowSelectedQuestion = jest.fn();
|
||||||
|
|
||||||
|
describe('LiveResults', () => {
|
||||||
|
test('renders LiveResults component', () => {
|
||||||
|
render(
|
||||||
|
<LiveResults
|
||||||
|
socket={null}
|
||||||
|
questions={mockQuestions}
|
||||||
|
showSelectedQuestion={mockShowSelectedQuestion}
|
||||||
|
quizMode="teacher"
|
||||||
|
students={mockStudents}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByText('Résultats du quiz')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('toggles show usernames switch', () => {
|
||||||
|
render(
|
||||||
|
<LiveResults
|
||||||
|
socket={null}
|
||||||
|
questions={mockQuestions}
|
||||||
|
showSelectedQuestion={mockShowSelectedQuestion}
|
||||||
|
quizMode="teacher"
|
||||||
|
students={mockStudents}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const switchElement = screen.getByLabelText('Afficher les noms');
|
||||||
|
expect(switchElement).toBeInTheDocument();
|
||||||
|
|
||||||
|
fireEvent.click(switchElement);
|
||||||
|
expect(switchElement).toBeChecked();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('toggles show correct answers switch', () => {
|
||||||
|
render(
|
||||||
|
<LiveResults
|
||||||
|
socket={null}
|
||||||
|
questions={mockQuestions}
|
||||||
|
showSelectedQuestion={mockShowSelectedQuestion}
|
||||||
|
quizMode="teacher"
|
||||||
|
students={mockStudents}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const switchElement = screen.getByLabelText('Afficher les réponses');
|
||||||
|
expect(switchElement).toBeInTheDocument();
|
||||||
|
|
||||||
|
fireEvent.click(switchElement);
|
||||||
|
expect(switchElement).toBeChecked();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('calls showSelectedQuestion when a table cell is clicked', () => {
|
||||||
|
render(
|
||||||
|
<LiveResults
|
||||||
|
socket={null}
|
||||||
|
questions={mockQuestions}
|
||||||
|
showSelectedQuestion={mockShowSelectedQuestion}
|
||||||
|
quizMode="teacher"
|
||||||
|
students={mockStudents}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const tableCell = screen.getByText('Q1');
|
||||||
|
fireEvent.click(tableCell);
|
||||||
|
|
||||||
|
expect(mockShowSelectedQuestion).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,110 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { render, screen, fireEvent } from '@testing-library/react';
|
||||||
|
import '@testing-library/jest-dom';
|
||||||
|
import { StudentType } from 'src/Types/StudentType';
|
||||||
|
import LiveResultsTable from 'src/components/LiveResults/LiveResultsTable/LiveResultsTable';
|
||||||
|
import { QuestionType } from 'src/Types/QuestionType';
|
||||||
|
import { BaseQuestion, parse } from 'gift-pegjs';
|
||||||
|
|
||||||
|
const mockGiftQuestions = parse(
|
||||||
|
`::Sample Question 1:: Sample Question 1 {=Answer 1 ~Answer 2}
|
||||||
|
|
||||||
|
::Sample Question 2:: Sample Question 2 {T}`);
|
||||||
|
|
||||||
|
const mockQuestions: QuestionType[] = mockGiftQuestions.map((question, index) => {
|
||||||
|
if (question.type !== "Category")
|
||||||
|
question.id = (index + 1).toString();
|
||||||
|
const newMockQuestion = question;
|
||||||
|
return {question : newMockQuestion as BaseQuestion};
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
const mockStudents: StudentType[] = [
|
||||||
|
{ id: "1", name: 'Student 1', answers: [{ idQuestion: 1, answer: 'Answer 1', isCorrect: true }] },
|
||||||
|
{ id: "2", name: 'Student 2', answers: [{ idQuestion: 2, answer: 'Answer 2', isCorrect: false }] },
|
||||||
|
];
|
||||||
|
|
||||||
|
const mockShowSelectedQuestion = jest.fn();
|
||||||
|
|
||||||
|
describe('LiveResultsTable', () => {
|
||||||
|
test('renders LiveResultsTable component', () => {
|
||||||
|
render(
|
||||||
|
<LiveResultsTable
|
||||||
|
questions={mockQuestions}
|
||||||
|
students={mockStudents}
|
||||||
|
showCorrectAnswers={false}
|
||||||
|
showSelectedQuestion={mockShowSelectedQuestion}
|
||||||
|
showUsernames={true}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByText('Student 1')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('Student 2')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('displays correct and incorrect answers', () => {
|
||||||
|
render(
|
||||||
|
<LiveResultsTable
|
||||||
|
questions={mockQuestions}
|
||||||
|
students={mockStudents}
|
||||||
|
showCorrectAnswers={true}
|
||||||
|
showSelectedQuestion={mockShowSelectedQuestion}
|
||||||
|
showUsernames={true}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByText('Answer 1')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('Answer 2')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('calls showSelectedQuestion when a table cell is clicked', () => {
|
||||||
|
render(
|
||||||
|
<LiveResultsTable
|
||||||
|
questions={mockQuestions}
|
||||||
|
students={mockStudents}
|
||||||
|
showCorrectAnswers={true}
|
||||||
|
showSelectedQuestion={mockShowSelectedQuestion}
|
||||||
|
showUsernames={true}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const tableCell = screen.getByText('Q1');
|
||||||
|
fireEvent.click(tableCell);
|
||||||
|
|
||||||
|
expect(mockShowSelectedQuestion).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('calculates and displays student grades', () => {
|
||||||
|
render(
|
||||||
|
<LiveResultsTable
|
||||||
|
questions={mockQuestions}
|
||||||
|
students={mockStudents}
|
||||||
|
showCorrectAnswers={true}
|
||||||
|
showSelectedQuestion={mockShowSelectedQuestion}
|
||||||
|
showUsernames={true}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
//50% because only one of the two questions have been answered (getALLByText, because there are a value 50% for the %reussite de la question
|
||||||
|
// and a second one for the student grade)
|
||||||
|
const gradeElements = screen.getAllByText('50 %');
|
||||||
|
expect(gradeElements.length).toBe(2);
|
||||||
|
|
||||||
|
const gradeElements2 = screen.getAllByText('0 %');
|
||||||
|
expect(gradeElements2.length).toBe(2); });
|
||||||
|
|
||||||
|
test('calculates and displays class average', () => {
|
||||||
|
render(
|
||||||
|
<LiveResultsTable
|
||||||
|
questions={mockQuestions}
|
||||||
|
students={mockStudents}
|
||||||
|
showCorrectAnswers={true}
|
||||||
|
showSelectedQuestion={mockShowSelectedQuestion}
|
||||||
|
showUsernames={true}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
//1 good answer out of 4 possible good answers (the second question has not been answered)
|
||||||
|
expect(screen.getByText('25 %')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,95 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { render, screen } from '@testing-library/react';
|
||||||
|
import '@testing-library/jest-dom';
|
||||||
|
import { StudentType } from 'src/Types/StudentType';
|
||||||
|
import LiveResultsTableBody from 'src/components/LiveResults/LiveResultsTable/TableComponents/LiveResultsTableBody';
|
||||||
|
import { QuestionType } from 'src/Types/QuestionType';
|
||||||
|
import { BaseQuestion, parse } from 'gift-pegjs';
|
||||||
|
|
||||||
|
|
||||||
|
const mockGiftQuestions = parse(
|
||||||
|
`::Sample Question 1:: Sample Question 1 {=Answer 1 ~Answer 2}
|
||||||
|
|
||||||
|
::Sample Question 2:: Sample Question 2 {T}`);
|
||||||
|
|
||||||
|
const mockQuestions: QuestionType[] = mockGiftQuestions.map((question, index) => {
|
||||||
|
if (question.type !== "Category")
|
||||||
|
question.id = (index + 1).toString();
|
||||||
|
const newMockQuestion = question;
|
||||||
|
return {question : newMockQuestion as BaseQuestion};
|
||||||
|
});
|
||||||
|
|
||||||
|
const mockStudents: StudentType[] = [
|
||||||
|
{ id: "1", name: 'Student 1', answers: [{ idQuestion: 1, answer: 'Answer 1', isCorrect: true }] },
|
||||||
|
{ id: "2", name: 'Student 2', answers: [{ idQuestion: 2, answer: 'Answer 2', isCorrect: false }] },
|
||||||
|
];
|
||||||
|
|
||||||
|
const mockGetStudentGrade = jest.fn((student: StudentType) => {
|
||||||
|
const correctAnswers = student.answers.filter(answer => answer.isCorrect).length;
|
||||||
|
return (correctAnswers / mockQuestions.length) * 100;
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('LiveResultsTableBody', () => {
|
||||||
|
test('renders LiveResultsTableBody component', () => {
|
||||||
|
render(
|
||||||
|
<LiveResultsTableBody
|
||||||
|
maxQuestions={2}
|
||||||
|
students={mockStudents}
|
||||||
|
showUsernames={true}
|
||||||
|
showCorrectAnswers={false}
|
||||||
|
getStudentGrade={mockGetStudentGrade}
|
||||||
|
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByText('Student 1')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('Student 2')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('displays correct and incorrect answers', () => {
|
||||||
|
render(
|
||||||
|
<LiveResultsTableBody
|
||||||
|
maxQuestions={2}
|
||||||
|
students={mockStudents}
|
||||||
|
showUsernames={true}
|
||||||
|
showCorrectAnswers={true}
|
||||||
|
getStudentGrade={mockGetStudentGrade}
|
||||||
|
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByText('Answer 1')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('Answer 2')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('displays icons for correct and incorrect answers when showCorrectAnswers is false', () => {
|
||||||
|
render(
|
||||||
|
<LiveResultsTableBody
|
||||||
|
maxQuestions={2}
|
||||||
|
students={mockStudents}
|
||||||
|
showUsernames={true}
|
||||||
|
showCorrectAnswers={false}
|
||||||
|
getStudentGrade={mockGetStudentGrade}
|
||||||
|
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByLabelText('correct')).toBeInTheDocument();
|
||||||
|
expect(screen.getByLabelText('incorrect')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('hides usernames when showUsernames is false', () => {
|
||||||
|
render(
|
||||||
|
<LiveResultsTableBody
|
||||||
|
maxQuestions={2}
|
||||||
|
students={mockStudents}
|
||||||
|
showUsernames={false}
|
||||||
|
showCorrectAnswers={true}
|
||||||
|
getStudentGrade={mockGetStudentGrade}
|
||||||
|
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getAllByText('******').length).toBe(2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { render, screen } from '@testing-library/react';
|
||||||
|
import '@testing-library/jest-dom';
|
||||||
|
import { StudentType } from 'src/Types/StudentType';
|
||||||
|
import LiveResultsTableFooter from 'src/components/LiveResults/LiveResultsTable/TableComponents/LiveResultTableFooter';
|
||||||
|
|
||||||
|
|
||||||
|
const mockStudents: StudentType[] = [
|
||||||
|
{ id: "1", name: 'Student 1', answers: [{ idQuestion: 1, answer: 'Answer 1', isCorrect: true }] },
|
||||||
|
{ id: "2", name: 'Student 2', answers: [{ idQuestion: 2, answer: 'Answer 2', isCorrect: false }] },
|
||||||
|
];
|
||||||
|
|
||||||
|
const mockGetStudentGrade = jest.fn((student: StudentType) => {
|
||||||
|
const correctAnswers = student.answers.filter(answer => answer.isCorrect).length;
|
||||||
|
return (correctAnswers / 2) * 100; // Assuming there are 2 questions
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('LiveResultsTableFooter', () => {
|
||||||
|
test('renders LiveResultsTableFooter component', () => {
|
||||||
|
render(
|
||||||
|
<LiveResultsTableFooter
|
||||||
|
maxQuestions={2}
|
||||||
|
students={mockStudents}
|
||||||
|
getStudentGrade={mockGetStudentGrade}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByText('% réussite')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('calculates and displays correct answers per question', () => {
|
||||||
|
render(
|
||||||
|
<LiveResultsTableFooter
|
||||||
|
maxQuestions={2}
|
||||||
|
students={mockStudents}
|
||||||
|
getStudentGrade={mockGetStudentGrade}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByText('50 %')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('0 %')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('calculates and displays class average', () => {
|
||||||
|
render(
|
||||||
|
<LiveResultsTableFooter
|
||||||
|
maxQuestions={2}
|
||||||
|
students={mockStudents}
|
||||||
|
getStudentGrade={mockGetStudentGrade}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByText('50 %')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { render, screen, fireEvent } from '@testing-library/react';
|
||||||
|
import '@testing-library/jest-dom';
|
||||||
|
import LiveResultsTableHeader from 'src/components/LiveResults/LiveResultsTable/TableComponents/LiveResultsTableHeader';
|
||||||
|
|
||||||
|
|
||||||
|
const mockShowSelectedQuestion = jest.fn();
|
||||||
|
|
||||||
|
describe('LiveResultsTableHeader', () => {
|
||||||
|
test('renders LiveResultsTableHeader component', () => {
|
||||||
|
render(
|
||||||
|
<LiveResultsTableHeader
|
||||||
|
maxQuestions={5}
|
||||||
|
showSelectedQuestion={mockShowSelectedQuestion}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByText("Nom d'utilisateur")).toBeInTheDocument();
|
||||||
|
for (let i = 1; i <= 5; i++) {
|
||||||
|
expect(screen.getByText(`Q${i}`)).toBeInTheDocument();
|
||||||
|
}
|
||||||
|
expect(screen.getByText('% réussite')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('calls showSelectedQuestion when a question header is clicked', () => {
|
||||||
|
render(
|
||||||
|
<LiveResultsTableHeader
|
||||||
|
maxQuestions={5}
|
||||||
|
showSelectedQuestion={mockShowSelectedQuestion}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const questionHeader = screen.getByText('Q1');
|
||||||
|
fireEvent.click(questionHeader);
|
||||||
|
|
||||||
|
expect(mockShowSelectedQuestion).toHaveBeenCalledWith(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('renders the correct number of question headers', () => {
|
||||||
|
render(
|
||||||
|
<LiveResultsTableHeader
|
||||||
|
maxQuestions={3}
|
||||||
|
showSelectedQuestion={mockShowSelectedQuestion}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
for (let i = 1; i <= 3; i++) {
|
||||||
|
expect(screen.getByText(`Q${i}`)).toBeInTheDocument();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
163
client/src/__tests__/components/LiveResults/LiveResults.test.tsx
Normal file
163
client/src/__tests__/components/LiveResults/LiveResults.test.tsx
Normal file
|
|
@ -0,0 +1,163 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { render, screen, fireEvent } from '@testing-library/react';
|
||||||
|
import '@testing-library/jest-dom';
|
||||||
|
import LiveResults from 'src/components/LiveResults/LiveResults';
|
||||||
|
import { QuestionType } from 'src/Types/QuestionType';
|
||||||
|
import { StudentType } from 'src/Types/StudentType';
|
||||||
|
import { Socket } from 'socket.io-client';
|
||||||
|
import { BaseQuestion,parse } from 'gift-pegjs';
|
||||||
|
|
||||||
|
const mockSocket: Socket = {
|
||||||
|
on: jest.fn(),
|
||||||
|
off: jest.fn(),
|
||||||
|
emit: jest.fn(),
|
||||||
|
connect: jest.fn(),
|
||||||
|
disconnect: jest.fn(),
|
||||||
|
} as unknown as Socket;
|
||||||
|
|
||||||
|
const mockGiftQuestions = parse(
|
||||||
|
`::Sample Question 1:: Question stem
|
||||||
|
{
|
||||||
|
=Choice 1
|
||||||
|
~Choice 2
|
||||||
|
}`);
|
||||||
|
|
||||||
|
const mockQuestions: QuestionType[] = mockGiftQuestions.map((question, index) => {
|
||||||
|
if (question.type !== "Category")
|
||||||
|
question.id = (index + 1).toString();
|
||||||
|
const newMockQuestion = question;
|
||||||
|
return {question : newMockQuestion as BaseQuestion};
|
||||||
|
});
|
||||||
|
|
||||||
|
const mockStudents: StudentType[] = [
|
||||||
|
{ id: '1', name: 'Student 1', answers: [{ idQuestion: 1, answer: 'Choice 1', isCorrect: true }] },
|
||||||
|
{ id: '2', name: 'Student 2', answers: [{ idQuestion: 1, answer: 'Choice 2', isCorrect: false }] },
|
||||||
|
];
|
||||||
|
|
||||||
|
describe('LiveResults', () => {
|
||||||
|
test('renders the component with questions and students', () => {
|
||||||
|
render(
|
||||||
|
<LiveResults
|
||||||
|
socket={mockSocket}
|
||||||
|
questions={mockQuestions}
|
||||||
|
showSelectedQuestion={jest.fn()}
|
||||||
|
quizMode="teacher"
|
||||||
|
students={mockStudents}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
expect(screen.getByText(`Q${1}`)).toBeInTheDocument();
|
||||||
|
|
||||||
|
// Toggle the display of usernames
|
||||||
|
const toggleUsernamesSwitch = screen.getByLabelText('Afficher les noms');
|
||||||
|
|
||||||
|
// Toggle the display of usernames back
|
||||||
|
fireEvent.click(toggleUsernamesSwitch);
|
||||||
|
|
||||||
|
// Check if the component renders the students
|
||||||
|
mockStudents.forEach((student) => {
|
||||||
|
expect(screen.getByText(student.name)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('toggles the display of usernames', () => {
|
||||||
|
render(
|
||||||
|
<LiveResults
|
||||||
|
socket={mockSocket}
|
||||||
|
questions={mockQuestions}
|
||||||
|
showSelectedQuestion={jest.fn()}
|
||||||
|
quizMode="teacher"
|
||||||
|
students={mockStudents}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
// Toggle the display of usernames
|
||||||
|
const toggleUsernamesSwitch = screen.getByLabelText('Afficher les noms');
|
||||||
|
|
||||||
|
// Toggle the display of usernames back
|
||||||
|
fireEvent.click(toggleUsernamesSwitch);
|
||||||
|
|
||||||
|
// Check if the usernames are shown again
|
||||||
|
mockStudents.forEach((student) => {
|
||||||
|
expect(screen.getByText(student.name)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
test('calculates and displays the correct student grades', () => {
|
||||||
|
render(
|
||||||
|
<LiveResults
|
||||||
|
socket={mockSocket}
|
||||||
|
questions={mockQuestions}
|
||||||
|
showSelectedQuestion={jest.fn()}
|
||||||
|
quizMode="teacher"
|
||||||
|
students={mockStudents}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
// Toggle the display of usernames
|
||||||
|
const toggleUsernamesSwitch = screen.getByLabelText('Afficher les noms');
|
||||||
|
|
||||||
|
// Toggle the display of usernames back
|
||||||
|
fireEvent.click(toggleUsernamesSwitch);
|
||||||
|
|
||||||
|
// Check if the student grades are calculated and displayed correctly
|
||||||
|
mockStudents.forEach((student) => {
|
||||||
|
const grade = student.answers.filter(answer => answer.isCorrect).length / mockQuestions.length * 100;
|
||||||
|
expect(screen.getByText(`${grade.toFixed()} %`)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('calculates and displays the class average', () => {
|
||||||
|
render(
|
||||||
|
<LiveResults
|
||||||
|
socket={mockSocket}
|
||||||
|
questions={mockQuestions}
|
||||||
|
showSelectedQuestion={jest.fn()}
|
||||||
|
quizMode="teacher"
|
||||||
|
students={mockStudents}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
// Toggle the display of usernames
|
||||||
|
const toggleUsernamesSwitch = screen.getByLabelText('Afficher les noms');
|
||||||
|
|
||||||
|
// Toggle the display of usernames back
|
||||||
|
fireEvent.click(toggleUsernamesSwitch);
|
||||||
|
|
||||||
|
// Calculate the class average
|
||||||
|
const totalGrades = mockStudents.reduce((total, student) => {
|
||||||
|
return total + (student.answers.filter(answer => answer.isCorrect).length / mockQuestions.length * 100);
|
||||||
|
}, 0);
|
||||||
|
const classAverage = totalGrades / mockStudents.length;
|
||||||
|
|
||||||
|
// Check if the class average is displayed correctly
|
||||||
|
const classAverageElements = screen.getAllByText(`${classAverage.toFixed()} %`);
|
||||||
|
const classAverageElement = classAverageElements.find((element) => {
|
||||||
|
return element.closest('td')?.classList.contains('MuiTableCell-footer');
|
||||||
|
});
|
||||||
|
expect(classAverageElement).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('displays the correct answers per question', () => {
|
||||||
|
render(
|
||||||
|
<LiveResults
|
||||||
|
socket={mockSocket}
|
||||||
|
questions={mockQuestions}
|
||||||
|
showSelectedQuestion={jest.fn()}
|
||||||
|
quizMode="teacher"
|
||||||
|
students={mockStudents}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check if the correct answers per question are displayed correctly
|
||||||
|
mockQuestions.forEach((_, index) => {
|
||||||
|
const correctAnswers = mockStudents.filter(student => student.answers.some(answer => answer.idQuestion === index + 1 && answer.isCorrect)).length;
|
||||||
|
const correctAnswersPercentage = (correctAnswers / mockStudents.length) * 100;
|
||||||
|
const correctAnswersElements = screen.getAllByText(`${correctAnswersPercentage.toFixed()} %`);
|
||||||
|
const correctAnswersElement = correctAnswersElements.find((element) => {
|
||||||
|
return element.closest('td')?.classList.contains('MuiTableCell-root');
|
||||||
|
});
|
||||||
|
expect(correctAnswersElement).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
253
client/src/__tests__/pages/ManageRoom/ManageRoom.test.tsx
Normal file
253
client/src/__tests__/pages/ManageRoom/ManageRoom.test.tsx
Normal file
|
|
@ -0,0 +1,253 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { render, screen, fireEvent, waitFor, act } from '@testing-library/react';
|
||||||
|
import '@testing-library/jest-dom';
|
||||||
|
import { MemoryRouter, useNavigate, useParams } from 'react-router-dom';
|
||||||
|
import ManageRoom from 'src/pages/Teacher/ManageRoom/ManageRoom';
|
||||||
|
import { StudentType } from 'src/Types/StudentType';
|
||||||
|
import { QuizType } from 'src/Types/QuizType';
|
||||||
|
import webSocketService, { AnswerReceptionFromBackendType } from 'src/services/WebsocketService';
|
||||||
|
import ApiService from 'src/services/ApiService';
|
||||||
|
import { Socket } from 'socket.io-client';
|
||||||
|
|
||||||
|
jest.mock('src/services/WebsocketService');
|
||||||
|
jest.mock('src/services/ApiService');
|
||||||
|
jest.mock('react-router-dom', () => ({
|
||||||
|
...jest.requireActual('react-router-dom'),
|
||||||
|
useNavigate: jest.fn(),
|
||||||
|
useParams: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const mockSocket = {
|
||||||
|
on: jest.fn(),
|
||||||
|
off: jest.fn(),
|
||||||
|
emit: jest.fn(),
|
||||||
|
connect: jest.fn(),
|
||||||
|
disconnect: jest.fn(),
|
||||||
|
} as unknown as Socket;
|
||||||
|
|
||||||
|
const mockQuiz: QuizType = {
|
||||||
|
_id: 'test-quiz-id',
|
||||||
|
title: 'Test Quiz',
|
||||||
|
content: ['::Q1:: Question 1 { =Answer1 ~Answer2 }', '::Q2:: Question 2 { =Answer1 ~Answer2 }'],
|
||||||
|
folderId: 'folder-id',
|
||||||
|
folderName: 'folder-name',
|
||||||
|
userId: 'user-id',
|
||||||
|
created_at: new Date(),
|
||||||
|
updated_at: new Date()
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockStudents: StudentType[] = [
|
||||||
|
{ id: '1', name: 'Student 1', answers: [] },
|
||||||
|
{ id: '2', name: 'Student 2', answers: [] },
|
||||||
|
];
|
||||||
|
|
||||||
|
const mockAnswerData: AnswerReceptionFromBackendType = {
|
||||||
|
answer: 'Answer1',
|
||||||
|
idQuestion: 1,
|
||||||
|
idUser: '1',
|
||||||
|
username: 'Student 1',
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('ManageRoom', () => {
|
||||||
|
const navigate = jest.fn();
|
||||||
|
const useParamsMock = useParams as jest.Mock;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
(useNavigate as jest.Mock).mockReturnValue(navigate);
|
||||||
|
useParamsMock.mockReturnValue({ id: 'test-quiz-id' });
|
||||||
|
(ApiService.getQuiz as jest.Mock).mockResolvedValue(mockQuiz);
|
||||||
|
(webSocketService.connect as jest.Mock).mockReturnValue(mockSocket);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('prepares to launch quiz and fetches quiz data', async () => {
|
||||||
|
await act(async () => {
|
||||||
|
render(
|
||||||
|
<MemoryRouter>
|
||||||
|
<ManageRoom />
|
||||||
|
</MemoryRouter>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
const createSuccessCallback = (mockSocket.on as jest.Mock).mock.calls.find(call => call[0] === 'create-success')[1];
|
||||||
|
createSuccessCallback('test-room-name');
|
||||||
|
});
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(ApiService.getQuiz).toHaveBeenCalledWith('test-quiz-id');
|
||||||
|
});
|
||||||
|
|
||||||
|
const launchButton = screen.getByText('Lancer');
|
||||||
|
fireEvent.click(launchButton);
|
||||||
|
|
||||||
|
const rythmeButton = screen.getByText('Rythme du professeur');
|
||||||
|
fireEvent.click(rythmeButton);
|
||||||
|
|
||||||
|
const secondLaunchButton = screen.getAllByText('Lancer');
|
||||||
|
fireEvent.click(secondLaunchButton[1]);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByText('Test Quiz')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('Salle: test-room-name')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('0/60')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('Question 1/2')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('handles create-success event', async () => {
|
||||||
|
await act(async () => {
|
||||||
|
render(
|
||||||
|
<MemoryRouter>
|
||||||
|
<ManageRoom />
|
||||||
|
</MemoryRouter>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
const createSuccessCallback = (mockSocket.on as jest.Mock).mock.calls.find(call => call[0] === 'create-success')[1];
|
||||||
|
createSuccessCallback('test-room-name');
|
||||||
|
});
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByText('Salle: test-room-name')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('handles user-joined event', async () => {
|
||||||
|
await act(async () => {
|
||||||
|
render(
|
||||||
|
<MemoryRouter>
|
||||||
|
<ManageRoom />
|
||||||
|
</MemoryRouter>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
const createSuccessCallback = (mockSocket.on as jest.Mock).mock.calls.find(call => call[0] === 'create-success')[1];
|
||||||
|
createSuccessCallback('test-room-name');
|
||||||
|
});
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
const userJoinedCallback = (mockSocket.on as jest.Mock).mock.calls.find(call => call[0] === 'user-joined')[1];
|
||||||
|
userJoinedCallback(mockStudents[0]);
|
||||||
|
});
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByText('Student 1')).toBeInTheDocument();
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
const launchButton = screen.getByText('Lancer');
|
||||||
|
fireEvent.click(launchButton);
|
||||||
|
|
||||||
|
const rythmeButton = screen.getByText('Rythme du professeur');
|
||||||
|
fireEvent.click(rythmeButton);
|
||||||
|
|
||||||
|
const secondLaunchButton = screen.getAllByText('Lancer');
|
||||||
|
fireEvent.click(secondLaunchButton[1]);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByText('1/60')).toBeInTheDocument();
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('handles submit-answer-room event', async () => {
|
||||||
|
const consoleSpy = jest.spyOn(console, 'log');
|
||||||
|
await act(async () => {
|
||||||
|
render(
|
||||||
|
<MemoryRouter>
|
||||||
|
<ManageRoom />
|
||||||
|
</MemoryRouter>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
const createSuccessCallback = (mockSocket.on as jest.Mock).mock.calls.find(call => call[0] === 'create-success')[1];
|
||||||
|
createSuccessCallback('test-room-name');
|
||||||
|
});
|
||||||
|
|
||||||
|
const launchButton = screen.getByText('Lancer');
|
||||||
|
fireEvent.click(launchButton);
|
||||||
|
|
||||||
|
const rythmeButton = screen.getByText('Rythme du professeur');
|
||||||
|
fireEvent.click(rythmeButton);
|
||||||
|
|
||||||
|
const secondLaunchButton = screen.getAllByText('Lancer');
|
||||||
|
fireEvent.click(secondLaunchButton[1]);
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
const userJoinedCallback = (mockSocket.on as jest.Mock).mock.calls.find(call => call[0] === 'user-joined')[1];
|
||||||
|
userJoinedCallback(mockStudents[0]);
|
||||||
|
});
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
const submitAnswerCallback = (mockSocket.on as jest.Mock).mock.calls.find(call => call[0] === 'submit-answer-room')[1];
|
||||||
|
submitAnswerCallback(mockAnswerData);
|
||||||
|
});
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(consoleSpy).toHaveBeenCalledWith('Received answer from Student 1 for question 1: Answer1');
|
||||||
|
});
|
||||||
|
|
||||||
|
consoleSpy.mockRestore();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('handles next question', async () => {
|
||||||
|
await act(async () => {
|
||||||
|
render(
|
||||||
|
<MemoryRouter>
|
||||||
|
<ManageRoom />
|
||||||
|
</MemoryRouter>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
const createSuccessCallback = (mockSocket.on as jest.Mock).mock.calls.find(call => call[0] === 'create-success')[1];
|
||||||
|
createSuccessCallback('test-room-name');
|
||||||
|
});
|
||||||
|
|
||||||
|
const launchButton = screen.getByText('Lancer');
|
||||||
|
fireEvent.click(launchButton);
|
||||||
|
|
||||||
|
const rythmeButton = screen.getByText('Rythme du professeur');
|
||||||
|
fireEvent.click(rythmeButton);
|
||||||
|
|
||||||
|
const secondLaunchButton = screen.getAllByText('Lancer');
|
||||||
|
fireEvent.click(secondLaunchButton[1]);
|
||||||
|
|
||||||
|
const nextQuestionButton = screen.getByText('Prochaine question');
|
||||||
|
fireEvent.click(nextQuestionButton);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByText('Question 2/2')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('handles disconnect', async () => {
|
||||||
|
await act(async () => {
|
||||||
|
render(
|
||||||
|
<MemoryRouter>
|
||||||
|
<ManageRoom />
|
||||||
|
</MemoryRouter>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
const createSuccessCallback = (mockSocket.on as jest.Mock).mock.calls.find(call => call[0] === 'create-success')[1];
|
||||||
|
createSuccessCallback('test-room-name');
|
||||||
|
});
|
||||||
|
|
||||||
|
const disconnectButton = screen.getByText('Quitter');
|
||||||
|
fireEvent.click(disconnectButton);
|
||||||
|
|
||||||
|
const confirmButton = screen.getAllByText('Confirmer');
|
||||||
|
fireEvent.click(confirmButton[1]);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(webSocketService.disconnect).toHaveBeenCalled();
|
||||||
|
expect(navigate).toHaveBeenCalledWith('/teacher/dashboard');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -53,7 +53,7 @@ describe('TeacherModeQuiz', () => {
|
||||||
fireEvent.click(screen.getByText('Répondre'));
|
fireEvent.click(screen.getByText('Répondre'));
|
||||||
});
|
});
|
||||||
expect(mockSubmitAnswer).toHaveBeenCalledWith('Option A', 1);
|
expect(mockSubmitAnswer).toHaveBeenCalledWith('Option A', 1);
|
||||||
expect(screen.getByText('Votre réponse est "Option A".')).toBeInTheDocument();
|
expect(screen.getByText('Votre réponse est:')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('handles disconnect button click', () => {
|
test('handles disconnect button click', () => {
|
||||||
|
|
|
||||||
|
|
@ -21,11 +21,11 @@ const GiftCheatSheet: React.FC = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const QuestionVraiFaux = "2+2 \\= 4 ? {T}\n// Utilisez les valeurs {T}, {F}, {TRUE} \net {FALSE}.";
|
const QuestionVraiFaux = "::Exemple de question vrai/faux:: \n 2+2 \\= 4 ? {T} //Utilisez les valeurs {T}, {F}, {TRUE} et {FALSE}.";
|
||||||
const QuestionChoixMul = "Quelle ville est la capitale du Canada? {\n~ Toronto\n~ Montréal\n= Ottawa #Bonne réponse!\n}\n// La bonne réponse est Ottawa";
|
const QuestionChoixMul = "::Ville capitale du Canada:: \nQuelle ville est la capitale du Canada? {\n~ Toronto\n~ Montréal\n= Ottawa #Rétroaction spécifique.\n} // Commentaire non visible (au besoin)";
|
||||||
const QuestionChoixMulMany = "Quelles villes trouve-t-on au Canada? { \n~ %33.3% Montréal \n ~ %33.3% Ottawa \n ~ %33.3% Vancouver \n ~ %-100% New York \n ~ %-100% Paris \n#### La bonne réponse est Montréal, Ottawa et Vancouver \n}\n// Utilisez tilde (signe de vague) pour toutes les réponses.\n// On doit indiquer le pourcentage de chaque réponse.";
|
const QuestionChoixMulMany = "::Villes canadiennes:: \n Quelles villes trouve-t-on au Canada? { \n~ %33.3% Montréal \n ~ %33.3% Ottawa \n ~ %33.3% Vancouver \n ~ %-100% New York \n ~ %-100% Paris \n#### Rétroaction globale de la question. \n} // Utilisez tilde (signe de vague) pour toutes les réponses. // On doit indiquer le pourcentage de chaque réponse.";
|
||||||
const QuestionCourte ="Avec quoi ouvre-t-on une porte? { \n= clé \n= clef \n}\n// Permet de fournir plusieurs bonnes réponses.\n// Note: La casse n'est pas prise en compte.";
|
const QuestionCourte ="::Clé et porte:: \n Avec quoi ouvre-t-on une porte? { \n= clé \n= clef \n} // Permet de fournir plusieurs bonnes réponses. // Note: La casse n'est pas prise en compte.";
|
||||||
const QuestionNum ="// Question de plage mathématique. \n Quel est un nombre de 1 à 5 ? {\n#3:2\n}\n \n// Plage mathématique spécifiée avec des points de fin d'intervalle. \n Quel est un nombre de 1 à 5 ? {\n#1..5\n} \n\n// Réponses numériques multiples avec crédit partiel et commentaires.\nQuand est né Ulysses S. Grant ? {\n# =1822:0 # Correct ! Crédit complet. \n=%50%1822:2 # Il est né en 1822. Demi-crédit pour être proche.\n}";
|
const QuestionNum ="::Question numérique avec marge:: \nQuel est un nombre de 1 à 5 ? {\n#3:2\n}\n \n// Plage mathématique spécifiée avec des points de fin d'intervalle. \n ::Question numérique avec plage:: \n Quel est un nombre de 1 à 5 ? {\n#1..5\n} \n\n// Réponses numériques multiples avec crédit partiel et commentaires.\n::Question numérique avec plusieurs réponses::\nQuand est né Ulysses S. Grant ? {\n# =1822:0 # Correct ! Crédit complet. \n=%50%1822:2 # Il est né en 1822. Demi-crédit pour être proche.\n}";
|
||||||
return (
|
return (
|
||||||
<div className="gift-cheat-sheet">
|
<div className="gift-cheat-sheet">
|
||||||
<h2 className="subtitle">Informations pratiques sur l'éditeur</h2>
|
<h2 className="subtitle">Informations pratiques sur l'éditeur</h2>
|
||||||
|
|
@ -79,7 +79,7 @@ const GiftCheatSheet: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="question-type">
|
<div className="question-type">
|
||||||
<h4> 5. Question numérique </h4>
|
<h4> 5. Questions numériques </h4>
|
||||||
<pre>
|
<pre>
|
||||||
<code className="question-code-block selectable-text">
|
<code className="question-code-block selectable-text">
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import React, { useEffect, useState } from 'react';
|
||||||
import Template, { ErrorTemplate } from './templates';
|
import Template, { ErrorTemplate } from './templates';
|
||||||
import { parse } from 'gift-pegjs';
|
import { parse } from 'gift-pegjs';
|
||||||
import './styles.css';
|
import './styles.css';
|
||||||
import DOMPurify from 'dompurify';
|
import { FormattedTextTemplate } from './templates/TextTypeTemplate';
|
||||||
|
|
||||||
interface GIFTTemplatePreviewProps {
|
interface GIFTTemplatePreviewProps {
|
||||||
questions: string[];
|
questions: string[];
|
||||||
|
|
@ -74,7 +74,8 @@ const GIFTTemplatePreview: React.FC<GIFTTemplatePreviewProps> = ({
|
||||||
<div className="error">{error}</div>
|
<div className="error">{error}</div>
|
||||||
) : isPreviewReady ? (
|
) : isPreviewReady ? (
|
||||||
<div data-testid="preview-container">
|
<div data-testid="preview-container">
|
||||||
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(items) }}></div>
|
|
||||||
|
<div dangerouslySetInnerHTML={{ __html: FormattedTextTemplate({ format: 'html', text: items }) }}></div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="loading">Chargement de la prévisualisation...</div>
|
<div className="loading">Chargement de la prévisualisation...</div>
|
||||||
|
|
|
||||||
|
|
@ -4,14 +4,24 @@ import katex from 'katex';
|
||||||
import { TextFormat } from 'gift-pegjs';
|
import { TextFormat } from 'gift-pegjs';
|
||||||
import DOMPurify from 'dompurify'; // cleans HTML to prevent XSS attacks, etc.
|
import DOMPurify from 'dompurify'; // cleans HTML to prevent XSS attacks, etc.
|
||||||
|
|
||||||
export function formatLatex(text: string): string {
|
function formatLatex(text: string): string {
|
||||||
return text
|
|
||||||
|
let renderedText = '';
|
||||||
|
|
||||||
|
try {
|
||||||
|
renderedText = text
|
||||||
.replace(/\$\$(.*?)\$\$/g, (_, inner) => katex.renderToString(inner, { displayMode: true }))
|
.replace(/\$\$(.*?)\$\$/g, (_, inner) => katex.renderToString(inner, { displayMode: true }))
|
||||||
.replace(/\$(.*?)\$/g, (_, inner) => katex.renderToString(inner, { displayMode: false }))
|
.replace(/\$(.*?)\$/g, (_, inner) => katex.renderToString(inner, { displayMode: false }))
|
||||||
.replace(/\\\[(.*?)\\\]/g, (_, inner) => katex.renderToString(inner, { displayMode: true }))
|
.replace(/\\\[(.*?)\\\]/g, (_, inner) => katex.renderToString(inner, { displayMode: true }))
|
||||||
.replace(/\\\((.*?)\\\)/g, (_, inner) =>
|
.replace(/\\\((.*?)\\\)/g, (_, inner) =>
|
||||||
katex.renderToString(inner, { displayMode: false })
|
katex.renderToString(inner, { displayMode: false })
|
||||||
);
|
);
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
} catch (error) {
|
||||||
|
renderedText = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
return renderedText;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -47,10 +47,10 @@ const LaunchQuizDialog: React.FC<Props> = ({ open, handleOnClose, launchQuiz, se
|
||||||
|
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button variant="outlined" onClick={handleOnClose}>
|
<Button variant="outlined" onClick={handleOnClose}>
|
||||||
Annuler
|
<div>Annuler</div>
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="contained" onClick={launchQuiz}>
|
<Button variant="contained" onClick={launchQuiz}>
|
||||||
Lancer
|
<div>Lancer</div>
|
||||||
</Button>
|
</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,16 @@
|
||||||
// LiveResults.tsx
|
// LiveResults.tsx
|
||||||
import React, { useMemo, useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { Socket } from 'socket.io-client';
|
import { Socket } from 'socket.io-client';
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
|
||||||
import { faCheck, faCircleXmark } from '@fortawesome/free-solid-svg-icons';
|
|
||||||
import { QuestionType } from '../../Types/QuestionType';
|
import { QuestionType } from '../../Types/QuestionType';
|
||||||
|
|
||||||
import './liveResult.css';
|
import './liveResult.css';
|
||||||
import {
|
import {
|
||||||
Button,
|
|
||||||
FormControlLabel,
|
FormControlLabel,
|
||||||
FormGroup,
|
FormGroup,
|
||||||
Paper,
|
|
||||||
Switch,
|
Switch,
|
||||||
Table,
|
|
||||||
TableBody,
|
|
||||||
TableCell,
|
|
||||||
TableContainer,
|
|
||||||
TableFooter,
|
|
||||||
TableHead,
|
|
||||||
TableRow
|
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import { StudentType } from '../../Types/StudentType';
|
import { StudentType } from '../../Types/StudentType';
|
||||||
import { formatLatex } from '../GiftTemplate/templates/TextTypeTemplate';
|
|
||||||
|
import LiveResultsTable from './LiveResultsTable/LiveResultsTable';
|
||||||
|
|
||||||
interface LiveResultsProps {
|
interface LiveResultsProps {
|
||||||
socket: Socket | null;
|
socket: Socket | null;
|
||||||
|
|
@ -31,269 +20,10 @@ interface LiveResultsProps {
|
||||||
students: StudentType[]
|
students: StudentType[]
|
||||||
}
|
}
|
||||||
|
|
||||||
// interface Answer {
|
|
||||||
// answer: string | number | boolean;
|
|
||||||
// isCorrect: boolean;
|
|
||||||
// idQuestion: number;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// interface StudentResult {
|
|
||||||
// username: string;
|
|
||||||
// idUser: string;
|
|
||||||
// answers: Answer[];
|
|
||||||
// }
|
|
||||||
|
|
||||||
const LiveResults: React.FC<LiveResultsProps> = ({ questions, showSelectedQuestion, students }) => {
|
const LiveResults: React.FC<LiveResultsProps> = ({ questions, showSelectedQuestion, students }) => {
|
||||||
const [showUsernames, setShowUsernames] = useState<boolean>(false);
|
const [showUsernames, setShowUsernames] = useState<boolean>(false);
|
||||||
const [showCorrectAnswers, setShowCorrectAnswers] = useState<boolean>(false);
|
const [showCorrectAnswers, setShowCorrectAnswers] = useState<boolean>(false);
|
||||||
const [showFullAnswer, setShowFullAnswer] = useState(false);
|
|
||||||
const [selectedAnswer, setSelectedAnswer] = useState('');
|
|
||||||
|
|
||||||
const handleShowAnswer = (answer: string) => {
|
|
||||||
setSelectedAnswer(answer);
|
|
||||||
setShowFullAnswer(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderAnswerCell = (answer: string) => {
|
|
||||||
if (!answer) return null;
|
|
||||||
const shortAnswer = answer.length > 20 ? answer.slice(0, 20) : answer;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<span>{shortAnswer}</span>
|
|
||||||
{answer.length > 20 && (
|
|
||||||
<button onClick={() => handleShowAnswer(answer)}
|
|
||||||
style={{
|
|
||||||
backgroundColor: '#D3D3D3', // Darker background color
|
|
||||||
color: '#fff',
|
|
||||||
border: 'none',
|
|
||||||
borderRadius: '4px',
|
|
||||||
padding: '0.1rem 0.4rem',
|
|
||||||
cursor: 'pointer',
|
|
||||||
marginLeft: '0.5rem'
|
|
||||||
}}>...</button>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
// const [students, setStudents] = useState<StudentType[]>(initialStudents);
|
|
||||||
// const [studentResultsMap, setStudentResultsMap] = useState<Map<string, StudentResult>>(new Map());
|
|
||||||
|
|
||||||
const maxQuestions = questions.length;
|
|
||||||
|
|
||||||
// useEffect(() => {
|
|
||||||
// // Initialize the map with the current students
|
|
||||||
// const newStudentResultsMap = new Map<string, StudentResult>();
|
|
||||||
|
|
||||||
// for (const student of students) {
|
|
||||||
// newStudentResultsMap.set(student.id, { username: student.name, idUser: student.id, answers: [] });
|
|
||||||
// }
|
|
||||||
|
|
||||||
// setStudentResultsMap(newStudentResultsMap);
|
|
||||||
// }, [])
|
|
||||||
|
|
||||||
// update when students change
|
|
||||||
// useEffect(() => {
|
|
||||||
// // studentResultsMap is inconsistent with students -- need to update
|
|
||||||
|
|
||||||
// for (const student of students as StudentType[]) {
|
|
||||||
// }
|
|
||||||
|
|
||||||
// }, [students])
|
|
||||||
|
|
||||||
// useEffect(() => {
|
|
||||||
// if (socket) {
|
|
||||||
// const submitAnswerHandler = ({
|
|
||||||
// idUser,
|
|
||||||
// answer,
|
|
||||||
// idQuestion
|
|
||||||
// }: {
|
|
||||||
// idUser: string;
|
|
||||||
// username: string;
|
|
||||||
// answer: string | number | boolean;
|
|
||||||
// idQuestion: number;
|
|
||||||
// }) => {
|
|
||||||
// console.log(`Received answer from ${idUser} for question ${idQuestion}: ${answer}`);
|
|
||||||
|
|
||||||
// // print the list of current student names
|
|
||||||
// console.log('Current students:');
|
|
||||||
// students.forEach((student) => {
|
|
||||||
// console.log(student.name);
|
|
||||||
// });
|
|
||||||
|
|
||||||
// // Update the students state using the functional form of setStudents
|
|
||||||
// setStudents((prevStudents) => {
|
|
||||||
// let foundStudent = false;
|
|
||||||
// const updatedStudents = prevStudents.map((student) => {
|
|
||||||
// if (student.id === idUser) {
|
|
||||||
// foundStudent = true;
|
|
||||||
// const updatedAnswers = student.answers.map((ans) => {
|
|
||||||
// const newAnswer: Answer = { answer, isCorrect: checkIfIsCorrect(answer, idQuestion), idQuestion };
|
|
||||||
// console.log(`Updating answer for ${student.name} for question ${idQuestion} to ${answer}`);
|
|
||||||
// return (ans.idQuestion === idQuestion ? { ...ans, newAnswer } : ans);
|
|
||||||
// }
|
|
||||||
// );
|
|
||||||
// return { ...student, answers: updatedAnswers };
|
|
||||||
// }
|
|
||||||
// return student;
|
|
||||||
// });
|
|
||||||
// if (!foundStudent) {
|
|
||||||
// console.log(`Student ${idUser} not found in the list of students in LiveResults`);
|
|
||||||
// }
|
|
||||||
// return updatedStudents;
|
|
||||||
// });
|
|
||||||
|
|
||||||
|
|
||||||
// // make a copy of the students array so we can update it
|
|
||||||
// // const updatedStudents = [...students];
|
|
||||||
|
|
||||||
// // const student = updatedStudents.find((student) => student.id === idUser);
|
|
||||||
// // if (!student) {
|
|
||||||
// // // this is a bad thing if an answer was submitted but the student isn't in the list
|
|
||||||
// // console.log(`Student ${idUser} not found in the list of students in LiveResults`);
|
|
||||||
// // return;
|
|
||||||
// // }
|
|
||||||
|
|
||||||
// // const isCorrect = checkIfIsCorrect(answer, idQuestion);
|
|
||||||
// // const newAnswer: Answer = { answer, isCorrect, idQuestion };
|
|
||||||
// // student.answers.push(newAnswer);
|
|
||||||
// // // print list of answers
|
|
||||||
// // console.log('Answers:');
|
|
||||||
// // student.answers.forEach((answer) => {
|
|
||||||
// // console.log(answer.answer);
|
|
||||||
// // });
|
|
||||||
// // setStudents(updatedStudents); // update the state
|
|
||||||
// };
|
|
||||||
|
|
||||||
// socket.on('submit-answer', submitAnswerHandler);
|
|
||||||
// return () => {
|
|
||||||
// socket.off('submit-answer');
|
|
||||||
// };
|
|
||||||
// }
|
|
||||||
// }, [socket]);
|
|
||||||
|
|
||||||
const getStudentGrade = (student: StudentType): number => {
|
|
||||||
if (student.answers.length === 0) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
const uniqueQuestions = new Set();
|
|
||||||
let correctAnswers = 0;
|
|
||||||
|
|
||||||
for (const answer of student.answers) {
|
|
||||||
const { idQuestion, isCorrect } = answer;
|
|
||||||
|
|
||||||
if (!uniqueQuestions.has(idQuestion)) {
|
|
||||||
uniqueQuestions.add(idQuestion);
|
|
||||||
|
|
||||||
if (isCorrect) {
|
|
||||||
correctAnswers++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (correctAnswers / questions.length) * 100;
|
|
||||||
};
|
|
||||||
|
|
||||||
const classAverage: number = useMemo(() => {
|
|
||||||
let classTotal = 0;
|
|
||||||
|
|
||||||
students.forEach((student) => {
|
|
||||||
classTotal += getStudentGrade(student);
|
|
||||||
});
|
|
||||||
|
|
||||||
return classTotal / students.length;
|
|
||||||
}, [students]);
|
|
||||||
|
|
||||||
const getCorrectAnswersPerQuestion = (index: number): number => {
|
|
||||||
return (
|
|
||||||
(students.filter((student) =>
|
|
||||||
student.answers.some(
|
|
||||||
(answer) =>
|
|
||||||
parseInt(answer.idQuestion.toString()) === index + 1 && answer.isCorrect
|
|
||||||
)
|
|
||||||
).length / students.length) * 100
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
// (studentResults.filter((student) =>
|
|
||||||
// student.answers.some(
|
|
||||||
// (answer) =>
|
|
||||||
// parseInt(answer.idQuestion.toString()) === index + 1 && answer.isCorrect
|
|
||||||
// )
|
|
||||||
// ).length /
|
|
||||||
// studentResults.length) *
|
|
||||||
// 100
|
|
||||||
// );
|
|
||||||
// };
|
|
||||||
|
|
||||||
// function checkIfIsCorrect(answer: string | number | boolean, idQuestion: number): boolean {
|
|
||||||
// const questionInfo = questions.find((q) =>
|
|
||||||
// q.question.id ? q.question.id === idQuestion.toString() : false
|
|
||||||
// ) as QuestionType | undefined;
|
|
||||||
|
|
||||||
// const answerText = answer.toString();
|
|
||||||
// if (questionInfo) {
|
|
||||||
// const question = questionInfo.question as GIFTQuestion;
|
|
||||||
// if (question.type === 'TF') {
|
|
||||||
// return (
|
|
||||||
// (question.isTrue && answerText == 'true') ||
|
|
||||||
// (!question.isTrue && answerText == 'false')
|
|
||||||
// );
|
|
||||||
// } else if (question.type === 'MC') {
|
|
||||||
// return question.choices.some(
|
|
||||||
// (choice) => choice.isCorrect && choice.text.text === answerText
|
|
||||||
// );
|
|
||||||
// } else if (question.type === 'Numerical') {
|
|
||||||
// if (question.choices && !Array.isArray(question.choices)) {
|
|
||||||
// if (
|
|
||||||
// question.choices.type === 'high-low' &&
|
|
||||||
// question.choices.numberHigh &&
|
|
||||||
// question.choices.numberLow
|
|
||||||
// ) {
|
|
||||||
// const answerNumber = parseFloat(answerText);
|
|
||||||
// if (!isNaN(answerNumber)) {
|
|
||||||
// return (
|
|
||||||
// answerNumber <= question.choices.numberHigh &&
|
|
||||||
// answerNumber >= question.choices.numberLow
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// if (question.choices && Array.isArray(question.choices)) {
|
|
||||||
// if (
|
|
||||||
// question.choices[0].text.type === 'range' &&
|
|
||||||
// question.choices[0].text.number &&
|
|
||||||
// question.choices[0].text.range
|
|
||||||
// ) {
|
|
||||||
// const answerNumber = parseFloat(answerText);
|
|
||||||
// const range = question.choices[0].text.range;
|
|
||||||
// const correctAnswer = question.choices[0].text.number;
|
|
||||||
// if (!isNaN(answerNumber)) {
|
|
||||||
// return (
|
|
||||||
// answerNumber <= correctAnswer + range &&
|
|
||||||
// answerNumber >= correctAnswer - range
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// if (
|
|
||||||
// question.choices[0].text.type === 'simple' &&
|
|
||||||
// question.choices[0].text.number
|
|
||||||
// ) {
|
|
||||||
// const answerNumber = parseFloat(answerText);
|
|
||||||
// if (!isNaN(answerNumber)) {
|
|
||||||
// return answerNumber === question.choices[0].text.number;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// } else if (question.type === 'Short') {
|
|
||||||
// return question.choices.some(
|
|
||||||
// (choice) => choice.text.text.toUpperCase() === answerText.toUpperCase()
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// return false;
|
|
||||||
// }
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
||||||
|
|
@ -328,201 +58,15 @@ const LiveResults: React.FC<LiveResultsProps> = ({ questions, showSelectedQuesti
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="table-container">
|
<div className="table-container">
|
||||||
<TableContainer component={Paper}>
|
|
||||||
<Table size="small">
|
<LiveResultsTable
|
||||||
<TableHead>
|
students={students}
|
||||||
<TableRow>
|
questions={questions}
|
||||||
<TableCell className="sticky-column">
|
showCorrectAnswers={showCorrectAnswers}
|
||||||
<div className="text-base text-bold">Nom d'utilisateur</div>
|
showSelectedQuestion={showSelectedQuestion}
|
||||||
</TableCell>
|
showUsernames={showUsernames}
|
||||||
{Array.from({ length: maxQuestions }, (_, index) => (
|
/>
|
||||||
<TableCell
|
</div>
|
||||||
|
|
||||||
key={index}
|
|
||||||
sx={{
|
|
||||||
textAlign: 'center',
|
|
||||||
cursor: 'pointer',
|
|
||||||
borderStyle: 'solid',
|
|
||||||
borderWidth: 1,
|
|
||||||
borderColor: 'rgba(224, 224, 224, 1)'
|
|
||||||
}}
|
|
||||||
onClick={() => showSelectedQuestion(index)}
|
|
||||||
>
|
|
||||||
<div className="text-base text-bold blue">{`Q${index + 1}`}</div>
|
|
||||||
</TableCell>
|
|
||||||
))}
|
|
||||||
<TableCell
|
|
||||||
className="sticky-header"
|
|
||||||
sx={{
|
|
||||||
textAlign: 'center',
|
|
||||||
borderStyle: 'solid',
|
|
||||||
borderWidth: 1,
|
|
||||||
borderColor: 'rgba(224, 224, 224, 1)'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className="text-base text-bold">% réussite</div>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
</TableHead>
|
|
||||||
<TableBody>
|
|
||||||
{students.map((student) => (
|
|
||||||
<TableRow key={student.id}>
|
|
||||||
<TableCell
|
|
||||||
className="sticky-column"
|
|
||||||
sx={{
|
|
||||||
borderStyle: 'solid',
|
|
||||||
borderWidth: 1,
|
|
||||||
borderColor: 'rgba(224, 224, 224, 1)'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className="text-base">
|
|
||||||
{showUsernames ? student.name : '******'}
|
|
||||||
</div>
|
|
||||||
</TableCell>
|
|
||||||
{Array.from({ length: maxQuestions }, (_, index) => {
|
|
||||||
const answer = student.answers.find(
|
|
||||||
(answer) => parseInt(answer.idQuestion.toString()) === index + 1
|
|
||||||
);
|
|
||||||
const answerText = answer ? answer.answer.toString() : '';
|
|
||||||
const isCorrect = answer ? answer.isCorrect : false;
|
|
||||||
return (
|
|
||||||
<TableCell
|
|
||||||
style={{ maxWidth: '65px'}}
|
|
||||||
key={index}
|
|
||||||
sx={{
|
|
||||||
textAlign: 'center',
|
|
||||||
borderStyle: 'solid',
|
|
||||||
borderWidth: 1,
|
|
||||||
borderColor: 'rgba(224, 224, 224, 1)'
|
|
||||||
}}
|
|
||||||
className={
|
|
||||||
answerText === ''
|
|
||||||
? ''
|
|
||||||
: isCorrect
|
|
||||||
? 'correct-answer'
|
|
||||||
: 'incorrect-answer'
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{showCorrectAnswers ? (
|
|
||||||
<div>{renderAnswerCell(formatLatex(answerText))}</div>
|
|
||||||
) : isCorrect ? (
|
|
||||||
<FontAwesomeIcon icon={faCheck} />
|
|
||||||
) : (
|
|
||||||
answerText !== '' && (
|
|
||||||
<FontAwesomeIcon icon={faCircleXmark} />
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
</TableCell>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
<TableCell
|
|
||||||
sx={{
|
|
||||||
textAlign: 'center',
|
|
||||||
borderStyle: 'solid',
|
|
||||||
borderWidth: 1,
|
|
||||||
borderColor: 'rgba(224, 224, 224, 1)',
|
|
||||||
fontWeight: 'bold',
|
|
||||||
color: 'rgba(0, 0, 0)'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{getStudentGrade(student).toFixed()} %
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
))}
|
|
||||||
</TableBody>
|
|
||||||
<TableFooter>
|
|
||||||
<TableRow sx={{ backgroundColor: '#d3d3d34f' }}>
|
|
||||||
<TableCell className="sticky-column" sx={{ color: 'black' }}>
|
|
||||||
<div className="text-base text-bold">% réussite</div>
|
|
||||||
</TableCell>
|
|
||||||
{Array.from({ length: maxQuestions }, (_, index) => (
|
|
||||||
<TableCell
|
|
||||||
key={index}
|
|
||||||
sx={{
|
|
||||||
textAlign: 'center',
|
|
||||||
borderStyle: 'solid',
|
|
||||||
borderWidth: 1,
|
|
||||||
borderColor: 'rgba(224, 224, 224, 1)',
|
|
||||||
fontWeight: 'bold',
|
|
||||||
color: 'rgba(0, 0, 0)'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{students.length > 0
|
|
||||||
? `${getCorrectAnswersPerQuestion(index).toFixed()} %`
|
|
||||||
: '-'}
|
|
||||||
</TableCell>
|
|
||||||
))}
|
|
||||||
<TableCell
|
|
||||||
sx={{
|
|
||||||
textAlign: 'center',
|
|
||||||
borderStyle: 'solid',
|
|
||||||
borderWidth: 1,
|
|
||||||
borderColor: 'rgba(224, 224, 224, 1)',
|
|
||||||
fontWeight: 'bold',
|
|
||||||
fontSize: '1rem',
|
|
||||||
color: 'rgba(0, 0, 0)'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{students.length > 0 ? `${classAverage.toFixed()} %` : '-'}
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
</TableFooter>
|
|
||||||
</Table>
|
|
||||||
</TableContainer>
|
|
||||||
</div>
|
|
||||||
{showFullAnswer && (
|
|
||||||
<div
|
|
||||||
onClick={() => setShowFullAnswer(false)}
|
|
||||||
style={{
|
|
||||||
position: 'fixed',
|
|
||||||
top: 0,
|
|
||||||
left: 0,
|
|
||||||
right: 0,
|
|
||||||
bottom: 0,
|
|
||||||
background: 'rgba(0,0,0,0.3)',
|
|
||||||
zIndex: 9999,}}>
|
|
||||||
<dialog
|
|
||||||
open
|
|
||||||
onClick={(e) => e.stopPropagation()}
|
|
||||||
style={{
|
|
||||||
position: 'fixed',
|
|
||||||
top: '50%',
|
|
||||||
left: '50%',
|
|
||||||
transform: 'translate(-50%, -50%)',
|
|
||||||
border: 'none',
|
|
||||||
padding: '1rem',
|
|
||||||
background: '#fff',
|
|
||||||
boxShadow: '0 2px 10px rgba(0, 0, 0, 0.3)',
|
|
||||||
minWidth: '300px',
|
|
||||||
minHeight: '200px',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
gap: '1rem',
|
|
||||||
maxWidth: '600px',
|
|
||||||
wordWrap: 'break-word',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<p style={{ margin: 0 }}>{selectedAnswer}</p>
|
|
||||||
<Button
|
|
||||||
variant="contained"
|
|
||||||
color="primary"
|
|
||||||
onClick={() => setShowFullAnswer(false)}
|
|
||||||
sx={{
|
|
||||||
position: 'absolute',
|
|
||||||
bottom: '1rem',
|
|
||||||
right: '1rem',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Fermer
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</dialog>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,75 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Paper, Table, TableContainer } from '@mui/material';
|
||||||
|
import { StudentType } from 'src/Types/StudentType';
|
||||||
|
import { QuestionType } from '../../../Types/QuestionType';
|
||||||
|
import LiveResultsTableFooter from './TableComponents/LiveResultTableFooter';
|
||||||
|
import LiveResultsTableHeader from './TableComponents/LiveResultsTableHeader';
|
||||||
|
import LiveResultsTableBody from './TableComponents/LiveResultsTableBody';
|
||||||
|
|
||||||
|
interface LiveResultsTableProps {
|
||||||
|
students: StudentType[];
|
||||||
|
questions: QuestionType[];
|
||||||
|
showCorrectAnswers: boolean;
|
||||||
|
showSelectedQuestion: (index: number) => void;
|
||||||
|
showUsernames: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const LiveResultsTable: React.FC<LiveResultsTableProps> = ({
|
||||||
|
questions,
|
||||||
|
students,
|
||||||
|
showSelectedQuestion,
|
||||||
|
showUsernames,
|
||||||
|
showCorrectAnswers
|
||||||
|
}) => {
|
||||||
|
|
||||||
|
const maxQuestions = questions.length;
|
||||||
|
|
||||||
|
const getStudentGrade = (student: StudentType): number => {
|
||||||
|
if (student.answers.length === 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uniqueQuestions = new Set();
|
||||||
|
let correctAnswers = 0;
|
||||||
|
|
||||||
|
for (const answer of student.answers) {
|
||||||
|
const { idQuestion, isCorrect } = answer;
|
||||||
|
|
||||||
|
if (!uniqueQuestions.has(idQuestion)) {
|
||||||
|
uniqueQuestions.add(idQuestion);
|
||||||
|
|
||||||
|
if (isCorrect) {
|
||||||
|
correctAnswers++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (correctAnswers / questions.length) * 100;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TableContainer component={Paper}>
|
||||||
|
<Table size="small">
|
||||||
|
<LiveResultsTableHeader
|
||||||
|
maxQuestions={maxQuestions}
|
||||||
|
showSelectedQuestion={showSelectedQuestion}
|
||||||
|
/>
|
||||||
|
<LiveResultsTableBody
|
||||||
|
maxQuestions={maxQuestions}
|
||||||
|
students={students}
|
||||||
|
showUsernames={showUsernames}
|
||||||
|
showCorrectAnswers={showCorrectAnswers}
|
||||||
|
getStudentGrade={getStudentGrade}
|
||||||
|
/>
|
||||||
|
<LiveResultsTableFooter
|
||||||
|
students={students}
|
||||||
|
maxQuestions={maxQuestions}
|
||||||
|
getStudentGrade={getStudentGrade}
|
||||||
|
/>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LiveResultsTable;
|
||||||
|
|
@ -0,0 +1,79 @@
|
||||||
|
import { TableCell, TableFooter, TableRow } from "@mui/material";
|
||||||
|
import React, { useMemo } from "react";
|
||||||
|
import { StudentType } from "src/Types/StudentType";
|
||||||
|
|
||||||
|
interface LiveResultsFooterProps {
|
||||||
|
students: StudentType[];
|
||||||
|
maxQuestions: number;
|
||||||
|
getStudentGrade: (student: StudentType) => number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const LiveResultsTableFooter: React.FC<LiveResultsFooterProps> = ({
|
||||||
|
maxQuestions,
|
||||||
|
students,
|
||||||
|
getStudentGrade
|
||||||
|
|
||||||
|
}) => {
|
||||||
|
|
||||||
|
const getCorrectAnswersPerQuestion = (index: number): number => {
|
||||||
|
return (
|
||||||
|
(students.filter((student) =>
|
||||||
|
student.answers.some(
|
||||||
|
(answer) =>
|
||||||
|
parseInt(answer.idQuestion.toString()) === index + 1 && answer.isCorrect
|
||||||
|
)
|
||||||
|
).length / students.length) * 100
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const classAverage: number = useMemo(() => {
|
||||||
|
let classTotal = 0;
|
||||||
|
|
||||||
|
students.forEach((student) => {
|
||||||
|
classTotal += getStudentGrade(student);
|
||||||
|
});
|
||||||
|
|
||||||
|
return classTotal / students.length;
|
||||||
|
}, [students]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TableFooter>
|
||||||
|
<TableRow sx={{ backgroundColor: '#d3d3d34f' }}>
|
||||||
|
<TableCell className="sticky-column" sx={{ color: 'black' }}>
|
||||||
|
<div className="text-base text-bold">% réussite</div>
|
||||||
|
</TableCell>
|
||||||
|
{Array.from({ length: maxQuestions }, (_, index) => (
|
||||||
|
<TableCell
|
||||||
|
key={index}
|
||||||
|
sx={{
|
||||||
|
textAlign: 'center',
|
||||||
|
borderStyle: 'solid',
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: 'rgba(224, 224, 224, 1)',
|
||||||
|
fontWeight: 'bold',
|
||||||
|
color: 'rgba(0, 0, 0)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{students.length > 0
|
||||||
|
? `${getCorrectAnswersPerQuestion(index).toFixed()} %`
|
||||||
|
: '-'}
|
||||||
|
</TableCell>
|
||||||
|
))}
|
||||||
|
<TableCell
|
||||||
|
sx={{
|
||||||
|
textAlign: 'center',
|
||||||
|
borderStyle: 'solid',
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: 'rgba(224, 224, 224, 1)',
|
||||||
|
fontWeight: 'bold',
|
||||||
|
fontSize: '1rem',
|
||||||
|
color: 'rgba(0, 0, 0)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{students.length > 0 ? `${classAverage.toFixed()} %` : '-'}
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableFooter>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default LiveResultsTableFooter;
|
||||||
|
|
@ -0,0 +1,94 @@
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import { TableBody, TableCell, TableRow } from "@mui/material";
|
||||||
|
import { faCheck, faCircleXmark } from '@fortawesome/free-solid-svg-icons';
|
||||||
|
import { FormattedTextTemplate } from '../../../GiftTemplate/templates/TextTypeTemplate';
|
||||||
|
import React from "react";
|
||||||
|
import { StudentType } from "src/Types/StudentType";
|
||||||
|
|
||||||
|
interface LiveResultsFooterProps {
|
||||||
|
maxQuestions: number;
|
||||||
|
students: StudentType[];
|
||||||
|
showUsernames: boolean;
|
||||||
|
showCorrectAnswers: boolean;
|
||||||
|
getStudentGrade: (student: StudentType) => number;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const LiveResultsTableFooter: React.FC<LiveResultsFooterProps> = ({
|
||||||
|
maxQuestions,
|
||||||
|
students,
|
||||||
|
showUsernames,
|
||||||
|
showCorrectAnswers,
|
||||||
|
getStudentGrade
|
||||||
|
}) => {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TableBody>
|
||||||
|
{students.map((student) => (
|
||||||
|
<TableRow key={student.id}>
|
||||||
|
<TableCell
|
||||||
|
className="sticky-column"
|
||||||
|
sx={{
|
||||||
|
borderStyle: 'solid',
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: 'rgba(224, 224, 224, 1)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="text-base">
|
||||||
|
{showUsernames ? student.name : '******'}
|
||||||
|
</div>
|
||||||
|
</TableCell>
|
||||||
|
{Array.from({ length: maxQuestions }, (_, index) => {
|
||||||
|
const answer = student.answers.find(
|
||||||
|
(answer) => parseInt(answer.idQuestion.toString()) === index + 1
|
||||||
|
);
|
||||||
|
const answerText = answer ? answer.answer.toString() : '';
|
||||||
|
const isCorrect = answer ? answer.isCorrect : false;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TableCell
|
||||||
|
key={index}
|
||||||
|
sx={{
|
||||||
|
textAlign: 'center',
|
||||||
|
borderStyle: 'solid',
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: 'rgba(224, 224, 224, 1)'
|
||||||
|
}}
|
||||||
|
className={
|
||||||
|
answerText === ''
|
||||||
|
? ''
|
||||||
|
: isCorrect
|
||||||
|
? 'correct-answer'
|
||||||
|
: 'incorrect-answer'
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{showCorrectAnswers ? (
|
||||||
|
<div dangerouslySetInnerHTML={{ __html: FormattedTextTemplate({ format: '', text: answerText }) }}></div>
|
||||||
|
) : isCorrect ? (
|
||||||
|
<FontAwesomeIcon icon={faCheck} aria-label="correct" />
|
||||||
|
) : (
|
||||||
|
answerText !== '' && (
|
||||||
|
<FontAwesomeIcon icon={faCircleXmark} aria-label="incorrect"/>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</TableCell>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
<TableCell
|
||||||
|
sx={{
|
||||||
|
textAlign: 'center',
|
||||||
|
borderStyle: 'solid',
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: 'rgba(224, 224, 224, 1)',
|
||||||
|
fontWeight: 'bold',
|
||||||
|
color: 'rgba(0, 0, 0)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{getStudentGrade(student).toFixed()} %
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default LiveResultsTableFooter;
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
import { TableCell, TableHead, TableRow } from "@mui/material";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
interface LiveResultsFooterProps {
|
||||||
|
maxQuestions: number;
|
||||||
|
showSelectedQuestion: (index: number) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const LiveResultsTableFooter: React.FC<LiveResultsFooterProps> = ({
|
||||||
|
maxQuestions,
|
||||||
|
showSelectedQuestion,
|
||||||
|
}) => {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TableHead>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell className="sticky-column">
|
||||||
|
<div className="text-base text-bold">Nom d'utilisateur</div>
|
||||||
|
</TableCell>
|
||||||
|
{Array.from({ length: maxQuestions }, (_, index) => (
|
||||||
|
<TableCell
|
||||||
|
key={index}
|
||||||
|
sx={{
|
||||||
|
textAlign: 'center',
|
||||||
|
cursor: 'pointer',
|
||||||
|
borderStyle: 'solid',
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: 'rgba(224, 224, 224, 1)'
|
||||||
|
}}
|
||||||
|
onClick={() => showSelectedQuestion(index)}
|
||||||
|
>
|
||||||
|
<div className="text-base text-bold blue">{`Q${index + 1}`}</div>
|
||||||
|
</TableCell>
|
||||||
|
))}
|
||||||
|
<TableCell
|
||||||
|
className="sticky-header"
|
||||||
|
sx={{
|
||||||
|
textAlign: 'center',
|
||||||
|
borderStyle: 'solid',
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: 'rgba(224, 224, 224, 1)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="text-base text-bold">% réussite</div>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default LiveResultsTableFooter;
|
||||||
|
|
@ -27,7 +27,6 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.question-wrapper .katex {
|
.question-wrapper .katex {
|
||||||
display: block;
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -120,9 +119,9 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.feedback-container {
|
.feedback-container {
|
||||||
margin-left: 1.1rem;
|
display: inline-block !important; /* override the parent */
|
||||||
display: inline-flex !important; /* override the parent */
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
margin-left: 1.1rem;
|
||||||
position: relative;
|
position: relative;
|
||||||
padding: 0 0.5rem;
|
padding: 0 0.5rem;
|
||||||
background-color: hsl(43, 100%, 94%);
|
background-color: hsl(43, 100%, 94%);
|
||||||
|
|
|
||||||
|
|
@ -23,16 +23,33 @@ const TeacherModeQuiz: React.FC<TeacherModeQuizProps> = ({
|
||||||
}) => {
|
}) => {
|
||||||
const [isAnswerSubmitted, setIsAnswerSubmitted] = useState(false);
|
const [isAnswerSubmitted, setIsAnswerSubmitted] = useState(false);
|
||||||
const [isFeedbackDialogOpen, setIsFeedbackDialogOpen] = useState(false);
|
const [isFeedbackDialogOpen, setIsFeedbackDialogOpen] = useState(false);
|
||||||
const [feedbackMessage, setFeedbackMessage] = useState('');
|
const [feedbackMessage, setFeedbackMessage] = useState<React.ReactNode>('');
|
||||||
|
|
||||||
|
const renderFeedbackMessage = (answer: string) => {
|
||||||
|
|
||||||
|
if(answer === 'true' || answer === 'false'){
|
||||||
|
return (<span>
|
||||||
|
<strong>Votre réponse est: </strong>{answer==="true" ? 'Vrai' : 'Faux'}
|
||||||
|
</span>)
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
return (
|
||||||
|
<span>
|
||||||
|
<strong>Votre réponse est: </strong>{answer.toString()}
|
||||||
|
</span>
|
||||||
|
);}
|
||||||
|
};
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
// Close the feedback dialog when the question changes
|
||||||
|
handleFeedbackDialogClose();
|
||||||
setIsAnswerSubmitted(false);
|
setIsAnswerSubmitted(false);
|
||||||
}, [questionInfos]);
|
|
||||||
|
}, [questionInfos.question]);
|
||||||
|
|
||||||
const handleOnSubmitAnswer = (answer: string | number | boolean) => {
|
const handleOnSubmitAnswer = (answer: string | number | boolean) => {
|
||||||
const idQuestion = Number(questionInfos.question.id) || -1;
|
const idQuestion = Number(questionInfos.question.id) || -1;
|
||||||
submitAnswer(answer, idQuestion);
|
submitAnswer(answer, idQuestion);
|
||||||
setFeedbackMessage(`Votre réponse est "${answer.toString()}".`);
|
setFeedbackMessage(renderFeedbackMessage(answer.toString()));
|
||||||
setIsFeedbackDialogOpen(true);
|
setIsFeedbackDialogOpen(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -74,7 +91,17 @@ const TeacherModeQuiz: React.FC<TeacherModeQuizProps> = ({
|
||||||
>
|
>
|
||||||
<DialogTitle>Rétroaction</DialogTitle>
|
<DialogTitle>Rétroaction</DialogTitle>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
|
<div style={{
|
||||||
|
wordWrap: 'break-word',
|
||||||
|
whiteSpace: 'pre-wrap',
|
||||||
|
maxHeight: '400px',
|
||||||
|
overflowY: 'auto',
|
||||||
|
}}>
|
||||||
{feedbackMessage}
|
{feedbackMessage}
|
||||||
|
<div style={{ textAlign: 'left', fontWeight: 'bold', marginTop: '10px'}}
|
||||||
|
>Question : </div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<QuestionComponent
|
<QuestionComponent
|
||||||
handleOnSubmitAnswer={handleOnSubmitAnswer}
|
handleOnSubmitAnswer={handleOnSubmitAnswer}
|
||||||
question={questionInfos.question as Question}
|
question={questionInfos.question as Question}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import LiveResultsComponent from 'src/components/LiveResults/LiveResults';
|
||||||
// import { QuestionService } from '../../../services/QuestionService';
|
// import { QuestionService } from '../../../services/QuestionService';
|
||||||
import webSocketService, { AnswerReceptionFromBackendType } from '../../../services/WebsocketService';
|
import webSocketService, { AnswerReceptionFromBackendType } from '../../../services/WebsocketService';
|
||||||
import { QuizType } from '../../../Types/QuizType';
|
import { QuizType } from '../../../Types/QuizType';
|
||||||
|
import GroupIcon from '@mui/icons-material/Group';
|
||||||
|
|
||||||
import './manageRoom.css';
|
import './manageRoom.css';
|
||||||
import { ENV_VARIABLES } from 'src/constants';
|
import { ENV_VARIABLES } from 'src/constants';
|
||||||
|
|
@ -33,7 +34,8 @@ const ManageRoom: React.FC = () => {
|
||||||
const [quizMode, setQuizMode] = useState<'teacher' | 'student'>('teacher');
|
const [quizMode, setQuizMode] = useState<'teacher' | 'student'>('teacher');
|
||||||
const [connectingError, setConnectingError] = useState<string>('');
|
const [connectingError, setConnectingError] = useState<string>('');
|
||||||
const [currentQuestion, setCurrentQuestion] = useState<QuestionType | undefined>(undefined);
|
const [currentQuestion, setCurrentQuestion] = useState<QuestionType | undefined>(undefined);
|
||||||
|
const [quizStarted, setQuizStarted] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (quizId.id) {
|
if (quizId.id) {
|
||||||
const fetchquiz = async () => {
|
const fetchquiz = async () => {
|
||||||
|
|
@ -145,7 +147,7 @@ const ManageRoom: React.FC = () => {
|
||||||
console.log('Quiz questions not found (cannot update answers without them).');
|
console.log('Quiz questions not found (cannot update answers without them).');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the students state using the functional form of setStudents
|
// Update the students state using the functional form of setStudents
|
||||||
setStudents((prevStudents) => {
|
setStudents((prevStudents) => {
|
||||||
// print the list of current student names
|
// print the list of current student names
|
||||||
|
|
@ -153,7 +155,7 @@ const ManageRoom: React.FC = () => {
|
||||||
prevStudents.forEach((student) => {
|
prevStudents.forEach((student) => {
|
||||||
console.log(student.name);
|
console.log(student.name);
|
||||||
});
|
});
|
||||||
|
|
||||||
let foundStudent = false;
|
let foundStudent = false;
|
||||||
const updatedStudents = prevStudents.map((student) => {
|
const updatedStudents = prevStudents.map((student) => {
|
||||||
console.log(`Comparing ${student.id} to ${idUser}`);
|
console.log(`Comparing ${student.id} to ${idUser}`);
|
||||||
|
|
@ -173,7 +175,7 @@ const ManageRoom: React.FC = () => {
|
||||||
updatedAnswers = [...student.answers, newAnswer];
|
updatedAnswers = [...student.answers, newAnswer];
|
||||||
}
|
}
|
||||||
return { ...student, answers: updatedAnswers };
|
return { ...student, answers: updatedAnswers };
|
||||||
}
|
}
|
||||||
return student;
|
return student;
|
||||||
});
|
});
|
||||||
if (!foundStudent) {
|
if (!foundStudent) {
|
||||||
|
|
@ -316,13 +318,18 @@ const ManageRoom: React.FC = () => {
|
||||||
if (!socket || !roomName || !quiz?.content || quiz?.content.length === 0) {
|
if (!socket || !roomName || !quiz?.content || quiz?.content.length === 0) {
|
||||||
// TODO: This error happens when token expires! Need to handle it properly
|
// TODO: This error happens when token expires! Need to handle it properly
|
||||||
console.log(`Error launching quiz. socket: ${socket}, roomName: ${roomName}, quiz: ${quiz}`);
|
console.log(`Error launching quiz. socket: ${socket}, roomName: ${roomName}, quiz: ${quiz}`);
|
||||||
|
setQuizStarted(true);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
switch (quizMode) {
|
switch (quizMode) {
|
||||||
case 'student':
|
case 'student':
|
||||||
|
setQuizStarted(true);
|
||||||
return launchStudentMode();
|
return launchStudentMode();
|
||||||
case 'teacher':
|
case 'teacher':
|
||||||
|
setQuizStarted(true);
|
||||||
return launchTeacherMode();
|
return launchTeacherMode();
|
||||||
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -427,9 +434,19 @@ const ManageRoom: React.FC = () => {
|
||||||
askConfirm
|
askConfirm
|
||||||
message={`Êtes-vous sûr de vouloir quitter?`} />
|
message={`Êtes-vous sûr de vouloir quitter?`} />
|
||||||
|
|
||||||
<div className='centerTitle'>
|
|
||||||
<div className='title'>Salle: {roomName}</div>
|
|
||||||
<div className='userCount subtitle'>Utilisateurs: {students.length}/60</div>
|
|
||||||
|
<div className='headerContent' style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', width: '100%' }}>
|
||||||
|
<div style={{ flex: 1, display: 'flex', justifyContent: 'center' }}>
|
||||||
|
<div className='title'>Salle: {roomName}</div>
|
||||||
|
</div>
|
||||||
|
{quizStarted && (
|
||||||
|
<div className='userCount subtitle smallText' style={{ display: 'flex', alignItems: 'center' }}>
|
||||||
|
<GroupIcon style={{ marginRight: '5px' }} />
|
||||||
|
{students.length}/60
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='dumb'></div>
|
<div className='dumb'></div>
|
||||||
|
|
@ -441,8 +458,12 @@ const ManageRoom: React.FC = () => {
|
||||||
{quizQuestions ? (
|
{quizQuestions ? (
|
||||||
|
|
||||||
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
||||||
|
|
||||||
<div className="title center-h-align mb-2">{quiz?.title}</div>
|
<div className="title center-h-align mb-2">{quiz?.title}</div>
|
||||||
|
{!isNaN(Number(currentQuestion?.question.id)) && (
|
||||||
|
<strong className='number of questions'>
|
||||||
|
Question {Number(currentQuestion?.question.id)}/{quizQuestions?.length}
|
||||||
|
</strong>
|
||||||
|
)}
|
||||||
|
|
||||||
{quizMode === 'teacher' && (
|
{quizMode === 'teacher' && (
|
||||||
|
|
||||||
|
|
@ -479,23 +500,23 @@ const ManageRoom: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{quizMode === 'teacher' && (
|
{quizMode === 'teacher' && (
|
||||||
<div className="questionNavigationButtons" style={{ display: 'flex', justifyContent: 'center' }}>
|
<div className="questionNavigationButtons" style={{ display: 'flex', justifyContent: 'center' }}>
|
||||||
<div className="previousQuestionButton">
|
<div className="previousQuestionButton">
|
||||||
<Button onClick={previousQuestion}
|
<Button onClick={previousQuestion}
|
||||||
variant="contained"
|
variant="contained"
|
||||||
disabled={Number(currentQuestion?.question.id) <= 1}>
|
disabled={Number(currentQuestion?.question.id) <= 1}>
|
||||||
Question précédente
|
Question précédente
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div className="nextQuestionButton">
|
<div className="nextQuestionButton">
|
||||||
<Button onClick={nextQuestion}
|
<Button onClick={nextQuestion}
|
||||||
variant="contained"
|
variant="contained"
|
||||||
disabled={Number(currentQuestion?.question.id) >=quizQuestions.length}
|
disabled={Number(currentQuestion?.question.id) >= quizQuestions.length}
|
||||||
>
|
>
|
||||||
Prochaine question
|
Prochaine question
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div> )}
|
</div>)}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,8 @@
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: center;
|
justify-content: flex-end;
|
||||||
align-items: center;
|
align-items: flex-end;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue