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;
|
||||