first commit

This commit is contained in:
skillens.ai 2025-10-27 20:16:51 +05:30
commit d305baee71
295 changed files with 89515 additions and 0 deletions

10
.env Normal file
View File

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

View File

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

15
.firebaserc Normal file
View File

@ -0,0 +1,15 @@
{
"projects": {
"default": "practice-kea-7cb5b"
},
"targets": {
"practice-kea-7cb5b": {
"hosting": {
"default": [
"practicekea"
]
}
}
},
"etags": {}
}

45
.gitignore vendored Normal file
View File

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

1
Book.csv Normal file
View File

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

14
README.md Normal file
View File

@ -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.

25
craco.config.js Normal file
View File

@ -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,
},
},
},
},
],
};

17
firebase.json Normal file
View File

@ -0,0 +1,17 @@
{
"hosting": {
"target" : "default",
"public": "build",
"ignore": [
"firebase.json",
"**/.*",
"**/node_modules/**"
],
"rewrites": [
{
"source": "**",
"destination": "/index.html"
}
]
}
}

46094
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

81
package.json Normal file
View File

@ -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"
}
}

62
package_old.json.bkp Normal file
View File

@ -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"
]
}
}

9
public/.htaccess Normal file
View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

View File

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

55
public/favicon.svg Normal file
View File

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

46
public/index.html Normal file
View File

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

52
public/manifest.json Normal file
View File

@ -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"
}
]
}

3
public/robots.txt Normal file
View File

@ -0,0 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

7
src/App.css Normal file
View File

@ -0,0 +1,7 @@
.App {
text-align: center;
}
.ant-layout {
background: #ffffff00;
}

150
src/App.js Normal file
View File

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

81
src/App.less Normal file
View File

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

9
src/App.test.js Normal file
View File

@ -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();
});

106
src/Theme/AppDark.less Normal file
View File

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

89
src/Theme/AppLight.less Normal file
View File

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

6
src/Theme/Dark.jsx Normal file
View File

@ -0,0 +1,6 @@
import React from "react";
import "./AppDark.less";
import "./variableDark.less";
const Dark = () => <></>;
export default Dark;

6
src/Theme/Light.jsx Normal file
View File

@ -0,0 +1,6 @@
import React from "react";
import "./AppLight.less";
import "./variableLight.less";
const Light = () => <></>;
export default Light;

View File

@ -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}
</>
);
};

19
src/Theme/useTheme.jsx Normal file
View File

@ -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];
};

View File

@ -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);
}

View File

@ -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);
}

119
src/UtilityClass.less Normal file
View File

@ -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));
}

View File

@ -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 {};
}
}

View File

@ -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);
});
}
}

View File

@ -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;
});
}

3
src/_helpers/history.js Normal file
View File

@ -0,0 +1,3 @@
import { createBrowserHistory } from 'history';
export const history = createBrowserHistory();

4
src/_helpers/index.js Normal file
View File

@ -0,0 +1,4 @@
export * from './auth-header';
export * from './fake-backend';
export * from './handle-response';
export * from './history';

View File

@ -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." }
}
}

21
src/_services/config.js Normal file
View File

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

View File

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

2
src/_services/index.js Normal file
View File

@ -0,0 +1,2 @@
export * from './authentication.service';
export * from './user.service';

View File

@ -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));
// });
// }

View File

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

View File

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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>
);
}

View File

@ -0,0 +1,3 @@
.logged-in-view {
height: 100%;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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=="
}

View File

@ -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>);
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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; */
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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>);
};

View File

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

View File

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

View File

@ -0,0 +1,3 @@
export const ItemTypes = {
CARD: 'card',
}

View File

@ -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>);
};

View File

@ -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>
);
};

View File

@ -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 */
}

View File

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

View File

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

View File

@ -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)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

Some files were not shown because too many files have changed in this diff Show More