UI changes

This commit is contained in:
skillens.ai 2025-10-30 02:40:29 +05:30
parent d305baee71
commit 663d97f6dc
7 changed files with 1146 additions and 944 deletions

View File

@ -1,12 +1,12 @@
robots.txt,1761396900570,bfe106a3fb878dc83461c86818bf74fc1bdc7f28538ba613cd3e775516ce8b49 robots.txt,1761396900570,bfe106a3fb878dc83461c86818bf74fc1bdc7f28538ba613cd3e775516ce8b49
manifest.json,1761396900568,a9350a49aaac9fe94d3dd77b8270cc998c04ab97944a606189675022431faa51 manifest.json,1761396900568,a9350a49aaac9fe94d3dd77b8270cc998c04ab97944a606189675022431faa51
favicon.svg,1761396900561,a2a4880301751061a600b0bfc5c26fc413aed41e581516c4fa976bcb7fff6663 favicon.svg,1761396900561,a2a4880301751061a600b0bfc5c26fc413aed41e581516c4fa976bcb7fff6663
service-worker.js,1761576002341,023c58598eaf6728e3bbc8ed608e32fd608d37851fa12ce79816a48180c9cd31 service-worker.js,1761764783124,a4416108bc325f844ff2591db3c12fd9443bb2a3b4dedd09cee90e333e41bb51
asset-manifest.json,1761576002341,d747a6089e42c15217b542311c92d5809e7cae25a755c6242d55d93072ac5281 precache-manifest.f827a120dee6582417cfe4b1979f19d5.js,1761764783123,a4ff8d6cbb691d416e802893cc984f2135008b6167e99ee019b88d026a476a1f
precache-manifest.816159c176bc481e03384d794646b865.js,1761576002341,8b8a70a80ada7aa8b5e21993a642189e9d1dbed0805111893a8ae9cdd5038c23 static/media/tru-fals-icon.d0b962d8.svg,1761764783104,a1deab7e5db8e8a94a9c5812dcde5f8cbfbe5da790da8500bf106980b9c79c33
static/media/sub-ques-icon.366b4f1e.svg,1761576002260,8e397611007ec5db7581a4c1bcca006950390ddf652cae209bb3973a645af425 asset-manifest.json,1761764783148,995b77288d91dfaddb18b8753357c286829de77928b93c12f5111f35a76670b8
static/media/translate.610ad011.svg,1761576002262,c06f8a3d0c976b02429ce805d8a4944571ab0128ed2ce49c2d3ca134511ed120 index.html,1761764783123,19c7a6e51b117070c55ac4aae347ca3434878b774a65a4a185b0914d2965a1c0
static/media/student-engaged.53fb7b0b.svg,1761576002257,5f8e01d1a5efcbdf3aa00ed07de843ab84404a28b672ca91f4d70d89fa8b37cf static/media/translate.610ad011.svg,1761764783106,c06f8a3d0c976b02429ce805d8a4944571ab0128ed2ce49c2d3ca134511ed120
assets/images/locale-icon.svg,1761396900579,610498c7ca3b5800d268b1654473f0b1d79de2c6493a7c6982bef90456d35179 assets/images/locale-icon.svg,1761396900579,610498c7ca3b5800d268b1654473f0b1d79de2c6493a7c6982bef90456d35179
assets/images/icons/icon-96x96.png,1761396900602,43a8ac4df8945d7a44e3e4911032214f01eaea92267baa31a93e77942b424c73 assets/images/icons/icon-96x96.png,1761396900602,43a8ac4df8945d7a44e3e4911032214f01eaea92267baa31a93e77942b424c73
assets/images/icons/icon-72x72.png,1761396900600,a42f1df8ab0a8fe921573976d49158e1a2c52fe430460c869947f221aba30a94 assets/images/icons/icon-72x72.png,1761396900600,a42f1df8ab0a8fe921573976d49158e1a2c52fe430460c869947f221aba30a94
@ -16,52 +16,52 @@ assets/images/icons/icon-192x192.png,1761396900592,ac9fe46fbeb4c54fb3c838b645380
assets/images/icons/icon-152x152.png,1761396900590,95a5a117fbd5640f1f1f13c9923398545e3f5b66734ff6c8ce67c942319a8b67 assets/images/icons/icon-152x152.png,1761396900590,95a5a117fbd5640f1f1f13c9923398545e3f5b66734ff6c8ce67c942319a8b67
assets/images/icons/icon-144x144.png,1761396900588,e42168e0e1abb5bd7811ebe1b5a2183d0ce287bf266c2efd8d97a23d23ca8d00 assets/images/icons/icon-144x144.png,1761396900588,e42168e0e1abb5bd7811ebe1b5a2183d0ce287bf266c2efd8d97a23d23ca8d00
assets/images/icons/icon-128x128.png,1761396900585,45b577c86e7c03fef868bfa3f96810c5b36f31156c32c0d85620d2e6fe1fc85b assets/images/icons/icon-128x128.png,1761396900585,45b577c86e7c03fef868bfa3f96810c5b36f31156c32c0d85620d2e6fe1fc85b
index.html,1761576002341,7983b57b52860a70c9a6382d9b2bae797ed4b2b744dbd7af42a74464f9f5e2b2 static/media/student-engaged.53fb7b0b.svg,1761764783104,5f8e01d1a5efcbdf3aa00ed07de843ab84404a28b672ca91f4d70d89fa8b37cf
static/media/quizexam.5545802e.svg,1761576002242,dfc1278bfcd264264a4d0e0e0247c229335abf0573439c5e9829c8607aacd569 static/media/quizexam.5545802e.svg,1761764783100,dfc1278bfcd264264a4d0e0e0247c229335abf0573439c5e9829c8607aacd569
static/media/question.0c505ed9.svg,1761576002242,29938066f93476c487414bb7a5dd5227d891c8ab8e115a74a7e7ffccd8d87b36 static/media/sub-ques-icon.366b4f1e.svg,1761764783106,8e397611007ec5db7581a4c1bcca006950390ddf652cae209bb3973a645af425
static/media/questions-icon.3d3c1aaf.svg,1761576002254,197f459a359b00c1c44b6ddd71cbc9160f593d1fe08534e3711e9d72a2956ef1 static/media/questions-icon.3d3c1aaf.svg,1761764783104,197f459a359b00c1c44b6ddd71cbc9160f593d1fe08534e3711e9d72a2956ef1
static/media/practice.f05e6f00.svg,1761576002244,13b094bcbbb8c50906b1ee0ce68305059e99319464dd4147b4c2194dbb460475 static/media/PracticeKiaLogo.a8336af5.svg,1761764783097,1a592518bcfabb2f86669d06ae2fb7b2948bbdbfaf5a80d138ab30f4e482a0f8
static/media/PracticeKiaLogo.a8336af5.svg,1761576002241,1a592518bcfabb2f86669d06ae2fb7b2948bbdbfaf5a80d138ab30f4e482a0f8 static/media/practice.f05e6f00.svg,1761764783099,13b094bcbbb8c50906b1ee0ce68305059e99319464dd4147b4c2194dbb460475
static/media/practice-icon.bf603115.svg,1761576002256,27b6a729d91914d899f4b3c1f817e26ac58d2ec0be54dc9b93c01f28adcdd00c static/media/performance.88855f12.svg,1761764783101,03fc1b19005084049382091745d3f1f29a447763091ad4a9bc5d097c338671c9
static/media/perf-icon.1597a235.svg,1761576002258,7183482b785de425506f49deb27bba02e906132ac7ce5af01f7da8452ef64f28 static/media/perf-icon.1597a235.svg,1761764783104,7183482b785de425506f49deb27bba02e906132ac7ce5af01f7da8452ef64f28
static/media/performance.88855f12.svg,1761576002247,03fc1b19005084049382091745d3f1f29a447763091ad4a9bc5d097c338671c9 static/media/mul-res-icon.8ef3b097.svg,1761764783104,1f0a79350cb5546361ef02af1657ad36efeff833fd7bb61db88a20e127a290b4
static/media/tru-fals-icon.d0b962d8.svg,1761576002260,a1deab7e5db8e8a94a9c5812dcde5f8cbfbe5da790da8500bf106980b9c79c33 static/media/OdiSVGlogo.f0834bb1.svg,1761764783097,3ec1cab31a32db378894d796afedaee7c35f8ff99dfb1039b0bb757225a47235
static/media/mul-res-icon.8ef3b097.svg,1761576002257,1f0a79350cb5546361ef02af1657ad36efeff833fd7bb61db88a20e127a290b4 static/media/practice-icon.bf603115.svg,1761764783104,27b6a729d91914d899f4b3c1f817e26ac58d2ec0be54dc9b93c01f28adcdd00c
static/media/mul-cho-icon.b3dc9ea9.svg,1761576002260,e8edbceb0eb49f7629cf0b8edf599f9347acbf0c6d1df2fa6145cbed03f37e1f static/media/question.0c505ed9.svg,1761764783097,29938066f93476c487414bb7a5dd5227d891c8ab8e115a74a7e7ffccd8d87b36
static/media/locale-icon.b3596424.svg,1761576002254,68618c76952aa4c5f2623bb010514871688960bb3b9edbcda0eab7eb75442054 static/media/mul-cho-icon.b3dc9ea9.svg,1761764783104,e8edbceb0eb49f7629cf0b8edf599f9347acbf0c6d1df2fa6145cbed03f37e1f
static/media/OdiSVGlogo.f0834bb1.svg,1761576002242,3ec1cab31a32db378894d796afedaee7c35f8ff99dfb1039b0bb757225a47235 static/media/locale-icon.b3596424.svg,1761764783104,68618c76952aa4c5f2623bb010514871688960bb3b9edbcda0eab7eb75442054
static/media/getFetch.2b2b7da4.cjs,1761576002242,b2d82abee5b8af22b81d67fc20b3feef1eaaf04585dbc24ec755e3304c469096 static/media/GrayscalePKLogo.6bb74404.svg,1761764783097,576b38300e49eda6407adce420001e7ad0333cca317eb668aff457c3258dcca2
static/media/exam-icon.10f48851.svg,1761576002256,b766e9975582af716870a844f8deabd4d80e33a47c400d36cee6bc4840062693 static/media/getFetch.2b2b7da4.cjs,1761764783097,b2d82abee5b8af22b81d67fc20b3feef1eaaf04585dbc24ec755e3304c469096
static/media/GrayscalePKLogo.6bb74404.svg,1761576002242,576b38300e49eda6407adce420001e7ad0333cca317eb668aff457c3258dcca2 static/media/exam-icon.10f48851.svg,1761764783104,b766e9975582af716870a844f8deabd4d80e33a47c400d36cee6bc4840062693
static/media/delete-icon.da38c0f4.svg,1761576002263,8078772ee88a0588989eec477da1ca949430f3f06efaa9c2db860b74c65f6722 static/media/feature-2.36f8d7e2.webp,1761764783097,fc1c038517abf3b731ff3a4675cbaf1a6aa1150ca970762dd0ffc4199b92b75c
static/media/dashboard.8ec7624b.svg,1761576002242,5a5b5d4bf416d414842bd5eac30432b592342a392450e52943f067db4077bec1 static/media/delete-icon.da38c0f4.svg,1761764783107,8078772ee88a0588989eec477da1ca949430f3f06efaa9c2db860b74c65f6722
static/media/classes.3b73dba0.svg,1761576002244,2abfbb7015d5bccb8640fd0614d3782c048672c9543fe17d127db63287ac141d static/media/dashboard.8ec7624b.svg,1761764783097,5a5b5d4bf416d414842bd5eac30432b592342a392450e52943f067db4077bec1
static/media/Checkmark.1356376c.svg,1761576002270,aa56f27c8198bcae3236a881a7134cd3b7d3dbb048ec75654e8ce2d4710ce027 static/media/classes.3b73dba0.svg,1761764783100,2abfbb7015d5bccb8640fd0614d3782c048672c9543fe17d127db63287ac141d
static/media/feature-2.36f8d7e2.webp,1761576002242,fc1c038517abf3b731ff3a4675cbaf1a6aa1150ca970762dd0ffc4199b92b75c static/media/class-icon.6afd34b5.svg,1761764783103,a2f492c6c9c7b5201773062dfe90c238c12bc00bec097dc06141201f02b9588e
static/media/class-icon.6afd34b5.svg,1761576002254,a2f492c6c9c7b5201773062dfe90c238c12bc00bec097dc06141201f02b9588e static/media/Checkmark.1356376c.svg,1761764783113,aa56f27c8198bcae3236a881a7134cd3b7d3dbb048ec75654e8ce2d4710ce027
static/media/batch.3fcff66e.svg,1761576002244,f29038480286f091e3805a43f90ca5a70a13bbcef3076831e0b29b1fcb61d862 static/media/batch.3fcff66e.svg,1761764783097,f29038480286f091e3805a43f90ca5a70a13bbcef3076831e0b29b1fcb61d862
static/media/auth-BG.2835584f.svg,1761576002241,238d3a2ff1aead1c4aaed0e07d23d2c1164f8be0ef2d5fced5c5ade370f3c5bf static/media/auth-BG.2835584f.svg,1761764783094,238d3a2ff1aead1c4aaed0e07d23d2c1164f8be0ef2d5fced5c5ade370f3c5bf
static/media/add-circle.0011f2bc.svg,1761576002255,d9e5d90e8de1ce16df5720b43ca79543036fcf1a6cd6439a9b15c5a97d269f17 static/media/batch-icon.bf664771.svg,1761764783104,7519e2a92f436a2f46ef7420d721e24df2837d7fce7fe8c30f4bc12980b3d1a0
static/media/batch-icon.bf664771.svg,1761576002260,7519e2a92f436a2f46ef7420d721e24df2837d7fce7fe8c30f4bc12980b3d1a0 static/media/add-circle.0011f2bc.svg,1761764783104,d9e5d90e8de1ce16df5720b43ca79543036fcf1a6cd6439a9b15c5a97d269f17
static/js/runtime-main.96ceaa04.js,1761576002273,455ae6008568041ddd40d50a96c614534f9c268f4d0fff4c2700d0d9646ddc57 static/js/runtime-main.2bdeab81.js,1761764783115,5c04066aee33f757af11aff4783499664f995e641476c3c1552c373bf87171bb
static/js/4.7247a6ee.chunk.js.map,1761576002344,e708b3fc1a1b324a3b6c468da749aaa4361b2f44d7c9b2e1614165faa04e072b static/js/4.568469c1.chunk.js.map,1761764783148,f626752d2d2cffe76387c84c314248f1ab02d9c6a79a16442a98e124c8f7d5fa
static/js/runtime-main.96ceaa04.js.map,1761576002341,454ee9efc7767f76d20d24cb4395b51fe7735f63f7ceb89ebbe56f138716b92b static/js/runtime-main.2bdeab81.js.map,1761764783148,a6cd518ca4773cf5562057dad7acb281edac1b802572abcdc6ee19c33f02b744
static/js/4.7247a6ee.chunk.js,1761576002280,32fa7d3f8777fffae2bc6dc1de3608e836c27d9044fe4be8fab745f6c08cb373 static/js/4.568469c1.chunk.js,1761764783115,7f998d124bddb5cb8e916a7bb559ddd2f5c133920e79651382bbabf25affc9c4
static/js/3.5470394c.chunk.js.map,1761576002343,0aed88423ebfa4792f1a2daf5809524a872b20f67a0ecb6e1becc7a52fa1c423 static/js/3.d75ced33.chunk.js.map,1761764783148,a12fbfb03ced36f6a8c055bf8b3961a9170b3676c8e4f545921152b23e9bc4dd
static/js/3.5470394c.chunk.js,1761576002279,564d1d12db256f1c984a60c05dfc4e34c782c3c8b1b548447c5c30dd71aba0d6 static/js/3.d75ced33.chunk.js,1761764783115,c68f66b973c4099ec168a05edb6212303bf31b91c57a2adc0a46595d31262355
static/js/2.337cf2a6.chunk.js.LICENSE.txt,1761576002280,9c84bc4d2f8584d32d75e01e0317e22af1e39f5ac5ded3e2e4e34984704c172b static/js/2.ac9f0d75.chunk.js.LICENSE.txt,1761764783115,9c84bc4d2f8584d32d75e01e0317e22af1e39f5ac5ded3e2e4e34984704c172b
static/media/logo.0dd03933.png,1761576002242,b9716ed1f565a052edc1154a207334de81856339e8ca43d5d8f51041f3785085 static/media/logo.0dd03933.png,1761764783097,b9716ed1f565a052edc1154a207334de81856339e8ca43d5d8f51041f3785085
static/media/feature-1.ea5c34ea.svg,1761576002234,d802659785b69508e521d5543fca40a58cb9076521ffc3e6b006f4786b8079f8 static/media/feature-1.ea5c34ea.svg,1761764783097,d802659785b69508e521d5543fca40a58cb9076521ffc3e6b006f4786b8079f8
static/media/feature-3.3d5d81e9.svg,1761576002242,ba23393d89776cb78f7a559903baff48be76665abd99bdd4e5ea103c5e5ed88b static/media/feature-3.3d5d81e9.svg,1761764783097,ba23393d89776cb78f7a559903baff48be76665abd99bdd4e5ea103c5e5ed88b
static/js/main.e80be951.chunk.js,1761576002272,b15fab5b06dc6f90c0f6677def9a66a9d9609026af14db6e2c114c27c1c22db9 static/js/main.962d5966.chunk.js,1761764783108,79a04f8bcc7d0c6dc396754ba1e4010597781782bc2743b2074a2c5a4107412b
static/css/main.3624148f.chunk.css,1761576002265,6a13470e019853946811aebb68baac2e094470affc5520d835a986b420b1edab static/css/main.b90ebd71.chunk.css,1761764783107,47c0c4a15920e9cea3ceba32d4bc59829a376a7f4347c15b82d7e8878d59008d
static/css/4.629184c9.chunk.css,1761576002280,dfeba86da1256521df7a877a265d56d314f65016837f30878338c2b7fc62a6ab static/css/2.561a8df6.chunk.css,1761764783113,c87247fc5cf38902aee0ee29244f6ab0c4bc14e5a0a1d6860cd771410dc826f8
static/css/2.561a8df6.chunk.css,1761576002273,c87247fc5cf38902aee0ee29244f6ab0c4bc14e5a0a1d6860cd771410dc826f8 static/css/4.629184c9.chunk.css,1761764783115,dfeba86da1256521df7a877a265d56d314f65016837f30878338c2b7fc62a6ab
static/css/3.6c5a3051.chunk.css,1761576002280,1ae150a22b1d42322775be9055605f93c75188e7081cfbdb206a59fc2f5ab8f1 static/css/3.6c5a3051.chunk.css,1761764783113,1ae150a22b1d42322775be9055605f93c75188e7081cfbdb206a59fc2f5ab8f1
static/js/main.e80be951.chunk.js.map,1761576002342,c7567119cb47238c88452f154ae648333c59cd204a9f3734b3b90ae2a422f534 static/js/main.962d5966.chunk.js.map,1761764783148,c15e557e07ac2c5dd288726a7e260295a75111bcdd614be16ec21bb71dcb9e8c
static/css/4.629184c9.chunk.css.map,1761576002292,b82aa57e6e357dbb20067334e544e9b30d20ed0d928c13e6c61e1747e6f5a205 static/css/4.629184c9.chunk.css.map,1761764783148,b82aa57e6e357dbb20067334e544e9b30d20ed0d928c13e6c61e1747e6f5a205
static/css/3.6c5a3051.chunk.css.map,1761576002289,bcc301c3b3b46c96afc0fc028a6aa3cd299ccda538d7c6cb9ebc87f8de535796 static/css/3.6c5a3051.chunk.css.map,1761764783124,bcc301c3b3b46c96afc0fc028a6aa3cd299ccda538d7c6cb9ebc87f8de535796
static/css/2.561a8df6.chunk.css.map,1761576002281,ed9f46fc5b36bd835967cfd533fb33fd9ceec9c3aadcbe6c12bfb90b3ad1bc83 static/css/main.b90ebd71.chunk.css.map,1761764783116,bff960067cfb59c88b8ec495e5b5334532e05ae898ad3f3a78ee293f74fb7855
static/css/main.3624148f.chunk.css.map,1761576002281,588c07ee54c0d7bc5ec65c6e4ab3890e922520cac4eac29b6fd638715a5ac5d1 static/css/2.561a8df6.chunk.css.map,1761764783122,ed9f46fc5b36bd835967cfd533fb33fd9ceec9c3aadcbe6c12bfb90b3ad1bc83
static/js/2.337cf2a6.chunk.js,1761576002282,e0edd93ede2589762ee74717e09fd5917dcb2660daa4ac6332bd8bc5b3c676ec static/js/2.ac9f0d75.chunk.js,1761764783116,a2f10af26647312232ac4cb0601a77496d561a7494b9db1206e2f8632813d0b6
static/js/2.337cf2a6.chunk.js.map,1761576002358,b607efb90aec0e30321307c0738064e935f3d4b90d423885c732d57f1ebba603 static/js/2.ac9f0d75.chunk.js.map,1761764783158,093171b5c14d2415bee5b4dbbf9caa643ee948acbb18398fbc5293ec5e9009aa

