first commit
|
|
@ -0,0 +1,10 @@
|
||||||
|
RROWSER=FALSE
|
||||||
|
SKIP_PREFLIGHT_CHECK=true
|
||||||
|
REACT_APP_API_VERSION=1
|
||||||
|
REACT_APP_FIREBASE_API_KEY=AIzaSyBb_mfZOYFGmWa_UqQPIXVYhGdnFk6K2zo
|
||||||
|
# REACT_APP_API_USER_SIGN_IN=https://api.odiprojects.com/api-user/v1/Users/SignIn
|
||||||
|
# REACT_APP_INSTITUTE_API_PREFIX=https://api.odiprojects.com/api-institute/v
|
||||||
|
REACT_APP_INSTITUTE_API_PREFIX=https://api-institute.odiprojects.com/v
|
||||||
|
# REACT_APP_ADMIN_API_PREFIX=https://api.odiprojects.com/api-admin/v
|
||||||
|
REACT_APP_ADMIN_API_PREFIX=https://api-admin.odiprojects.com/v
|
||||||
|
|
||||||
|
|
@ -0,0 +1,67 @@
|
||||||
|
robots.txt,1761396900570,bfe106a3fb878dc83461c86818bf74fc1bdc7f28538ba613cd3e775516ce8b49
|
||||||
|
manifest.json,1761396900568,a9350a49aaac9fe94d3dd77b8270cc998c04ab97944a606189675022431faa51
|
||||||
|
favicon.svg,1761396900561,a2a4880301751061a600b0bfc5c26fc413aed41e581516c4fa976bcb7fff6663
|
||||||
|
service-worker.js,1761576002341,023c58598eaf6728e3bbc8ed608e32fd608d37851fa12ce79816a48180c9cd31
|
||||||
|
asset-manifest.json,1761576002341,d747a6089e42c15217b542311c92d5809e7cae25a755c6242d55d93072ac5281
|
||||||
|
precache-manifest.816159c176bc481e03384d794646b865.js,1761576002341,8b8a70a80ada7aa8b5e21993a642189e9d1dbed0805111893a8ae9cdd5038c23
|
||||||
|
static/media/sub-ques-icon.366b4f1e.svg,1761576002260,8e397611007ec5db7581a4c1bcca006950390ddf652cae209bb3973a645af425
|
||||||
|
static/media/translate.610ad011.svg,1761576002262,c06f8a3d0c976b02429ce805d8a4944571ab0128ed2ce49c2d3ca134511ed120
|
||||||
|
static/media/student-engaged.53fb7b0b.svg,1761576002257,5f8e01d1a5efcbdf3aa00ed07de843ab84404a28b672ca91f4d70d89fa8b37cf
|
||||||
|
assets/images/locale-icon.svg,1761396900579,610498c7ca3b5800d268b1654473f0b1d79de2c6493a7c6982bef90456d35179
|
||||||
|
assets/images/icons/icon-96x96.png,1761396900602,43a8ac4df8945d7a44e3e4911032214f01eaea92267baa31a93e77942b424c73
|
||||||
|
assets/images/icons/icon-72x72.png,1761396900600,a42f1df8ab0a8fe921573976d49158e1a2c52fe430460c869947f221aba30a94
|
||||||
|
assets/images/icons/icon-512x512.png,1761396900598,bf32ebfb9a228c708ff357f091ba358bcbbc2f3c23515088c6fef3c5eba7412b
|
||||||
|
assets/images/icons/icon-384x384.png,1761396900595,ca7e5423af62d02193f6fdf5ddf8a783fe866f9725dd2dfb24a44e452a10373e
|
||||||
|
assets/images/icons/icon-192x192.png,1761396900592,ac9fe46fbeb4c54fb3c838b64538039db4b11df2a3885f189e10a6f7e04003e5
|
||||||
|
assets/images/icons/icon-152x152.png,1761396900590,95a5a117fbd5640f1f1f13c9923398545e3f5b66734ff6c8ce67c942319a8b67
|
||||||
|
assets/images/icons/icon-144x144.png,1761396900588,e42168e0e1abb5bd7811ebe1b5a2183d0ce287bf266c2efd8d97a23d23ca8d00
|
||||||
|
assets/images/icons/icon-128x128.png,1761396900585,45b577c86e7c03fef868bfa3f96810c5b36f31156c32c0d85620d2e6fe1fc85b
|
||||||
|
index.html,1761576002341,7983b57b52860a70c9a6382d9b2bae797ed4b2b744dbd7af42a74464f9f5e2b2
|
||||||
|
static/media/quizexam.5545802e.svg,1761576002242,dfc1278bfcd264264a4d0e0e0247c229335abf0573439c5e9829c8607aacd569
|
||||||
|
static/media/question.0c505ed9.svg,1761576002242,29938066f93476c487414bb7a5dd5227d891c8ab8e115a74a7e7ffccd8d87b36
|
||||||
|
static/media/questions-icon.3d3c1aaf.svg,1761576002254,197f459a359b00c1c44b6ddd71cbc9160f593d1fe08534e3711e9d72a2956ef1
|
||||||
|
static/media/practice.f05e6f00.svg,1761576002244,13b094bcbbb8c50906b1ee0ce68305059e99319464dd4147b4c2194dbb460475
|
||||||
|
static/media/PracticeKiaLogo.a8336af5.svg,1761576002241,1a592518bcfabb2f86669d06ae2fb7b2948bbdbfaf5a80d138ab30f4e482a0f8
|
||||||
|
static/media/practice-icon.bf603115.svg,1761576002256,27b6a729d91914d899f4b3c1f817e26ac58d2ec0be54dc9b93c01f28adcdd00c
|
||||||
|
static/media/perf-icon.1597a235.svg,1761576002258,7183482b785de425506f49deb27bba02e906132ac7ce5af01f7da8452ef64f28
|
||||||
|
static/media/performance.88855f12.svg,1761576002247,03fc1b19005084049382091745d3f1f29a447763091ad4a9bc5d097c338671c9
|
||||||
|
static/media/tru-fals-icon.d0b962d8.svg,1761576002260,a1deab7e5db8e8a94a9c5812dcde5f8cbfbe5da790da8500bf106980b9c79c33
|
||||||
|
static/media/mul-res-icon.8ef3b097.svg,1761576002257,1f0a79350cb5546361ef02af1657ad36efeff833fd7bb61db88a20e127a290b4
|
||||||
|
static/media/mul-cho-icon.b3dc9ea9.svg,1761576002260,e8edbceb0eb49f7629cf0b8edf599f9347acbf0c6d1df2fa6145cbed03f37e1f
|
||||||
|
static/media/locale-icon.b3596424.svg,1761576002254,68618c76952aa4c5f2623bb010514871688960bb3b9edbcda0eab7eb75442054
|
||||||
|
static/media/OdiSVGlogo.f0834bb1.svg,1761576002242,3ec1cab31a32db378894d796afedaee7c35f8ff99dfb1039b0bb757225a47235
|
||||||
|
static/media/getFetch.2b2b7da4.cjs,1761576002242,b2d82abee5b8af22b81d67fc20b3feef1eaaf04585dbc24ec755e3304c469096
|
||||||
|
static/media/exam-icon.10f48851.svg,1761576002256,b766e9975582af716870a844f8deabd4d80e33a47c400d36cee6bc4840062693
|
||||||
|
static/media/GrayscalePKLogo.6bb74404.svg,1761576002242,576b38300e49eda6407adce420001e7ad0333cca317eb668aff457c3258dcca2
|
||||||
|
static/media/delete-icon.da38c0f4.svg,1761576002263,8078772ee88a0588989eec477da1ca949430f3f06efaa9c2db860b74c65f6722
|
||||||
|
static/media/dashboard.8ec7624b.svg,1761576002242,5a5b5d4bf416d414842bd5eac30432b592342a392450e52943f067db4077bec1
|
||||||
|
static/media/classes.3b73dba0.svg,1761576002244,2abfbb7015d5bccb8640fd0614d3782c048672c9543fe17d127db63287ac141d
|
||||||
|
static/media/Checkmark.1356376c.svg,1761576002270,aa56f27c8198bcae3236a881a7134cd3b7d3dbb048ec75654e8ce2d4710ce027
|
||||||
|
static/media/feature-2.36f8d7e2.webp,1761576002242,fc1c038517abf3b731ff3a4675cbaf1a6aa1150ca970762dd0ffc4199b92b75c
|
||||||
|
static/media/class-icon.6afd34b5.svg,1761576002254,a2f492c6c9c7b5201773062dfe90c238c12bc00bec097dc06141201f02b9588e
|
||||||
|
static/media/batch.3fcff66e.svg,1761576002244,f29038480286f091e3805a43f90ca5a70a13bbcef3076831e0b29b1fcb61d862
|
||||||
|
static/media/auth-BG.2835584f.svg,1761576002241,238d3a2ff1aead1c4aaed0e07d23d2c1164f8be0ef2d5fced5c5ade370f3c5bf
|
||||||
|
static/media/add-circle.0011f2bc.svg,1761576002255,d9e5d90e8de1ce16df5720b43ca79543036fcf1a6cd6439a9b15c5a97d269f17
|
||||||
|
static/media/batch-icon.bf664771.svg,1761576002260,7519e2a92f436a2f46ef7420d721e24df2837d7fce7fe8c30f4bc12980b3d1a0
|
||||||
|
static/js/runtime-main.96ceaa04.js,1761576002273,455ae6008568041ddd40d50a96c614534f9c268f4d0fff4c2700d0d9646ddc57
|
||||||
|
static/js/4.7247a6ee.chunk.js.map,1761576002344,e708b3fc1a1b324a3b6c468da749aaa4361b2f44d7c9b2e1614165faa04e072b
|
||||||
|
static/js/runtime-main.96ceaa04.js.map,1761576002341,454ee9efc7767f76d20d24cb4395b51fe7735f63f7ceb89ebbe56f138716b92b
|
||||||
|
static/js/4.7247a6ee.chunk.js,1761576002280,32fa7d3f8777fffae2bc6dc1de3608e836c27d9044fe4be8fab745f6c08cb373
|
||||||
|
static/js/3.5470394c.chunk.js.map,1761576002343,0aed88423ebfa4792f1a2daf5809524a872b20f67a0ecb6e1becc7a52fa1c423
|
||||||
|
static/js/3.5470394c.chunk.js,1761576002279,564d1d12db256f1c984a60c05dfc4e34c782c3c8b1b548447c5c30dd71aba0d6
|
||||||
|
static/js/2.337cf2a6.chunk.js.LICENSE.txt,1761576002280,9c84bc4d2f8584d32d75e01e0317e22af1e39f5ac5ded3e2e4e34984704c172b
|
||||||
|
static/media/logo.0dd03933.png,1761576002242,b9716ed1f565a052edc1154a207334de81856339e8ca43d5d8f51041f3785085
|
||||||
|
static/media/feature-1.ea5c34ea.svg,1761576002234,d802659785b69508e521d5543fca40a58cb9076521ffc3e6b006f4786b8079f8
|
||||||
|
static/media/feature-3.3d5d81e9.svg,1761576002242,ba23393d89776cb78f7a559903baff48be76665abd99bdd4e5ea103c5e5ed88b
|
||||||
|
static/js/main.e80be951.chunk.js,1761576002272,b15fab5b06dc6f90c0f6677def9a66a9d9609026af14db6e2c114c27c1c22db9
|
||||||
|
static/css/main.3624148f.chunk.css,1761576002265,6a13470e019853946811aebb68baac2e094470affc5520d835a986b420b1edab
|
||||||
|
static/css/4.629184c9.chunk.css,1761576002280,dfeba86da1256521df7a877a265d56d314f65016837f30878338c2b7fc62a6ab
|
||||||
|
static/css/2.561a8df6.chunk.css,1761576002273,c87247fc5cf38902aee0ee29244f6ab0c4bc14e5a0a1d6860cd771410dc826f8
|
||||||
|
static/css/3.6c5a3051.chunk.css,1761576002280,1ae150a22b1d42322775be9055605f93c75188e7081cfbdb206a59fc2f5ab8f1
|
||||||
|
static/js/main.e80be951.chunk.js.map,1761576002342,c7567119cb47238c88452f154ae648333c59cd204a9f3734b3b90ae2a422f534
|
||||||
|
static/css/4.629184c9.chunk.css.map,1761576002292,b82aa57e6e357dbb20067334e544e9b30d20ed0d928c13e6c61e1747e6f5a205
|
||||||
|
static/css/3.6c5a3051.chunk.css.map,1761576002289,bcc301c3b3b46c96afc0fc028a6aa3cd299ccda538d7c6cb9ebc87f8de535796
|
||||||
|
static/css/2.561a8df6.chunk.css.map,1761576002281,ed9f46fc5b36bd835967cfd533fb33fd9ceec9c3aadcbe6c12bfb90b3ad1bc83
|
||||||
|
static/css/main.3624148f.chunk.css.map,1761576002281,588c07ee54c0d7bc5ec65c6e4ab3890e922520cac4eac29b6fd638715a5ac5d1
|
||||||
|
static/js/2.337cf2a6.chunk.js,1761576002282,e0edd93ede2589762ee74717e09fd5917dcb2660daa4ac6332bd8bc5b3c676ec
|
||||||
|
static/js/2.337cf2a6.chunk.js.map,1761576002358,b607efb90aec0e30321307c0738064e935f3d4b90d423885c732d57f1ebba603
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"projects": {
|
||||||
|
"default": "practice-kea-7cb5b"
|
||||||
|
},
|
||||||
|
"targets": {
|
||||||
|
"practice-kea-7cb5b": {
|
||||||
|
"hosting": {
|
||||||
|
"default": [
|
||||||
|
"practicekea"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"etags": {}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
|
||||||
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
|
lib-cov
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage
|
||||||
|
|
||||||
|
# nyc test coverage
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
|
.grunt
|
||||||
|
|
||||||
|
# node-waf configuration
|
||||||
|
.lock-wscript
|
||||||
|
|
||||||
|
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
||||||
|
build
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
node_modules
|
||||||
|
jspm_packages
|
||||||
|
typings
|
||||||
|
.vscode
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional REPL history
|
||||||
|
.node_repl_history
|
||||||
|
build.7z
|
||||||
|
build.zip
|
||||||
|
|
||||||
|
# Environment variable
|
||||||
|
.env.production
|
||||||
|
.env.development
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
question,option1,option2,option3,option4
does this work?,yes,no,maybe, maybe not
is earth round?,no,no,nope,absolutely not
is education necessary?,lol,hmmmm,uhhhhh,pfffft
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
# Online Assesment ReactJS Application
|
||||||
|
|
||||||
|
# To start with local setup and run;
|
||||||
|
> npm install
|
||||||
|
|
||||||
|
## Make sure you have the .env file with you or contact the developer for the same;
|
||||||
|
|
||||||
|
After successful installation of dependencies, start the React Application;
|
||||||
|
> npm start
|
||||||
|
|
||||||
|
# To create build and deploy in production server;
|
||||||
|
> npm run build
|
||||||
|
|
||||||
|
Then, copy the content of "build" directory to server "www" folder to deploy.
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
/* craco.config.js */
|
||||||
|
const CracoLessPlugin = require('craco-less');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
resolve: {
|
||||||
|
extensions: ['.js', '.jsx']
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
{
|
||||||
|
plugin: CracoLessPlugin,
|
||||||
|
options: {
|
||||||
|
lessLoaderOptions: {
|
||||||
|
lessOptions: {
|
||||||
|
modifyVars: {
|
||||||
|
//'@primary-color': '#EDA57A',
|
||||||
|
//'@text-color': 'rgba(250, 0, 0, 0.65)',
|
||||||
|
//'@layout-header-background': '#0E3997',
|
||||||
|
},
|
||||||
|
javascriptEnabled: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"hosting": {
|
||||||
|
"target" : "default",
|
||||||
|
"public": "build",
|
||||||
|
"ignore": [
|
||||||
|
"firebase.json",
|
||||||
|
"**/.*",
|
||||||
|
"**/node_modules/**"
|
||||||
|
],
|
||||||
|
"rewrites": [
|
||||||
|
{
|
||||||
|
"source": "**",
|
||||||
|
"destination": "/index.html"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,81 @@
|
||||||
|
{
|
||||||
|
"name": "antd-demo",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"engines": {
|
||||||
|
"node": "16.17.0",
|
||||||
|
"npm": "8.15.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@ant-design/icons": "^4.3.0",
|
||||||
|
"@craco/craco": "^5.8.0",
|
||||||
|
"@testing-library/jest-dom": "^4.2.4",
|
||||||
|
"@testing-library/react": "^9.5.0",
|
||||||
|
"@testing-library/user-event": "^7.2.1",
|
||||||
|
"antd": "^4.5.4",
|
||||||
|
"antd-img-crop": "^4.2.4",
|
||||||
|
"axios": "^0.21.1",
|
||||||
|
"better-react-mathjax": "^2.0.2",
|
||||||
|
"cors": "^2.8.5",
|
||||||
|
"craco-less": "^1.16.0",
|
||||||
|
"csvtojson": "^2.0.10",
|
||||||
|
"easymde": "^2.18.0",
|
||||||
|
"formik": "^2.1.5",
|
||||||
|
"html-react-parser": "^3.0.4",
|
||||||
|
"i18next": "^21.9.2",
|
||||||
|
"i18next-http-backend": "^1.4.4",
|
||||||
|
"immutability-helper": "^3.1.1",
|
||||||
|
"katex": "^0.12.0",
|
||||||
|
"less": "^2.7.2",
|
||||||
|
"lodash": "^4.17.20",
|
||||||
|
"marked-react": "^1.3.1",
|
||||||
|
"query-string": "^7.1.1",
|
||||||
|
"quill": "^1.3.7",
|
||||||
|
"react": "^16.13.1",
|
||||||
|
"react-bootstrap": "^2.5.0",
|
||||||
|
"react-card-flip": "^1.0.11",
|
||||||
|
"react-confirm-alert": "^2.8.0",
|
||||||
|
"react-dnd": "^11.1.3",
|
||||||
|
"react-dnd-html5-backend": "^11.1.3",
|
||||||
|
"react-dom": "^16.13.1",
|
||||||
|
"react-i18next": "^11.18.6",
|
||||||
|
"react-katex": "^2.0.2",
|
||||||
|
"react-latex": "^2.0.0",
|
||||||
|
"react-masonry-css": "^1.0.16",
|
||||||
|
"react-mathquill": "^1.0.1",
|
||||||
|
"react-plotly.js": "^2.5.0",
|
||||||
|
"react-quill": "^2.0.0",
|
||||||
|
"react-responsive": "^8.2.0",
|
||||||
|
"react-router-dom": "^5.2.0",
|
||||||
|
"react-scripts": "^3.4.3",
|
||||||
|
"react-simplemde-editor": "^5.0.2",
|
||||||
|
"rxjs": "^6.6.3",
|
||||||
|
"uuid": "^9.0.0",
|
||||||
|
"yup": "^0.29.3",
|
||||||
|
"zod": "^3.20.2"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"start": "craco --expose-gc --max-old-space-size=9000 start",
|
||||||
|
"build": "craco --expose-gc --max-old-space-size=9000 build",
|
||||||
|
"test": "craco --expose-gc --max-old-space-size=3500 test",
|
||||||
|
"eject": "react-scripts --expose-gc --max-old-space-size=3500 eject"
|
||||||
|
},
|
||||||
|
"eslintConfig": {
|
||||||
|
"extends": "react-app"
|
||||||
|
},
|
||||||
|
"browserslist": {
|
||||||
|
"production": [
|
||||||
|
">0.2%",
|
||||||
|
"not dead",
|
||||||
|
"not op_mini all"
|
||||||
|
],
|
||||||
|
"development": [
|
||||||
|
"last 1 chrome version",
|
||||||
|
"last 1 firefox version",
|
||||||
|
"last 1 safari version"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"react-error-overlay": "^6.0.9"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,62 @@
|
||||||
|
{
|
||||||
|
"name": "antd-demo",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@ant-design/icons": "^4.3.0",
|
||||||
|
"@testing-library/jest-dom": "^4.2.4",
|
||||||
|
"@testing-library/react": "^9.5.0",
|
||||||
|
"@testing-library/user-event": "^7.2.1",
|
||||||
|
"antd": "^4.5.4",
|
||||||
|
"axios": "^0.21.1",
|
||||||
|
"cors": "^2.8.5",
|
||||||
|
"formik": "^2.1.5",
|
||||||
|
"html-react-parser": "^0.14.1",
|
||||||
|
"immutability-helper": "^3.1.1",
|
||||||
|
"katex": "^0.12.0",
|
||||||
|
"loadash": "^1.0.0",
|
||||||
|
"lodash": "^4.17.20",
|
||||||
|
"plotly.js": "^1.57.1",
|
||||||
|
"quill": "^1.3.7",
|
||||||
|
"react": "^16.13.1",
|
||||||
|
"react-bootstrap": "^1.3.0",
|
||||||
|
"react-confirm-alert": "^2.7.0",
|
||||||
|
"react-dnd": "^11.1.3",
|
||||||
|
"react-dnd-html5-backend": "^11.1.3",
|
||||||
|
"react-dom": "^16.13.1",
|
||||||
|
"react-draft-wysiwyg": "^1.14.5",
|
||||||
|
"react-excel-renderer": "^1.1.0",
|
||||||
|
"react-katex": "^2.0.2",
|
||||||
|
"react-latex": "^2.0.0",
|
||||||
|
"react-plotly.js": "^2.5.0",
|
||||||
|
"react-quill": "^1.3.5",
|
||||||
|
"react-redux": "^7.2.1",
|
||||||
|
"react-router-dom": "^5.2.0",
|
||||||
|
"react-scripts": "^3.4.3",
|
||||||
|
"redux": "^4.0.5",
|
||||||
|
"rxjs": "^6.6.3",
|
||||||
|
"update": "^0.7.4",
|
||||||
|
"yup": "^0.29.3"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"start": "react-scripts --expose-gc --max-old-space-size=9000 start",
|
||||||
|
"build": "react-scripts --expose-gc --max-old-space-size=9000 build",
|
||||||
|
"test": "react-scripts --expose-gc --max-old-space-size=3500 test",
|
||||||
|
"eject": "react-scripts --expose-gc --max-old-space-size=3500 eject"
|
||||||
|
},
|
||||||
|
"eslintConfig": {
|
||||||
|
"extends": "react-app"
|
||||||
|
},
|
||||||
|
"browserslist": {
|
||||||
|
"production": [
|
||||||
|
">0.2%",
|
||||||
|
"not dead",
|
||||||
|
"not op_mini all"
|
||||||
|
],
|
||||||
|
"development": [
|
||||||
|
"last 1 chrome version",
|
||||||
|
"last 1 firefox version",
|
||||||
|
"last 1 safari version"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
<IfModule mod_rewrite.c>
|
||||||
|
RewriteEngine On
|
||||||
|
RewriteBase /
|
||||||
|
RewriteRule ^index\.html$ - [L]
|
||||||
|
RewriteCond %{REQUEST_FILENAME} !-f
|
||||||
|
RewriteCond %{REQUEST_FILENAME} !-d
|
||||||
|
RewriteCond %{REQUEST_FILENAME} !-l
|
||||||
|
RewriteRule . /index.html [L]
|
||||||
|
</IfModule>
|
||||||
|
After Width: | Height: | Size: 9.1 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 43 KiB |
|
After Width: | Height: | Size: 4.6 KiB |
|
After Width: | Height: | Size: 6.5 KiB |
|
|
@ -0,0 +1,15 @@
|
||||||
|
<svg width="20" height="20" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<defs>
|
||||||
|
<clipPath id="uogj5ypaea">
|
||||||
|
<path d="M1440 0v900H0V0h1440z"/>
|
||||||
|
</clipPath>
|
||||||
|
<clipPath id="zm6ko0ulzb">
|
||||||
|
<path d="M11.054 4.8 14 12h-1.378l-.65-1.89h-3.04L8.188 12H6.889l2.867-7.2h1.298zM5.701 0v1.2h4.435v1.2h-1.79c-.232.69-.54 1.368-.926 2.033A10.508 10.508 0 0 1 6.002 6.33l1.552 1.485L7.08 9.03 5.068 7.2l-3.168 3-.87-.825 3.23-3.06c-.401-.44-.757-.883-1.069-1.328A10.056 10.056 0 0 1 2.376 3.6h1.377c.18.33.378.642.594.937.217.295.462.598.737.908A8.18 8.18 0 0 0 6.27 3.982c.317-.505.586-1.032.808-1.582H0V1.2h4.434V0h1.267zm4.72 6.15h-.032L9.281 9.06h2.248l-1.108-2.91z"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
<g clip-path="url(#uogj5ypaea)" transform="translate(-1000 -30)">
|
||||||
|
<g clip-path="url(#zm6ko0ulzb)" transform="translate(1003 34)">
|
||||||
|
<path fill="#858585" d="M0 0h14v12H0V0z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 974 B |
|
|
@ -0,0 +1,55 @@
|
||||||
|
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 392.571 392.571" style="enable-background:new 0 0 392.571 392.571;" xml:space="preserve">
|
||||||
|
<path style="fill:#FFFFFF;" d="M192.232,211.019L85.371,168.675v119.79c8.275-1.681,16.743-2.521,25.471-2.521
|
||||||
|
c33.487,0,63.677,12.735,81.519,33.552c17.842-20.816,47.968-33.552,81.519-33.552c8.727,0,17.325,0.84,25.471,2.521V171.649
|
||||||
|
l-99.232,39.434h-7.887V211.019z"/>
|
||||||
|
<path style="fill:#194F82;" d="M385.654,117.152L200.248,43.585c-2.521-1.034-5.495-1.034-8.016,0L6.697,117.152
|
||||||
|
c-9.438,4.461-8.404,17.067,0,20.299l56.889,22.562v142.739c0.517,7.176,6.4,13.059,14.481,10.279
|
||||||
|
c10.279-3.556,21.333-5.301,32.84-5.301c32,0,60.897,14.545,71.887,36.073c4.202,7.499,14.869,8.404,19.459-0.065
|
||||||
|
c10.925-21.527,39.693-36.008,71.693-36.008c11.507,0,22.626,1.745,32.84,5.301c7.046,2.392,14.287-2.909,14.481-10.279V162.987
|
||||||
|
l49.455-19.653v109.64c0,6.012,4.848,10.925,10.925,10.925c6.012,0,10.925-4.848,10.925-10.925V127.366
|
||||||
|
C392.571,122.712,389.856,119.544,385.654,117.152z M299.48,288.465c-8.275-1.681-16.743-2.521-25.471-2.521
|
||||||
|
c-33.487,0-63.677,12.735-81.519,33.552c-17.907-20.816-48.162-33.552-81.519-33.552c-8.727,0-17.325,0.84-25.471,2.521v-119.79
|
||||||
|
l106.861,42.343h8.016l99.232-39.434v116.816h-0.129V288.465z M196.24,189.168L40.313,127.366L196.24,65.564l155.798,61.802
|
||||||
|
L196.24,189.168z"/>
|
||||||
|
<g>
|
||||||
|
<path style="fill:#FFC10D;" d="M107.092,200.869h29.543c6.012,0,10.925,4.848,10.925,10.925c0,6.012-4.848,10.925-10.925,10.925
|
||||||
|
h-29.543v18.747h14.222c6.012,0,10.925,4.848,10.925,10.925c0,6.012-4.848,10.925-10.925,10.925h-14.222v0.84
|
||||||
|
c1.228,0,52.299-0.453,85.204,25.729c21.657-16.291,50.489-25.794,81.519-25.794c1.228,0,2.457,0,3.685,0.065v-76.154h-19.459
|
||||||
|
l-57.794,22.949h-8.016l-57.859-22.949h-27.281L107.092,200.869L107.092,200.869z"/>
|
||||||
|
<polygon style="fill:#FFC10D;" points="196.24,189.168 198.955,188.069 193.525,188.069 "/>
|
||||||
|
</g>
|
||||||
|
<polygon style="fill:#56ACE0;" points="40.313,127.366 196.24,189.168 352.038,127.366 196.24,65.435 "/>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.3 KiB |
|
|
@ -0,0 +1,46 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<base href="/">
|
||||||
|
<link rel="icon" href="%PUBLIC_URL%/favicon.svg" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<meta name="theme-color" content="#000000" />
|
||||||
|
<meta name="color-scheme" content="dark light">
|
||||||
|
<meta name="description"
|
||||||
|
content="Offers teachers a platform to publish their questions and prepare exams for students
|
||||||
|
who can also login to remotely give the exam."
|
||||||
|
/>
|
||||||
|
<link rel="apple-touch-icon" href="%PUBLIC_URL%/assets/images/icons/icon-192x192.png" />
|
||||||
|
<!--
|
||||||
|
manifest.json provides metadata used when your web app is installed on a
|
||||||
|
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||||
|
-->
|
||||||
|
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||||
|
<!--
|
||||||
|
Notice the use of %PUBLIC_URL% in the tags above.
|
||||||
|
It will be replaced with the URL of the `public` folder during the build.
|
||||||
|
Only files inside the `public` folder can be referenced from the HTML.
|
||||||
|
|
||||||
|
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||||
|
work correctly both with client-side routing and a non-root public URL.
|
||||||
|
Learn how to configure a non-root public URL by running `npm run build`.
|
||||||
|
-->
|
||||||
|
<title>Exam Assessment App</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
|
<div id="root"></div>
|
||||||
|
<div id="modal-root-test"></div>
|
||||||
|
<!--
|
||||||
|
This HTML file is a template.
|
||||||
|
If you open it directly in the browser, you will see an empty page.
|
||||||
|
|
||||||
|
You can add webfonts, meta tags, or analytics to this file.
|
||||||
|
The build step will place the bundled scripts into the <body> tag.
|
||||||
|
|
||||||
|
To begin the development, run `npm start` or `yarn start`.
|
||||||
|
To create a production bundle, use `npm run build` or `yarn build`.
|
||||||
|
-->
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
{
|
||||||
|
"name": "Online Assessment App",
|
||||||
|
"short_name": "Assessment App",
|
||||||
|
"theme_color": "#fff",
|
||||||
|
"background_color": "#fff",
|
||||||
|
"display": "browser",
|
||||||
|
"orientation": "",
|
||||||
|
"scope": "/",
|
||||||
|
"start_url": "/",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "assets/images/icons/icon-72x72.png",
|
||||||
|
"sizes": "72x72",
|
||||||
|
"type": "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "assets/images/icons/icon-96x96.png",
|
||||||
|
"sizes": "96x96",
|
||||||
|
"type": "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "assets/images/icons/icon-128x128.png",
|
||||||
|
"sizes": "128x128",
|
||||||
|
"type": "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "assets/images/icons/icon-144x144.png",
|
||||||
|
"sizes": "144x144",
|
||||||
|
"type": "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "assets/images/icons/icon-152x152.png",
|
||||||
|
"sizes": "152x152",
|
||||||
|
"type": "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "assets/images/icons/icon-192x192.png",
|
||||||
|
"sizes": "192x192",
|
||||||
|
"type": "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "assets/images/icons/icon-384x384.png",
|
||||||
|
"sizes": "384x384",
|
||||||
|
"type": "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "assets/images/icons/icon-512x512.png",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"type": "image/png"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
# https://www.robotstxt.org/robotstxt.html
|
||||||
|
User-agent: *
|
||||||
|
Disallow:
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
.App {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-layout {
|
||||||
|
background: #ffffff00;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,150 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Route, BrowserRouter, Switch } from 'react-router-dom';
|
||||||
|
import { Layout } from 'antd';
|
||||||
|
|
||||||
|
import { history } from './_helpers';
|
||||||
|
import { authenticationService } from './_services';
|
||||||
|
import { MathJaxContext } from "better-react-mathjax";
|
||||||
|
|
||||||
|
import { PrivateRoute } from './component/PrivateRoute';
|
||||||
|
import Login from './component/Login/Login';
|
||||||
|
import Dashboard from './component/Dashboard/Dashboard';
|
||||||
|
import CreateAccount from './component/Login/CreateAccount';
|
||||||
|
import ResetPassword from './component/Login/ResetPassword';
|
||||||
|
import FirebaseAction from './component/Login/FirebaseAction';
|
||||||
|
|
||||||
|
// import 'antd/dist/antd.less'; // imported inside App.less
|
||||||
|
import './App.less';
|
||||||
|
|
||||||
|
// import AllQuestions from './component/Questions/AllQuestions';
|
||||||
|
// import MyQuestions from './component/Questions/MyQuestions';
|
||||||
|
// import Draft from './component/Questions/DraftQuestion/Draft'
|
||||||
|
// import Classes from './component/Usergroup/Classes';
|
||||||
|
// import Class from './component/Class/CreateClass';
|
||||||
|
// import ViewClass from './component/Class/ViewClass';
|
||||||
|
// import AddQuestion from './component/Questions/AddQuestion';
|
||||||
|
// import Bookmark from './component/Questions/Bookmark'
|
||||||
|
// import CreateQuestion from './component/Questions/CreateQuestion'
|
||||||
|
// import UploadQuestion from './component/Questions/UploadQuestion'
|
||||||
|
// import CreateBatch from './component/batches/CreateBatch';
|
||||||
|
// import ViewBatch from './component/batches/ViewBatch';
|
||||||
|
// import CreateExam from './component/Exams/CreateExam';
|
||||||
|
// import DraftExam from './component/Exams/DraftExam';
|
||||||
|
// import CreatePractise from './component/Exams/createPractise';
|
||||||
|
// import DraftPractice from './component/Practice/DraftPractice';
|
||||||
|
// import LivePractice from './component/Practice/LivePractice';
|
||||||
|
// import UpcomingPractice from './component/Practice/UpcomingPractice';
|
||||||
|
|
||||||
|
// import AssociateBatchPractice from './component/Practice/PracticeAssociateBatch';
|
||||||
|
|
||||||
|
|
||||||
|
// import LiveExam from './component/Exams/LiveExam';
|
||||||
|
// import UpcomingExam from './component/Exams/UpcomingExam';
|
||||||
|
// import AssociateBatch from './component/Exams/AssociateBatch';
|
||||||
|
// import ExpiredExam from './component/Exams/ExpiredExam';
|
||||||
|
|
||||||
|
// import EditQuestion from './component/Questions/EditQuestion/EditQuestion';
|
||||||
|
import UpcomingExams from './component/Student/Exams/UpcomingExams/UpcomingExams';
|
||||||
|
import LiveExams from './component/Student/Exams/LiveExams/LiveExams';
|
||||||
|
import AttemptExam from './component/Student/Exams/AttemptExam/AttemptExam';
|
||||||
|
import Report from './component/Student/Exams/Report/Report';
|
||||||
|
import PracticeExams from './component/Student/Exams/PracticeExam/PracticeExams';
|
||||||
|
import AttemptRevision from './component/Student/Exams/AttemptPractice/AttemptRevision';
|
||||||
|
import AttemptPractice from './component/Student/Exams/AttemptPractice/AttemptPractice';
|
||||||
|
import Authenticate from './component/Login/Authenticate';
|
||||||
|
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
loader: { load: ["[tex]/html"] },
|
||||||
|
startup: {
|
||||||
|
typeset: false
|
||||||
|
},
|
||||||
|
tex: {
|
||||||
|
packages: { "[+]": ["html"] },
|
||||||
|
inlineMath: [
|
||||||
|
["$", "$"],
|
||||||
|
["\\(", "\\)"]
|
||||||
|
],
|
||||||
|
displayMath: [
|
||||||
|
["$$", "$$"],
|
||||||
|
["\\[", "\\]"]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class App extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
mj: {current: null},
|
||||||
|
currentUser: null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
authenticationService.currentUser.subscribe(x => this.setState({ currentUser: x }));
|
||||||
|
}
|
||||||
|
|
||||||
|
logout() {
|
||||||
|
authenticationService.logout();
|
||||||
|
history.push('/login');
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<Layout className='logged-in-view'>
|
||||||
|
<MathJaxContext
|
||||||
|
onStartup={(mathJax) => {
|
||||||
|
this.setState(prev => ({...prev, mj: {current: mathJax}}));
|
||||||
|
console.log("getting maths..");
|
||||||
|
}}
|
||||||
|
config={config}
|
||||||
|
>
|
||||||
|
<BrowserRouter history={history}>
|
||||||
|
<Switch>
|
||||||
|
<Route exact path="/login" component={Login} />
|
||||||
|
<Route exact path="/reset-password" component={ResetPassword} />
|
||||||
|
<Route exact path="/createAccount" component={CreateAccount} />
|
||||||
|
<Route exact path="/action" component={FirebaseAction} />
|
||||||
|
<Route exact path="/Student" component={Authenticate} />
|
||||||
|
{/* <PrivateRoute exact path="/classes" comp={Classes} /> */}
|
||||||
|
<PrivateRoute exact path="/liveExams" comp={LiveExams} />
|
||||||
|
<PrivateRoute exact path="/practiceExams" comp={PracticeExams} />
|
||||||
|
<PrivateRoute exact path="/upcomingExams" comp={UpcomingExams} />
|
||||||
|
<PrivateRoute exact path="/attemptRevision" comp={AttemptRevision} />
|
||||||
|
<PrivateRoute exact path="/attemptPractice" comp={AttemptPractice} />
|
||||||
|
<PrivateRoute exact path="/attemptExam" comp={AttemptExam} />
|
||||||
|
<PrivateRoute exact path="/report" comp={Report} />
|
||||||
|
{/* Always have non-exact path at the very end to eliminate conflicts with other 'exact' routes */}
|
||||||
|
{/* <PrivateRoute exact path="/class" comp={Class} /> */}
|
||||||
|
{/* <PrivateRoute exact path="/viewclass" comp={ViewClass} /> */}
|
||||||
|
{/* <PrivateRoute exact path="/viewbatch" comp={ViewBatch} /> */}
|
||||||
|
{/* <PrivateRoute exact path="/createbatch" comp={CreateBatch} /> */}
|
||||||
|
{/* <PrivateRoute exact path="/associatebatch" comp={AssociateBatch} /> */}
|
||||||
|
{/* <PrivateRoute exact path="/associatePracticeBatch" comp={AssociateBatchPractice} /> */}
|
||||||
|
{/* <PrivateRoute exact path="/expiredExams" comp={ExpiredExam} /> */}
|
||||||
|
{/* <PrivateRoute exact path="/upcomingExam" comp={UpcomingExam} /> */}
|
||||||
|
{/* <PrivateRoute exact path="/liveExam" comp={LiveExam} /> */}
|
||||||
|
{/* <PrivateRoute exact path="/draftExams" comp={DraftExam} /> */}
|
||||||
|
{/* <PrivateRoute exact path="/livePractise" comp={LivePractice} /> */}
|
||||||
|
{/* <PrivateRoute exact path="/draftPractise" comp={DraftPractice} /> */}
|
||||||
|
{/* <PrivateRoute exact path="/upcomingPractise" comp={UpcomingPractice} /> */}
|
||||||
|
{/* <PrivateRoute exact path="/editQuestion" comp={EditQuestion} /> */}
|
||||||
|
{/* <PrivateRoute exact path="/createPractise" comp={CreatePractise} /> */}
|
||||||
|
{/* <PrivateRoute exact path="/createExam" comp={CreateExam} /> do not uncomment this */}
|
||||||
|
{/* <PrivateRoute exact path="/draftQuestions" comp={Draft} /> do not uncomment this */}
|
||||||
|
{/* <PrivateRoute exact path="/bookmarkQuestions" comp={Bookmark} /> do not uncomment this */}
|
||||||
|
{/* <PrivateRoute exact path="/myQuestions" comp={MyQuestions} /> do not uncomment this */}
|
||||||
|
{/* <PrivateRoute exact path="/addQuestion" comp={AddQuestion} /> do not uncomment this */}
|
||||||
|
<PrivateRoute path="/" comp={Dashboard} mathJax={this.state.mj} />
|
||||||
|
</Switch>
|
||||||
|
</BrowserRouter>
|
||||||
|
</MathJaxContext>
|
||||||
|
</Layout>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export default App;
|
||||||
|
|
@ -0,0 +1,81 @@
|
||||||
|
@import '~antd/dist/antd.less';
|
||||||
|
@import 'UtilityClass.less';
|
||||||
|
|
||||||
|
@primary-color: #3b3cea;
|
||||||
|
|
||||||
|
@layout-body-background: #ffffff00;
|
||||||
|
@layout-header-background: #f0f3f8;
|
||||||
|
@layout-sider-background: #1449bc;
|
||||||
|
|
||||||
|
// MENU Styler
|
||||||
|
@menu-bg: @layout-sider-background;
|
||||||
|
// @menu-popup-bg: #ff4;
|
||||||
|
@menu-item-color: white;
|
||||||
|
@menu-highlight-color: white;
|
||||||
|
@menu-item-active-bg: lighten(@layout-sider-background, 30%);
|
||||||
|
@menu-inline-submenu-bg: lighten(@layout-sider-background, 5%);
|
||||||
|
@menu-item-active-border-width: 5px;
|
||||||
|
@layout-trigger-color: #f3a;
|
||||||
|
|
||||||
|
//buttons
|
||||||
|
@btn-primary-bg: @primary-color;
|
||||||
|
|
||||||
|
//checkboxes
|
||||||
|
@checkbox-color: @primary-color;
|
||||||
|
|
||||||
|
.App {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
// .ant-layout {
|
||||||
|
// background: #ffffff00;
|
||||||
|
// }
|
||||||
|
|
||||||
|
body {
|
||||||
|
overflow-y: hidden;
|
||||||
|
max-height: 100vh;
|
||||||
|
// position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
#root {
|
||||||
|
height: 100%;
|
||||||
|
background-color: #f0f3f8;
|
||||||
|
}
|
||||||
|
|
||||||
|
// .logged-in-view class styling is written in dashboard.css
|
||||||
|
|
||||||
|
.ant-layout-content {
|
||||||
|
overflow-y: scroll;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-layout-header {
|
||||||
|
padding: 0 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-layout-sider + .ant-layout {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
max-height: 90vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-layout > .ant-layout > header.ant-layout-header + section.ant-layout {
|
||||||
|
flex: 1 0 100%;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
// to override the style of ant-table-cells
|
||||||
|
body .ant-table-tbody > tr > td.ant-table-cell, .ant-table tfoot > tr > td.ant-table-cell {
|
||||||
|
padding: 3px 16px;
|
||||||
|
height: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
body .ant-table > .ant-table-container > .ant-table-content > table > thead > tr > th {
|
||||||
|
background-color: @layout-header-background;
|
||||||
|
padding-inline: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-empty {
|
||||||
|
margin: 24px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { render } from '@testing-library/react';
|
||||||
|
import App from './App';
|
||||||
|
|
||||||
|
test('renders learn react link', () => {
|
||||||
|
const { getByText } = render(<App />);
|
||||||
|
const linkElement = getByText(/learn react/i);
|
||||||
|
expect(linkElement).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,106 @@
|
||||||
|
@import '~antd/lib/style/color/colorPalette.less';
|
||||||
|
@import '~antd/dist/antd.less';
|
||||||
|
@import '~antd/lib/style/themes/dark.less';
|
||||||
|
@import '../UtilityClass.less';
|
||||||
|
|
||||||
|
// @primary-color: #5b9af8;
|
||||||
|
@primary-color: @blue-7;
|
||||||
|
@error-color: @red-8;
|
||||||
|
// @border-radius-base: 4px;
|
||||||
|
|
||||||
|
// @component-background: #303030;
|
||||||
|
// @body-background: #303030;
|
||||||
|
// @popover-background: #303030;
|
||||||
|
// @border-color-base: #6f6c6c;
|
||||||
|
// @border-color-split: #424242;
|
||||||
|
// @table-header-sort-active-bg: #424242;
|
||||||
|
// @card-skeleton-bg: #424242;
|
||||||
|
// @skeleton-color: #424242;
|
||||||
|
// @table-header-sort-active-bg: #424242;
|
||||||
|
|
||||||
|
// ----------------------- //
|
||||||
|
|
||||||
|
@layout-body-background: #ffffff00;
|
||||||
|
@layout-header-background: #1f2327;
|
||||||
|
@layout-sider-background: #000000;
|
||||||
|
|
||||||
|
// MENU Styler
|
||||||
|
@menu-bg: @layout-sider-background;
|
||||||
|
// @menu-popup-bg: #ff4;
|
||||||
|
@menu-item-color: white;
|
||||||
|
@menu-highlight-color: white;
|
||||||
|
@menu-item-active-bg: lighten(@layout-sider-background, 30%);
|
||||||
|
@menu-inline-submenu-bg: lighten(@layout-sider-background, 15%);
|
||||||
|
@menu-item-active-border-width: 5px;
|
||||||
|
@layout-trigger-color: #f3a;
|
||||||
|
@border-color-base: fade(#fff, 30%);
|
||||||
|
@select-dropdown-bg: lighten(@layout-header-background, 10%);
|
||||||
|
|
||||||
|
//buttons
|
||||||
|
@btn-primary-bg: @primary-color;
|
||||||
|
|
||||||
|
//checkboxes
|
||||||
|
@checkbox-color: @primary-color;
|
||||||
|
|
||||||
|
.App {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
// .ant-layout {
|
||||||
|
// background: #ffffff00;
|
||||||
|
// }
|
||||||
|
|
||||||
|
body {
|
||||||
|
overflow-y: hidden;
|
||||||
|
max-height: 100vh;
|
||||||
|
// position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
#root {
|
||||||
|
height: 100%;
|
||||||
|
background-color: @layout-header-background;
|
||||||
|
}
|
||||||
|
|
||||||
|
// .logged-in-view class styling is written in dashboard.css
|
||||||
|
|
||||||
|
.ant-layout-content {
|
||||||
|
overflow-y: auto;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-layout-header {
|
||||||
|
padding: 0 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-layout-has-sider {
|
||||||
|
height: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// below CSS is container for Header + Content
|
||||||
|
.ant-layout-sider + .ant-layout,
|
||||||
|
.main-sider-menu + .ant-layout {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
max-height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-layout > .ant-layout > header.ant-layout-header + section.ant-layout {
|
||||||
|
flex: 0 1 auto;
|
||||||
|
overflow-y: auto;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
}
|
||||||
|
|
||||||
|
// to override the style of ant-table-cells
|
||||||
|
body .ant-table-tbody > tr > td.ant-table-cell, .ant-table tfoot > tr > td.ant-table-cell {
|
||||||
|
padding: 3px 16px;
|
||||||
|
height: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
body .ant-table > .ant-table-container > .ant-table-content > table > thead > tr > th {
|
||||||
|
background-color: @layout-header-background;
|
||||||
|
padding-inline: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-empty {
|
||||||
|
margin: 24px 8px;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,89 @@
|
||||||
|
@import '~antd/dist/antd.less';
|
||||||
|
@import '../UtilityClass.less';
|
||||||
|
|
||||||
|
@primary-color: #3b3cea;
|
||||||
|
|
||||||
|
@layout-body-background: #ffffff00;
|
||||||
|
@layout-header-background: #f0f3f8;
|
||||||
|
// @layout-sider-background: #1449bc;
|
||||||
|
@layout-sider-background: @primary-color;
|
||||||
|
|
||||||
|
// MENU Styler
|
||||||
|
@menu-bg: @layout-sider-background;
|
||||||
|
// @menu-popup-bg: #ff4;
|
||||||
|
@menu-item-color: white;
|
||||||
|
@menu-highlight-color: white;
|
||||||
|
@menu-item-active-bg: lighten(@layout-sider-background, 10%);
|
||||||
|
@menu-inline-submenu-bg: lighten(@layout-sider-background, 5%);
|
||||||
|
@menu-item-active-border-width: 5px;
|
||||||
|
@layout-trigger-color: #f3a;
|
||||||
|
|
||||||
|
//buttons
|
||||||
|
@btn-primary-bg: @primary-color;
|
||||||
|
|
||||||
|
//checkboxes
|
||||||
|
@checkbox-color: @primary-color;
|
||||||
|
|
||||||
|
.App {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
// .ant-layout {
|
||||||
|
// background: #ffffff00;
|
||||||
|
// }
|
||||||
|
|
||||||
|
body {
|
||||||
|
overflow-y: hidden;
|
||||||
|
max-height: 100vh;
|
||||||
|
// position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
#root {
|
||||||
|
height: 100%;
|
||||||
|
background-color: @layout-header-background;
|
||||||
|
}
|
||||||
|
|
||||||
|
// .logged-in-view class styling is written in dashboard.css
|
||||||
|
|
||||||
|
.ant-layout-content {
|
||||||
|
overflow-y: auto;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-layout-header {
|
||||||
|
padding: 0 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.ant-layout-has-sider {
|
||||||
|
height: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// below CSS is container for Header + Content
|
||||||
|
.ant-layout-sider + .ant-layout,
|
||||||
|
.main-sider-menu + .ant-layout {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
max-height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-layout > .ant-layout > header.ant-layout-header + section.ant-layout {
|
||||||
|
flex: 0 1 auto;
|
||||||
|
overflow-y: auto;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
}
|
||||||
|
|
||||||
|
// to override the style of ant-table-cells
|
||||||
|
body .ant-table-tbody > tr > td.ant-table-cell, .ant-table tfoot > tr > td.ant-table-cell {
|
||||||
|
padding: 3px 16px;
|
||||||
|
height: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
body .ant-table > .ant-table-container > .ant-table-content > table > thead > tr > th {
|
||||||
|
background-color: @layout-header-background;
|
||||||
|
padding-inline: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-empty {
|
||||||
|
margin: 24px 8px;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
import React from "react";
|
||||||
|
import "./AppDark.less";
|
||||||
|
import "./variableDark.less";
|
||||||
|
|
||||||
|
const Dark = () => <></>;
|
||||||
|
export default Dark;
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
import React from "react";
|
||||||
|
import "./AppLight.less";
|
||||||
|
import "./variableLight.less";
|
||||||
|
|
||||||
|
const Light = () => <></>;
|
||||||
|
export default Light;
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
import React, { lazy, Suspense } from "react";
|
||||||
|
import { useTheme } from "./useTheme";
|
||||||
|
|
||||||
|
const DarkTheme = lazy(() => import("./Dark"));
|
||||||
|
const LightTheme = lazy(() => import("./Light"));
|
||||||
|
|
||||||
|
export const ThemeProvider = ({ children }) => {
|
||||||
|
const [darkMode] = useTheme();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Suspense fallback={<span />}>
|
||||||
|
{darkMode ? <DarkTheme /> : <LightTheme />}
|
||||||
|
</Suspense>
|
||||||
|
{children}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
const DARK_MODE = "dark-mode";
|
||||||
|
|
||||||
|
const getDarkMode = () => JSON.parse(localStorage.getItem(DARK_MODE)) || false;
|
||||||
|
|
||||||
|
export const useTheme = () => {
|
||||||
|
const [darkMode, setDarkMode] = useState(getDarkMode);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const initialValue = getDarkMode();
|
||||||
|
if (initialValue !== darkMode) {
|
||||||
|
localStorage.setItem(DARK_MODE, darkMode);
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
}, [darkMode]);
|
||||||
|
|
||||||
|
return [darkMode, setDarkMode];
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,70 @@
|
||||||
|
@font-color: #fff;
|
||||||
|
@font-color-alternate: #000;
|
||||||
|
@font-color-primary: #3b3cea;
|
||||||
|
@background-color-custom-1: #292e33;
|
||||||
|
@background-color-custom-2: #1f2327;
|
||||||
|
@button-activated-bg-color: darken(#1890FF,40%);
|
||||||
|
@button-deactivated-color: transparent;
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--theme-mode: light;
|
||||||
|
--font-color: @font-color;
|
||||||
|
--color-alternate: @font-color-alternate;
|
||||||
|
--font-color-primary: lighten(@font-color-primary, 20%);
|
||||||
|
--color-accent-1-40: fade(@font-color-primary, 40%);
|
||||||
|
--background-color-custom-1: @background-color-custom-1;
|
||||||
|
--background-color-custom-1-70: fade(@background-color-custom-1, 90%);
|
||||||
|
--background-color-custom-2: @background-color-custom-2;
|
||||||
|
--button-activated-bg-color: @button-activated-bg-color;
|
||||||
|
--success-button-font-color: limegreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
body svg {
|
||||||
|
// color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
// dark-mode for Markdown input
|
||||||
|
.CodeMirror {
|
||||||
|
color: @font-color;
|
||||||
|
border-color: @background-color-custom-1;
|
||||||
|
background-color: @background-color-custom-1;
|
||||||
|
}
|
||||||
|
.EasyMDEContainer .editor-toolbar button.active {
|
||||||
|
background-color: @font-color-alternate;
|
||||||
|
}
|
||||||
|
.EasyMDEContainer .CodeMirror-fullscreen {
|
||||||
|
background-color: @background-color-custom-2;
|
||||||
|
}
|
||||||
|
.EasyMDEContainer .editor-toolbar.fullscreen {
|
||||||
|
background-color: @background-color-custom-2;
|
||||||
|
}
|
||||||
|
.cm-s-easymde .CodeMirror-cursor {
|
||||||
|
border-color: @font-color;
|
||||||
|
}
|
||||||
|
.editor-toolbar > * {
|
||||||
|
color: @font-color;
|
||||||
|
}
|
||||||
|
.editor-toolbar > .active, .editor-toolbar > button:hover, .editor-preview pre, .cm-s-easymde .cm-comment {
|
||||||
|
background-color: @background-color-custom-2
|
||||||
|
}
|
||||||
|
.editor-preview {
|
||||||
|
background-color: @background-color-custom-1
|
||||||
|
}
|
||||||
|
#formula-input, .latex-container {
|
||||||
|
color: @font-color-alternate;
|
||||||
|
}
|
||||||
|
// fin
|
||||||
|
|
||||||
|
//darkmode for react-confirm-alert-body
|
||||||
|
.react-confirm-alert-body {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.react-confirm-alert-body > h1 {
|
||||||
|
font-size: 1.4rem;
|
||||||
|
color: var(--color-alternate);
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-confirm-alert-overlay {
|
||||||
|
background-color: var(--background-color-custom-1-70);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
@font-color: #616161;
|
||||||
|
@font-color-alternate: #fff;
|
||||||
|
@font-color-primary: #3b3cea;
|
||||||
|
@background-color-custom-1: #fff;
|
||||||
|
@background-color-custom-2: #f0f3f8;
|
||||||
|
@button-activated-bg-color: #1890FF;
|
||||||
|
@button-deactivated-color: transparent;
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--theme-mode: light;
|
||||||
|
--font-color: @font-color;
|
||||||
|
--color-alternate: @font-color-alternate;
|
||||||
|
--font-color-primary: @font-color-primary;
|
||||||
|
--color-accent-1-40: fade(@font-color-primary, 40%);
|
||||||
|
--background-color-custom-1: @background-color-custom-1;
|
||||||
|
--background-color-custom-1-70: fade(@background-color-custom-1, 80%);
|
||||||
|
--background-color-custom-2: @background-color-custom-2;
|
||||||
|
--button-activated-bg-color: @background-color-custom-1;
|
||||||
|
--success-button-font-color: #008633;
|
||||||
|
}
|
||||||
|
|
||||||
|
// body svg {
|
||||||
|
// color: #000;
|
||||||
|
// }
|
||||||
|
|
||||||
|
//lightmode for react-confirm-alert-body
|
||||||
|
.react-confirm-alert-body {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.react-confirm-alert-body > h1 {
|
||||||
|
font-size: 1.4rem;
|
||||||
|
// color: var(--color-alternate);
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-confirm-alert-overlay {
|
||||||
|
background-color: var(--background-color-custom-1-70);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,119 @@
|
||||||
|
// UTILITY Classes
|
||||||
|
.class-hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.overflow-y--ready {
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.overflow-x--ready {
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.overflow--ready {
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.w-100 {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.float-right {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.float-left {
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex--wrap {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex--column {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.center-flex {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.center-grid {
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.align--center {
|
||||||
|
align-self: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx-auto {
|
||||||
|
margin-inline: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ml-auto {
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.generate-margin-x-class(25);
|
||||||
|
|
||||||
|
.generate-margin-x-class(@n, @i: 0) when (@i =< @n) {
|
||||||
|
@a: floor(@i);
|
||||||
|
@b: floor(mod(@i, 1)*10);
|
||||||
|
.mx-@{a}-@{b} {
|
||||||
|
margin-inline: (@i * 100%/ @n);
|
||||||
|
}
|
||||||
|
.ml-@{a}-@{b} {
|
||||||
|
margin-left: (@i * 100%/ @n);
|
||||||
|
}
|
||||||
|
.mr-@{a}-@{b} {
|
||||||
|
margin-right: (@i * 100%/ @n);
|
||||||
|
}
|
||||||
|
.generate-margin-x-class(@n, (@i + 1/2));
|
||||||
|
}
|
||||||
|
|
||||||
|
.generate-margin-y-class(25);
|
||||||
|
|
||||||
|
.generate-margin-y-class(@n, @i: 0) when (@i =< @n) {
|
||||||
|
@a: floor(@i);
|
||||||
|
@b: floor(mod(@i, 1)*10);
|
||||||
|
.my-@{a}-@{b} {
|
||||||
|
margin-block: (@i * 100%/ @n);
|
||||||
|
}
|
||||||
|
.mt-@{a}-@{b} {
|
||||||
|
margin-top: (@i * 100%/ @n);
|
||||||
|
}
|
||||||
|
.mb-@{a}-@{b} {
|
||||||
|
margin-bottom: (@i * 100%/ @n);
|
||||||
|
}
|
||||||
|
.generate-margin-y-class(@n, (@i + 1/2));
|
||||||
|
}
|
||||||
|
|
||||||
|
.generate-padding-class(25);
|
||||||
|
|
||||||
|
.generate-padding-class(@n, @i: 0) when (@i =< @n) {
|
||||||
|
@a: floor(@i);
|
||||||
|
@b: floor(mod(@i, 1)*10);
|
||||||
|
.px-@{a}-@{b} {
|
||||||
|
padding-inline: (@i * 100%/ @n);
|
||||||
|
}
|
||||||
|
.py-@{a}-@{b} {
|
||||||
|
padding-block: (@i * 100%/ @n);
|
||||||
|
}
|
||||||
|
.pl-@{a}-@{b} {
|
||||||
|
padding-left: (@i * 100%/ @n);
|
||||||
|
}
|
||||||
|
.pr-@{a}-@{b} {
|
||||||
|
padding-right: (@i * 100%/ @n);
|
||||||
|
}
|
||||||
|
.pt-@{a}-@{b} {
|
||||||
|
padding-top: (@i * 100%/ @n);
|
||||||
|
}
|
||||||
|
.pb-@{a}-@{b} {
|
||||||
|
padding-bottom: (@i * 100%/ @n);
|
||||||
|
}
|
||||||
|
.generate-padding-class(@n, (@i + 1/2));
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
import { authenticationService } from '../_services';
|
||||||
|
|
||||||
|
export function authHeader() {
|
||||||
|
// return authorization header with jwt token
|
||||||
|
const currentUser = authenticationService.currentUserValue;
|
||||||
|
if (currentUser && currentUser.jwtToken) {
|
||||||
|
//return { Authorization: `Bearer ${currentUser.token}` };
|
||||||
|
console.log({ Authorization: `Bearer ${currentUser.jwtToken}` });
|
||||||
|
return { Authorization: `Bearer ${currentUser.jwtToken}` };
|
||||||
|
} else {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
export function configureFakeBackend() {
|
||||||
|
let users = [{ id: 1, username: 'test', password: 'test', firstName: 'Test', lastName: 'User' }];
|
||||||
|
let realFetch = window.fetch;
|
||||||
|
window.fetch = function (url, opts) {
|
||||||
|
const isLoggedIn = opts.headers['Authorization'] === 'Bearer fake-jwt-token';
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
// wrap in timeout to simulate server api call
|
||||||
|
setTimeout(() => {
|
||||||
|
// authenticate - public
|
||||||
|
if (url.endsWith('/users/authenticate') && opts.method === 'POST') {
|
||||||
|
const params = JSON.parse(opts.body);
|
||||||
|
const user = users.find(x => x.username === params.username && x.password === params.password);
|
||||||
|
if (!user) return error('Username or password is incorrect');
|
||||||
|
return ok({
|
||||||
|
id: user.id,
|
||||||
|
username: user.username,
|
||||||
|
firstName: user.firstName,
|
||||||
|
lastName: user.lastName,
|
||||||
|
token: 'fake-jwt-token'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// get users - secure
|
||||||
|
if (url.endsWith('/users') && opts.method === 'GET') {
|
||||||
|
if (!isLoggedIn) return unauthorised();
|
||||||
|
return ok(users);
|
||||||
|
}
|
||||||
|
|
||||||
|
// pass through any requests not handled above
|
||||||
|
realFetch(url, opts).then(response => resolve(response));
|
||||||
|
|
||||||
|
// private helper functions
|
||||||
|
|
||||||
|
function ok(body) {
|
||||||
|
resolve({ ok: true, text: () => Promise.resolve(JSON.stringify(body)) })
|
||||||
|
}
|
||||||
|
|
||||||
|
function unauthorised() {
|
||||||
|
resolve({ status: 401, text: () => Promise.resolve(JSON.stringify({ message: 'Unauthorised' })) })
|
||||||
|
}
|
||||||
|
|
||||||
|
function error(message) {
|
||||||
|
resolve({ status: 400, text: () => Promise.resolve(JSON.stringify({ message })) })
|
||||||
|
}
|
||||||
|
}, 500);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
import { authenticationService } from '../_services';
|
||||||
|
|
||||||
|
export function handleResponse(response) {
|
||||||
|
return response.text().then(text => {
|
||||||
|
const data = text && JSON.parse(text);
|
||||||
|
if (!response.ok) {
|
||||||
|
if ([401, 403].indexOf(response.status) !== -1) {
|
||||||
|
// auto logout if 401 Unauthorized or 403 Forbidden response returned from api
|
||||||
|
authenticationService.logout();
|
||||||
|
window.location.reload(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
const error = (data && data.message) || response.statusText;
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
console.log("Printing data!!!")
|
||||||
|
console.log(data);
|
||||||
|
return data.result;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
import { createBrowserHistory } from 'history';
|
||||||
|
|
||||||
|
export const history = createBrowserHistory();
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
export * from './auth-header';
|
||||||
|
export * from './fake-backend';
|
||||||
|
export * from './handle-response';
|
||||||
|
export * from './history';
|
||||||
|
|
@ -0,0 +1,313 @@
|
||||||
|
import { BehaviorSubject } from 'rxjs';
|
||||||
|
import { API_USER_SIGN_IN, FIREBASE_LOGIN } from './config';
|
||||||
|
|
||||||
|
const currentUserSubject = new BehaviorSubject(sessionStorage.getItem('currentUser') !== undefined ? JSON.parse(sessionStorage.getItem('currentUser')) : {});
|
||||||
|
|
||||||
|
|
||||||
|
export const authenticationService = {
|
||||||
|
login,
|
||||||
|
logout,
|
||||||
|
updateClass,
|
||||||
|
registerUser,
|
||||||
|
authenticate,
|
||||||
|
loginToSystem,
|
||||||
|
refreshFireToken,
|
||||||
|
resetPassword,
|
||||||
|
newPassword,
|
||||||
|
fetchUserEmail,
|
||||||
|
verifyEmail,
|
||||||
|
currentUser: currentUserSubject.asObservable(),
|
||||||
|
get currentUserValue() { return currentUserSubject.value },
|
||||||
|
get currentClassValue() { return sessionStorage.getItem('currentClass') },
|
||||||
|
get currentClassNameValue() { return sessionStorage.getItem('currentClassName') }
|
||||||
|
};
|
||||||
|
|
||||||
|
async function registerUser(email, password) {
|
||||||
|
const requestOptions = {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ email, password, returnSecureToken: true })
|
||||||
|
};
|
||||||
|
const response = await fetch(`https://identitytoolkit.googleapis.com/v1/accounts:signUp?key=${process.env.REACT_APP_FIREBASE_API_KEY}`, requestOptions);
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
return data;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function login(username, password, saveAuth) {
|
||||||
|
const requestOptions = {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ email: username, password, returnSecureToken: true })
|
||||||
|
};
|
||||||
|
|
||||||
|
// return fetch(`${config.apiUrl}/users/authenticate`, requestOptions)
|
||||||
|
return fetch(FIREBASE_LOGIN, requestOptions)
|
||||||
|
.then(response => {
|
||||||
|
if (response.ok) {
|
||||||
|
return response.json();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(token => {
|
||||||
|
|
||||||
|
//check to see if the credentials were received
|
||||||
|
// console.log(token.idToken);
|
||||||
|
if (!token) {
|
||||||
|
throw new Error("Credentials not correct, please check and try again.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// store user details and jwt token in local storage to keep user logged in between page refreshes
|
||||||
|
sessionStorage.setItem('currentToken', token.idToken);
|
||||||
|
saveAuth && localStorage.setItem('currentToken', token.idToken);
|
||||||
|
sessionStorage.setItem('refreshToken', token.refreshToken);
|
||||||
|
saveAuth && localStorage.setItem('refreshToken', token.refreshToken);
|
||||||
|
let user = loginToSystem(saveAuth);
|
||||||
|
currentUserSubject.next(user); //Why is this written twice?
|
||||||
|
return user;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// TOKEN VERIFICATION
|
||||||
|
|
||||||
|
function loginToSystem(saveAuth) {
|
||||||
|
let idToken = sessionStorage.getItem('currentToken');
|
||||||
|
const requestOptions = {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Access-Control-Allow-Origin": "*",
|
||||||
|
Accept: "application/json",
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Authorization: "Bearer ".concat(idToken),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
return fetch(
|
||||||
|
API_USER_SIGN_IN,
|
||||||
|
requestOptions
|
||||||
|
)
|
||||||
|
.then((response) => {
|
||||||
|
if (response.ok) {
|
||||||
|
return response.json();
|
||||||
|
}
|
||||||
|
// window.localStorage.clear();
|
||||||
|
// history.push("/login");
|
||||||
|
})
|
||||||
|
.then((user) => {
|
||||||
|
// console.log(user.result);
|
||||||
|
let userResult = user.result;
|
||||||
|
userResult['jwtToken'] = sessionStorage.getItem('currentToken');
|
||||||
|
userResult['refreshToken'] = sessionStorage.getItem('refreshToken');
|
||||||
|
sessionStorage.setItem('currentUser', JSON.stringify(userResult));
|
||||||
|
saveAuth && localStorage.setItem('currentUser', JSON.stringify(userResult));
|
||||||
|
currentUserSubject.next(userResult); // Check login function. This is written twice
|
||||||
|
return userResult;
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('Error:', error); // This was removed but I added it back
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function authenticate(auth) {
|
||||||
|
const requestOptions = {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// return fetch(`${config.apiUrl}/users/authenticate`, requestOptions)
|
||||||
|
return fetch('https://api.odiprojects.com/api-user/v1/Users/VerifyAccount/' + auth, requestOptions)
|
||||||
|
.then((response) => {
|
||||||
|
if (response.ok) {
|
||||||
|
// console.log(response.json());
|
||||||
|
return response.json();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(data => {
|
||||||
|
return data;
|
||||||
|
}).catch((error) => {
|
||||||
|
// console.error('Error:', error);
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function updateClass(className, classId) {
|
||||||
|
sessionStorage.setItem('currentClass', classId);
|
||||||
|
sessionStorage.setItem('currentClassName', className);
|
||||||
|
|
||||||
|
}
|
||||||
|
function logout() {
|
||||||
|
// remove user from local storage to log user out
|
||||||
|
sessionStorage.removeItem('currentUser');
|
||||||
|
sessionStorage.removeItem('currentToken');
|
||||||
|
sessionStorage.removeItem('refreshToken');
|
||||||
|
sessionStorage.removeItem('currentClass');
|
||||||
|
sessionStorage.removeItem('currentClassName');
|
||||||
|
localStorage.removeItem('currentUser');
|
||||||
|
localStorage.removeItem('currentToken');
|
||||||
|
localStorage.removeItem('refreshToken');
|
||||||
|
localStorage.removeItem('currentClass');
|
||||||
|
localStorage.removeItem('currentClassName');
|
||||||
|
currentUserSubject.next(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
function refreshFireToken() {
|
||||||
|
let refreshTokenInStore = sessionStorage.getItem('refreshToken');
|
||||||
|
let currentUserStored = JSON.parse(sessionStorage.getItem('currentUser'));
|
||||||
|
if (!refreshTokenInStore) {
|
||||||
|
let json = JSON.parse(localStorage.getItem('currentUser'));
|
||||||
|
refreshTokenInStore = json.refreshToken;
|
||||||
|
currentUserStored = json;
|
||||||
|
}
|
||||||
|
// console.log(refreshTokenInStore);
|
||||||
|
if (!refreshTokenInStore) {
|
||||||
|
throw "No login found";
|
||||||
|
}
|
||||||
|
const requestOptions = {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ grant_type: 'refresh_token', refresh_token: refreshTokenInStore })
|
||||||
|
};
|
||||||
|
|
||||||
|
return fetch(`https://securetoken.googleapis.com/v1/token?key=${process.env.REACT_APP_FIREBASE_API_KEY}`, requestOptions)
|
||||||
|
.then(response => {
|
||||||
|
if (response.ok) {
|
||||||
|
return response.json();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(token => {
|
||||||
|
|
||||||
|
// check to see if the credentials were received
|
||||||
|
if (!token) {
|
||||||
|
throw new Error("Credentials not correct, please check and try again.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// store user details and jwt token in session storage to keep user logged in between page refreshes
|
||||||
|
sessionStorage.setItem('currentToken', token.id_token);
|
||||||
|
sessionStorage.setItem('refreshToken', token.refresh_token);
|
||||||
|
|
||||||
|
if (currentUserStored.id) {
|
||||||
|
currentUserStored.refreshToken = token.refresh_token;
|
||||||
|
currentUserStored.jwtToken = token.id_token;
|
||||||
|
sessionStorage.setItem('currentUser', JSON.stringify(currentUserStored));
|
||||||
|
currentUserSubject.next(currentUserStored);
|
||||||
|
return currentUserStored;
|
||||||
|
}
|
||||||
|
|
||||||
|
// cases when currentUserStored does not exist
|
||||||
|
let user = loginToSystem(); // may not be needed everytime
|
||||||
|
currentUserSubject.next(user); //Why is this written twice?
|
||||||
|
return user;
|
||||||
|
}).catch((err) => {
|
||||||
|
// refreshFireToken();
|
||||||
|
console.error(err);
|
||||||
|
throw "No access token found, trying again...";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function resetPassword(email) {
|
||||||
|
if (email) {
|
||||||
|
const requestOptions = {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ requestType: 'PASSWORD_RESET', email })
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
const res = await fetch(`https://identitytoolkit.googleapis.com/v1/accounts:sendOobCode?key=${process.env.REACT_APP_FIREBASE_API_KEY}`, requestOptions)
|
||||||
|
const data = await res.json();
|
||||||
|
if (res.status === 400) {
|
||||||
|
throw data;
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
} catch (err) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
throw "Please enter an email address";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function newPassword(oobCode, newPassword) {
|
||||||
|
if (newPassword && oobCode) {
|
||||||
|
const requestOptions = {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ oobCode, newPassword })
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
const res = await fetch(`https://identitytoolkit.googleapis.com/v1/accounts:resetPassword?key=${process.env.REACT_APP_FIREBASE_API_KEY}`, requestOptions);
|
||||||
|
const data = await res.json();
|
||||||
|
if (res.status === 400) {
|
||||||
|
throw data;
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
} catch (err) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw "Please enter your new password";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchUserEmail(oobCode) {
|
||||||
|
if (oobCode) {
|
||||||
|
const requestOptions = {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ oobCode })
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
const res = await fetch(`https://identitytoolkit.googleapis.com/v1/accounts:resetPassword?key=${process.env.REACT_APP_FIREBASE_API_KEY}`, requestOptions);
|
||||||
|
const data = await res.json();
|
||||||
|
if (res.status === 400) {
|
||||||
|
throw data;
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
} catch (err) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw { error: "Please check your reset password link" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function verifyEmail(oobCode) {
|
||||||
|
if (oobCode) {
|
||||||
|
const requestOptions = {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ oobCode })
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
const res = await fetch(`https://identitytoolkit.googleapis.com/v1/accounts:update?key=${process.env.REACT_APP_FIREBASE_API_KEY}`, requestOptions);
|
||||||
|
const data = await res.json();
|
||||||
|
if (res.status === 400) {
|
||||||
|
throw data;
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
} catch (err) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw { error: "Please check if your verification email link is correct." }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
const DOMAIN = "https://api.odiprojects.com";
|
||||||
|
const FIREBASE_API_KEY = process.env.REACT_APP_FIREBASE_API_KEY;
|
||||||
|
const API_VERSION = process.env.REACT_APP_API_VERSION;
|
||||||
|
const INSTITUTE_API_PREFIX = `${DOMAIN}/api-institute/v`;
|
||||||
|
const ADMIN_API_PREFIX = `${DOMAIN}/api-admin/v`;
|
||||||
|
const TEACHER_API_PREFIX = `${DOMAIN}/api-teacher/v`;
|
||||||
|
|
||||||
|
export const S3_BUCKET_EXAM_PREFIX = "https://s3bucket-for-oa.s3.ap-south-1.amazonaws.com/1/exams/" //ADD FILENAME.PNG AT THE END
|
||||||
|
export const S3_BUCKET_PRACTICE_PREFIX = "https://s3bucket-for-oa.s3.ap-south-1.amazonaws.com/1/practices/" //ADD FILENAME.PNG AT THE END
|
||||||
|
|
||||||
|
export const FIREBASE_LOGIN = `https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key=${FIREBASE_API_KEY}`
|
||||||
|
export const API_USER_SIGN_IN = `https://api.odiprojects.com/api-student/v${API_VERSION}/Users/SignIn`;
|
||||||
|
|
||||||
|
export const INSTITUTE_API = INSTITUTE_API_PREFIX + API_VERSION;
|
||||||
|
|
||||||
|
export const TEACHER_API = TEACHER_API_PREFIX + API_VERSION;
|
||||||
|
|
||||||
|
export const LOAD_QUESTIONTYPES = `${ADMIN_API_PREFIX}${API_VERSION}/QuestionTypes`;
|
||||||
|
|
||||||
|
export const LOAD_LANGAUGES = `${ADMIN_API_PREFIX}${API_VERSION}/Languages`;
|
||||||
|
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
import i18n from 'i18next';
|
||||||
|
import { initReactI18next } from 'react-i18next';
|
||||||
|
import httpApi from "i18next-http-backend";
|
||||||
|
|
||||||
|
i18n.use(initReactI18next).use(httpApi).init({
|
||||||
|
fallbackLng: 'en',
|
||||||
|
lng: 'en',
|
||||||
|
keySeparator: '.',
|
||||||
|
resources: {
|
||||||
|
en: {
|
||||||
|
translations: require('../locales/en/translation.json')
|
||||||
|
},
|
||||||
|
hi: {
|
||||||
|
translations: require('../locales/hi/translation.json')
|
||||||
|
},
|
||||||
|
or: {
|
||||||
|
translations: require('../locales/or/translation.json')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
interporlation: {
|
||||||
|
escapeValue: false,
|
||||||
|
},
|
||||||
|
debug: process.env.NODE_ENV === 'development',
|
||||||
|
ns: ['translations'],
|
||||||
|
defaultNS: 'translations',
|
||||||
|
|
||||||
|
// special options for react-i18next
|
||||||
|
// learn more: https://react.i18next.com/components/i18next-instance
|
||||||
|
react: {
|
||||||
|
wait: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
i18n.languages = ['en', 'hi', 'or'];
|
||||||
|
|
||||||
|
// // export function to change Language on the fly
|
||||||
|
// export const changeLanguage = (lang) => {
|
||||||
|
// const { i18n } = useTranslation();
|
||||||
|
|
||||||
|
// function changeLanguage(lang) {
|
||||||
|
// i18n.changeLanguage(lang);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// changeLanguage(lang);
|
||||||
|
// }
|
||||||
|
|
||||||
|
export default i18n;
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
export * from './authentication.service';
|
||||||
|
export * from './user.service';
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
// import config from 'config';
|
||||||
|
// import { authHeader, handleResponse } from '../_helpers';
|
||||||
|
// import { authenticationService } from '../_services';
|
||||||
|
|
||||||
|
// export const userService = {
|
||||||
|
// getAll
|
||||||
|
// };
|
||||||
|
|
||||||
|
// function getAll() {
|
||||||
|
// //const requestOptions = { method: 'GET', headers: authHeader() };
|
||||||
|
// const currentUser = authenticationService.currentUserValue;
|
||||||
|
// const requestOptions = {
|
||||||
|
// method: 'GET',
|
||||||
|
// headers: {
|
||||||
|
// //'Accept': '*/*',
|
||||||
|
// //'Connection': 'keep-alive',
|
||||||
|
// //'Content-Type': 'application/json',
|
||||||
|
// //'Access-Control-Allow-Origin': '*',
|
||||||
|
// //'Access-Control-Allow-Methods': 'HEAD, GET, POST, PUT, PATCH, DELETE, OPTIONS',
|
||||||
|
// //'Access-Control-Allow-Headers': 'Origin, Content-Type, X-Auth-Token',
|
||||||
|
// 'Authorization': 'Bearer '.concat(currentUser.jwtToken)
|
||||||
|
// },
|
||||||
|
// };
|
||||||
|
// console.log(requestOptions);
|
||||||
|
// //return fetch(`${config.apiUrl}/v1/Users`, requestOptions)
|
||||||
|
// return fetch(`http://api.odiprojects.com/api-institute/v1/en/Classes`, requestOptions)
|
||||||
|
// .then((response) => response.text())
|
||||||
|
// .then((responseText) => {
|
||||||
|
// console.log(JSON.parse(responseText));
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
import React from 'react';
|
||||||
|
import './AllQuestions.css';
|
||||||
|
import { Layout } from 'antd';
|
||||||
|
import 'antd/dist/antd.css';
|
||||||
|
import Selector from './Selector';
|
||||||
|
import QuestionTable from './QuestionTable';
|
||||||
|
import { authenticationService } from '../_services';
|
||||||
|
import { Siderc } from './Siderc';
|
||||||
|
import { Headerc } from './Headerc';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class AllQuestions extends React.Component{
|
||||||
|
state = {
|
||||||
|
currentUser: authenticationService.currentUserValue,
|
||||||
|
top: 'topLeft',
|
||||||
|
bottom: 'bottomRight',
|
||||||
|
classes: "1" ,
|
||||||
|
subject: "",
|
||||||
|
category:"",
|
||||||
|
questionType:"",
|
||||||
|
complexity:""
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
callbackFunction = (childData) => {
|
||||||
|
console.log("Parent recieved Selector Data: "+ childData);
|
||||||
|
this.setState({
|
||||||
|
classes: childData.classes ,
|
||||||
|
module_id: childData.module_id,
|
||||||
|
module: childData.module,
|
||||||
|
questionType: childData.questionType,
|
||||||
|
complexity: childData.complexity,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
render(){
|
||||||
|
return(
|
||||||
|
<Layout>
|
||||||
|
{/* <Siderc /> */}
|
||||||
|
<Layout style={{}}>
|
||||||
|
{/* <Headerc /> */}
|
||||||
|
<Layout>
|
||||||
|
<Layout className="SubSection1">
|
||||||
|
<Selector page = "allQuestions" parentCallback = {this.callbackFunction}/>
|
||||||
|
<Layout className="SubSection1">
|
||||||
|
<div style={{padding:'5px'}}>
|
||||||
|
<QuestionTable selectorState ={this.state} />
|
||||||
|
</div>
|
||||||
|
</Layout>
|
||||||
|
</Layout>
|
||||||
|
</Layout>
|
||||||
|
</Layout>
|
||||||
|
</Layout>
|
||||||
|
)
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AllQuestions;
|
||||||
|
|
@ -0,0 +1,570 @@
|
||||||
|
import {
|
||||||
|
Steps,
|
||||||
|
Button,
|
||||||
|
message,
|
||||||
|
Layout,
|
||||||
|
Input,
|
||||||
|
List,
|
||||||
|
Avatar,
|
||||||
|
Skeleton,
|
||||||
|
Row,
|
||||||
|
Col,
|
||||||
|
Empty,
|
||||||
|
Checkbox,
|
||||||
|
Transfer,
|
||||||
|
} from "antd";
|
||||||
|
import { Siderc } from "../Main/Siderc";
|
||||||
|
import {
|
||||||
|
FormOutlined,
|
||||||
|
CheckSquareTwoTone,
|
||||||
|
CloseSquareTwoTone,
|
||||||
|
} from "@ant-design/icons";
|
||||||
|
import React from "react";
|
||||||
|
import "./CreateClass.css";
|
||||||
|
import { Headerc } from "../Main/Headerc";
|
||||||
|
import { Link, Redirect } from "react-router-dom";
|
||||||
|
import history from "../../history";
|
||||||
|
import { selectorService } from "../../services/selectorService";
|
||||||
|
import parse from "html-react-parser";
|
||||||
|
|
||||||
|
const { Content, Footer } = Layout;
|
||||||
|
|
||||||
|
class CreateBatch extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
className:
|
||||||
|
props.location.state && props.location.state.name
|
||||||
|
? props.location.state.name
|
||||||
|
: "",
|
||||||
|
users: [],
|
||||||
|
step: props.location.state && props.location.state.name ? 2 : 1,
|
||||||
|
mockData: [],
|
||||||
|
targetKeys: [],
|
||||||
|
batch_id:
|
||||||
|
props.location.state && props.location.state.id
|
||||||
|
? props.location.state.id
|
||||||
|
: "",
|
||||||
|
addingSubject: false,
|
||||||
|
updatedName:
|
||||||
|
props.location.state && props.location.state.name
|
||||||
|
? props.location.state.name
|
||||||
|
: "",
|
||||||
|
child: [],
|
||||||
|
addingTopic: false,
|
||||||
|
currentTopic: "",
|
||||||
|
editing: false,
|
||||||
|
idArray: [],
|
||||||
|
res: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
componentDidMount() {
|
||||||
|
if (typeof this.state.batch_id === "number") {
|
||||||
|
selectorService.getClassDetails(this.state.batch_id).then((res) => {
|
||||||
|
let idArray = [];
|
||||||
|
let users = res.result.subjects.map((x) => {
|
||||||
|
idArray.push(x.id);
|
||||||
|
return [x.name];
|
||||||
|
});
|
||||||
|
res.result.subjects.map((x, idx) => {
|
||||||
|
x.topics.map((y, idxY) => {
|
||||||
|
users[idx].push(y.name);
|
||||||
|
});
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
this.setState({ child: users, idArray });
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.log(err);
|
||||||
|
});
|
||||||
|
// this.setState({ upd });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let header =
|
||||||
|
this.state.step === 1
|
||||||
|
? "Class Name"
|
||||||
|
: !this.state.editing
|
||||||
|
? this.state.className + " Class"
|
||||||
|
: "";
|
||||||
|
console.log(this.state.child);
|
||||||
|
return (
|
||||||
|
<Content className="site-layout-background" style={{ padding: 24, margin: 0 }}>
|
||||||
|
<Layout className="createExamLayout">
|
||||||
|
<div style={{ position: "relative" }}>
|
||||||
|
<strong>
|
||||||
|
<h2>
|
||||||
|
<span>
|
||||||
|
<Input
|
||||||
|
value={this.state.updatedName}
|
||||||
|
onChange={(e) =>
|
||||||
|
this.setState({ updatedName: e.target.value })
|
||||||
|
}
|
||||||
|
hidden={!this.state.editing}
|
||||||
|
style={{ width: "10%", float: "left", height: "30px" }}
|
||||||
|
placeholder="Class Name"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<span style={{ float: "left" }}> {header}</span>
|
||||||
|
<span
|
||||||
|
className="examNameEdit"
|
||||||
|
hidden={this.state.step === 1}
|
||||||
|
style={{
|
||||||
|
cursor: "pointer",
|
||||||
|
width: "5%",
|
||||||
|
float: "left",
|
||||||
|
// position: "absolute",
|
||||||
|
// top: "10%",
|
||||||
|
// right: "0",
|
||||||
|
display: "inline-block",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div onClick={() => this.setState({ editing: true })}>
|
||||||
|
<FormOutlined
|
||||||
|
hidden={this.state.editing}
|
||||||
|
style={{ fontSize: "16px", color: "#1890FF" }}
|
||||||
|
twoToneColor="#1890FF"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
hidden={!this.state.editing}
|
||||||
|
onClick={() => {
|
||||||
|
this.setState({
|
||||||
|
editing: false,
|
||||||
|
updatedName: this.state.className,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CloseSquareTwoTone />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
onClick={() => {
|
||||||
|
const {
|
||||||
|
updatedName,
|
||||||
|
batch_id,
|
||||||
|
className,
|
||||||
|
} = this.state;
|
||||||
|
selectorService.updateClass({name: updatedName, id: batch_id }).then((res) => {
|
||||||
|
// console.log(res);
|
||||||
|
if (!res.result?.id) {
|
||||||
|
message.error("Some Error occured");
|
||||||
|
this.setState({
|
||||||
|
editing: false,
|
||||||
|
updatedName: className,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.setState({
|
||||||
|
editing: false,
|
||||||
|
className: this.state.updatedName,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.log(err);
|
||||||
|
this.setState({ editing: false });
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
hidden={!this.state.editing}
|
||||||
|
>
|
||||||
|
<CheckSquareTwoTone />
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
</h2>
|
||||||
|
</strong>
|
||||||
|
|
||||||
|
<span
|
||||||
|
hidden={this.state.step === 1}
|
||||||
|
style={{
|
||||||
|
cursor: "pointer",
|
||||||
|
width: "5px",
|
||||||
|
float: "right",
|
||||||
|
position: "absolute",
|
||||||
|
right: "15%",
|
||||||
|
top: "0",
|
||||||
|
// right: "1",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<Button
|
||||||
|
disabled={
|
||||||
|
this.state.addingSubject || this.state.addingTopic
|
||||||
|
}
|
||||||
|
onClick={() => {
|
||||||
|
let child = this.state.child;
|
||||||
|
child = [[]].concat(child);
|
||||||
|
this.setState({ child, addingSubject: true });
|
||||||
|
}}
|
||||||
|
type="primary"
|
||||||
|
>
|
||||||
|
Add Subjects
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div hidden={this.state.step !== 1}>
|
||||||
|
<Input
|
||||||
|
value={this.state.className}
|
||||||
|
onChange={(e) => {
|
||||||
|
this.setState({ className: e.target.value });
|
||||||
|
}}
|
||||||
|
style={{ width: "80%" }}
|
||||||
|
size="large"
|
||||||
|
placeholder="Class"
|
||||||
|
/>
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
if (
|
||||||
|
!this.state.className ||
|
||||||
|
this.state.className.length === 0
|
||||||
|
) {
|
||||||
|
message.error("Class name is mandatory");
|
||||||
|
} else {
|
||||||
|
selectorService.createClass({name: this.state.className}).then((res) => {
|
||||||
|
if (res.result.id) {
|
||||||
|
this.setState({
|
||||||
|
step: this.state.step + 1,
|
||||||
|
batch_id: res.result.id,
|
||||||
|
updatedName: this.state.className,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
message.error("Some error occured");
|
||||||
|
this.setState({ className: "" });
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.log(err);
|
||||||
|
this.setState({ className: "" });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// this.setState({ step: 2 });
|
||||||
|
}}
|
||||||
|
style={{ width: "13%" }}
|
||||||
|
type="primary"
|
||||||
|
>
|
||||||
|
Create Class
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div hidden={this.state.step === 1}>
|
||||||
|
<br />
|
||||||
|
{this.state.child.map((x, idx) => {
|
||||||
|
if (this.state.addingSubject && idx === 0) {
|
||||||
|
return (
|
||||||
|
<Footer
|
||||||
|
style={{
|
||||||
|
marginBottom: "10px",
|
||||||
|
backgroundColor: "white",
|
||||||
|
border: "1px solid rgb(231 230 230)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
style={{ width: "80%" }}
|
||||||
|
placeholder="Enter Subject"
|
||||||
|
onChange={(e) => {
|
||||||
|
let child = [...this.state.child];
|
||||||
|
child[idx] = [e.target.value];
|
||||||
|
this.setState({ child });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
selectorService
|
||||||
|
.addSubjectToClass({
|
||||||
|
name: this.state.child[idx][0],
|
||||||
|
id: this.state.batch_id,
|
||||||
|
})
|
||||||
|
.then((res) => {
|
||||||
|
console.log(res);
|
||||||
|
if (res.status.code !== "-1") {
|
||||||
|
console.log("1");
|
||||||
|
this.setState({
|
||||||
|
addingSubject: false,
|
||||||
|
idArray: [res.result.id].concat(
|
||||||
|
this.state.idArray
|
||||||
|
),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
let child = [...this.state.child];
|
||||||
|
child = child.slice(1);
|
||||||
|
this.setState(
|
||||||
|
{
|
||||||
|
addingSubject: false,
|
||||||
|
child,
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
message.error(
|
||||||
|
"Same Subject name under class not allowed"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.log(err);
|
||||||
|
this.setState({ addingSubject: false });
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
type="primary"
|
||||||
|
style={{
|
||||||
|
float: "right",
|
||||||
|
width: "8%",
|
||||||
|
textAlign: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
let child = [...this.state.child];
|
||||||
|
child = child.slice(1);
|
||||||
|
this.setState({ child, addingSubject: false });
|
||||||
|
}}
|
||||||
|
type="primary"
|
||||||
|
style={{
|
||||||
|
float: "right",
|
||||||
|
marginRight: "2%",
|
||||||
|
width: "8%",
|
||||||
|
textAlign: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</Footer>
|
||||||
|
);
|
||||||
|
} else if (
|
||||||
|
this.state.addingTopic &&
|
||||||
|
idx === this.state.currentTopic
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
<Footer
|
||||||
|
style={{
|
||||||
|
marginBottom: "10px",
|
||||||
|
backgroundColor: "white",
|
||||||
|
border: "1px solid rgb(231 230 230)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{x[0]}
|
||||||
|
<Button
|
||||||
|
disabled={true}
|
||||||
|
onClick={() => {
|
||||||
|
console.log(idx);
|
||||||
|
}}
|
||||||
|
type="primary"
|
||||||
|
style={{ float: "right" }}
|
||||||
|
>
|
||||||
|
Add Topic
|
||||||
|
</Button>
|
||||||
|
{x.map((y, idxTopic) => {
|
||||||
|
if (idxTopic === 0) return null;
|
||||||
|
else if (idxTopic !== x.length - 1)
|
||||||
|
return (
|
||||||
|
<Footer
|
||||||
|
style={{
|
||||||
|
border: "1px solid rgb(231 230 230)",
|
||||||
|
backgroundColor: "white",
|
||||||
|
padding: "20px",
|
||||||
|
marginTop: "20px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{y}
|
||||||
|
</Footer>
|
||||||
|
);
|
||||||
|
else
|
||||||
|
return (
|
||||||
|
<Footer
|
||||||
|
style={{
|
||||||
|
border: "1px solid rgb(231 230 230)",
|
||||||
|
backgroundColor: "white",
|
||||||
|
padding: "20px",
|
||||||
|
marginTop: "20px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
style={{ width: "80%" }}
|
||||||
|
placeholder="Enter Topic"
|
||||||
|
onChange={(e) => {
|
||||||
|
let child = [...this.state.child];
|
||||||
|
let topic = [...this.state.child[idx]];
|
||||||
|
topic[idxTopic] = e.target.value;
|
||||||
|
child[idx] = topic;
|
||||||
|
this.setState({ child });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
selectorService
|
||||||
|
.addTopicToClass({
|
||||||
|
name: this.state.child[idx][idxTopic],
|
||||||
|
id: this.state.idArray[idx],
|
||||||
|
})
|
||||||
|
.then((res) => {
|
||||||
|
console.log(res);
|
||||||
|
if (res.status.code !== "-1") {
|
||||||
|
this.setState({
|
||||||
|
addingTopic: false,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
let child = [...this.state.child];
|
||||||
|
console.log(child);
|
||||||
|
let topic = [
|
||||||
|
...this.state.child[idx],
|
||||||
|
];
|
||||||
|
console.log(topic);
|
||||||
|
topic = topic.slice(0, idxTopic);
|
||||||
|
child[idx] = topic;
|
||||||
|
this.setState(
|
||||||
|
{
|
||||||
|
addingTopic: false,
|
||||||
|
|
||||||
|
child,
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
message.error(
|
||||||
|
"Same topic names under a subject not allowed"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.log(err);
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
type="primary"
|
||||||
|
style={{ float: "right" }}
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
let child = [...this.state.child];
|
||||||
|
let topic = [...this.state.child[idx]];
|
||||||
|
topic = topic.slice(0, idxTopic);
|
||||||
|
child[idx] = topic;
|
||||||
|
this.setState({
|
||||||
|
child,
|
||||||
|
addingTopic: false,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
type="primary"
|
||||||
|
style={{
|
||||||
|
float: "right",
|
||||||
|
marginRight: "2%",
|
||||||
|
width: "8%",
|
||||||
|
textAlign: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</Footer>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Footer>
|
||||||
|
);
|
||||||
|
} else if (x.length === 1) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Footer
|
||||||
|
style={{
|
||||||
|
marginBottom: "10px",
|
||||||
|
backgroundColor: "white",
|
||||||
|
border: "1px solid rgb(231 230 230)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{x[0]}
|
||||||
|
<Button
|
||||||
|
disabled={
|
||||||
|
this.state.addingTopic || this.state.addingSubject
|
||||||
|
}
|
||||||
|
onClick={() => {
|
||||||
|
let child = [...this.state.child];
|
||||||
|
let topics = [...this.state.child[idx]];
|
||||||
|
topics.push("");
|
||||||
|
child[idx] = topics;
|
||||||
|
this.setState({
|
||||||
|
child,
|
||||||
|
currentTopic: idx,
|
||||||
|
addingTopic: true,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
type="primary"
|
||||||
|
style={{ float: "right" }}
|
||||||
|
>
|
||||||
|
Add Topic
|
||||||
|
</Button>
|
||||||
|
</Footer>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<Footer
|
||||||
|
style={{
|
||||||
|
marginBottom: "10px",
|
||||||
|
backgroundColor: "white",
|
||||||
|
border: "1px solid rgb(231 230 230)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{x[0]}
|
||||||
|
<Button
|
||||||
|
disabled={
|
||||||
|
this.state.addingTopic || this.state.addingSubject
|
||||||
|
}
|
||||||
|
onClick={() => {
|
||||||
|
let child = [...this.state.child];
|
||||||
|
let topics = [...this.state.child[idx]];
|
||||||
|
topics.push("");
|
||||||
|
child[idx] = topics;
|
||||||
|
this.setState({
|
||||||
|
child,
|
||||||
|
currentTopic: idx,
|
||||||
|
addingTopic: true,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
type="primary"
|
||||||
|
style={{ float: "right" }}
|
||||||
|
>
|
||||||
|
Add Topic
|
||||||
|
</Button>
|
||||||
|
{x.map((y, idx) => {
|
||||||
|
if (idx === 0) return null;
|
||||||
|
else
|
||||||
|
return (
|
||||||
|
<Footer
|
||||||
|
style={{
|
||||||
|
border: "1px solid rgb(231 230 230)",
|
||||||
|
backgroundColor: "white",
|
||||||
|
padding: "20px",
|
||||||
|
marginTop: "20px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{y}
|
||||||
|
</Footer>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Footer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
|
||||||
|
<br />
|
||||||
|
<Button type="primary">
|
||||||
|
<Link
|
||||||
|
to={{
|
||||||
|
pathname: "/viewclass",
|
||||||
|
state: this.state.batch_id,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Continue
|
||||||
|
</Link>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Layout>
|
||||||
|
</Content>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CreateBatch;
|
||||||
|
|
@ -0,0 +1,152 @@
|
||||||
|
li.DraftExam {
|
||||||
|
-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;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.DraftExamImage {
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
line-height: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.DraftExamImage img {
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
}
|
||||||
|
section.createExamLayoutHeader {
|
||||||
|
max-height: 60vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
section.createExamLayout {
|
||||||
|
padding: 30px;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
section.createSmallDiv {
|
||||||
|
padding: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* fin old css above */
|
||||||
|
|
||||||
|
/* start new css below */
|
||||||
|
.class-creation {
|
||||||
|
background-color: var(--background-color-custom-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.class-creation .ant-steps-horizontal:not(.ant-steps-label-vertical) .ant-steps-item-description {
|
||||||
|
max-width: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
.class-creation .step-1 {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.class-creation .step-1, .class-creation .step-2 .title-section {
|
||||||
|
margin-block: 3rem 2rem;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 150px 0.6fr 1fr;
|
||||||
|
grid-template-rows: 1fr;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.class-creation .step-2 .title-section .vertical-stacked-button {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin-left: 0.5rem;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
.vertical-stacked-button > :first-child {
|
||||||
|
max-height: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vertical-stacked-button > * {
|
||||||
|
cursor: pointer;
|
||||||
|
} */
|
||||||
|
|
||||||
|
.class-creation .step-2 .add-subject-body {
|
||||||
|
display: grid;
|
||||||
|
grid: auto-flow/150px 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.class-creation .step-2 .operations-side .subject-space {
|
||||||
|
position: relative;
|
||||||
|
padding: 1rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
background-color: var(--background-color-custom-1);
|
||||||
|
border: 2px solid var(--color-accent-1-40);
|
||||||
|
/* height: 90px; */
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
-webkit-border-radius: 0.5rem;
|
||||||
|
-moz-border-radius: 0.5rem;
|
||||||
|
-ms-border-radius: 0.5rem;
|
||||||
|
-o-border-radius: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.class-creation .step-2 .operations-side .subject-space:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.class-creation .step-2 .operations-side .subject-space > div:first-child {
|
||||||
|
padding-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.class-creation .step-2 .operations-side .subject-space > .input-space {
|
||||||
|
/* margin-top: 0.5rem; */
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.class-creation .step-2 .operations-side .subject-space > .input-space:not(:nth-child(2)) {
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.class-creation .step-2 .operations-side .subject-space .topic-space {
|
||||||
|
list-style-type: circle;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.class-creation .step-2 .operations-side .subject-space .topic-space .topic-name:not(:last-child) {
|
||||||
|
padding-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.class-creation .step-2 .operations-side .subject-space .more-options {
|
||||||
|
position: absolute;
|
||||||
|
top: 1rem;
|
||||||
|
right: 1rem;
|
||||||
|
display: flex;
|
||||||
|
/* gap: 1rem; */
|
||||||
|
}
|
||||||
|
|
||||||
|
.class-card-info {
|
||||||
|
position: relative;
|
||||||
|
background-color: var(--background-color-custom-1);
|
||||||
|
padding: 1rem;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
border-radius: 1rem;
|
||||||
|
-webkit-border-radius: 1rem;
|
||||||
|
-moz-border-radius: 1rem;
|
||||||
|
-ms-border-radius: 1rem;
|
||||||
|
-o-border-radius: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.class-card-info > * {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
/* row-gap: 5px; */
|
||||||
|
}
|
||||||
|
|
||||||
|
.class-card-info > :first-child {
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.class-card-info .action-buttons {
|
||||||
|
position: absolute;
|
||||||
|
top: 1.05rem;
|
||||||
|
right: 1.05rem;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,395 @@
|
||||||
|
import { Button, Input, Layout, Steps, Tooltip, Transfer, Typography, message, notification } from 'antd';
|
||||||
|
import React, { useEffect, useState, useRef } from 'react';
|
||||||
|
import { selectorService } from '../../services/selectorService';
|
||||||
|
import { AppstoreAddOutlined, CheckSquareOutlined, CloseSquareOutlined, DeleteOutlined, EditFilled, EditOutlined, FormOutlined, InfoCircleOutlined, LoadingOutlined, PlusOutlined } from '@ant-design/icons';
|
||||||
|
import { useLocation, useHistory } from 'react-router-dom';
|
||||||
|
import './CreateClass.css';
|
||||||
|
|
||||||
|
const steps = [
|
||||||
|
{
|
||||||
|
title: 'Step 1',
|
||||||
|
description: "Write a Class Name",
|
||||||
|
// content: <CreateExamName selectorState={state} parentCallback={callbackFunctionStep1} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Step 2',
|
||||||
|
description: "Add Subjects and Topics",
|
||||||
|
// content: <SelectSubjects selectorState={state} parentCallback={(childData) => { callbackFunctionStep2(childData) }} />,
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const CreateClass = () => {
|
||||||
|
const loc = useLocation();
|
||||||
|
const history = useHistory();
|
||||||
|
const mounted = useRef(true);
|
||||||
|
const [currentStep, setCurrentStep] = useState(0);
|
||||||
|
const [classLabel, setClassLabel] = useState("");
|
||||||
|
const [pageState, setPageState] = useState({
|
||||||
|
sourceLoading: false,
|
||||||
|
buttonLoading: false,
|
||||||
|
inputEditActive: false,
|
||||||
|
temporaryName: "",
|
||||||
|
structureTemp: "",
|
||||||
|
activeEditIndex: "",
|
||||||
|
});
|
||||||
|
const [classID, setClassID] = useState("");
|
||||||
|
|
||||||
|
// step-2 state variables
|
||||||
|
const [subStructure, setSubStructure] = useState([
|
||||||
|
{
|
||||||
|
name: "",
|
||||||
|
id: -1,
|
||||||
|
topics: [{
|
||||||
|
name: "",
|
||||||
|
id: -1
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
let subjectCount = subStructure.length;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
mounted.current = true;
|
||||||
|
if (loc && loc.state && loc.state.id && currentStep === 0) {
|
||||||
|
setClassID(loc.state.id);
|
||||||
|
setClassLabel(loc.state.name);
|
||||||
|
setPageState(prev => ({ ...prev, temporaryName: loc.state.name, sourceLoading: true }));
|
||||||
|
setCurrentStep(1);
|
||||||
|
}
|
||||||
|
if (currentStep === 1) {
|
||||||
|
fetchClassDetail();
|
||||||
|
// getAllStudents();
|
||||||
|
// getStudentsFromBatch();
|
||||||
|
}
|
||||||
|
return () => {
|
||||||
|
mounted.current = false;
|
||||||
|
}
|
||||||
|
}, [currentStep]);
|
||||||
|
|
||||||
|
async function fetchClassDetail() {
|
||||||
|
try {
|
||||||
|
const res = await selectorService.getClassDetails(classID);
|
||||||
|
if(+res.status.code < 0) {
|
||||||
|
throw res.status.message[0];
|
||||||
|
}
|
||||||
|
if(mounted.current) {
|
||||||
|
process.env.NODE_ENV === "development" && console.log(res);
|
||||||
|
setSubStructure([...res.result.subjects]);
|
||||||
|
setPageState(prev => ({...prev, sourceLoading: false, buttonLoading: false, activeEditIndex: res.result.subjects.length-1}));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
message.error("Something went wrong, please try again");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleClassNameInput(event, type) {
|
||||||
|
// console.log(event.target.value);
|
||||||
|
const { value } = event.target;
|
||||||
|
if (!type) setClassLabel(value);
|
||||||
|
if (type === "temp") {
|
||||||
|
setPageState(prev => ({ ...prev, temporaryName: value }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function creationOfClass() {
|
||||||
|
setPageState(prev => ({
|
||||||
|
...prev, buttonLoading: true
|
||||||
|
}));
|
||||||
|
if (!classLabel) {
|
||||||
|
message.error("Please enter a Class name before pressing on Create");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
message.loading("Creating the Class, give us a few second...");
|
||||||
|
try {
|
||||||
|
const res = await selectorService.createClass({ name: classLabel });
|
||||||
|
if (+res.status.code < 0) {
|
||||||
|
throw res.status.message[0];
|
||||||
|
}
|
||||||
|
if (mounted.current) {
|
||||||
|
setClassID(res.result.id);
|
||||||
|
setPageState(prev => ({ ...prev, temporaryName: classLabel }));
|
||||||
|
setCurrentStep(currentStep + 1);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
process.env.NODE_ENV === "development" && console.log(err);
|
||||||
|
if(mounted.current) {
|
||||||
|
notification.error({
|
||||||
|
message: "Something went wrong, please try again"
|
||||||
|
});
|
||||||
|
setPageState(prev => ({
|
||||||
|
...prev, buttonLoading: false
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCancelForInput() {
|
||||||
|
setPageState(prev => ({
|
||||||
|
...prev,
|
||||||
|
inputEditActive: false,
|
||||||
|
temporaryName: classLabel
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleUpdateForInput() {
|
||||||
|
if(pageState.temporaryName === classLabel) {
|
||||||
|
setPageState(prev => ({ ...prev, inputEditActive: false }));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setPageState(prev => ({ ...prev, buttonLoading: true }));
|
||||||
|
message.loading("Updating Class Name...");
|
||||||
|
try {
|
||||||
|
const res = await selectorService.updateClass({
|
||||||
|
id: classID, name: pageState.temporaryName,
|
||||||
|
});
|
||||||
|
if (+res.status.code < 0) {
|
||||||
|
throw res.status.message[0];
|
||||||
|
}
|
||||||
|
if (+res.status.code > 0 && mounted.current) {
|
||||||
|
setClassLabel(pageState.temporaryName);
|
||||||
|
setPageState(prev => ({ ...prev, buttonLoading: false, inputEditActive: false }));
|
||||||
|
message.success("Operation Success! Class name has been changed");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
process.env.NODE_ENV === "development" && console.log(error);
|
||||||
|
if(mounted.current) {
|
||||||
|
notification.error({ message: "Something went wrong while updating the name, operation failed" });
|
||||||
|
setPageState(prev => ({ ...prev, buttonLoading: false }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleInputOperation(e, location) {
|
||||||
|
// const type = location.split("-")[0];
|
||||||
|
// const index = location.split("-")[1];
|
||||||
|
const {value} = e.target;
|
||||||
|
// console.log(type, index, value);
|
||||||
|
setPageState(prev => ({...prev, structureTemp: value}));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
async function submitSyllabusStructure() {
|
||||||
|
const index = pageState.activeEditIndex;
|
||||||
|
setPageState(prev => ({...prev, buttonLoading: true}));
|
||||||
|
try {
|
||||||
|
if(subStructure[index].id < 0) {
|
||||||
|
console.log("here to subject")
|
||||||
|
const res = await selectorService.addSubjectToClass({name: pageState.structureTemp, id: classID});
|
||||||
|
if(+res.status.code < 0) {
|
||||||
|
throw res.status.message[0];
|
||||||
|
}
|
||||||
|
if(mounted.current) {
|
||||||
|
const subjectArray = subStructure;
|
||||||
|
subjectArray[index] = {
|
||||||
|
...subjectArray[index],
|
||||||
|
name: res.result.name,
|
||||||
|
id: res.result.id
|
||||||
|
}
|
||||||
|
setPageState((prev) => ({...prev, buttonLoading: false, structureTemp: ""}));
|
||||||
|
setSubStructure(() => ([...subjectArray]));
|
||||||
|
fetchClassDetail();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log("here to topic")
|
||||||
|
const res = await selectorService.addTopicToClass({name: pageState.structureTemp, id: subStructure[index].id});
|
||||||
|
if(+res.status.code < 0) {
|
||||||
|
throw res.status.message[0];
|
||||||
|
}
|
||||||
|
console.log(res.result);
|
||||||
|
if(mounted.current) {
|
||||||
|
const subjectArray = subStructure;
|
||||||
|
subjectArray[index].topics = [...subjectArray[index].topics, {id: res.result.id, name: res.result.name}]
|
||||||
|
setPageState((prev) => ({...prev, buttonLoading: false, structureTemp: ""}));
|
||||||
|
setSubStructure(() => ([...subjectArray]));
|
||||||
|
// fetchClassDetail();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
if(mounted.current) {
|
||||||
|
message.error("Something went wrong, please try again");
|
||||||
|
setPageState((prev) => ({...prev, buttonLoading: false }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function pushNewSubjectEntry() {
|
||||||
|
let newSubjectInitial = {
|
||||||
|
name: "", id: -1, topics: [{name: "", id: -1}]
|
||||||
|
};
|
||||||
|
if (mounted.current) {
|
||||||
|
setSubStructure(prev => ([...prev, newSubjectInitial]));
|
||||||
|
// subjectCount has not updated yet, so we don't subtract 1 from it. It updates
|
||||||
|
// after this function is excecuted.
|
||||||
|
setPageState(prev => ({...prev, activeEditIndex: subjectCount}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteSubjectRecord(index) {
|
||||||
|
setPageState((prev) => ({...prev, buttonLoading: true }));
|
||||||
|
try {
|
||||||
|
const res = await selectorService.removeSubjectFromClass(index);
|
||||||
|
if(+res.status.code < 0) {
|
||||||
|
throw res.status.message[0];
|
||||||
|
}
|
||||||
|
if(mounted.current && +res.status.code === 1) {
|
||||||
|
message.success("The subject was removed from the class");
|
||||||
|
fetchClassDetail();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
if(mounted.current) {
|
||||||
|
notification.error({
|
||||||
|
message: "Please make sure no topic under it for the delete operation to be successful"
|
||||||
|
});
|
||||||
|
setPageState((prev) => ({...prev, buttonLoading: false }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteTopicRecord(index) {
|
||||||
|
setPageState((prev) => ({...prev, buttonLoading: true }));
|
||||||
|
try {
|
||||||
|
const res = await selectorService.removeTopicFromClass(index);
|
||||||
|
if(+res.status.code < 0) {
|
||||||
|
throw res.status.message[0];
|
||||||
|
}
|
||||||
|
if(mounted.current && +res.status.code === 1) {
|
||||||
|
message.success("The subject was removed from the class");
|
||||||
|
fetchClassDetail();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
if(mounted.current) {
|
||||||
|
notification.error({
|
||||||
|
message: "Please make sure no questions are linked to this topic before deleting the topic"
|
||||||
|
});
|
||||||
|
setPageState((prev) => ({...prev, buttonLoading: false }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Layout.Content
|
||||||
|
className="site-layout-background class-creation" style={{ padding: 24, margin: 0, overflowY: 'auto' }}>
|
||||||
|
{/* <Layout className='createExamLayoutHeader'>Create Exam</Layout> */}
|
||||||
|
{/* <Layout className='createExamLayout'> */}
|
||||||
|
<Steps key={"steps"} current={currentStep}>
|
||||||
|
{steps.map(item => (
|
||||||
|
<Steps.Step key={item.title} title={item.title} description={item.description} />
|
||||||
|
))}
|
||||||
|
</Steps>
|
||||||
|
|
||||||
|
{currentStep === 0 && <div className="step-1">
|
||||||
|
<Typography.Text key={"class-name-1"} strong style={{ color: "var(--font-color-primary)" }}>
|
||||||
|
Class Name
|
||||||
|
</Typography.Text>
|
||||||
|
<Input key={"input-class-1"} placeholder='Enter Class Name here' onChange={handleClassNameInput} value={classLabel} />
|
||||||
|
</div>}
|
||||||
|
{currentStep === 0 && <div data-need-margin={"false"} className="button-container">
|
||||||
|
<Button key={"btn-create-class"} type="primary" loading={pageState.buttonLoading} onClick={creationOfClass}>
|
||||||
|
Create Class
|
||||||
|
</Button>
|
||||||
|
</div>}
|
||||||
|
|
||||||
|
|
||||||
|
{currentStep === 1 && <div className='step-2'>
|
||||||
|
<div className='title-section'>
|
||||||
|
<Typography.Text key={"class-name-2"} strong style={{ color: "var(--font-color-primary)" }}>
|
||||||
|
Class Name
|
||||||
|
</Typography.Text>
|
||||||
|
{pageState.inputEditActive && <Input key={"input-class-2"} placeholder='Enter Class Name here' onChange={(e) => handleClassNameInput(e, "temp")} value={pageState.temporaryName} />}
|
||||||
|
{pageState.inputEditActive && <div className={"vertical-stacked-button"}>
|
||||||
|
<div onClick={handleCancelForInput}><CloseSquareOutlined style={{ fontSize: 14, color: 'red' }} /></div>
|
||||||
|
<div onClick={handleUpdateForInput}><CheckSquareOutlined style={{ fontSize: 14, color: 'var(--font-color-primary)' }} /></div>
|
||||||
|
</div>}
|
||||||
|
{!pageState.inputEditActive && <Typography.Text key={"class-label"}>{classLabel}</Typography.Text>}
|
||||||
|
{!pageState.inputEditActive &&
|
||||||
|
<Button key={"edit-btn"} onClick={() => setPageState(prev => ({ ...prev, inputEditActive: true }))}
|
||||||
|
icon={<FormOutlined />} />
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
{pageState.sourceLoading && <div className='center-flex'><LoadingOutlined spin /></div>}
|
||||||
|
{!pageState.sourceLoading && <div className='add-subject-body'>
|
||||||
|
<Typography.Text type={"secondary"} style={{gridArea: "1/1/1/3", marginBottom: '1rem'}}>
|
||||||
|
<InfoCircleOutlined/> If you are done adding subjects and topic, you can simply navigate out of this page, changes will be saved.
|
||||||
|
</Typography.Text>
|
||||||
|
<Typography.Text key={"add-subject"} strong style={{ color: "var(--font-color-primary)" }}>
|
||||||
|
Add Subjects
|
||||||
|
</Typography.Text>
|
||||||
|
<div className='operations-side'>
|
||||||
|
{subjectCount > 0 && subStructure.map((subject, index) => {
|
||||||
|
return <div key={`subject-space-${index}`} className='subject-space'>
|
||||||
|
{subject?.id > 0 && <div key={`subject-name-${subject.id}`} className='subject-name'>{subject.name}</div>}
|
||||||
|
{subject.topics.length > 0 && <ul key={`topic-space-${index}`} className='topic-space'>
|
||||||
|
{subject.topics.map((topic, topicIndex) => {
|
||||||
|
if(topic.id > 0) {
|
||||||
|
return <li key={`topic-name-${topic.id}`}
|
||||||
|
className='topic-name'
|
||||||
|
>
|
||||||
|
{topic.name}
|
||||||
|
<Tooltip title={"Delete the Category"}>
|
||||||
|
<Button size='small'
|
||||||
|
type={"text"}
|
||||||
|
loading={pageState.buttonLoading}
|
||||||
|
onClick={() => deleteTopicRecord(topicIndex)}
|
||||||
|
>
|
||||||
|
<DeleteOutlined/>
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
</ul>}
|
||||||
|
{pageState.activeEditIndex === index && <div className='input-space'>
|
||||||
|
{subject.id < 0 && <Input key={"add-subject-name"} name='subject'
|
||||||
|
placeholder='Write subject name here'
|
||||||
|
value={pageState.structureTemp}
|
||||||
|
onChange={(e) => handleInputOperation(e, `subject-${index}`)}
|
||||||
|
/>}
|
||||||
|
{subject.id > 0 && <Input key={"add-topic-name"} name='topic'
|
||||||
|
placeholder='Write topic name here'
|
||||||
|
value={pageState.structureTemp}
|
||||||
|
onChange={(e) => handleInputOperation(e, `topic-${index}`)}
|
||||||
|
/>}
|
||||||
|
<Button key={`save-btn-${index}`} onClick={() => submitSyllabusStructure()} loading={pageState.buttonLoading}>Add</Button>
|
||||||
|
{/* <Button key={`cancel-btn-${index}`} disabled={pageState.buttonLoading}>Cancel</Button> */}
|
||||||
|
</div>}
|
||||||
|
{subject.id > 0 && <div className='more-options'>
|
||||||
|
<Tooltip title={"Add More Topic"}>
|
||||||
|
<Button type='text' size='small' disabled={pageState.buttonLoading||pageState.activeEditIndex === index}
|
||||||
|
onClick={() => setPageState(prev => ({...prev, activeEditIndex: index}))}>
|
||||||
|
<AppstoreAddOutlined />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip title={"Delete the Subject"}>
|
||||||
|
<Button type='text' size='small' disabled={pageState.buttonLoading}
|
||||||
|
onClick={() => deleteSubjectRecord(index)}>
|
||||||
|
<DeleteOutlined />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
</div>}
|
||||||
|
</div>
|
||||||
|
})}
|
||||||
|
{subjectCount === 0 || subStructure[subjectCount - 1].id > 0 ? <Button key={"add-more-subject"}
|
||||||
|
onClick={pushNewSubjectEntry} disabled={pageState.buttonLoading}
|
||||||
|
type='primary' icon={<PlusOutlined />}>Add Subject</Button>: null}
|
||||||
|
</div>
|
||||||
|
</div>}
|
||||||
|
</div>}
|
||||||
|
<div className="steps-action">
|
||||||
|
{currentStep < steps.length - 1}
|
||||||
|
{currentStep === steps.length - 1 && (
|
||||||
|
<div data-need-margin={"false"} className="button-container">
|
||||||
|
{/* <Button type="primary" loading={pageState.buttonLoading} onClick={}>
|
||||||
|
Add Users to Batch
|
||||||
|
</Button> */}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{/* </Layout> */}
|
||||||
|
</Layout.Content>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CreateClass;
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
import React, {useState} from 'react';
|
||||||
|
import './CreateClass.css';
|
||||||
|
import { Tooltip, Typography, Button } from 'antd';
|
||||||
|
import { DeleteTwoTone, EditTwoTone, EyeOutlined, EyeFilled } from '@ant-design/icons';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
|
const Lmao = (props) => {
|
||||||
|
const [viewActive, setViewActive] = useState(false);
|
||||||
|
|
||||||
|
function handleView() {
|
||||||
|
setViewActive(true);
|
||||||
|
// console.log(props.details)
|
||||||
|
props.viewButton(props.details.id, props.idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='class-card-info'>
|
||||||
|
<div className='title'>
|
||||||
|
<Typography.Text style={{ fontSize: 12 }}>Class Name</Typography.Text>
|
||||||
|
<span><Typography.Text strong style={{width: 150}} ellipsis={true}>{props.classTitle}</Typography.Text></span>
|
||||||
|
</div>
|
||||||
|
<div className='view-section'>
|
||||||
|
<Button onClick={handleView} icon={props.viewState? <EyeFilled />:<EyeOutlined />} type={"ghost"}>View</Button>
|
||||||
|
</div>
|
||||||
|
<div className='action-buttons'>
|
||||||
|
<Tooltip title={"Delete"}>
|
||||||
|
<Link onClick={() => {
|
||||||
|
props.deleteButton(props.idx, props.classTitle)
|
||||||
|
}}
|
||||||
|
to="#"
|
||||||
|
>
|
||||||
|
<DeleteTwoTone />
|
||||||
|
</Link>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip title={"Edit"}>
|
||||||
|
<Link style={{ float: "right" }}
|
||||||
|
to={{
|
||||||
|
pathname: "/class",
|
||||||
|
state: props.details,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<EditTwoTone />
|
||||||
|
</Link>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Lmao;
|
||||||
|
|
@ -0,0 +1,199 @@
|
||||||
|
import { message, Layout, Row, Col, Drawer, Tree, Typography } from "antd";
|
||||||
|
import { confirmAlert } from "react-confirm-alert"; // Import
|
||||||
|
import "react-confirm-alert/src/react-confirm-alert.css"; // Import css
|
||||||
|
import React from "react";
|
||||||
|
import "./CreateClass.css";
|
||||||
|
import { DownOutlined, LoadingOutlined } from "@ant-design/icons";
|
||||||
|
import { selectorService } from "../../services/selectorService";
|
||||||
|
|
||||||
|
import Lmao from "./Lmao";
|
||||||
|
|
||||||
|
const { Content } = Layout;
|
||||||
|
|
||||||
|
class ViewClass extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
batchName: "",
|
||||||
|
batches: [],
|
||||||
|
step: 1,
|
||||||
|
mockData: [],
|
||||||
|
targetKeys: [],
|
||||||
|
currentIndex: "",
|
||||||
|
visible: false,
|
||||||
|
subjects: [],
|
||||||
|
pageLoading: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
componentDidMount() {
|
||||||
|
// console.log(this.props);
|
||||||
|
this.setState(prev => ({ ...prev, pageLoading: true }));
|
||||||
|
selectorService.getAllClass().then((res) => {
|
||||||
|
let batches = res.result;
|
||||||
|
// console.log(batches);
|
||||||
|
if (this.props.location.state) {
|
||||||
|
selectorService.getClassDetails(this.props.location.state).then((res) => {
|
||||||
|
// console.log(res);
|
||||||
|
let currentIndex = "";
|
||||||
|
batches.map((x, idx) => {
|
||||||
|
currentIndex = x.id === this.props.location.state ? idx : currentIndex;
|
||||||
|
});
|
||||||
|
// console.log(currentIndex);
|
||||||
|
this.setState({
|
||||||
|
batches,
|
||||||
|
visible: true,
|
||||||
|
subjects: res.result.subjects,
|
||||||
|
currentIndex,
|
||||||
|
pageLoading: false
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.log(err);
|
||||||
|
this.setState(prev => ({ ...prev, pageLoading: false }));
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.setState({
|
||||||
|
batches,
|
||||||
|
pageLoading: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.log(err);
|
||||||
|
this.setState(prev => ({ ...prev, pageLoading: false }));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleDeleteButton = (index, name) => {
|
||||||
|
confirmAlert({
|
||||||
|
title: "Confirm to delete",
|
||||||
|
message: "Are you sure you want to delete \"" + name + "\" class",
|
||||||
|
buttons: [
|
||||||
|
{
|
||||||
|
label: "Yes",
|
||||||
|
onClick: () => {
|
||||||
|
this.setState(prev => ({ ...prev, loadingState: true }));
|
||||||
|
selectorService.deleteClass(this.state.batches[index].id).then((res) => {
|
||||||
|
if (+res.status.code < 0) {
|
||||||
|
throw "Operation failed, could not delete the batch"
|
||||||
|
}
|
||||||
|
let batches = this.state.batches.filter((y) => y.id !== this.state.batches[index].id);
|
||||||
|
this.setState({ batches, loadingState: false }, () => {
|
||||||
|
message.success("Deleted Successfully");
|
||||||
|
});
|
||||||
|
}).catch((err) => {
|
||||||
|
console.log(err);
|
||||||
|
message.error(err);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "No",
|
||||||
|
onClick: () => { },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleViewButton = (id, arrayIdx) => {
|
||||||
|
// console.log("here");
|
||||||
|
this.setState(prev => ({ ...prev, pageLoading: true }));
|
||||||
|
selectorService.getClassDetails(id)
|
||||||
|
.then((res) => {
|
||||||
|
// console.log(res);
|
||||||
|
if(+res.status.code < 0) {
|
||||||
|
throw res.status.message[0];
|
||||||
|
}
|
||||||
|
this.setState({
|
||||||
|
currentIndex: arrayIdx,
|
||||||
|
visible: true,
|
||||||
|
subjects: res.result.subjects,
|
||||||
|
pageLoading: false
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.log(err);
|
||||||
|
this.setState(prev => ({ ...prev, pageLoading: false }));
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// componentDidUpdate(prevprops) {
|
||||||
|
// if (!isEqual(this.props.location, prevprops.location)) console.log("here");
|
||||||
|
// }
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let treeData = [
|
||||||
|
{
|
||||||
|
title:
|
||||||
|
typeof this.state.currentIndex !== "string" &&
|
||||||
|
this.state.batches.length >= this.state.currentIndex + 1
|
||||||
|
? `${this.state.batches[this.state.currentIndex].name}`
|
||||||
|
: "",
|
||||||
|
key: "0",
|
||||||
|
children: [],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
treeData[0].children = this.state.subjects.map((x, idx) => {
|
||||||
|
return {
|
||||||
|
title: x.name,
|
||||||
|
key: "0" + idx,
|
||||||
|
children: x.topics.map((y, idxY) => {
|
||||||
|
return {
|
||||||
|
title: y.name,
|
||||||
|
key: "0" + idx + idxY,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
if (this.state.pageLoading) {
|
||||||
|
return (<div className="center-flex mt-2-0"><LoadingOutlined style={{ fontSize: 32 }} spin /></div>)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Content className="site-layout-background" style={{ padding: 24, margin: 0 }}>
|
||||||
|
<Drawer
|
||||||
|
title={
|
||||||
|
typeof this.state.currentIndex !== "string" &&
|
||||||
|
this.state.batches.length >= this.state.currentIndex + 1
|
||||||
|
? `${this.state.batches[this.state.currentIndex].name}`
|
||||||
|
: ""
|
||||||
|
}
|
||||||
|
placement="right"
|
||||||
|
closable={false}
|
||||||
|
onClose={() => {
|
||||||
|
this.setState({ visible: false, currentIndex: "", pageLoading: false });
|
||||||
|
}}
|
||||||
|
visible={this.state.visible}
|
||||||
|
>
|
||||||
|
<div className="center-flex"><Typography.Text type={"secondary"}>Class syllabus as follows:</Typography.Text></div>
|
||||||
|
<Tree
|
||||||
|
showLine
|
||||||
|
switcherIcon={<DownOutlined />}
|
||||||
|
onSelect={this.onSelect}
|
||||||
|
treeData={treeData}
|
||||||
|
/>
|
||||||
|
</Drawer>
|
||||||
|
<Row>
|
||||||
|
{this.state.batches.map((x, idx) => {
|
||||||
|
if (idx % 4 === 0)
|
||||||
|
return (
|
||||||
|
<Col span={5} key={`${x.name.substring(0, 4)}-${idx}`}>
|
||||||
|
<Lmao viewState={this.state.currentIndex === idx} details={x} viewButton={this.handleViewButton} classTitle={x.name} idx={idx} deleteButton={this.handleDeleteButton} />
|
||||||
|
</Col>
|
||||||
|
);
|
||||||
|
else
|
||||||
|
return (
|
||||||
|
<Col span={5} offset={1} key={`${x.name.substring(0, 4)}-${idx}`}>
|
||||||
|
<Lmao viewState={this.state.currentIndex === idx} details={x} viewButton={this.handleViewButton} classTitle={x.name} idx={idx} deleteButton={this.handleDeleteButton} />
|
||||||
|
</Col>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Row>
|
||||||
|
</Content>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ViewClass;
|
||||||
|
|
@ -0,0 +1,192 @@
|
||||||
|
import { CaretLeftFilled, CaretRightFilled } from '@ant-design/icons';
|
||||||
|
import { Select } from 'antd';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import './calendar.css';
|
||||||
|
|
||||||
|
const dayAr = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
|
||||||
|
const monthAr = [
|
||||||
|
'January',
|
||||||
|
'February',
|
||||||
|
'March',
|
||||||
|
'April',
|
||||||
|
'May',
|
||||||
|
'June',
|
||||||
|
'July',
|
||||||
|
'August',
|
||||||
|
'September',
|
||||||
|
'October',
|
||||||
|
'November',
|
||||||
|
'December',
|
||||||
|
];
|
||||||
|
|
||||||
|
export default function CalendarComp() {
|
||||||
|
|
||||||
|
let lastDate = new Date();
|
||||||
|
|
||||||
|
const [month, setMonth] = React.useState(lastDate.getMonth());
|
||||||
|
const [year, setYear] = React.useState(lastDate.getFullYear());
|
||||||
|
const [selectedDate, setSelectedDate] = React.useState();
|
||||||
|
|
||||||
|
function calendarNodeLoop() {
|
||||||
|
let Nodes = [];
|
||||||
|
|
||||||
|
lastDate.setMonth(month);
|
||||||
|
lastDate.setDate(31);
|
||||||
|
if (lastDate.getMonth() !== 1 && lastDate.getDate() === 1) {
|
||||||
|
lastDate.setDate(30);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastDate.getDate() === 3 || lastDate.getDate() === 2) {
|
||||||
|
//chech for leap year and set date to 29 if true else 28
|
||||||
|
(year % 4 == 0 && year % 100 != 0) || year % 400 == 0
|
||||||
|
? lastDate.setDate(29)
|
||||||
|
: lastDate.setDate(28);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 1; i <= lastDate.getDate(); i++) {
|
||||||
|
const dateObj = new Date();
|
||||||
|
dateObj.setMonth(month);
|
||||||
|
dateObj.setFullYear(year);
|
||||||
|
dateObj.setDate(i);
|
||||||
|
Nodes.push(
|
||||||
|
<Calendar
|
||||||
|
key={dateObj}
|
||||||
|
day={dayAr[dateObj.getDay()]}
|
||||||
|
date={dateObj.getDate()}
|
||||||
|
month={month}
|
||||||
|
year={year}
|
||||||
|
setSelectedDate={setSelectedDate}
|
||||||
|
selectedDate={selectedDate}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return Nodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
const todayDiv = document.querySelector('.today');
|
||||||
|
if (todayDiv) {
|
||||||
|
todayDiv.scrollIntoView({ behavior: 'smooth', block: "nearest", inline: 'start' });
|
||||||
|
}
|
||||||
|
}, 0);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// this is to make an array with numbers starting from 'start' to 'end'.
|
||||||
|
function range(start, end) {
|
||||||
|
return Array(end - start + 1)
|
||||||
|
.fill()
|
||||||
|
.map((_, idx) => start + idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handlerMonth (val) {
|
||||||
|
setMonth(() => val);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handlerYear (val) {
|
||||||
|
setYear(() => val);
|
||||||
|
}
|
||||||
|
|
||||||
|
function scrollStrip(direction) {
|
||||||
|
const strip = document.querySelector('.strip');
|
||||||
|
if(strip) {
|
||||||
|
direction === "left"? strip.scrollTo({left: strip.scrollLeft - 50, behavior: 'smooth'})
|
||||||
|
: strip.scrollTo({left: strip.scrollLeft + 50, behavior: 'smooth'});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="calendar-strip"
|
||||||
|
style={{
|
||||||
|
position: 'relative',
|
||||||
|
backgroundColor: '#3b3cea',
|
||||||
|
borderRadius: '12px',
|
||||||
|
color: 'white',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="calendar-context pr-2-0"
|
||||||
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'flex-end',
|
||||||
|
paddingBlock: '8px',
|
||||||
|
gap: '10px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Select name="month" id="month" onChange={handlerMonth} value={month}>
|
||||||
|
{monthAr.map((item, i) => (
|
||||||
|
<Select.Option key={i} value={i}>
|
||||||
|
{item}
|
||||||
|
</Select.Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
<Select name="year" id="year" onChange={handlerYear} value={year}>
|
||||||
|
{range(2020, 2050).map((item) => (
|
||||||
|
<Select.Option key={item} value={item}>
|
||||||
|
{item}
|
||||||
|
</Select.Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className="strip py-0-5 px-1-0"
|
||||||
|
style={{
|
||||||
|
// height: '85px',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'flex-start',
|
||||||
|
overflowX: 'auto',
|
||||||
|
gap: '3px',
|
||||||
|
marginInline: '25px'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CaretLeftFilled style={{ position: 'absolute', left: '5px' }} onClick={() => scrollStrip("left")} />
|
||||||
|
<CaretRightFilled style={{ position: 'absolute', right: '5px' }} onClick={() => scrollStrip("right")} />
|
||||||
|
{calendarNodeLoop()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Calendar({ date, day, month, year, setSelectedDate, selectedDate }) {
|
||||||
|
const dateToday = new Date();
|
||||||
|
let classString = "calendar-node";
|
||||||
|
let childClass = "";
|
||||||
|
|
||||||
|
if (selectedDate) {
|
||||||
|
const split = selectedDate.split('/');
|
||||||
|
|
||||||
|
if( date === parseInt(split[0], 10) && month === parseInt(split[1], 10) && year === parseInt(split[2], 10) ) {
|
||||||
|
classString = "calendar-node selected";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dateToday.getDate() === date && dateToday.getMonth() === month && dateToday.getFullYear() === year) {
|
||||||
|
classString = "calendar-node today"
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={classString}
|
||||||
|
style={{
|
||||||
|
minHeight: '60px',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
height: '100%',
|
||||||
|
flex: '1 0 50px',
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedDate(() => `${date}/${month}/${year}`);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ opacity: '70%', lineHeight: 1.2 }}>{day}</div>
|
||||||
|
<div style={{ fontSize: '1.8rem', lineHeight: 1.2 }}>{date}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
.logged-in-view {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,130 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Route, useRouteMatch, Switch, Redirect } from 'react-router-dom';
|
||||||
|
import { Layout, Spin } from 'antd';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
import { Siderc } from '../Main/Siderc';
|
||||||
|
import Headerc from '../Main/FuncHeader';
|
||||||
|
import { authenticationService } from '../../_services';
|
||||||
|
|
||||||
|
import './Dashboard.css';
|
||||||
|
|
||||||
|
import ErrorHandler from '../ErrorHandler';
|
||||||
|
import InfoDash from './InfoDash';
|
||||||
|
|
||||||
|
// import AddQuestion from '../Questions/AddQuestion';
|
||||||
|
import AllQuestionNew from '../Questions/AllQuestionNew';
|
||||||
|
import MyQuestion from '../Questions/MyQuestions';
|
||||||
|
import Bookmark from '../Questions/Bookmark';
|
||||||
|
import Draft from '../Questions/DraftQuestion/DraftNew';
|
||||||
|
|
||||||
|
import CreateExam from '../Exams/CreateExam';
|
||||||
|
import CreatePractise from '../Exams/createPractise';
|
||||||
|
// import CreateQuestion from '../Questions/createQuestionTest';
|
||||||
|
import CreateQuestion2 from '../Questions/CreateQuestion2';
|
||||||
|
import RepeatedDataProvider from '../RepeatedDataProvider';
|
||||||
|
// import { MathJax } from 'better-react-mathjax';
|
||||||
|
import TranslateQuestion from '../Questions/TranslateQuestion';
|
||||||
|
// import LivePractice from '../Practice/LivePractice';
|
||||||
|
// import DraftPractice from '../Practice/DraftPractice';
|
||||||
|
// import UpcomingPractice from '../Practice/UpcomingPractice';
|
||||||
|
import DraftPracticeNew from '../Practice/DraftPracticeNew';
|
||||||
|
import LivePracticeNew from '../Practice/LivePracticeNew';
|
||||||
|
import UpcomingPracticeNew from '../Practice/UpcomingPracticeNew';
|
||||||
|
import DraftExamNew from '../Exams/DraftExamNew';
|
||||||
|
import LiveExamNew from '../Exams/LiveExamNew';
|
||||||
|
import UpcomingExamNew from '../Exams/UpcomingExamNew';
|
||||||
|
import ExpiredExamNew from '../Exams/ExpiredExamNew';
|
||||||
|
// import AssociateBatchPractice from '../Practice/PracticeAssociateBatch';
|
||||||
|
// import AssociateBatch from '../Exams/AssociateBatch';
|
||||||
|
import ViewBatch from '../batches/ViewBatch';
|
||||||
|
import CreateBatchNew from "../batches/CreateBatchNew";
|
||||||
|
import ViewClass from '../Class/ViewClass';
|
||||||
|
// import Class from '../Class/CreateClass-re'
|
||||||
|
import CreateClass from '../Class/CreateClassNew';
|
||||||
|
import AssociateBatchNew from '../Exams/AssociateBatchNew';
|
||||||
|
import PracticeAssociateBatchv2 from '../Practice/PracticeAssociateBatchv2';
|
||||||
|
import Profile from '../Profile';
|
||||||
|
|
||||||
|
function Dashboard(props) {
|
||||||
|
|
||||||
|
const { i18n } = useTranslation();
|
||||||
|
|
||||||
|
const { url, path } = useRouteMatch();
|
||||||
|
|
||||||
|
const [classLoading, isClassLoading] = React.useState(true);
|
||||||
|
const [language, setLanguage] = React.useState([]);
|
||||||
|
|
||||||
|
function tokenGetter() {
|
||||||
|
authenticationService.refreshFireToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
// console.log(path, url);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
const intervalID = setInterval(() => tokenGetter(), 2220000);
|
||||||
|
return () => clearInterval(intervalID);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
// RepeatedDataProvider can block its children from rendering if there is no classes fetched from server.
|
||||||
|
<RepeatedDataProvider mathJax={props.mathJax}>
|
||||||
|
<Layout>
|
||||||
|
<Siderc location={props.location}/>
|
||||||
|
<Layout style={{}}>
|
||||||
|
<Headerc location={props.location} isClassLoading={isClassLoading} setLanguage={setLanguage} />
|
||||||
|
<Layout>
|
||||||
|
{!classLoading && <Switch key={i18n.language} >
|
||||||
|
<Route exact path={`${url}dashboard`} render={() => <InfoDash />} />
|
||||||
|
<Route exact path={`${url}profile-page`} render={() => <Profile />} />
|
||||||
|
{/* <Route exact path={`${path}allQuestions`} component={AllQuestions} /> only for testing against the new component */}
|
||||||
|
<Route exact path={`${path}allQuestions`} render={() => <AllQuestionNew language={language} />} />
|
||||||
|
{/* <Route exact path={`${path}addQuestion`} component={AddQuestion} /> */}
|
||||||
|
<Route exact path={`${path}addQuestion`} render={() =>
|
||||||
|
<ErrorHandler>
|
||||||
|
<CreateQuestion2 language={language} mathJax={props.mathJax}/>
|
||||||
|
</ErrorHandler>
|
||||||
|
} />
|
||||||
|
<Route exact path={`${path}myQuestions`} render={() => <ErrorHandler> <MyQuestion language={language}/> </ErrorHandler>} />
|
||||||
|
<Route exact path={`${path}bookmarkQuestions`} render={() => <ErrorHandler> <Bookmark language={language}/> </ErrorHandler>} />
|
||||||
|
<Route exact path={`${path}draftQuestions`} render={() => <ErrorHandler> <Draft language={language}/> </ErrorHandler>} />
|
||||||
|
{language.length > 0 && <Route exact path={`${path}translateQuestion`} render={() => <ErrorHandler> <TranslateQuestion language={language}/> </ErrorHandler>} />}
|
||||||
|
|
||||||
|
<Route exact path={`${path}createExam`} render={() => <ErrorHandler> <CreateExam /> </ErrorHandler>} />
|
||||||
|
<Route exact path={`${path}draftExams`} render={() => <ErrorHandler> <DraftExamNew /> </ErrorHandler>} />
|
||||||
|
<Route exact path={`${path}liveExam`} render={() => <ErrorHandler> <LiveExamNew /> </ErrorHandler>} />
|
||||||
|
<Route exact path={`${path}upcomingExam`} render={() => <ErrorHandler> <UpcomingExamNew /> </ErrorHandler>} />
|
||||||
|
<Route exact path={`${path}expiredExams`} render={() => <ErrorHandler> <ExpiredExamNew /> </ErrorHandler>} />
|
||||||
|
|
||||||
|
<Route exact path={`${path}createPractise`} render={() => <ErrorHandler> <CreatePractise /> </ErrorHandler>} />
|
||||||
|
<Route exact path={`${path}livePractise`} render={() => <ErrorHandler><LivePracticeNew /></ErrorHandler>} />
|
||||||
|
<Route exact path={`${path}draftPractise`} render={() => <ErrorHandler><DraftPracticeNew /></ErrorHandler>} />
|
||||||
|
<Route exact path={`${path}upcomingPractise`} render={() => <ErrorHandler><UpcomingPracticeNew /></ErrorHandler>} />
|
||||||
|
{/* <Route exact path={`${path}testPractise`} render={() => <ErrorHandler><LivePracticeNew/></ErrorHandler>} /> */}
|
||||||
|
|
||||||
|
<Route exact path={`${path}associatePracticeBatch`} render={() => <ErrorHandler><PracticeAssociateBatchv2 location={props.location} /></ErrorHandler>} />
|
||||||
|
<Route exact path={`${path}associatebatch`} render={() => <ErrorHandler><AssociateBatchNew location={props.location} /></ErrorHandler>} />
|
||||||
|
|
||||||
|
|
||||||
|
<Route exact path={`${path}createbatch`} render={() => <ErrorHandler><CreateBatchNew location={props.location}/></ErrorHandler>} />
|
||||||
|
<Route exact path={`${path}viewbatch`} render={() => <ErrorHandler><ViewBatch location={props.location}/></ErrorHandler>} />
|
||||||
|
|
||||||
|
<Route exact path={`${path}viewclass`} render={() => <ErrorHandler><ViewClass location={props.location}/></ErrorHandler>} />
|
||||||
|
<Route exact path={`${path}class`} render={() => <ErrorHandler><CreateClass location={props.location}/></ErrorHandler>} />
|
||||||
|
|
||||||
|
{/* <Route exact path={`${path}testQuest`} render={() => <ErrorHandler><CreateQuestion2 language={language} mathJax={props.mathJax}/></ErrorHandler>} /> */}
|
||||||
|
|
||||||
|
|
||||||
|
{/* Redirect route from root to dashboard */}
|
||||||
|
<Route exact path={`${url}`} render={ () => <Redirect to={`${url}dashboard`} /> } />
|
||||||
|
</Switch>}
|
||||||
|
{classLoading && <div style={{textAlign: 'center', marginTop: 50}}><Spin/></div>}
|
||||||
|
</Layout>
|
||||||
|
</Layout>
|
||||||
|
</Layout>
|
||||||
|
</RepeatedDataProvider>
|
||||||
|
)
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Dashboard;
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
import React from 'react';
|
||||||
|
import CalendarComp from './CalendarComp';
|
||||||
|
import LiveExamSnippet from './LiveExamSnippet';
|
||||||
|
|
||||||
|
const Events = () => {
|
||||||
|
return (
|
||||||
|
<div className='event-card'>
|
||||||
|
<CalendarComp />
|
||||||
|
<LiveExamSnippet />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Events;
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
import { Typography } from 'antd';
|
||||||
|
import { ReactComponent as AddCircle } from '../assets/image/info-icons/add-circle.svg';
|
||||||
|
import { ReactComponent as QuestionIcon } from '../assets/image/info-icons/questions-icon.svg';
|
||||||
|
import React from 'react';
|
||||||
|
import { validate } from 'uuid';
|
||||||
|
|
||||||
|
const GraphComp = ({heading, footing}) => {
|
||||||
|
return (
|
||||||
|
<div className='graph-card'>
|
||||||
|
<div className="head">
|
||||||
|
<Typography.Text strong style={{color: '#616161'}}>{heading}</Typography.Text>
|
||||||
|
</div>
|
||||||
|
<div className="mid-graph">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div className="foot">
|
||||||
|
<Typography.Text className=''>{footing}</Typography.Text>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default GraphComp;
|
||||||
|
|
@ -0,0 +1,65 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Col, Row } from 'antd';
|
||||||
|
import { uniqueId } from 'lodash';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import Masonry from 'react-masonry-css';
|
||||||
|
|
||||||
|
import StatCard from './StatCard';
|
||||||
|
import GraphComp from './GraphComp';
|
||||||
|
import Events from './Events';
|
||||||
|
import './infodash.css';
|
||||||
|
|
||||||
|
import { ReactComponent as QuestionIcon } from '../assets/image/info-icons/questions-icon.svg';
|
||||||
|
import { ReactComponent as ExamIcon } from '../assets/image/info-icons/exam-icon.svg';
|
||||||
|
import { ReactComponent as PracticeIcon } from '../assets/image/info-icons/practice-icon.svg';
|
||||||
|
import { ReactComponent as StudentEngaged } from '../assets/image/info-icons/student-engaged.svg';
|
||||||
|
import { ReactComponent as BatchIcon } from '../assets/image/info-icons/batch-icon.svg';
|
||||||
|
import { ReactComponent as PerformanceIcon } from '../assets/image/info-icons/perf-icon.svg';
|
||||||
|
|
||||||
|
const InfoDash = () => {
|
||||||
|
|
||||||
|
const masonryStatItem = [
|
||||||
|
<StatCard key={uniqueId()} heading="QUESTIONS CREATED" statVal={34} icon={<QuestionIcon/>}
|
||||||
|
footing={<>{<Link to="/draftQuestions">See questions</Link>} that are in the draft</>} />,
|
||||||
|
<StatCard key={uniqueId()} heading="EXAMS CREATED" statVal={5} icon={<ExamIcon/>}
|
||||||
|
footing={<>{<Link to="/draftExams">See exams</Link>} that are in the draft</>} />,
|
||||||
|
<StatCard key={uniqueId()} heading="PRACTICES CREATED" statVal={40} icon={<PracticeIcon/>}
|
||||||
|
footing={<>{<Link to="/draftPractise">See practices</Link>} that are in the draft</>} />,
|
||||||
|
<StatCard key={uniqueId()} heading="STUDENTS ENGAGED" statVal={46} icon={<StudentEngaged/>}
|
||||||
|
footing={<Link to="">Manage Students</Link>} />,
|
||||||
|
<StatCard key={uniqueId()} heading="ACTIVE BATCHES" statVal={23} icon={<BatchIcon/>}
|
||||||
|
footing={<Link to="">Create New</Link>} />,
|
||||||
|
<StatCard key={uniqueId()} heading="TOTAL LIKES RECEIVED" statVal={146} icon={<PerformanceIcon/>}
|
||||||
|
footing={<Link to="">Go to Performance Section</Link>} />,
|
||||||
|
];
|
||||||
|
|
||||||
|
const breakpointColumnsObj = {
|
||||||
|
default: 3,
|
||||||
|
1150: 2,
|
||||||
|
768: 2,
|
||||||
|
600: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='info-dash'>
|
||||||
|
<Row gutter={[15, 20]}>
|
||||||
|
<Col sm={{span: 16, order: 1}} order={2} className='stats' span={24}>
|
||||||
|
<Masonry
|
||||||
|
breakpointCols={breakpointColumnsObj}
|
||||||
|
className="stat-masonry-grid"
|
||||||
|
columnClassName="stat-masonry-grid_column">
|
||||||
|
{masonryStatItem}
|
||||||
|
</Masonry>
|
||||||
|
<div className="stat-graph">
|
||||||
|
<GraphComp heading="STATISTICS"/>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
<Col sm={{span: 8, order: 1}} order={1} className='schedules' span={24}>
|
||||||
|
<Events />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default InfoDash;
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
.event-card .live-exam-snippet {
|
||||||
|
height: 400px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.event-card .live-exam-snippet .exam-item:not(:last-child) {
|
||||||
|
border-bottom: 1px solid;
|
||||||
|
border-image: linear-gradient(90deg, rgba(0,0,0,0) 5%, rgba(0,0,0,.2) 20%, rgba(0,0,0,.2) 50%, rgba(0,0,0,.2) 80%, rgba(0,0,0,0) 95%) 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.event-card .live-exam-snippet .exam-item {
|
||||||
|
padding: 14px 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { selectorService } from "../../services/selectorService";
|
||||||
|
import { Skeleton, Typography } from 'antd';
|
||||||
|
import { CoffeeOutlined, RestTwoTone } from '@ant-design/icons';
|
||||||
|
import './LiveExamSnippet.css';
|
||||||
|
|
||||||
|
const LiveExamSnippet = () => {
|
||||||
|
|
||||||
|
const [loading, setLoading] = React.useState(true);
|
||||||
|
const [liveExamList, setExamList] = React.useState([]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
selectorService.liveExam({
|
||||||
|
pageSize: 5, pageNumber: 1,
|
||||||
|
}).then((res)=> {
|
||||||
|
// console.log(res);
|
||||||
|
if(res.status.code === "-1") {
|
||||||
|
if(res.status.message[0] === "No Record(s) Found!") {
|
||||||
|
setExamList([]);
|
||||||
|
setLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
throw "Something went wrong, please try again";
|
||||||
|
}
|
||||||
|
setExamList(() => (res.result.exams));
|
||||||
|
setLoading(false);
|
||||||
|
}).catch((err) => {
|
||||||
|
console.log(err);
|
||||||
|
})
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='live-exam-snippet'>
|
||||||
|
{loading && <div style={{padding: '1rem'}}>
|
||||||
|
<Skeleton paragraph={{rows: 2}} active />
|
||||||
|
</div>}
|
||||||
|
{!loading && liveExamList.length!== 0? liveExamList.map(examItem => {
|
||||||
|
const createdDate = new Date(examItem.created_on);
|
||||||
|
const endDate = new Date(examItem.end_date);
|
||||||
|
// const updatedDate = new Date(examItem.updated_on);
|
||||||
|
return <div className='exam-item' key={examItem.id}>
|
||||||
|
<Typography.Text style={{display: 'block'}} strong>{examItem.name}</Typography.Text>
|
||||||
|
<Typography.Text style={{display: 'block'}} type='secondary'>
|
||||||
|
{createdDate.toLocaleDateString()} {createdDate.toLocaleTimeString('hi', { hour12: false })} - {endDate.toLocaleDateString()} {endDate.toLocaleTimeString('hi', { hour12: false })}
|
||||||
|
</Typography.Text>
|
||||||
|
<Typography.Text style={{display: 'block'}} type='secondary'>Duration - {examItem.exam_duration/60} hrs</Typography.Text>
|
||||||
|
</div>
|
||||||
|
})
|
||||||
|
: !loading && <div className='exam-item mt-1-5 center-flex flex--column'>
|
||||||
|
<RestTwoTone twoToneColor="#1449bc" style={{fontSize: 50, marginBottom: 15, borderRadius: '50%', padding: 12, border: '4px solid #1449bc'}} />
|
||||||
|
<Typography.Title style={{textAlign: 'center'}} level={4}>Hey, you have already caught up!!! Awesome!</Typography.Title>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default LiveExamSnippet;
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
import { Typography } from 'antd';
|
||||||
|
|
||||||
|
import { ReactComponent as AddCircle } from '../assets/image/info-icons/add-circle.svg';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const StatCard = ({heading, statVal, icon, footing}) => {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='stat-card'>
|
||||||
|
<div className="head">
|
||||||
|
<Typography.Text strong style={{color: 'var(--font-color)'}}>{heading}</Typography.Text>
|
||||||
|
<AddCircle />
|
||||||
|
</div>
|
||||||
|
<div className="mid">
|
||||||
|
{icon}
|
||||||
|
<Typography.Text className='ml-2-0 center-flex'>{statVal}</Typography.Text>
|
||||||
|
</div>
|
||||||
|
<div className="foot">
|
||||||
|
<Typography.Text className=''>{footing}</Typography.Text>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default StatCard;
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
.calendar-strip * {
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-strip {
|
||||||
|
box-shadow: 0 2px 3px 0 rgba(0, 0, 0, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-strip .strip .today {
|
||||||
|
background-color: snow;
|
||||||
|
color: rebeccapurple;
|
||||||
|
border-radius: 4px;
|
||||||
|
-webkit-border-radius: 4px;
|
||||||
|
-moz-border-radius: 4px;
|
||||||
|
-ms-border-radius: 4px;
|
||||||
|
-o-border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-strip .strip .selected {
|
||||||
|
box-shadow: 0 0 1px 2px snow;
|
||||||
|
border-radius: 4px;
|
||||||
|
-webkit-border-radius: 4px;
|
||||||
|
-moz-border-radius: 4px;
|
||||||
|
-ms-border-radius: 4px;
|
||||||
|
-o-border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-strip .strip {
|
||||||
|
scrollbar-width: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-strip .strip::-webkit-scrollbar {
|
||||||
|
display: block;
|
||||||
|
height: 5px;
|
||||||
|
/* background-color: paleturquoise; */
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-strip .strip::-webkit-scrollbar-thumb {
|
||||||
|
background-color: rgba(175, 238, 238, 0.75);
|
||||||
|
border-radius: 6px;
|
||||||
|
-webkit-border-radius: 6px;
|
||||||
|
-moz-border-radius: 6px;
|
||||||
|
-ms-border-radius: 6px;
|
||||||
|
-o-border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-strip .strip:hover::-webkit-scrollbar {
|
||||||
|
height: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,83 @@
|
||||||
|
.info-dash {
|
||||||
|
width: 100%;
|
||||||
|
margin-block: 20px;
|
||||||
|
padding-inline: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-dash .stats {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-dash .schedules {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-dash .stats .stat-masonry-grid {
|
||||||
|
display: -webkit-box; /* Not needed if autoprefixing */
|
||||||
|
display: -ms-flexbox; /* Not needed if autoprefixing */
|
||||||
|
display: flex;
|
||||||
|
/* margin-left: -30px; gutter size offset */
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-dash .stats .stat-masonry-grid_column > .stat-card:first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-dash .stats .stat-masonry-grid_column:first-child > .stat-card {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-dash .stats .stat-masonry-grid_column:last-child > .stat-card {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-dash .stat-card {
|
||||||
|
padding: 10px 15px;
|
||||||
|
margin: 7px;
|
||||||
|
background-color: var(--background-color-custom-1);
|
||||||
|
border-radius: 12px;
|
||||||
|
-webkit-border-radius: 12px;
|
||||||
|
-moz-border-radius: 12px;
|
||||||
|
-ms-border-radius: 12px;
|
||||||
|
-o-border-radius: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-dash .stat-card > :not(:last-child) {
|
||||||
|
padding-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-dash .stat-card .head, .info-dash .stat-card .mid {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-dash .stat-card .head {
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-dash .stat-card .foot {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-dash .stats .stat-graph {
|
||||||
|
min-height: 300px;
|
||||||
|
min-width: 100%;
|
||||||
|
border-radius: 12px;
|
||||||
|
background-color: var(--background-color-custom-1);
|
||||||
|
padding: 15px 18px;
|
||||||
|
-webkit-border-radius: 12px;
|
||||||
|
-moz-border-radius: 12px;
|
||||||
|
-ms-border-radius: 12px;
|
||||||
|
-o-border-radius: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-dash .schedules .event-card {
|
||||||
|
min-height: 400px;
|
||||||
|
background-color: var(--background-color-custom-1);
|
||||||
|
border-radius: 12px;
|
||||||
|
-webkit-border-radius: 12px;
|
||||||
|
-moz-border-radius: 12px;
|
||||||
|
-ms-border-radius: 12px;
|
||||||
|
-o-border-radius: 12px;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
export default function empty() {
|
||||||
|
return "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMIAAADDCAYAAADQvc6UAAABRWlDQ1BJQ0MgUHJvZmlsZQAAKJFjYGASSSwoyGFhYGDIzSspCnJ3UoiIjFJgf8LAwSDCIMogwMCcmFxc4BgQ4ANUwgCjUcG3awyMIPqyLsis7PPOq3QdDFcvjV3jOD1boQVTPQrgSkktTgbSf4A4LbmgqISBgTEFyFYuLykAsTuAbJEioKOA7DkgdjqEvQHEToKwj4DVhAQ5A9k3gGyB5IxEoBmML4BsnSQk8XQkNtReEOBxcfXxUQg1Mjc0dyHgXNJBSWpFCYh2zi+oLMpMzyhRcASGUqqCZ16yno6CkYGRAQMDKMwhqj/fAIcloxgHQqxAjIHBEugw5sUIsSQpBobtQPdLciLEVJYzMPBHMDBsayhILEqEO4DxG0txmrERhM29nYGBddr//5/DGRjYNRkY/l7////39v///y4Dmn+LgeHANwDrkl1AuO+pmgAAADhlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAAqACAAQAAAABAAAAwqADAAQAAAABAAAAwwAAAAD9b/HnAAAHlklEQVR4Ae3dP3PTWBSGcbGzM6GCKqlIBRV0dHRJFarQ0eUT8LH4BnRU0NHR0UEFVdIlFRV7TzRksomPY8uykTk/zewQfKw/9znv4yvJynLv4uLiV2dBoDiBf4qP3/ARuCRABEFAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghgg0Aj8i0JO4OzsrPv69Wv+hi2qPHr0qNvf39+iI97soRIh4f3z58/u7du3SXX7Xt7Z2enevHmzfQe+oSN2apSAPj09TSrb+XKI/f379+08+A0cNRE2ANkupk+ACNPvkSPcAAEibACyXUyfABGm3yNHuAECRNgAZLuYPgEirKlHu7u7XdyytGwHAd8jjNyng4OD7vnz51dbPT8/7z58+NB9+/bt6jU/TI+AGWHEnrx48eJ/EsSmHzx40L18+fLyzxF3ZVMjEyDCiEDjMYZZS5wiPXnyZFbJaxMhQIQRGzHvWR7XCyOCXsOmiDAi1HmPMMQjDpbpEiDCiL358eNHurW/5SnWdIBbXiDCiA38/Pnzrce2YyZ4//59F3ePLNMl4PbpiL2J0L979+7yDtHDhw8vtzzvdGnEXdvUigSIsCLAWavHp/+qM0BcXMd/q25n1vF57TYBp0a3mUzilePj4+7k5KSLb6gt6ydAhPUzXnoPR0dHl79WGTNCfBnn1uvSCJdegQhLI1vvCk+fPu2ePXt2tZOYEV6/fn31dz+shwAR1sP1cqvLntbEN9MxA9xcYjsxS1jWR4AIa2Ibzx0tc44fYX/16lV6NDFLXH+YL32jwiACRBiEbf5KcXoTIsQSpzXx4N28Ja4BQoK7rgXiydbHjx/P25TaQAJEGAguWy0+2Q8PD6/Ki4R8EVl+bzBOnZY95fq9rj9zAkTI2SxdidBHqG9+skdw43borCXO/ZcJdraPWdv22uIEiLA4q7nvvCug8WTqzQveOH26fodo7g6uFe/a17W3+nFBAkRYENRdb1vkkz1CH9cPsVy/jrhr27PqMYvENYNlHAIesRiBYwRy0V+8iXP8+/fvX11Mr7L7ECueb/r48eMqm7FuI2BGWDEG8cm+7G3NEOfmdcTQw4h9/55lhm7DekRYKQPZF2ArbXTAyu4kDYB2YxUzwg0gi/41ztHnfQG26HbGel/crVrm7tNY+/1btkOEAZ2M05r4FB7r9GbAIdxaZYrHdOsgJ/wCEQY0J74TmOKnbxxT9n3FgGGWWsVdowHtjt9Nnvf7yQM2aZU/TIAIAxrw6dOnAWtZZcoEnBpNuTuObWMEiLAx1HY0ZQJEmHJ3HNvGCBBhY6jtaMoEiJB0Z29vL6ls58vxPcO8/zfrdo5qvKO+d3Fx8Wu8zf1dW4p/cPzLly/dtv9Ts/EbcvGAHhHyfBIhZ6NSiIBTo0LNNtScABFyNiqFCBChULMNNSdAhJyNSiECRCjUbEPNCRAhZ6NSiAARCjXbUHMCRMjZqBQiQIRCzTbUnAARcjYqhQgQoVCzDTUnQIScjUohAkQo1GxDzQkQIWejUogAEQo121BzAkTI2agUIkCEQs021JwAEXI2KoUIEKFQsw01J0CEnI1KIQJEKNRsQ80JECFno1KIABEKNdtQcwJEyNmoFCJAhELNNtScABFyNiqFCBChULMNNSdAhJyNSiECRCjUbEPNCRAhZ6NSiAARCjXbUHMCRMjZqBQiQIRCzTbUnAARcjYqhQgQoVCzDTUnQIScjUohAkQo1GxDzQkQIWejUogAEQo121BzAkTI2agUIkCEQs021JwAEXI2KoUIEKFQsw01J0CEnI1KIQJEKNRsQ80JECFno1KIABEKNdtQcwJEyNmoFCJAhELNNtScABFyNiqFCBChULMNNSdAhJyNSiECRCjUbEPNCRAhZ6NSiAARCjXbUHMCRMjZqBQiQIRCzTbUnAARcjYqhQgQoVCzDTUnQIScjUohAkQo1GxDzQkQIWejUogAEQo121BzAkTI2agUIkCEQs021JwAEXI2KoUIEKFQsw01J0CEnI1KIQJEKNRsQ80JECFno1KIABEKNdtQcwJEyNmoFCJAhELNNtScABFyNiqFCBChULMNNSdAhJyNSiEC/wGgKKC4YMA4TAAAAABJRU5ErkJggg=="
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
import React from "react";
|
||||||
|
import { authenticationService } from "../_services";
|
||||||
|
import { MathJax } from "better-react-mathjax";
|
||||||
|
|
||||||
|
export default class ErrorBoundaries extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props)
|
||||||
|
this.state = {
|
||||||
|
hasError: false,
|
||||||
|
errorMessage: ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
componentDidCatch(error, errorInfo) {
|
||||||
|
this.setState({ hasError: true });
|
||||||
|
this.setState({ errorMessage: error.message });
|
||||||
|
//Do something with err and errorInfo
|
||||||
|
console.log(error);
|
||||||
|
authenticationService.refreshFireToken().then(() => {
|
||||||
|
this.forceUpdate();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
render() {
|
||||||
|
if (this.state?.hasError) {
|
||||||
|
return (
|
||||||
|
<div className="divClass">
|
||||||
|
{this.state.errorMessage}
|
||||||
|
<br/>
|
||||||
|
<p>Something went wrong, please try again.</p>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return (<MathJax>{this.props.children}</MathJax>);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
.site-layout-content {
|
||||||
|
background: #fff;
|
||||||
|
padding: 24px;
|
||||||
|
min-height: 280px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.SubHeader {
|
||||||
|
padding: 0 25px;
|
||||||
|
line-height: 64px;
|
||||||
|
background: #fff !important;
|
||||||
|
border: 1px #282c34;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-page-header-heading-title {
|
||||||
|
margin-right: 12px;
|
||||||
|
margin-bottom: 0;
|
||||||
|
color: rgba(0, 0, 0, 0.5);
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 32px;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-table-thead>tr>th, .ant-table tfoot>tr>th {
|
||||||
|
position: relative;
|
||||||
|
padding: 8px 16px;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-table-tbody>tr>td, .ant-table tfoot>tr>td {
|
||||||
|
position: relative;
|
||||||
|
padding: 3px 16px;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.SubSection2 {
|
||||||
|
margin: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ExamSelector {
|
||||||
|
text-align: left;
|
||||||
|
display: inline-flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ExamSelector .subheader-info {
|
||||||
|
flex: 0 1 auto;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,173 @@
|
||||||
|
//@ts-check
|
||||||
|
import React from 'react';
|
||||||
|
import './AddQuestions.css';
|
||||||
|
import { Layout, Button, Drawer, message } from 'antd';
|
||||||
|
import 'antd/dist/antd.css';
|
||||||
|
import Selector from './Selector';
|
||||||
|
import { authenticationService } from '../../_services';
|
||||||
|
import QuestionTable from './QuestionTable';
|
||||||
|
import equal from 'fast-deep-equal';
|
||||||
|
import { selectorService } from '../../services/selectorService';
|
||||||
|
import ReviewQuestions from './ReviewQuestions';
|
||||||
|
import { InfoCircleFilled } from '@ant-design/icons';
|
||||||
|
import { withTranslation, Trans } from 'react-i18next';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class AddQuestions extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
currentUser: authenticationService.currentUserValue,
|
||||||
|
top: 'topLeft',
|
||||||
|
bottom: 'bottomRight',
|
||||||
|
subject: "",
|
||||||
|
category: "",
|
||||||
|
questionType: "",
|
||||||
|
complexity: "",
|
||||||
|
classes: props.addQuestion.classes || 1,
|
||||||
|
module_id: props.addQuestion.subjectId,
|
||||||
|
section_id: props.addQuestion.module_id,
|
||||||
|
module: props.addQuestion.module,
|
||||||
|
subjectId: props.addQuestion.subjectId,
|
||||||
|
visible: props.addQuestion.visible,
|
||||||
|
attachedQuestions: [],
|
||||||
|
examId: props.addQuestion.examId,
|
||||||
|
reviewVisible: false,
|
||||||
|
isUnmounting: false,
|
||||||
|
};
|
||||||
|
this.ReviewQuestionRef = React.createRef();
|
||||||
|
this.attachQuestion = this.attachQuestion.bind(this);
|
||||||
|
this.reviewQuestions = this.reviewQuestions.bind(this);
|
||||||
|
this.handleSubmit = this.handleSubmit.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
attachQuestion() {
|
||||||
|
const data = {};
|
||||||
|
data.idList = this.state.attachedQuestions;
|
||||||
|
message.loading({ content: 'Attaching Question...' });
|
||||||
|
let examSectionId = this.props.addQuestion.sections.filter(x => x.label === this.props.addQuestion.sectionName)[0].id;
|
||||||
|
selectorService.attachQuestionsToExam(examSectionId, data).then(data1 => {
|
||||||
|
// console.log(data1);
|
||||||
|
if(+data1.status.code < 1) {
|
||||||
|
throw "Something went wrong, failed to attach question(s)."
|
||||||
|
}
|
||||||
|
message.success({ content: 'Question Attached to : ' + this.props.addQuestion.sectionName, duration: 3 });
|
||||||
|
}).catch(err => {
|
||||||
|
if(err === "Something went wrong, failed to attach question(s).") {
|
||||||
|
message.error({
|
||||||
|
content: err
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
message.error({
|
||||||
|
content: "Operation failed!!!"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
console.log(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
handleCancel = e => {
|
||||||
|
// console.log(e);
|
||||||
|
this.props.closeComplete(e);
|
||||||
|
// this.setState({ reviewVisible: false, visible: false, isUnmounting: true }, () => { this.props.closeComplete(e); });
|
||||||
|
};
|
||||||
|
|
||||||
|
handleSubmit = e => {
|
||||||
|
this.setState(p => ({...p, isUnmounting: true}));
|
||||||
|
this.ReviewQuestionRef.current.submitReviewQuestion();
|
||||||
|
// this.handleCancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
reviewQuestions = () => {
|
||||||
|
this.setState({
|
||||||
|
reviewVisible: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
addQuestions = (questions) => {
|
||||||
|
// console.log("Parent recieved Selector Data: "+ childData);
|
||||||
|
this.state.attachedQuestions = questions
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
callbackFunction = (childData) => {
|
||||||
|
// console.log("Parent recieved Selector Data: "+ childData);
|
||||||
|
this.setState({
|
||||||
|
classes: childData.classes,
|
||||||
|
module_id: childData.module_id,
|
||||||
|
subjectId: this.props.addQuestion.subjectId,
|
||||||
|
module: childData.module,
|
||||||
|
questionType: childData.questionType,
|
||||||
|
complexity: childData.complexity,
|
||||||
|
visible: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps) {
|
||||||
|
if (!equal(this.props.addQuestion, prevProps.addQuestion)) // Check if it's a new user, you can also use some unique property, like the ID (this.props.user.id !== prevProps.user.id)
|
||||||
|
{
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
classes: this.props.addQuestion.classes,
|
||||||
|
module_id: this.props.addQuestion.module_id,
|
||||||
|
module: this.props.addQuestion.module,
|
||||||
|
subjectId: this.props.addQuestion.subjectId,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.setState({
|
||||||
|
classes: this.props.addQuestion.classes,
|
||||||
|
module_id: this.props.addQuestion.module_id,
|
||||||
|
module: this.props.addQuestion.module,
|
||||||
|
subjectId: this.props.addQuestion.subjectId,
|
||||||
|
})
|
||||||
|
// this.ReviewQuestionRef = React.createRef(); // took the initialisation to contructor function
|
||||||
|
}
|
||||||
|
render() {
|
||||||
|
// console.log(this.state, this.props);
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{this.props.step === 0 && <Layout>
|
||||||
|
<Layout>
|
||||||
|
<Layout className="SubSection1">
|
||||||
|
<div className='ExamSelector'>
|
||||||
|
<Selector page="addQuestions" selectorState={this.props.addQuestion} parentCallback={this.callbackFunction} />
|
||||||
|
<div className="selector ml-auto">
|
||||||
|
<Button className="selectorLeftButton" type="primary" onClick={this.attachQuestion}>
|
||||||
|
<Trans i18nKey={"attach-btn"}>Attach</Trans>
|
||||||
|
</Button>
|
||||||
|
<div className="subheader-info">
|
||||||
|
<InfoCircleFilled style={{color: "darkorange", fontSize: 16}} /> <span>
|
||||||
|
<Trans i18nKey={'note-in-attach-modal'}>Select questions and 'ATTACH' them before moving to next page.</Trans>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{!this.state.isUnmounting && <Layout className="SubSection2">
|
||||||
|
<div style={{ padding: '5px' }}>
|
||||||
|
<QuestionTable selectorState={this.state} parentCallback={this.addQuestions} page={1}/>
|
||||||
|
</div>
|
||||||
|
</Layout>}
|
||||||
|
</Layout>
|
||||||
|
</Layout>
|
||||||
|
|
||||||
|
</Layout>}
|
||||||
|
{
|
||||||
|
this.state.reviewVisible && this.props.step === 1 ? (
|
||||||
|
<ReviewQuestions
|
||||||
|
ref={this.ReviewQuestionRef}
|
||||||
|
handleCancel={this.handleCancel}
|
||||||
|
sectionState={this.props.addQuestion}
|
||||||
|
/>
|
||||||
|
) : null
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AddQuestions;
|
||||||
|
|
@ -0,0 +1,420 @@
|
||||||
|
import React from "react";
|
||||||
|
import "./SelectSubjects.css";
|
||||||
|
import { Button, Form, Switch, Drawer, Input, message, Typography, Modal, Steps } from "antd";
|
||||||
|
import {
|
||||||
|
FormOutlined,
|
||||||
|
CheckSquareTwoTone,
|
||||||
|
CloseSquareTwoTone,
|
||||||
|
InfoCircleOutlined,
|
||||||
|
} from "@ant-design/icons";
|
||||||
|
import { authenticationService } from "../../_services";
|
||||||
|
import { selectorService } from "../../services/selectorService";
|
||||||
|
import DragDropContainer from "./DragNDrop/Container";
|
||||||
|
import { DndProvider } from "react-dnd";
|
||||||
|
import { HTML5Backend } from "react-dnd-html5-backend";
|
||||||
|
import AddQuestions from "./AddQuestions";
|
||||||
|
import { isEqual } from "lodash";
|
||||||
|
import empty from "../EmptyImage";
|
||||||
|
import { withTranslation, Trans } from 'react-i18next';
|
||||||
|
|
||||||
|
let subject = [];
|
||||||
|
|
||||||
|
class ArrangeSections extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.child = React.createRef();
|
||||||
|
this.state = {
|
||||||
|
currentUser: authenticationService.currentUserValue,
|
||||||
|
examName: props.selectorState.name,
|
||||||
|
update: false,
|
||||||
|
examId: props.selectorState.examId,
|
||||||
|
sections: props.selectorState.sections,
|
||||||
|
currentUser: authenticationService.currentUserValue,
|
||||||
|
authorId: props.selectorState.authorId,
|
||||||
|
examStatus: props.selectorState.examStatus,
|
||||||
|
examTypeId: props.selectorState.examTypeId,
|
||||||
|
id: props.selectorState.id,
|
||||||
|
image: props.selectorState.image,
|
||||||
|
instituteId: props.selectorState.instituteId,
|
||||||
|
isActive: props.selectorState.isActive,
|
||||||
|
languageId: props.selectorState.languageId,
|
||||||
|
subjects: [],
|
||||||
|
currentStatus: 0,
|
||||||
|
rearrange: true,
|
||||||
|
visible: false,
|
||||||
|
visibleButtonIndex: 0,
|
||||||
|
modal: { examId: props.selectorState.id },
|
||||||
|
classes: 1,
|
||||||
|
loaded: false,
|
||||||
|
updated: false,
|
||||||
|
updatedName: props.selectorState.name,
|
||||||
|
currentStep: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
this.steps = [
|
||||||
|
{
|
||||||
|
title: this.props.t('step-1'),
|
||||||
|
description: this.props.t('attach-que'),
|
||||||
|
// content: <AddQuestions selectorState={state} parentCallback={callbackFunctionStep1} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: this.props.t('step-2'),
|
||||||
|
description: this.props.t('review-btn'),
|
||||||
|
// content: <ReviewQuestions />
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
this.addImageDefault = () => {
|
||||||
|
this.setState (prev => {
|
||||||
|
return { ...prev, image: empty() }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.sendData = this.sendData.bind(this);
|
||||||
|
this.handleRearrange = this.handleRearrange.bind(this);
|
||||||
|
this.handleArrangeSubject = this.handleArrangeSubject.bind(this);
|
||||||
|
this.handleLoadSections = this.handleLoadSections.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.handleLoadSections();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(prevprops, prevState) {
|
||||||
|
// console.log("Previous :", prevState);
|
||||||
|
if (!this.state.updated)
|
||||||
|
selectorService.loadSections(this.state.id).then((data) => {
|
||||||
|
// console.log("Success:", data);
|
||||||
|
if (data) {
|
||||||
|
const result = data.result;
|
||||||
|
// this.state.sections = [];
|
||||||
|
let array = [];
|
||||||
|
const sections = prevState.sections; // prevState.sections because we want to preserve the new order of the re-arranged section
|
||||||
|
for (let index = 0; index < sections.length; index++) {
|
||||||
|
let c = {};
|
||||||
|
// c.id = sections[index].id;
|
||||||
|
// c.label = sections[index].subject_name;
|
||||||
|
// c.value = sections[index].subject_id;
|
||||||
|
// c.section_state = sections[index].section_state;
|
||||||
|
// c.totalQuestion = sections[index].total_questions;
|
||||||
|
// c.subject_id = sections[index].subject_id;
|
||||||
|
// c.index = index;
|
||||||
|
// c.buttonState = false;
|
||||||
|
const correspondingSection = result.sections.find(subject => subject.subject_id === sections[index].subject_id);
|
||||||
|
if(correspondingSection) {
|
||||||
|
c.id = correspondingSection.id;
|
||||||
|
c.label = correspondingSection.subject_name;
|
||||||
|
c.value = correspondingSection.subject_id;
|
||||||
|
c.section_state = correspondingSection.section_state;
|
||||||
|
c.totalQuestion = correspondingSection.total_questions;
|
||||||
|
c.subject_id = correspondingSection.subject_id;
|
||||||
|
c.index = index;
|
||||||
|
c.buttonState = false;
|
||||||
|
}
|
||||||
|
array.push(c);
|
||||||
|
}
|
||||||
|
// console.log(array, this.state.sections, isEqual(array, this.state.sections));
|
||||||
|
if (!isEqual(array, this.state.sections)) {
|
||||||
|
this.setState({ sections: array, updated: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleLoadSections = () => {
|
||||||
|
selectorService.loadSections(this.state.id).then((data) => {
|
||||||
|
// console.log("Success:", data);
|
||||||
|
const result = data.result;
|
||||||
|
this.state.sections = [];
|
||||||
|
const sections = result.sections;
|
||||||
|
for (let index = 0; index < sections.length; index++) {
|
||||||
|
let c = {};
|
||||||
|
c.id = sections[index].id;
|
||||||
|
c.label = sections[index].subject_name;
|
||||||
|
c.value = sections[index].subject_id;
|
||||||
|
c.section_state = sections[index].section_state;
|
||||||
|
c.totalQuestion = sections[index].total_questions;
|
||||||
|
c.subject_id = sections[index].subject_id;
|
||||||
|
c.index = index;
|
||||||
|
c.buttonState = false;
|
||||||
|
this.state.sections.push(c);
|
||||||
|
}
|
||||||
|
this.state.loaded = true;
|
||||||
|
this.forceUpdate();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
handleRearrange = (event) => {
|
||||||
|
this.setState({
|
||||||
|
rearrange: event,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
callbackFunctionDragNDrop = (childData) => {
|
||||||
|
// console.log("Parent recieved Selector Data: " + JSON.stringify(childData));
|
||||||
|
this.setState({
|
||||||
|
sections: childData,
|
||||||
|
updated: true,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
callbackButtonClick = (childData) => {
|
||||||
|
// console.log("Parent recieved Selector Data: " + childData);
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
visible: true,
|
||||||
|
visibleButtonIndex: childData,
|
||||||
|
modal: {
|
||||||
|
sections: this.state.sections,
|
||||||
|
visible: true,
|
||||||
|
examId: this.props.selectorState.id,
|
||||||
|
classes: 1,
|
||||||
|
module_id: this.state.sections[childData].value,
|
||||||
|
module: "subject",
|
||||||
|
subjectId: this.state.sections[childData].subject_id,
|
||||||
|
sectionName: this.state.sections[childData].label,
|
||||||
|
},
|
||||||
|
subject: this.state.sections[childData].label,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
handleArrangeSubject() {
|
||||||
|
if ( this.state.sections.filter((x) => x.section_state === "PUBLISHED").length !== this.state.sections.length ) {
|
||||||
|
message.warning("Please add questions for all sections");
|
||||||
|
} else {
|
||||||
|
let sectionsId = [];
|
||||||
|
this.state.sections.forEach((section) => {
|
||||||
|
sectionsId.push(section.id);
|
||||||
|
});
|
||||||
|
let json = {};
|
||||||
|
json.idList = sectionsId;
|
||||||
|
selectorService.arrangeSectionsFromExam(this.state.id, json)
|
||||||
|
.then((data) => {
|
||||||
|
if(+data.status.code < 0) {
|
||||||
|
throw data.status.message[0];
|
||||||
|
}
|
||||||
|
let result = {};
|
||||||
|
result.current = 3;
|
||||||
|
result.sections = this.state.sections;
|
||||||
|
result.rearrange = this.state.rearrange;
|
||||||
|
result.exam = this.state.examName;
|
||||||
|
this.sendData(result);
|
||||||
|
}).catch(err => {
|
||||||
|
message.error("Something went wrong, could not continue to next step.");
|
||||||
|
console.log(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sendData = (result) => {
|
||||||
|
// console.log(result);
|
||||||
|
this.props.parentCallback(result);
|
||||||
|
};
|
||||||
|
|
||||||
|
handleCancel = (e) => {
|
||||||
|
// console.log(e);
|
||||||
|
// this.state.modal.visible = false;
|
||||||
|
this.setState(prev => ({
|
||||||
|
...prev,
|
||||||
|
visible: false,
|
||||||
|
updated: false,
|
||||||
|
currentStep: 0
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { current } = this.state.currentStatus;
|
||||||
|
// let disabledSize = this.state.sections.filter((x) => x.section_state === "PUBLISHED").length !== this.state.sections.length;
|
||||||
|
// console.log(disabledSize, this.state.sections);
|
||||||
|
return (
|
||||||
|
<div className="SelectSubjects SelectSubjects--Exam">
|
||||||
|
<DndProvider backend={HTML5Backend}>
|
||||||
|
<Form className="exam-form">
|
||||||
|
<div className="exam">
|
||||||
|
<table className="examTable">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<div className="examNameOuter">
|
||||||
|
<div>
|
||||||
|
<img
|
||||||
|
onError={this.addImageDefault}
|
||||||
|
width={100}
|
||||||
|
height={100}
|
||||||
|
src={this.state.image}
|
||||||
|
></img>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="examName">
|
||||||
|
<div>
|
||||||
|
<Typography.Text style={{color: 'var(--font-color-primary)', fontSize: '0.85rem'}}>{this.props.t('exam-name')}</Typography.Text>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span
|
||||||
|
hidden={this.state.editing}
|
||||||
|
className="examNameSpan"
|
||||||
|
>
|
||||||
|
{this.state.examName}
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
{" "}
|
||||||
|
<Input
|
||||||
|
value={this.state.updatedName}
|
||||||
|
onChange={(e) =>
|
||||||
|
this.setState({ updatedName: e.target.value })
|
||||||
|
}
|
||||||
|
hidden={!this.state.editing}
|
||||||
|
style={{ width: "60%" }}
|
||||||
|
placeholder="Exam Name"
|
||||||
|
/>{" "}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
className="examNameEdit"
|
||||||
|
style={{ cursor: "pointer", width: "5px" }}
|
||||||
|
>
|
||||||
|
<div onClick={() => this.setState({ editing: true })}>
|
||||||
|
<FormOutlined hidden={this.state.editing} style={{ fontSize: "16px", color: "#1890FF" }} twoToneColor="#1890FF" />
|
||||||
|
</div>
|
||||||
|
<div hidden={!this.state.editing} onClick={() => {
|
||||||
|
this.setState({
|
||||||
|
editing: false,
|
||||||
|
updatedName: this.state.examName,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CloseSquareTwoTone />
|
||||||
|
</div>
|
||||||
|
<div onClick={() => {
|
||||||
|
const { examId, updatedName, image, authorId, examTypeId, id, } = this.state;
|
||||||
|
selectorService.updateExam({ id, examId, name: updatedName, image, authorId, examTypeId, })
|
||||||
|
.then((res) => {
|
||||||
|
// console.log(res);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.log(err);
|
||||||
|
});
|
||||||
|
this.setState({
|
||||||
|
editing: false,
|
||||||
|
examName: this.state.updatedName,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
hidden={!this.state.editing}
|
||||||
|
>
|
||||||
|
<CheckSquareTwoTone />
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>{" "}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<br />
|
||||||
|
<div className="select-subject-grid">
|
||||||
|
<div className="subject__switching">
|
||||||
|
<Typography.Text strong
|
||||||
|
style={{color: 'var(--font-color-primary)', display: 'block', alignSelf: 'start', maxWidth: 120}}>
|
||||||
|
{this.props.t('add-que-sub')}
|
||||||
|
</Typography.Text>
|
||||||
|
</div>
|
||||||
|
{this.state.loaded && !this.state.visible ? (
|
||||||
|
// <div className="subjectButtons">
|
||||||
|
|
||||||
|
<DragDropContainer key={"container-movable"} id={this.state.id} modelState={this.state.sections}
|
||||||
|
parentCallback={this.callbackFunctionDragNDrop} parentButtonCallback={this.callbackButtonClick}/>
|
||||||
|
|
||||||
|
// </div>
|
||||||
|
) : null}
|
||||||
|
<br />
|
||||||
|
<div>
|
||||||
|
<span>
|
||||||
|
<InfoCircleOutlined />{" "}
|
||||||
|
<strong style={{color: 'orange'}}>{this.props.t('note')}: </strong>
|
||||||
|
<Trans i18nKey="step-3-note-main">
|
||||||
|
<strong>CLICK</strong> on each subject to add their respective questions.
|
||||||
|
</Trans>
|
||||||
|
</span>
|
||||||
|
<p>
|
||||||
|
<Trans i18nKey="step-3-note-2">
|
||||||
|
You can also <strong>DRAG</strong> the subjects to reorder them
|
||||||
|
</Trans>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
<div className="button-container" data-need-margin={"false"}>
|
||||||
|
<Button type="primary" onClick={(e) => { this.handleArrangeSubject(); }} >
|
||||||
|
{this.props.t('continue-btn')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Form>
|
||||||
|
{this.state.visible && <Modal
|
||||||
|
title={
|
||||||
|
<Steps current={this.state.currentStep}>
|
||||||
|
{this.steps.map((item) => (
|
||||||
|
<Steps.Step key={item.title} title={item.title} description={item.description} />
|
||||||
|
))}
|
||||||
|
</Steps>
|
||||||
|
}
|
||||||
|
width={"auto"}
|
||||||
|
// placement="right"
|
||||||
|
centered
|
||||||
|
closable={true}
|
||||||
|
wrapClassName={"attach-question--modal"}
|
||||||
|
style={{
|
||||||
|
minWidth: "360px",
|
||||||
|
maxWidth: "calc(100vw - 40px)",
|
||||||
|
maxHeight: "calc(100vh - 100px)"
|
||||||
|
}}
|
||||||
|
onCancel={this.handleCancel}
|
||||||
|
visible={this.state.visible}
|
||||||
|
destroyOnClose={true}
|
||||||
|
footer={
|
||||||
|
<div>
|
||||||
|
<Button
|
||||||
|
onClick={this.state.currentStatus===0? () => this.handleCancel()
|
||||||
|
: () => this.child.current.handleCancel()}
|
||||||
|
type="primary"
|
||||||
|
style={{ marginRight: 8 }}
|
||||||
|
>
|
||||||
|
{this.props.t('close-btn')}
|
||||||
|
</Button>
|
||||||
|
{this.state.currentStep === 0? <Button
|
||||||
|
onClick={() => {
|
||||||
|
this.child.current.reviewQuestions();
|
||||||
|
this.setState(prev => ({...prev, currentStep: 1}));
|
||||||
|
}}
|
||||||
|
type="primary"
|
||||||
|
style={{ marginRight: 8 }}
|
||||||
|
>
|
||||||
|
{this.props.t('review-btn')}
|
||||||
|
</Button>
|
||||||
|
: <Button
|
||||||
|
onClick={async () => {
|
||||||
|
const a = await this.child.current.handleSubmit();
|
||||||
|
// this.child.current.handleCancel();
|
||||||
|
// check if a is NOT = 0, then set step back to 0.
|
||||||
|
// if a = 0, then operation was unsuccessful, so the step remains at 1.
|
||||||
|
// a !== 0 && this.setState(prev => ({...prev, currentStep: 0}));
|
||||||
|
}}
|
||||||
|
type="primary"
|
||||||
|
// style={{ marginRight: 8 }}
|
||||||
|
>
|
||||||
|
{this.props.t('submit-btn')}
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
</div>}
|
||||||
|
>
|
||||||
|
<AddQuestions step={this.state.currentStep} ref={this.child} closeComplete={this.handleCancel} addQuestion={this.state.modal} />
|
||||||
|
{/* </AddQuestions> */}
|
||||||
|
</Modal>}
|
||||||
|
</DndProvider>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withTranslation()(ArrangeSections);
|
||||||
|
|
@ -0,0 +1,495 @@
|
||||||
|
import {
|
||||||
|
Steps,
|
||||||
|
Button,
|
||||||
|
message,
|
||||||
|
Layout,
|
||||||
|
List,
|
||||||
|
Avatar,
|
||||||
|
Skeleton,
|
||||||
|
Empty,
|
||||||
|
Row,
|
||||||
|
Col,
|
||||||
|
Checkbox,
|
||||||
|
} from "antd";
|
||||||
|
import { EditTwoTone, DeleteTwoTone } from "@ant-design/icons";
|
||||||
|
import "./DraftExam.css";
|
||||||
|
import { Siderc } from "../Main/Siderc";
|
||||||
|
import React from "react";
|
||||||
|
import { Headerc } from "../Main/Headerc";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
import history from "../../history";
|
||||||
|
import { selectorService } from "../../services/selectorService";
|
||||||
|
import parse from "html-react-parser";
|
||||||
|
|
||||||
|
const { Content } = Layout;
|
||||||
|
|
||||||
|
class AssociateBatch extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
start_date: this.props.location.state.start_date,
|
||||||
|
end_date: this.props.location.state.end_date,
|
||||||
|
exam_duration: this.props.location.state.exam_duration,
|
||||||
|
total_marks: this.props.location.state.total_marks,
|
||||||
|
total_questions: this.props.location.state.total_questions,
|
||||||
|
name: this.props.location.state.name,
|
||||||
|
id: this.props.location.state.id,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
total_count: 0,
|
||||||
|
total_pages: 0,
|
||||||
|
processing: true,
|
||||||
|
batches: [],
|
||||||
|
selectedId: [],
|
||||||
|
response: [],
|
||||||
|
resp: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
selectorService
|
||||||
|
.getAllBatch()
|
||||||
|
.then((res) => {
|
||||||
|
let batches = res.result;
|
||||||
|
console.log(batches);
|
||||||
|
// selectorService()
|
||||||
|
|
||||||
|
return batches;
|
||||||
|
})
|
||||||
|
.then((batches) => {
|
||||||
|
selectorService
|
||||||
|
.getExamBatch({ id: this.state.data[0].id })
|
||||||
|
.then((response) => {
|
||||||
|
console.log(response);
|
||||||
|
this.setState({
|
||||||
|
batches,
|
||||||
|
selectedId: response.result.idList,
|
||||||
|
resp: response.result.idList,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.log(error);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.log(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// componentDidMount() {
|
||||||
|
// selectorService
|
||||||
|
// .draftExam({ pageSize: 10, pageNumber: 1, classId: 1 })
|
||||||
|
// .then((res) => {
|
||||||
|
// console.log(res);
|
||||||
|
// this.setState({
|
||||||
|
// data: res.result.exams,
|
||||||
|
// total_count: res.result.total_count,
|
||||||
|
// total_pages: res.result.total_pages,
|
||||||
|
// processing: false,
|
||||||
|
// });
|
||||||
|
// })
|
||||||
|
// .catch((err) => {
|
||||||
|
// console.log(err);
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
// handleChange = (pageNumber, pageSize) => {
|
||||||
|
// selectorService
|
||||||
|
// .upcomingExam({ pageNumber: pageNumber, pageSize: pageSize, classId: 1 })
|
||||||
|
// .then((res) => {
|
||||||
|
// this.setState({
|
||||||
|
// data: res.result.exams,
|
||||||
|
// total_count: res.result.total_count,
|
||||||
|
// total_pages: res.result.total_pages,
|
||||||
|
// processing: false,
|
||||||
|
// });
|
||||||
|
// })
|
||||||
|
// .catch((err) => {
|
||||||
|
// console.log(err);
|
||||||
|
// });
|
||||||
|
// };
|
||||||
|
|
||||||
|
render() {
|
||||||
|
console.log(this.state);
|
||||||
|
if (this.state.data.length !== 0)
|
||||||
|
return (
|
||||||
|
<Content
|
||||||
|
className="site-layout-background"
|
||||||
|
style={{
|
||||||
|
padding: 24,
|
||||||
|
margin: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Layout className="createExamLayoutHeader">Batches</Layout>
|
||||||
|
<Layout className="createExamLayout">
|
||||||
|
<List
|
||||||
|
itemLayout="horizontal"
|
||||||
|
dataSource={this.state.data}
|
||||||
|
pagination={false}
|
||||||
|
renderItem={(item, index) => {
|
||||||
|
// let step = 2;
|
||||||
|
// if (item.sections_count >= 1) step = 3;
|
||||||
|
// if (item.sections_status !== "DRAFT") step = 4;
|
||||||
|
// let currentTime = new Date();
|
||||||
|
// let loginTime = new Date(item.updated_on);
|
||||||
|
// const diff = currentTime.getTime() - loginTime.getTime();
|
||||||
|
// // console.log(diff,loginTime,currentTime,item.updated_on);
|
||||||
|
// var years = Math.floor(
|
||||||
|
// diff / (1000 * 60 * 60 * 24 * 30 * 12)
|
||||||
|
// );
|
||||||
|
// var months = Math.floor(
|
||||||
|
// (diff % (1000 * 60 * 60 * 24 * 30 * 12)) /
|
||||||
|
// (1000 * 60 * 60 * 24 * 30)
|
||||||
|
// );
|
||||||
|
// var days = Math.floor(
|
||||||
|
// (diff % (1000 * 60 * 60 * 24 * 30)) /
|
||||||
|
// (1000 * 60 * 60 * 24)
|
||||||
|
// );
|
||||||
|
|
||||||
|
// var hours = Math.floor(
|
||||||
|
// (diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)
|
||||||
|
// );
|
||||||
|
// var minutes = Math.floor((diff % 3.6e6) / 6e4);
|
||||||
|
// let updatedOn =
|
||||||
|
// years > 0
|
||||||
|
// ? `${years} year(s)/${months} months`
|
||||||
|
// : months > 0
|
||||||
|
// ? `${months} month(s)/${days} days`
|
||||||
|
// : days > 0
|
||||||
|
// ? `${days} day(s)/${hours} hours`
|
||||||
|
// : minutes > 0
|
||||||
|
// ? `${hours} hour(s)${minutes}minutes(s)`
|
||||||
|
// : minutes + "minutes";
|
||||||
|
// console.log(item);
|
||||||
|
return (
|
||||||
|
<List.Item
|
||||||
|
className="DraftExam"
|
||||||
|
key={index}
|
||||||
|
actions={[
|
||||||
|
<Button
|
||||||
|
onClick={(e, newValue) => {
|
||||||
|
selectorService
|
||||||
|
.addBatchToExam({
|
||||||
|
id: this.state.data[0].id,
|
||||||
|
idList: this.state.selectedId,
|
||||||
|
})
|
||||||
|
.then((res) => {
|
||||||
|
if (!res) {
|
||||||
|
message.error("Some error occured");
|
||||||
|
} else {
|
||||||
|
message.success(
|
||||||
|
"Batches added successfully"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
type="primary"
|
||||||
|
>
|
||||||
|
<Link to={{ pathname: "/upcomingExam" }}>
|
||||||
|
Update
|
||||||
|
</Link>
|
||||||
|
</Button>,
|
||||||
|
<Button type="primary">
|
||||||
|
<Link to={{ pathname: "/upcomingExam" }}>
|
||||||
|
Cancel
|
||||||
|
</Link>
|
||||||
|
</Button>,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<List.Item.Meta
|
||||||
|
avatar={
|
||||||
|
<Avatar
|
||||||
|
src="https://cdn.xl.thumbs.canstockphoto.com/exam-written-on-a-chalkboard-books-pencils-and-an-apple-on-foreground-picture_csp2468961.jpg"
|
||||||
|
size="large"
|
||||||
|
shape="square"
|
||||||
|
className="DraftExamImage"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
size="large"
|
||||||
|
bordered="true"
|
||||||
|
title={item.name}
|
||||||
|
description={
|
||||||
|
<>
|
||||||
|
<div className="MyExamDiv">
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Start Date</th>
|
||||||
|
<th>End Date</th>
|
||||||
|
<th>Durations</th>
|
||||||
|
<th>Questions</th>
|
||||||
|
<th>Marks</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>{item.start_date}</td>
|
||||||
|
<td>{item.end_date}</td>
|
||||||
|
<td>{item.exam_duration}</td>
|
||||||
|
<td>{item.total_questions}</td>
|
||||||
|
<td>{item.total_marks}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</List.Item>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<br />
|
||||||
|
<div className="MyExamDiv">
|
||||||
|
<Checkbox.Group
|
||||||
|
style={{ width: "100%" }}
|
||||||
|
value={this.state.selectedId}
|
||||||
|
onChange={(checkedValues) => {
|
||||||
|
console.log("checked = ", checkedValues);
|
||||||
|
|
||||||
|
let deletedId = this.state.selectedId.filter(
|
||||||
|
(x) =>
|
||||||
|
!checkedValues.includes(x) &&
|
||||||
|
!this.state.response.includes(x) &&
|
||||||
|
this.state.resp.includes(x)
|
||||||
|
)[0];
|
||||||
|
if (!isNaN(parseInt(deletedId))) {
|
||||||
|
selectorService
|
||||||
|
.deleteExamBatch({
|
||||||
|
id: this.state.data[0].id,
|
||||||
|
idList: [deletedId],
|
||||||
|
})
|
||||||
|
.then((res) => {
|
||||||
|
console.log(res);
|
||||||
|
if (!res.result) {
|
||||||
|
message.error("Some error occured");
|
||||||
|
} else {
|
||||||
|
this.setState({
|
||||||
|
selectedId: checkedValues,
|
||||||
|
response: this.state.response.concat([
|
||||||
|
deletedId,
|
||||||
|
]),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.log(err);
|
||||||
|
});
|
||||||
|
} else this.setState({ selectedId: checkedValues });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Row>
|
||||||
|
{this.state.batches.map((x, idx) => {
|
||||||
|
if (idx % 3 === 0)
|
||||||
|
return (
|
||||||
|
<Col span={7}>
|
||||||
|
<div
|
||||||
|
key={idx}
|
||||||
|
style={{
|
||||||
|
minHeight: "7vh",
|
||||||
|
backgroundColor: "#F5F6F7",
|
||||||
|
marginBottom: "10%",
|
||||||
|
padding: "2vh",
|
||||||
|
width: "100%",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Checkbox
|
||||||
|
style={{ width: "100%" }}
|
||||||
|
value={x.id}
|
||||||
|
// disabled={true}
|
||||||
|
>
|
||||||
|
<strong>{x.name}</strong>
|
||||||
|
<span
|
||||||
|
style={{ marginLeft: "2%", width: "100%" }}
|
||||||
|
>
|
||||||
|
{"- (" + x.student_count + " Students)"}
|
||||||
|
</span>
|
||||||
|
{/* <Link
|
||||||
|
onClick={() => {
|
||||||
|
selectorService
|
||||||
|
.deleteBatch(this.state.batches[idx].id)
|
||||||
|
.then((res) => {
|
||||||
|
let batches = this.state.batches.filter(
|
||||||
|
(x) =>
|
||||||
|
x.id !==
|
||||||
|
this.state.batches[idx].id
|
||||||
|
);
|
||||||
|
this.setState({ batches });
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.log(err);
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
style={{ float: "right", marginLeft: "2%" }}
|
||||||
|
to="#"
|
||||||
|
>
|
||||||
|
<DeleteTwoTone />
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
<Link
|
||||||
|
style={{ float: "right" }}
|
||||||
|
to={{
|
||||||
|
pathname: "/createbatch",
|
||||||
|
state: this.state.batches[idx],
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<EditTwoTone />
|
||||||
|
</Link> */}
|
||||||
|
</Checkbox>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
);
|
||||||
|
else
|
||||||
|
return (
|
||||||
|
<Col span={7} offset={1}>
|
||||||
|
<div
|
||||||
|
key={idx}
|
||||||
|
style={{
|
||||||
|
minHeight: "7vh",
|
||||||
|
backgroundColor: "#F5F6F7",
|
||||||
|
marginBottom: "10%",
|
||||||
|
padding: "2vh",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Checkbox
|
||||||
|
style={{ width: "100%" }}
|
||||||
|
value={x.id}
|
||||||
|
>
|
||||||
|
<strong>{x.name}</strong>
|
||||||
|
<span style={{ marginLeft: "2%" }}>
|
||||||
|
{"- (" + x.student_count + " Students)"}
|
||||||
|
</span>
|
||||||
|
{/* <Link
|
||||||
|
onClick={() => {
|
||||||
|
selectorService
|
||||||
|
.deleteBatch(this.state.batches[idx].id)
|
||||||
|
.then((res) => {
|
||||||
|
let batches = this.state.batches.filter(
|
||||||
|
(x) =>
|
||||||
|
x.id !==
|
||||||
|
this.state.batches[idx].id
|
||||||
|
);
|
||||||
|
this.setState({ batches });
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.log(err);
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
style={{ float: "right", marginLeft: "2%" }}
|
||||||
|
to="#"
|
||||||
|
>
|
||||||
|
<DeleteTwoTone />
|
||||||
|
</Link>
|
||||||
|
<Link
|
||||||
|
style={{ float: "right" }}
|
||||||
|
to={{
|
||||||
|
pathname: "/createbatch",
|
||||||
|
state: this.state.batches[idx],
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<EditTwoTone />
|
||||||
|
</Link> */}
|
||||||
|
</Checkbox>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Row>
|
||||||
|
</Checkbox.Group>
|
||||||
|
</div>
|
||||||
|
</Layout>
|
||||||
|
</Content>
|
||||||
|
);
|
||||||
|
else if (this.state.processing)
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Content
|
||||||
|
className="site-layout-background"
|
||||||
|
style={{
|
||||||
|
padding: 24,
|
||||||
|
margin: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Layout className="createExamLayoutHeader">Batches</Layout>
|
||||||
|
<Layout className="createExamLayout">
|
||||||
|
<List
|
||||||
|
itemLayout="horizontal"
|
||||||
|
dataSource={[{}, {}, {}, {}, {}, {}, {}, {}, {}, {}]}
|
||||||
|
pagination={{
|
||||||
|
onChange: (page, pageSize) => {
|
||||||
|
this.handleChange(page, pageSize);
|
||||||
|
},
|
||||||
|
|
||||||
|
total: this.state.total_count,
|
||||||
|
}}
|
||||||
|
renderItem={(item, index) => {
|
||||||
|
return (
|
||||||
|
<List.Item
|
||||||
|
className="DraftExam"
|
||||||
|
key={index}
|
||||||
|
// actions={[
|
||||||
|
// <Button
|
||||||
|
// onClick={(e, newValue) => {
|
||||||
|
// console.log(item, e);
|
||||||
|
// }}
|
||||||
|
// type="primary"
|
||||||
|
// >
|
||||||
|
// <Link
|
||||||
|
// to={{
|
||||||
|
// pathname: "/createExam",
|
||||||
|
// state: { id: item.id, current: step - 1 },
|
||||||
|
// }}
|
||||||
|
// >
|
||||||
|
// Recreate Exam
|
||||||
|
// </Link>
|
||||||
|
// </Button>,
|
||||||
|
// ]}
|
||||||
|
>
|
||||||
|
<Skeleton>
|
||||||
|
<List.Item.Meta
|
||||||
|
avatar={
|
||||||
|
<Avatar
|
||||||
|
src="https://cdn.xl.thumbs.canstockphoto.com/exam-written-on-a-chalkboard-books-pencils-and-an-apple-on-foreground-picture_csp2468961.jpg"
|
||||||
|
size="large"
|
||||||
|
shape="square"
|
||||||
|
className="DraftExamImage"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
size="large"
|
||||||
|
bordered="true"
|
||||||
|
title={parse(`<b>${"item.name"}</b>`)}
|
||||||
|
description={parse(
|
||||||
|
`<b>Progress - Step</b> ${"step"} <b>Updated At:</b> ${"updatedOn"} <b>Author Name:</b> ${"item.author_name"} <b>Sections Count: </b>${"item.sections_count"}`
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Skeleton>
|
||||||
|
</List.Item>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Layout>
|
||||||
|
</Content>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
else
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Content
|
||||||
|
className="site-layout-background"
|
||||||
|
style={{
|
||||||
|
padding: 24,
|
||||||
|
margin: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Layout className="createExamLayoutHeader">Batches</Layout>
|
||||||
|
<Layout className="createExamLayout">
|
||||||
|
<Empty />
|
||||||
|
</Layout>
|
||||||
|
</Content>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AssociateBatch;
|
||||||
|
|
@ -0,0 +1,164 @@
|
||||||
|
import React, { useRef, useState, useEffect } from 'react';
|
||||||
|
import { selectorService } from '../../services/selectorService';
|
||||||
|
import { Card, Layout, Switch, Typography, message } from 'antd';
|
||||||
|
import { LoadingOutlined } from '@ant-design/icons';
|
||||||
|
import "./AssociateExam.css";
|
||||||
|
import { useHistory } from 'react-router-dom';
|
||||||
|
|
||||||
|
const AssociateBatchNew = (props) => {
|
||||||
|
const history = useHistory();
|
||||||
|
const mounted = useRef(true);
|
||||||
|
const [aboutExam, setAboutExam] = useState({
|
||||||
|
start_date: new Date(),
|
||||||
|
end_date: new Date(),
|
||||||
|
exam_duration: "",
|
||||||
|
total_marks: "",
|
||||||
|
total_questions: "",
|
||||||
|
name: "",
|
||||||
|
id: -1,
|
||||||
|
});
|
||||||
|
const [pageState, setpageState] = useState({
|
||||||
|
loading: true,
|
||||||
|
buttonProcessing: false,
|
||||||
|
});
|
||||||
|
const [associatedBatches, setAssociatedBatches] = useState([]);
|
||||||
|
const [sourcePool, setPool] = useState([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (props.location && props.location.state && props.location.state.name) {
|
||||||
|
setAboutExam(prev => ({
|
||||||
|
...prev,
|
||||||
|
start_date: new Date(props.location.state.start_date),
|
||||||
|
end_date: new Date(props.location.state.end_date),
|
||||||
|
exam_duration: props.location.state.exam_duration,
|
||||||
|
total_marks: props.location.state.total_marks,
|
||||||
|
total_questions: props.location.state.total_questions,
|
||||||
|
name: props.location.state.name,
|
||||||
|
id: props.location.state.id,
|
||||||
|
}))
|
||||||
|
mounted.current = true;
|
||||||
|
fetchBatchesForCurrentClass();
|
||||||
|
fetchAssociatedBatches();
|
||||||
|
} else {
|
||||||
|
history.push("/");
|
||||||
|
}
|
||||||
|
return () => {
|
||||||
|
mounted.current = false;
|
||||||
|
}
|
||||||
|
}, [aboutExam.id]);
|
||||||
|
|
||||||
|
async function fetchBatchesForCurrentClass() {
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await selectorService.getAllBatch();
|
||||||
|
if (+res.status.code < 0) {
|
||||||
|
throw res.status.message[0];
|
||||||
|
}
|
||||||
|
if (mounted.current) {
|
||||||
|
setPool(res.result);
|
||||||
|
setpageState(prev => ({ ...prev, loading: false }));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
message.error("Something went wrong, please reload and try again");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchAssociatedBatches() {
|
||||||
|
try {
|
||||||
|
if(aboutExam.id < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const res = await selectorService.getExamBatch({id: aboutExam.id});
|
||||||
|
if (+res.status.code < 0) {
|
||||||
|
if(res.status.message[0] === "No Record(s) Found!") return;
|
||||||
|
throw res.status.message[0];
|
||||||
|
}
|
||||||
|
if (mounted.current) {
|
||||||
|
setAssociatedBatches(res.result.idList);
|
||||||
|
setpageState(prev => ({ ...prev, loading: false }));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
message.error("Something went wrong, please reload and try again");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pageState.loading) {
|
||||||
|
return <Layout><LoadingOutlined spin /></Layout>
|
||||||
|
}
|
||||||
|
|
||||||
|
function computeSwitchValue(id) {
|
||||||
|
return associatedBatches.find(item => (item === id));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function changeSwitch(value, batchID) {
|
||||||
|
setpageState(prev => ({...prev, buttonProcessing: true}));
|
||||||
|
try {
|
||||||
|
if(value) {
|
||||||
|
const res = await selectorService.addBatchToExam({id: aboutExam.id, idList: [batchID]});
|
||||||
|
if(+res.status.code < 0) {
|
||||||
|
throw res.status.message[0];
|
||||||
|
}
|
||||||
|
if (mounted.current) {
|
||||||
|
setAssociatedBatches(prev => ([...prev, batchID]));
|
||||||
|
setpageState(prev => ({...prev, buttonProcessing: false}));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const res = await selectorService.deleteExamBatch({id: aboutExam.id, idList: [batchID]});
|
||||||
|
if(+res.status.code < 0) {
|
||||||
|
throw res.status.message[0];
|
||||||
|
}
|
||||||
|
if(mounted.current) {
|
||||||
|
const array = associatedBatches.filter(item => ( item !== batchID ));
|
||||||
|
setAssociatedBatches(() => (array));
|
||||||
|
setpageState(prev => ({...prev, buttonProcessing: false}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
message.error("Something went wrong, please reload and try again");
|
||||||
|
mounted.current && setpageState(prev => ({...prev, buttonProcessing: false}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Layout prefixCls='associate-exam'>
|
||||||
|
<Layout.Content prefixCls='container'>
|
||||||
|
<div className='exam-info'>
|
||||||
|
<div className='exam-info-header'>
|
||||||
|
<Typography.Text strong>Exam Name</Typography.Text>
|
||||||
|
<Typography.Text strong>Duration</Typography.Text>
|
||||||
|
<Typography.Text strong>Start Date</Typography.Text>
|
||||||
|
<Typography.Text strong>End Date</Typography.Text>
|
||||||
|
</div>
|
||||||
|
<div className='selected-exam-info'>
|
||||||
|
<Typography.Text strong ellipsis>{aboutExam.name}</Typography.Text>
|
||||||
|
<span>{aboutExam.exam_duration}</span>
|
||||||
|
<span>{aboutExam.start_date.toLocaleDateString()} {aboutExam.start_date.toLocaleTimeString()}</span>
|
||||||
|
<span>{aboutExam.end_date.toLocaleDateString()} {aboutExam.end_date.toLocaleTimeString()}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Typography.Paragraph type='secondary'>Switch off/on the batches to (de-)associate above exam to them</Typography.Paragraph>
|
||||||
|
<div className='batch-pool'>
|
||||||
|
{sourcePool.map((batch, i) => {
|
||||||
|
return <div key={batch.id}>
|
||||||
|
<Card size={"small"} title={<>Batch Name - <Typography.Text strong>{batch.name}</Typography.Text></>}>
|
||||||
|
<Typography.Text>No. of students - {batch.student_count}</Typography.Text>
|
||||||
|
<Typography.Text>
|
||||||
|
<Switch
|
||||||
|
onChange={(e) => changeSwitch(e, batch.id)}
|
||||||
|
checked={computeSwitchValue(batch.id)? true : false}
|
||||||
|
loading={pageState.buttonProcessing}
|
||||||
|
/>
|
||||||
|
</Typography.Text>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</Layout.Content>
|
||||||
|
</Layout>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AssociateBatchNew;
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
.associate-exam .container {
|
||||||
|
padding-inline: 32px;
|
||||||
|
padding-block: 1rem;
|
||||||
|
}
|
||||||
|
.associate-exam .exam-info {
|
||||||
|
--grid-column: 250px 100px 1fr 1fr;
|
||||||
|
--minimum-width: 700px;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
border: 2px solid var(--color-accent-1-40);
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
overflow-y: auto;
|
||||||
|
-webkit-border-radius: 0.5rem;
|
||||||
|
-moz-border-radius: 0.5rem;
|
||||||
|
-ms-border-radius: 0.5rem;
|
||||||
|
-o-border-radius: 0.5rem;
|
||||||
|
}
|
||||||
|
.associate-exam .exam-info-header {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: var(--grid-column);
|
||||||
|
grid-template-rows: 1fr;
|
||||||
|
background-color: var(--background-color-custom-1);
|
||||||
|
padding: 1rem;
|
||||||
|
min-width: var(--minimum-width);
|
||||||
|
}
|
||||||
|
.associate-exam .selected-exam-info {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: var(--grid-column);
|
||||||
|
grid-template-rows: 1fr;
|
||||||
|
padding: 1rem;
|
||||||
|
min-width: var(--minimum-width);
|
||||||
|
}
|
||||||
|
|
||||||
|
.associate-exam .batch-pool {
|
||||||
|
margin-top: 1rem;
|
||||||
|
display: grid;
|
||||||
|
grid: auto-flow/repeat(3, minmax(0, 1fr));
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
.associate-exam .batch-pool .ant-card-head {
|
||||||
|
background-color: var(--color-accent-1-40);
|
||||||
|
/* color: var(--color-alternate); */
|
||||||
|
}
|
||||||
|
.associate-exam .batch-pool .ant-card-body {
|
||||||
|
display: grid;
|
||||||
|
grid: 1fr/ 0 1fr auto;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,213 @@
|
||||||
|
import { Steps, Button, message, Layout } from 'antd';
|
||||||
|
// import { Siderc } from '../Main/Siderc';
|
||||||
|
import React from 'react';
|
||||||
|
import { authenticationService } from '../../_services';
|
||||||
|
// import { isEqual } from 'lodash'
|
||||||
|
// import { Headerc } from '../Main/Headerc';
|
||||||
|
import CreateExamName from './CreateExamName'
|
||||||
|
import SelectSubjects from './SelectSubjects';
|
||||||
|
import ArrangeSections from './ArrangeSections';
|
||||||
|
import ManageExam from './ManageExam';
|
||||||
|
import history from '../../history';
|
||||||
|
import { selectorService } from '../../services/selectorService';
|
||||||
|
import { useLocation } from 'react-router-dom';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
const { Content } = Layout;
|
||||||
|
|
||||||
|
const { Step } = Steps;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function CreateExamcopy(props) {
|
||||||
|
const [state, setState] = React.useState({
|
||||||
|
current: 0,
|
||||||
|
currentUser: authenticationService.currentUserValue,
|
||||||
|
attemptsAllowed: null,
|
||||||
|
authorId: '',
|
||||||
|
description: null,
|
||||||
|
endDate: null,
|
||||||
|
examStatus: "",
|
||||||
|
examTypeId: 1,
|
||||||
|
id: '',
|
||||||
|
image: '',
|
||||||
|
instituteId: '',
|
||||||
|
instruction: '',
|
||||||
|
isActive: false,
|
||||||
|
languageId: '',
|
||||||
|
name: "",
|
||||||
|
sections: [],
|
||||||
|
startDate: null,
|
||||||
|
rearrange: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const location = useLocation();
|
||||||
|
const { t, i18n } = useTranslation();
|
||||||
|
|
||||||
|
const ManageExamRef = React.createRef();
|
||||||
|
const steps = [
|
||||||
|
{
|
||||||
|
title: <>{t('step-1')}</>,
|
||||||
|
description: <>{t('write-exam')}</>,
|
||||||
|
content: <CreateExamName selectorState={state} parentCallback={callbackFunctionStep1} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: <>{t('step-2')}</>,
|
||||||
|
description: <>{t('select-sections')}</>,
|
||||||
|
content: <SelectSubjects selectorState={state} parentCallback={(childData) => { callbackFunctionStep2(childData) }} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: <>{t('step-3')}</>,
|
||||||
|
description: <>{t('arrange-sections')}</>,
|
||||||
|
content: <ArrangeSections selectorState={state} parentCallback={(childData, e) => { callbackFunctionStep3(childData) }} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: <>{t('step-4')}</>,
|
||||||
|
description: <>{t('manage-exam')}</>,
|
||||||
|
content: <ManageExam ref={ManageExamRef} selectorState={state} parentCallback={(childData) => { callbackFunctionStep4(childData) }} />,
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
function next() {
|
||||||
|
const current = state.current + 1;
|
||||||
|
setState({ current });
|
||||||
|
}
|
||||||
|
|
||||||
|
function prev() {
|
||||||
|
const current = state.current - 1;
|
||||||
|
setState({ current });
|
||||||
|
}
|
||||||
|
|
||||||
|
function callbackFunctionStep1(childData) {
|
||||||
|
// console.log("Parent recieved Selector Data: " + childData);
|
||||||
|
setState(prev => ({...prev,
|
||||||
|
current: childData.current,
|
||||||
|
authorId: childData.authorId,
|
||||||
|
examStatus: childData.examStatus,
|
||||||
|
examTypeId: childData.examTypeId,
|
||||||
|
id: childData.id,
|
||||||
|
image: childData.image,
|
||||||
|
instituteId: childData.instituteId,
|
||||||
|
isActive: childData.isActive,
|
||||||
|
languageId: childData.languageId,
|
||||||
|
name: childData.name
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
function callbackFunctionStep2(childData) {
|
||||||
|
// console.log("Parent recieved Selector Data: " + JSON.stringify(childData), childData.sections);
|
||||||
|
setState(prev => ({...prev,
|
||||||
|
image: childData.image,
|
||||||
|
current: childData.current,
|
||||||
|
sections: childData.sections,
|
||||||
|
name: childData.name,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
function callbackFunctionStep3(childData) {
|
||||||
|
// console.log("Parent recieved Selector Data: " + JSON.stringify(childData));
|
||||||
|
setState(prev => ({...prev,
|
||||||
|
current: childData.current,
|
||||||
|
sections: childData.sections,
|
||||||
|
rearrange: childData.rearrange,
|
||||||
|
name: childData.exam
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
// console.log(location);
|
||||||
|
let id = location.state && location.state.id;
|
||||||
|
let current = location.state && location.state.current;
|
||||||
|
// console.log(current, id);
|
||||||
|
if (id) {
|
||||||
|
selectorService.getExamDetails(id).then((res) => {
|
||||||
|
// console.log(res);
|
||||||
|
setState(prev => ({...prev,
|
||||||
|
attemptsAllowed: res.result.attempts_allowed,
|
||||||
|
description: res.result.description,
|
||||||
|
endDate: res.result.end_date,
|
||||||
|
examStatus: res.result.exam_status,
|
||||||
|
examTypeId: res.result.examtype_id,
|
||||||
|
id: res.result.id,
|
||||||
|
image: `https://s3bucket-for-oa.s3.ap-south-1.amazonaws.com/1/exams/${id}.png`,
|
||||||
|
instituteId: res.result.institute_id,
|
||||||
|
instruction: res.result.instruction,
|
||||||
|
isActive: res.result.isActive,
|
||||||
|
languageId: res.result.language_id,
|
||||||
|
name: res.result.name,
|
||||||
|
sections: res.result.sections,
|
||||||
|
startDate: res.result.start_date,
|
||||||
|
current: current,
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.log(err);
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
function callbackFunctionStep4 () {
|
||||||
|
setState(prev => ({...prev,
|
||||||
|
current: 0,
|
||||||
|
currentUser: authenticationService.currentUserValue,
|
||||||
|
attemptsAllowed: null,
|
||||||
|
authorId: '',
|
||||||
|
description: null,
|
||||||
|
endDate: null,
|
||||||
|
examStatus: "",
|
||||||
|
examTypeId: 1,
|
||||||
|
id: '',
|
||||||
|
image: '',
|
||||||
|
instituteId: '',
|
||||||
|
instruction: '',
|
||||||
|
isActive: false,
|
||||||
|
languageId: '',
|
||||||
|
name: "",
|
||||||
|
sections: [],
|
||||||
|
startDate: null,
|
||||||
|
rearrange: true,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
function handlePublishExam(e) {
|
||||||
|
ManageExamRef.current.handleSubmitExam();
|
||||||
|
history.push('/dashboard');
|
||||||
|
}
|
||||||
|
|
||||||
|
const { current } = state;
|
||||||
|
|
||||||
|
return (
|
||||||
|
// <Layout>
|
||||||
|
// <Siderc />
|
||||||
|
// <Layout>
|
||||||
|
// <Headerc />
|
||||||
|
<Content
|
||||||
|
className="site-layout-background exam-creation" style={{ padding: 24, margin: 0 }}>
|
||||||
|
{/* <Layout className='createExamLayoutHeader'>Create Exam</Layout> */}
|
||||||
|
<Layout className='createExamLayout'>
|
||||||
|
<Steps current={current}>
|
||||||
|
{steps.map(item => (
|
||||||
|
<Step key={item.title} title={item.title} description={item.description} />
|
||||||
|
))}
|
||||||
|
</Steps>
|
||||||
|
|
||||||
|
<div className="steps-content" >{steps[current].content}</div>
|
||||||
|
<div className="steps-action">
|
||||||
|
{current < steps.length - 1}
|
||||||
|
{current === steps.length - 1 && (
|
||||||
|
<div data-need-margin={"false"} className="button-container">
|
||||||
|
<Button type="primary" onClick={handlePublishExam}>
|
||||||
|
{t('create-exam-btn')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Layout>
|
||||||
|
</Content>
|
||||||
|
// </Layout>
|
||||||
|
// </Layout>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CreateExamcopy;
|
||||||
|
|
@ -0,0 +1,72 @@
|
||||||
|
.exam-creation {
|
||||||
|
background-color: var(--background-color-custom-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.createExamName input[type=text] {
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.createExamName button {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.createExamName {
|
||||||
|
/* border-style: solid;
|
||||||
|
border-width: 1px;
|
||||||
|
border-color: rgb(231, 230, 230);
|
||||||
|
padding: 20px; */
|
||||||
|
margin-top: 40px;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 200px 1fr;
|
||||||
|
grid-template-rows: auto;
|
||||||
|
align-items: center;
|
||||||
|
row-gap: 2rem;
|
||||||
|
margin-bottom: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.createExamName .custom-dropdown-link {
|
||||||
|
color: #000;
|
||||||
|
border: 1px solid var(--font-color);
|
||||||
|
padding: 4px 8px;
|
||||||
|
background-color: var(--background-color-custom-1);
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 150px 1fr;
|
||||||
|
grid-template-rows: 1fr;
|
||||||
|
align-items: center;
|
||||||
|
width: fit-content;
|
||||||
|
border-radius: 2px;
|
||||||
|
-webkit-border-radius: 2px;
|
||||||
|
-moz-border-radius: 2px;
|
||||||
|
-ms-border-radius: 2px;
|
||||||
|
-o-border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.createExamLayoutHeader {
|
||||||
|
display: flex;
|
||||||
|
flex: auto;
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: 10vh;
|
||||||
|
/* background: #fafafa !important; */
|
||||||
|
border-style: solid;
|
||||||
|
border-width: 1px;
|
||||||
|
border-color: rgb(231, 230, 230);
|
||||||
|
padding: 10px;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.exam-creation .createExamLayout {
|
||||||
|
display: flex;
|
||||||
|
flex: auto;
|
||||||
|
flex-direction: column;
|
||||||
|
background: unset;
|
||||||
|
/* border-style: solid; */
|
||||||
|
/* border-width: 1px; */
|
||||||
|
/* border-color: rgb(231, 230, 230); */
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-layout-background {
|
||||||
|
/* background: #fafafa !important; */
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,98 @@
|
||||||
|
import React from 'react';
|
||||||
|
import './CreateExamName.css';
|
||||||
|
import { Input, Button, Form, message, Typography } from 'antd';
|
||||||
|
import { withTranslation } from 'react-i18next';
|
||||||
|
import { authenticationService } from '../../_services';
|
||||||
|
import { selectorService } from '../../services/selectorService';
|
||||||
|
import empty from '../EmptyImage';
|
||||||
|
|
||||||
|
|
||||||
|
class CreateExamName extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
currentUser: authenticationService.currentUserValue,
|
||||||
|
examName: props.selectorState.name,
|
||||||
|
image: empty(),
|
||||||
|
update: false,
|
||||||
|
examId: ''
|
||||||
|
};
|
||||||
|
this.handleCreateExamName = this.handleCreateExamName.bind(this);
|
||||||
|
this.onTextChange = this.onTextChange.bind(this);
|
||||||
|
this.sendData = this.sendData.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
onTextChange = (event) => {
|
||||||
|
this.setState({
|
||||||
|
examName: event.target.value,
|
||||||
|
update: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleCreateExamName() {
|
||||||
|
if (!this.state.examName || this.state.examName === '') {
|
||||||
|
message.warning('Please enter a valid exam name');
|
||||||
|
} else {
|
||||||
|
if (!this.state.update) this.state.examName = this.props.selectorState.name;
|
||||||
|
let json = {
|
||||||
|
name: this.state.examName,
|
||||||
|
image: this.state.image,
|
||||||
|
examtype_id: '1'
|
||||||
|
}
|
||||||
|
message.loading({ content: 'Creating Exam Name...' });
|
||||||
|
selectorService.createExam(json).then(data => {
|
||||||
|
if(+data.status.code < 0) {
|
||||||
|
throw data.status.message[0];
|
||||||
|
}
|
||||||
|
let result = data.result;
|
||||||
|
// console.log(result);
|
||||||
|
result.current = 1;
|
||||||
|
result.image = this.state.image;
|
||||||
|
message.success({ content: 'Exam Name Created!', duration: 3 });
|
||||||
|
this.sendData(result);
|
||||||
|
}).catch(err => {
|
||||||
|
console.log(err);
|
||||||
|
message.error("Something went wrong, please try again");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sendData = (result) => {
|
||||||
|
this.props.parentCallback(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const placeholderTrans = this.props.t('enter-exam-name');
|
||||||
|
return (<>
|
||||||
|
<div className='createExamName'>
|
||||||
|
{/* <Form className="exam-form"> */}
|
||||||
|
<Typography.Text strong style={{ color: 'var(--font-color-primary)' }}>{this.props.t('exam-name')}</Typography.Text>
|
||||||
|
{/* <br /> */}
|
||||||
|
{/* <Space> */}
|
||||||
|
<Input style={{width: 250}} placeholder={placeholderTrans} value={this.state.examName} onChange={e => { this.onTextChange(e) }} />
|
||||||
|
{/* <Upload >
|
||||||
|
<Button type="primary" icon={<UploadOutlined />}>Upload</Button>
|
||||||
|
</Upload> */}
|
||||||
|
{/* </Space> */}
|
||||||
|
{/* <br /> */}
|
||||||
|
{/* </Form> */}
|
||||||
|
</div>
|
||||||
|
<div className="button-container" data-need-margin={"false"}>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
onClick={(e) => {
|
||||||
|
// console.log("inside");
|
||||||
|
this.handleCreateExamName();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Continue
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default withTranslation()(CreateExamName);
|
||||||
|
|
@ -0,0 +1,370 @@
|
||||||
|
import {
|
||||||
|
Steps,
|
||||||
|
Button,
|
||||||
|
message,
|
||||||
|
Layout,
|
||||||
|
List,
|
||||||
|
Avatar,
|
||||||
|
Skeleton,
|
||||||
|
Empty,
|
||||||
|
Image,
|
||||||
|
notification,
|
||||||
|
} from "antd";
|
||||||
|
import "./DraftExam.css";
|
||||||
|
import { Siderc } from "../Main/Siderc";
|
||||||
|
import React from "react";
|
||||||
|
import { Headerc } from "../Main/Headerc";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
import history from "../../history";
|
||||||
|
import { selectorService } from "../../services/selectorService";
|
||||||
|
import parse from "html-react-parser";
|
||||||
|
import { S3_BUCKET_EXAM_PREFIX } from "../../_services/config";
|
||||||
|
|
||||||
|
const { Content } = Layout;
|
||||||
|
|
||||||
|
class DraftExam extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
data: [],
|
||||||
|
total_count: 0,
|
||||||
|
total_pages: 0,
|
||||||
|
processing: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
componentDidMount() {
|
||||||
|
selectorService
|
||||||
|
.draftExam({
|
||||||
|
pageSize: 10,
|
||||||
|
pageNumber: 1,
|
||||||
|
classId: parseInt(sessionStorage.getItem("currentClass")),
|
||||||
|
})
|
||||||
|
.then((res) => {
|
||||||
|
// console.log(res);
|
||||||
|
if(res.status.code === "-1") {
|
||||||
|
throw "Something went wrong, please reload."
|
||||||
|
}
|
||||||
|
this.setState({
|
||||||
|
data: res.result.exams,
|
||||||
|
total_count: res.result.total_count,
|
||||||
|
total_pages: res.result.total_pages,
|
||||||
|
processing: false,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.log(err);
|
||||||
|
notification.error({
|
||||||
|
message: "Error!!!",
|
||||||
|
description: err,
|
||||||
|
duration: 10
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleChange = (pageNumber, pageSize) => {
|
||||||
|
selectorService
|
||||||
|
.draftExam({ pageNumber: pageNumber, pageSize: pageSize, classId: 1 })
|
||||||
|
.then((res) => {
|
||||||
|
this.setState({
|
||||||
|
data: res.result.exams,
|
||||||
|
total_count: res.result.total_count,
|
||||||
|
total_pages: res.result.total_pages,
|
||||||
|
processing: false,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.log(err);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
if (this.state.data.length !== 0)
|
||||||
|
return (
|
||||||
|
<Layout>
|
||||||
|
<Siderc />
|
||||||
|
<Layout>
|
||||||
|
<Headerc />
|
||||||
|
<Content
|
||||||
|
className="site-layout-background"
|
||||||
|
style={{
|
||||||
|
padding: 24,
|
||||||
|
margin: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Layout className="createExamLayoutHeader">Draft Exam</Layout>
|
||||||
|
<Layout className="createExamLayout">
|
||||||
|
<List
|
||||||
|
itemLayout="horizontal"
|
||||||
|
dataSource={this.state.data}
|
||||||
|
pagination={{
|
||||||
|
onChange: (page, pageSize) => {
|
||||||
|
this.handleChange(page, pageSize);
|
||||||
|
},
|
||||||
|
|
||||||
|
total: this.state.total_count,
|
||||||
|
}}
|
||||||
|
renderItem={(item, index) => {
|
||||||
|
let step = 2;
|
||||||
|
if (item.sections_count >= 1) step = 3;
|
||||||
|
if (item.sections_status !== "DRAFT") step = 4;
|
||||||
|
let currentTime = new Date();
|
||||||
|
let loginTime = new Date(item.updated_on);
|
||||||
|
const diff = currentTime.getTime() - loginTime.getTime();
|
||||||
|
// console.log(diff,loginTime,currentTime,item.updated_on);
|
||||||
|
var years = Math.floor(
|
||||||
|
diff / (1000 * 60 * 60 * 24 * 30 * 12)
|
||||||
|
);
|
||||||
|
var months = Math.floor(
|
||||||
|
(diff % (1000 * 60 * 60 * 24 * 30 * 12)) /
|
||||||
|
(1000 * 60 * 60 * 24 * 30)
|
||||||
|
);
|
||||||
|
var days = Math.floor(
|
||||||
|
(diff % (1000 * 60 * 60 * 24 * 30)) /
|
||||||
|
(1000 * 60 * 60 * 24)
|
||||||
|
);
|
||||||
|
|
||||||
|
var hours = Math.floor(
|
||||||
|
(diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)
|
||||||
|
);
|
||||||
|
var minutes = Math.floor((diff % 3.6e6) / 6e4);
|
||||||
|
let updatedOn =
|
||||||
|
years > 0
|
||||||
|
? `${years} year(s)/${months} months`
|
||||||
|
: months > 0
|
||||||
|
? `${months} month(s)/${days} days`
|
||||||
|
: days > 0
|
||||||
|
? `${days} day(s)/${hours} hours`
|
||||||
|
: minutes > 0
|
||||||
|
? `${hours} hour(s)${minutes}minutes(s)`
|
||||||
|
: minutes + "minutes";
|
||||||
|
return (
|
||||||
|
<List.Item
|
||||||
|
className="DraftExam"
|
||||||
|
key={index}
|
||||||
|
actions={[
|
||||||
|
<Button
|
||||||
|
onClick={(e, newValue) => {
|
||||||
|
selectorService
|
||||||
|
.deleteExam(item.id)
|
||||||
|
.then((res) => {
|
||||||
|
console.log(res);
|
||||||
|
if (res.status.code === "-1")
|
||||||
|
message.error("Some error occured");
|
||||||
|
else {
|
||||||
|
let data = [...this.state.data];
|
||||||
|
data = data.filter((x) => x.id !== item.id);
|
||||||
|
this.setState({ data }, () => {
|
||||||
|
message.success("Deleted Successfully");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.log(err);
|
||||||
|
message.error("Some error occured");
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
type="primary"
|
||||||
|
>
|
||||||
|
Delete Exam
|
||||||
|
</Button>,
|
||||||
|
<Button
|
||||||
|
onClick={(e, newValue) => {
|
||||||
|
console.log(item, e);
|
||||||
|
}}
|
||||||
|
type="primary"
|
||||||
|
>
|
||||||
|
<Link
|
||||||
|
to={{
|
||||||
|
pathname: "/createExam",
|
||||||
|
state: { id: item.id, current: step - 1 },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Recreate Exam
|
||||||
|
</Link>
|
||||||
|
</Button>,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<List.Item.Meta
|
||||||
|
avatar={
|
||||||
|
<Image
|
||||||
|
// src="https://cdn.xl.thumbs.canstockphoto.com/exam-written-on-a-chalkboard-books-pencils-and-an-apple-on-foreground-picture_csp2468961.jpg"
|
||||||
|
src={`${S3_BUCKET_EXAM_PREFIX+item.id}.png`}
|
||||||
|
fallback="https://cdn.xl.thumbs.canstockphoto.com/exam-written-on-a-chalkboard-books-pencils-and-an-apple-on-foreground-picture_csp2468961.jpg"
|
||||||
|
// size="large"
|
||||||
|
// shape="square"
|
||||||
|
width={100}
|
||||||
|
height={100}
|
||||||
|
className="DraftExamImage"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
size="large"
|
||||||
|
bordered="true"
|
||||||
|
title={parse(`<b>${item.name}</b>`)}
|
||||||
|
description={
|
||||||
|
<>
|
||||||
|
<div className="MyExamDiv">
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Progress</th>
|
||||||
|
<th>Last Update</th>
|
||||||
|
<th>Author Name</th>
|
||||||
|
<th>Sections Count</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>{step}</td>
|
||||||
|
<td>{updatedOn}</td>
|
||||||
|
<td>{item.author_name}</td>
|
||||||
|
<td>{item.sections_count}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
// description={parse(
|
||||||
|
// `<b>Progress - Step</b> ${step} <b>Updated At:</b> ${updatedOn} <b>Author Name:</b> ${item.author_name} <b>Sections Count: </b>${item.sections_count}`
|
||||||
|
// )}
|
||||||
|
/>
|
||||||
|
</List.Item>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Layout>
|
||||||
|
</Content>
|
||||||
|
</Layout>
|
||||||
|
</Layout>
|
||||||
|
);
|
||||||
|
else if (this.state.processing)
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Layout>
|
||||||
|
<Siderc />
|
||||||
|
<Layout>
|
||||||
|
<Headerc />
|
||||||
|
|
||||||
|
<Content
|
||||||
|
className="site-layout-background"
|
||||||
|
style={{
|
||||||
|
padding: 24,
|
||||||
|
margin: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Layout className="createExamLayoutHeader">Draft Exam</Layout>
|
||||||
|
<Layout className="createExamLayout">
|
||||||
|
<List
|
||||||
|
itemLayout="horizontal"
|
||||||
|
dataSource={[{}, {}, {}, {}, {}, {}, {}, {}, {}, {}]}
|
||||||
|
pagination={{
|
||||||
|
onChange: (page, pageSize) => {
|
||||||
|
this.handleChange(page, pageSize);
|
||||||
|
},
|
||||||
|
|
||||||
|
total: this.state.total_count,
|
||||||
|
}}
|
||||||
|
renderItem={(item, index) => {
|
||||||
|
// let step = 2;
|
||||||
|
// if (item.sections_count >= 1) step = 3;
|
||||||
|
// if (item.sections_status !== "DRAFT") step = 4;
|
||||||
|
// let currentTime = new Date();
|
||||||
|
// let loginTime = new Date(item.updated_on);
|
||||||
|
// const diff = currentTime.getTime() - loginTime.getTime();
|
||||||
|
// // console.log(diff,loginTime,currentTime,item.updated_on);
|
||||||
|
// var years = Math.floor(diff / (1000 * 60 * 60 * 24 * 30 * 12));
|
||||||
|
// var months = Math.floor(
|
||||||
|
// (diff % (1000 * 60 * 60 * 24 * 30 * 12)) / (1000 * 60 * 60 * 24 * 30),
|
||||||
|
// );
|
||||||
|
// var days = Math.floor(
|
||||||
|
// (diff % (1000 * 60 * 60 * 24 * 30)) / (1000 * 60 * 60 * 24),
|
||||||
|
// );
|
||||||
|
|
||||||
|
// var hours = Math.floor(diff%(1000*60*60*24) / (1000 * 60 * 60));
|
||||||
|
// var minutes=Math.floor((diff % 3.6e+6) / 6e+4);
|
||||||
|
// let updatedOn=
|
||||||
|
// years > 0
|
||||||
|
// ? `${years} year(s)/${months} months`
|
||||||
|
// : months > 0
|
||||||
|
// ? `${months} month(s)/${days} days`
|
||||||
|
// : days > 0
|
||||||
|
// ? `${days} day(s)/${hours} hours`
|
||||||
|
// :minutes>0? `${hours} hour(s)${minutes}minutes(s)`:minutes+'minutes';
|
||||||
|
return (
|
||||||
|
<List.Item
|
||||||
|
className="DraftExam"
|
||||||
|
key={index}
|
||||||
|
// actions={[
|
||||||
|
// <Button
|
||||||
|
// onClick={(e, newValue) => {
|
||||||
|
// console.log(item, e);
|
||||||
|
// }}
|
||||||
|
// type="primary"
|
||||||
|
// >
|
||||||
|
// <Link
|
||||||
|
// to={{
|
||||||
|
// pathname: "/createExam",
|
||||||
|
// state: { id: item.id, current: step - 1 },
|
||||||
|
// }}
|
||||||
|
// >
|
||||||
|
// Recreate Exam
|
||||||
|
// </Link>
|
||||||
|
// </Button>,
|
||||||
|
// ]}
|
||||||
|
>
|
||||||
|
<Skeleton>
|
||||||
|
<List.Item.Meta
|
||||||
|
avatar={
|
||||||
|
<Avatar
|
||||||
|
src="https://cdn.xl.thumbs.canstockphoto.com/exam-written-on-a-chalkboard-books-pencils-and-an-apple-on-foreground-picture_csp2468961.jpg"
|
||||||
|
size="large"
|
||||||
|
shape="square"
|
||||||
|
className="DraftExamImage"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
size="large"
|
||||||
|
bordered="true"
|
||||||
|
title={parse(`<b>${"item.name"}</b>`)}
|
||||||
|
description={parse(
|
||||||
|
`<b>Progress - Step</b> ${"step"} <b>Updated At:</b> ${"updatedOn"} <b>Author Name:</b> ${"item.author_name"} <b>Sections Count: </b>${"item.sections_count"}`
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Skeleton>
|
||||||
|
</List.Item>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Layout>
|
||||||
|
</Content>
|
||||||
|
</Layout>
|
||||||
|
</Layout>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
else
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Layout>
|
||||||
|
<Siderc />
|
||||||
|
<Layout>
|
||||||
|
<Headerc />
|
||||||
|
|
||||||
|
<Content
|
||||||
|
className="site-layout-background"
|
||||||
|
style={{
|
||||||
|
padding: 24,
|
||||||
|
margin: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Layout className="createExamLayoutHeader">Draft Exam</Layout>
|
||||||
|
<Layout className="createExamLayout">
|
||||||
|
<Empty />
|
||||||
|
</Layout>
|
||||||
|
</Content>
|
||||||
|
</Layout>
|
||||||
|
</Layout>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DraftExam;
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
li.DraftExam{
|
||||||
|
-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;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.DraftExamImage{
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
line-height: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.DraftExamImage img{
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
div.MyExamDiv table tr> th{
|
||||||
|
border:none;
|
||||||
|
padding: 0 0 0 20px;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.MyExamDiv table tr> td{
|
||||||
|
border:none;
|
||||||
|
padding: 0 0 0 20px;
|
||||||
|
text-align: center;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,177 @@
|
||||||
|
import { Button, Empty, Image, Layout, Pagination, Radio, Skeleton, Tooltip, Typography, message } from 'antd';
|
||||||
|
import React, { useState, useEffect, useRef } from 'react';
|
||||||
|
import './ExamModule.css';
|
||||||
|
import { selectorService } from '../../services/selectorService';
|
||||||
|
import { S3_BUCKET_EXAM_PREFIX } from '../../_services/config';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import EmptyImage from '../EmptyImage';
|
||||||
|
|
||||||
|
const DraftExamNew = () => {
|
||||||
|
const mounted = useRef();
|
||||||
|
const [buttonState, setButtonState] = useState(true);
|
||||||
|
const [pageLoading, setPageLoadingState] = useState(true);
|
||||||
|
const [pageState, setPageValues] = useState({
|
||||||
|
total_count: -1,
|
||||||
|
total_pages: -1,
|
||||||
|
pageNumber: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
type: "subject",
|
||||||
|
});
|
||||||
|
const [pageData, setPageData] = useState([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
mounted.current = true;
|
||||||
|
// someFetchingFunctionHere
|
||||||
|
if (pageState.type === "subject") {
|
||||||
|
fetchSubjectDraftExam();
|
||||||
|
}
|
||||||
|
return () => {
|
||||||
|
// cleanup
|
||||||
|
mounted.current = false;
|
||||||
|
};
|
||||||
|
}, [pageState.pageNumber, pageState.type]);
|
||||||
|
|
||||||
|
async function fetchSubjectDraftExam() {
|
||||||
|
if (!pageLoading) {
|
||||||
|
setPageLoadingState(true);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const res = await selectorService.draftExam(pageState);
|
||||||
|
process.env.NODE_ENV === "development" && console.log(res);
|
||||||
|
if(+res.status.code < 0) {
|
||||||
|
if(res.status.message[0] === "No Record(s) Found!") {
|
||||||
|
mounted.current && setPageValues(prev => ({
|
||||||
|
...prev,
|
||||||
|
total_count: 0,
|
||||||
|
total_pages: 0,
|
||||||
|
}));
|
||||||
|
mounted.current && setPageData([]);
|
||||||
|
setPageLoadingState(false);
|
||||||
|
setButtonState(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else throw res.status.message[0];
|
||||||
|
}
|
||||||
|
mounted.current && res && setPageValues(prev => ({
|
||||||
|
...prev,
|
||||||
|
total_count: res.result.total_count,
|
||||||
|
total_pages: res.result.total_pages,
|
||||||
|
}));
|
||||||
|
mounted.current && res && setPageData(prev => (
|
||||||
|
[...res.result.exams]
|
||||||
|
));
|
||||||
|
setButtonState(false);
|
||||||
|
setPageLoadingState(false);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
setButtonState(false);
|
||||||
|
setPageLoadingState(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onChangePageNumber = (page) => {
|
||||||
|
setPageValues(prev => ({ ...prev, pageNumber: page }));
|
||||||
|
};
|
||||||
|
|
||||||
|
function onClickDeleteButton(id) {
|
||||||
|
setButtonState(true);
|
||||||
|
selectorService.deleteExam(id).then((res) => {
|
||||||
|
if (+res.status.code < 0)
|
||||||
|
throw res.status.message[0];
|
||||||
|
else {
|
||||||
|
setPageLoadingState(true);
|
||||||
|
if (pageState.type === "subject") {
|
||||||
|
fetchSubjectDraftExam();
|
||||||
|
}
|
||||||
|
message.success("Deleted Successfully");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
process.env.NODE_ENV === "development" && console.log(err);
|
||||||
|
message.error("Some error occured");
|
||||||
|
setButtonState(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Layout className=''>
|
||||||
|
<section className='practice-container exam-container'>
|
||||||
|
<div className='practice-type-selector'>
|
||||||
|
<Radio.Group buttonStyle={"solid"} size={"large"}
|
||||||
|
style={{ display: 'grid', gridTemplateColumns: '1fr', width: '100%' }}
|
||||||
|
defaultValue="subject"
|
||||||
|
>
|
||||||
|
<Radio.Button value="subject">Draft Exams</Radio.Button>
|
||||||
|
</Radio.Group>
|
||||||
|
</div>
|
||||||
|
<div className='table-look-up--draft'>
|
||||||
|
<div className='draft-table-header'>
|
||||||
|
<Typography.Text strong>Image</Typography.Text>
|
||||||
|
<Typography.Text strong>Exam Name</Typography.Text>
|
||||||
|
<Typography.Text strong>Created</Typography.Text>
|
||||||
|
<Typography.Text strong>Last updated</Typography.Text>
|
||||||
|
<Typography.Text strong>Author</Typography.Text>
|
||||||
|
<Typography.Text strong>Section Count</Typography.Text>
|
||||||
|
<Typography.Text strong>Actions</Typography.Text>
|
||||||
|
</div>
|
||||||
|
<div className='draft-table-data'>
|
||||||
|
{pageLoading ? [0, 1, 2].map((index) => (<div key={index}>
|
||||||
|
<Skeleton.Avatar size={"large"} shape={'square'} active={true} />
|
||||||
|
<Skeleton avatar={false} title={false} paragraph={{ rows: 1, width: 120 }} active={true} />
|
||||||
|
<Skeleton avatar={false} title={false} paragraph={{ rows: 1, width: 80 }} active={true} />
|
||||||
|
<Skeleton avatar={false} title={false} paragraph={{ rows: 1, width: 80 }} active={true} />
|
||||||
|
<Skeleton avatar={false} title={false} paragraph={{ rows: 1, width: 60 }} active={true} />
|
||||||
|
<Skeleton avatar={false} title={false} paragraph={{ rows: 1, width: 20 }} active={true} />
|
||||||
|
<span><Skeleton.Button size='small' /> <Skeleton.Button size='small' /></span>
|
||||||
|
</div>))
|
||||||
|
: pageData.length > 0? pageData.map(data => {
|
||||||
|
let step = 2;
|
||||||
|
if (data.sections_count >= 1) step = 3;
|
||||||
|
if (data.sections_status !== "DRAFT") step = 4;
|
||||||
|
const createdDate = new Date(data.created_on);
|
||||||
|
const updatedDate = new Date(data.updated_on);
|
||||||
|
return <div key={data.id}>
|
||||||
|
<span><Image
|
||||||
|
src={`${S3_BUCKET_EXAM_PREFIX + data.id}.png`}
|
||||||
|
fallback={EmptyImage()}
|
||||||
|
width={70}
|
||||||
|
height={70}
|
||||||
|
className="DraftExamImage"
|
||||||
|
/></span>
|
||||||
|
<span><Tooltip title={data.name}>
|
||||||
|
<Typography.Text ellipsis style={{ width: 120 }}>{data.name}</Typography.Text>
|
||||||
|
</Tooltip></span>
|
||||||
|
<span>
|
||||||
|
<p className='mb-0-0'>{createdDate.toLocaleDateString()}</p>
|
||||||
|
<p className='mb-0-0'>{createdDate.toLocaleTimeString('hi', { hour12: true })}</p>
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
<p className='mb-0-0'>{updatedDate.toLocaleDateString()}</p>
|
||||||
|
<p className='mb-0-0'>{updatedDate.toLocaleTimeString('hi', { hour12: true })}</p>
|
||||||
|
</span>
|
||||||
|
<span>{data.author_name}</span>
|
||||||
|
<span>{data.sections_count}</span>
|
||||||
|
<span>
|
||||||
|
<Button loading={buttonState}>
|
||||||
|
<Link to={{
|
||||||
|
pathname: "/createExam",
|
||||||
|
state: { id: data.id, current: (step - 1) },
|
||||||
|
}}>
|
||||||
|
Edit
|
||||||
|
</Link>
|
||||||
|
</Button>
|
||||||
|
<Button loading={buttonState} danger onClick={() => onClickDeleteButton(data.id)}>Delete</Button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
}) : <Empty />}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<div className="button-container mb-1-0">
|
||||||
|
<Pagination defaultCurrent={1} current={pageState.pageNumber} total={pageState.total_count} onChange={onChangePageNumber} />
|
||||||
|
</div>
|
||||||
|
</Layout>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DraftExamNew;
|
||||||
|
|
@ -0,0 +1,70 @@
|
||||||
|
div.movable-card {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1ch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ReaarngeSubjects input[type=text]{
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ReaarngeSubjects button{
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ReaarngeSubjects{
|
||||||
|
border-style: none;
|
||||||
|
padding: 20px;
|
||||||
|
margin-top: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.ReaarngeSubjects {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ArrangeQuestions {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.createExamLayout .ReaarngeButtonTrue {
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 14px;
|
||||||
|
/* cursor: move; */
|
||||||
|
text-align: center;
|
||||||
|
text-decoration: none;
|
||||||
|
outline: none;
|
||||||
|
color: var(--font-color);
|
||||||
|
/* background-color: white; */
|
||||||
|
border-width: 1px;
|
||||||
|
border-style: solid;
|
||||||
|
border-color: #1890FF;
|
||||||
|
/* padding: 7px; */
|
||||||
|
/* margin: 5px 5px 0 0; */
|
||||||
|
}
|
||||||
|
|
||||||
|
.createExamLayout .sectionStatusPUB {
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 14px;
|
||||||
|
/* cursor: move; */
|
||||||
|
text-align: center;
|
||||||
|
text-decoration: none;
|
||||||
|
outline: none;
|
||||||
|
color: var(--success-button-font-color);
|
||||||
|
border: 1px solid #008633;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ArrangeSpan {
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 14px;
|
||||||
|
cursor: move;
|
||||||
|
/* text-align: center; */
|
||||||
|
text-decoration: none;
|
||||||
|
outline: none;
|
||||||
|
color: var(--font-color);
|
||||||
|
/* background-color: white; */
|
||||||
|
padding: 7px;
|
||||||
|
margin:5px;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,71 @@
|
||||||
|
import React, { useRef } from 'react';
|
||||||
|
import { useDrag, useDrop } from 'react-dnd';
|
||||||
|
import { ItemTypes } from './ItemTypes';
|
||||||
|
import './Card.css';
|
||||||
|
import { Button, Typography } from 'antd';
|
||||||
|
import { CheckCircleTwoTone, HolderOutlined } from '@ant-design/icons';
|
||||||
|
|
||||||
|
export const Card = ({ id, text, index, moveCard, clickButton, status }) => {
|
||||||
|
const ref = useRef(null);
|
||||||
|
const [, drop] = useDrop({
|
||||||
|
accept: ItemTypes.CARD,
|
||||||
|
hover(item, monitor) {
|
||||||
|
if (!ref.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const dragIndex = item.index;
|
||||||
|
const hoverIndex = index;
|
||||||
|
// Don't replace items with themselves
|
||||||
|
if (dragIndex === hoverIndex) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Determine rectangle on screen
|
||||||
|
const hoverBoundingRect = ref.current?.getBoundingClientRect();
|
||||||
|
// Get vertical middle
|
||||||
|
const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2;
|
||||||
|
// Determine mouse position
|
||||||
|
const clientOffset = monitor.getClientOffset();
|
||||||
|
// Get pixels to the top
|
||||||
|
const hoverClientY = clientOffset.y - hoverBoundingRect.top;
|
||||||
|
// Only perform the move when the mouse has crossed half of the items height
|
||||||
|
// When dragging downwards, only move when the cursor is below 50%
|
||||||
|
// When dragging upwards, only move when the cursor is above 50%
|
||||||
|
// Dragging downwards
|
||||||
|
if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Dragging upwards
|
||||||
|
if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Time to actually perform the action
|
||||||
|
moveCard(dragIndex, hoverIndex);
|
||||||
|
// Note: we're mutating the monitor item here!
|
||||||
|
// Generally it's better to avoid mutations,
|
||||||
|
// but it's good here for the sake of performance
|
||||||
|
// to avoid expensive index searches.
|
||||||
|
item.index = hoverIndex;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const [{ isDragging }, drag] = useDrag({
|
||||||
|
item: { type: ItemTypes.CARD, id, index },
|
||||||
|
collect: (monitor) => ({
|
||||||
|
isDragging: monitor.isDragging(),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
const opacity = isDragging ? 0.3 : 1;
|
||||||
|
drag(drop(ref));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='movable-card' key={id} ref={ref} style={{ opacity, cursor: 'move' }}>
|
||||||
|
<HolderOutlined key={id}/>
|
||||||
|
<Button
|
||||||
|
className={status !== 'DRAFT' ? 'sectionStatusPUB' : 'ReaarngeButtonTrue'}
|
||||||
|
size={"small"}
|
||||||
|
onClick={() => clickButton(index)}
|
||||||
|
>
|
||||||
|
{text}
|
||||||
|
</Button>
|
||||||
|
<span style={{display: status!== 'DRAFT'? 'inline-block': 'none'}}><CheckCircleTwoTone twoToneColor={"limegreen"}/> <Typography.Text>Questions Added</Typography.Text></span>
|
||||||
|
</div>);
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
|
||||||
|
.ReaarngeButtons input[type=text]{
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ReaarngeButtons button{
|
||||||
|
/* margin-top: 10px; */
|
||||||
|
}
|
||||||
|
|
||||||
|
.ReaarngeButtons{
|
||||||
|
border-style: none;
|
||||||
|
padding: 20px;
|
||||||
|
/* margin-top: 10px; */
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.ReaarngeButtons {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
row-gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ReaarngeButtons > div {
|
||||||
|
padding: 0.5rem;
|
||||||
|
background-color: #3b3cea0f;
|
||||||
|
border-radius: 0.2rem;
|
||||||
|
-webkit-border-radius: 0.2rem;
|
||||||
|
-moz-border-radius: 0.2rem;
|
||||||
|
-ms-border-radius: 0.2rem;
|
||||||
|
-o-border-radius: 0.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,110 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Card } from './Card';
|
||||||
|
import update from 'immutability-helper';
|
||||||
|
import './Container.css';
|
||||||
|
import { selectorService } from '../../../services/selectorService';
|
||||||
|
import { isEqual } from 'lodash'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class Container extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
cards: props.modelState,
|
||||||
|
styleClassName: "subjectButtonTrue",//this.prop.styleClassName
|
||||||
|
sections: [],
|
||||||
|
loaded: false,
|
||||||
|
};
|
||||||
|
this.sendData = this.sendData.bind(this);
|
||||||
|
this.renderCard = this.renderCard.bind(this);
|
||||||
|
this.moveCard = this.moveCard.bind(this);
|
||||||
|
this.sendClickData = this.sendClickData.bind(this);
|
||||||
|
}
|
||||||
|
sendData = () => {
|
||||||
|
this.props.parentCallback(this.state.cards);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.setState({
|
||||||
|
cards: this.props.modelState,
|
||||||
|
styleClassName: "subjectButtonTrue",//this.prop.styleClassName
|
||||||
|
sections: [],
|
||||||
|
loaded: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(prevprops) {
|
||||||
|
if (!isEqual(prevprops.modelState, this.props.modelState))
|
||||||
|
this.setState({ cards: this.props.modelState });
|
||||||
|
}
|
||||||
|
|
||||||
|
// componentDidMount(){
|
||||||
|
// selectorService.loadSections(this.props.id).then(data => {
|
||||||
|
// console.log('Success:', data);
|
||||||
|
// const result = data.result;
|
||||||
|
// this.state.sections = [];
|
||||||
|
// const sections = result.sections;
|
||||||
|
// for (let index = 0; index < sections.length; index++) {
|
||||||
|
// let c = {};
|
||||||
|
// c.id=(sections[index]).id;
|
||||||
|
// c.label = (sections[index]).subject_name;
|
||||||
|
// c.value = (sections[index]).subject_id;
|
||||||
|
// c.section_state=(sections[index]).section_state;
|
||||||
|
// c.totalQuestion = (sections[index]).total_questions;
|
||||||
|
// c.subject_id = (sections[index]).subject_id;
|
||||||
|
// c.index = index;
|
||||||
|
// c.buttonState = false;
|
||||||
|
// this.state.sections.push(c);
|
||||||
|
// }
|
||||||
|
// this.state.loaded=true;
|
||||||
|
// this.forceUpdate();
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
sendClickData = (index) => {
|
||||||
|
this.props.parentButtonCallback(index);
|
||||||
|
}
|
||||||
|
renderCard = (card, index) => {
|
||||||
|
// console.log(card);
|
||||||
|
return (
|
||||||
|
<Card key={card.value}
|
||||||
|
status={card.section_state}
|
||||||
|
index={index}
|
||||||
|
id={card.value}
|
||||||
|
text={card.label}
|
||||||
|
moveCard={this.moveCard}
|
||||||
|
clickButton={(e) => { this.sendClickData(e) }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
moveCard = (dragIndex, hoverIndex) => {
|
||||||
|
// console.log(dragIndex, hoverIndex, this.state.cards, this.props);
|
||||||
|
const dragCard = this.state.cards[dragIndex];
|
||||||
|
this.setState({
|
||||||
|
cards: update(this.state.cards, {
|
||||||
|
$splice: [
|
||||||
|
[dragIndex, 1],
|
||||||
|
[hoverIndex, 0, dragCard],
|
||||||
|
],
|
||||||
|
})
|
||||||
|
});
|
||||||
|
this.sendData();
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
// console.log(this.props);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{/* {this.state.loaded? */}
|
||||||
|
<div className='ReaarngeButtons' key={"container-for-movable-card"}>
|
||||||
|
{this.props.modelState.map((card, i) => this.renderCard(card, i))}
|
||||||
|
</div>
|
||||||
|
{/* :null} */}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Container;
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
export const ItemTypes = {
|
||||||
|
CARD: 'card',
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,78 @@
|
||||||
|
import React, { useRef } from 'react';
|
||||||
|
import { useDrag, useDrop } from 'react-dnd';
|
||||||
|
import {InputNumber, Button} from 'antd';
|
||||||
|
import { ItemTypes } from './ItemTypes';
|
||||||
|
import './Card.css';
|
||||||
|
import { Trans } from 'react-i18next';
|
||||||
|
|
||||||
|
export const QuestionCard = ({ id, text, index, pmark, nmark, moveCard, detachQuestion, changeNegativeMarkCall, changePositiveMarkCall, detachOngoing }) => {
|
||||||
|
const ref = useRef(null);
|
||||||
|
const [, drop] = useDrop({
|
||||||
|
accept: ItemTypes.CARD,
|
||||||
|
hover(item, monitor) {
|
||||||
|
if (!ref.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const dragIndex = item.index;
|
||||||
|
const hoverIndex = index;
|
||||||
|
// Don't replace items with themselves
|
||||||
|
if (dragIndex === hoverIndex) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Determine rectangle on screen
|
||||||
|
const hoverBoundingRect = ref.current?.getBoundingClientRect();
|
||||||
|
// Get vertical middle
|
||||||
|
const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2;
|
||||||
|
// Determine mouse position
|
||||||
|
const clientOffset = monitor.getClientOffset();
|
||||||
|
// Get pixels to the top
|
||||||
|
const hoverClientY = clientOffset.y - hoverBoundingRect.top;
|
||||||
|
// Only perform the move when the mouse has crossed half of the items height
|
||||||
|
// When dragging downwards, only move when the cursor is below 50%
|
||||||
|
// When dragging upwards, only move when the cursor is above 50%
|
||||||
|
// Dragging downwards
|
||||||
|
if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Dragging upwards
|
||||||
|
if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Time to actually perform the action
|
||||||
|
moveCard(dragIndex, hoverIndex);
|
||||||
|
// Note: we're mutating the monitor item here!
|
||||||
|
// Generally it's better to avoid mutations,
|
||||||
|
// but it's good here for the sake of performance
|
||||||
|
// to avoid expensive index searches.
|
||||||
|
item.index = hoverIndex;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const [{ isDragging }, drag] = useDrag({
|
||||||
|
item: { type: ItemTypes.CARD, id, index },
|
||||||
|
collect: (monitor) => ({
|
||||||
|
isDragging: monitor.isDragging(),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
const opacity = isDragging ? 0 : 1;
|
||||||
|
drag(drop(ref));
|
||||||
|
|
||||||
|
function detachQuestionFromSection(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
detachQuestion(index);
|
||||||
|
}
|
||||||
|
function changePositiveMark(e) {
|
||||||
|
changePositiveMarkCall(index,e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
function changeNegativeMark(e) {
|
||||||
|
changeNegativeMarkCall(index,e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return (<tr ref={ref}>
|
||||||
|
<td>{(index+1)}</td>
|
||||||
|
<td><span className='ArrangeSpan' style={{ opacity,textAlign:'left' }}>{text}</span></td>
|
||||||
|
<td><InputNumber value={pmark} onChange={changePositiveMark} min={1} step={0.25}/></td>
|
||||||
|
<td><InputNumber value={nmark} min={0} step={0.25} onChange={changeNegativeMark}/></td>
|
||||||
|
<td><Button disabled={detachOngoing} onClick={detachQuestionFromSection} type="primary" style={{ marginRight: 8 }}><Trans i18nKey={"detach-btn"}>Detach</Trans></Button></td>
|
||||||
|
</tr>);
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,108 @@
|
||||||
|
import React, { useRef } from "react";
|
||||||
|
import { useDrag, useDrop } from "react-dnd";
|
||||||
|
import { InputNumber, Button } from "antd";
|
||||||
|
import { ItemTypes } from "./ItemTypes";
|
||||||
|
import "./Card.css";
|
||||||
|
import { Trans } from "react-i18next";
|
||||||
|
|
||||||
|
export const QuestionCard = ({
|
||||||
|
id,
|
||||||
|
text,
|
||||||
|
index,
|
||||||
|
pmark,
|
||||||
|
duration_seconds,
|
||||||
|
nmark,
|
||||||
|
moveCard,
|
||||||
|
detachQuestion,
|
||||||
|
// changeNegativeMarkCall,
|
||||||
|
changePositiveMarkCall,
|
||||||
|
}) => {
|
||||||
|
const ref = useRef(null);
|
||||||
|
const [, drop] = useDrop({
|
||||||
|
accept: ItemTypes.CARD,
|
||||||
|
hover(item, monitor) {
|
||||||
|
if (!ref.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const dragIndex = item.index;
|
||||||
|
const hoverIndex = index;
|
||||||
|
// Don't replace items with themselves
|
||||||
|
if (dragIndex === hoverIndex) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Determine rectangle on screen
|
||||||
|
const hoverBoundingRect = ref.current?.getBoundingClientRect();
|
||||||
|
// Get vertical middle
|
||||||
|
const hoverMiddleY =
|
||||||
|
(hoverBoundingRect.bottom - hoverBoundingRect.top) / 2;
|
||||||
|
// Determine mouse position
|
||||||
|
const clientOffset = monitor.getClientOffset();
|
||||||
|
// Get pixels to the top
|
||||||
|
const hoverClientY = clientOffset.y - hoverBoundingRect.top;
|
||||||
|
// Only perform the move when the mouse has crossed half of the items height
|
||||||
|
// When dragging downwards, only move when the cursor is below 50%
|
||||||
|
// When dragging upwards, only move when the cursor is above 50%
|
||||||
|
// Dragging downwards
|
||||||
|
if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Dragging upwards
|
||||||
|
if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Time to actually perform the action
|
||||||
|
moveCard(dragIndex, hoverIndex);
|
||||||
|
// Note: we're mutating the monitor item here!
|
||||||
|
// Generally it's better to avoid mutations,
|
||||||
|
// but it's good here for the sake of performance
|
||||||
|
// to avoid expensive index searches.
|
||||||
|
item.index = hoverIndex;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const [{ isDragging }, drag] = useDrag({
|
||||||
|
item: { type: ItemTypes.CARD, id, index },
|
||||||
|
collect: (monitor) => ({
|
||||||
|
isDragging: monitor.isDragging(),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
const opacity = isDragging ? 0 : 1;
|
||||||
|
drag(drop(ref));
|
||||||
|
|
||||||
|
function detachQuestionFromSection(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
detachQuestion(index);
|
||||||
|
}
|
||||||
|
function changePositiveMark(e) {
|
||||||
|
// console.log(e);
|
||||||
|
changePositiveMarkCall(index, e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<tr ref={ref}>
|
||||||
|
<td>{index + 1}</td>
|
||||||
|
<td>
|
||||||
|
<span className="ArrangeSpan" style={{ opacity }}>
|
||||||
|
{text}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<InputNumber
|
||||||
|
value={duration_seconds}
|
||||||
|
onChange={changePositiveMark}
|
||||||
|
min={0}
|
||||||
|
step={1}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<Button
|
||||||
|
onClick={detachQuestionFromSection}
|
||||||
|
type="primary"
|
||||||
|
// style={{ marginRight: 8 }}
|
||||||
|
>
|
||||||
|
<Trans i18nKey={"detach-btn"}>Detach</Trans>
|
||||||
|
</Button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,132 @@
|
||||||
|
.ReaarngeButtons input[type=text] {
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ReaarngeButtons {
|
||||||
|
border-style: none;
|
||||||
|
padding: 0px 20px;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.ReviewTable {
|
||||||
|
border: 1px solid #f0f0f0;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ReviewQuestionsDiv .review-questions-subheader {
|
||||||
|
display: flex;
|
||||||
|
column-gap: 10px;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ReviewQuestionsDiv .review-questions-subheader > *:first-child {
|
||||||
|
width: auto;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 0 90px 90px 80px;
|
||||||
|
grid-template-rows: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ReviewQuestionsDiv .review-questions-subheader .subheader-info {
|
||||||
|
flex: 0 1 500px;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.ReviewTable, .ReviewTable td {
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
.ReviewTable th {
|
||||||
|
border-left: 1px solid rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
table.ReviewTable {
|
||||||
|
border-collapse: collapse;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.ReviewTable {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column;
|
||||||
|
width: 100%;
|
||||||
|
/* max-height: 400px; */
|
||||||
|
}
|
||||||
|
|
||||||
|
.ReviewTable thead {
|
||||||
|
/* paading-right is set to account for a scrollbar. set that to be the width of
|
||||||
|
scrollbar for perfect table alignment */
|
||||||
|
margin-right: 19px;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ReviewTable thead > tr > th {
|
||||||
|
padding: 5px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ReviewTable thead[data-scroll-enable="1"] {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ReviewTable tbody {
|
||||||
|
/* margin-right: 19px; */
|
||||||
|
flex: 1 1 auto;
|
||||||
|
display: block;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ReviewTable tr {
|
||||||
|
width: 100%;
|
||||||
|
display: table;
|
||||||
|
table-layout: fixed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ReviewTable tr > th:first-child {
|
||||||
|
width: 70px;
|
||||||
|
text-align: center;
|
||||||
|
/* width in [.ReviewTable tr > td:first-child] should be same */
|
||||||
|
}
|
||||||
|
|
||||||
|
.ReviewTable tr > td:first-child {
|
||||||
|
width: 70px;
|
||||||
|
text-align: center;
|
||||||
|
/* width in [.ReviewTable tr > th:first-child] ahould be same */
|
||||||
|
}
|
||||||
|
|
||||||
|
.ReviewTable tr > th:nth-child(3) {
|
||||||
|
width:120px;
|
||||||
|
text-align: center;
|
||||||
|
/* width in [.ReviewTable tr > td:nth-child(3] should be same */
|
||||||
|
}
|
||||||
|
|
||||||
|
.ReviewTable tr > td:nth-child(3) {
|
||||||
|
width: 120px;
|
||||||
|
text-align: center;
|
||||||
|
/* width in [.ReviewTable tr > th:nth-child(3)] ahould be same */
|
||||||
|
}
|
||||||
|
|
||||||
|
.ReviewTable tr > th:nth-child(4) {
|
||||||
|
width:120px;
|
||||||
|
text-align: center;
|
||||||
|
/* width in [.ReviewTable tr > td:nth-child(4] should be same */
|
||||||
|
}
|
||||||
|
|
||||||
|
.ReviewTable tr > td:nth-child(4) {
|
||||||
|
width: 120px;
|
||||||
|
text-align: center;
|
||||||
|
/* width in [.ReviewTable tr > th:nth-child(4)] ahould be same */
|
||||||
|
}
|
||||||
|
|
||||||
|
.ReviewTable tr > th:nth-child(5) {
|
||||||
|
width:120px;
|
||||||
|
padding-left: 2rem;
|
||||||
|
/* text-align: center; */
|
||||||
|
/* width in [.ReviewTable tr > td:nth-child(5)] should be same */
|
||||||
|
}
|
||||||
|
|
||||||
|
.ReviewTable tr > td:nth-child(5) {
|
||||||
|
width: 120px;
|
||||||
|
/* padding-left: 2rem; */
|
||||||
|
text-align: center;
|
||||||
|
/* width in [.ReviewTable tr > th:nth-child(5)] ahould be same */
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,112 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Input, Col, Row, Select, InputNumber, DatePicker, AutoComplete, Cascader,Button, Typography } from 'antd';
|
||||||
|
import { QuestionCard } from './QuestionCard';
|
||||||
|
import update from 'immutability-helper';
|
||||||
|
import './ReviewContainer.css';
|
||||||
|
import { InfoCircleOutlined } from '@ant-design/icons';
|
||||||
|
import { Trans, withTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class ReviewContainer extends React.Component{
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
cards : this.props.modelState,
|
||||||
|
styleClassName: "subjectButtonTrue",//this.prop.styleClassName,
|
||||||
|
pmark:1,
|
||||||
|
nmark:0,
|
||||||
|
};
|
||||||
|
this.sendData = this.sendData.bind(this);
|
||||||
|
this.renderCard = this.renderCard.bind(this);
|
||||||
|
this.moveCard = this.moveCard.bind(this);
|
||||||
|
this.sendClickData = this.sendClickData.bind(this);
|
||||||
|
this.detachQuestion = this.detachQuestion.bind(this);
|
||||||
|
this.changePositiveMark = this.changePositiveMark.bind(this);
|
||||||
|
this.changeNegativeMark = this.changeNegativeMark.bind(this);
|
||||||
|
}
|
||||||
|
changePositiveMark = (index, value) => {
|
||||||
|
// console.log(index,value);
|
||||||
|
if (value > 0 ) this.state.cards[index].pmark = value;
|
||||||
|
this.sendData();
|
||||||
|
}
|
||||||
|
changeNegativeMark = (index, value) => {
|
||||||
|
// console.log(index,value);
|
||||||
|
if (value >= 0 ) this.state.cards[index].nmark = value;
|
||||||
|
this.sendData();
|
||||||
|
}
|
||||||
|
|
||||||
|
sendData = () => {
|
||||||
|
this.props.parentCallback(this.state.cards);
|
||||||
|
}
|
||||||
|
|
||||||
|
sendClickData = (index) => {
|
||||||
|
this.props.parentButtonCallback(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
detachQuestion = (index) => {
|
||||||
|
this.setState(prev => ({...prev, cards: this.state.cards.filter((card, idx) => idx!==index)}));
|
||||||
|
this.props.detachQuestionCallback(index);
|
||||||
|
}
|
||||||
|
renderCard = (card, index) => {
|
||||||
|
// console.log(card,index);
|
||||||
|
return (<QuestionCard key={card.value} index={index} id={card.value} detachOngoing={this.props.detachOngoing} text={card.label} pmark={card.pmark} nmark={card.nmark} moveCard={this.moveCard} detachQuestion={this.detachQuestion} changeNegativeMarkCall={this.changeNegativeMark} changePositiveMarkCall={this.changePositiveMark}/>);
|
||||||
|
};
|
||||||
|
|
||||||
|
moveCard = (dragIndex, hoverIndex) => {
|
||||||
|
const dragCard = this.state.cards[dragIndex];
|
||||||
|
this.setState({cards:update(this.state.cards, {
|
||||||
|
$splice: [
|
||||||
|
[dragIndex, 1],
|
||||||
|
[hoverIndex, 0, dragCard],
|
||||||
|
],
|
||||||
|
})});
|
||||||
|
this.sendData();
|
||||||
|
};
|
||||||
|
|
||||||
|
render(){
|
||||||
|
// console.log(this.state.cards)
|
||||||
|
return(
|
||||||
|
<>
|
||||||
|
<div className="review-questions-subheader">
|
||||||
|
<Input.Group compact>
|
||||||
|
<InputNumber value={this.state.pmark} onChange={(e)=>{e > 0 && this.setState({pmark:e})}} min={1} step={0.25}/>
|
||||||
|
<InputNumber value={this.state.nmark} onChange={(e)=>{e >= 0 && this.setState({nmark:e})}} min={0} step={0.25}/>
|
||||||
|
<Button type="primary" onClick={()=>{
|
||||||
|
for(let i in this.state.cards)
|
||||||
|
{
|
||||||
|
this.changePositiveMark(i,Number(this.state.pmark));
|
||||||
|
this.changeNegativeMark(i,Number(this.state.nmark));
|
||||||
|
}
|
||||||
|
}}>{this.props.t('apply-btn')}</Button>
|
||||||
|
</Input.Group>
|
||||||
|
<div className="subheader-info">
|
||||||
|
<InfoCircleOutlined style={{ fontSize: 16, padding: 2, margin: "4px" }} />
|
||||||
|
<Typography.Text type={"warning"} style={{verticalAlign: 'text-bottom'}}>
|
||||||
|
<Trans i18nKey="review-note">
|
||||||
|
<strong>Note:</strong> Provide positive and negative marks respectively to apply to all questions. The values will always be positive.
|
||||||
|
</Trans>
|
||||||
|
</Typography.Text>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='ArrangeQuestions'>
|
||||||
|
<table className='ReviewTable'>
|
||||||
|
<thead data-scroll-enable={"1"}>
|
||||||
|
<tr>
|
||||||
|
<th>{this.props.t('sl-no')}</th>
|
||||||
|
<th>{this.props.t('questions-header')}</th>
|
||||||
|
<th>{this.props.t('pos-marks')}</th>
|
||||||
|
<th>{this.props.t('neg-marks')}</th>
|
||||||
|
<th>{this.props.t('action-header')}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>{this.props.modelState.map((card, i) => this.renderCard(card, i))}</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default withTranslation()(ReviewContainer);
|
||||||
|
|
@ -0,0 +1,141 @@
|
||||||
|
import React from "react";
|
||||||
|
import { Input, Col, Row, Select, InputNumber, DatePicker, AutoComplete, Cascader, Button, Typography } from "antd";
|
||||||
|
import { InfoCircleOutlined } from "@ant-design/icons";
|
||||||
|
import { QuestionCard } from "./QuestionCardPractice";
|
||||||
|
import update from "immutability-helper";
|
||||||
|
import "./ReviewContainer.css";
|
||||||
|
import { Trans } from "react-i18next";
|
||||||
|
|
||||||
|
class ReviewContainer extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
cards: this.props.modelState,
|
||||||
|
styleClassName: "subjectButtonTrue", //this.prop.styleClassName,
|
||||||
|
pmark: 1,
|
||||||
|
nmark: 0,
|
||||||
|
};
|
||||||
|
this.sendData = this.sendData.bind(this);
|
||||||
|
this.renderCard = this.renderCard.bind(this);
|
||||||
|
this.moveCard = this.moveCard.bind(this);
|
||||||
|
this.sendClickData = this.sendClickData.bind(this);
|
||||||
|
this.detachQuestion = this.detachQuestion.bind(this);
|
||||||
|
this.changePositiveMark = this.changePositiveMark.bind(this);
|
||||||
|
this.changeNegativeMark = this.changeNegativeMark.bind(this);
|
||||||
|
}
|
||||||
|
changePositiveMark = (index, value) => {
|
||||||
|
// console.log(index, value);
|
||||||
|
if (value > 0) this.state.cards[index].duration_seconds = value;
|
||||||
|
this.sendData();
|
||||||
|
}
|
||||||
|
changeNegativeMark = (index, value) => {
|
||||||
|
// console.log(index, value);
|
||||||
|
this.state.cards[index].nmark = value;
|
||||||
|
this.sendData();
|
||||||
|
}
|
||||||
|
|
||||||
|
sendData = () => {
|
||||||
|
this.props.parentCallback(this.state.cards);
|
||||||
|
}
|
||||||
|
|
||||||
|
sendClickData = (index) => {
|
||||||
|
this.props.parentButtonCallback(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
detachQuestion = (index) => {
|
||||||
|
// this.setState(prev => ({...prev, cards: this.state.cards.filter((card, idx) => idx!==index)}));
|
||||||
|
this.props.detachQuestionCallback(index);
|
||||||
|
}
|
||||||
|
renderCard = (card, index) => {
|
||||||
|
// console.log(card, index);
|
||||||
|
return (
|
||||||
|
<QuestionCard
|
||||||
|
key={card.value}
|
||||||
|
index={index}
|
||||||
|
id={card.value}
|
||||||
|
text={card.label}
|
||||||
|
pmark={card.pmark}
|
||||||
|
nmark={card.nmark}
|
||||||
|
duration_seconds={card.duration_seconds}
|
||||||
|
moveCard={this.moveCard}
|
||||||
|
detachQuestion={this.detachQuestion}
|
||||||
|
// changeNegativeMarkCall={this.changeNegativeMark}
|
||||||
|
changePositiveMarkCall={this.changePositiveMark}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
moveCard = (dragIndex, hoverIndex) => {
|
||||||
|
const dragCard = this.state.cards[dragIndex];
|
||||||
|
this.setState({
|
||||||
|
cards: update(this.state.cards, {
|
||||||
|
$splice: [
|
||||||
|
[dragIndex, 1],
|
||||||
|
[hoverIndex, 0, dragCard],
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
this.sendData();
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
// console.log(this.state.cards);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="review-questions-subheader">
|
||||||
|
<Input.Group compact>
|
||||||
|
{/* this input has the 'pmark' property associated with it because its copied from
|
||||||
|
exam module but the duration property in practice is actually recorded by this
|
||||||
|
object variable. */}
|
||||||
|
<Input
|
||||||
|
allowClear
|
||||||
|
type="number"
|
||||||
|
placeholder="Duration"
|
||||||
|
// style={{ width: "10%" }}
|
||||||
|
defaultValue="1"
|
||||||
|
onChange={(e, newValue) => {
|
||||||
|
e.target.value > 0 && this.setState({ pmark: e.target.value });
|
||||||
|
}}
|
||||||
|
value={this.state.pmark}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
onClick={() => {
|
||||||
|
for (let i in this.state.cards) {
|
||||||
|
this.changePositiveMark(i, Number(this.state.pmark));
|
||||||
|
// this.changeNegativeMark(i, Number(this.state.nmark));
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Trans i18nKey={"apply-btn"}>Apply</Trans>
|
||||||
|
</Button>
|
||||||
|
</Input.Group>
|
||||||
|
<div className="subheader-info">
|
||||||
|
<InfoCircleOutlined style={{ fontSize: 16, padding: 2, margin: "4px" }} />
|
||||||
|
<Typography.Text type={"warning"} style={{verticalAlign: 'text-bottom'}}>
|
||||||
|
<Trans i18nKey={"practice.step2.note2"}><strong>Note:</strong> Provide duration in seconds to apply to all questions</Trans>
|
||||||
|
</Typography.Text>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='ArrangeQuestions'>
|
||||||
|
<table className='ReviewTable'>
|
||||||
|
<thead data-scroll-enable={"1"}>
|
||||||
|
<tr>
|
||||||
|
<th><Trans i18nKey={"sl-no"}>S.No.</Trans></th>
|
||||||
|
<th><Trans i18nKey={"questions-header"}>Questions</Trans></th>
|
||||||
|
<th><Trans i18nKey={"practice.step2.duration"}>Duration</Trans><div>(<Trans i18nKey={"practice.step2.seconds"}>Seconds</Trans>)</div></th>
|
||||||
|
<th><Trans i18nKey={"action-header"}>Action</Trans></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
{/* <div className="review-table-data-container"> */}
|
||||||
|
<tbody>{this.props.modelState.map((card, i) => this.renderCard(card, i))}</tbody>
|
||||||
|
{/* </div> */}
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ReviewContainer;
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
|
||||||
|
import React from 'react'
|
||||||
|
import ReactDOM from 'react-dom'
|
||||||
|
import Example from './example'
|
||||||
|
import { DndProvider } from 'react-dnd'
|
||||||
|
import { HTML5Backend } from 'react-dnd-html5-backend'
|
||||||
|
|
||||||
|
function App() {
|
||||||
|
return (
|
||||||
|
<div className="App">
|
||||||
|
<DndProvider backend={HTML5Backend}>
|
||||||
|
<Example />
|
||||||
|
</DndProvider>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const rootElement = document.getElementById('root')
|
||||||
|
ReactDOM.render(<App />, rootElement)
|
||||||
|
|
@ -0,0 +1,66 @@
|
||||||
|
.practice-container {
|
||||||
|
margin: 1rem 2rem;
|
||||||
|
background-color: var(--background-color-custom-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.practice-container .practice-type-selector {
|
||||||
|
/* height: 72px; */
|
||||||
|
display: flex;
|
||||||
|
place-items: center;
|
||||||
|
/* padding: 16px; */
|
||||||
|
}
|
||||||
|
|
||||||
|
.exam-container .table-look-up--draft {
|
||||||
|
--table-rows: 100px 1fr 90px 110px 120px 120px 1fr;
|
||||||
|
--minimum-width: 850px;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.exam-container .table-look-up--live, .exam-container .table-look-up--upcoming,
|
||||||
|
.exam-container .table-look-up--expired {
|
||||||
|
--table-rows: 100px 220px 100px 100px 140px 120px 90px 70px 1fr;
|
||||||
|
--minimum-width: 1300px;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.exam-container [class^="table-look-up"] > * {
|
||||||
|
padding: 16px;
|
||||||
|
min-width: var(--minimum-width);
|
||||||
|
}
|
||||||
|
|
||||||
|
.exam-container [class^="table-look-up"] [class$="table-header"] {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: var(--table-rows);
|
||||||
|
align-items: center;
|
||||||
|
background-color: var(--background-color-custom-2);
|
||||||
|
height: 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.exam-container .ant-skeleton ul {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.exam-container [class^="table-look-up"] [class$="table-data"] > div {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: var(--table-rows);
|
||||||
|
align-items: center;
|
||||||
|
padding-block: 0.75rem;
|
||||||
|
border-bottom: 1px solid #00000040;
|
||||||
|
}
|
||||||
|
|
||||||
|
.exam-container [class^="table-look-up"] [class$="table-data"] > div:nth-child(1) {
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.exam-container [class^="table-look-up"] [class$="table-data"] > div:nth-last-child(1) {
|
||||||
|
padding-bottom: 0;
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.exam-container [class^="table-look-up"] [class$="table-data"] .ant-empty .ant-empty-image {
|
||||||
|
grid-area: 1/1/auto/span 9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.exam-container [class^="table-look-up"] [class$="table-data"] .ant-empty .ant-empty-description {
|
||||||
|
grid-area: 2/1/auto/span 9;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
li.DraftExam{
|
||||||
|
-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;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.DraftExamImage{
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
line-height: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.DraftExamImage img{
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,345 @@
|
||||||
|
import {
|
||||||
|
Steps,
|
||||||
|
Button,
|
||||||
|
message,
|
||||||
|
Layout,
|
||||||
|
List,
|
||||||
|
Avatar,
|
||||||
|
Skeleton,
|
||||||
|
Empty,
|
||||||
|
notification,
|
||||||
|
Image,
|
||||||
|
} from "antd";
|
||||||
|
import "./DraftExam.css";
|
||||||
|
import { Siderc } from "../Main/Siderc";
|
||||||
|
import React from "react";
|
||||||
|
import { Headerc } from "../Main/Headerc";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
import history from "../../history";
|
||||||
|
import { selectorService } from "../../services/selectorService";
|
||||||
|
import parse from "html-react-parser";
|
||||||
|
import { S3_BUCKET_EXAM_PREFIX } from "../../_services/config";
|
||||||
|
|
||||||
|
const { Content } = Layout;
|
||||||
|
|
||||||
|
class ExpiredExam extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
data: [],
|
||||||
|
total_count: 0,
|
||||||
|
total_pages: 0,
|
||||||
|
processing: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
componentDidMount() {
|
||||||
|
selectorService
|
||||||
|
.expiredExam({
|
||||||
|
pageSize: 10,
|
||||||
|
pageNumber: 1,
|
||||||
|
classId: parseInt(sessionStorage.getItem("currentClass")),
|
||||||
|
})
|
||||||
|
.then((res) => {
|
||||||
|
// console.log(res);
|
||||||
|
if(res.status.code === "-1") throw "Something went wrong, please reload the page." //error handling
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
data: res.result.exams,
|
||||||
|
total_count: res.result.total_count,
|
||||||
|
total_pages: res.result.total_pages,
|
||||||
|
processing: false,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.log(err);
|
||||||
|
notification.error({
|
||||||
|
message: err, duration: 10
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleChange = (pageNumber, pageSize) => {
|
||||||
|
selectorService
|
||||||
|
.draftExam({ pageNumber: pageNumber, pageSize: pageSize, classId: 1 })
|
||||||
|
.then((res) => {
|
||||||
|
this.setState({
|
||||||
|
data: res.result.exams,
|
||||||
|
total_count: res.result.total_count,
|
||||||
|
total_pages: res.result.total_pages,
|
||||||
|
processing: false,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.log(err);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
if (this.state.data.length !== 0)
|
||||||
|
return (
|
||||||
|
<Layout>
|
||||||
|
<Siderc />
|
||||||
|
<Layout>
|
||||||
|
<Headerc />
|
||||||
|
<Content
|
||||||
|
className="site-layout-background"
|
||||||
|
style={{
|
||||||
|
padding: 24,
|
||||||
|
margin: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Layout className="createExamLayoutHeader">Expired Exams</Layout>
|
||||||
|
<Layout className="createExamLayout">
|
||||||
|
<List
|
||||||
|
itemLayout="horizontal"
|
||||||
|
dataSource={this.state.data}
|
||||||
|
pagination={{
|
||||||
|
onChange: (page, pageSize) => {
|
||||||
|
this.handleChange(page, pageSize);
|
||||||
|
},
|
||||||
|
|
||||||
|
total: this.state.total_count,
|
||||||
|
}}
|
||||||
|
renderItem={(item, index) => {
|
||||||
|
let step = 2;
|
||||||
|
if (item.sections_count >= 1) step = 3;
|
||||||
|
if (item.sections_status !== "DRAFT") step = 4;
|
||||||
|
let currentTime = new Date();
|
||||||
|
let loginTime = new Date(item.updated_on);
|
||||||
|
const diff = currentTime.getTime() - loginTime.getTime();
|
||||||
|
// console.log(diff,loginTime,currentTime,item.updated_on);
|
||||||
|
var years = Math.floor(
|
||||||
|
diff / (1000 * 60 * 60 * 24 * 30 * 12)
|
||||||
|
);
|
||||||
|
var months = Math.floor(
|
||||||
|
(diff % (1000 * 60 * 60 * 24 * 30 * 12)) /
|
||||||
|
(1000 * 60 * 60 * 24 * 30)
|
||||||
|
);
|
||||||
|
var days = Math.floor(
|
||||||
|
(diff % (1000 * 60 * 60 * 24 * 30)) /
|
||||||
|
(1000 * 60 * 60 * 24)
|
||||||
|
);
|
||||||
|
|
||||||
|
var hours = Math.floor(
|
||||||
|
(diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)
|
||||||
|
);
|
||||||
|
var minutes = Math.floor((diff % 3.6e6) / 6e4);
|
||||||
|
let updatedOn =
|
||||||
|
years > 0
|
||||||
|
? `${years} year(s)/${months} months`
|
||||||
|
: months > 0
|
||||||
|
? `${months} month(s)/${days} days`
|
||||||
|
: days > 0
|
||||||
|
? `${days} day(s)/${hours} hours`
|
||||||
|
: minutes > 0
|
||||||
|
? `${hours} hour(s)${minutes}minutes(s)`
|
||||||
|
: minutes + "minutes";
|
||||||
|
return (
|
||||||
|
<List.Item
|
||||||
|
className="DraftExam"
|
||||||
|
key={index}
|
||||||
|
actions={[
|
||||||
|
<Button
|
||||||
|
onClick={(e, newValue) => {
|
||||||
|
console.log(item, e);
|
||||||
|
}}
|
||||||
|
type="primary"
|
||||||
|
>
|
||||||
|
<Link
|
||||||
|
to={{
|
||||||
|
pathname: "",
|
||||||
|
state: { id: item.id, current: step - 1 },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
View Report
|
||||||
|
</Link>
|
||||||
|
</Button>,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<List.Item.Meta
|
||||||
|
avatar={
|
||||||
|
<Image
|
||||||
|
// src="https://cdn.xl.thumbs.canstockphoto.com/exam-written-on-a-chalkboard-books-pencils-and-an-apple-on-foreground-picture_csp2468961.jpg"
|
||||||
|
src={`${S3_BUCKET_EXAM_PREFIX+item.id}.png`}
|
||||||
|
fallback="https://cdn.xl.thumbs.canstockphoto.com/exam-written-on-a-chalkboard-books-pencils-and-an-apple-on-foreground-picture_csp2468961.jpg"
|
||||||
|
// size="large"
|
||||||
|
// shape="square"
|
||||||
|
width={100}
|
||||||
|
height={100}
|
||||||
|
className="DraftExamImage"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
size="large"
|
||||||
|
bordered="true"
|
||||||
|
title={parse(`<b>${item.name}</b>`)}
|
||||||
|
description={
|
||||||
|
<>
|
||||||
|
<div className="MyExamDiv">
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Start Date</th>
|
||||||
|
<th>End Date</th>
|
||||||
|
<th>Durations</th>
|
||||||
|
<th>Questions</th>
|
||||||
|
<th>Marks</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>{item.start_date}</td>
|
||||||
|
<td>{item.end_date}</td>
|
||||||
|
<td>{item.exam_duration}</td>
|
||||||
|
<td>{item.total_questions}</td>
|
||||||
|
<td>{item.total_marks}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</List.Item>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Layout>
|
||||||
|
</Content>
|
||||||
|
</Layout>
|
||||||
|
</Layout>
|
||||||
|
);
|
||||||
|
else if (this.state.processing)
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Layout>
|
||||||
|
<Siderc />
|
||||||
|
<Layout>
|
||||||
|
<Headerc />
|
||||||
|
|
||||||
|
<Content
|
||||||
|
className="site-layout-background"
|
||||||
|
style={{
|
||||||
|
padding: 24,
|
||||||
|
margin: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Layout className="createExamLayoutHeader">
|
||||||
|
Expired Exams
|
||||||
|
</Layout>
|
||||||
|
<Layout className="createExamLayout">
|
||||||
|
<List
|
||||||
|
itemLayout="horizontal"
|
||||||
|
dataSource={[{}, {}, {}, {}, {}, {}, {}, {}, {}, {}]}
|
||||||
|
pagination={{
|
||||||
|
onChange: (page, pageSize) => {
|
||||||
|
this.handleChange(page, pageSize);
|
||||||
|
},
|
||||||
|
|
||||||
|
total: this.state.total_count,
|
||||||
|
}}
|
||||||
|
renderItem={(item, index) => {
|
||||||
|
// let step = 2;
|
||||||
|
// if (item.sections_count >= 1) step = 3;
|
||||||
|
// if (item.sections_status !== "DRAFT") step = 4;
|
||||||
|
// let currentTime = new Date();
|
||||||
|
// let loginTime = new Date(item.updated_on);
|
||||||
|
// const diff = currentTime.getTime() - loginTime.getTime();
|
||||||
|
// // console.log(diff,loginTime,currentTime,item.updated_on);
|
||||||
|
// var years = Math.floor(diff / (1000 * 60 * 60 * 24 * 30 * 12));
|
||||||
|
// var months = Math.floor(
|
||||||
|
// (diff % (1000 * 60 * 60 * 24 * 30 * 12)) / (1000 * 60 * 60 * 24 * 30),
|
||||||
|
// );
|
||||||
|
// var days = Math.floor(
|
||||||
|
// (diff % (1000 * 60 * 60 * 24 * 30)) / (1000 * 60 * 60 * 24),
|
||||||
|
// );
|
||||||
|
|
||||||
|
// var hours = Math.floor(diff%(1000*60*60*24) / (1000 * 60 * 60));
|
||||||
|
// var minutes=Math.floor((diff % 3.6e+6) / 6e+4);
|
||||||
|
// let updatedOn=
|
||||||
|
// years > 0
|
||||||
|
// ? `${years} year(s)/${months} months`
|
||||||
|
// : months > 0
|
||||||
|
// ? `${months} month(s)/${days} days`
|
||||||
|
// : days > 0
|
||||||
|
// ? `${days} day(s)/${hours} hours`
|
||||||
|
// :minutes>0? `${hours} hour(s)${minutes}minutes(s)`:minutes+'minutes';
|
||||||
|
return (
|
||||||
|
<List.Item
|
||||||
|
className="DraftExam"
|
||||||
|
key={index}
|
||||||
|
// actions={[
|
||||||
|
// <Button
|
||||||
|
// onClick={(e, newValue) => {
|
||||||
|
// console.log(item, e);
|
||||||
|
// }}
|
||||||
|
// type="primary"
|
||||||
|
// >
|
||||||
|
// <Link
|
||||||
|
// to={{
|
||||||
|
// pathname: "/createExam",
|
||||||
|
// state: { id: item.id, current: step - 1 },
|
||||||
|
// }}
|
||||||
|
// >
|
||||||
|
// Recreate Exam
|
||||||
|
// </Link>
|
||||||
|
// </Button>,
|
||||||
|
// ]}
|
||||||
|
>
|
||||||
|
<Skeleton>
|
||||||
|
<List.Item.Meta
|
||||||
|
avatar={
|
||||||
|
<Avatar
|
||||||
|
src="https://cdn.xl.thumbs.canstockphoto.com/exam-written-on-a-chalkboard-books-pencils-and-an-apple-on-foreground-picture_csp2468961.jpg"
|
||||||
|
size="large"
|
||||||
|
shape="square"
|
||||||
|
className="DraftExamImage"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
size="large"
|
||||||
|
bordered="true"
|
||||||
|
title={parse(`<b>${"item.name"}</b>`)}
|
||||||
|
description={parse(
|
||||||
|
`<b>Progress - Step</b> ${"step"} <b>Updated At:</b> ${"updatedOn"} <b>Author Name:</b> ${"item.author_name"} <b>Sections Count: </b>${"item.sections_count"}`
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Skeleton>
|
||||||
|
</List.Item>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Layout>
|
||||||
|
</Content>
|
||||||
|
</Layout>
|
||||||
|
</Layout>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
else
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Layout>
|
||||||
|
<Siderc />
|
||||||
|
<Layout>
|
||||||
|
<Headerc />
|
||||||
|
|
||||||
|
<Content
|
||||||
|
className="site-layout-background"
|
||||||
|
style={{
|
||||||
|
padding: 24,
|
||||||
|
margin: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Layout className="createExamLayoutHeader">
|
||||||
|
Expired Exams
|
||||||
|
</Layout>
|
||||||
|
<Layout className="createExamLayout">
|
||||||
|
<Empty />
|
||||||
|
</Layout>
|
||||||
|
</Content>
|
||||||
|
</Layout>
|
||||||
|
</Layout>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ExpiredExam;
|
||||||
|
|
@ -0,0 +1,148 @@
|
||||||
|
import { Button, Empty, Image, Layout, Pagination, Radio, Skeleton, Tooltip, Typography, message } from 'antd';
|
||||||
|
import React, { useState, useEffect, useRef } from 'react';
|
||||||
|
import { S3_BUCKET_EXAM_PREFIX } from '../../_services/config';
|
||||||
|
import { selectorService } from '../../services/selectorService';
|
||||||
|
// import { Link } from 'react-router-dom';
|
||||||
|
import './ExamModule.css';
|
||||||
|
import empty from '../EmptyImage';
|
||||||
|
|
||||||
|
const ExpiredExamNew = () => {
|
||||||
|
const mounted = useRef();
|
||||||
|
const [pageLoading, setPageLoadingState] = useState(true);
|
||||||
|
const [pageState, setPageValues] = useState({
|
||||||
|
total_count: -1,
|
||||||
|
total_pages: -1,
|
||||||
|
pageNumber: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
type: "subject",
|
||||||
|
});
|
||||||
|
const [pageData, setPageData] = useState([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
mounted.current = true;
|
||||||
|
// someFetchingFunctionHere
|
||||||
|
if (pageState.type === "subject") {
|
||||||
|
fetchSubjectExpiredExam();
|
||||||
|
}
|
||||||
|
return () => {
|
||||||
|
// cleanup
|
||||||
|
mounted.current = false;
|
||||||
|
};
|
||||||
|
}, [pageState.pageNumber, pageState.type]);
|
||||||
|
|
||||||
|
async function fetchSubjectExpiredExam() {
|
||||||
|
if(!pageLoading) {
|
||||||
|
setPageLoadingState(true);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const res = await selectorService.expiredExam(pageState);
|
||||||
|
process.env.NODE_ENV === "development" && console.log(res);
|
||||||
|
if(+res.status.code < 0) {
|
||||||
|
if(res.status.message[0] === "No Record(s) Found!") {
|
||||||
|
mounted.current && setPageValues(prev => ({
|
||||||
|
...prev,
|
||||||
|
total_count: 0,
|
||||||
|
total_pages: 0,
|
||||||
|
}));
|
||||||
|
mounted.current && setPageData([]);
|
||||||
|
mounted.current && setPageLoadingState(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else throw res.status.message[0];
|
||||||
|
}
|
||||||
|
mounted.current && res && setPageValues(prev => ({
|
||||||
|
...prev,
|
||||||
|
total_count: res.result.total_count,
|
||||||
|
total_pages: res.result.total_pages,
|
||||||
|
}));
|
||||||
|
mounted.current && res && setPageData(prev => (
|
||||||
|
[...res.result.exams]
|
||||||
|
));
|
||||||
|
mounted.current && setPageLoadingState(false);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onChangePageNumber = (page) => {
|
||||||
|
setPageValues(prev => ({...prev, pageNumber: page}));
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Layout className=''>
|
||||||
|
<section className='practice-container exam-container'>
|
||||||
|
<div className='practice-type-selector'>
|
||||||
|
<Radio.Group buttonStyle={"solid"} size={"large"}
|
||||||
|
style={{display: 'grid', gridTemplateColumns: '1fr', width: '100%'}}
|
||||||
|
defaultValue="subject"
|
||||||
|
>
|
||||||
|
<Radio.Button value="subject">Expired Exams</Radio.Button>
|
||||||
|
</Radio.Group>
|
||||||
|
</div>
|
||||||
|
<div className='table-look-up--expired'>
|
||||||
|
<div className='expired-table-header'>
|
||||||
|
<Typography.Text strong>Image</Typography.Text>
|
||||||
|
<Typography.Text strong>Exam Name</Typography.Text>
|
||||||
|
<Typography.Text strong>Start Date</Typography.Text>
|
||||||
|
<Typography.Text strong>End Date</Typography.Text>
|
||||||
|
<Typography.Text strong>Author</Typography.Text>
|
||||||
|
<Typography.Text strong>Duration</Typography.Text>
|
||||||
|
<Typography.Text strong>Questions</Typography.Text>
|
||||||
|
<Typography.Text strong>Mark</Typography.Text>
|
||||||
|
<Typography.Text strong>Actions</Typography.Text>
|
||||||
|
</div>
|
||||||
|
<div className='expired-table-data'>
|
||||||
|
{pageLoading? [0, 1, 2].map((index) => (<div key={index}>
|
||||||
|
<Skeleton.Avatar size={"large"} shape={'square'} active={true} />
|
||||||
|
<Skeleton avatar={false} title={false} paragraph={{rows: 1, width: 120}} active={true} />
|
||||||
|
<Skeleton avatar={false} title={false} paragraph={{rows: 1, width: 80}} active={true} />
|
||||||
|
<Skeleton avatar={false} title={false} paragraph={{rows: 1, width: 50}} active={true} />
|
||||||
|
<Skeleton avatar={false} title={false} paragraph={{rows: 1, width: 50}} active={true} />
|
||||||
|
<Skeleton avatar={false} title={false} paragraph={{rows: 1, width: 50}} active={true} />
|
||||||
|
<Skeleton avatar={false} title={false} paragraph={{rows: 1, width: 20}} active={true} />
|
||||||
|
<Skeleton avatar={false} title={false} paragraph={{rows: 1, width: 20}} active={true} />
|
||||||
|
<span><Skeleton.Button size='small' /> <Skeleton.Button size='small' /></span>
|
||||||
|
</div>))
|
||||||
|
: pageData.length? pageData.map(data => {
|
||||||
|
const startDate = new Date(data.start_date);
|
||||||
|
const endDate = new Date(data.end_date);
|
||||||
|
return <div key={data.id}>
|
||||||
|
<span><Image
|
||||||
|
src={`${S3_BUCKET_EXAM_PREFIX + data.id}.png`}
|
||||||
|
fallback={empty()}
|
||||||
|
width={70}
|
||||||
|
height={70}
|
||||||
|
className="DraftExamImage"
|
||||||
|
/></span>
|
||||||
|
<span><Tooltip title={data.name} placement={"topLeft"}>
|
||||||
|
<Typography.Text ellipsis style={{width: 120}}>{data.name}</Typography.Text>
|
||||||
|
</Tooltip></span>
|
||||||
|
<span>
|
||||||
|
<p className='mb-0-0'>{startDate.toLocaleDateString()}</p>
|
||||||
|
<p className='mb-0-0'>{startDate.toLocaleTimeString('hi', { hour12: true })}</p>
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
<p className='mb-0-0'>{endDate.toLocaleDateString()}</p>
|
||||||
|
<p className='mb-0-0'>{endDate.toLocaleTimeString('hi', { hour12: true })}</p>
|
||||||
|
</span>
|
||||||
|
<span>{data.author_name}</span>
|
||||||
|
<span>{data.exam_duration/60} miuntes</span>
|
||||||
|
<span>{data.total_questions}</span>
|
||||||
|
<span>{data.total_marks}</span>
|
||||||
|
<span>
|
||||||
|
<Button>Recreate</Button>
|
||||||
|
<Button type='primary'>View Report</Button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
}): <Empty />}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<div className="button-container mb-1-0">
|
||||||
|
<Pagination defaultCurrent={1} current={pageState.pageNumber} total={pageState.total_count} onChange={onChangePageNumber} />
|
||||||
|
</div>
|
||||||
|
</Layout>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ExpiredExamNew;
|
||||||
|
|
@ -0,0 +1,527 @@
|
||||||
|
import {
|
||||||
|
Steps,
|
||||||
|
Button,
|
||||||
|
message,
|
||||||
|
Layout,
|
||||||
|
List,
|
||||||
|
Avatar,
|
||||||
|
Skeleton,
|
||||||
|
Empty,
|
||||||
|
Drawer,
|
||||||
|
Transfer,
|
||||||
|
notification,
|
||||||
|
Image,
|
||||||
|
} from "antd";
|
||||||
|
import "./DraftExam.css";
|
||||||
|
import { Siderc } from "../Main/Siderc";
|
||||||
|
import React from "react";
|
||||||
|
import { Headerc } from "../Main/Headerc";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
import history from "../../history";
|
||||||
|
import { selectorService } from "../../services/selectorService";
|
||||||
|
import parse from "html-react-parser";
|
||||||
|
import { authenticationService } from "../../_services";
|
||||||
|
import { S3_BUCKET_EXAM_PREFIX } from "../../_services/config";
|
||||||
|
|
||||||
|
const { Content } = Layout;
|
||||||
|
|
||||||
|
class LiveExam extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
data: [],
|
||||||
|
total_count: 0,
|
||||||
|
total_pages: 0,
|
||||||
|
processing: true,
|
||||||
|
associateBatchVisible: false,
|
||||||
|
batches:[],
|
||||||
|
selectedBatches:[],
|
||||||
|
selectedExam: "",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
showDrawer = (exam) => {
|
||||||
|
this.setState({
|
||||||
|
associateBatchVisible: true,
|
||||||
|
selectedExam: exam,
|
||||||
|
}, () => {
|
||||||
|
this.getBatches(exam);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
handleChangeBatch =(nextTargetKeys, direction, moveKeys) =>{
|
||||||
|
if(direction === 'right'){
|
||||||
|
selectorService.addBatchesToExam({
|
||||||
|
id: this.state.selectedExam.id,
|
||||||
|
batches: {idList:moveKeys},
|
||||||
|
})
|
||||||
|
.then((res) => {
|
||||||
|
if (!res) message.error("Some error occured");
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.log(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else if(direction === 'left'){
|
||||||
|
let updatedArray = this.state.selectedBatches.filter(item => !moveKeys.includes(item));
|
||||||
|
selectorService.detachBatchesFromExam({
|
||||||
|
id: this.state.selectedExam.id,
|
||||||
|
batches: {idList:moveKeys},
|
||||||
|
})
|
||||||
|
.then((res) => {
|
||||||
|
if (!res) message.error("Some error occured");
|
||||||
|
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.log(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.setState({
|
||||||
|
selectedBatches : nextTargetKeys,
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
getBatches = (exam) => {
|
||||||
|
selectorService
|
||||||
|
.getExamBatch({ id: exam.id })
|
||||||
|
.then((response) => {
|
||||||
|
console.log(response);
|
||||||
|
this.setState({
|
||||||
|
selectedBatches: response.result.idList.map(String),
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
selectorService
|
||||||
|
.getAllBatch({ class_id: authenticationService.currentClassValue })
|
||||||
|
.then((res) => {
|
||||||
|
|
||||||
|
let batches = res.result.map((x) => {
|
||||||
|
const data = {
|
||||||
|
key: `${x.id.toString()}`,
|
||||||
|
title: `${x.name}`,
|
||||||
|
description: `${x.name}`,
|
||||||
|
chosen: this.state.selectedBatches.includes(x.id.toString()) ? true : false,
|
||||||
|
};
|
||||||
|
return data;
|
||||||
|
})
|
||||||
|
this.setState({
|
||||||
|
"batches":batches,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.log(err);
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
handleCancel = (e) => {
|
||||||
|
// console.log(e);
|
||||||
|
|
||||||
|
this.hideDrawer();
|
||||||
|
};
|
||||||
|
|
||||||
|
handleSubmit = (e) => {
|
||||||
|
// console.log(e);
|
||||||
|
|
||||||
|
this.hideDrawer();
|
||||||
|
};
|
||||||
|
hideDrawer = () => {
|
||||||
|
this.setState({
|
||||||
|
associateBatchVisible: false,
|
||||||
|
selectedExam: "",
|
||||||
|
selectedBatches:[]
|
||||||
|
});
|
||||||
|
};
|
||||||
|
componentDidMount() {
|
||||||
|
selectorService
|
||||||
|
.liveExam({
|
||||||
|
pageSize: 10,
|
||||||
|
pageNumber: 1,
|
||||||
|
classId: parseInt(sessionStorage.getItem("currentClass")),
|
||||||
|
})
|
||||||
|
.then((res) => {
|
||||||
|
// console.log(res);
|
||||||
|
if (res.status.code === "-1") throw "Something went wrong, please reload the page again"
|
||||||
|
this.setState({
|
||||||
|
data: res.result.exams,
|
||||||
|
total_count: res.result.total_count,
|
||||||
|
total_pages: res.result.total_pages,
|
||||||
|
processing: false,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error(err);
|
||||||
|
notification.info({
|
||||||
|
message: err,
|
||||||
|
description: "",
|
||||||
|
duration: 0
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
associateBatch = (exam) =>{
|
||||||
|
this.showDrawer(exam);
|
||||||
|
console.log(exam);
|
||||||
|
console.log(this.state.associateBatchVisible);
|
||||||
|
console.log(authenticationService.currentClassValue);
|
||||||
|
console.log(authenticationService.currentClassNameValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleChange = (pageNumber, pageSize) => {
|
||||||
|
selectorService
|
||||||
|
.draftExam({ pageNumber: pageNumber, pageSize: pageSize, classId: 1 })
|
||||||
|
.then((res) => {
|
||||||
|
if (res.status.code === "-1") throw "Something went wrong, please reload the page again"
|
||||||
|
this.setState({
|
||||||
|
data: res.result.exams,
|
||||||
|
total_count: res.result.total_count,
|
||||||
|
total_pages: res.result.total_pages,
|
||||||
|
processing: false,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.log(err);
|
||||||
|
notification.error({
|
||||||
|
message: err, duration: 10
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let drawerWidget = this.state.associateBatchVisible?<Transfer
|
||||||
|
dataSource={this.state.batches}
|
||||||
|
listStyle={{
|
||||||
|
width: "50%",
|
||||||
|
height: 300,
|
||||||
|
}}
|
||||||
|
targetKeys={this.state.selectedBatches}
|
||||||
|
onChange={this.handleChangeBatch}
|
||||||
|
render={item => item.title}
|
||||||
|
locale={{
|
||||||
|
itemUnit: "Batch",
|
||||||
|
itemsUnit: "Batches",
|
||||||
|
searchPlaceholder: "Search here",
|
||||||
|
}}
|
||||||
|
/>:""
|
||||||
|
if (this.state.data.length !== 0)
|
||||||
|
return (
|
||||||
|
<Layout>
|
||||||
|
<Siderc />
|
||||||
|
<Layout>
|
||||||
|
<Headerc />
|
||||||
|
<Content
|
||||||
|
className="site-layout-background"
|
||||||
|
style={{
|
||||||
|
padding: 24,
|
||||||
|
margin: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Layout className="createExamLayoutHeader">Live Exams</Layout>
|
||||||
|
<Layout className="createExamLayout">
|
||||||
|
<List
|
||||||
|
itemLayout="horizontal"
|
||||||
|
dataSource={this.state.data}
|
||||||
|
pagination={{
|
||||||
|
onChange: (page, pageSize) => {
|
||||||
|
this.handleChange(page, pageSize);
|
||||||
|
},
|
||||||
|
|
||||||
|
total: this.state.total_count,
|
||||||
|
}}
|
||||||
|
renderItem={(item, index) => {
|
||||||
|
let step = 2;
|
||||||
|
if (item.sections_count >= 1) step = 3;
|
||||||
|
if (item.sections_status !== "DRAFT") step = 4;
|
||||||
|
let currentTime = new Date();
|
||||||
|
let loginTime = new Date(item.updated_on);
|
||||||
|
const diff = currentTime.getTime() - loginTime.getTime();
|
||||||
|
// console.log(diff,loginTime,currentTime,item.updated_on);
|
||||||
|
var years = Math.floor(
|
||||||
|
diff / (1000 * 60 * 60 * 24 * 30 * 12)
|
||||||
|
);
|
||||||
|
var months = Math.floor(
|
||||||
|
(diff % (1000 * 60 * 60 * 24 * 30 * 12)) /
|
||||||
|
(1000 * 60 * 60 * 24 * 30)
|
||||||
|
);
|
||||||
|
var days = Math.floor(
|
||||||
|
(diff % (1000 * 60 * 60 * 24 * 30)) /
|
||||||
|
(1000 * 60 * 60 * 24)
|
||||||
|
);
|
||||||
|
|
||||||
|
var hours = Math.floor(
|
||||||
|
(diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)
|
||||||
|
);
|
||||||
|
var minutes = Math.floor((diff % 3.6e6) / 6e4);
|
||||||
|
let updatedOn =
|
||||||
|
years > 0
|
||||||
|
? `${years} year(s)/${months} months`
|
||||||
|
: months > 0
|
||||||
|
? `${months} month(s)/${days} days`
|
||||||
|
: days > 0
|
||||||
|
? `${days} day(s)/${hours} hours`
|
||||||
|
: minutes > 0
|
||||||
|
? `${hours} hour(s)${minutes}minutes(s)`
|
||||||
|
: minutes + "minutes";
|
||||||
|
console.log(item);
|
||||||
|
|
||||||
|
let drawerWidget = this.state.associateBatchVisible?<Transfer
|
||||||
|
dataSource={this.state.batches}
|
||||||
|
listStyle={{
|
||||||
|
width: "50%",
|
||||||
|
height: 300,
|
||||||
|
}}
|
||||||
|
targetKeys={this.state.selectedBatches}
|
||||||
|
onChange={this.handleChangeBatch}
|
||||||
|
render={item => item.title}
|
||||||
|
locale={{
|
||||||
|
itemUnit: "Batch",
|
||||||
|
itemsUnit: "Batches",
|
||||||
|
searchPlaceholder: "Search here",
|
||||||
|
}}
|
||||||
|
/>:""
|
||||||
|
return (
|
||||||
|
<List.Item
|
||||||
|
className="DraftExam"
|
||||||
|
key={index}
|
||||||
|
actions={[
|
||||||
|
<Button
|
||||||
|
onClick={(e, newValue) => {
|
||||||
|
console.log(item, e);
|
||||||
|
}}
|
||||||
|
type="primary"
|
||||||
|
>
|
||||||
|
<Link
|
||||||
|
to={{
|
||||||
|
pathname: "",
|
||||||
|
state: { id: item.id, current: step - 1 },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
View Report
|
||||||
|
</Link>
|
||||||
|
</Button>,<Button
|
||||||
|
onClick={(e, newValue) => {
|
||||||
|
this.associateBatch(item);
|
||||||
|
}}
|
||||||
|
type="primary"
|
||||||
|
>
|
||||||
|
<Link
|
||||||
|
to={{
|
||||||
|
pathname: "",
|
||||||
|
state: { id: item.id, current: step - 1 },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Associate Batch
|
||||||
|
</Link>
|
||||||
|
</Button>,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<List.Item.Meta
|
||||||
|
avatar={
|
||||||
|
<Image
|
||||||
|
// src="https://cdn.xl.thumbs.canstockphoto.com/exam-written-on-a-chalkboard-books-pencils-and-an-apple-on-foreground-picture_csp2468961.jpg"
|
||||||
|
src={`${S3_BUCKET_EXAM_PREFIX+item.id}.png`}
|
||||||
|
fallback="https://cdn.xl.thumbs.canstockphoto.com/exam-written-on-a-chalkboard-books-pencils-and-an-apple-on-foreground-picture_csp2468961.jpg"
|
||||||
|
// size="large"
|
||||||
|
// shape="square"
|
||||||
|
width={100}
|
||||||
|
height={100}
|
||||||
|
className="DraftExamImage"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
size="large"
|
||||||
|
bordered="true"
|
||||||
|
title={parse(`<b>${item.name}</b>`)}
|
||||||
|
description={
|
||||||
|
<>
|
||||||
|
<div className="MyExamDiv">
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Start Date</th>
|
||||||
|
<th>End Date</th>
|
||||||
|
<th>Durations</th>
|
||||||
|
<th>Questions</th>
|
||||||
|
<th>Marks</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>{item.start_date}</td>
|
||||||
|
<td>{item.end_date}</td>
|
||||||
|
<td>{item.exam_duration}</td>
|
||||||
|
<td>{item.total_questions}</td>
|
||||||
|
<td>{item.total_marks}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</List.Item>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Layout>
|
||||||
|
</Content>
|
||||||
|
</Layout>
|
||||||
|
{this.state.associateBatchVisible?<Drawer
|
||||||
|
title = {"Associate Exam to Batch for "+ authenticationService.currentClassNameValue + " class"}
|
||||||
|
width={"100%"}
|
||||||
|
placement="right"
|
||||||
|
closable={true}
|
||||||
|
onClose={this.handleCancel}
|
||||||
|
visible={this.state.associateBatchVisible}
|
||||||
|
footer={
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
textAlign: 'left',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button onClick={this.handleCancel} type="primary" style={{ marginRight: 8 }}>
|
||||||
|
Close
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
>
|
||||||
|
<div><br />
|
||||||
|
<p style={{ fontSize: "15px" }}>Add Batches</p>
|
||||||
|
{drawerWidget}
|
||||||
|
</div>
|
||||||
|
</Drawer>:null}
|
||||||
|
</Layout>
|
||||||
|
);
|
||||||
|
else if (this.state.processing)
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Layout>
|
||||||
|
<Siderc />
|
||||||
|
<Layout>
|
||||||
|
<Headerc />
|
||||||
|
|
||||||
|
<Content
|
||||||
|
className="site-layout-background"
|
||||||
|
style={{
|
||||||
|
padding: 24,
|
||||||
|
margin: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Layout className="createExamLayoutHeader">Live Exams</Layout>
|
||||||
|
<Layout className="createExamLayout">
|
||||||
|
<List
|
||||||
|
itemLayout="horizontal"
|
||||||
|
dataSource={[{}, {}, {}, {}, {}, {}, {}, {}, {}, {}]}
|
||||||
|
pagination={{
|
||||||
|
onChange: (page, pageSize) => {
|
||||||
|
this.handleChange(page, pageSize);
|
||||||
|
},
|
||||||
|
|
||||||
|
total: this.state.total_count,
|
||||||
|
}}
|
||||||
|
renderItem={(item, index) => {
|
||||||
|
// let step = 2;
|
||||||
|
// if (item.sections_count >= 1) step = 3;
|
||||||
|
// if (item.sections_status !== "DRAFT") step = 4;
|
||||||
|
// let currentTime = new Date();
|
||||||
|
// let loginTime = new Date(item.updated_on);
|
||||||
|
// const diff = currentTime.getTime() - loginTime.getTime();
|
||||||
|
// // console.log(diff,loginTime,currentTime,item.updated_on);
|
||||||
|
// var years = Math.floor(diff / (1000 * 60 * 60 * 24 * 30 * 12));
|
||||||
|
// var months = Math.floor(
|
||||||
|
// (diff % (1000 * 60 * 60 * 24 * 30 * 12)) / (1000 * 60 * 60 * 24 * 30),
|
||||||
|
// );
|
||||||
|
// var days = Math.floor(
|
||||||
|
// (diff % (1000 * 60 * 60 * 24 * 30)) / (1000 * 60 * 60 * 24),
|
||||||
|
// );
|
||||||
|
|
||||||
|
// var hours = Math.floor(diff%(1000*60*60*24) / (1000 * 60 * 60));
|
||||||
|
// var minutes=Math.floor((diff % 3.6e+6) / 6e+4);
|
||||||
|
// let updatedOn=
|
||||||
|
// years > 0
|
||||||
|
// ? `${years} year(s)/${months} months`
|
||||||
|
// : months > 0
|
||||||
|
// ? `${months} month(s)/${days} days`
|
||||||
|
// : days > 0
|
||||||
|
// ? `${days} day(s)/${hours} hours`
|
||||||
|
// :minutes>0? `${hours} hour(s)${minutes}minutes(s)`:minutes+'minutes';
|
||||||
|
return (
|
||||||
|
<List.Item
|
||||||
|
className="DraftExam"
|
||||||
|
key={index}
|
||||||
|
// actions={[
|
||||||
|
// <Button
|
||||||
|
// onClick={(e, newValue) => {
|
||||||
|
// console.log(item, e);
|
||||||
|
// }}
|
||||||
|
// type="primary"
|
||||||
|
// >
|
||||||
|
// <Link
|
||||||
|
// to={{
|
||||||
|
// pathname: "/createExam",
|
||||||
|
// state: { id: item.id, current: step - 1 },
|
||||||
|
// }}
|
||||||
|
// >
|
||||||
|
// Recreate Exam
|
||||||
|
// </Link>
|
||||||
|
// </Button>,
|
||||||
|
// ]}
|
||||||
|
>
|
||||||
|
<Skeleton>
|
||||||
|
<List.Item.Meta
|
||||||
|
avatar={
|
||||||
|
<Avatar
|
||||||
|
src="https://cdn.xl.thumbs.canstockphoto.com/exam-written-on-a-chalkboard-books-pencils-and-an-apple-on-foreground-picture_csp2468961.jpg"
|
||||||
|
size="large"
|
||||||
|
shape="square"
|
||||||
|
className="DraftExamImage"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
size="large"
|
||||||
|
bordered="true"
|
||||||
|
title={parse(`<b>${"item.name"}</b>`)}
|
||||||
|
description={parse(
|
||||||
|
`<b>Progress - Step</b> ${"step"} <b>Updated At:</b> ${"updatedOn"} <b>Author Name:</b> ${"item.author_name"} <b>Sections Count: </b>${"item.sections_count"}`
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Skeleton>
|
||||||
|
</List.Item>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Layout>
|
||||||
|
</Content>
|
||||||
|
</Layout>
|
||||||
|
</Layout>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
else
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Layout>
|
||||||
|
<Siderc />
|
||||||
|
<Layout>
|
||||||
|
<Headerc />
|
||||||
|
|
||||||
|
<Content
|
||||||
|
className="site-layout-background"
|
||||||
|
style={{
|
||||||
|
padding: 24,
|
||||||
|
margin: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Layout className="createExamLayoutHeader">Live Exams</Layout>
|
||||||
|
<Layout className="createExamLayout">
|
||||||
|
<Empty />
|
||||||
|
</Layout>
|
||||||
|
|
||||||
|
</Content>
|
||||||
|
</Layout>
|
||||||
|
|
||||||
|
</Layout>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default LiveExam;
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
li.DraftExam{
|
||||||
|
-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;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.DraftExamImage{
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
line-height: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.DraftExamImage img{
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,195 @@
|
||||||
|
import { Button, Empty, Image, Layout, Pagination, Radio, Skeleton, Tooltip, Typography, message } from 'antd';
|
||||||
|
import React, { useState, useEffect, useRef } from 'react';
|
||||||
|
import { S3_BUCKET_EXAM_PREFIX } from '../../_services/config';
|
||||||
|
import { selectorService } from '../../services/selectorService';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import './ExamModule.css';
|
||||||
|
import { authenticationService } from '../../_services';
|
||||||
|
import { PauseOutlined } from '@ant-design/icons';
|
||||||
|
import empty from '../EmptyImage';
|
||||||
|
|
||||||
|
const LiveExamNew = () => {
|
||||||
|
const mounted = useRef();
|
||||||
|
const userRole = authenticationService.currentUserValue?.role_id;
|
||||||
|
const [buttonLoadState, setButtonLoadState] = useState(false);
|
||||||
|
const [pageLoading, setPageLoadingState] = useState(true);
|
||||||
|
const [pageState, setPageValues] = useState({
|
||||||
|
total_count: -1,
|
||||||
|
total_pages: -1,
|
||||||
|
pageNumber: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
type: "subject",
|
||||||
|
});
|
||||||
|
const [pageData, setPageData] = useState([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
mounted.current = true;
|
||||||
|
// someFetchingFunctionHere
|
||||||
|
if (pageState.type === "subject") {
|
||||||
|
fetchSubjectLiveExam();
|
||||||
|
}
|
||||||
|
return () => {
|
||||||
|
// cleanup
|
||||||
|
mounted.current = false;
|
||||||
|
};
|
||||||
|
}, [pageState.pageNumber, pageState.type]);
|
||||||
|
|
||||||
|
async function fetchSubjectLiveExam() {
|
||||||
|
if(!pageLoading) {
|
||||||
|
setPageLoadingState(true);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const res = await selectorService.liveExam(pageState);
|
||||||
|
process.env.NODE_ENV === "development" && console.log(res);
|
||||||
|
if(+res.status.code < 0) {
|
||||||
|
if(res.status.message[0] === "No Record(s) Found!") {
|
||||||
|
mounted.current && setPageValues(prev => ({
|
||||||
|
...prev,
|
||||||
|
total_count: 0,
|
||||||
|
total_pages: 0,
|
||||||
|
}));
|
||||||
|
mounted.current && setPageData([]);
|
||||||
|
setPageLoadingState(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else throw res.status.message[0];
|
||||||
|
}
|
||||||
|
mounted.current && res && setPageValues(prev => ({
|
||||||
|
...prev,
|
||||||
|
total_count: res.result.total_count,
|
||||||
|
total_pages: res.result.total_pages,
|
||||||
|
}));
|
||||||
|
mounted.current && res && setPageData(prev => (
|
||||||
|
[...res.result.exams]
|
||||||
|
));
|
||||||
|
setPageLoadingState(false);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
setPageLoadingState(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onChangePageNumber = (page) => {
|
||||||
|
setPageValues(prev => ({...prev, pageNumber: page}));
|
||||||
|
};
|
||||||
|
|
||||||
|
async function onClickPauseButton(id) {
|
||||||
|
setButtonLoadState(true);
|
||||||
|
try {
|
||||||
|
const res = await selectorService.stopPublishedExam(id);
|
||||||
|
if(res === 1) {
|
||||||
|
message.success("Paused the exam successfully!");
|
||||||
|
fetchSubjectLiveExam();
|
||||||
|
} else {
|
||||||
|
message.error("Couldn't pause the exam, operation failed.");
|
||||||
|
}
|
||||||
|
setButtonLoadState(false);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
if(error === 1) {
|
||||||
|
message.success("Paused the exam successfully!");
|
||||||
|
} else {
|
||||||
|
message.error("Couldn't pause the exam, operation failed.");
|
||||||
|
}
|
||||||
|
setButtonLoadState(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Layout className=''>
|
||||||
|
<section className='practice-container exam-container'>
|
||||||
|
<div className='practice-type-selector'>
|
||||||
|
<Radio.Group buttonStyle={"solid"} size={"large"}
|
||||||
|
style={{display: 'grid', gridTemplateColumns: '1fr', width: '100%'}}
|
||||||
|
defaultValue="subject"
|
||||||
|
>
|
||||||
|
<Radio.Button value="subject">Live Exams</Radio.Button>
|
||||||
|
</Radio.Group>
|
||||||
|
</div>
|
||||||
|
<div className='table-look-up--live'>
|
||||||
|
<div className='expired-table-header'>
|
||||||
|
<Typography.Text strong>Image</Typography.Text>
|
||||||
|
<Typography.Text strong>Exam Name</Typography.Text>
|
||||||
|
<Typography.Text strong>Start Date</Typography.Text>
|
||||||
|
<Typography.Text strong>End Date</Typography.Text>
|
||||||
|
<Typography.Text strong>Author</Typography.Text>
|
||||||
|
<Typography.Text strong>Duration</Typography.Text>
|
||||||
|
<Typography.Text strong>Questions</Typography.Text>
|
||||||
|
<Typography.Text strong>Mark</Typography.Text>
|
||||||
|
<Typography.Text strong>Actions</Typography.Text>
|
||||||
|
</div>
|
||||||
|
<div className='live-table-data'>
|
||||||
|
{pageLoading? [0, 1, 2].map((index) => (<div key={index}>
|
||||||
|
<Skeleton.Avatar size={"large"} shape={'square'} active={true} />
|
||||||
|
<Skeleton avatar={false} title={false} paragraph={{rows: 1, width: 120}} active={true} />
|
||||||
|
<Skeleton avatar={false} title={false} paragraph={{rows: 1, width: 80}} active={true} />
|
||||||
|
<Skeleton avatar={false} title={false} paragraph={{rows: 1, width: 50}} active={true} />
|
||||||
|
<Skeleton avatar={false} title={false} paragraph={{rows: 1, width: 50}} active={true} />
|
||||||
|
<Skeleton avatar={false} title={false} paragraph={{rows: 1, width: 50}} active={true} />
|
||||||
|
<Skeleton avatar={false} title={false} paragraph={{rows: 1, width: 20}} active={true} />
|
||||||
|
<Skeleton avatar={false} title={false} paragraph={{rows: 1, width: 20}} active={true} />
|
||||||
|
<span><Skeleton.Button size='small' /> <Skeleton.Button size='small' /></span>
|
||||||
|
</div>))
|
||||||
|
: pageData.length? pageData.map(data => {
|
||||||
|
const startDate = new Date(data.start_date);
|
||||||
|
const endDate = new Date(data.end_date);
|
||||||
|
return <div key={data.id}>
|
||||||
|
<span><Image
|
||||||
|
src={`${S3_BUCKET_EXAM_PREFIX + data.id}.png`}
|
||||||
|
fallback={empty()}
|
||||||
|
width={70}
|
||||||
|
height={70}
|
||||||
|
className="DraftExamImage"
|
||||||
|
/></span>
|
||||||
|
<span><Tooltip title={data.name} placement={"topLeft"}>
|
||||||
|
<Typography.Text ellipsis style={{width: 120}}>{data.name}</Typography.Text>
|
||||||
|
</Tooltip></span>
|
||||||
|
<span>
|
||||||
|
<p className='mb-0-0'>{startDate.toLocaleDateString()}</p>
|
||||||
|
<p className='mb-0-0'>{startDate.toLocaleTimeString('hi', { hour12: true })}</p>
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
<p className='mb-0-0'>{endDate.toLocaleDateString()}</p>
|
||||||
|
<p className='mb-0-0'>{endDate.toLocaleTimeString('hi', { hour12: true })}</p>
|
||||||
|
</span>
|
||||||
|
<span>{data.author_name}</span>
|
||||||
|
<span>{data.exam_duration/60} miuntes</span>
|
||||||
|
<span>{data.total_questions}</span>
|
||||||
|
<span>{data.total_marks}</span>
|
||||||
|
<span>
|
||||||
|
{userRole === 3 && <Typography.Text type='secondary' strong>No actions allowed for Teachers</Typography.Text>}
|
||||||
|
{userRole === 2 && <Tooltip title='Pause'>
|
||||||
|
<Button onClick={() => onClickPauseButton(data.id)} loading={buttonLoadState} shape={'circle'} icon={<PauseOutlined />} danger />
|
||||||
|
</Tooltip>}
|
||||||
|
{userRole === 2 && <Button loading={buttonLoadState}>Clone</Button> }
|
||||||
|
{userRole === 2 && <Button type='primary'>
|
||||||
|
<Link to={{
|
||||||
|
pathname: "/associateBatch",
|
||||||
|
state: {
|
||||||
|
id: data.id,
|
||||||
|
// current: step - 1,
|
||||||
|
start_date: data.start_date,
|
||||||
|
end_date: data.end_date,
|
||||||
|
exam_duration: data.exam_duration,
|
||||||
|
total_questions: data.exam_duration,
|
||||||
|
total_marks: data.total_marks,
|
||||||
|
name: data.name,
|
||||||
|
},
|
||||||
|
}}>
|
||||||
|
Edit Associated Batch
|
||||||
|
</Link>
|
||||||
|
</Button>}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
}): <Empty />}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<div className="button-container mb-1-0">
|
||||||
|
<Pagination defaultCurrent={1} current={pageState.pageNumber} total={pageState.total_count} onChange={onChangePageNumber} />
|
||||||
|
</div>
|
||||||
|
</Layout>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default LiveExamNew;
|
||||||
|
|
@ -0,0 +1,165 @@
|
||||||
|
import React from 'react';
|
||||||
|
import './SelectSubjects.css';
|
||||||
|
import { Button, Form, Space, message, DatePicker, InputNumber } from 'antd';
|
||||||
|
import { FormOutlined } from '@ant-design/icons';
|
||||||
|
import { authenticationService } from '../../_services';
|
||||||
|
import TextArea from 'antd/lib/input/TextArea';
|
||||||
|
import { selectorService } from '../../services/selectorService';
|
||||||
|
import moment from 'moment';
|
||||||
|
import { Trans } from 'react-i18next';
|
||||||
|
|
||||||
|
|
||||||
|
let subject = [
|
||||||
|
|
||||||
|
];
|
||||||
|
class ManageExam extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
currentUser: authenticationService.currentUserValue,
|
||||||
|
examName: props.selectorState.name,
|
||||||
|
update: false,
|
||||||
|
examId: props.selectorState.examId,
|
||||||
|
instruction: "",
|
||||||
|
start_date: "",
|
||||||
|
end_date: "",
|
||||||
|
attempts_allowed: 1,
|
||||||
|
exam_duration: 0
|
||||||
|
};
|
||||||
|
this.onTextAreaInstruction = this.onTextAreaInstruction.bind(this);
|
||||||
|
this.onOkExamAvailabityDate = this.onOkExamAvailabityDate.bind(this);
|
||||||
|
this.onOkExamEndDate = this.onOkExamEndDate.bind(this);
|
||||||
|
this.changeExamDuration = this.changeExamDuration.bind(this);
|
||||||
|
this.changeAttemptAllowed = this.changeAttemptAllowed.bind(this);
|
||||||
|
this.handleSubmitExam = this.handleSubmitExam.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSubmitExam = () => {
|
||||||
|
|
||||||
|
const startDate = new Date(this.state.start_date);
|
||||||
|
const endDate = new Date(this.state.end_date);
|
||||||
|
|
||||||
|
|
||||||
|
if(!this.state.instruction) {
|
||||||
|
message.error("Please set some Instructions for the Exam");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(endDate < startDate) {
|
||||||
|
message.error("Please set the Exam End Date to a date after Exam Start Date");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(this.state.attempts_allowed < 1) {
|
||||||
|
message.error("Please set the attempt allowed to be greater than 0");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.state.exam_duration < 1) {
|
||||||
|
message.error("Please set the exam duration to be greater than or equal to 1");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
message.loading({ content: 'Publishing Exam ...' });
|
||||||
|
|
||||||
|
let json = {
|
||||||
|
id: this.props.selectorState.id,
|
||||||
|
instruction: this.state.instruction,
|
||||||
|
start_date: this.state.start_date,
|
||||||
|
end_date: this.state.end_date,
|
||||||
|
attempts_allowed: this.state.attempts_allowed,
|
||||||
|
exam_duration: (this.state.exam_duration*60*60),
|
||||||
|
}
|
||||||
|
selectorService.publishExam(this.props.selectorState.id, json).then(data => {
|
||||||
|
if(+data.status.code < 0) {
|
||||||
|
throw data.status.message[0]
|
||||||
|
}
|
||||||
|
message.success({ content: 'Exam Published!', duration: 3 });
|
||||||
|
this.props.parentCallback();
|
||||||
|
}).catch(err => {
|
||||||
|
message.error("Failed to publish exam, please try again.");
|
||||||
|
console.log(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onTextAreaInstruction = (event) => {
|
||||||
|
this.setState({
|
||||||
|
instruction: event.target.value
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
changeAttemptAllowed = (value) => {
|
||||||
|
this.setState({
|
||||||
|
attempts_allowed: value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onOkExamAvailabityDate = (date, dateString) => {
|
||||||
|
if(date) {
|
||||||
|
let stDate = date.format("YYYY-MM-DDTHH:mm:ss.sss");
|
||||||
|
this.setState({
|
||||||
|
start_date: stDate,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if(!date) {
|
||||||
|
this.setState(prev => ({...prev, start_date: undefined}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onOkExamEndDate = (date, dateString) => {
|
||||||
|
if(date) {
|
||||||
|
let stDate = date.format("YYYY-MM-DDTHH:mm:ss.sss");
|
||||||
|
this.setState({
|
||||||
|
end_date: stDate,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if(!date) {
|
||||||
|
this.setState(prev => ({...prev, end_date: undefined}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
changeExamDuration = (value) => {
|
||||||
|
this.setState({
|
||||||
|
exam_duration: value
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div className='ManageExam'>
|
||||||
|
<div className="QuestionOtherContent">
|
||||||
|
<div className="ManageExamSpace">
|
||||||
|
<span><Trans i18nKey={"instructions"}>Instructions</Trans></span>
|
||||||
|
<TextArea className='TextArea' autoSize={{ minRows: 3, maxRows: 6 }} placeholder="Instruction" value={this.state.instruction} onChange={e => { this.onTextAreaInstruction(e) }} />
|
||||||
|
|
||||||
|
<span><Trans i18nKey={"exam-avail"}>Exam Availbility</Trans></span>
|
||||||
|
|
||||||
|
<DatePicker showTime={{ format: 'HH:mm' }}
|
||||||
|
// format="YYYY-MM-DDTHH:mm:ss.sss"
|
||||||
|
onChange={this.onOkExamAvailabityDate} />
|
||||||
|
<span><Trans i18nKey={"exam-end-date"}>Exam End Date</Trans></span>
|
||||||
|
|
||||||
|
<DatePicker showTime={{ format: 'HH:mm' }}
|
||||||
|
// format="YYYY-MM-DDTHH:mm:ss.sss"
|
||||||
|
onChange={this.onOkExamEndDate} />
|
||||||
|
<span><Trans i18nKey={"exam-duration"}>Exam Duration (in hours)</Trans></span>
|
||||||
|
<InputNumber defaultValue={this.state.exam_duration} onChange={this.changeExamDuration} min={0} step={1} />
|
||||||
|
<span><Trans i18nKey={"attempts"}>Attempts Allowed</Trans></span>
|
||||||
|
<InputNumber defaultValue={this.state.attempts_allowed} onChange={this.changeAttemptAllowed} min={1} step={1} />
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ManageExam;
|
||||||
|
|
@ -0,0 +1,178 @@
|
||||||
|
import React from "react";
|
||||||
|
import "../AddQuestions.css";
|
||||||
|
import { Layout, Button, message } from "antd";
|
||||||
|
import "antd/dist/antd.css";
|
||||||
|
import Selector from "../Selector";
|
||||||
|
import { authenticationService } from "../../../_services";
|
||||||
|
import QuestionTable from "../QuestionTable";
|
||||||
|
import equal from "fast-deep-equal";
|
||||||
|
import { selectorService } from "../../../services/selectorService";
|
||||||
|
import ReviewQuestions from "./ReviewQuestionPractice";
|
||||||
|
import { InfoCircleFilled } from "@ant-design/icons";
|
||||||
|
import { Trans } from "react-i18next";
|
||||||
|
|
||||||
|
class AddQuestions extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
currentUser: authenticationService.currentUserValue,
|
||||||
|
top: "topLeft",
|
||||||
|
bottom: "bottomRight",
|
||||||
|
classes: "1",
|
||||||
|
subject: "",
|
||||||
|
category: "",
|
||||||
|
questionType: "",
|
||||||
|
complexity: "",
|
||||||
|
classes: props.addQuestion.classes,
|
||||||
|
module_id: props.addQuestion.subjectId,
|
||||||
|
section_id: props.addQuestion.module_id,
|
||||||
|
module: props.addQuestion.module,
|
||||||
|
subjectId: props.addQuestion.subjectId,
|
||||||
|
visible: props.addQuestion.visible,
|
||||||
|
attachedQuestions: [],
|
||||||
|
examId: props.addQuestion.examId,
|
||||||
|
reviewVisible: false,
|
||||||
|
};
|
||||||
|
this.attachQuestion = this.attachQuestion.bind(this);
|
||||||
|
this.reviewQuestions = this.reviewQuestions.bind(this);
|
||||||
|
this.handleSubmit = this.handleSubmit.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
attachQuestion() {
|
||||||
|
const data = {};
|
||||||
|
data.idList = this.state.attachedQuestions;
|
||||||
|
message.loading({ content: "Attaching Question..." });
|
||||||
|
// let examSectionId=this.props.addQuestion.sections.filter(x=>x.label===this.props.addQuestion.sectionName)[0].id;
|
||||||
|
selectorService
|
||||||
|
.attachQuestionsToPractice(this.state.examId, data)
|
||||||
|
.then((data1) => {
|
||||||
|
// console.log(data1);
|
||||||
|
if(+data1.status.code < 1) {
|
||||||
|
throw "Something went wrong, failed to attach question(s)."
|
||||||
|
}
|
||||||
|
message.success({
|
||||||
|
content:
|
||||||
|
"Question Attached to : " + this.props.addQuestion.sectionName,
|
||||||
|
duration: 3,
|
||||||
|
});
|
||||||
|
}).catch(err => {
|
||||||
|
if(err === "Something went wrong, failed to attach question(s).") {
|
||||||
|
message.error({
|
||||||
|
content: err
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
message.error({
|
||||||
|
content: "Operation failed!!!"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
console.log(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
handleCancel = (e) => {
|
||||||
|
this.props.closeComplete(e);
|
||||||
|
};
|
||||||
|
|
||||||
|
handleSubmit = (e) => {
|
||||||
|
return this.ReviewQuestionRef.current.submitReviewQuestion();
|
||||||
|
|
||||||
|
// this.handleCancel();
|
||||||
|
};
|
||||||
|
|
||||||
|
reviewQuestions = () => {
|
||||||
|
this.setState({
|
||||||
|
reviewVisible: true,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
addQuestions = (questions) => {
|
||||||
|
// console.log("Parent recieved Selector Data: "+ childData);
|
||||||
|
// console.log(questions);
|
||||||
|
this.state.attachedQuestions = questions;
|
||||||
|
};
|
||||||
|
|
||||||
|
callbackFunction = (childData) => {
|
||||||
|
// console.log("Parent recieved Selector Data: "+ childData);
|
||||||
|
this.setState({
|
||||||
|
classes: childData.classes,
|
||||||
|
module_id: childData.module_id,
|
||||||
|
subjectId: this.props.addQuestion.subjectId,
|
||||||
|
module: childData.module,
|
||||||
|
questionType: childData.questionType,
|
||||||
|
complexity: childData.complexity,
|
||||||
|
visible: true,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps) {
|
||||||
|
if (!equal(this.props.addQuestion, prevProps.addQuestion)) {
|
||||||
|
// Check if it's a new user, you can also use some unique property, like the ID (this.props.user.id !== prevProps.user.id)
|
||||||
|
this.setState({
|
||||||
|
classes: this.props.addQuestion.classes,
|
||||||
|
module_id: this.props.addQuestion.module_id,
|
||||||
|
module: this.props.addQuestion.module,
|
||||||
|
subjectId: this.props.addQuestion.subjectId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.setState({
|
||||||
|
classes: this.props.addQuestion.classes,
|
||||||
|
module_id: this.props.addQuestion.module_id,
|
||||||
|
module: this.props.addQuestion.module,
|
||||||
|
subjectId: this.props.addQuestion.subjectId,
|
||||||
|
});
|
||||||
|
this.ReviewQuestionRef = React.createRef();
|
||||||
|
}
|
||||||
|
render() {
|
||||||
|
// console.log(this.state, this.props);
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{this.props.step === 0 && <Layout>
|
||||||
|
<Layout>
|
||||||
|
<Layout className="SubSection1">
|
||||||
|
<div className="ExamSelector">
|
||||||
|
<Selector
|
||||||
|
page="addQuestions"
|
||||||
|
selectorState={this.props.addQuestion}
|
||||||
|
parentCallback={this.callbackFunction}
|
||||||
|
/>
|
||||||
|
<div className="selector">
|
||||||
|
<Button
|
||||||
|
className="selectorLeftButton"
|
||||||
|
type="primary"
|
||||||
|
onClick={this.attachQuestion}
|
||||||
|
>
|
||||||
|
<Trans i18nKey={"attach-btn"}>Attach</Trans>
|
||||||
|
</Button>
|
||||||
|
<div className="subheader-info">
|
||||||
|
<InfoCircleFilled style={{color: "darkorange", fontSize: 16}} /> <span>
|
||||||
|
<Trans i18nKey={"note-in-attach-modal"}>Select questions and 'ATTACH' them before moving to next page.</Trans>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style={{margin: '20px 20px 0'}}>
|
||||||
|
<div>
|
||||||
|
<QuestionTable
|
||||||
|
selectorState={this.state}
|
||||||
|
parentCallback={this.addQuestions}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Layout>
|
||||||
|
</Layout>
|
||||||
|
</Layout>}
|
||||||
|
{this.state.reviewVisible && this.props.step === 1 ? (
|
||||||
|
<ReviewQuestions
|
||||||
|
ref={this.ReviewQuestionRef}
|
||||||
|
sectionState={this.props.addQuestion}
|
||||||
|
handleCancel={this.handleCancel}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AddQuestions;
|
||||||