Spaces:
Sleeping
Sleeping
quachtiensinh27 commited on
Commit ·
3924714
1
Parent(s): bbd714b
feat: Add Quiz Interactive feature - Phase 1 (Database + Backend)
Browse files- Database: quizzes, questions, quiz_attempts, quiz_summaries tables
- RLS policies for TA/student access control
- Backend API: generate, edit, send, submit, summary endpoints
- AI integration for quiz generation from recap content
- Fixed all security issues from code review
- Added checkSpaceMembership for student endpoints
- Prompt injection protection (topic validation)
- Quiz mutability check after attempts
- JSON parsing error handling
- API response format standardization
- Bulk insert optimization
- Added composite index for summaries
- package-lock.json +24 -13
- scripts/log_hook.py +1 -0
- src/components/ta/QuizMockupOptionA.html +758 -0
- src/components/ta/QuizMockupOptionB.html +797 -0
- src/components/ta/RecapWorkflow.jsx +29 -43
- src/pages/TADashboard.css +43 -0
- src/pages/TADashboard.jsx +147 -20
- src/services/ta.service.js +9 -0
package-lock.json
CHANGED
|
@@ -64,7 +64,6 @@
|
|
| 64 |
"integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
|
| 65 |
"dev": true,
|
| 66 |
"license": "MIT",
|
| 67 |
-
"peer": true,
|
| 68 |
"dependencies": {
|
| 69 |
"@babel/code-frame": "^7.29.0",
|
| 70 |
"@babel/generator": "^7.29.0",
|
|
@@ -274,6 +273,29 @@
|
|
| 274 |
"node": ">=6.9.0"
|
| 275 |
}
|
| 276 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 277 |
"node_modules/@emnapi/wasi-threads": {
|
| 278 |
"version": "1.2.1",
|
| 279 |
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz",
|
|
@@ -1310,7 +1332,6 @@
|
|
| 1310 |
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz",
|
| 1311 |
"integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
|
| 1312 |
"license": "MIT",
|
| 1313 |
-
"peer": true,
|
| 1314 |
"dependencies": {
|
| 1315 |
"csstype": "^3.2.2"
|
| 1316 |
}
|
|
@@ -1375,7 +1396,6 @@
|
|
| 1375 |
"integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
|
| 1376 |
"dev": true,
|
| 1377 |
"license": "MIT",
|
| 1378 |
-
"peer": true,
|
| 1379 |
"bin": {
|
| 1380 |
"acorn": "bin/acorn"
|
| 1381 |
},
|
|
@@ -1511,7 +1531,6 @@
|
|
| 1511 |
}
|
| 1512 |
],
|
| 1513 |
"license": "MIT",
|
| 1514 |
-
"peer": true,
|
| 1515 |
"dependencies": {
|
| 1516 |
"baseline-browser-mapping": "^2.10.12",
|
| 1517 |
"caniuse-lite": "^1.0.30001782",
|
|
@@ -2069,7 +2088,6 @@
|
|
| 2069 |
"integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==",
|
| 2070 |
"dev": true,
|
| 2071 |
"license": "MIT",
|
| 2072 |
-
"peer": true,
|
| 2073 |
"dependencies": {
|
| 2074 |
"@eslint-community/eslint-utils": "^4.8.0",
|
| 2075 |
"@eslint-community/regexpp": "^4.12.1",
|
|
@@ -4001,7 +4019,6 @@
|
|
| 4001 |
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
|
| 4002 |
"dev": true,
|
| 4003 |
"license": "MIT",
|
| 4004 |
-
"peer": true,
|
| 4005 |
"engines": {
|
| 4006 |
"node": ">=12"
|
| 4007 |
},
|
|
@@ -4082,7 +4099,6 @@
|
|
| 4082 |
"resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
|
| 4083 |
"integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==",
|
| 4084 |
"license": "MIT",
|
| 4085 |
-
"peer": true,
|
| 4086 |
"engines": {
|
| 4087 |
"node": ">=0.10.0"
|
| 4088 |
}
|
|
@@ -4092,7 +4108,6 @@
|
|
| 4092 |
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz",
|
| 4093 |
"integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==",
|
| 4094 |
"license": "MIT",
|
| 4095 |
-
"peer": true,
|
| 4096 |
"dependencies": {
|
| 4097 |
"scheduler": "^0.27.0"
|
| 4098 |
},
|
|
@@ -4148,7 +4163,6 @@
|
|
| 4148 |
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz",
|
| 4149 |
"integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==",
|
| 4150 |
"license": "MIT",
|
| 4151 |
-
"peer": true,
|
| 4152 |
"dependencies": {
|
| 4153 |
"@types/use-sync-external-store": "^0.0.6",
|
| 4154 |
"use-sync-external-store": "^1.4.0"
|
|
@@ -4211,8 +4225,7 @@
|
|
| 4211 |
"version": "5.0.1",
|
| 4212 |
"resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
|
| 4213 |
"integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
|
| 4214 |
-
"license": "MIT"
|
| 4215 |
-
"peer": true
|
| 4216 |
},
|
| 4217 |
"node_modules/redux-thunk": {
|
| 4218 |
"version": "3.1.0",
|
|
@@ -4736,7 +4749,6 @@
|
|
| 4736 |
"integrity": "sha512-dbU7/iLVa8KZALJyLOBOQ88nOXtNG8vxKuOT4I2mD+Ya70KPceF4IAmDsmU0h1Qsn5bPrvsY9HJstCRh3hG6Uw==",
|
| 4737 |
"dev": true,
|
| 4738 |
"license": "MIT",
|
| 4739 |
-
"peer": true,
|
| 4740 |
"dependencies": {
|
| 4741 |
"lightningcss": "^1.32.0",
|
| 4742 |
"picomatch": "^4.0.4",
|
|
@@ -4890,7 +4902,6 @@
|
|
| 4890 |
"integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
|
| 4891 |
"dev": true,
|
| 4892 |
"license": "MIT",
|
| 4893 |
-
"peer": true,
|
| 4894 |
"funding": {
|
| 4895 |
"url": "https://github.com/sponsors/colinhacks"
|
| 4896 |
}
|
|
|
|
| 64 |
"integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
|
| 65 |
"dev": true,
|
| 66 |
"license": "MIT",
|
|
|
|
| 67 |
"dependencies": {
|
| 68 |
"@babel/code-frame": "^7.29.0",
|
| 69 |
"@babel/generator": "^7.29.0",
|
|
|
|
| 273 |
"node": ">=6.9.0"
|
| 274 |
}
|
| 275 |
},
|
| 276 |
+
"node_modules/@emnapi/core": {
|
| 277 |
+
"version": "1.10.0",
|
| 278 |
+
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz",
|
| 279 |
+
"integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==",
|
| 280 |
+
"dev": true,
|
| 281 |
+
"license": "MIT",
|
| 282 |
+
"optional": true,
|
| 283 |
+
"dependencies": {
|
| 284 |
+
"@emnapi/wasi-threads": "1.2.1",
|
| 285 |
+
"tslib": "^2.4.0"
|
| 286 |
+
}
|
| 287 |
+
},
|
| 288 |
+
"node_modules/@emnapi/runtime": {
|
| 289 |
+
"version": "1.10.0",
|
| 290 |
+
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz",
|
| 291 |
+
"integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==",
|
| 292 |
+
"dev": true,
|
| 293 |
+
"license": "MIT",
|
| 294 |
+
"optional": true,
|
| 295 |
+
"dependencies": {
|
| 296 |
+
"tslib": "^2.4.0"
|
| 297 |
+
}
|
| 298 |
+
},
|
| 299 |
"node_modules/@emnapi/wasi-threads": {
|
| 300 |
"version": "1.2.1",
|
| 301 |
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz",
|
|
|
|
| 1332 |
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz",
|
| 1333 |
"integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
|
| 1334 |
"license": "MIT",
|
|
|
|
| 1335 |
"dependencies": {
|
| 1336 |
"csstype": "^3.2.2"
|
| 1337 |
}
|
|
|
|
| 1396 |
"integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
|
| 1397 |
"dev": true,
|
| 1398 |
"license": "MIT",
|
|
|
|
| 1399 |
"bin": {
|
| 1400 |
"acorn": "bin/acorn"
|
| 1401 |
},
|
|
|
|
| 1531 |
}
|
| 1532 |
],
|
| 1533 |
"license": "MIT",
|
|
|
|
| 1534 |
"dependencies": {
|
| 1535 |
"baseline-browser-mapping": "^2.10.12",
|
| 1536 |
"caniuse-lite": "^1.0.30001782",
|
|
|
|
| 2088 |
"integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==",
|
| 2089 |
"dev": true,
|
| 2090 |
"license": "MIT",
|
|
|
|
| 2091 |
"dependencies": {
|
| 2092 |
"@eslint-community/eslint-utils": "^4.8.0",
|
| 2093 |
"@eslint-community/regexpp": "^4.12.1",
|
|
|
|
| 4019 |
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
|
| 4020 |
"dev": true,
|
| 4021 |
"license": "MIT",
|
|
|
|
| 4022 |
"engines": {
|
| 4023 |
"node": ">=12"
|
| 4024 |
},
|
|
|
|
| 4099 |
"resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
|
| 4100 |
"integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==",
|
| 4101 |
"license": "MIT",
|
|
|
|
| 4102 |
"engines": {
|
| 4103 |
"node": ">=0.10.0"
|
| 4104 |
}
|
|
|
|
| 4108 |
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz",
|
| 4109 |
"integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==",
|
| 4110 |
"license": "MIT",
|
|
|
|
| 4111 |
"dependencies": {
|
| 4112 |
"scheduler": "^0.27.0"
|
| 4113 |
},
|
|
|
|
| 4163 |
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz",
|
| 4164 |
"integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==",
|
| 4165 |
"license": "MIT",
|
|
|
|
| 4166 |
"dependencies": {
|
| 4167 |
"@types/use-sync-external-store": "^0.0.6",
|
| 4168 |
"use-sync-external-store": "^1.4.0"
|
|
|
|
| 4225 |
"version": "5.0.1",
|
| 4226 |
"resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
|
| 4227 |
"integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
|
| 4228 |
+
"license": "MIT"
|
|
|
|
| 4229 |
},
|
| 4230 |
"node_modules/redux-thunk": {
|
| 4231 |
"version": "3.1.0",
|
|
|
|
| 4749 |
"integrity": "sha512-dbU7/iLVa8KZALJyLOBOQ88nOXtNG8vxKuOT4I2mD+Ya70KPceF4IAmDsmU0h1Qsn5bPrvsY9HJstCRh3hG6Uw==",
|
| 4750 |
"dev": true,
|
| 4751 |
"license": "MIT",
|
|
|
|
| 4752 |
"dependencies": {
|
| 4753 |
"lightningcss": "^1.32.0",
|
| 4754 |
"picomatch": "^4.0.4",
|
|
|
|
| 4902 |
"integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
|
| 4903 |
"dev": true,
|
| 4904 |
"license": "MIT",
|
|
|
|
| 4905 |
"funding": {
|
| 4906 |
"url": "https://github.com/sponsors/colinhacks"
|
| 4907 |
}
|
scripts/log_hook.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
# Placeholder
|
src/components/ta/QuizMockupOptionA.html
ADDED
|
@@ -0,0 +1,758 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="vi">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>Quiz Mockup - Option A (Inline Quiz Generation)</title>
|
| 7 |
+
<style>
|
| 8 |
+
:root {
|
| 9 |
+
--primary: #3b82f6;
|
| 10 |
+
--primary-active: #2563eb;
|
| 11 |
+
--bg-surface: #ffffff;
|
| 12 |
+
--bg-surface-secondary: #f8fafc;
|
| 13 |
+
--bg-surface-tertiary: #f1f5f9;
|
| 14 |
+
--text-primary: #1e293b;
|
| 15 |
+
--text-secondary: #475569;
|
| 16 |
+
--text-muted: #94a3b8;
|
| 17 |
+
--border-primary: #e2e8f0;
|
| 18 |
+
--ta-red: #ef4444;
|
| 19 |
+
--ta-red-bg: rgba(239, 68, 68, 0.1);
|
| 20 |
+
--ta-amber: #f59e0b;
|
| 21 |
+
--ta-amber-bg: rgba(245, 158, 11, 0.1);
|
| 22 |
+
--ta-blue: #3b82f6;
|
| 23 |
+
--ta-blue-bg: rgba(59, 130, 246, 0.1);
|
| 24 |
+
--ta-green: #10b981;
|
| 25 |
+
--ta-green-bg: rgba(16, 185, 129, 0.1);
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
+
* {
|
| 29 |
+
margin: 0;
|
| 30 |
+
padding: 0;
|
| 31 |
+
box-sizing: border-box;
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
body {
|
| 35 |
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
| 36 |
+
background: var(--bg-surface-secondary);
|
| 37 |
+
color: var(--text-primary);
|
| 38 |
+
line-height: 1.5;
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
.mockup-container {
|
| 42 |
+
max-width: 1400px;
|
| 43 |
+
margin: 0 auto;
|
| 44 |
+
padding: 40px 24px;
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
.mockup-header {
|
| 48 |
+
text-align: center;
|
| 49 |
+
margin-bottom: 40px;
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
.mockup-header h1 {
|
| 53 |
+
font-size: 28px;
|
| 54 |
+
font-weight: 700;
|
| 55 |
+
color: var(--primary);
|
| 56 |
+
margin-bottom: 8px;
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
.mockup-header p {
|
| 60 |
+
font-size: 14px;
|
| 61 |
+
color: var(--text-muted);
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
.option-badge {
|
| 65 |
+
display: inline-block;
|
| 66 |
+
padding: 6px 16px;
|
| 67 |
+
background: var(--primary);
|
| 68 |
+
color: white;
|
| 69 |
+
border-radius: 20px;
|
| 70 |
+
font-size: 12px;
|
| 71 |
+
font-weight: 600;
|
| 72 |
+
margin-bottom: 16px;
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
/* Main Card Container */
|
| 76 |
+
.quiz-container {
|
| 77 |
+
background: var(--bg-surface-secondary);
|
| 78 |
+
border: 1px solid var(--border-primary);
|
| 79 |
+
border-radius: 12px;
|
| 80 |
+
overflow: hidden;
|
| 81 |
+
height: 800px;
|
| 82 |
+
display: flex;
|
| 83 |
+
flex-direction: column;
|
| 84 |
+
}
|
| 85 |
+
|
| 86 |
+
/* Header */
|
| 87 |
+
.quiz-header {
|
| 88 |
+
padding: 16px 24px;
|
| 89 |
+
border-bottom: 1px solid var(--border-primary);
|
| 90 |
+
background: var(--bg-surface-tertiary);
|
| 91 |
+
display: flex;
|
| 92 |
+
align-items: center;
|
| 93 |
+
justify-content: space-between;
|
| 94 |
+
flex-shrink: 0;
|
| 95 |
+
}
|
| 96 |
+
|
| 97 |
+
.quiz-header-left {
|
| 98 |
+
display: flex;
|
| 99 |
+
align-items: center;
|
| 100 |
+
gap: 12px;
|
| 101 |
+
}
|
| 102 |
+
|
| 103 |
+
.quiz-icon {
|
| 104 |
+
width: 32px;
|
| 105 |
+
height: 32px;
|
| 106 |
+
background: var(--primary);
|
| 107 |
+
border-radius: 8px;
|
| 108 |
+
display: flex;
|
| 109 |
+
align-items: center;
|
| 110 |
+
justify-content: center;
|
| 111 |
+
color: white;
|
| 112 |
+
}
|
| 113 |
+
|
| 114 |
+
.quiz-title h3 {
|
| 115 |
+
font-size: 15px;
|
| 116 |
+
font-weight: 700;
|
| 117 |
+
margin: 0;
|
| 118 |
+
}
|
| 119 |
+
|
| 120 |
+
.quiz-title p {
|
| 121 |
+
font-size: 12px;
|
| 122 |
+
color: var(--text-muted);
|
| 123 |
+
margin-top: 2px;
|
| 124 |
+
}
|
| 125 |
+
|
| 126 |
+
.quiz-actions {
|
| 127 |
+
display: flex;
|
| 128 |
+
gap: 8px;
|
| 129 |
+
}
|
| 130 |
+
|
| 131 |
+
.quiz-btn {
|
| 132 |
+
padding: 6px 14px;
|
| 133 |
+
border-radius: 8px;
|
| 134 |
+
border: 1px solid var(--border-primary);
|
| 135 |
+
background: var(--bg-surface);
|
| 136 |
+
color: var(--text-secondary);
|
| 137 |
+
font-size: 12px;
|
| 138 |
+
font-weight: 600;
|
| 139 |
+
cursor: pointer;
|
| 140 |
+
transition: all 0.2s;
|
| 141 |
+
}
|
| 142 |
+
|
| 143 |
+
.quiz-btn:hover {
|
| 144 |
+
border-color: var(--primary);
|
| 145 |
+
color: var(--primary);
|
| 146 |
+
}
|
| 147 |
+
|
| 148 |
+
.quiz-btn.primary {
|
| 149 |
+
background: var(--primary);
|
| 150 |
+
color: white;
|
| 151 |
+
border-color: var(--primary);
|
| 152 |
+
}
|
| 153 |
+
|
| 154 |
+
/* Score Summary Bar */
|
| 155 |
+
.score-bar {
|
| 156 |
+
padding: 16px 24px;
|
| 157 |
+
background: linear-gradient(135deg, var(--ta-blue-bg) 0%, rgba(59, 130, 246, 0.05) 100%);
|
| 158 |
+
border-bottom: 1px solid var(--border-primary);
|
| 159 |
+
display: flex;
|
| 160 |
+
align-items: center;
|
| 161 |
+
justify-content: space-between;
|
| 162 |
+
flex-shrink: 0;
|
| 163 |
+
}
|
| 164 |
+
|
| 165 |
+
.score-left {
|
| 166 |
+
display: flex;
|
| 167 |
+
align-items: center;
|
| 168 |
+
gap: 16px;
|
| 169 |
+
}
|
| 170 |
+
|
| 171 |
+
.score-circle {
|
| 172 |
+
width: 56px;
|
| 173 |
+
height: 56px;
|
| 174 |
+
border-radius: 50%;
|
| 175 |
+
background: var(--primary);
|
| 176 |
+
display: flex;
|
| 177 |
+
flex-direction: column;
|
| 178 |
+
align-items: center;
|
| 179 |
+
justify-content: center;
|
| 180 |
+
color: white;
|
| 181 |
+
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3);
|
| 182 |
+
}
|
| 183 |
+
|
| 184 |
+
.score-number {
|
| 185 |
+
font-size: 20px;
|
| 186 |
+
font-weight: 700;
|
| 187 |
+
line-height: 1;
|
| 188 |
+
}
|
| 189 |
+
|
| 190 |
+
.score-label {
|
| 191 |
+
font-size: 10px;
|
| 192 |
+
font-weight: 600;
|
| 193 |
+
opacity: 0.9;
|
| 194 |
+
}
|
| 195 |
+
|
| 196 |
+
.score-stats {
|
| 197 |
+
display: flex;
|
| 198 |
+
gap: 24px;
|
| 199 |
+
}
|
| 200 |
+
|
| 201 |
+
.stat-item {
|
| 202 |
+
display: flex;
|
| 203 |
+
align-items: center;
|
| 204 |
+
gap: 8px;
|
| 205 |
+
}
|
| 206 |
+
|
| 207 |
+
.stat-dot {
|
| 208 |
+
width: 12px;
|
| 209 |
+
height: 12px;
|
| 210 |
+
border-radius: 50%;
|
| 211 |
+
}
|
| 212 |
+
|
| 213 |
+
.stat-dot.correct { background: var(--ta-green); }
|
| 214 |
+
.stat-dot.wrong { background: var(--ta-red); }
|
| 215 |
+
.stat-dot.pending { background: var(--text-muted); }
|
| 216 |
+
|
| 217 |
+
.stat-text {
|
| 218 |
+
display: flex;
|
| 219 |
+
flex-direction: column;
|
| 220 |
+
}
|
| 221 |
+
|
| 222 |
+
.stat-value {
|
| 223 |
+
font-size: 16px;
|
| 224 |
+
font-weight: 700;
|
| 225 |
+
line-height: 1;
|
| 226 |
+
}
|
| 227 |
+
|
| 228 |
+
.stat-label {
|
| 229 |
+
font-size: 10px;
|
| 230 |
+
color: var(--text-muted);
|
| 231 |
+
text-transform: uppercase;
|
| 232 |
+
}
|
| 233 |
+
|
| 234 |
+
.progress-bar {
|
| 235 |
+
width: 200px;
|
| 236 |
+
height: 8px;
|
| 237 |
+
background: var(--bg-surface);
|
| 238 |
+
border-radius: 4px;
|
| 239 |
+
overflow: hidden;
|
| 240 |
+
}
|
| 241 |
+
|
| 242 |
+
.progress-fill {
|
| 243 |
+
height: 100%;
|
| 244 |
+
background: linear-gradient(90deg, var(--primary) 0%, var(--ta-green) 100%);
|
| 245 |
+
border-radius: 4px;
|
| 246 |
+
transition: width 0.3s;
|
| 247 |
+
}
|
| 248 |
+
|
| 249 |
+
.progress-text {
|
| 250 |
+
font-size: 11px;
|
| 251 |
+
color: var(--text-muted);
|
| 252 |
+
margin-left: 8px;
|
| 253 |
+
}
|
| 254 |
+
|
| 255 |
+
/* Quiz Content Area */
|
| 256 |
+
.quiz-content {
|
| 257 |
+
flex: 1;
|
| 258 |
+
overflow-y: auto;
|
| 259 |
+
padding: 24px;
|
| 260 |
+
}
|
| 261 |
+
|
| 262 |
+
.quiz-content::-webkit-scrollbar {
|
| 263 |
+
width: 6px;
|
| 264 |
+
}
|
| 265 |
+
|
| 266 |
+
.quiz-content::-webkit-scrollbar-track {
|
| 267 |
+
background: transparent;
|
| 268 |
+
}
|
| 269 |
+
|
| 270 |
+
.quiz-content::-webkit-scrollbar-thumb {
|
| 271 |
+
background: var(--border-primary);
|
| 272 |
+
border-radius: 10px;
|
| 273 |
+
}
|
| 274 |
+
|
| 275 |
+
/* Question Card */
|
| 276 |
+
.question-card {
|
| 277 |
+
background: var(--bg-surface);
|
| 278 |
+
border: 1px solid var(--border-primary);
|
| 279 |
+
border-radius: 12px;
|
| 280 |
+
padding: 20px 24px;
|
| 281 |
+
margin-bottom: 16px;
|
| 282 |
+
transition: all 0.2s;
|
| 283 |
+
}
|
| 284 |
+
|
| 285 |
+
.question-card:hover {
|
| 286 |
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
|
| 287 |
+
}
|
| 288 |
+
|
| 289 |
+
.question-header {
|
| 290 |
+
display: flex;
|
| 291 |
+
align-items: flex-start;
|
| 292 |
+
gap: 12px;
|
| 293 |
+
margin-bottom: 16px;
|
| 294 |
+
}
|
| 295 |
+
|
| 296 |
+
.question-number {
|
| 297 |
+
width: 28px;
|
| 298 |
+
height: 28px;
|
| 299 |
+
border-radius: 6px;
|
| 300 |
+
background: var(--bg-surface-tertiary);
|
| 301 |
+
color: var(--text-muted);
|
| 302 |
+
display: flex;
|
| 303 |
+
align-items: center;
|
| 304 |
+
justify-content: center;
|
| 305 |
+
font-size: 12px;
|
| 306 |
+
font-weight: 700;
|
| 307 |
+
flex-shrink: 0;
|
| 308 |
+
}
|
| 309 |
+
|
| 310 |
+
.question-text {
|
| 311 |
+
font-size: 14px;
|
| 312 |
+
font-weight: 600;
|
| 313 |
+
line-height: 1.5;
|
| 314 |
+
flex: 1;
|
| 315 |
+
}
|
| 316 |
+
|
| 317 |
+
.question-card.correct .question-number {
|
| 318 |
+
background: var(--ta-green-bg);
|
| 319 |
+
color: var(--ta-green);
|
| 320 |
+
}
|
| 321 |
+
|
| 322 |
+
.question-card.wrong .question-number {
|
| 323 |
+
background: var(--ta-red-bg);
|
| 324 |
+
color: var(--ta-red);
|
| 325 |
+
}
|
| 326 |
+
|
| 327 |
+
.question-card.pending .question-number {
|
| 328 |
+
background: var(--bg-surface-tertiary);
|
| 329 |
+
color: var(--text-muted);
|
| 330 |
+
}
|
| 331 |
+
|
| 332 |
+
/* Options */
|
| 333 |
+
.options-list {
|
| 334 |
+
display: flex;
|
| 335 |
+
flex-direction: column;
|
| 336 |
+
gap: 8px;
|
| 337 |
+
}
|
| 338 |
+
|
| 339 |
+
.option-item {
|
| 340 |
+
display: flex;
|
| 341 |
+
align-items: center;
|
| 342 |
+
gap: 12px;
|
| 343 |
+
padding: 12px 16px;
|
| 344 |
+
border: 1px solid var(--border-primary);
|
| 345 |
+
border-radius: 8px;
|
| 346 |
+
cursor: pointer;
|
| 347 |
+
transition: all 0.2s;
|
| 348 |
+
background: var(--bg-surface-tertiary);
|
| 349 |
+
}
|
| 350 |
+
|
| 351 |
+
.option-item:hover {
|
| 352 |
+
border-color: var(--primary);
|
| 353 |
+
background: var(--ta-blue-bg);
|
| 354 |
+
}
|
| 355 |
+
|
| 356 |
+
.option-item.selected {
|
| 357 |
+
border-color: var(--primary);
|
| 358 |
+
background: var(--ta-blue-bg);
|
| 359 |
+
}
|
| 360 |
+
|
| 361 |
+
.option-item.correct-answer {
|
| 362 |
+
border-color: var(--ta-green);
|
| 363 |
+
background: var(--ta-green-bg);
|
| 364 |
+
}
|
| 365 |
+
|
| 366 |
+
.option-item.wrong-answer {
|
| 367 |
+
border-color: var(--ta-red);
|
| 368 |
+
background: var(--ta-red-bg);
|
| 369 |
+
}
|
| 370 |
+
|
| 371 |
+
.option-letter {
|
| 372 |
+
width: 24px;
|
| 373 |
+
height: 24px;
|
| 374 |
+
border-radius: 50%;
|
| 375 |
+
background: var(--bg-surface);
|
| 376 |
+
border: 2px solid var(--border-primary);
|
| 377 |
+
display: flex;
|
| 378 |
+
align-items: center;
|
| 379 |
+
justify-content: center;
|
| 380 |
+
font-size: 11px;
|
| 381 |
+
font-weight: 700;
|
| 382 |
+
flex-shrink: 0;
|
| 383 |
+
}
|
| 384 |
+
|
| 385 |
+
.option-item.correct-answer .option-letter {
|
| 386 |
+
background: var(--ta-green);
|
| 387 |
+
border-color: var(--ta-green);
|
| 388 |
+
color: white;
|
| 389 |
+
}
|
| 390 |
+
|
| 391 |
+
.option-item.wrong-answer .option-letter {
|
| 392 |
+
background: var(--ta-red);
|
| 393 |
+
border-color: var(--ta-red);
|
| 394 |
+
color: white;
|
| 395 |
+
}
|
| 396 |
+
|
| 397 |
+
.option-text {
|
| 398 |
+
flex: 1;
|
| 399 |
+
font-size: 13px;
|
| 400 |
+
color: var(--text-secondary);
|
| 401 |
+
}
|
| 402 |
+
|
| 403 |
+
.option-feedback {
|
| 404 |
+
display: flex;
|
| 405 |
+
align-items: center;
|
| 406 |
+
gap: 6px;
|
| 407 |
+
font-size: 12px;
|
| 408 |
+
font-weight: 600;
|
| 409 |
+
}
|
| 410 |
+
|
| 411 |
+
.feedback-icon {
|
| 412 |
+
font-size: 16px;
|
| 413 |
+
}
|
| 414 |
+
|
| 415 |
+
.feedback-correct {
|
| 416 |
+
color: var(--ta-green);
|
| 417 |
+
}
|
| 418 |
+
|
| 419 |
+
.feedback-wrong {
|
| 420 |
+
color: var(--ta-red);
|
| 421 |
+
}
|
| 422 |
+
|
| 423 |
+
/* Topic Badge */
|
| 424 |
+
.topic-badge {
|
| 425 |
+
display: inline-flex;
|
| 426 |
+
align-items: center;
|
| 427 |
+
gap: 6px;
|
| 428 |
+
padding: 4px 12px;
|
| 429 |
+
background: var(--ta-amber-bg);
|
| 430 |
+
border-radius: 12px;
|
| 431 |
+
font-size: 11px;
|
| 432 |
+
color: var(--ta-amber);
|
| 433 |
+
font-weight: 600;
|
| 434 |
+
margin-bottom: 12px;
|
| 435 |
+
}
|
| 436 |
+
|
| 437 |
+
/* Footer */
|
| 438 |
+
.quiz-footer {
|
| 439 |
+
padding: 16px 24px;
|
| 440 |
+
border-top: 1px solid var(--border-primary);
|
| 441 |
+
background: var(--bg-surface-tertiary);
|
| 442 |
+
display: flex;
|
| 443 |
+
justify-content: space-between;
|
| 444 |
+
align-items: center;
|
| 445 |
+
flex-shrink: 0;
|
| 446 |
+
}
|
| 447 |
+
|
| 448 |
+
.footer-info {
|
| 449 |
+
display: flex;
|
| 450 |
+
align-items: center;
|
| 451 |
+
gap: 16px;
|
| 452 |
+
}
|
| 453 |
+
|
| 454 |
+
.question-count {
|
| 455 |
+
font-size: 13px;
|
| 456 |
+
color: var(--text-muted);
|
| 457 |
+
}
|
| 458 |
+
|
| 459 |
+
.question-count strong {
|
| 460 |
+
color: var(--text-primary);
|
| 461 |
+
}
|
| 462 |
+
|
| 463 |
+
.footer-actions {
|
| 464 |
+
display: flex;
|
| 465 |
+
gap: 12px;
|
| 466 |
+
}
|
| 467 |
+
|
| 468 |
+
.action-btn {
|
| 469 |
+
padding: 10px 20px;
|
| 470 |
+
border-radius: 8px;
|
| 471 |
+
border: none;
|
| 472 |
+
font-size: 13px;
|
| 473 |
+
font-weight: 600;
|
| 474 |
+
cursor: pointer;
|
| 475 |
+
transition: all 0.2s;
|
| 476 |
+
font-family: inherit;
|
| 477 |
+
}
|
| 478 |
+
|
| 479 |
+
.action-btn.secondary {
|
| 480 |
+
background: var(--bg-surface);
|
| 481 |
+
color: var(--text-secondary);
|
| 482 |
+
border: 1px solid var(--border-primary);
|
| 483 |
+
}
|
| 484 |
+
|
| 485 |
+
.action-btn.secondary:hover {
|
| 486 |
+
border-color: var(--primary);
|
| 487 |
+
color: var(--primary);
|
| 488 |
+
}
|
| 489 |
+
|
| 490 |
+
.action-btn.primary {
|
| 491 |
+
background: var(--primary);
|
| 492 |
+
color: white;
|
| 493 |
+
}
|
| 494 |
+
|
| 495 |
+
.action-btn.primary:hover {
|
| 496 |
+
background: var(--primary-active);
|
| 497 |
+
}
|
| 498 |
+
|
| 499 |
+
/* Explanation Box */
|
| 500 |
+
.explanation-box {
|
| 501 |
+
margin-top: 12px;
|
| 502 |
+
padding: 12px 16px;
|
| 503 |
+
background: var(--bg-surface-tertiary);
|
| 504 |
+
border-left: 3px solid var(--primary);
|
| 505 |
+
border-radius: 0 8px 8px 0;
|
| 506 |
+
font-size: 12px;
|
| 507 |
+
color: var(--text-secondary);
|
| 508 |
+
line-height: 1.6;
|
| 509 |
+
}
|
| 510 |
+
|
| 511 |
+
.explanation-title {
|
| 512 |
+
font-weight: 700;
|
| 513 |
+
color: var(--primary);
|
| 514 |
+
margin-bottom: 4px;
|
| 515 |
+
font-size: 11px;
|
| 516 |
+
text-transform: uppercase;
|
| 517 |
+
}
|
| 518 |
+
|
| 519 |
+
/* Responsive */
|
| 520 |
+
@media (max-width: 1024px) {
|
| 521 |
+
.mockup-container {
|
| 522 |
+
padding: 20px;
|
| 523 |
+
}
|
| 524 |
+
|
| 525 |
+
.score-bar {
|
| 526 |
+
flex-wrap: wrap;
|
| 527 |
+
gap: 16px;
|
| 528 |
+
}
|
| 529 |
+
|
| 530 |
+
.progress-bar {
|
| 531 |
+
width: 100%;
|
| 532 |
+
}
|
| 533 |
+
}
|
| 534 |
+
</style>
|
| 535 |
+
</head>
|
| 536 |
+
<body>
|
| 537 |
+
<div class="mockup-container">
|
| 538 |
+
<div class="mockup-header">
|
| 539 |
+
<span class="option-badge">OPTION A</span>
|
| 540 |
+
<h1>Inline Quiz Generation</h1>
|
| 541 |
+
<p>Quiz questions are generated immediately when recap is created. Users answer and see results instantly.</p>
|
| 542 |
+
</div>
|
| 543 |
+
|
| 544 |
+
<div class="quiz-container">
|
| 545 |
+
<!-- Header -->
|
| 546 |
+
<div class="quiz-header">
|
| 547 |
+
<div class="quiz-header-left">
|
| 548 |
+
<div class="quiz-icon">
|
| 549 |
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
| 550 |
+
<path d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z"/>
|
| 551 |
+
</svg>
|
| 552 |
+
</div>
|
| 553 |
+
<div class="quiz-title">
|
| 554 |
+
<h3>Quiz - Bài giảng hoy</h3>
|
| 555 |
+
<p>Machine Learning Basics</p>
|
| 556 |
+
</div>
|
| 557 |
+
</div>
|
| 558 |
+
<div class="quiz-actions">
|
| 559 |
+
<button class="quiz-btn">
|
| 560 |
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 6px;">
|
| 561 |
+
<path d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
|
| 562 |
+
<path d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/>
|
| 563 |
+
</svg>
|
| 564 |
+
Xem lại
|
| 565 |
+
</button>
|
| 566 |
+
<button class="quiz-btn primary">
|
| 567 |
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 6px;">
|
| 568 |
+
<path d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"/>
|
| 569 |
+
</svg>
|
| 570 |
+
Xuất kết quả
|
| 571 |
+
</button>
|
| 572 |
+
</div>
|
| 573 |
+
</div>
|
| 574 |
+
|
| 575 |
+
<!-- Score Summary Bar -->
|
| 576 |
+
<div class="score-bar">
|
| 577 |
+
<div class="score-left">
|
| 578 |
+
<div class="score-circle">
|
| 579 |
+
<span class="score-number">85</span>
|
| 580 |
+
<span class="score-label">/100</span>
|
| 581 |
+
</div>
|
| 582 |
+
<div class="score-stats">
|
| 583 |
+
<div class="stat-item">
|
| 584 |
+
<div class="stat-dot correct"></div>
|
| 585 |
+
<div class="stat-text">
|
| 586 |
+
<div class="stat-value">17</div>
|
| 587 |
+
<div class="stat-label">Đúng</div>
|
| 588 |
+
</div>
|
| 589 |
+
</div>
|
| 590 |
+
<div class="stat-item">
|
| 591 |
+
<div class="stat-dot wrong"></div>
|
| 592 |
+
<div class="stat-text">
|
| 593 |
+
<div class="stat-value">3</div>
|
| 594 |
+
<div class="stat-label">Sai</div>
|
| 595 |
+
</div>
|
| 596 |
+
</div>
|
| 597 |
+
<div class="stat-item">
|
| 598 |
+
<div class="stat-dot pending"></div>
|
| 599 |
+
<div class="stat-text">
|
| 600 |
+
<div class="stat-value">0</div>
|
| 601 |
+
<div class="stat-label">Chưa làm</div>
|
| 602 |
+
</div>
|
| 603 |
+
</div>
|
| 604 |
+
</div>
|
| 605 |
+
</div>
|
| 606 |
+
<div style="display: flex; align-items: center;">
|
| 607 |
+
<div class="progress-bar">
|
| 608 |
+
<div class="progress-fill" style="width: 100%;"></div>
|
| 609 |
+
</div>
|
| 610 |
+
<span class="progress-text">20/20 câu</span>
|
| 611 |
+
</div>
|
| 612 |
+
</div>
|
| 613 |
+
|
| 614 |
+
<!-- Quiz Content -->
|
| 615 |
+
<div class="quiz-content">
|
| 616 |
+
<!-- Question 1 - Correct -->
|
| 617 |
+
<div class="question-card correct">
|
| 618 |
+
<div class="topic-badge">
|
| 619 |
+
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
| 620 |
+
<path d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"/>
|
| 621 |
+
</svg>
|
| 622 |
+
Giới thiệu Machine Learning
|
| 623 |
+
</div>
|
| 624 |
+
<div class="question-header">
|
| 625 |
+
<div class="question-number">1</div>
|
| 626 |
+
<div class="question-text">Machine Learning là gì?</div>
|
| 627 |
+
</div>
|
| 628 |
+
<div class="options-list">
|
| 629 |
+
<div class="option-item">
|
| 630 |
+
<div class="option-letter">A</div>
|
| 631 |
+
<span class="option-text">Lập trình máy tính truyền thống</span>
|
| 632 |
+
</div>
|
| 633 |
+
<div class="option-item correct-answer">
|
| 634 |
+
<div class="option-letter">B</div>
|
| 635 |
+
<span class="option-text">Lĩnh vực nghiên cứu giúp máy tính học từ dữ liệu</span>
|
| 636 |
+
<div class="option-feedback feedback-correct">
|
| 637 |
+
<span class="feedback-icon">✓</span>
|
| 638 |
+
<span>Đúng</span>
|
| 639 |
+
</div>
|
| 640 |
+
</div>
|
| 641 |
+
<div class="option-item">
|
| 642 |
+
<div class="option-letter">C</div>
|
| 643 |
+
<span class="option-text">Viết code tự động hoàn chỉnh</span>
|
| 644 |
+
</div>
|
| 645 |
+
<div class="option-item">
|
| 646 |
+
<div class="option-letter">D</div>
|
| 647 |
+
<span class="option-text">Sử dụng database để lưu trữ thông tin</span>
|
| 648 |
+
</div>
|
| 649 |
+
</div>
|
| 650 |
+
<div class="explanation-box">
|
| 651 |
+
<div class="explanation-title">Giải thích</div>
|
| 652 |
+
Machine Learning (Học máy) là một nhánh của AI tập trung vào việc phát triển thuật toán cho phép máy tính học hỏi từ dữ liệu mà không cần được lập trình tường minh.
|
| 653 |
+
</div>
|
| 654 |
+
</div>
|
| 655 |
+
|
| 656 |
+
<!-- Question 2 - Wrong -->
|
| 657 |
+
<div class="question-card wrong">
|
| 658 |
+
<div class="topic-badge">
|
| 659 |
+
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
| 660 |
+
<path d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z"/>
|
| 661 |
+
</svg>
|
| 662 |
+
Types of Learning
|
| 663 |
+
</div>
|
| 664 |
+
<div class="question-header">
|
| 665 |
+
<div class="question-number">2</div>
|
| 666 |
+
<div class="question-text">Loại nào KHÔNG phải là một dạng của Machine Learning?</div>
|
| 667 |
+
</div>
|
| 668 |
+
<div class="options-list">
|
| 669 |
+
<div class="option-item">
|
| 670 |
+
<div class="option-letter">A</div>
|
| 671 |
+
<span class="option-text">Supervised Learning</span>
|
| 672 |
+
</div>
|
| 673 |
+
<div class="option-item">
|
| 674 |
+
<div class="option-letter">B</div>
|
| 675 |
+
<span class="option-text">Unsupervised Learning</span>
|
| 676 |
+
</div>
|
| 677 |
+
<div class="option-item wrong-answer">
|
| 678 |
+
<div class="option-letter">C</div>
|
| 679 |
+
<span class="option-text">Manual Learning</span>
|
| 680 |
+
<div class="option-feedback feedback-wrong">
|
| 681 |
+
<span class="feedback-icon">✗</span>
|
| 682 |
+
<span>Sai</span>
|
| 683 |
+
</div>
|
| 684 |
+
</div>
|
| 685 |
+
<div class="option-item correct-answer">
|
| 686 |
+
<div class="option-letter">D</div>
|
| 687 |
+
<span class="option-text">Reinforcement Learning</span>
|
| 688 |
+
<div class="option-feedback feedback-correct">
|
| 689 |
+
<span class="feedback-icon">✓</span>
|
| 690 |
+
<span>Đáp án đúng</span>
|
| 691 |
+
</div>
|
| 692 |
+
</div>
|
| 693 |
+
</div>
|
| 694 |
+
<div class="explanation-box">
|
| 695 |
+
<div class="explanation-title">Giải thích</div>
|
| 696 |
+
"Manual Learning" không phải là một dạng của Machine Learning. Các dạng chính bao gồm: Supervised, Unsupervised, Semi-supervised, và Reinforcement Learning.
|
| 697 |
+
</div>
|
| 698 |
+
</div>
|
| 699 |
+
|
| 700 |
+
<!-- Question 3 - Pending -->
|
| 701 |
+
<div class="question-card pending">
|
| 702 |
+
<div class="topic-badge">
|
| 703 |
+
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
| 704 |
+
<path d="M19.428 15.428a2 2 0 00-1.022-.547l-2.387-.477a6 6 0 00-3.86.517l-.318.158a6 6 0 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 10.172V5L8 4z"/>
|
| 705 |
+
</svg>
|
| 706 |
+
Data Preprocessing
|
| 707 |
+
</div>
|
| 708 |
+
<div class="question-header">
|
| 709 |
+
<div class="question-number">3</div>
|
| 710 |
+
<div class="question-text">Bước nào KHÔNG thuộc quá trình tiền xử lý dữ liệu?</div>
|
| 711 |
+
</div>
|
| 712 |
+
<div class="options-list">
|
| 713 |
+
<div class="option-item">
|
| 714 |
+
<div class="option-letter">A</div>
|
| 715 |
+
<span class="option-text">Xử lý giá trị thiếu (Missing values)</span>
|
| 716 |
+
</div>
|
| 717 |
+
<div class="option-item">
|
| 718 |
+
<div class="option-letter">B</div>
|
| 719 |
+
<span class="option-text">Chuẩn hóa dữ liệu (Normalization)</span>
|
| 720 |
+
</div>
|
| 721 |
+
<div class="option-item">
|
| 722 |
+
<div class="option-letter">C</div>
|
| 723 |
+
<span class="option-text">Mã hóa biến phân loại (Encoding)</span>
|
| 724 |
+
</div>
|
| 725 |
+
<div class="option-item">
|
| 726 |
+
<div class="option-letter">D</div>
|
| 727 |
+
<span class="option-text">Huấn luyện mô hình (Model Training)</span>
|
| 728 |
+
</div>
|
| 729 |
+
</div>
|
| 730 |
+
</div>
|
| 731 |
+
|
| 732 |
+
</div>
|
| 733 |
+
|
| 734 |
+
<!-- Footer -->
|
| 735 |
+
<div class="quiz-footer">
|
| 736 |
+
<div class="footer-info">
|
| 737 |
+
<span class="question-count">Tổng <strong>20</strong> câu hỏi</span>
|
| 738 |
+
<span style="color: var(--ta-green); font-weight: 600;">✨ Hoàn thành 100%</span>
|
| 739 |
+
</div>
|
| 740 |
+
<div class="footer-actions">
|
| 741 |
+
<button class="action-btn secondary">
|
| 742 |
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 6px;">
|
| 743 |
+
<path d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/>
|
| 744 |
+
</svg>
|
| 745 |
+
Làm lại
|
| 746 |
+
</button>
|
| 747 |
+
<button class="action-btn primary">
|
| 748 |
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 6px;">
|
| 749 |
+
<path d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
| 750 |
+
</svg>
|
| 751 |
+
Xem chi tiết
|
| 752 |
+
</button>
|
| 753 |
+
</div>
|
| 754 |
+
</div>
|
| 755 |
+
</div>
|
| 756 |
+
</div>
|
| 757 |
+
</body>
|
| 758 |
+
</html>
|
src/components/ta/QuizMockupOptionB.html
ADDED
|
@@ -0,0 +1,797 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="vi">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>Quiz Mockup - Option B (Interactive Quiz Mode)</title>
|
| 7 |
+
<style>
|
| 8 |
+
:root {
|
| 9 |
+
--primary: #3b82f6;
|
| 10 |
+
--primary-active: #2563eb;
|
| 11 |
+
--bg-surface: #ffffff;
|
| 12 |
+
--bg-surface-secondary: #f8fafc;
|
| 13 |
+
--bg-surface-tertiary: #f1f5f9;
|
| 14 |
+
--text-primary: #1e293b;
|
| 15 |
+
--text-secondary: #475569;
|
| 16 |
+
--text-muted: #94a3b8;
|
| 17 |
+
--border-primary: #e2e8f0;
|
| 18 |
+
--ta-red: #ef4444;
|
| 19 |
+
--ta-red-bg: rgba(239, 68, 68, 0.1);
|
| 20 |
+
--ta-amber: #f59e0b;
|
| 21 |
+
--ta-amber-bg: rgba(245, 158, 11, 0.1);
|
| 22 |
+
--ta-blue: #3b82f6;
|
| 23 |
+
--ta-blue-bg: rgba(59, 130, 246, 0.1);
|
| 24 |
+
--ta-green: #10b981;
|
| 25 |
+
--ta-green-bg: rgba(16, 185, 129, 0.1);
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
+
* {
|
| 29 |
+
margin: 0;
|
| 30 |
+
padding: 0;
|
| 31 |
+
box-sizing: border-box;
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
body {
|
| 35 |
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
| 36 |
+
background: var(--bg-surface-secondary);
|
| 37 |
+
color: var(--text-primary);
|
| 38 |
+
line-height: 1.5;
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
.mockup-container {
|
| 42 |
+
max-width: 1400px;
|
| 43 |
+
margin: 0 auto;
|
| 44 |
+
padding: 40px 24px;
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
.mockup-header {
|
| 48 |
+
text-align: center;
|
| 49 |
+
margin-bottom: 40px;
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
.mockup-header h1 {
|
| 53 |
+
font-size: 28px;
|
| 54 |
+
font-weight: 700;
|
| 55 |
+
color: var(--primary);
|
| 56 |
+
margin-bottom: 8px;
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
.mockup-header p {
|
| 60 |
+
font-size: 14px;
|
| 61 |
+
color: var(--text-muted);
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
.option-badge {
|
| 65 |
+
display: inline-block;
|
| 66 |
+
padding: 6px 16px;
|
| 67 |
+
background: linear-gradient(135deg, var(--ta-amber) 0%, #f97316 100%);
|
| 68 |
+
color: white;
|
| 69 |
+
border-radius: 20px;
|
| 70 |
+
font-size: 12px;
|
| 71 |
+
font-weight: 600;
|
| 72 |
+
margin-bottom: 16px;
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
/* Main Card Container */
|
| 76 |
+
.quiz-container {
|
| 77 |
+
background: var(--bg-surface-secondary);
|
| 78 |
+
border: 1px solid var(--border-primary);
|
| 79 |
+
border-radius: 12px;
|
| 80 |
+
overflow: hidden;
|
| 81 |
+
height: 800px;
|
| 82 |
+
display: flex;
|
| 83 |
+
flex-direction: column;
|
| 84 |
+
}
|
| 85 |
+
|
| 86 |
+
/* Header */
|
| 87 |
+
.quiz-header {
|
| 88 |
+
padding: 16px 24px;
|
| 89 |
+
border-bottom: 1px solid var(--border-primary);
|
| 90 |
+
background: var(--bg-surface-tertiary);
|
| 91 |
+
display: flex;
|
| 92 |
+
align-items: center;
|
| 93 |
+
justify-content: space-between;
|
| 94 |
+
flex-shrink: 0;
|
| 95 |
+
}
|
| 96 |
+
|
| 97 |
+
.quiz-header-left {
|
| 98 |
+
display: flex;
|
| 99 |
+
align-items: center;
|
| 100 |
+
gap: 12px;
|
| 101 |
+
}
|
| 102 |
+
|
| 103 |
+
.quiz-icon {
|
| 104 |
+
width: 32px;
|
| 105 |
+
height: 32px;
|
| 106 |
+
background: linear-gradient(135deg, var(--primary) 0%, #8b5cf6 100%);
|
| 107 |
+
border-radius: 8px;
|
| 108 |
+
display: flex;
|
| 109 |
+
align-items: center;
|
| 110 |
+
justify-content: center;
|
| 111 |
+
color: white;
|
| 112 |
+
}
|
| 113 |
+
|
| 114 |
+
.quiz-title h3 {
|
| 115 |
+
font-size: 15px;
|
| 116 |
+
font-weight: 700;
|
| 117 |
+
margin: 0;
|
| 118 |
+
}
|
| 119 |
+
|
| 120 |
+
.quiz-title p {
|
| 121 |
+
font-size: 12px;
|
| 122 |
+
color: var(--text-muted);
|
| 123 |
+
margin-top: 2px;
|
| 124 |
+
}
|
| 125 |
+
|
| 126 |
+
.quiz-timer {
|
| 127 |
+
display: flex;
|
| 128 |
+
align-items: center;
|
| 129 |
+
gap: 6px;
|
| 130 |
+
padding: 6px 12px;
|
| 131 |
+
background: var(--ta-red-bg);
|
| 132 |
+
border-radius: 8px;
|
| 133 |
+
color: var(--ta-red);
|
| 134 |
+
font-size: 12px;
|
| 135 |
+
font-weight: 600;
|
| 136 |
+
}
|
| 137 |
+
|
| 138 |
+
/* Empty State / Start Screen */
|
| 139 |
+
.quiz-start-screen {
|
| 140 |
+
flex: 1;
|
| 141 |
+
display: flex;
|
| 142 |
+
flex-direction: column;
|
| 143 |
+
align-items: center;
|
| 144 |
+
justify-content: center;
|
| 145 |
+
padding: 40px;
|
| 146 |
+
text-align: center;
|
| 147 |
+
}
|
| 148 |
+
|
| 149 |
+
.start-icon {
|
| 150 |
+
width: 120px;
|
| 151 |
+
height: 120px;
|
| 152 |
+
background: linear-gradient(135deg, var(--ta-blue-bg) 0%, rgba(59, 130, 246, 0.2) 100%);
|
| 153 |
+
border-radius: 50%;
|
| 154 |
+
display: flex;
|
| 155 |
+
align-items: center;
|
| 156 |
+
justify-content: center;
|
| 157 |
+
margin-bottom: 24px;
|
| 158 |
+
}
|
| 159 |
+
|
| 160 |
+
.start-icon svg {
|
| 161 |
+
width: 56px;
|
| 162 |
+
height: 56px;
|
| 163 |
+
color: var(--primary);
|
| 164 |
+
}
|
| 165 |
+
|
| 166 |
+
.start-title {
|
| 167 |
+
font-size: 24px;
|
| 168 |
+
font-weight: 700;
|
| 169 |
+
color: var(--text-primary);
|
| 170 |
+
margin-bottom: 12px;
|
| 171 |
+
}
|
| 172 |
+
|
| 173 |
+
.start-description {
|
| 174 |
+
font-size: 14px;
|
| 175 |
+
color: var(--text-muted);
|
| 176 |
+
max-width: 400px;
|
| 177 |
+
margin-bottom: 32px;
|
| 178 |
+
line-height: 1.6;
|
| 179 |
+
}
|
| 180 |
+
|
| 181 |
+
.start-info-grid {
|
| 182 |
+
display: grid;
|
| 183 |
+
grid-template-columns: repeat(3, 1fr);
|
| 184 |
+
gap: 16px;
|
| 185 |
+
margin-bottom: 32px;
|
| 186 |
+
width: 100%;
|
| 187 |
+
max-width: 500px;
|
| 188 |
+
}
|
| 189 |
+
|
| 190 |
+
.info-card {
|
| 191 |
+
padding: 20px 16px;
|
| 192 |
+
background: var(--bg-surface);
|
| 193 |
+
border: 1px solid var(--border-primary);
|
| 194 |
+
border-radius: 12px;
|
| 195 |
+
text-align: center;
|
| 196 |
+
}
|
| 197 |
+
|
| 198 |
+
.info-value {
|
| 199 |
+
font-size: 24px;
|
| 200 |
+
font-weight: 700;
|
| 201 |
+
color: var(--primary);
|
| 202 |
+
margin-bottom: 4px;
|
| 203 |
+
}
|
| 204 |
+
|
| 205 |
+
.info-label {
|
| 206 |
+
font-size: 11px;
|
| 207 |
+
color: var(--text-muted);
|
| 208 |
+
text-transform: uppercase;
|
| 209 |
+
font-weight: 600;
|
| 210 |
+
}
|
| 211 |
+
|
| 212 |
+
.start-btn {
|
| 213 |
+
padding: 16px 48px;
|
| 214 |
+
background: linear-gradient(135deg, var(--primary) 0%, #6366f1 100%);
|
| 215 |
+
color: white;
|
| 216 |
+
border: none;
|
| 217 |
+
border-radius: 12px;
|
| 218 |
+
font-size: 16px;
|
| 219 |
+
font-weight: 700;
|
| 220 |
+
cursor: pointer;
|
| 221 |
+
display: flex;
|
| 222 |
+
align-items: center;
|
| 223 |
+
gap: 12px;
|
| 224 |
+
transition: all 0.3s;
|
| 225 |
+
box-shadow: 0 8px 24px rgba(59, 130, 246, 0.3);
|
| 226 |
+
font-family: inherit;
|
| 227 |
+
}
|
| 228 |
+
|
| 229 |
+
.start-btn:hover {
|
| 230 |
+
transform: translateY(-2px);
|
| 231 |
+
box-shadow: 0 12px 32px rgba(59, 130, 246, 0.4);
|
| 232 |
+
}
|
| 233 |
+
|
| 234 |
+
/* Active Quiz Screen */
|
| 235 |
+
.quiz-active-screen {
|
| 236 |
+
flex: 1;
|
| 237 |
+
display: flex;
|
| 238 |
+
flex-direction: column;
|
| 239 |
+
overflow: hidden;
|
| 240 |
+
}
|
| 241 |
+
|
| 242 |
+
/* Progress Bar */
|
| 243 |
+
.quiz-progress-bar {
|
| 244 |
+
height: 4px;
|
| 245 |
+
background: var(--bg-surface-tertiary);
|
| 246 |
+
display: flex;
|
| 247 |
+
flex-shrink: 0;
|
| 248 |
+
}
|
| 249 |
+
|
| 250 |
+
.progress-segment {
|
| 251 |
+
flex: 1;
|
| 252 |
+
height: 100%;
|
| 253 |
+
background: var(--border-primary);
|
| 254 |
+
transition: background 0.3s;
|
| 255 |
+
}
|
| 256 |
+
|
| 257 |
+
.progress-segment.completed {
|
| 258 |
+
background: var(--ta-green);
|
| 259 |
+
}
|
| 260 |
+
|
| 261 |
+
.progress-segment.current {
|
| 262 |
+
background: var(--primary);
|
| 263 |
+
}
|
| 264 |
+
|
| 265 |
+
.progress-segment.pending {
|
| 266 |
+
background: var(--bg-surface-tertiary);
|
| 267 |
+
}
|
| 268 |
+
|
| 269 |
+
/* Question Card Area */
|
| 270 |
+
.question-area {
|
| 271 |
+
flex: 1;
|
| 272 |
+
display: flex;
|
| 273 |
+
align-items: center;
|
| 274 |
+
justify-content: center;
|
| 275 |
+
padding: 32px 48px;
|
| 276 |
+
overflow-y: auto;
|
| 277 |
+
}
|
| 278 |
+
|
| 279 |
+
.question-card {
|
| 280 |
+
background: var(--bg-surface);
|
| 281 |
+
border: 1px solid var(--border-primary);
|
| 282 |
+
border-radius: 16px;
|
| 283 |
+
padding: 40px;
|
| 284 |
+
max-width: 600px;
|
| 285 |
+
width: 100%;
|
| 286 |
+
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.05);
|
| 287 |
+
}
|
| 288 |
+
|
| 289 |
+
.question-card-header {
|
| 290 |
+
display: flex;
|
| 291 |
+
justify-content: space-between;
|
| 292 |
+
align-items: center;
|
| 293 |
+
margin-bottom: 24px;
|
| 294 |
+
}
|
| 295 |
+
|
| 296 |
+
.question-badge {
|
| 297 |
+
padding: 6px 14px;
|
| 298 |
+
background: var(--ta-amber-bg);
|
| 299 |
+
color: var(--ta-amber);
|
| 300 |
+
border-radius: 20px;
|
| 301 |
+
font-size: 11px;
|
| 302 |
+
font-weight: 700;
|
| 303 |
+
text-transform: uppercase;
|
| 304 |
+
}
|
| 305 |
+
|
| 306 |
+
.question-number-badge {
|
| 307 |
+
padding: 6px 14px;
|
| 308 |
+
background: var(--bg-surface-tertiary);
|
| 309 |
+
color: var(--text-muted);
|
| 310 |
+
border-radius: 8px;
|
| 311 |
+
font-size: 13px;
|
| 312 |
+
font-weight: 600;
|
| 313 |
+
}
|
| 314 |
+
|
| 315 |
+
.question-text {
|
| 316 |
+
font-size: 18px;
|
| 317 |
+
font-weight: 600;
|
| 318 |
+
line-height: 1.5;
|
| 319 |
+
margin-bottom: 32px;
|
| 320 |
+
color: var(--text-primary);
|
| 321 |
+
}
|
| 322 |
+
|
| 323 |
+
/* Options */
|
| 324 |
+
.options-grid {
|
| 325 |
+
display: flex;
|
| 326 |
+
flex-direction: column;
|
| 327 |
+
gap: 12px;
|
| 328 |
+
}
|
| 329 |
+
|
| 330 |
+
.option-card {
|
| 331 |
+
display: flex;
|
| 332 |
+
align-items: center;
|
| 333 |
+
gap: 16px;
|
| 334 |
+
padding: 18px 22px;
|
| 335 |
+
border: 2px solid var(--border-primary);
|
| 336 |
+
border-radius: 12px;
|
| 337 |
+
cursor: pointer;
|
| 338 |
+
transition: all 0.2s;
|
| 339 |
+
background: var(--bg-surface);
|
| 340 |
+
}
|
| 341 |
+
|
| 342 |
+
.option-card:hover {
|
| 343 |
+
border-color: var(--primary);
|
| 344 |
+
background: var(--ta-blue-bg);
|
| 345 |
+
transform: translateX(4px);
|
| 346 |
+
}
|
| 347 |
+
|
| 348 |
+
.option-card.selected {
|
| 349 |
+
border-color: var(--primary);
|
| 350 |
+
background: var(--ta-blue-bg);
|
| 351 |
+
}
|
| 352 |
+
|
| 353 |
+
.option-letter {
|
| 354 |
+
width: 36px;
|
| 355 |
+
height: 36px;
|
| 356 |
+
border-radius: 10px;
|
| 357 |
+
background: var(--bg-surface-tertiary);
|
| 358 |
+
border: 2px solid var(--border-primary);
|
| 359 |
+
display: flex;
|
| 360 |
+
align-items: center;
|
| 361 |
+
justify-content: center;
|
| 362 |
+
font-size: 14px;
|
| 363 |
+
font-weight: 700;
|
| 364 |
+
color: var(--text-muted);
|
| 365 |
+
flex-shrink: 0;
|
| 366 |
+
transition: all 0.2s;
|
| 367 |
+
}
|
| 368 |
+
|
| 369 |
+
.option-card.selected .option-letter {
|
| 370 |
+
background: var(--primary);
|
| 371 |
+
border-color: var(--primary);
|
| 372 |
+
color: white;
|
| 373 |
+
}
|
| 374 |
+
|
| 375 |
+
.option-text {
|
| 376 |
+
flex: 1;
|
| 377 |
+
font-size: 14px;
|
| 378 |
+
color: var(--text-secondary);
|
| 379 |
+
line-height: 1.5;
|
| 380 |
+
}
|
| 381 |
+
|
| 382 |
+
/* Quiz Footer */
|
| 383 |
+
.quiz-footer {
|
| 384 |
+
padding: 20px 32px;
|
| 385 |
+
border-top: 1px solid var(--border-primary);
|
| 386 |
+
background: var(--bg-surface);
|
| 387 |
+
display: flex;
|
| 388 |
+
justify-content: space-between;
|
| 389 |
+
align-items: center;
|
| 390 |
+
flex-shrink: 0;
|
| 391 |
+
}
|
| 392 |
+
|
| 393 |
+
.navigation-btn {
|
| 394 |
+
padding: 12px 24px;
|
| 395 |
+
border-radius: 10px;
|
| 396 |
+
border: 1px solid var(--border-primary);
|
| 397 |
+
background: var(--bg-surface);
|
| 398 |
+
color: var(--text-secondary);
|
| 399 |
+
font-size: 13px;
|
| 400 |
+
font-weight: 600;
|
| 401 |
+
cursor: pointer;
|
| 402 |
+
display: flex;
|
| 403 |
+
align-items: center;
|
| 404 |
+
gap: 8px;
|
| 405 |
+
transition: all 0.2s;
|
| 406 |
+
font-family: inherit;
|
| 407 |
+
}
|
| 408 |
+
|
| 409 |
+
.navigation-btn:hover:not(:disabled) {
|
| 410 |
+
border-color: var(--primary);
|
| 411 |
+
color: var(--primary);
|
| 412 |
+
}
|
| 413 |
+
|
| 414 |
+
.navigation-btn:disabled {
|
| 415 |
+
opacity: 0.5;
|
| 416 |
+
cursor: not-allowed;
|
| 417 |
+
}
|
| 418 |
+
|
| 419 |
+
.submit-btn {
|
| 420 |
+
padding: 12px 32px;
|
| 421 |
+
border-radius: 10px;
|
| 422 |
+
border: none;
|
| 423 |
+
background: linear-gradient(135deg, var(--ta-green) 0%, #059669 100%);
|
| 424 |
+
color: white;
|
| 425 |
+
font-size: 14px;
|
| 426 |
+
font-weight: 700;
|
| 427 |
+
cursor: pointer;
|
| 428 |
+
display: flex;
|
| 429 |
+
align-items: center;
|
| 430 |
+
gap: 10px;
|
| 431 |
+
transition: all 0.3s;
|
| 432 |
+
box-shadow: 0 4px 12px rgba(16, 185, 129, 0.3);
|
| 433 |
+
font-family: inherit;
|
| 434 |
+
}
|
| 435 |
+
|
| 436 |
+
.submit-btn:hover {
|
| 437 |
+
transform: translateY(-2px);
|
| 438 |
+
box-shadow: 0 8px 20px rgba(16, 185, 129, 0.4);
|
| 439 |
+
}
|
| 440 |
+
|
| 441 |
+
/* Results Screen */
|
| 442 |
+
.results-screen {
|
| 443 |
+
flex: 1;
|
| 444 |
+
display: flex;
|
| 445 |
+
flex-direction: column;
|
| 446 |
+
align-items: center;
|
| 447 |
+
justify-content: center;
|
| 448 |
+
padding: 40px;
|
| 449 |
+
text-align: center;
|
| 450 |
+
}
|
| 451 |
+
|
| 452 |
+
.results-icon {
|
| 453 |
+
width: 100px;
|
| 454 |
+
height: 100px;
|
| 455 |
+
background: linear-gradient(135deg, var(--ta-green) 0%, #059669 100%);
|
| 456 |
+
border-radius: 50%;
|
| 457 |
+
display: flex;
|
| 458 |
+
align-items: center;
|
| 459 |
+
justify-content: center;
|
| 460 |
+
margin-bottom: 24px;
|
| 461 |
+
animation: pulse 2s infinite;
|
| 462 |
+
}
|
| 463 |
+
|
| 464 |
+
@keyframes pulse {
|
| 465 |
+
0%, 100% { transform: scale(1); }
|
| 466 |
+
50% { transform: scale(1.05); }
|
| 467 |
+
}
|
| 468 |
+
|
| 469 |
+
.results-icon svg {
|
| 470 |
+
width: 48px;
|
| 471 |
+
height: 48px;
|
| 472 |
+
color: white;
|
| 473 |
+
}
|
| 474 |
+
|
| 475 |
+
.results-title {
|
| 476 |
+
font-size: 28px;
|
| 477 |
+
font-weight: 700;
|
| 478 |
+
color: var(--text-primary);
|
| 479 |
+
margin-bottom: 8px;
|
| 480 |
+
}
|
| 481 |
+
|
| 482 |
+
.results-subtitle {
|
| 483 |
+
font-size: 14px;
|
| 484 |
+
color: var(--text-muted);
|
| 485 |
+
margin-bottom: 32px;
|
| 486 |
+
}
|
| 487 |
+
|
| 488 |
+
.score-display {
|
| 489 |
+
width: 180px;
|
| 490 |
+
height: 180px;
|
| 491 |
+
border-radius: 50%;
|
| 492 |
+
background: conic-gradient(var(--primary) 0deg, var(--bg-surface-tertiary) 0deg);
|
| 493 |
+
display: flex;
|
| 494 |
+
align-items: center;
|
| 495 |
+
justify-content: center;
|
| 496 |
+
margin-bottom: 32px;
|
| 497 |
+
position: relative;
|
| 498 |
+
}
|
| 499 |
+
|
| 500 |
+
.score-display::before {
|
| 501 |
+
content: '';
|
| 502 |
+
position: absolute;
|
| 503 |
+
width: 150px;
|
| 504 |
+
height: 150px;
|
| 505 |
+
background: var(--bg-surface);
|
| 506 |
+
border-radius: 50%;
|
| 507 |
+
}
|
| 508 |
+
|
| 509 |
+
.score-content {
|
| 510 |
+
position: relative;
|
| 511 |
+
z-index: 1;
|
| 512 |
+
}
|
| 513 |
+
|
| 514 |
+
.score-number {
|
| 515 |
+
font-size: 48px;
|
| 516 |
+
font-weight: 800;
|
| 517 |
+
color: var(--primary);
|
| 518 |
+
line-height: 1;
|
| 519 |
+
}
|
| 520 |
+
|
| 521 |
+
.score-text {
|
| 522 |
+
font-size: 12px;
|
| 523 |
+
color: var(--text-muted);
|
| 524 |
+
margin-top: 4px;
|
| 525 |
+
}
|
| 526 |
+
|
| 527 |
+
.results-stats {
|
| 528 |
+
display: grid;
|
| 529 |
+
grid-template-columns: repeat(3, 1fr);
|
| 530 |
+
gap: 16px;
|
| 531 |
+
margin-bottom: 32px;
|
| 532 |
+
width: 100%;
|
| 533 |
+
max-width: 500px;
|
| 534 |
+
}
|
| 535 |
+
|
| 536 |
+
.result-stat {
|
| 537 |
+
padding: 20px 16px;
|
| 538 |
+
background: var(--bg-surface);
|
| 539 |
+
border: 1px solid var(--border-primary);
|
| 540 |
+
border-radius: 12px;
|
| 541 |
+
text-align: center;
|
| 542 |
+
}
|
| 543 |
+
|
| 544 |
+
.result-stat-icon {
|
| 545 |
+
font-size: 24px;
|
| 546 |
+
margin-bottom: 8px;
|
| 547 |
+
}
|
| 548 |
+
|
| 549 |
+
.result-stat-value {
|
| 550 |
+
font-size: 24px;
|
| 551 |
+
font-weight: 700;
|
| 552 |
+
color: var(--text-primary);
|
| 553 |
+
}
|
| 554 |
+
|
| 555 |
+
.result-stat-label {
|
| 556 |
+
font-size: 11px;
|
| 557 |
+
color: var(--text-muted);
|
| 558 |
+
text-transform: uppercase;
|
| 559 |
+
font-weight: 600;
|
| 560 |
+
margin-top: 2px;
|
| 561 |
+
}
|
| 562 |
+
|
| 563 |
+
.results-actions {
|
| 564 |
+
display: flex;
|
| 565 |
+
gap: 12px;
|
| 566 |
+
}
|
| 567 |
+
|
| 568 |
+
.action-btn {
|
| 569 |
+
padding: 14px 28px;
|
| 570 |
+
border-radius: 10px;
|
| 571 |
+
font-size: 14px;
|
| 572 |
+
font-weight: 600;
|
| 573 |
+
cursor: pointer;
|
| 574 |
+
transition: all 0.2s;
|
| 575 |
+
font-family: inherit;
|
| 576 |
+
}
|
| 577 |
+
|
| 578 |
+
.action-btn.secondary {
|
| 579 |
+
background: var(--bg-surface);
|
| 580 |
+
color: var(--text-secondary);
|
| 581 |
+
border: 1px solid var(--border-primary);
|
| 582 |
+
}
|
| 583 |
+
|
| 584 |
+
.action-btn.secondary:hover {
|
| 585 |
+
border-color: var(--primary);
|
| 586 |
+
color: var(--primary);
|
| 587 |
+
}
|
| 588 |
+
|
| 589 |
+
.action-btn.primary {
|
| 590 |
+
background: var(--primary);
|
| 591 |
+
color: white;
|
| 592 |
+
border: none;
|
| 593 |
+
}
|
| 594 |
+
|
| 595 |
+
.action-btn.primary:hover {
|
| 596 |
+
background: var(--primary-active);
|
| 597 |
+
}
|
| 598 |
+
|
| 599 |
+
/* Performance Badge */
|
| 600 |
+
.performance-badge {
|
| 601 |
+
display: inline-flex;
|
| 602 |
+
align-items: center;
|
| 603 |
+
gap: 6px;
|
| 604 |
+
padding: 8px 16px;
|
| 605 |
+
background: var(--ta-green-bg);
|
| 606 |
+
border-radius: 20px;
|
| 607 |
+
color: var(--ta-green);
|
| 608 |
+
font-size: 13px;
|
| 609 |
+
font-weight: 600;
|
| 610 |
+
margin-bottom: 24px;
|
| 611 |
+
}
|
| 612 |
+
|
| 613 |
+
/* Responsive */
|
| 614 |
+
@media (max-width: 1024px) {
|
| 615 |
+
.mockup-container {
|
| 616 |
+
padding: 20px;
|
| 617 |
+
}
|
| 618 |
+
}
|
| 619 |
+
</style>
|
| 620 |
+
</head>
|
| 621 |
+
<body>
|
| 622 |
+
<div class="mockup-container">
|
| 623 |
+
<div class="mockup-header">
|
| 624 |
+
<span class="option-badge">OPTION B</span>
|
| 625 |
+
<h1>Interactive Quiz Mode</h1>
|
| 626 |
+
<p>Game-like experience with one question at a time, navigation controls, and final score submission.</p>
|
| 627 |
+
</div>
|
| 628 |
+
|
| 629 |
+
<div class="quiz-container">
|
| 630 |
+
<!-- Header -->
|
| 631 |
+
<div class="quiz-header">
|
| 632 |
+
<div class="quiz-header-left">
|
| 633 |
+
<div class="quiz-icon">
|
| 634 |
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
| 635 |
+
<path d="M13 10V3L4 14h7v7l9-11h-7z"/>
|
| 636 |
+
</svg>
|
| 637 |
+
</div>
|
| 638 |
+
<div class="quiz-title">
|
| 639 |
+
<h3>Quiz Challenge</h3>
|
| 640 |
+
<p>Machine Learning Basics</p>
|
| 641 |
+
</div>
|
| 642 |
+
</div>
|
| 643 |
+
<div class="quiz-timer">
|
| 644 |
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
| 645 |
+
<circle cx="12" cy="12" r="10"/>
|
| 646 |
+
<path d="M12 6v6l4 2"/>
|
| 647 |
+
</svg>
|
| 648 |
+
15:00
|
| 649 |
+
</div>
|
| 650 |
+
</div>
|
| 651 |
+
|
| 652 |
+
<!-- Progress Bar -->
|
| 653 |
+
<div class="quiz-progress-bar">
|
| 654 |
+
<div class="progress-segment completed"></div>
|
| 655 |
+
<div class="progress-segment completed"></div>
|
| 656 |
+
<div class="progress-segment current"></div>
|
| 657 |
+
<div class="progress-segment pending"></div>
|
| 658 |
+
<div class="progress-segment pending"></div>
|
| 659 |
+
<div class="progress-segment pending"></div>
|
| 660 |
+
<div class="progress-segment pending"></div>
|
| 661 |
+
<div class="progress-segment pending"></div>
|
| 662 |
+
<div class="progress-segment pending"></div>
|
| 663 |
+
<div class="progress-segment pending"></div>
|
| 664 |
+
</div>
|
| 665 |
+
|
| 666 |
+
<!-- Active Quiz Screen -->
|
| 667 |
+
<div class="quiz-active-screen">
|
| 668 |
+
<!-- Question Area -->
|
| 669 |
+
<div class="question-area">
|
| 670 |
+
<div class="question-card">
|
| 671 |
+
<div class="question-card-header">
|
| 672 |
+
<span class="question-badge">Kiến thức cơ bản</span>
|
| 673 |
+
<span class="question-number-badge">Câu 3/10</span>
|
| 674 |
+
</div>
|
| 675 |
+
|
| 676 |
+
<div class="question-text">
|
| 677 |
+
Trong Supervised Learning, dữ liệu huấn luyện cần có đặc điểm gì quan trọng nhất?
|
| 678 |
+
</div>
|
| 679 |
+
|
| 680 |
+
<div class="options-grid">
|
| 681 |
+
<div class="option-card">
|
| 682 |
+
<div class="option-letter">A</div>
|
| 683 |
+
<span class="option-text">Phải có kích thước lớn hơn 1GB</span>
|
| 684 |
+
</div>
|
| 685 |
+
<div class="option-card selected">
|
| 686 |
+
<div class="option-letter">B</div>
|
| 687 |
+
<span class="option-text">Có nhãn (label) hoặc target variable</span>
|
| 688 |
+
</div>
|
| 689 |
+
<div class="option-card">
|
| 690 |
+
<div class="option-letter">C</div>
|
| 691 |
+
<span class="option-text">Chỉ chứa dữ liệu số</span>
|
| 692 |
+
</div>
|
| 693 |
+
<div class="option-card">
|
| 694 |
+
<div class="option-letter">D</div>
|
| 695 |
+
<span class="option-text">Phải được chuẩn hóa trước</span>
|
| 696 |
+
</div>
|
| 697 |
+
</div>
|
| 698 |
+
</div>
|
| 699 |
+
</div>
|
| 700 |
+
|
| 701 |
+
<!-- Footer Navigation -->
|
| 702 |
+
<div class="quiz-footer">
|
| 703 |
+
<button class="navigation-btn" disabled>
|
| 704 |
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
| 705 |
+
<path d="M15 19l-7-7 7-7"/>
|
| 706 |
+
</svg>
|
| 707 |
+
Previous
|
| 708 |
+
</button>
|
| 709 |
+
<div style="display: flex; gap: 12px;">
|
| 710 |
+
<button class="navigation-btn">
|
| 711 |
+
Next
|
| 712 |
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
| 713 |
+
<path d="M9 5l7 7-7 7"/>
|
| 714 |
+
</svg>
|
| 715 |
+
</button>
|
| 716 |
+
<button class="submit-btn">
|
| 717 |
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
| 718 |
+
<path d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
| 719 |
+
</svg>
|
| 720 |
+
Nộp bài
|
| 721 |
+
</button>
|
| 722 |
+
</div>
|
| 723 |
+
</div>
|
| 724 |
+
</div>
|
| 725 |
+
</div>
|
| 726 |
+
|
| 727 |
+
<!-- Results Screen Section -->
|
| 728 |
+
<div style="margin-top: 60px;">
|
| 729 |
+
<div class="mockup-header" style="margin-bottom: 24px;">
|
| 730 |
+
<h2 style="font-size: 20px; color: var(--text-primary); margin-bottom: 8px;">Results Screen</h2>
|
| 731 |
+
<p style="font-size: 13px;">Hiển thị kết quả sau khi nộp bài</p>
|
| 732 |
+
</div>
|
| 733 |
+
|
| 734 |
+
<div class="quiz-container">
|
| 735 |
+
<div class="results-screen">
|
| 736 |
+
<div class="performance-badge">
|
| 737 |
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
| 738 |
+
<path d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
| 739 |
+
</svg>
|
| 740 |
+
Xuất sắc!
|
| 741 |
+
</div>
|
| 742 |
+
|
| 743 |
+
<div class="score-display" style="background: conic-gradient(var(--ta-green) 306deg, var(--bg-surface-tertiary) 306deg);">
|
| 744 |
+
<div class="score-content">
|
| 745 |
+
<div class="score-number">85</div>
|
| 746 |
+
<div class="score-text">/ 100 điểm</div>
|
| 747 |
+
</div>
|
| 748 |
+
</div>
|
| 749 |
+
|
| 750 |
+
<h3 class="results-title">Hoàn thành tốt!</h3>
|
| 751 |
+
<p class="results-subtitle">Bạn đã làm rất tốt bài quiz về Machine Learning Basics</p>
|
| 752 |
+
|
| 753 |
+
<div class="results-stats">
|
| 754 |
+
<div class="result-stat">
|
| 755 |
+
<div class="result-stat-icon">✓</div>
|
| 756 |
+
<div class="result-stat-value">8.5</div>
|
| 757 |
+
<div class="result-stat-label">Đúng</div>
|
| 758 |
+
</div>
|
| 759 |
+
<div class="result-stat">
|
| 760 |
+
<div class="result-stat-icon">✗</div>
|
| 761 |
+
<div class="result-stat-value">1.5</div>
|
| 762 |
+
<div class="result-stat-label">Sai</div>
|
| 763 |
+
</div>
|
| 764 |
+
<div class="result-stat">
|
| 765 |
+
<div class="result-stat-icon">⏱️</div>
|
| 766 |
+
<div class="result-stat-value">12:34</div>
|
| 767 |
+
<div class="result-stat-label">Thời gian</div>
|
| 768 |
+
</div>
|
| 769 |
+
</div>
|
| 770 |
+
|
| 771 |
+
<div class="results-actions">
|
| 772 |
+
<button class="action-btn secondary">
|
| 773 |
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 6px;">
|
| 774 |
+
<path d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/>
|
| 775 |
+
</svg>
|
| 776 |
+
Làm lại
|
| 777 |
+
</button>
|
| 778 |
+
<button class="action-btn secondary">
|
| 779 |
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 6px;">
|
| 780 |
+
<path d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
|
| 781 |
+
<path d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/>
|
| 782 |
+
</svg>
|
| 783 |
+
Xem chi tiết
|
| 784 |
+
</button>
|
| 785 |
+
<button class="action-btn primary">
|
| 786 |
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 6px;">
|
| 787 |
+
<path d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12"/>
|
| 788 |
+
</svg>
|
| 789 |
+
Xuất kết quả
|
| 790 |
+
</button>
|
| 791 |
+
</div>
|
| 792 |
+
</div>
|
| 793 |
+
</div>
|
| 794 |
+
</div>
|
| 795 |
+
</div>
|
| 796 |
+
</body>
|
| 797 |
+
</html>
|
src/components/ta/RecapWorkflow.jsx
CHANGED
|
@@ -27,6 +27,15 @@ const RecapWorkflow = (props) => {
|
|
| 27 |
const [refineQuery, setRefineQuery] = useState('');
|
| 28 |
const [selectedChips, setSelectedChips] = useState([]);
|
| 29 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
const refineOptions = [
|
| 31 |
{ id: 'shorter', label: '✨ Ngắn gọn', prompt: 'Làm ngắn gọn lại, súc tích hơn.' },
|
| 32 |
{ id: 'funny', label: '✨ Hài hước', prompt: 'Viết lại với giọng văn hài hước, năng lượng hơn.' },
|
|
@@ -60,43 +69,6 @@ const RecapWorkflow = (props) => {
|
|
| 60 |
}
|
| 61 |
};
|
| 62 |
|
| 63 |
-
const renderMarkdown = (content) => {
|
| 64 |
-
return { __html: marked.parse(content || '') };
|
| 65 |
-
};
|
| 66 |
-
|
| 67 |
-
// Extract deadlines from summary content (look for homework/bài tập patterns)
|
| 68 |
-
const extractDeadlines = (content) => {
|
| 69 |
-
if (!content) return [];
|
| 70 |
-
|
| 71 |
-
// Tìm các dòng có từ khóa: bài tập, homework, deadline, nộp, due, assignment
|
| 72 |
-
const patterns = [
|
| 73 |
-
/[-•*]\s*.*?(?:bài tập|homework|assignment|deadline|hạn|nộp|due date|làm|chuẩn bị).*?(?:\n|$)/gi,
|
| 74 |
-
/[-•*]\s*(?:đóng|gửi|submit).*?(?:bài|file|nội dung).*?(?:\n|$)/gi,
|
| 75 |
-
/\d{1,2}[\/-]\d{1,2}(?:\/\d{2,4})?\s*[.:]\s*.+?[\n]/gi
|
| 76 |
-
];
|
| 77 |
-
|
| 78 |
-
const deadlines = new Set();
|
| 79 |
-
|
| 80 |
-
for (const pattern of patterns) {
|
| 81 |
-
const matches = content.match(pattern);
|
| 82 |
-
if (matches) {
|
| 83 |
-
matches.forEach(match => {
|
| 84 |
-
const cleanMatch = match.trim()
|
| 85 |
-
.replace(/^[-•*]\s*/, '')
|
| 86 |
-
.replace(/\s+/g, ' ')
|
| 87 |
-
.slice(0, 150);
|
| 88 |
-
if (cleanMatch.length > 8 && cleanMatch.length < 151) {
|
| 89 |
-
deadlines.add(cleanMatch);
|
| 90 |
-
}
|
| 91 |
-
});
|
| 92 |
-
}
|
| 93 |
-
}
|
| 94 |
-
|
| 95 |
-
return Array.from(deadlines).slice(0, 5);
|
| 96 |
-
};
|
| 97 |
-
|
| 98 |
-
const deadlines = extractDeadlines(aiPreview?.content || '');
|
| 99 |
-
|
| 100 |
return (
|
| 101 |
<div className="animate-fade">
|
| 102 |
<div style={{ display: 'grid', gridTemplateColumns: '320px 1fr', gap: '24px', alignItems: 'start' }}>
|
|
@@ -182,7 +154,7 @@ const RecapWorkflow = (props) => {
|
|
| 182 |
|
| 183 |
{/* Right Panel - Content Area */}
|
| 184 |
<div style={{ display: 'flex', flexDirection: 'column', gap: '20px' }}>
|
| 185 |
-
<div className="ta-card-premium" style={{ display: 'flex', flexDirection: 'column', height: 'calc(100vh - 140px)', overflow: 'hidden' }}>
|
| 186 |
|
| 187 |
{/* Header */}
|
| 188 |
<div style={{ padding: '16px 24px', borderBottom: '1px solid var(--border-primary)', background: 'var(--bg-surface-tertiary)', display: 'flex', alignItems: 'center', justifyContent: 'space-between', flexShrink: 0 }}>
|
|
@@ -201,10 +173,14 @@ const RecapWorkflow = (props) => {
|
|
| 201 |
|
| 202 |
{/* Processing Overlay */}
|
| 203 |
{uploading && (
|
| 204 |
-
<div
|
| 205 |
-
<
|
| 206 |
-
|
| 207 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 208 |
</div>
|
| 209 |
)}
|
| 210 |
|
|
@@ -258,7 +234,17 @@ const RecapWorkflow = (props) => {
|
|
| 258 |
<div style={{ display: 'flex', flexDirection: 'column', gap: '10px' }}>
|
| 259 |
{deadlines.map((deadline, idx) => (
|
| 260 |
<div key={idx} style={{ padding: '12px', background: 'var(--bg-surface-tertiary)', borderRadius: '8px', borderLeft: '3px solid var(--ta-amber)', fontSize: '13px', lineHeight: '1.5' }}>
|
| 261 |
-
{deadline}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 262 |
</div>
|
| 263 |
))}
|
| 264 |
</div>
|
|
|
|
| 27 |
const [refineQuery, setRefineQuery] = useState('');
|
| 28 |
const [selectedChips, setSelectedChips] = useState([]);
|
| 29 |
|
| 30 |
+
// Use deadlines from aiPreview if available, otherwise fall back to empty array
|
| 31 |
+
const deadlines = (aiPreview?.deadlines && Array.isArray(aiPreview.deadlines) && aiPreview.deadlines.length > 0)
|
| 32 |
+
? aiPreview.deadlines
|
| 33 |
+
: [];
|
| 34 |
+
|
| 35 |
+
const renderMarkdown = (content) => {
|
| 36 |
+
return { __html: marked.parse(content || '') };
|
| 37 |
+
};
|
| 38 |
+
|
| 39 |
const refineOptions = [
|
| 40 |
{ id: 'shorter', label: '✨ Ngắn gọn', prompt: 'Làm ngắn gọn lại, súc tích hơn.' },
|
| 41 |
{ id: 'funny', label: '✨ Hài hước', prompt: 'Viết lại với giọng văn hài hước, năng lượng hơn.' },
|
|
|
|
| 69 |
}
|
| 70 |
};
|
| 71 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 72 |
return (
|
| 73 |
<div className="animate-fade">
|
| 74 |
<div style={{ display: 'grid', gridTemplateColumns: '320px 1fr', gap: '24px', alignItems: 'start' }}>
|
|
|
|
| 154 |
|
| 155 |
{/* Right Panel - Content Area */}
|
| 156 |
<div style={{ display: 'flex', flexDirection: 'column', gap: '20px' }}>
|
| 157 |
+
<div className="ta-card-premium" style={{ display: 'flex', flexDirection: 'column', height: 'calc(100vh - 140px)', overflow: 'hidden', position: 'relative' }}>
|
| 158 |
|
| 159 |
{/* Header */}
|
| 160 |
<div style={{ padding: '16px 24px', borderBottom: '1px solid var(--border-primary)', background: 'var(--bg-surface-tertiary)', display: 'flex', alignItems: 'center', justifyContent: 'space-between', flexShrink: 0 }}>
|
|
|
|
| 173 |
|
| 174 |
{/* Processing Overlay */}
|
| 175 |
{uploading && (
|
| 176 |
+
<div className="processing-overlay-card">
|
| 177 |
+
<div className="processing-card-content">
|
| 178 |
+
<Icons.FiCpu className="spin" size={48} color="var(--primary)" />
|
| 179 |
+
<div style={{ textAlign: 'center' }}>
|
| 180 |
+
<h4 className="processing-card-title">AI đang phân tích...</h4>
|
| 181 |
+
<p className="processing-card-text">Đang đọc file và tạo tóm tắt bài giảng</p>
|
| 182 |
+
</div>
|
| 183 |
+
</div>
|
| 184 |
</div>
|
| 185 |
)}
|
| 186 |
|
|
|
|
| 234 |
<div style={{ display: 'flex', flexDirection: 'column', gap: '10px' }}>
|
| 235 |
{deadlines.map((deadline, idx) => (
|
| 236 |
<div key={idx} style={{ padding: '12px', background: 'var(--bg-surface-tertiary)', borderRadius: '8px', borderLeft: '3px solid var(--ta-amber)', fontSize: '13px', lineHeight: '1.5' }}>
|
| 237 |
+
<div style={{ fontWeight: 600, marginBottom: '4px' }}>{deadline.title || deadline}</div>
|
| 238 |
+
{deadline.due_date && (
|
| 239 |
+
<div style={{ fontSize: '11px', color: 'var(--text-muted)', marginTop: '4px' }}>
|
| 240 |
+
📅 {deadline.due_date}
|
| 241 |
+
</div>
|
| 242 |
+
)}
|
| 243 |
+
{deadline.description && (
|
| 244 |
+
<div style={{ fontSize: '12px', marginTop: '4px' }}>
|
| 245 |
+
{deadline.description}
|
| 246 |
+
</div>
|
| 247 |
+
)}
|
| 248 |
</div>
|
| 249 |
))}
|
| 250 |
</div>
|
src/pages/TADashboard.css
CHANGED
|
@@ -355,6 +355,49 @@
|
|
| 355 |
border-radius: 12px;
|
| 356 |
}
|
| 357 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 358 |
/* Toast Notification */
|
| 359 |
.toast-container {
|
| 360 |
position: fixed;
|
|
|
|
| 355 |
border-radius: 12px;
|
| 356 |
}
|
| 357 |
|
| 358 |
+
/* Processing overlay với card trắng (dành cho Recap Workflow) */
|
| 359 |
+
.processing-overlay-card {
|
| 360 |
+
position: absolute;
|
| 361 |
+
top: 0;
|
| 362 |
+
left: 0;
|
| 363 |
+
right: 0;
|
| 364 |
+
bottom: 0;
|
| 365 |
+
display: flex;
|
| 366 |
+
flex-direction: column;
|
| 367 |
+
align-items: center;
|
| 368 |
+
justify-content: center;
|
| 369 |
+
background: linear-gradient(135deg, rgba(59, 130, 246, 0.15) 0%, rgba(147, 197, 253, 0.25) 100%);
|
| 370 |
+
backdrop-filter: blur(4px);
|
| 371 |
+
z-index: 100;
|
| 372 |
+
border-radius: 12px;
|
| 373 |
+
}
|
| 374 |
+
|
| 375 |
+
.processing-card-content {
|
| 376 |
+
display: flex;
|
| 377 |
+
flex-direction: column;
|
| 378 |
+
align-items: center;
|
| 379 |
+
gap: 16px;
|
| 380 |
+
padding: 32px;
|
| 381 |
+
background: rgba(255, 255, 255, 0.95);
|
| 382 |
+
border-radius: 16px;
|
| 383 |
+
box-shadow: 0 8px 32px rgba(59, 130, 246, 0.2);
|
| 384 |
+
}
|
| 385 |
+
|
| 386 |
+
.processing-card-title {
|
| 387 |
+
margin: 0;
|
| 388 |
+
font-weight: 700;
|
| 389 |
+
font-size: 16px;
|
| 390 |
+
color: #1e3a8a;
|
| 391 |
+
font-family: inherit;
|
| 392 |
+
}
|
| 393 |
+
|
| 394 |
+
.processing-card-text {
|
| 395 |
+
margin: 4px 0 0;
|
| 396 |
+
font-size: 13px;
|
| 397 |
+
color: #4b5563;
|
| 398 |
+
font-family: inherit;
|
| 399 |
+
}
|
| 400 |
+
|
| 401 |
/* Toast Notification */
|
| 402 |
.toast-container {
|
| 403 |
position: fixed;
|
src/pages/TADashboard.jsx
CHANGED
|
@@ -144,14 +144,89 @@ const TADashboard = () => {
|
|
| 144 |
const r = aiConfig.recap;
|
| 145 |
const pronouns = r.pronouns || g.pronouns;
|
| 146 |
return `[ROLE] Bạn là một Trợ Giảng (Teaching Assistant) xuất sắc.
|
| 147 |
-
[OBJECTIVE] Phân tích nội dung buổi học và
|
| 148 |
[RULES]
|
| 149 |
- Xưng hô bắt buộc: ${pronouns}.
|
| 150 |
- Tone giọng: ${r.tone}.
|
| 151 |
- Trọng tâm (Focus): ${r.highlight}.
|
| 152 |
- Quy tắc chung:\\n${compileRules(g.rules)}\\n${g.instruction}
|
| 153 |
- Quy tắc Recap:\\n${compileRules(r.rules)}\\n${r.instruction}
|
| 154 |
-
[OUTPUT FORMAT]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 155 |
};
|
| 156 |
|
| 157 |
const buildAnnouncementPrompt = (p, c) => {
|
|
@@ -222,7 +297,10 @@ const TADashboard = () => {
|
|
| 222 |
addToast('Đã giải quyết');
|
| 223 |
fetchData();
|
| 224 |
}
|
| 225 |
-
} catch (error) {
|
|
|
|
|
|
|
|
|
|
| 226 |
};
|
| 227 |
|
| 228 |
const handleApproveSummary = async (draftId, spaceId, autoPilotData = null) => {
|
|
@@ -230,18 +308,26 @@ const TADashboard = () => {
|
|
| 230 |
try {
|
| 231 |
const activeContent = autoPilotData?.content || aiPreview?.content || '';
|
| 232 |
const activeType = autoPilotData?.draft_type || aiPreview?.draft_type || '';
|
|
|
|
| 233 |
|
| 234 |
if (aiPreview?.id === draftId) await taService.updateSummaryDraft(draftId, spaceId, { content: activeContent });
|
| 235 |
const res = await taService.approveSummary(draftId, spaceId);
|
| 236 |
-
if (res.success
|
|
|
|
|
|
|
|
|
|
|
|
|
| 237 |
const otherSpaces = selectedSpaces.filter(id => id !== spaceId);
|
| 238 |
await Promise.all(otherSpaces.map(async (sid) => {
|
| 239 |
-
const newDraftRes = await taService.createSummaryDraft({ spaceId: sid, content: activeContent, draft_type: activeType });
|
| 240 |
if (newDraftRes?.success) await taService.approveSummary(newDraftRes.data.id, sid);
|
| 241 |
}));
|
| 242 |
}
|
| 243 |
addToast('Đã đăng bài!'); fetchData(); setAiPreview(null); setCurrentStep(1);
|
| 244 |
-
} catch (error) {
|
|
|
|
|
|
|
|
|
|
| 245 |
};
|
| 246 |
|
| 247 |
const handleScheduleSummary = async (draftId, spaceId, scheduledAt, autoPilotData = null) => {
|
|
@@ -249,6 +335,7 @@ const TADashboard = () => {
|
|
| 249 |
try {
|
| 250 |
const activeContent = autoPilotData?.content || aiPreview?.content || '';
|
| 251 |
const activeType = autoPilotData?.draft_type || aiPreview?.draft_type || '';
|
|
|
|
| 252 |
|
| 253 |
if (aiPreview?.id === draftId) await taService.updateSummaryDraft(draftId, spaceId, { content: activeContent });
|
| 254 |
const isoDate = new Date(scheduledAt).toISOString();
|
|
@@ -257,17 +344,25 @@ const TADashboard = () => {
|
|
| 257 |
if (res.success && selectedSpaces.length > 1 && (!aiPreview || aiPreview.status === 'pending')) {
|
| 258 |
const otherSpaces = selectedSpaces.filter(id => id !== spaceId);
|
| 259 |
await Promise.all(otherSpaces.map(async (sid) => {
|
| 260 |
-
const newDraftRes = await taService.createSummaryDraft({ spaceId: sid, content: activeContent, draft_type: activeType });
|
| 261 |
if (newDraftRes?.success) await taService.scheduleSummary(newDraftRes.data.id, sid, isoDate);
|
| 262 |
}));
|
| 263 |
}
|
| 264 |
|
| 265 |
addToast('Đã đặt lịch!'); fetchData(); setAiPreview(null); setCurrentStep(1);
|
| 266 |
-
} catch (error) {
|
|
|
|
|
|
|
|
|
|
| 267 |
};
|
| 268 |
|
| 269 |
const handleEditScheduled = (item) => {
|
| 270 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 271 |
setSelectedSpaces([item.space_id]);
|
| 272 |
if (item.draft_type === 'lesson_recap') { setActiveTab('summary'); setCurrentStep(3); }
|
| 273 |
else { setActiveTab('announcements'); }
|
|
@@ -283,7 +378,10 @@ const TADashboard = () => {
|
|
| 283 |
}));
|
| 284 |
addToast('Đã hủy lịch hàng loạt');
|
| 285 |
fetchData();
|
| 286 |
-
} catch (e) {
|
|
|
|
|
|
|
|
|
|
| 287 |
};
|
| 288 |
|
| 289 |
const handleBulkSendNow = async (ids) => {
|
|
@@ -295,7 +393,10 @@ const TADashboard = () => {
|
|
| 295 |
}));
|
| 296 |
addToast('Đã gửi hàng loạt');
|
| 297 |
fetchData();
|
| 298 |
-
} catch (e) {
|
|
|
|
|
|
|
|
|
|
| 299 |
};
|
| 300 |
|
| 301 |
const handleCancelSchedule = async (draftId, spaceId) => {
|
|
@@ -303,7 +404,10 @@ const TADashboard = () => {
|
|
| 303 |
setIsSending(true);
|
| 304 |
const res = await taService.cancelSchedule(draftId, spaceId);
|
| 305 |
if (res.success) { addToast('Đã hủy lịch'); fetchData(); }
|
| 306 |
-
} catch (error) {
|
|
|
|
|
|
|
|
|
|
| 307 |
};
|
| 308 |
|
| 309 |
const handleRefineAi = async (refineInstruction) => {
|
|
@@ -333,7 +437,10 @@ ${compileRules(g.rules)}
|
|
| 333 |
addToast('Đã cập nhật bản thảo!');
|
| 334 |
}
|
| 335 |
} else { addToast('AI không phản hồi lệnh sửa', 'error'); }
|
| 336 |
-
} catch (error) {
|
|
|
|
|
|
|
|
|
|
| 337 |
};
|
| 338 |
|
| 339 |
const handleOpenCompose = async (snapshotId, spaceId) => {
|
|
@@ -344,7 +451,10 @@ ${compileRules(g.rules)}
|
|
| 344 |
setCurrentContext({ id: snapshotId, space_id: spaceId, ...res.data, aiConfig, taName: user?.display_name });
|
| 345 |
setIsComposeOpen(true);
|
| 346 |
}
|
| 347 |
-
} catch (error) {
|
|
|
|
|
|
|
|
|
|
| 348 |
};
|
| 349 |
|
| 350 |
const handleAiSend = async (message) => {
|
|
@@ -356,7 +466,10 @@ ${compileRules(g.rules)}
|
|
| 356 |
content: message, snapshotId: currentContext.id
|
| 357 |
});
|
| 358 |
if (res.success) { setIsComposeOpen(false); handleResolveAlert(currentContext.id, currentContext.space_id); addToast('Đã gửi tin nhắn!'); }
|
| 359 |
-
} catch (error) {
|
|
|
|
|
|
|
|
|
|
| 360 |
};
|
| 361 |
|
| 362 |
const RuleCheckbox = ({ label, checked, onChange }) => (
|
|
@@ -512,7 +625,10 @@ ${compileRules(g.rules)}
|
|
| 512 |
try {
|
| 513 |
const res = await taService.uploadSlide(selectedSpaces[0], file);
|
| 514 |
if (res.success) setUploadedFile({ ...res.data, rawFile: file });
|
| 515 |
-
} catch (error) {
|
|
|
|
|
|
|
|
|
|
| 516 |
}}
|
| 517 |
startAiAnalysis={async () => {
|
| 518 |
if (selectedSpaces.length === 0) return;
|
|
@@ -530,10 +646,14 @@ ${compileRules(g.rules)}
|
|
| 530 |
|
| 531 |
const aiContent = resAgent?.answer || resAgent?.content || resAgent?.response;
|
| 532 |
if (resAgent?.success && aiContent) {
|
|
|
|
|
|
|
|
|
|
| 533 |
const res = await taService.createSummaryDraft({
|
| 534 |
spaceId: selectedSpaces[0],
|
| 535 |
-
content:
|
| 536 |
draft_type: 'lesson_recap',
|
|
|
|
| 537 |
});
|
| 538 |
if (res?.success) {
|
| 539 |
if (scheduleDate) handleScheduleSummary(res.data.id, res.data.space_id, scheduleDate, res.data);
|
|
@@ -554,13 +674,17 @@ ${compileRules(g.rules)}
|
|
| 554 |
|
| 555 |
const aiContent = resAgent?.answer || resAgent?.content || resAgent?.response;
|
| 556 |
if (resAgent?.success && aiContent) {
|
|
|
|
|
|
|
|
|
|
| 557 |
const res = await taService.createSummaryDraft({
|
| 558 |
spaceId: selectedSpaces[0],
|
| 559 |
-
content:
|
| 560 |
draft_type: 'lesson_recap',
|
|
|
|
| 561 |
});
|
| 562 |
if (res?.success) {
|
| 563 |
-
setAiPreview(res.data);
|
| 564 |
setCurrentStep(3);
|
| 565 |
}
|
| 566 |
} else { addToast('AI không phản hồi', 'error'); }
|
|
@@ -612,7 +736,10 @@ ${compileRules(g.rules)}
|
|
| 612 |
setAiPreview(res.data);
|
| 613 |
}
|
| 614 |
} else { addToast('AI không phản hồi', 'error'); }
|
| 615 |
-
} catch (error) {
|
|
|
|
|
|
|
|
|
|
| 616 |
}}
|
| 617 |
loading={isAnalyzing} aiPreview={aiPreview} setAiPreview={setAiPreview} handleApprove={handleApproveSummary} handleSchedule={handleScheduleSummary}
|
| 618 |
scheduleDate={scheduleDate} setScheduleDate={setScheduleDate} selectedSpaces={selectedSpaces} setSelectedSpaces={setSelectedSpaces}
|
|
|
|
| 144 |
const r = aiConfig.recap;
|
| 145 |
const pronouns = r.pronouns || g.pronouns;
|
| 146 |
return `[ROLE] Bạn là một Trợ Giảng (Teaching Assistant) xuất sắc.
|
| 147 |
+
[OBJECTIVE] Phân tích nội dung buổi học và trả về dữ liệu có cấu trúc gồm: tóm tắt bài giảng + danh sách deadline.
|
| 148 |
[RULES]
|
| 149 |
- Xưng hô bắt buộc: ${pronouns}.
|
| 150 |
- Tone giọng: ${r.tone}.
|
| 151 |
- Trọng tâm (Focus): ${r.highlight}.
|
| 152 |
- Quy tắc chung:\\n${compileRules(g.rules)}\\n${g.instruction}
|
| 153 |
- Quy tắc Recap:\\n${compileRules(r.rules)}\\n${r.instruction}
|
| 154 |
+
[OUTPUT FORMAT] Bắt buộc trả về valid JSON với cấu trúc sau:
|
| 155 |
+
{
|
| 156 |
+
"summary": "Nội dung tóm tắt bài giảng định dạng Markdown",
|
| 157 |
+
"deadlines": [
|
| 158 |
+
{ "title": "Tên bài tập", "due_date": "YYYY-MM-DD hoặc mô tả ngày", "description": "Mô tả ngắn về yêu cầu" }
|
| 159 |
+
]
|
| 160 |
+
}
|
| 161 |
+
TUYỆT ĐỐI KHÔNG thêm bất kỳ nội dung nào khác ngoài JSON (không có markdown code blocks, không có lời giải thích).`;
|
| 162 |
+
};
|
| 163 |
+
|
| 164 |
+
// Parse AI JSON response with fallback to text extraction
|
| 165 |
+
const parseAiRecapResponse = (aiContent) => {
|
| 166 |
+
if (!aiContent) return { summary: '', deadlines: [] };
|
| 167 |
+
|
| 168 |
+
// Extract deadlines from text using regex (for fallback)
|
| 169 |
+
const extractDeadlinesFromText = (content) => {
|
| 170 |
+
const patterns = [
|
| 171 |
+
/[-•*]\s*.*?(?:bài tập|homework|assignment|deadline|hạn|nộp|due date|làm|chuẩn bị).*?(?:\n|$)/gi,
|
| 172 |
+
/[-•*]\s*(?:đóng|gửi|submit).*?(?:bài|file|nội dung).*?(?:\n|$)/gi,
|
| 173 |
+
/\d{1,2}[\/-]\d{1,2}(?:\/\d{2,4})?\s*[.:]\s*.+?[\n]/gi
|
| 174 |
+
];
|
| 175 |
+
const deadlines = new Set();
|
| 176 |
+
for (const pattern of patterns) {
|
| 177 |
+
const matches = content.match(pattern);
|
| 178 |
+
if (matches) {
|
| 179 |
+
matches.forEach(match => {
|
| 180 |
+
const cleanMatch = match.trim()
|
| 181 |
+
.replace(/^[-•*]\s*/, '')
|
| 182 |
+
.replace(/\s+/g, ' ')
|
| 183 |
+
.slice(0, 150);
|
| 184 |
+
if (cleanMatch.length > 8 && cleanMatch.length < 151) {
|
| 185 |
+
deadlines.add(cleanMatch);
|
| 186 |
+
}
|
| 187 |
+
});
|
| 188 |
+
}
|
| 189 |
+
}
|
| 190 |
+
return Array.from(deadlines).slice(0, 5);
|
| 191 |
+
};
|
| 192 |
+
|
| 193 |
+
try {
|
| 194 |
+
// Extract JSON from code blocks first (handle ```json ... ```)
|
| 195 |
+
let jsonStr = aiContent.trim();
|
| 196 |
+
const codeBlockMatch = aiContent.match(/```(?:json)?\s*([\s\S]*?)```/);
|
| 197 |
+
if (codeBlockMatch && codeBlockMatch[1]) {
|
| 198 |
+
jsonStr = codeBlockMatch[1].trim();
|
| 199 |
+
}
|
| 200 |
+
|
| 201 |
+
const parsed = JSON.parse(jsonStr);
|
| 202 |
+
|
| 203 |
+
// Validate structure
|
| 204 |
+
if (parsed.summary && Array.isArray(parsed.deadlines)) {
|
| 205 |
+
return {
|
| 206 |
+
summary: parsed.summary,
|
| 207 |
+
deadlines: parsed.deadlines
|
| 208 |
+
.filter(d => d?.title && d?.due_date)
|
| 209 |
+
.map(d => ({
|
| 210 |
+
title: d.title,
|
| 211 |
+
due_date: d.due_date,
|
| 212 |
+
description: d.description || ''
|
| 213 |
+
}))
|
| 214 |
+
};
|
| 215 |
+
}
|
| 216 |
+
} catch (e) {
|
| 217 |
+
// JSON parse failed, fall back to text extraction
|
| 218 |
+
console.warn('Failed to parse AI JSON response, using fallback extraction');
|
| 219 |
+
}
|
| 220 |
+
|
| 221 |
+
// Fallback: return raw content with extracted deadlines from text
|
| 222 |
+
return {
|
| 223 |
+
summary: aiContent,
|
| 224 |
+
deadlines: extractDeadlinesFromText(aiContent).map(text => ({
|
| 225 |
+
title: text,
|
| 226 |
+
due_date: '',
|
| 227 |
+
description: ''
|
| 228 |
+
}))
|
| 229 |
+
};
|
| 230 |
};
|
| 231 |
|
| 232 |
const buildAnnouncementPrompt = (p, c) => {
|
|
|
|
| 297 |
addToast('Đã giải quyết');
|
| 298 |
fetchData();
|
| 299 |
}
|
| 300 |
+
} catch (error) {
|
| 301 |
+
console.error('handleResolveAlert error:', error);
|
| 302 |
+
addToast('Lỗi giải quyết cảnh báo', 'error');
|
| 303 |
+
}
|
| 304 |
};
|
| 305 |
|
| 306 |
const handleApproveSummary = async (draftId, spaceId, autoPilotData = null) => {
|
|
|
|
| 308 |
try {
|
| 309 |
const activeContent = autoPilotData?.content || aiPreview?.content || '';
|
| 310 |
const activeType = autoPilotData?.draft_type || aiPreview?.draft_type || '';
|
| 311 |
+
const activeDeadlines = autoPilotData?.metadata?.deadlines || aiPreview?.deadlines || [];
|
| 312 |
|
| 313 |
if (aiPreview?.id === draftId) await taService.updateSummaryDraft(draftId, spaceId, { content: activeContent });
|
| 314 |
const res = await taService.approveSummary(draftId, spaceId);
|
| 315 |
+
if (!res.success) {
|
| 316 |
+
console.error('Approve summary failed:', res);
|
| 317 |
+
throw new Error('API returned failure');
|
| 318 |
+
}
|
| 319 |
+
if (selectedSpaces.length > 1 && (!aiPreview || aiPreview.status === 'pending')) {
|
| 320 |
const otherSpaces = selectedSpaces.filter(id => id !== spaceId);
|
| 321 |
await Promise.all(otherSpaces.map(async (sid) => {
|
| 322 |
+
const newDraftRes = await taService.createSummaryDraft({ spaceId: sid, content: activeContent, draft_type: activeType, metadata: { deadlines: activeDeadlines } });
|
| 323 |
if (newDraftRes?.success) await taService.approveSummary(newDraftRes.data.id, sid);
|
| 324 |
}));
|
| 325 |
}
|
| 326 |
addToast('Đã đăng bài!'); fetchData(); setAiPreview(null); setCurrentStep(1);
|
| 327 |
+
} catch (error) {
|
| 328 |
+
console.error('handleApproveSummary error:', error);
|
| 329 |
+
addToast(error.message || 'Lỗi gửi bài', 'error');
|
| 330 |
+
} finally { setIsSending(false); }
|
| 331 |
};
|
| 332 |
|
| 333 |
const handleScheduleSummary = async (draftId, spaceId, scheduledAt, autoPilotData = null) => {
|
|
|
|
| 335 |
try {
|
| 336 |
const activeContent = autoPilotData?.content || aiPreview?.content || '';
|
| 337 |
const activeType = autoPilotData?.draft_type || aiPreview?.draft_type || '';
|
| 338 |
+
const activeDeadlines = autoPilotData?.metadata?.deadlines || aiPreview?.deadlines || [];
|
| 339 |
|
| 340 |
if (aiPreview?.id === draftId) await taService.updateSummaryDraft(draftId, spaceId, { content: activeContent });
|
| 341 |
const isoDate = new Date(scheduledAt).toISOString();
|
|
|
|
| 344 |
if (res.success && selectedSpaces.length > 1 && (!aiPreview || aiPreview.status === 'pending')) {
|
| 345 |
const otherSpaces = selectedSpaces.filter(id => id !== spaceId);
|
| 346 |
await Promise.all(otherSpaces.map(async (sid) => {
|
| 347 |
+
const newDraftRes = await taService.createSummaryDraft({ spaceId: sid, content: activeContent, draft_type: activeType, metadata: { deadlines: activeDeadlines } });
|
| 348 |
if (newDraftRes?.success) await taService.scheduleSummary(newDraftRes.data.id, sid, isoDate);
|
| 349 |
}));
|
| 350 |
}
|
| 351 |
|
| 352 |
addToast('Đã đặt lịch!'); fetchData(); setAiPreview(null); setCurrentStep(1);
|
| 353 |
+
} catch (error) {
|
| 354 |
+
console.error('handleScheduleSummary error:', error);
|
| 355 |
+
addToast(error.message || 'Lỗi đặt lịch', 'error');
|
| 356 |
+
} finally { setIsSending(false); }
|
| 357 |
};
|
| 358 |
|
| 359 |
const handleEditScheduled = (item) => {
|
| 360 |
+
// Normalize: add top-level deadlines from metadata for consistent extraction
|
| 361 |
+
const normalizedItem = {
|
| 362 |
+
...item,
|
| 363 |
+
deadlines: item.metadata?.deadlines || []
|
| 364 |
+
};
|
| 365 |
+
setAiPreview(normalizedItem);
|
| 366 |
setSelectedSpaces([item.space_id]);
|
| 367 |
if (item.draft_type === 'lesson_recap') { setActiveTab('summary'); setCurrentStep(3); }
|
| 368 |
else { setActiveTab('announcements'); }
|
|
|
|
| 378 |
}));
|
| 379 |
addToast('Đã hủy lịch hàng loạt');
|
| 380 |
fetchData();
|
| 381 |
+
} catch (e) {
|
| 382 |
+
console.error('handleBulkCancel error:', e);
|
| 383 |
+
addToast('Lỗi hủy lịch hàng loạt', 'error');
|
| 384 |
+
} finally { setIsSending(false); }
|
| 385 |
};
|
| 386 |
|
| 387 |
const handleBulkSendNow = async (ids) => {
|
|
|
|
| 393 |
}));
|
| 394 |
addToast('Đã gửi hàng loạt');
|
| 395 |
fetchData();
|
| 396 |
+
} catch (e) {
|
| 397 |
+
console.error('handleBulkSendNow error:', e);
|
| 398 |
+
addToast('Lỗi gửi hàng loạt', 'error');
|
| 399 |
+
} finally { setIsSending(false); }
|
| 400 |
};
|
| 401 |
|
| 402 |
const handleCancelSchedule = async (draftId, spaceId) => {
|
|
|
|
| 404 |
setIsSending(true);
|
| 405 |
const res = await taService.cancelSchedule(draftId, spaceId);
|
| 406 |
if (res.success) { addToast('Đã hủy lịch'); fetchData(); }
|
| 407 |
+
} catch (error) {
|
| 408 |
+
console.error('handleCancelSchedule error:', error);
|
| 409 |
+
addToast('Lỗi hủy lịch', 'error');
|
| 410 |
+
} finally { setIsSending(false); }
|
| 411 |
};
|
| 412 |
|
| 413 |
const handleRefineAi = async (refineInstruction) => {
|
|
|
|
| 437 |
addToast('Đã cập nhật bản thảo!');
|
| 438 |
}
|
| 439 |
} else { addToast('AI không phản hồi lệnh sửa', 'error'); }
|
| 440 |
+
} catch (error) {
|
| 441 |
+
console.error('handleRefineAi error:', error);
|
| 442 |
+
addToast('Lỗi hiệu chỉnh AI', 'error');
|
| 443 |
+
} finally { setIsAnalyzing(false); }
|
| 444 |
};
|
| 445 |
|
| 446 |
const handleOpenCompose = async (snapshotId, spaceId) => {
|
|
|
|
| 451 |
setCurrentContext({ id: snapshotId, space_id: spaceId, ...res.data, aiConfig, taName: user?.display_name });
|
| 452 |
setIsComposeOpen(true);
|
| 453 |
}
|
| 454 |
+
} catch (error) {
|
| 455 |
+
console.error('handleOpenCompose error:', error);
|
| 456 |
+
addToast('Lỗi mở compose', 'error');
|
| 457 |
+
} finally { setLoading(false); }
|
| 458 |
};
|
| 459 |
|
| 460 |
const handleAiSend = async (message) => {
|
|
|
|
| 466 |
content: message, snapshotId: currentContext.id
|
| 467 |
});
|
| 468 |
if (res.success) { setIsComposeOpen(false); handleResolveAlert(currentContext.id, currentContext.space_id); addToast('Đã gửi tin nhắn!'); }
|
| 469 |
+
} catch (error) {
|
| 470 |
+
console.error('handleAiSend error:', error);
|
| 471 |
+
addToast('Lỗi gửi tin nhắn', 'error');
|
| 472 |
+
} finally { setLoading(false); }
|
| 473 |
};
|
| 474 |
|
| 475 |
const RuleCheckbox = ({ label, checked, onChange }) => (
|
|
|
|
| 625 |
try {
|
| 626 |
const res = await taService.uploadSlide(selectedSpaces[0], file);
|
| 627 |
if (res.success) setUploadedFile({ ...res.data, rawFile: file });
|
| 628 |
+
} catch (error) {
|
| 629 |
+
console.error('handleFileUpload error:', error);
|
| 630 |
+
addToast('Lỗi tải file lên', 'error');
|
| 631 |
+
} finally { setUploading(false); }
|
| 632 |
}}
|
| 633 |
startAiAnalysis={async () => {
|
| 634 |
if (selectedSpaces.length === 0) return;
|
|
|
|
| 646 |
|
| 647 |
const aiContent = resAgent?.answer || resAgent?.content || resAgent?.response;
|
| 648 |
if (resAgent?.success && aiContent) {
|
| 649 |
+
// Parse structured JSON response
|
| 650 |
+
const parsed = parseAiRecapResponse(aiContent);
|
| 651 |
+
|
| 652 |
const res = await taService.createSummaryDraft({
|
| 653 |
spaceId: selectedSpaces[0],
|
| 654 |
+
content: parsed.summary,
|
| 655 |
draft_type: 'lesson_recap',
|
| 656 |
+
metadata: { deadlines: parsed.deadlines }
|
| 657 |
});
|
| 658 |
if (res?.success) {
|
| 659 |
if (scheduleDate) handleScheduleSummary(res.data.id, res.data.space_id, scheduleDate, res.data);
|
|
|
|
| 674 |
|
| 675 |
const aiContent = resAgent?.answer || resAgent?.content || resAgent?.response;
|
| 676 |
if (resAgent?.success && aiContent) {
|
| 677 |
+
// Parse structured JSON response
|
| 678 |
+
const parsed = parseAiRecapResponse(aiContent);
|
| 679 |
+
|
| 680 |
const res = await taService.createSummaryDraft({
|
| 681 |
spaceId: selectedSpaces[0],
|
| 682 |
+
content: parsed.summary,
|
| 683 |
draft_type: 'lesson_recap',
|
| 684 |
+
metadata: { deadlines: parsed.deadlines }
|
| 685 |
});
|
| 686 |
if (res?.success) {
|
| 687 |
+
setAiPreview({ ...res.data, deadlines: parsed.deadlines });
|
| 688 |
setCurrentStep(3);
|
| 689 |
}
|
| 690 |
} else { addToast('AI không phản hồi', 'error'); }
|
|
|
|
| 736 |
setAiPreview(res.data);
|
| 737 |
}
|
| 738 |
} else { addToast('AI không phản hồi', 'error'); }
|
| 739 |
+
} catch (error) {
|
| 740 |
+
console.error('Announcement AI error:', error);
|
| 741 |
+
addToast('Lỗi khi soạn thông báo', 'error');
|
| 742 |
+
} finally { setIsAnalyzing(false); }
|
| 743 |
}}
|
| 744 |
loading={isAnalyzing} aiPreview={aiPreview} setAiPreview={setAiPreview} handleApprove={handleApproveSummary} handleSchedule={handleScheduleSummary}
|
| 745 |
scheduleDate={scheduleDate} setScheduleDate={setScheduleDate} selectedSpaces={selectedSpaces} setSelectedSpaces={setSelectedSpaces}
|
src/services/ta.service.js
CHANGED
|
@@ -37,6 +37,15 @@ const taService = {
|
|
| 37 |
* Duyệt bản tóm tắt
|
| 38 |
*/
|
| 39 |
approveSummary: async (draftId, spaceId) => {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
const response = await api.post(`/ta/summary-queue/${draftId}/approve?spaceId=${spaceId}`);
|
| 41 |
return response.data;
|
| 42 |
},
|
|
|
|
| 37 |
* Duyệt bản tóm tắt
|
| 38 |
*/
|
| 39 |
approveSummary: async (draftId, spaceId) => {
|
| 40 |
+
console.log('=== approveSummary DEBUG ===');
|
| 41 |
+
console.log('draftId:', draftId, 'type:', typeof draftId);
|
| 42 |
+
console.log('spaceId:', spaceId, 'type:', typeof spaceId);
|
| 43 |
+
console.log('URL:', `/ta/summary-queue/${draftId}/approve?spaceId=${spaceId}`);
|
| 44 |
+
|
| 45 |
+
if (!draftId || !spaceId) {
|
| 46 |
+
throw new Error(`Invalid IDs: draftId=${draftId}, spaceId=${spaceId}`);
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
const response = await api.post(`/ta/summary-queue/${draftId}/approve?spaceId=${spaceId}`);
|
| 50 |
return response.data;
|
| 51 |
},
|