View File

@ -32,7 +32,7 @@ const Headerc = (props) => {
language_code: "", language_code: "",
language_name: "", language_name: "",
classID: "", classID: "",
batchID: "", batch_id: "",
}); });
const [languages, setlanguages] = useState([]); const [languages, setlanguages] = useState([]);
@ -114,13 +114,13 @@ const Headerc = (props) => {
// Persist last selected batch if available // Persist last selected batch if available
const storedUser = JSON.parse(sessionStorage.getItem("currentUser")) || {}; const storedUser = JSON.parse(sessionStorage.getItem("currentUser")) || {};
if (storedUser.batchID) { if (storedUser.batch_id) {
setState(prev => ({ ...prev, batchID: storedUser.batchID })); setState(prev => ({ ...prev, batch_id: storedUser.batch_id }));
} else if (batchData.length > 0) { } else if (batchData.length > 0) {
// Default to first batch // Default to first batch
storedUser.batchID = batchData[0].id; storedUser.batch_id = batchData[0].id;
authenticationService.currentUserValue.batchID = batchData[0].id; authenticationService.currentUserValue.batch_id = batchData[0].id;
setState(prev => ({ ...prev, batchID: batchData[0].id })); setState(prev => ({ ...prev, batch_id: batchData[0].id }));
sessionStorage.setItem("currentUser", JSON.stringify(storedUser)); sessionStorage.setItem("currentUser", JSON.stringify(storedUser));
} }
@ -166,11 +166,11 @@ const Headerc = (props) => {
/** ✅ Handle Batch Change **/ /** ✅ Handle Batch Change **/
const onClickBatchChange = (_, objectSelected) => { const onClickBatchChange = (_, objectSelected) => {
authenticationService.currentUserValue.batchID = objectSelected.value; authenticationService.currentUserValue.batch_id = objectSelected.value;
const storedUser = JSON.parse(sessionStorage.getItem("currentUser")) || {}; const storedUser = JSON.parse(sessionStorage.getItem("currentUser")) || {};
storedUser.batchID = objectSelected.value; storedUser.batch_id = objectSelected.value;
sessionStorage.setItem("currentUser", JSON.stringify(storedUser)); sessionStorage.setItem("currentUser", JSON.stringify(storedUser));
setState(prev => ({ ...prev, batchID: objectSelected.value })); setState(prev => ({ ...prev, batch_id: objectSelected.value }));
}; };
function onClickLanguageChange(_, objectSelected) { function onClickLanguageChange(_, objectSelected) {
@ -246,7 +246,7 @@ const Headerc = (props) => {
)} )}
dropdownClassName="batch-select-dropdown" dropdownClassName="batch-select-dropdown"
onChange={onClickBatchChange} onChange={onClickBatchChange}
value={batches.length > 0 ? state.batchID : undefined} value={batches.length > 0 ? state.batch_id : undefined}
> >
{batches.map((batchItem) => ( {batches.map((batchItem) => (
<Select.Option <Select.Option

View File

@ -1,242 +1,307 @@
li.MyExam{ .exam-header {
-moz-box-shadow: 0 0 5px #888;
-webkit-box-shadow: 0 0 5px#888;
box-shadow: 0 0 5px #E1E1E1;
margin-bottom: 10px;
padding:5px 35px 5px 25px;
border-radius: 7px;
}
li.MyExam table{
width:80%;
}
span.MyExamImage{
width: 100px;
height: 100px;
line-height: 40px;
}
span.MyExamImage img{
width: 100px;
height: 100px;
}
div.MyExamDiv table tr> th{
border:none;
padding: 0 0 0 10px;
text-align: left;
font-weight: normal;
}
div.MyExamDiv table tr> td{
border:none;
padding: 0 0 0 10px;
text-align: left;
font-weight: bolder;
}
li.MyExam h4.ant-list-item-meta-title{
padding: 0 0 0 10px;
}
li.MyExam span.MyExamSpan{
padding: 0 0 0 10px;
}
.ExamNameHeader {
font-size: 18pt;
float: left;
color: white;
}
.ExamTimeHeader{
font-size: 16pt;
float: right;
color: white;
}
.ExamTimeHeader div{
font-size: 13pt;
float:none;
color: white;
}
.MainExamLayout{
flex-direction: row;
}
.createExamLayoutLeft{
display: flex;
flex: auto;
flex-direction: column;
background: #ffffff !important;
border-style: solid;
border-width: 1px;
border-color: rgb(231, 230, 230);
padding: 10px;
width:70%;
}
.createExamLayoutRight{
display: flex;
flex: auto;
flex-direction: column;
background: #ffffff !important;
border-style: solid;
border-width: 1px;
border-color: rgb(231, 230, 230);
padding: 10px;
position:sticky;
position: -webkit-sticky;
top:0
}
.StatusLabel{
padding: 5px;
}
.ExamAvatar{
padding: 25px 0px 25px 0px;
}
.ExamStatusTable td{
padding: 5px;
}
div.AttemptQuestionGrid{
grid-template-columns: repeat(auto-fit, minmax(221px, 1fr));
display: grid;
margin-left: 0px!important;
margin-right: 0px!important;
}
div.AttemptQuestionGrid > div.ant-col{
padding-right: 0px !important;
padding-left: 0px !important;
}
.QuestionCard{
height: 336px;
margin: 12px;
max-width: 282px;
float: left;
border-radius: 16px;
cursor: pointer;
color:#150F2D;
font-size: 13px;
text-overflow: ellipsis;
width:90%;
box-shadow: 0 0 5px #e1e1e1;
}
.QuestionCard div.ant-card-head{
border-bottom:none;
padding: 0 10px;
}
.QuestionCard div.ant-card-head div.ant-card-head-wrapper .ant-card-head-title
{
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
background: #ffffff;
border-bottom: 1px solid #f0f0f0;
padding: 0 24px;
height: 64px;
box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
} }
.QuestionCard div.ant-card-body{
height: 67%; .exam-header-left .exam-title {
padding:16px; font-size: 18px;
font-weight: 600;
color: #1f1f1f;
} }
.QuestionCard ul.ant-card-actions{
background: none; .exam-header-center {
border-top:none; flex: 1;
width: max-content;
background-color: #EAECF1;
height: 20px;
padding: 1px 4px;
box-sizing: border-box;
display: flex; display: flex;
align-items: center; justify-content: center;
justify-content: space-around;
border-radius: 4px;
float: right;
margin-right: 5px;
}
.QuestionCard ul.ant-card-actions > li > span{
font-size: 12px;
}
.QuestionCard ul.ant-card-actions > li:not(:last-child) {
border-right: none;
}
.Options{
margin-left:13px;
} }
.QuesttionStatusNotVisited{ .exam-countdown .ant-statistic-title {
background-color: #F0F0F0 color: #555;
}
.QuesttionStatusNotAnswered{
background-color: #FED911
}
.QuesttionStatusAnswered{
background-color: #54C51D
}
.QuesttionStatusReview{
background-color: #A028FF
}
.QuesttionStatusAnsweredReview{
background-color: #FFA500
}
.QuesttionStatusNotVisitedCard{
border-color: #F0F0F0
}
.QuesttionStatusNotAnsweredCard{
border-color: #FED911
}
.QuesttionStatusAnsweredCard{
border-color: #54C51D
}
.QuesttionStatusReviewCard{
border-color: #A028FF
}
.QuesttionStatusAnsweredReviewCard{
border-color: #FFA500
}
div.QuestionStatusHeaderIcon{
height: 8px;
width: 8px;
border-radius: 274.76px;
box-shadow: 0 0 15px 0 rgba(0,0,0,0.1);
}
div.HeaderQuestionStatusLabel{
-webkit-font-smoothing: antialiased;
font-size: 12px;
line-height: 1.125rem;
font-weight: 500;
letter-spacing: .25px;
text-decoration: none;
text-transform: none;
}
.QuesttionStatusNotVisitedTitle{
color: #F0F0F0
}
.QuesttionStatusNotAnsweredTitle{
color: #FED911
}
.QuesttionStatusAnsweredTitle{
color: #54C51D
}
.QuesttionStatusReviewTitle{
color: #A028FF
}
.QuesttionStatusAnsweredReviewTitle{
color: #FFA500
}
.Outline{ display: flex;
justify-content: flex-end;
align-items: center;
width: 77%;
font-size: 14px; font-size: 14px;
} }
.MainExamQuestionLayout{ .exam-countdown .ant-statistic-content {
padding: 20px; font-size: 16px;
font-weight: 600;
color: #1677ff;
}
.exam-header-right {
display: flex;
gap: 10px;
}
.exam-header-right .ant-btn {
border-radius: 6px;
}
/* Card container */
.create-exam-card {
background: #ffffff;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
padding: 24px;
margin: 24px;
transition: box-shadow 0.3s ease;
}
.create-exam-card:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
}
/* Tabs styling */
.create-exam-tabs {
font-size: 16px;
}
.create-exam-tabs .ant-tabs-nav {
margin-bottom: 20px;
border-bottom: 2px solid #f0f0f0;
}
.create-exam-tabs .ant-tabs-tab {
font-weight: 500;
color: #555;
transition: color 0.2s ease;
padding: 10px 16px;
border-radius: 0;
background: transparent !important;
}
/* ✨ Hover effect only text color changes */
.create-exam-tabs .ant-tabs-tab:hover {
color: #1677ff;
background: transparent !important;
}
/* Active tab only bold text + blue ink bar */
.create-exam-tabs .ant-tabs-tab-active {
color: #1677ff !important;
font-weight: 600;
background: transparent !important;
}
/* Ink bar thin blue underline */
.create-exam-tabs .ant-tabs-ink-bar {
background: #1677ff;
height: 3px;
border-radius: 3px;
}
.exam-status-card {
background: #ffffff;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
padding: 24px;
margin: 24px;
transition: box-shadow 0.3s ease;
}
.exam-status-card:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
}
.exam-status-section {
margin-bottom: 24px;
}
.exam-status-table {
width: 100%;
border-collapse: separate;
border-spacing: 16px 12px;
}
.status-item {
display: flex;
align-items: center;
gap: 10px;
}
.status-label {
font-size: 15px;
font-weight: 500;
color: #444;
}
/* Avatar badges */
.status-avatar {
font-weight: 600;
color: #fff;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.15);
}
/* Individual colors for clarity */
.status-avatar.not-visited {
background-color: #d9d9d9;
}
.status-avatar.not-answered {
background-color: #ff7875;
}
.status-avatar.answered {
background-color: #52c41a;
}
.status-avatar.to-review {
background-color: #faad14;
}
.status-avatar.answered-review {
background-color: #1890ff;
}
/* Collapse styling */
.exam-accordion .ant-collapse {
border: none;
background: #fafafa;
border-radius: 8px;
}
.exam-accordion .ant-collapse-item {
border-bottom: 1px solid #f0f0f0;
}
.exam-accordion .ant-collapse-header {
font-weight: 500;
color: #333;
transition: color 0.2s ease;
}
.exam-accordion .ant-collapse-header:hover {
color: #1677ff;
}
.AttemptQuestionGrid {
display: flex;
flex-wrap: wrap;
gap: 20px; /* space between cards */
justify-content: flex-start;
}
.question-col {
flex: 1 1 250px; /* make responsive */
min-width: 250px;
max-width: 300px;
}
.question-card {
margin: 10px;
border-radius: 12px !important;
overflow: hidden;
transition: all 0.2s ease-in-out;
}
/* Add a subtle hover effect */
.question-card:hover {
transform: translateY(-4px);
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.12);
}
/* Header (title + type tag) */
.question-card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 14px;
}
/* Title (avatar + status label) */
.question-title {
display: flex;
align-items: center;
gap: 10px;
}
/* Question content */
.question-content {
margin-top: 10px;
line-height: 1.6;
font-size: 15px;
}
/* Tag styling */
.question-type {
font-size: 12px;
border-radius: 8px;
padding: 2px 8px;
}
.MainExamQuestionLayout {
background: #ffffff;
border-radius: 16px;
padding: 28px 36px;
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.1);
margin: 30px 20px; /* ⬅️ More margin around the card */
transition: box-shadow 0.3s ease, transform 0.2s ease;
}
.MainExamQuestionLayout:hover {
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.12);
transform: translateY(-2px);
}
.question-header {
margin-bottom: 20px;
}
.question-title {
font-size: 1.3rem;
font-weight: 600;
color: #1f1f1f;
line-height: 1.6;
}
.question-options {
margin-top: 16px;
margin-bottom: 24px;
}
.radio-group {
display: flex;
flex-direction: column;
gap: 12px;
}
.radioStyle {
font-size: 16px;
line-height: 1.5;
}
.review-switch {
margin-bottom: 28px;
}
.question-actions {
display: flex;
justify-content: flex-end;
}
.action-buttons {
display: flex;
gap: 12px; /* ⬅️ spacing between buttons */
}
.rounded-btn {
border-radius: 10px !important; /* ⬅️ rounded corners */
font-size: 15px;
height: 40px;
padding: 0 20px;
}
.ant-btn-primary {
background-color: #1677ff;
border-color: #1677ff;
}
.ant-btn-primary:hover {
background-color: #3b89ff;
border-color: #3b89ff;
} }

View File

@ -1,9 +1,21 @@
import React from "react"; import React from "react";
import "./AttemptExam.css"; import "./AttemptExam.css";
import { authenticationService } from '../../../../_services'; import { authenticationService } from "../../../../_services";
import { selectorService } from "../../../../services/selectorService"; import { selectorService } from "../../../../services/selectorService";
import { Layout, Avatar, Button, List, Tabs, Card, Collapse, Statistic, Col, Row } from 'antd'; import {
import { Link } from 'react-router-dom'; Layout,
Avatar,
Button,
List,
Tabs,
Card,
Collapse,
Statistic,
Col,
Row,
Tag
} from "antd";
import { Link } from "react-router-dom";
import QuestionDetail from "./QuestionDetail"; import QuestionDetail from "./QuestionDetail";
import parse from "html-react-parser"; import parse from "html-react-parser";
@ -12,24 +24,25 @@ const { Header } = Layout;
const { TabPane } = Tabs; const { TabPane } = Tabs;
const { Panel } = Collapse; const { Panel } = Collapse;
const { Countdown } = Statistic; const { Countdown } = Statistic;
class AttemptExam extends React.Component { class AttemptExam extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
currentUser: authenticationService.currentUserValue, currentUser: authenticationService.currentUserValue,
exam_id: 'props.location.state.examId', exam_id: "props.location.state.examId",
showQuestion: false, showQuestion: false,
activeDetailQuestion: {}, activeDetailQuestion: {},
activeQuestionIndex: -1, activeQuestionIndex: -1,
pauseUpdate: false pauseUpdate: false,
}; };
this.onGetExamAttempt = this.onGetExamAttempt.bind(this); this.onGetExamAttempt = this.onGetExamAttempt.bind(this);
this.callbackFunction = this.callbackFunction.bind(this); this.callbackFunction = this.callbackFunction.bind(this);
this.callbackFunctionNext = this.callbackFunctionNext.bind(this); this.callbackFunctionNext = this.callbackFunctionNext.bind(this);
this.sendExamUpdate =this.sendExamUpdate.bind(this); this.sendHeartbeat = this.sendHeartbeat.bind(this);
this.stopExamUpdate = this.stopExamUpdate.bind(this); this.stopExamUpdate = this.stopExamUpdate.bind(this);
this.updateExamStatus = this.updateExamStatus.bind(this);
this.onSubmitExam = this.onSubmitExam.bind(this); this.onSubmitExam = this.onSubmitExam.bind(this);
this.onPauseExam = this.onPauseExam.bind(this); this.onPauseExam = this.onPauseExam.bind(this);
this.getExamModelToUpdate = this.getExamModelToUpdate.bind(this); this.getExamModelToUpdate = this.getExamModelToUpdate.bind(this);
@ -37,15 +50,13 @@ class AttemptExam extends React.Component {
componentDidMount() { componentDidMount() {
this.onGetExamAttempt(); this.onGetExamAttempt();
this.sendExamUpdate(); this.sendHeartbeat();
} }
getExamModelToUpdate() { getExamModelToUpdate() {
let tempAllQuestions = this.state.allQuestions; let tempAllQuestions = this.state.allQuestions;
let questionsArray = []; let questionsArray = [];
if(tempAllQuestions === undefined) if (tempAllQuestions === undefined) return;
return;
tempAllQuestions.map((question, index) => { tempAllQuestions.map((question, index) => {
let quest = {}; let quest = {};
quest.question_id = question.id; quest.question_id = question.id;
@ -56,44 +67,48 @@ class AttemptExam extends React.Component {
let answers = []; let answers = [];
for (let i = 0; i < options.length; i++) { for (let i = 0; i < options.length; i++) {
if (options[i].isSelected) { if (options[i].isSelected) {
let ans = {} let ans = {};
ans.id = options[i].id; ans.id = options[i].id;
answers.push(ans); answers.push(ans);
} }
} }
quest.answers = answers; quest.answers = answers;
if(quest.is_visited) if (quest.is_visited) questionsArray.push(quest);
questionsArray.push(quest); });
})
return questionsArray; return questionsArray;
} }
onPauseExam() { onPauseExam() {
let questionsArray = this.getExamModelToUpdate();
this.stopExamUpdate(); this.stopExamUpdate();
selectorService.pauseExam(this.state.attempt_id).then((data) => {
selectorService.pauseExam(this.state.attempt_id)
.then((_data) => {
console.log("Exam Paused"); console.log("Exam Paused");
// ✅ Go back to the previous page after pausing the exam
window.history.back();
})
.catch((error) => {
console.error("Error pausing exam:", error);
}); });
} }
onSubmitExam() { onSubmitExam() {
let questionsArray = this.getExamModelToUpdate(); let questionsArray = this.getExamModelToUpdate();
this.stopExamUpdate(); this.stopExamUpdate();
selectorService.endExam(this.state.attempt_id,questionsArray).then((data) => {
console.log(data); selectorService.endExam(this.state.attempt_id, questionsArray)
.then((_data) => {
console.log("Exam Ended"); console.log("Exam Ended");
// ✅ Go back to the previous page after exam ends
window.history.back();
})
.catch((error) => {
console.error("Error ending exam:", error);
}); });
} }
stopExamUpdate() { stopExamUpdate() {
clearInterval(this.interval); clearInterval(this.interval);
} }
@ -102,37 +117,27 @@ class AttemptExam extends React.Component {
this.stopExamUpdate(); this.stopExamUpdate();
} }
sendExamUpdate(){ sendHeartbeat() {
this.interval = setInterval(() => { this.interval = setInterval(() => {
if (this.state.pauseUpdate) { if (this.state.pauseUpdate) {
this.stopExamUpdate(); this.stopExamUpdate();
} } else {
else{ selectorService
this.updateExamStatus(); .heartbeat(this.state.attempt_id)
console.log("UpdateAnswer"); .then((data) => {
const result = data.result;
this.setState({
time_left: result,
});
});
} }
}, 60000); }, 60000);
} }
updateExamStatus(){
let questionsArray = this.getExamModelToUpdate();
console.log(questionsArray);
selectorService.updateExamStatus(this.state.attempt_id,questionsArray).then((data) => {
const result = data.result;
this.setState({
time_left: result,
});
console.log(result);
});
}
showDetailQuestion(question) { showDetailQuestion(question) {
question.isVisited = true; question.isVisited = true;
this.state.sections[question.sectionIndx].questions[question.secQIndex] = question; this.state.sections[question.sectionIndx].questions[question.secQIndex] =
question;
this.state.allQuestions[question.index - 1] = question; this.state.allQuestions[question.index - 1] = question;
let tempSections = this.state.sections; let tempSections = this.state.sections;
let tempAllQuestions = this.state.allQuestions; let tempAllQuestions = this.state.allQuestions;
@ -141,8 +146,8 @@ class AttemptExam extends React.Component {
activeDetailQuestion: question, activeDetailQuestion: question,
activeQuestionIndex: question.index - 1, activeQuestionIndex: question.index - 1,
sections: tempSections, sections: tempSections,
allQuestions: tempAllQuestions allQuestions: tempAllQuestions,
}) });
this.forceUpdate(); this.forceUpdate();
} }
@ -155,36 +160,34 @@ class AttemptExam extends React.Component {
this.setState({ this.setState({
showQuestion: false, showQuestion: false,
activeDetailQuestion: {}, activeDetailQuestion: {},
activeQuestionIndex: -1 activeQuestionIndex: -1,
}) });
} }
onGetExamAttempt() { onGetExamAttempt() {
var attemptId; var attemptId;
selectorService
selectorService.createExamAttempt(this.props.location.state.examId).then((data) => { .createExamAttempt(this.props.location.state.examId)
.then((data) => {
const result = data.result; const result = data.result;
attemptId = result.attempt_id; attemptId = result.attempt_id;
let index1 = 0; let index1 = 0;
selectorService.getExamAttempt(attemptId).then((data) => { selectorService.getExamAttempt(attemptId).then((data) => {
const result = data.result; const result = data.result;
let sections1 = []; let sections1 = [];
let questionsArray = []; let questionsArray = [];
sections1 = result.sections.map((item, secIndex) => { sections1 = result.sections.map((item, secIndex) => {
let quest = []; let quest = [];
quest = item.questions.map((question, index) => { quest = item.questions.map((question, index) => {
index1 = index1 + 1 index1 = index1 + 1;
question.index = index1; question.index = index1;
question.sectionIndx = secIndex; question.sectionIndx = secIndex;
question.secQIndex = index; question.secQIndex = index;
questionsArray.push(question); questionsArray.push(question);
return question; return question;
}) });
item.questions = quest; item.questions = quest;
return item return item;
}); });
this.setState({ this.setState({
attempt_id: attemptId, attempt_id: attemptId,
@ -194,19 +197,13 @@ class AttemptExam extends React.Component {
sections: sections1, sections: sections1,
time_left: result.time_left, time_left: result.time_left,
deadline: Date.now() + result.time_left * 1000, deadline: Date.now() + result.time_left * 1000,
allQuestions:questionsArray allQuestions: questionsArray,
}); });
}); });
}); });
}
};
callbackFunction = (question) => { callbackFunction = (question) => {
// console.log("Parent recieved Selector Data: "+ childData);
let q = question; let q = question;
let indx = q.index; let indx = q.index;
let secQuesIndx = q.secQIndex; let secQuesIndx = q.secQIndex;
@ -217,17 +214,38 @@ class AttemptExam extends React.Component {
this.setState({ this.setState({
showQuestion: true, showQuestion: true,
sections: tempSections, sections: tempSections,
allQuestions: tempAllQuestions allQuestions: tempAllQuestions,
}) });
} };
callbackFunctionNext = (childData) => { callbackFunctionNext = (childData) => {
// console.log("Parent recieved Selector Data: "+ childData); // ✅ Extract selected answer IDs (just numbers)
let q = childData; const selectedAnswers = childData.options
let indx = q.index; .filter(option => option.isSelected)
let nxtQuestion = this.state.allQuestions[(indx)]; .map(option => option.id); // <-- plain IDs, not objects
// ✅ Build the JSON object like your Kotlin version
const answerObject = {
question_id: childData.id,
answer_duration: childData.answer_duration || 10,
is_reviewed: childData.is_reviewed || false,
answers: selectedAnswers
};
// ✅ Send it (stringify the full object)
selectorService.updateExamStatus(this.state.attempt_id, JSON.stringify(answerObject));
// ✅ Load next question
const q = childData;
const indx = q.index;
const nxtQuestion = this.state.allQuestions[indx];
this.showDetailQuestion(nxtQuestion); this.showDetailQuestion(nxtQuestion);
};
callbackFunctionUpdateAnswer = (question) => {
console.log("Answer Updated", question);
} }
render() { render() {
let listItems = ""; let listItems = "";
let accordions = ""; let accordions = "";
@ -237,7 +255,14 @@ class AttemptExam extends React.Component {
let total_Answered = 0; let total_Answered = 0;
let total_AnsweredReviwed = 0; let total_AnsweredReviwed = 0;
let total_Reviwed = 0; let total_Reviwed = 0;
let questionStatusLabel ={"QuesttionStatusNotAnswered":"Not Answered","QuesttionStatusAnswered":"Answered","QuesttionStatusReview":"To Review","QuesttionStatusAnsweredReview":"Answered and To Review"};
let questionStatusLabel = {
QuesttionStatusNotAnswered: "Not Answered",
QuesttionStatusAnswered: "Answered",
QuesttionStatusReview: "To Review",
QuesttionStatusAnsweredReview: "Answered and To Review",
};
if (this.state.sections !== undefined) { if (this.state.sections !== undefined) {
let tempSections = this.state.sections.map((item, index) => { let tempSections = this.state.sections.map((item, index) => {
let tempQuestions = item.questions.map((question) => { let tempQuestions = item.questions.map((question) => {
@ -249,11 +274,9 @@ class AttemptExam extends React.Component {
answered.push(options[i]); answered.push(options[i]);
} }
} }
if(answered.length>0) if (answered.length > 0) isAnswered = true;
isAnswered = true;
let classN = "QuesttionStatusNotVisited"; let classN = "QuesttionStatusNotVisited";
if(question.isVisited) if (question.isVisited) classN = "QuesttionStatusNotAnswered";
classN = "QuesttionStatusNotAnswered";
if (isAnswered && !question.isReviewMarked) if (isAnswered && !question.isReviewMarked)
classN = "QuesttionStatusAnswered"; classN = "QuesttionStatusAnswered";
if (question.isReviewMarked && isAnswered) { if (question.isReviewMarked && isAnswered) {
@ -264,147 +287,204 @@ class AttemptExam extends React.Component {
} }
question.classN = classN; question.classN = classN;
question.statusLabel = questionStatusLabel[classN]; question.statusLabel = questionStatusLabel[classN];
if(classN === "QuesttionStatusReview") if (classN === "QuesttionStatusReview") total_Reviwed += 1;
total_Reviwed=total_Reviwed+1;
if (classN === "QuesttionStatusAnsweredReview") if (classN === "QuesttionStatusAnsweredReview")
total_AnsweredReviwed=total_AnsweredReviwed+1; total_AnsweredReviwed += 1;
if(classN === "QuesttionStatusAnswered") if (classN === "QuesttionStatusAnswered") total_Answered += 1;
total_Answered=total_Answered+1; if (classN === "QuesttionStatusNotAnswered") total_notAnswered += 1;
if(classN === "QuesttionStatusNotAnswered") if (classN === "QuesttionStatusNotVisited") total_notVisited += 1;
total_notAnswered=total_notAnswered+1;
if(classN === "QuesttionStatusNotVisited")
total_notVisited=total_notVisited+1;
return question; return question;
}) });
item.questions = tempQuestions; item.questions = tempQuestions;
return item; return item;
}) });
this.state.sections = tempSections; this.state.sections = tempSections;
let index1 = 0; let index1 = 0;
activeQuestion = <Layout><QuestionDetail totalQuestion={this.state.allQuestions.length} question={this.state.activeDetailQuestion} parentCallbackNext = {this.callbackFunctionNext} parentCallback = {this.callbackFunction}/></Layout>; activeQuestion = (
listItems = this.state.sections.map((item, index) => <Layout>
<QuestionDetail
totalQuestion={this.state.allQuestions.length}
question={this.state.activeDetailQuestion}
updateAnswerCallback={this.callbackFunctionUpdateAnswer}
parentCallbackNext={this.callbackFunctionNext}
parentCallback={this.callbackFunction}
/>
</Layout>
);
listItems = this.state.sections.map((item, index) => (
<TabPane tab={item.subject_name} key={index}> <TabPane tab={item.subject_name} key={index}>
{ {this.state.showQuestion ? (
this.state.showQuestion?activeQuestion: activeQuestion
<Row gutter={40} className="AttemptQuestionGrid"> ) : (
<Row gutter={40} className="AttemptQuestionGrid" style={{ padding: "20px" }}>
{item.questions.map((question, index) => { {item.questions.map((question, index) => {
let title = <><Avatar className={question.classN}>{question.index}</Avatar><div className='Outerline'> let title = (
<div className={"HeaderQuestionStatusLabel "+question.classN+"Title"}>{question.statusLabel}</div> <>
<Avatar className={question.classN}>{question.index}</Avatar>
<div className="Outerline">
<div
className={
"HeaderQuestionStatusLabel " +
question.classN +
"Title"
}
>
{question.statusLabel}
</div>
</div>
</>
);
</div></> return (
<Col key={question.id} className="question-col">
<Card
hoverable
className={`question-card ${question.classN}-card`}
onClick={() => this.showDetailQuestion(question)}
>
<div className="question-card-header">
<span className="question-title">{title}</span>
<Tag color="blue" className="question-type">
{question.type_text}
</Tag>
</div>
return <Col key={question.id} > <div className="question-content">
<Card title={title} actions={[
question.type_text
]} className={"QuestionCard "+question.classN+"Card"} onClick={this.showDetailQuestion.bind(this,question)}>
<p>{parse(question.question_text)}</p> <p>{parse(question.question_text)}</p>
</div>
</Card> </Card>
</Col> </Col>
);
})} })}
</Row> </Row>
} )}
</TabPane> </TabPane>
));
accordions = this.state.sections.map((item, index) => (
);
accordions = this.state.sections.map((item, index) =>
<Panel header={item.subject_name} key={index}> <Panel header={item.subject_name} key={index}>
<Row gutter={20}> <Row gutter={20}>
{item.questions.map((question) => { {item.questions.map((question) => (
return <Col key={question.id}> <Col key={question.id}>
<Avatar className={question.classN} onClick={this.onQuestionClick.bind(this,question.index)}>{question.index}</Avatar> <Avatar
</Col>})} className={question.classN}
onClick={this.onQuestionClick.bind(this, question.index)}
>
{question.index}
</Avatar>
</Col>
))}
</Row> </Row>
</Panel> </Panel>
));
);
} }
return ( return (
<> <>
<Layout> <Layout>
<Layout> <Layout>
<Header className="header"> <Header className="exam-header">
<div style={{ paddingRight: 10, display: 'inline' }} className="ExamNameHeader"> <div className="exam-header-left">
<span >{this.state.exam_name}</span> <span className="exam-title">{this.state.exam_name}</span>
</div> </div>
<div className="ExamTimeHeader">
<Countdown title="Time Left" value={this.state.deadline} onFinish={this.onSubmitExam} /> <div className="exam-header-center">
<Countdown
title="Time Left"
value={this.state.deadline}
onFinish={this.onSubmitExam}
className="exam-countdown"
/>
</div> </div>
<div style={{ paddingLeft: 10, display: 'inline' }}>
<Button onClick={this.onPauseExam} style={{ marginRight: 8 }}>Leave</Button> <div className="exam-header-right">
</div> <Button onClick={this.onPauseExam}>Leave</Button>
<div style={{ paddingRight: 10, display: 'inline' }}> <Button type="primary" onClick={this.onSubmitExam}>
<Button type="primary" onClick={this.onSubmitExam} Submit
style={{ marginRight: 8 }}>Submit</Button> </Button>
</div> </div>
</Header> </Header>
<Content <Content
className="site-layout-background" className="site-layout-background"
style={{ style={{ padding: 24, margin: 0 }}
padding: 24,
margin: 0,
}}
> >
<Layout className="MainExamLayout"> <Layout className="MainExamLayout">
<Layout className="createExamLayoutLeft">{ <Layout className="create-exam-card">
<Tabs defaultActiveKey="1" size={"large"} type={"line"} style={{ marginBottom: 32 }} onTabClick={this.onTabPaneClick.bind(this)}> <Tabs
{ listItems }</Tabs> defaultActiveKey="1"
size="large"
type="line"
} animated
onTabClick={this.onTabPaneClick.bind(this)}
className="create-exam-tabs"
>
{listItems}
</Tabs>
</Layout> </Layout>
<Layout className="createExamLayoutRight">
<div> <Layout className="exam-status-card">
<table className="ExamStatusTable"> <div className="exam-status-section">
<table className="exam-status-table">
<tbody> <tbody>
<tr> <tr>
<td><span className="ExamAvatar"><Avatar className="StatusAvatar QuesttionStatusNotVisited" >{total_notVisited}</Avatar><span className="StatusLabel">{" Not Visited "}</span></span></td> <td>
<td><span className="ExamAvatar"><Avatar className="StatusAvatar QuesttionStatusNotAnswered" >{total_notAnswered}</Avatar><span className="StatusLabel">{" Not Answered "}</span></span></td> <div className="status-item">
<Avatar className="status-avatar not-visited">{total_notVisited}</Avatar>
<span className="status-label">Not Visited</span>
</div>
</td>
<td>
<div className="status-item">
<Avatar className="status-avatar not-answered">{total_notAnswered}</Avatar>
<span className="status-label">Not Answered</span>
</div>
</td>
</tr> </tr>
<tr>
<td><span className="ExamAvatar"><Avatar className="StatusAvatar QuesttionStatusAnswered">{total_Answered}</Avatar><span className="StatusLabel">{" Answered "}</span></span></td>
<td><span className="ExamAvatar"><Avatar className="StatusAvatar QuesttionStatusReview" >{total_Reviwed}</Avatar><span className="StatusLabel">{" To Review "}</span></span></td>
</tr>
<tr>
<td><span className="ExamAvatar"><Avatar className="StatusAvatar QuesttionStatusAnsweredReview">{total_AnsweredReviwed}</Avatar><span className="StatusLabel">{" Answered & To Review "}</span></span></td>
<tr>
<td>
<div className="status-item">
<Avatar className="status-avatar answered">{total_Answered}</Avatar>
<span className="status-label">Answered</span>
</div>
</td>
<td>
<div className="status-item">
<Avatar className="status-avatar to-review">{total_Reviwed}</Avatar>
<span className="status-label">To Review</span>
</div>
</td>
</tr>
<tr>
<td colSpan="2">
<div className="status-item">
<Avatar className="status-avatar answered-review">{total_AnsweredReviwed}</Avatar>
<span className="status-label">Answered & To Review</span>
</div>
</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
</div> </div>
<div>
<Collapse accordion> <div className="exam-accordion">
{accordions} <Collapse accordion>{accordions}</Collapse>
</Collapse>
</div> </div>
</Layout> </Layout>
</Layout> </Layout>
</Content> </Content>
</Layout> </Layout>
</Layout> </Layout>
</> </>
) );
}; }
}; }
export default AttemptExam; export default AttemptExam;

View File

@ -7,7 +7,6 @@ import { Link } from 'react-router-dom';
import parse from "html-react-parser"; import parse from "html-react-parser";
import { ButtonGroup } from "react-bootstrap"; import { ButtonGroup } from "react-bootstrap";
class QuestionDetail extends React.Component { class QuestionDetail extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
@ -28,7 +27,8 @@ class QuestionDetail extends React.Component {
loadNextQuestion = () => { loadNextQuestion = () => {
this.props.parentCallbackNext(this.props.question); this.props.parentCallbackNext(this.props.question);
} };
onReviewQuestion = (val) => { onReviewQuestion = (val) => {
this.state.question.isReviewMarked = val; this.state.question.isReviewMarked = val;
@ -53,6 +53,7 @@ class QuestionDetail extends React.Component {
this.setState({ this.setState({
question: tempQuestion question: tempQuestion
}) })
this.sendData(this.state.question); this.sendData(this.state.question);
} }
@ -69,76 +70,76 @@ class QuestionDetail extends React.Component {
this.sendData(this.state.question); this.sendData(this.state.question);
} }
render() { render() {
this.state.question = this.props.question; const { question, totalQuestion } = this.props;
this.state.question = question;
let answer = ""; let answer = "";
let answerOption = this.state.question.options.map((option,i)=>{ const answerOptions = question.options.map((option) => {
if(option.isSelected) if (option.isSelected) answer = option.id;
answer = option.id; return (
return <Radio className='radioStyle' key={option.id} value={option.id} checked={option.isSelected}> <Radio className="radioStyle" key={option.id} value={option.id}>
{option.text} {option.text}
</Radio> </Radio>
);
});
}) return (
let questionDetails= <Layout className="MainExamQuestionLayout"> <Layout className="MainExamQuestionLayout" style={{ background: "#fff" }}>
<Row> <Row className="question-header">
<Col> <Col>
<h3>{this.props.question.index}. {parse(this.props.question.question_text)}</h3> <h3 className="question-title">
{question.index}. {parse(question.question_text)}
</h3>
</Col> </Col>
</Row> </Row>
<Row className="Options">
<Col>
<div> <Radio.Group onChange={this.onAnswerChange} value={answer}>
{ <Row className="question-options">
answerOption <Col>
} <Radio.Group
onChange={this.onAnswerChange}
value={answer}
className="radio-group"
>
{answerOptions}
</Radio.Group> </Radio.Group>
</Col>
</Row>
<Row className="review-switch">
<Col>
<Switch
checkedChildren="Review On"
unCheckedChildren="Review Off"
checked={question.isReviewMarked}
onChange={this.onReviewQuestion}
/>
</Col>
</Row>
<Row className="question-actions">
<Col>
<div className="action-buttons">
<Button
onClick={this.loadNextQuestion}
type="primary"
disabled={question.index >= totalQuestion}
className="rounded-btn"
>
Next Question
</Button>
<Button
onClick={this.onClearResponse}
className="rounded-btn"
>
Clear Response
</Button>
</div> </div>
</Col> </Col>
</Row> </Row>
<Row>
<Col>
<br/>
<Switch checkedChildren="Review On" unCheckedChildren="Review Off" checked={this.state.question.isReviewMarked} onChange={this.onReviewQuestion} />
<br/>
</Col>
</Row>
<Row>
<Col>
<br/>
<br/>
</Col>
</Row>
<ButtonGroup><Button
onClick={this.loadNextQuestion}
type="primary"
disabled={this.props.question.index>=this.props.totalQuestion}
style={{ marginRight: 8 }}
>
{" "}
Next Question{" "}
</Button>
<Button
onClick={this.onClearResponse}
style={{ marginRight: 8 }}
>
{" "}
Clear Response{" "}
</Button></ButtonGroup>
</Layout> </Layout>
);
return ( }
<>
{questionDetails}
</>
)
};
}; };
export default QuestionDetail; export default QuestionDetail;

View File

@ -28,7 +28,8 @@ class LiveExams extends React.Component {
const json = {}; const json = {};
json.pageSize = 10; json.pageSize = 10;
json.pageNumber = 1; json.pageNumber = 1;
json.batch = this.state.batch; const storedUser = JSON.parse(sessionStorage.getItem("currentUser")) || {};
json.batch = storedUser.batch_id || null;
selectorService.loadLiveExams(json).then((data1) => { selectorService.loadLiveExams(json).then((data1) => {
// console.log('Success:', data1); // console.log('Success:', data1);
//data = $.parseJSON(data); //data = $.parseJSON(data);

View File

@ -101,6 +101,7 @@ export const selectorService = {
createPracticeAttempt, createPracticeAttempt,
getPracticeAttempt, getPracticeAttempt,
stopPublishedExam, stopPublishedExam,
heartbeat,
}; };
async function stopPublishedExam(examId) { async function stopPublishedExam(examId) {
@ -168,112 +169,159 @@ function loadReport(examId) {
}); });
} }
function pauseExam(attempt_id) { async function pauseExam(attemptId) {
const currentUser = authenticationService.currentUserValue; const currentUser = authenticationService.currentUserValue;
const requestOptions = { if (!currentUser?.jwtToken) {
method: "PUT", console.error("❌ No valid user token found");
headers: { return null;
"Access-Control-Allow-Origin": "*",
Accept: "application/json",
"Content-Type": "application/json",
Authorization: "Bearer ".concat(currentUser.jwtToken),
},
//body: JSON.stringify(json)
// body: JSON.stringify(this.state)
//body: JSON.stringify({ emailId:"sa@odiware.com", userPassword:"aaaa" })
};
return fetch(
"https://api.odiprojects.com/api-institute/v1/en/ExamAttempts/" +
attempt_id +
"/Pause",
requestOptions
)
.then((response) => {
if (response.ok) {
return response.json();
}
// window.localStorage.clear();
// history.push("/login");
})
.then((data) => {
console.log(data);
return data;
})
.catch((error) => {
// console.error('Error:', error);
});
} }
function endExam(attempt_id, json) { const languageCode = currentUser.language_code || "En";
const currentUser = authenticationService.currentUserValue; const url = `https://api.odiprojects.com/api-student/v1/${languageCode}/ExamAttempts/${attemptId}/Pause`;
const requestOptions = {
method: "PUT", try {
const response = await fetch(url, {
method: "POST",
headers: { headers: {
"Access-Control-Allow-Origin": "*",
Accept: "application/json", Accept: "application/json",
"Content-Type": "application/json", "Content-Type": "application/json",
Authorization: "Bearer ".concat(currentUser.jwtToken), Authorization: `Bearer ${currentUser.jwtToken}`,
}, },
body: JSON.stringify(json), });
// body: JSON.stringify(this.state)
//body: JSON.stringify({ emailId:"sa@odiware.com", userPassword:"aaaa" }) if (!response.ok) {
}; console.error(`❌ Failed to pause exam (HTTP ${response.status})`);
return fetch( // Optional: handle session expiration
"https://api.odiprojects.com/api-institute/v1/en/ExamAttempts/" + // if (response.status === 401) {
attempt_id +
"/End",
requestOptions
)
.then((response) => {
if (response.ok) {
return response.json();
}
// window.localStorage.clear(); // window.localStorage.clear();
// history.push("/login"); // history.push("/login");
}) // }
.then((data) => { return null;
console.log(data);
return data;
})
.catch((error) => {
// console.error('Error:', error);
});
} }
function updateExamStatus(attempt_id, json) { const data = await response.json();
console.log("⏸️ Exam paused successfully:", data);
return data;
} catch (error) {
console.error("🚨 Error pausing exam:", error);
return null;
}
}
async function endExam(attemptId, payload) {
const currentUser = authenticationService.currentUserValue; const currentUser = authenticationService.currentUserValue;
const requestOptions = { if (!currentUser?.jwtToken) {
method: "PUT", console.error("❌ No valid user token found");
return null;
}
const languageCode = currentUser.language_code || "En";
const url = `https://api.odiprojects.com/api-student/v1/${languageCode}/ExamAttempts/${attemptId}/End`;
try {
const response = await fetch(url, {
method: "POST",
headers: { headers: {
"Access-Control-Allow-Origin": "*",
Accept: "application/json", Accept: "application/json",
"Content-Type": "application/json", "Content-Type": "application/json",
Authorization: "Bearer ".concat(currentUser.jwtToken), Authorization: `Bearer ${currentUser.jwtToken}`,
}, },
body: JSON.stringify(json), body: JSON.stringify(payload),
// body: JSON.stringify(this.state) });
//body: JSON.stringify({ emailId:"sa@odiware.com", userPassword:"aaaa" })
}; if (!response.ok) {
return fetch( console.error(`❌ Failed to end exam (HTTP ${response.status})`);
"https://api.odiprojects.com/api-institute/v1/ExamAttempts/" + // Optional: handle expired sessions
attempt_id + // if (response.status === 401) {
"/HeartBeat",
requestOptions
)
.then((response) => {
if (response.ok) {
return response.json();
}
// window.localStorage.clear(); // window.localStorage.clear();
// history.push("/login"); // history.push("/login");
}) // }
.then((data) => { return null;
console.log(data); }
const data = await response.json();
console.log("✅ Exam ended successfully:", data);
return data; return data;
}) } catch (error) {
.catch((error) => { console.error("🚨 Error ending exam:", error);
// console.error('Error:', error); return null;
}
}
async function heartbeat(attemptId) {
const currentUser = authenticationService.currentUserValue;
if (!currentUser?.jwtToken) {
console.error("❌ No valid user token found");
return null;
}
const url = `https://api.odiprojects.com/api-student/v1/ExamAttempts/${attemptId}/Heartbeat`;
try {
const response = await fetch(url, {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
Authorization: `Bearer ${currentUser.jwtToken}`,
},
body: JSON.stringify({
attempt_id: attemptId,
timestamp: new Date().toISOString(),
}),
}); });
if (!response.ok) {
console.error(`❌ Heartbeat failed (HTTP ${response.status})`);
return null;
}
const data = await response.json();
console.log("💓 Heartbeat sent successfully:", data);
return data; // ✅ Return the parsed response data
} catch (error) {
console.error("🚨 Error sending heartbeat:", error);
return null;
}
}
async function updateExamStatus(attemptId, payload) {
const currentUser = authenticationService.currentUserValue;
if (!currentUser?.jwtToken) {
console.error("❌ No valid user token found");
return null;
}
const url = `https://api.odiprojects.com/api-student/v1/ExamAttempts/${attemptId}/UpdateAnswer`;
try {
const response = await fetch(url, {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
Authorization: `Bearer ${currentUser.jwtToken}`,
},
body: payload,
});
if (!response.ok) {
console.error(`❌ Failed to update exam status (HTTP ${response.status})`);
// Optional: Handle unauthorized users
// if (response.status === 401) {
// window.localStorage.clear();
// history.push("/login");
// }
return null;
}
const data = await response.json();
console.log("✅ Exam status updated:", data);
return data;
} catch (error) {
console.error("🚨 Error updating exam status:", error);
return null;
}
} }
function updateBatchName(obj) { function updateBatchName(obj) {
@ -353,43 +401,45 @@ function getPracticeAttempt(practiceId) {
} }
function getExamAttempt(attemptId) { async function getExamAttempt(attemptId) {
const currentUser = authenticationService.currentUserValue; const currentUser = authenticationService.currentUserValue;
const requestOptions = { if (!currentUser?.jwtToken) {
console.error("❌ No valid user token found");
return null;
}
const languageCode = currentUser.language_code || "En";
const languageName = currentUser.language_name || "English";
const url = `https://api.odiprojects.com/api-student/v1/${languageCode}/ExamAttempts/${attemptId}/Questions`;
try {
const response = await fetch(url, {
method: "GET", method: "GET",
headers: { headers: {
"Access-Control-Allow-Origin": "*",
Accept: "application/json", Accept: "application/json",
"Content-Type": "application/json", "Content-Type": "application/json",
Authorization: "Bearer ".concat(currentUser.jwtToken), Authorization: `Bearer ${currentUser.jwtToken}`,
}, },
}; });
if (currentUser.language_code === null) {
currentUser.language_code = "En"; if (!response.ok) {
currentUser.language_name = "English"; console.error(`❌ Request failed with status: ${response.status}`);
} // Optional: handle unauthorized user
return fetch( // if (response.status === 401) {
"https://api.odiprojects.com/api-institute/v1/" +
currentUser.language_code +
"/ExamAttempts/" +
attemptId +
"/Questions",
requestOptions
)
.then((response) => {
if (response.ok) {
return response.json();
}
// window.localStorage.clear(); // window.localStorage.clear();
// history.push("/login"); // history.push("/login");
}) // }
.then((data) => { return null;
console.log(data); }
const data = await response.json();
console.log("✅ Exam attempt data:", data);
return data; return data;
}) } catch (error) {
.catch((error) => { console.error("🚨 Error fetching exam attempt:", error);
// console.error('Error:', error); return null;
}); }
} }
function getAllUser(page_size, page_number) { function getAllUser(page_size, page_number) {
@ -641,42 +691,47 @@ function createPracticeAttempt(practiceId) {
}); });
} }
function createExamAttempt(examId) { async function createExamAttempt(examId) {
const currentUser = authenticationService.currentUserValue; const currentUser = authenticationService.currentUserValue;
const requestOptions = { if (!currentUser?.jwtToken) {
console.error("❌ No valid user token found");
return null;
}
const languageCode = currentUser.language_code || "En";
const languageName = currentUser.language_name || "English";
const url = `https://api.odiprojects.com/api-student/v1/${languageCode}/ExamAttempts/${examId}`;
try {
const response = await fetch(url, {
method: "POST", method: "POST",
headers: { headers: {
"Access-Control-Allow-Origin": "*",
Accept: "application/json", Accept: "application/json",
"Content-Type": "application/json", "Content-Type": "application/json",
Authorization: "Bearer ".concat(currentUser.jwtToken), Authorization: `Bearer ${currentUser.jwtToken}`,
}, },
}; });
if (currentUser.language_code === null) {
currentUser.language_code = "En"; console.log(response);
currentUser.language_name = "English";
} if (!response.ok) {
return fetch( console.error(`❌ Request failed with status: ${response.status}`);
"https://api.odiprojects.com/api-institute/v1/" + // Optionally handle unauthorized user
currentUser.language_code + // if (response.status === 401) {
"/ExamAttempts/" +
examId,
requestOptions
)
.then((response) => {
if (response.ok) {
return response.json();
}
// window.localStorage.clear(); // window.localStorage.clear();
// history.push("/login"); // history.push("/login");
}) // }
.then((data) => { return null;
console.log(data); }
const data = await response.json();
console.log("✅ Exam attempt created:", data);
return data; return data;
}) } catch (error) {
.catch((error) => { console.error("🚨 Error creating exam attempt:", error);
// console.error('Error:', error); return null;
}); }
} }
function loadLiveExams(jsonObj) { function loadLiveExams(jsonObj) {
@ -701,7 +756,7 @@ function loadLiveExams(jsonObj) {
currentUser.language_name = "English"; currentUser.language_name = "English";
} }
return fetch( return fetch(
"https://api.odiprojects.com/api-institute/v1/" + "https://api.odiprojects.com/api-student/v1/" +
currentUser.language_code + currentUser.language_code +
"/Batches/" + "/Batches/" +
jsonObj.batch + jsonObj.batch +