openlearn-next 0.7.3 → 0.7.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/cli-doctor.mjs +18 -5
- package/cli.mjs +49 -0
- package/dist/assets/{ActivityWorkspaceWidget-5PzyxF6a.js → ActivityWorkspaceWidget-Dq4kjBS2.js} +1 -1
- package/dist/assets/{AdminDirectoryView-BCtmQTEK.js → AdminDirectoryView-B6OAs5Rj.js} +1 -1
- package/dist/assets/AssessmentCenter-B3f1DxSG.js +1 -0
- package/dist/assets/AssignmentFilePreview-DQbvbgnd.js +3 -0
- package/dist/assets/{BatchPickerModal-CbmjInp7.js → BatchPickerModal-CoOWloSS.js} +1 -1
- package/dist/assets/{ClassesView-DEnMaDOt.js → ClassesView-Q56Kf4eL.js} +5 -5
- package/dist/assets/{ClassroomCountdownWidget-CHq4983w.js → ClassroomCountdownWidget-C6luhy8P.js} +1 -1
- package/dist/assets/{CloudDriveModal-D2t3Uyzr.js → CloudDriveModal-DLf4fbKR.js} +1 -1
- package/dist/assets/ComputerLabView-D9-Tk7yA.js +1 -0
- package/dist/assets/{CourseManagement-DOXyJ_j-.js → CourseManagement-DNSfyre-.js} +1 -1
- package/dist/assets/{CourseWizardModal-Dbi6Caw2.js → CourseWizardModal-DAueHXcl.js} +1 -1
- package/dist/assets/{CoursewareHubPanel-CyxDIJAF.js → CoursewareHubPanel-DLjH-UzF.js} +1 -1
- package/dist/assets/{Dashboard-2rLu_ZBy.js → Dashboard-qMVadkd2.js} +2 -2
- package/dist/assets/{ExportWeightModal-BrmAh6VX.js → ExportWeightModal-Dkh7OC8U.js} +1 -1
- package/dist/assets/{HelpTour-D_q1z_MI.js → HelpTour-BLuiS8rr.js} +1 -1
- package/dist/assets/{HelpView-CVBrKDrd.js → HelpView-CTF6B9ml.js} +1 -1
- package/dist/assets/{ImportLessonsModal-PhHtX31R.js → ImportLessonsModal-Ra-OF_Vo.js} +1 -1
- package/dist/assets/{ImportModal-DqjqNeH4.js → ImportModal-H5s5MyZY.js} +1 -1
- package/dist/assets/{InteractiveCoursewareViewer-Vi6lLz7a.js → InteractiveCoursewareViewer-BfHEDrCf.js} +1 -1
- package/dist/assets/InteractiveWhiteboard-KBzmiNBG.js +65 -0
- package/dist/assets/{LazyCourseware-KIt0rGtW.js → LazyCourseware-BaZS8-i1.js} +2 -2
- package/dist/assets/LazyWhiteboard-DGyln3JC.js +2 -0
- package/dist/assets/LessonEditorView-B5JXYyup.js +1 -0
- package/dist/assets/{LiveClassroomView-BRsZ6QEq.js → LiveClassroomView-DezAlve2.js} +7 -7
- package/dist/assets/{NotificationDetailModal-tY5rxYCN.js → NotificationDetailModal-DbMMrIHQ.js} +1 -1
- package/dist/assets/{PluginView-CedpAW6d.js → PluginView-CvIr-ANj.js} +1 -1
- package/dist/assets/{ProcessLogsModal-Cfr-T9Gd.js → ProcessLogsModal-_BwkmJVw.js} +1 -1
- package/dist/assets/{QuizGeneratorModal-bUA002kP.js → QuizGeneratorModal-CTj2moWM.js} +1 -1
- package/dist/assets/StudentAssignmentEvalPanel-DrfZT8Lr.js +1 -0
- package/dist/assets/{StudentAssignmentView-BhAz6Hfk.js → StudentAssignmentView-D_EhblVQ.js} +1 -1
- package/dist/assets/StudentLessonView-BHG50-4M.js +1 -0
- package/dist/assets/{StudentView-PHKxIV9T.js → StudentView-DKDhnJQx.js} +2 -2
- package/dist/assets/{SystemResourceLibraryModal-DgXYe6_x.js → SystemResourceLibraryModal-C5y8H5Pf.js} +1 -1
- package/dist/assets/TeacherView-CWDK3DOe.js +2 -0
- package/dist/assets/{TimetableView-D2ozoj_D.js → TimetableView-C8kdJlf7.js} +1 -1
- package/dist/assets/{UTIF-FfCsrHhe.js → UTIF-4E-wdh3X.js} +1 -1
- package/dist/assets/index-BBznBaN5.js +131 -0
- package/dist/assets/index-DPky2leG.css +1 -0
- package/dist/assets/{index.es-DyEF0CZ9.js → index.es-WXdI0L-F.js} +1 -1
- package/dist/assets/pluginParsers-DkiO-BBu.js +1 -0
- package/dist/assets/student-workspace-1yVTbWHC.js +1 -0
- package/dist/assets/{vendor-icons-CXrZ3R2V.js → vendor-icons-hc6MjYFv.js} +1 -1
- package/dist/assets/{vendor-pdf-C1Vh2Aqq.js → vendor-pdf-By0fofA0.js} +3 -3
- package/dist/assets/{vendor-pptx-Bzn6whfK.js → vendor-pptx-EjNUtdws.js} +178 -178
- package/dist/cli-migrator.cjs +18 -1
- package/dist/index.html +3 -3
- package/dist/server.cjs +731 -277
- package/package.json +7 -4
- package/scripts/assert-node-runtime.mjs +165 -0
- package/dist/assets/AssessmentCenter-B43ojHG-.js +0 -1
- package/dist/assets/ComputerLabView-YtnoT5Rb.js +0 -1
- package/dist/assets/InteractiveWhiteboard-lTzeayX5.js +0 -65
- package/dist/assets/LazyWhiteboard-CCN3pYcW.js +0 -2
- package/dist/assets/LessonEditorView-D_GVhHuM.js +0 -1
- package/dist/assets/StudentLessonView-DC1yX4kG.js +0 -1
- package/dist/assets/TeacherView-DscJtOJ9.js +0 -2
- package/dist/assets/index-2676X8YB.js +0 -131
- package/dist/assets/index-C6hlh8pw.css +0 -1
- package/dist/assets/pluginParsers-e4PSm2Iy.js +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openlearn-next",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.5",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "A plugin-driven Educational OS / LMS platform with AI agent teaching assistant",
|
|
6
6
|
"keywords": [
|
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
"cli-cleaner.mjs",
|
|
36
36
|
"cli-doctor.mjs",
|
|
37
37
|
"cli-data.mjs",
|
|
38
|
+
"scripts/assert-node-runtime.mjs",
|
|
38
39
|
"package.json",
|
|
39
40
|
"README.md"
|
|
40
41
|
],
|
|
@@ -44,19 +45,21 @@
|
|
|
44
45
|
"registry": "https://registry.npmjs.org/"
|
|
45
46
|
},
|
|
46
47
|
"engines": {
|
|
47
|
-
"node": "
|
|
48
|
+
"node": "20.x || 22.x || 24.x"
|
|
48
49
|
},
|
|
49
50
|
"packageManager": "pnpm@10.33.4",
|
|
50
51
|
"scripts": {
|
|
52
|
+
"preinstall": "node scripts/assert-node-runtime.mjs",
|
|
51
53
|
"dev": "tsx server.ts",
|
|
52
54
|
"dev:demo": "tsx scripts/dev-demo.ts",
|
|
53
55
|
"build:bridge-sdk": "node scripts/build-bridge-sdk.mjs",
|
|
54
56
|
"build:plugin-sdk": "node scripts/build-plugin-sdk.mjs",
|
|
55
57
|
"prepare": "node scripts/build-plugin-sdk.mjs",
|
|
58
|
+
"prepublishOnly": "node scripts/assert-publishable.mjs",
|
|
56
59
|
"build": "node scripts/build-plugin-sdk.mjs && node scripts/build-bridge-sdk.mjs && vite build && node scripts/build-plugins.mjs && esbuild server.ts --bundle --platform=node --format=cjs --packages=external --sourcemap --outfile=dist/server.cjs && esbuild server/security/env-validation.ts --bundle --platform=node --format=cjs --packages=external --outfile=dist/env-validation.cjs && esbuild packages/core/db/migrator.ts --bundle --platform=node --format=cjs --packages=external --outfile=dist/cli-migrator.cjs && cp packages/core/worker-runtime/plugin-net-preload.cjs dist/plugin-net-preload.cjs",
|
|
57
60
|
"start": "node dist/server.cjs",
|
|
58
61
|
"preview": "vite preview",
|
|
59
|
-
"clean": "rm -rf dist server.js",
|
|
62
|
+
"clean": "rm -rf dist server.js .vite-cache",
|
|
60
63
|
"lint": "tsc --noEmit",
|
|
61
64
|
"lint:strict": "tsc --noEmit -p tsconfig.strict.json",
|
|
62
65
|
"lint:eslint": "eslint . --ext .ts,.tsx --ignore-pattern dist --ignore-pattern node_modules",
|
|
@@ -82,7 +85,7 @@
|
|
|
82
85
|
},
|
|
83
86
|
"dependencies": {
|
|
84
87
|
"@google/genai": "^2.4.0",
|
|
85
|
-
"@openlearn/plugin-sdk": "
|
|
88
|
+
"@openlearn/plugin-sdk": "3.7.4",
|
|
86
89
|
"bcryptjs": "^3.0.3",
|
|
87
90
|
"better-sqlite3": "^12.11.1",
|
|
88
91
|
"chokidar": "^5.0.0",
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* 安装期 Node 运行时守卫(`package.json` 的 `preinstall` 钩子)。
|
|
4
|
+
*
|
|
5
|
+
* ## 为什么需要这道闸
|
|
6
|
+
*
|
|
7
|
+
* 本平台的硬依赖 `better-sqlite3` 是**原生模块**:它的 `.node` 二进制按 Node 的
|
|
8
|
+
* ABI(`process.versions.modules`)编译,**不是**跨 Node 版本通用的 JS 包。
|
|
9
|
+
* 于是「换了个 Node 版本、但复用同一份 node_modules」会在运行期炸出:
|
|
10
|
+
*
|
|
11
|
+
* ERR_DLOPEN_FAILED
|
|
12
|
+
* The module '.../better_sqlite3.node' was compiled against a different Node.js version
|
|
13
|
+
* using NODE_MODULE_VERSION 127. This version of Node.js requires NODE_MODULE_VERSION 147.
|
|
14
|
+
*
|
|
15
|
+
* 这条报错对客户毫无可操作性:它既没说「你的 Node 版本不被支持」,也没说
|
|
16
|
+
* 「该重建还是该换版本」。而在**安装期**就把版本问题拦下来,报错就能说人话。
|
|
17
|
+
*
|
|
18
|
+
* ## 为什么是硬失败(而不是只告警)
|
|
19
|
+
*
|
|
20
|
+
* npm 的 `engines` 默认只是**警告**(除非用户开了 `engine-strict`),
|
|
21
|
+
* 单靠 engines 拦不住跑在 Node 26 上的客户机 —— 装得上、起不来。
|
|
22
|
+
* `preinstall` 是安装流程里唯一能在依赖装完前拿到控制权的钩子:这里失败,
|
|
23
|
+
* 安装当场中止,用户看到的是一段可执行的修复指引,而不是半小时后的运行期栈。
|
|
24
|
+
*
|
|
25
|
+
* ## 支持范围为什么是 20.x / 22.x / 24.x
|
|
26
|
+
*
|
|
27
|
+
* - **20.x**:仍在现场的旧 LTS(better-sqlite3 官方提供预编译产物)。
|
|
28
|
+
* - **22.x**:CI 全程、Docker 镜像(`node:22-alpine`)、`.nvmrc` / `.node-version` 都钉它。
|
|
29
|
+
* - **24.x**:当前 LTS,本项目有全量单测通过的记录。
|
|
30
|
+
* - **不含 21.x / 23.x**:非 LTS 奇数版本,且 better-sqlite3 官方 engines 不含 21.x
|
|
31
|
+
* (无预编译产物 → 客户机必须自带编译工具链,装不上)。
|
|
32
|
+
* - **不含 25.x / 26.x**:本项目未验证;实测 Node 26 下复用 Node 22 装的
|
|
33
|
+
* node_modules 直接 `ERR_DLOPEN_FAILED`。对**未验证**的版本采取
|
|
34
|
+
* 「拦住并说清楚」,而不是「放行然后在运行期炸」。
|
|
35
|
+
*
|
|
36
|
+
* `package.json` 的 `engines.node` 与本文件的 `SUPPORTED_NODE_MAJORS` 必须一致 ——
|
|
37
|
+
* 由 `scripts/__tests__/node-runtime-guard.test.ts` 盯着,漂了就红。
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* 受支持的 Node 主版本。
|
|
42
|
+
*
|
|
43
|
+
* 与 `package.json` 的 `engines.node`(`20.x || 22.x || 24.x`)、CI 的
|
|
44
|
+
* node-runtime-compat 矩阵、server 侧启动守卫共用同一份口径。
|
|
45
|
+
*/
|
|
46
|
+
export const SUPPORTED_NODE_MAJORS = Object.freeze([20, 22, 24]);
|
|
47
|
+
|
|
48
|
+
/** 对外文案里的支持范围描述。 */
|
|
49
|
+
export const SUPPORTED_NODE_TEXT = 'Node.js 20.x / 22.x / 24.x';
|
|
50
|
+
|
|
51
|
+
/** 仓库钉住的版本(.nvmrc / .node-version / Dockerfile 都用它)。 */
|
|
52
|
+
export const PINNED_NODE_MAJOR = 22;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* 从版本串里解析主版本号。
|
|
56
|
+
*
|
|
57
|
+
* @param {string} version 形如 `v22.16.0` / `22.16.0`
|
|
58
|
+
* @returns {number|null} 主版本号;解析不出来返回 null
|
|
59
|
+
*/
|
|
60
|
+
export function parseNodeMajor(version) {
|
|
61
|
+
if (typeof version !== 'string') return null;
|
|
62
|
+
const matched = /^v?(\d+)\./.exec(version.trim());
|
|
63
|
+
if (!matched) return null;
|
|
64
|
+
const major = Number(matched[1]);
|
|
65
|
+
return Number.isFinite(major) ? major : null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* 判定某个 Node 版本是否受支持。
|
|
70
|
+
*
|
|
71
|
+
* 纯函数:不读 `process`、不打印、不退出,便于逐个版本覆盖单测。
|
|
72
|
+
*
|
|
73
|
+
* @param {string} version 待判定的版本串
|
|
74
|
+
* @param {readonly number[]} [supported] 受支持的主版本列表
|
|
75
|
+
* @returns {{ok: boolean, code: 'supported'|'unsupported'|'unparseable', major: number|null, message: string}}
|
|
76
|
+
*/
|
|
77
|
+
export function checkNodeVersion(version, supported = SUPPORTED_NODE_MAJORS) {
|
|
78
|
+
const major = parseNodeMajor(version);
|
|
79
|
+
if (major === null) {
|
|
80
|
+
return {
|
|
81
|
+
ok: false,
|
|
82
|
+
code: 'unparseable',
|
|
83
|
+
major: null,
|
|
84
|
+
message: `无法解析 Node 版本「${version}」。`,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
if (supported.includes(major)) {
|
|
88
|
+
return { ok: true, code: 'supported', major, message: '' };
|
|
89
|
+
}
|
|
90
|
+
return {
|
|
91
|
+
ok: false,
|
|
92
|
+
code: 'unsupported',
|
|
93
|
+
major,
|
|
94
|
+
message:
|
|
95
|
+
`当前 Node 版本 v${major}.x 不受支持(支持范围:${SUPPORTED_NODE_TEXT})。` +
|
|
96
|
+
`本项目的原生依赖(better-sqlite3)按 Node ABI 编译,复用旧 node_modules 会在运行期报 ` +
|
|
97
|
+
`ERR_DLOPEN_FAILED / NODE_MODULE_VERSION 不一致。`,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* 生成可执行的修复指引。
|
|
103
|
+
*
|
|
104
|
+
* @param {number|null} major 当前主版本号
|
|
105
|
+
* @returns {string} 多行指引文本
|
|
106
|
+
*/
|
|
107
|
+
export function buildFixGuidance(major) {
|
|
108
|
+
const pinned = PINNED_NODE_MAJOR;
|
|
109
|
+
const lines = [
|
|
110
|
+
'怎么修(任选其一):',
|
|
111
|
+
` 1) 切到受支持的 Node 版本(本项目实测 ${SUPPORTED_NODE_MAJORS.join(' / ')}):`,
|
|
112
|
+
` nvm install ${pinned} && nvm use ${pinned}`,
|
|
113
|
+
` fnm install ${pinned} && fnm use ${pinned}`,
|
|
114
|
+
` asdf install nodejs ${pinned}.16.0 && asdf local nodejs ${pinned}.16.0`,
|
|
115
|
+
' 仓库根的 .nvmrc / .node-version 已钉住 ' + pinned + ',nvm / fnm 会自动采用。',
|
|
116
|
+
' 2) 用官方容器镜像(内含 Node ' + pinned + ',宿主机无需装 Node):',
|
|
117
|
+
' docker compose up -d',
|
|
118
|
+
' 3) 确实要用别的 Node 版本:必须让原生模块按新 ABI 重新编译 ——',
|
|
119
|
+
' 保留旧 node_modules 只会继续复用 ABI 不匹配的二进制:',
|
|
120
|
+
' rm -rf node_modules && pnpm install',
|
|
121
|
+
];
|
|
122
|
+
if (major !== null && major === 21) {
|
|
123
|
+
lines.push(
|
|
124
|
+
' 注:Node 21 不是 LTS,且 better-sqlite3 官方不提供 21.x 预编译产物,',
|
|
125
|
+
' 重装后仍需要本地编译工具链(python3 + make + g++),不建议用于生产。',
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
return lines.join('\n');
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* 组装完整的失败文案(标题 + 原因 + 修复指引)。
|
|
133
|
+
*
|
|
134
|
+
* @param {string} version 当前版本串
|
|
135
|
+
* @param {ReturnType<typeof checkNodeVersion>} [check] 已算好的判定结果
|
|
136
|
+
* @returns {string}
|
|
137
|
+
*/
|
|
138
|
+
export function formatUnsupportedMessage(version, check = checkNodeVersion(version)) {
|
|
139
|
+
if (check.ok) return '';
|
|
140
|
+
const head = `[NodeRuntime] FATAL: ${check.message}`;
|
|
141
|
+
return `${head}\n\n${buildFixGuidance(check.major)}\n`;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* 以主模块方式运行时:判定当前 Node 版本,不受支持就打印并退出码 1。
|
|
146
|
+
*
|
|
147
|
+
* @returns {void}
|
|
148
|
+
*/
|
|
149
|
+
export function runCliGuard() {
|
|
150
|
+
const result = checkNodeVersion(process.version);
|
|
151
|
+
if (result.ok) return;
|
|
152
|
+
console.error(formatUnsupportedMessage(process.version, result));
|
|
153
|
+
process.exit(1);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// 仅在作为主入口脚本执行时唤起(被 import 时不触发 —— server / CLI 侧各自按需调用)
|
|
157
|
+
const invokedDirectly =
|
|
158
|
+
process.argv[1] &&
|
|
159
|
+
(process.argv[1].endsWith('assert-node-runtime.mjs') ||
|
|
160
|
+
process.argv[1].endsWith('assert-node-runtime') ||
|
|
161
|
+
process.argv[1].includes('assert-node-runtime'));
|
|
162
|
+
|
|
163
|
+
if (invokedDirectly) {
|
|
164
|
+
runCliGuard();
|
|
165
|
+
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{r as n,j as e}from"./vendor-react-Kv_JATwr.js";import{a as k,$ as U,E as ae,c as re,a0 as K}from"./index-2676X8YB.js";import{ay as X,R as Y,c as T,o as ie,z as oe,D as de,ae as ce,aZ as xe,ak as V,aq as ne,ce as me,bQ as ue,ac as ee,N as be,U as pe,E as he,an as Z,x as ge,T as fe,f as ye}from"./vendor-icons-CXrZ3R2V.js";import"./vendor-utils-aM7aYvPP.js";import"./vendor-pdf-C1Vh2Aqq.js";import"./vendor-motion-BFAPOs-b.js";import"./vendor-charts-SrpBsvM3.js";const se="w-full px-2.5 py-1.5 border border-theme rounded-lg text-xs bg-surface text-main focus:outline-none focus:ring-1 focus:ring-indigo-500";function je(l){return l==null?"(未作答)":Array.isArray(l)?l.map(i=>String(i)).join(" | "):typeof l=="object"?JSON.stringify(l):String(l)}function ve({assignment:l,lang:i,onBack:c}){const t=i==="zh",[h,f]=n.useState(null),[b,y]=n.useState(null),[x,o]=n.useState(null),[v,u]=n.useState({}),[w,C]=n.useState({}),[A,g]=n.useState(!1),[j,S]=n.useState(null),[$,a]=n.useState(null),[N,P]=n.useState(null),[I,E]=n.useState({}),[L,m]=n.useState(null),[p,M]=n.useState(new Set),F=n.useCallback(async()=>{g(!0),a(null);try{const s=await k.listAttempts(l.id);f(s.items)}catch(s){a(s instanceof U?s.message:String(s?.message??s))}finally{g(!1)}},[l.id]);n.useEffect(()=>{F()},[F]);const _=async s=>{y(s),o(null),a(null),E({}),M(new Set);try{const r=await k.getTeacherView(s);o(r),u(Object.fromEntries(r.items.map(d=>[d.itemId,d.score===null?"":String(d.score)]))),C(Object.fromEntries(r.items.map(d=>[d.itemId,d.feedback??""])))}catch(r){a(r?.message??String(r))}},Q=async s=>{if(x){m(s.itemId),a(null);try{const r=await k.aiSuggestGrade(x.attempt.id,s.itemId);E(d=>({...d,[s.itemId]:r})),M(d=>{const B=new Set(d);return B.delete(s.itemId),B})}catch(r){a(t?"AI 建议不可用:"+(r?.message??String(r)):"AI suggestion unavailable: "+(r?.message??String(r)))}finally{m(null)}}},H=s=>{const r=I[s.itemId];r&&(u(d=>({...d,[s.itemId]:String(r.suggestedScore)})),M(d=>new Set(d).add(s.itemId)))},J=async s=>{if(!x)return;const r=v[s.itemId];if(r===""||r===void 0){a(t?"请填写分数":"Score is required");return}S(s.itemId),a(null);try{await k.gradeAnswer(x.attempt.id,s.itemId,{score:Number(r),feedback:w[s.itemId]||null,acceptedAiSuggestion:p.has(s.itemId),suggestedScore:I[s.itemId]?.suggestedScore});const d=await k.getTeacherView(x.attempt.id);o(d),P(t?"已保存该题评分(自动判分不再覆盖)":"Score saved — auto-grading will not override it."),await F()}catch(d){a(d?.message??String(d))}finally{S(null)}};return e.jsxs("div",{className:"flex-1 flex flex-col min-h-0 bg-surface border border-theme rounded-2xl shadow-xl overflow-hidden",children:[e.jsxs("div",{className:"px-5 py-3 border-b border-theme bg-surface-secondary/70 flex flex-wrap items-center gap-3 shrink-0",children:[e.jsx("button",{type:"button",onClick:c,className:"p-1.5 rounded-lg border border-theme bg-surface text-muted hover:text-main cursor-pointer","aria-label":t?"返回试卷":"Back to assessment",children:e.jsx(X,{size:15})}),e.jsxs("div",{className:"min-w-0 flex-1",children:[e.jsxs("h1",{className:"text-sm font-bold text-main truncate",children:[t?"人工批改":"Manual grading"," · ",l.className??l.classId]}),e.jsx("p",{className:"text-2xs text-muted",children:t?"简答题与「无法自动判分」的题在这里给分;保存后该题不再被自动判分覆盖。":"Grade short answers and any item the grader could not score. Saved scores are never auto-overwritten."})]}),e.jsxs("button",{type:"button",onClick:()=>void F(),disabled:A,className:"inline-flex items-center gap-1.5 px-3 py-1.5 rounded-xl border border-theme bg-surface text-xs font-semibold text-main disabled:opacity-50 cursor-pointer",children:[e.jsx(Y,{size:13,className:A?"animate-spin":""}),t?"刷新":"Refresh"]})]}),($||N)&&e.jsx("div",{role:$?"alert":"status",className:`mx-5 mt-3 px-3 py-2 rounded-xl border text-xs ${$?"border-rose-200 bg-rose-50 text-rose-700":"border-emerald-200 bg-emerald-50 text-emerald-700"}`,children:$??N}),e.jsxs("div",{className:"flex-1 overflow-y-auto p-5 grid grid-cols-1 lg:grid-cols-[280px_1fr] gap-4",children:[e.jsxs("section",{"aria-labelledby":"attempts-heading",children:[e.jsxs("h2",{id:"attempts-heading",className:"text-xs font-bold text-main mb-2",children:[t?"作答列表":"Attempts","(",h?.length??0,")"]}),h===null&&A?e.jsxs("p",{className:"text-xs text-muted flex items-center gap-2",children:[e.jsx(T,{size:13,className:"animate-spin"}),t?"加载中…":"Loading…"]}):(h?.length??0)===0?e.jsx("p",{className:"text-xs text-muted",children:t?"还没有人交卷。":"No submissions yet."}):e.jsx("ul",{"data-testid":"grading-attempt-list",className:"space-y-1.5",children:h.map(s=>{const r=b===s.id,d=Number(s.proctor?.summary?.total??0);return e.jsx("li",{children:e.jsxs("button",{type:"button",onClick:()=>void _(s.id),"aria-pressed":r,className:`w-full text-left px-3 py-2 rounded-xl border text-xs cursor-pointer ${r?"border-indigo-400 bg-indigo-50 dark:bg-indigo-950/40":"border-theme hover:bg-surface-secondary"}`,children:[e.jsxs("div",{className:"flex items-center justify-between gap-1",children:[e.jsx("span",{className:"font-semibold text-main block truncate",children:s.anonymousLabel??s.studentId??s.id}),d>0&&e.jsxs("span",{className:"px-1.5 py-0.2 rounded text-3xs font-bold bg-rose-100 text-rose-700 dark:bg-rose-950/50 dark:text-rose-300 shrink-0",title:t?`监考异常事件 ${d} 次`:`${d} proctor violations`,children:["⚠️ ",d]})]}),e.jsxs("span",{className:"text-2xs text-muted",children:[s.status," · ",s.score??"—","/",s.maxScore??"—",s.needsManualReview&&e.jsx("span",{className:"ml-1 text-amber-700 dark:text-amber-300",children:t?"待批改":"pending"})]})]})},s.id)})})]}),e.jsxs("section",{"aria-labelledby":"grade-heading",children:[e.jsxs("div",{className:"flex items-center justify-between mb-2",children:[e.jsx("h2",{id:"grade-heading",className:"text-xs font-bold text-main",children:t?"逐题批改":"Grade items"}),x&&Number(x.attempt.proctor?.summary?.total??0)>0&&e.jsxs("div",{className:"px-2 py-0.5 rounded-lg border border-rose-200 bg-rose-50 text-rose-700 dark:bg-rose-950/40 dark:text-rose-300 dark:border-rose-800 text-2xs font-semibold",children:["⚠️ ",t?"监考异常":"Proctor alert",":",t?"切屏":"tab switch"," ",x.attempt.proctor?.summary?.visibility_hidden??0," · ",t?"失焦":"blur"," ",x.attempt.proctor?.summary?.blur??0," · ",t?"退出全屏":"exit fullscreen"," ",x.attempt.proctor?.summary?.fullscreen_exit??0]})]}),x?e.jsx("ul",{className:"space-y-3",children:x.items.map((s,r)=>e.jsxs("li",{"data-testid":`grading-item-${s.itemId}`,className:"border border-theme rounded-xl p-3 space-y-2",children:[e.jsxs("div",{className:"flex flex-wrap items-center gap-2",children:[e.jsxs("span",{className:"text-2xs font-bold text-muted",children:["#",r+1]}),e.jsx("span",{className:"text-xs font-semibold text-main flex-1 min-w-0 line-clamp-2",children:s.stem}),e.jsxs("span",{className:"text-2xs text-muted",children:[s.score??"—","/",s.maxScore]}),s.manualOverride&&e.jsxs("span",{className:"text-2xs text-indigo-600 inline-flex items-center gap-0.5",children:[e.jsx(ie,{size:11}),t?"已人工批改":"manual"]})]}),e.jsx("p",{className:"text-xs text-main bg-surface-secondary rounded-lg px-2 py-1.5 whitespace-pre-wrap break-words",children:je(s.answer)}),s.needsManualReview&&e.jsxs("div",{className:"space-y-1.5",children:[e.jsxs("button",{type:"button",onClick:()=>void Q(s),disabled:L===s.itemId,className:"inline-flex items-center gap-1 px-2 py-1 rounded-lg border border-theme text-2xs font-semibold text-main hover:bg-surface-secondary disabled:opacity-50 cursor-pointer",children:[L===s.itemId?e.jsx(T,{size:11,className:"animate-spin"}):e.jsx(oe,{size:11}),t?"AI 建议":"AI suggestion"]}),I[s.itemId]&&e.jsxs("div",{className:"text-2xs text-muted bg-indigo-50 dark:bg-indigo-950/30 border border-indigo-200 dark:border-indigo-900 rounded-lg px-2 py-1.5 space-y-1",children:[e.jsxs("p",{className:"text-main",children:[t?"建议分":"Suggested"," ",I[s.itemId].suggestedScore,"/",s.maxScore,I[s.itemId].confidence!==null&&" · "+(t?"置信度":"confidence")+" "+Math.round(I[s.itemId].confidence*100)+"%",p.has(s.itemId)&&" · "+(t?"已填入,待保存":"applied, pending save")]}),e.jsx("p",{className:"whitespace-pre-wrap break-words",children:I[s.itemId].rationale}),e.jsx("button",{type:"button",onClick:()=>H(s),className:"inline-flex items-center gap-1 px-2 py-0.5 rounded-md bg-indigo-600 text-white text-2xs font-bold hover:bg-indigo-700 cursor-pointer",children:t?"采纳并填入分数":"Apply to score"})]})]}),e.jsxs("div",{className:"grid grid-cols-1 sm:grid-cols-[100px_1fr_auto] gap-2 items-end",children:[e.jsxs("div",{children:[e.jsx("label",{htmlFor:`score-${s.itemId}`,className:"block text-2xs font-semibold text-muted mb-1",children:t?"得分":"Score"}),e.jsx("input",{id:`score-${s.itemId}`,type:"number",min:0,max:s.maxScore,step:.5,value:v[s.itemId]??"",onChange:d=>u(B=>({...B,[s.itemId]:d.target.value})),className:se})]}),e.jsxs("div",{children:[e.jsx("label",{htmlFor:`feedback-${s.itemId}`,className:"block text-2xs font-semibold text-muted mb-1",children:t?"评语(可选)":"Feedback (optional)"}),e.jsx("input",{id:`feedback-${s.itemId}`,type:"text",value:w[s.itemId]??"",onChange:d=>C(B=>({...B,[s.itemId]:d.target.value})),className:se})]}),e.jsxs("button",{type:"button",onClick:()=>void J(s),disabled:j===s.itemId,className:"inline-flex items-center gap-1.5 px-3 py-1.5 rounded-xl bg-indigo-600 text-white text-xs font-bold hover:bg-indigo-700 disabled:opacity-50 cursor-pointer",children:[j===s.itemId&&e.jsx(T,{size:13,className:"animate-spin"}),t?"保存评分":"Save"]})]})]},s.itemId))}):e.jsx("p",{className:"text-xs text-muted",children:t?"从左侧选择一份作答。":"Pick an attempt on the left."})]})]})]})}function Ne(l){return{id:l.id,kind:l.kind,title:l.title,description:l.description,status:l.status,itemCount:l.itemCount,totalScore:l.totalScore,settings:l.settings,createdAt:l.createdAt,updatedAt:l.updatedAt}}function ke(l){return{attemptCount:l.attemptCount,submittedCount:l.submittedCount,gradedCount:l.gradedCount,needsManualReviewCount:l.needsManualReviewCount,averageScore:l.averageScore,averagePercent:l.averagePercent,maxScore:l.maxScore,items:l.items.map(i=>({itemId:i.itemId,type:i.type,stem:i.stem,maxScore:i.maxScore,answered:i.answered,graded:i.graded,correct:i.correct,correctRate:i.correctRate,avgScore:i.avgScore,distribution:i.distribution}))}}function Se({assignment:l,lang:i,onBack:c}){const t=i==="zh",[h,f]=n.useState(null),[b,y]=n.useState(!1),[x,o]=n.useState(null),v=n.useCallback(async()=>{y(!0),o(null);try{f(await k.stats(l.id))}catch(u){o(u instanceof U?u.message:String(u?.message??u))}finally{y(!1)}},[l.id]);return n.useEffect(()=>{v()},[v]),e.jsxs("div",{className:"flex-1 flex flex-col min-h-0 bg-surface border border-theme rounded-2xl shadow-xl overflow-hidden",children:[e.jsxs("div",{className:"px-5 py-3 border-b border-theme bg-surface-secondary/70 flex flex-wrap items-center gap-3 shrink-0",children:[e.jsx("button",{type:"button",onClick:c,className:"p-1.5 rounded-lg border border-theme bg-surface text-muted hover:text-main cursor-pointer","aria-label":t?"返回试卷":"Back to assessment",children:e.jsx(X,{size:15})}),e.jsx("div",{className:"min-w-0 flex-1",children:e.jsxs("h1",{className:"text-sm font-bold text-main truncate",children:[t?"统计":"Statistics"," · ",l.className??l.classId]})}),e.jsxs("a",{href:k.exportCsvUrl(l.id),className:"inline-flex items-center gap-1.5 px-3 py-1.5 rounded-xl border border-theme bg-surface text-xs font-semibold text-main hover:bg-surface-secondary",children:[e.jsx(de,{size:13}),t?"导出 CSV":"Export CSV"]}),e.jsxs("button",{type:"button",onClick:()=>void v(),disabled:b,className:"inline-flex items-center gap-1.5 px-3 py-1.5 rounded-xl border border-theme bg-surface text-xs font-semibold text-main disabled:opacity-50 cursor-pointer",children:[e.jsx(Y,{size:13,className:b?"animate-spin":""}),t?"刷新":"Refresh"]})]}),x&&e.jsx("p",{role:"alert",className:"mx-5 mt-3 px-3 py-2 rounded-xl border border-rose-200 bg-rose-50 text-rose-700 text-xs",children:x}),e.jsx("div",{className:"flex-1 overflow-y-auto p-5 space-y-4",children:h?e.jsxs(e.Fragment,{children:[e.jsxs("div",{className:"grid grid-cols-2 sm:grid-cols-4 gap-3",children:[[{label:t?"作答数":"Attempts",value:h.attemptCount},{label:t?"已交卷":"Submitted",value:h.submittedCount},{label:t?"待人工批改":"Pending review",value:h.needsManualReviewCount},{label:t?"平均分":"Average",value:h.averageScore===null?"—":`${h.averageScore}${h.averagePercent!==null?` (${h.averagePercent}%)`:""}`}].map(u=>e.jsxs("div",{className:"border border-theme rounded-xl px-3 py-2 bg-surface-secondary/40",children:[e.jsx("p",{className:"text-2xs text-muted",children:u.label}),e.jsx("p",{className:"text-sm font-bold text-main",children:u.value})]},u.label)),e.jsx(ae,{slot:"assessment.stats.card",slotProps:{assignmentId:l.id,stats:ke(h),lang:i},lang:i})]}),e.jsxs("table",{"data-testid":"stats-table",className:"w-full text-xs border border-theme rounded-xl overflow-hidden",children:[e.jsx("caption",{className:"sr-only",children:t?"逐题统计":"Per-item statistics"}),e.jsx("thead",{className:"bg-surface-secondary text-muted",children:e.jsxs("tr",{children:[e.jsx("th",{scope:"col",className:"text-left px-3 py-2 font-semibold",children:"#"}),e.jsx("th",{scope:"col",className:"text-left px-3 py-2 font-semibold",children:t?"题干":"Stem"}),e.jsx("th",{scope:"col",className:"text-right px-3 py-2 font-semibold",children:t?"作答":"Answered"}),e.jsx("th",{scope:"col",className:"text-right px-3 py-2 font-semibold",children:t?"正确率":"Correct rate"}),e.jsx("th",{scope:"col",className:"text-right px-3 py-2 font-semibold",children:t?"均分":"Avg score"})]})}),e.jsx("tbody",{children:h.items.map((u,w)=>e.jsxs("tr",{className:"border-t border-theme",children:[e.jsx("td",{className:"px-3 py-2 text-muted",children:w+1}),e.jsx("td",{className:"px-3 py-2 text-main max-w-[320px] truncate",title:u.stem,children:u.stem}),e.jsx("td",{className:"px-3 py-2 text-right text-main",children:u.answered}),e.jsx("td",{className:"px-3 py-2 text-right text-main",children:u.correctRate===null?"—":`${u.correctRate}%`}),e.jsx("td",{className:"px-3 py-2 text-right text-main",children:u.avgScore===null?"—":`${u.avgScore}/${u.maxScore}`})]},u.itemId))})]}),e.jsx("p",{className:"text-2xs text-muted",children:t?"待人工批改的题不计入正确率与均分;匿名问卷的导出不含身份信息。":"Items awaiting manual review are excluded from correct-rate and averages. Anonymous exports contain no identity."})]}):e.jsxs("p",{className:"text-xs text-muted flex items-center gap-2",children:[e.jsx(T,{size:13,className:"animate-spin"}),t?"正在统计…":"Computing…"]})})]})}const q="w-full px-2.5 py-1.5 border border-theme rounded-lg text-xs bg-surface text-main focus:outline-none focus:ring-1 focus:ring-indigo-500";function we({assessmentId:l,lang:i,onBack:c}){const t=i==="zh",f=re()?.classes??[],[b,y]=n.useState(null),[x,o]=n.useState([]),[v,u]=n.useState([]),[w,C]=n.useState(!0),[A,g]=n.useState(null),[j,S]=n.useState(null),[$,a]=n.useState(null),[N,P]=n.useState(null),[I,E]=n.useState(null),[L,m]=n.useState(null),p=b?.status==="draft",M=n.useMemo(()=>(K.subscribe(()=>{}),K.list().filter(s=>s.supports.includes(b?.kind??"quiz"))),[b?.kind]),F=n.useCallback(async()=>{C(!0),S(null);try{const s=await k.get(l);y(s),o(s.items.map(d=>({id:d.id,type:d.type,stem:d.stem,config:d.config??{},answerKey:d.answerKey??null,maxScore:d.maxScore})));const r=await k.getAssignments(l).catch(()=>({items:[]}));u(r.items)}catch(s){S(s instanceof U?s.message:String(s?.message??s))}finally{C(!1)}},[l]);n.useEffect(()=>{F()},[F]);const _=async s=>{g("settings"),S(null);try{const r=await k.update(l,{settings:s});y(r),a(t?"设置已保存":"Settings saved")}catch(r){S(r?.message??String(r))}finally{g(null)}},Q=async()=>{g("items"),S(null);try{const s=await k.replaceItems(l,x.map((r,d)=>({id:r.id,type:r.type,stem:r.stem,config:r.config,answerKey:r.answerKey,maxScore:r.maxScore,sortOrder:d})));y(s),o(s.items.map(r=>({id:r.id,type:r.type,stem:r.stem,config:r.config??{},answerKey:r.answerKey??null,maxScore:r.maxScore}))),a(t?"题目已保存":"Items saved")}catch(s){S(s?.message??String(s))}finally{g(null)}},H=s=>{const r=K.get(s);o(d=>[...d,{id:`new-${Date.now()}-${d.length}`,type:s,stem:"",config:r?.createDefaultConfig?.()??{},answerKey:r?.createDefaultAnswerKey?.()??null,maxScore:5}])},J=async()=>{try{const s=await k.listBank();m(s.items)}catch(s){S(s?.message??String(s))}};return w?e.jsxs("div",{className:"flex-1 flex items-center justify-center gap-2 py-16 text-sm text-muted",children:[e.jsx(T,{size:16,className:"animate-spin"}),t?"正在加载试卷…":"Loading assessment…"]}):b?N?e.jsx(ve,{assignment:N,lang:i,onBack:()=>{P(null),F()}}):I?e.jsx(Se,{assignment:I,lang:i,onBack:()=>E(null)}):e.jsxs("div",{className:"flex-1 flex flex-col min-h-0 bg-surface border border-theme rounded-2xl shadow-xl overflow-hidden",children:[e.jsxs("div",{className:"px-5 py-3 border-b border-theme bg-surface-secondary/70 flex flex-wrap items-center gap-3 shrink-0",children:[e.jsx("button",{type:"button",onClick:c,className:"p-1.5 rounded-lg border border-theme bg-surface hover:bg-surface-secondary text-muted hover:text-main cursor-pointer","aria-label":t?"返回测评列表":"Back to assessment list",children:e.jsx(X,{size:15})}),e.jsxs("div",{className:"min-w-0 flex-1",children:[e.jsx("h1",{className:"text-sm font-bold text-main truncate",children:b.title}),e.jsxs("p",{className:"text-2xs text-muted",children:[t?"类型":"Kind",":",b.kind," · ",t?"状态":"Status",":",b.status," · ",x.length," ",t?"题":"items"," · ",t?"满分":"total"," ",x.reduce((s,r)=>s+r.maxScore,0)]})]}),p&&e.jsxs("button",{type:"button",onClick:()=>void Q(),disabled:A!==null,className:"inline-flex items-center gap-1.5 px-3 py-1.5 rounded-xl bg-indigo-600 text-white text-xs font-bold hover:bg-indigo-700 disabled:opacity-50 cursor-pointer",children:[A==="items"?e.jsx(T,{size:13,className:"animate-spin"}):e.jsx(ce,{size:13}),t?"保存题目":"Save items"]})]}),(j||$)&&e.jsx("div",{role:j?"alert":"status",className:`mx-5 mt-3 px-3 py-2 rounded-xl border text-xs ${j?"border-rose-200 bg-rose-50 text-rose-700":"border-emerald-200 bg-emerald-50 text-emerald-700"}`,children:j??$}),e.jsxs("div",{className:"flex-1 overflow-y-auto p-5 space-y-5",children:[e.jsxs("section",{"aria-labelledby":"settings-heading",className:"border border-theme rounded-xl p-4",children:[e.jsxs("h2",{id:"settings-heading",className:"text-xs font-bold text-main flex items-center gap-1.5 mb-3",children:[e.jsx(xe,{size:13}),t?"测评设置":"Settings",!p&&e.jsxs("span",{className:"text-2xs font-normal text-muted",children:["(",t?"已发布,只读":"published, read-only",")"]})]}),e.jsxs("div",{className:"grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-3 gap-3",children:[e.jsxs("div",{children:[e.jsx("label",{htmlFor:"set-attempts",className:"block text-2xs font-semibold text-muted mb-1",children:t?"允许作答次数":"Max attempts"}),e.jsx("input",{id:"set-attempts",type:"number",min:1,max:10,value:b.settings.maxAttempts,disabled:!p||b.kind!=="quiz",onChange:s=>void _({maxAttempts:Number(s.target.value)||1}),className:q})]}),e.jsxs("div",{children:[e.jsx("label",{htmlFor:"set-policy",className:"block text-2xs font-semibold text-muted mb-1",children:t?"多次作答取哪次":"Score policy"}),e.jsxs("select",{id:"set-policy",value:b.settings.scorePolicy,disabled:!p||b.kind!=="quiz",onChange:s=>void _({scorePolicy:s.target.value}),className:q,children:[e.jsx("option",{value:"last",children:t?"最后一次":"Last"}),e.jsx("option",{value:"first",children:t?"第一次":"First"}),e.jsx("option",{value:"highest",children:t?"最高分":"Highest"}),e.jsx("option",{value:"average",children:t?"平均分":"Average"})]})]}),e.jsxs("div",{children:[e.jsx("label",{htmlFor:"set-visibility",className:"block text-2xs font-semibold text-muted mb-1",children:t?"结果可见性":"Result visibility"}),e.jsxs("select",{id:"set-visibility",value:b.settings.resultVisibility,disabled:!p||b.kind==="survey",onChange:s=>void _({resultVisibility:s.target.value}),className:q,children:[e.jsx("option",{value:"immediate",children:t?"交卷即显示":"Immediately"}),e.jsx("option",{value:"after_release",children:t?"教师发布成绩后":"After release"}),e.jsx("option",{value:"score_only",children:t?"只显示分数":"Score only"})]})]})]}),e.jsx("div",{className:"mt-3 flex flex-wrap gap-4",children:[{key:"singleAttempt",zh:"严格单次作答(禁止重考)",en:"Single attempt only",disabled:!p},{key:"shuffleItems",zh:"题目乱序",en:"Shuffle items",disabled:!p},{key:"shuffleOptions",zh:"选项乱序",en:"Shuffle options",disabled:!p},{key:"strictProctor",zh:"严格监考(切屏/失焦/防复制)",en:"Strict proctoring",disabled:!p},{key:"partialCredit",zh:"多选少选给部分分",en:"Partial credit for multi-choice",disabled:!p},{key:"caseSensitive",zh:"填空题区分大小写",en:"Case-sensitive blanks",disabled:!p},{key:"showItemAnalysis",zh:"允许查看逐题解析",en:"Show item analysis",disabled:!p||b.kind==="survey"},{key:"anonymous",zh:"匿名问卷",en:"Anonymous survey",disabled:!p||b.kind!=="survey"}].map(s=>e.jsxs("label",{className:"inline-flex items-center gap-1.5 text-xs text-main",children:[e.jsx("input",{type:"checkbox",checked:!!b.settings[s.key],disabled:s.disabled||!p,onChange:r=>void _({[s.key]:r.target.checked}),className:"accent-indigo-600"}),s[t?"zh":"en"]]},s.key))})]}),e.jsxs("section",{"aria-labelledby":"items-heading",className:"border border-theme rounded-xl p-4 space-y-3",children:[e.jsxs("div",{className:"flex flex-wrap items-center justify-between gap-2",children:[e.jsxs("h2",{id:"items-heading",className:"text-xs font-bold text-main",children:[t?"题目列表":"Items","(",x.length,")"]}),p&&e.jsxs("div",{className:"flex flex-wrap items-center gap-2",children:[M.map(s=>e.jsxs("button",{type:"button",onClick:()=>H(s.type),className:"inline-flex items-center gap-1 px-2 py-1 rounded-lg border border-theme text-2xs font-semibold text-main hover:bg-surface-secondary cursor-pointer",children:[e.jsx(V,{size:11}),s.label[t?"zh":"en"]]},s.type)),e.jsxs("button",{type:"button",onClick:()=>void J(),className:"inline-flex items-center gap-1 px-2 py-1 rounded-lg border border-theme text-2xs font-semibold text-main hover:bg-surface-secondary cursor-pointer",children:[e.jsx(ne,{size:11}),t?"从题库添加":"From bank"]})]})]}),x.length===0?e.jsx("p",{className:"text-xs text-muted py-6 text-center",children:t?"还没有题目。用上方按钮添加第一道题。":"No items yet — add your first question above."}):e.jsx("ol",{className:"space-y-3",children:x.map((s,r)=>{const d=K.get(s.type),B=d?.Editor;return e.jsxs("li",{"data-testid":`item-row-${r}`,className:"border border-theme rounded-xl p-3 space-y-2",children:[e.jsxs("div",{className:"flex flex-wrap items-center gap-2",children:[e.jsxs("span",{className:"text-2xs font-bold text-muted",children:["#",r+1]}),e.jsx("span",{className:"px-2 py-0.5 rounded-full bg-surface-secondary border border-theme text-2xs font-semibold text-main",children:d?.label[t?"zh":"en"]??s.type}),e.jsx("label",{htmlFor:`item-score-${s.id}`,className:"ml-auto text-2xs text-muted",children:t?"分值":"Score"}),e.jsx("input",{id:`item-score-${s.id}`,type:"number",min:1,value:s.maxScore,disabled:!p,onChange:D=>o(z=>z.map((R,O)=>O===r?{...R,maxScore:Number(D.target.value)||1}:R)),className:"w-16 px-2 py-1 border border-theme rounded-lg text-xs bg-surface text-main"}),p&&e.jsxs(e.Fragment,{children:[e.jsx("button",{type:"button",disabled:r===0,onClick:()=>o(D=>{const z=[...D];return[z[r-1],z[r]]=[z[r],z[r-1]],z}),"aria-label":t?`上移第 ${r+1} 题`:`Move item ${r+1} up`,className:"p-1 rounded-md text-muted hover:text-main hover:bg-surface-secondary disabled:opacity-30 cursor-pointer",children:e.jsx(me,{size:12})}),e.jsx("button",{type:"button",disabled:r===x.length-1,onClick:()=>o(D=>{const z=[...D];return[z[r+1],z[r]]=[z[r],z[r+1]],z}),"aria-label":t?`下移第 ${r+1} 题`:`Move item ${r+1} down`,className:"p-1 rounded-md text-muted hover:text-main hover:bg-surface-secondary disabled:opacity-30 cursor-pointer",children:e.jsx(ue,{size:12})}),e.jsx("button",{type:"button",onClick:()=>o(D=>D.filter((z,R)=>R!==r)),"aria-label":t?`删除第 ${r+1} 题`:`Delete item ${r+1}`,className:"p-1 rounded-md text-muted hover:text-rose-500 hover:bg-rose-500/10 cursor-pointer",children:e.jsx(ee,{size:12})})]})]}),e.jsxs("div",{children:[e.jsx("label",{htmlFor:`item-stem-${s.id}`,className:"block text-2xs font-semibold text-muted mb-1",children:t?"题干":"Stem"}),e.jsx("textarea",{id:`item-stem-${s.id}`,rows:2,value:s.stem,disabled:!p,onChange:D=>o(z=>z.map((R,O)=>O===r?{...R,stem:D.target.value}:R)),className:`${q} resize-none`})]}),B?e.jsx(B,{config:s.config,answerKey:s.answerKey,lang:i,disabled:!p,onChange:({config:D,answerKey:z})=>o(R=>R.map((O,le)=>le===r?{...O,config:D,answerKey:z}:O))}):e.jsx("p",{className:"text-2xs text-amber-700 dark:text-amber-300",children:t?`题型「${s.type}」没有注册出题编辑器(可能来自未启用/已卸载的插件)。`:`No editor registered for "${s.type}" (plugin missing or disabled).`})]},s.id)})})]}),e.jsx(Ce,{lang:i,detail:b,classes:f,assignments:v,disabled:!p,onPublished:async()=>{await F()},onGrading:P,onStats:E,onError:S})]}),L&&e.jsx(ze,{lang:i,items:L,allowedTypes:M.map(s=>s.type),onClose:()=>m(null),onPick:s=>{o(r=>[...r,{id:`new-${Date.now()}-${r.length}`,type:s.type,stem:s.stem,config:s.config??{},answerKey:s.answerKey??null,maxScore:5}]),m(null)}})]}):e.jsxs("div",{className:"flex-1 flex flex-col items-center justify-center gap-3 py-16",children:[e.jsx("p",{role:"alert",className:"text-sm text-rose-600",children:j??(t?"加载失败":"Failed to load")}),e.jsx("button",{type:"button",onClick:c,className:"text-xs font-semibold text-indigo-600 underline cursor-pointer",children:t?"返回列表":"Back to list"})]})}function Ce({lang:l,detail:i,classes:c,assignments:t,disabled:h,onPublished:f,onGrading:b,onStats:y,onError:x}){const o=l==="zh",[v,u]=n.useState(""),[w,C]=n.useState(""),[A,g]=n.useState(""),[j,S]=n.useState(!1),$=async()=>{if(!v){x(o?"请选择班级":"Pick a class first");return}S(!0);try{await k.publish(i.id,{classId:v,durationSec:w?Number(w)*60:null,closesAt:A?new Date(A).getTime():null}),await f()}catch(a){x(a?.message??String(a))}finally{S(!1)}};return e.jsxs("section",{"aria-labelledby":"publish-heading",className:"border border-theme rounded-xl p-4 space-y-3",children:[e.jsxs("h2",{id:"publish-heading",className:"text-xs font-bold text-main flex items-center gap-1.5",children:[e.jsx(be,{size:13}),o?"发布到班级":"Publish to a class"]}),h?e.jsx("p",{className:"text-2xs text-muted",children:o?"试卷已发布,内容只读。需要改题请新建一份试卷。":"Published papers are read-only. Create a new one to change items."}):e.jsxs("div",{className:"grid grid-cols-1 sm:grid-cols-[1.2fr_1fr_1fr_auto] gap-2 items-end",children:[e.jsxs("div",{children:[e.jsx("label",{htmlFor:"pub-class",className:"block text-2xs font-semibold text-muted mb-1",children:o?"班级":"Class"}),e.jsxs("select",{id:"pub-class",value:v,onChange:a=>u(a.target.value),className:q,children:[e.jsx("option",{value:"",children:o?"请选择班级":"Select a class"}),c.map(a=>e.jsx("option",{value:a.id,children:a.name},a.id))]})]}),e.jsxs("div",{children:[e.jsx("label",{htmlFor:"pub-duration",className:"block text-2xs font-semibold text-muted mb-1",children:o?"限时(分钟,可选)":"Time limit (min)"}),e.jsx("input",{id:"pub-duration",type:"number",min:1,value:w,onChange:a=>C(a.target.value),className:q})]}),e.jsxs("div",{children:[e.jsx("label",{htmlFor:"pub-closes",className:"block text-2xs font-semibold text-muted mb-1",children:o?"截止时间(可选)":"Closes at"}),e.jsx("input",{id:"pub-closes",type:"datetime-local",value:A,onChange:a=>g(a.target.value),className:q})]}),e.jsxs("button",{type:"button",onClick:()=>void $(),disabled:j,className:"inline-flex items-center gap-1.5 px-3 py-1.5 rounded-xl bg-emerald-600 text-white text-xs font-bold hover:bg-emerald-700 disabled:opacity-50 cursor-pointer",children:[j&&e.jsx(T,{size:13,className:"animate-spin"}),o?"发布":"Publish"]})]}),t.length>0&&e.jsx("ul",{"data-testid":"assignment-list",className:"space-y-2 pt-1",children:t.map(a=>e.jsxs("li",{"data-testid":`assignment-row-${a.id}`,className:"flex flex-wrap items-center gap-2 border border-theme rounded-xl px-3 py-2",children:[e.jsx(pe,{size:13,className:"text-muted"}),e.jsx("span",{className:"text-xs font-semibold text-main",children:a.className??a.classId}),e.jsx("span",{className:`px-2 py-0.5 rounded-full text-2xs font-semibold border ${a.status==="open"?"bg-emerald-50 text-emerald-700 border-emerald-200 dark:bg-emerald-950/40 dark:text-emerald-300 dark:border-emerald-800":"bg-surface-secondary text-muted border-theme"}`,children:a.status==="open"?o?"进行中":"Open":o?"已关闭":"Closed"}),e.jsxs("span",{className:"text-2xs text-muted",children:[o?"已交卷":"Submitted"," ",a.submittedCount,"/",a.attemptCount," ·"," ",o?"待批改":"Pending"," ",a.pendingReviewCount]}),a.scoreReleased&&e.jsx("span",{className:"text-2xs text-indigo-600",children:o?"成绩已发布":"Scores released"}),e.jsxs("span",{className:"ml-auto flex items-center gap-1.5",children:[e.jsxs("a",{href:k.exportCsvUrl(a.id),className:"inline-flex items-center gap-1 px-2 py-1 rounded-lg border border-theme text-2xs font-semibold text-main hover:bg-surface-secondary",children:[e.jsx(he,{size:11}),"CSV"]}),e.jsx("button",{type:"button",onClick:()=>y(a),className:"px-2 py-1 rounded-lg border border-theme text-2xs font-semibold text-main hover:bg-surface-secondary cursor-pointer",children:o?"统计":"Stats"}),e.jsx("button",{type:"button",onClick:()=>b(a),className:"px-2 py-1 rounded-lg border border-theme text-2xs font-semibold text-main hover:bg-surface-secondary cursor-pointer",children:o?"批改":"Grade"}),a.scoreReleased?e.jsx("button",{type:"button",onClick:async()=>{try{await k.syncGradebook(a.id),await f()}catch(N){x(N?.message??String(N))}},title:o?"将本场成绩同步至系统成绩单":"Sync scores to gradebook",className:"px-2 py-1 rounded-lg border border-indigo-200 text-indigo-700 bg-indigo-50/50 hover:bg-indigo-100 dark:border-indigo-800 dark:text-indigo-300 dark:bg-indigo-950/40 text-2xs font-semibold cursor-pointer",children:o?"同步成绩单":"Sync Grades"}):e.jsx("button",{type:"button",onClick:async()=>{try{await k.releaseScores(a.id),await f()}catch(N){x(N?.message??String(N))}},title:o?"发布成绩并自动同步至成绩单":"Release scores and sync to gradebook",className:"px-2 py-1 rounded-lg border border-theme text-2xs font-semibold text-main hover:bg-surface-secondary cursor-pointer",children:o?"发布成绩":"Release"}),a.status==="open"&&e.jsx("button",{type:"button",onClick:async()=>{try{await k.closeAssignment(a.id),await f()}catch(N){x(N?.message??String(N))}},className:"px-2 py-1 rounded-lg border border-amber-300 text-2xs font-semibold text-amber-700 hover:bg-amber-50 cursor-pointer",children:o?"关闭":"Close"})]})]},a.id))})]})}function ze({lang:l,items:i,allowedTypes:c,onClose:t,onPick:h}){const f=l==="zh",b=i.filter(y=>c.includes(y.type));return e.jsx("div",{className:"fixed inset-0 z-50 flex items-center justify-center bg-black/40 p-4",children:e.jsxs("div",{role:"dialog","aria-modal":"true","aria-labelledby":"bank-picker-title",className:"w-full max-w-lg max-h-[80vh] flex flex-col bg-surface border border-theme rounded-2xl shadow-2xl",children:[e.jsxs("div",{className:"px-4 py-3 border-b border-theme flex items-center justify-between",children:[e.jsx("h2",{id:"bank-picker-title",className:"text-sm font-bold text-main",children:f?"从题库添加":"Add from question bank"}),e.jsx("button",{type:"button",onClick:t,className:"text-xs font-semibold text-muted hover:text-main cursor-pointer",children:f?"关闭":"Close"})]}),e.jsx("div",{className:"flex-1 overflow-y-auto p-4 space-y-2",children:b.length===0?e.jsx("p",{className:"text-xs text-muted py-6 text-center",children:f?"题库里还没有可用于本测评类型的题目。":"No bank items usable for this assessment kind."}):b.map(y=>e.jsxs("button",{type:"button",onClick:()=>h(y),className:"w-full text-left border border-theme rounded-xl px-3 py-2 hover:border-indigo-300 hover:bg-surface-secondary cursor-pointer",children:[e.jsx("p",{className:"text-xs font-semibold text-main line-clamp-2",children:y.stem}),e.jsx("p",{className:"text-2xs text-muted mt-0.5",children:y.type})]},y.id))})]})})}const W="w-full px-2.5 py-1.5 border border-theme rounded-lg text-xs bg-surface text-main focus:outline-none focus:ring-1 focus:ring-indigo-500";function Ae({lang:l}){const i=l==="zh",[c,t]=n.useState(null),[h,f]=n.useState(null),[b,y]=n.useState(!1),[x,o]=n.useState("single_choice"),[v,u]=n.useState(""),[w,C]=n.useState({}),[A,g]=n.useState(null),[j,S]=n.useState("private"),[$,a]=n.useState(!1),N=K.list(),I=K.get(x)?.Editor,E=n.useCallback(async()=>{y(!0),f(null);try{const m=await k.listBank();t(m.items)}catch(m){f(m instanceof U?m.message:String(m?.message??m))}finally{y(!1)}},[]);n.useEffect(()=>{E()},[E]),n.useEffect(()=>{const m=K.get(x);C(m?.createDefaultConfig?.()??{}),g(m?.createDefaultAnswerKey?.()??null)},[x]);const L=async m=>{if(m.preventDefault(),!v.trim()){f(i?"题干不能为空":"Stem is required");return}a(!0),f(null);try{await k.createBankItem({type:x,stem:v.trim(),config:w,answerKey:A,visibility:j}),u(""),await E()}catch(p){f(p?.message??String(p))}finally{a(!1)}};return e.jsxs("div",{className:"flex-1 overflow-y-auto p-6 space-y-5",children:[e.jsxs("section",{"aria-labelledby":"bank-create-heading",className:"border border-theme rounded-xl p-4 max-w-3xl mx-auto",children:[e.jsxs("h2",{id:"bank-create-heading",className:"text-xs font-bold text-main flex items-center gap-1.5 mb-3",children:[e.jsx(V,{size:13}),i?"新增题库题目":"New bank item"]}),e.jsxs("form",{onSubmit:L,className:"space-y-3",children:[e.jsxs("div",{children:[e.jsx("label",{htmlFor:"bank-type",className:"block text-2xs font-semibold text-muted mb-1",children:i?"题型":"Type"}),e.jsx("select",{id:"bank-type",value:x,onChange:m=>o(m.target.value),className:W,children:N.map(m=>e.jsx("option",{value:m.type,children:m.label[i?"zh":"en"]},m.type))})]}),e.jsxs("div",{children:[e.jsx("label",{htmlFor:"bank-stem",className:"block text-2xs font-semibold text-muted mb-1",children:i?"题干":"Stem"}),e.jsx("textarea",{id:"bank-stem",rows:2,value:v,onChange:m=>u(m.target.value),className:`${W} resize-none`})]}),I&&e.jsx(I,{config:w,answerKey:A,lang:l,onChange:({config:m,answerKey:p})=>{C(m),g(p)}}),e.jsxs("div",{children:[e.jsx("label",{htmlFor:"bank-visibility",className:"block text-2xs font-semibold text-muted mb-1",children:i?"可见范围":"Visibility"}),e.jsxs("select",{id:"bank-visibility",value:j,onChange:m=>S(m.target.value==="school"?"school":"private"),className:W,children:[e.jsx("option",{value:"private",children:i?"仅自己":"Private"}),e.jsx("option",{value:"school",children:i?"同校教师可见":"Teachers on this instance"})]})]}),h&&e.jsx("p",{role:"alert",className:"text-xs text-rose-600",children:h}),e.jsxs("button",{type:"submit",disabled:$,className:"inline-flex items-center gap-1.5 px-3 py-1.5 rounded-xl bg-indigo-600 text-white text-xs font-bold hover:bg-indigo-700 disabled:opacity-50 cursor-pointer",children:[$&&e.jsx(T,{size:13,className:"animate-spin"}),i?"存入题库":"Save to bank"]})]})]}),e.jsxs("section",{"aria-labelledby":"bank-list-heading",className:"max-w-3xl mx-auto",children:[e.jsxs("h2",{id:"bank-list-heading",className:"text-xs font-bold text-main mb-2 flex items-center gap-1.5",children:[e.jsx(ne,{size:13}),i?"题库题目":"Bank items","(",c?.length??0,")"]}),c===null&&b?e.jsxs("p",{className:"text-xs text-muted flex items-center gap-2",children:[e.jsx(T,{size:13,className:"animate-spin"}),i?"加载中…":"Loading…"]}):(c?.length??0)===0?e.jsx("p",{className:"text-xs text-muted",children:i?"题库还是空的。":"The bank is empty."}):e.jsx("ul",{"data-testid":"bank-list",className:"space-y-2",children:c.map(m=>e.jsxs("li",{"data-testid":`bank-row-${m.id}`,className:"flex items-start gap-2 border border-theme rounded-xl px-3 py-2",children:[e.jsxs("div",{className:"min-w-0 flex-1",children:[e.jsx("p",{className:"text-xs font-semibold text-main line-clamp-2",children:m.stem}),e.jsx("p",{className:"text-2xs text-muted mt-0.5",children:K.get(m.type)?.label[i?"zh":"en"]??m.type})]}),e.jsx("button",{type:"button",onClick:async()=>{if(window.confirm(i?"从题库删除该题?":"Delete this bank item?"))try{await k.removeBankItem(m.id),await E()}catch(p){f(p?.message??String(p))}},"aria-label":i?`删除题库题目:${m.stem.slice(0,20)}`:"Delete bank item",className:"p-1 rounded-md text-muted hover:text-rose-500 hover:bg-rose-500/10 cursor-pointer shrink-0",children:e.jsx(ee,{size:13})})]},m.id))})]})]})}const G={quiz:{zh:"测试",en:"Quiz",cls:"bg-indigo-50 text-indigo-700 border-indigo-200 dark:bg-indigo-950/40 dark:text-indigo-300 dark:border-indigo-800"},exam:{zh:"考试",en:"Exam",cls:"bg-rose-50 text-rose-700 border-rose-200 dark:bg-rose-950/40 dark:text-rose-300 dark:border-rose-800"},survey:{zh:"问卷",en:"Survey",cls:"bg-emerald-50 text-emerald-700 border-emerald-200 dark:bg-emerald-950/40 dark:text-emerald-300 dark:border-emerald-800"}},te={draft:{zh:"草稿",en:"Draft",cls:"bg-surface-secondary text-muted border-theme"},published:{zh:"已发布",en:"Published",cls:"bg-emerald-50 text-emerald-700 border-emerald-200 dark:bg-emerald-950/40 dark:text-emerald-300 dark:border-emerald-800"},closed:{zh:"已关闭",en:"Closed",cls:"bg-amber-50 text-amber-700 border-amber-200 dark:bg-amber-950/40 dark:text-amber-300 dark:border-amber-800"},archived:{zh:"已归档",en:"Archived",cls:"bg-surface-secondary text-muted border-theme"}};function Re(){const i=re()?.lang==="en"?"en":"zh",c=i==="zh",[t,h]=n.useState("papers"),[f,b]=n.useState(null),[y,x]=n.useState(!1),[o,v]=n.useState(null),[u,w]=n.useState(null),[C,A]=n.useState("all"),[g,j]=n.useState(!1),S=n.useCallback(async()=>{x(!0),v(null);try{const a=await k.list(C==="all"?{}:{kind:C});b(a.items)}catch(a){v(a instanceof U?a.message:String(a?.message??a))}finally{x(!1)}},[C]);n.useEffect(()=>{t==="papers"&&u===null&&S()},[t,u,S]);const $=n.useMemo(()=>[...f??[]].sort((N,P)=>P.updatedAt-N.updatedAt),[f]);return u?e.jsx(we,{assessmentId:u,lang:i,onBack:()=>{w(null),S()}}):e.jsxs("div",{className:"flex-1 flex flex-col min-h-0 bg-surface border border-theme rounded-2xl shadow-xl overflow-hidden",children:[e.jsxs("div",{className:"px-6 py-4 border-b border-theme bg-surface-secondary/70 flex flex-col lg:flex-row lg:items-center justify-between gap-3 shrink-0",children:[e.jsxs("div",{className:"min-w-0",children:[e.jsxs("h1",{className:"text-base font-bold text-main flex items-center gap-2",children:[e.jsx(Z,{size:18,className:"text-indigo-500"}),c?"测评中心":"Assessment Center"]}),e.jsx("p",{className:"text-xs text-muted mt-0.5",children:c?"组卷、发布到班级、批改与统计。测试 / 考试 / 问卷共用同一套题目与判分模型。":"Build papers, publish to classes, grade and analyse. Quizzes, exams and surveys share one item model."})]}),e.jsxs("div",{className:"flex items-center gap-2 shrink-0",children:[e.jsx("div",{className:"inline-flex p-1 bg-surface-secondary border border-theme rounded-xl text-xs font-semibold",children:[{id:"papers",label:c?"试卷":"Papers",icon:Z},{id:"bank",label:c?"题库":"Question bank",icon:ge}].map(a=>{const N=a.icon,P=t===a.id;return e.jsxs("button",{type:"button",onClick:()=>h(a.id),"aria-pressed":P,className:`inline-flex items-center gap-1.5 px-3 py-1.5 rounded-lg transition-colors cursor-pointer ${P?"bg-surface text-main shadow-3xs font-bold":"text-muted hover:text-main"}`,children:[e.jsx(N,{size:13}),a.label]},a.id)})}),e.jsxs("button",{type:"button",onClick:()=>void S(),disabled:y||t!=="papers",className:"inline-flex items-center gap-1.5 px-3 py-1.5 rounded-xl border border-theme bg-surface hover:bg-surface-secondary text-xs font-semibold text-main disabled:opacity-50 cursor-pointer",children:[e.jsx(Y,{size:13,className:y?"animate-spin":""}),c?"刷新":"Refresh"]}),t==="papers"&&e.jsxs("button",{type:"button",onClick:()=>j(!0),className:"inline-flex items-center gap-1.5 px-3 py-1.5 rounded-xl bg-indigo-600 text-white text-xs font-bold hover:bg-indigo-700 cursor-pointer",children:[e.jsx(V,{size:13}),c?"新建测评":"New assessment"]})]})]}),o&&e.jsxs("div",{role:"alert",className:"mx-6 mt-4 px-3 py-2 rounded-xl border border-rose-200 bg-rose-50 text-rose-700 text-xs flex items-center gap-2",children:[e.jsx(fe,{size:14}),o]}),t==="bank"?e.jsx(Ae,{lang:i}):e.jsxs(e.Fragment,{children:[e.jsxs("div",{className:"px-6 py-3 border-b border-theme flex flex-wrap items-center gap-2 shrink-0",children:[e.jsx("span",{className:"text-xs font-semibold text-muted",children:c?"类型筛选":"Filter by kind"}),["all","quiz","exam","survey"].map(a=>e.jsx("button",{type:"button",onClick:()=>A(a),"aria-pressed":C===a,className:`px-2.5 py-1 rounded-full text-xs font-semibold border cursor-pointer ${C===a?"bg-indigo-600 text-white border-indigo-600":"bg-surface text-muted border-theme hover:text-main"}`,children:a==="all"?c?"全部":"All":G[a]?.[c?"zh":"en"]??a},a))]}),e.jsx("div",{className:"flex-1 overflow-y-auto p-6",children:f===null&&y?e.jsxs("div",{className:"flex items-center justify-center gap-2 py-16 text-sm text-muted",children:[e.jsx(T,{size:16,className:"animate-spin"}),c?"正在加载试卷…":"Loading papers…"]}):$.length===0?e.jsxs("div",{className:"flex flex-col items-center justify-center gap-3 py-16 text-center",children:[e.jsx(Z,{size:40,className:"text-muted opacity-40"}),e.jsx("p",{className:"text-sm font-semibold text-main",children:c?"还没有任何测评":"No assessments yet"}),e.jsx("p",{className:"text-xs text-muted max-w-sm",children:c?"新建一份测试、考试或问卷,组卷后发布到班级,学生就能在「我的测评」里作答。":"Create a quiz, exam or survey, add questions, then publish it to a class."}),e.jsxs("button",{type:"button",onClick:()=>j(!0),className:"inline-flex items-center gap-1.5 px-3 py-1.5 rounded-xl bg-indigo-600 text-white text-xs font-bold hover:bg-indigo-700 cursor-pointer",children:[e.jsx(V,{size:13}),c?"新建测评":"New assessment"]})]}):e.jsx("ul",{"data-testid":"assessment-list",className:"grid grid-cols-1 xl:grid-cols-2 gap-3 max-w-[1400px] mx-auto",children:$.map(a=>{const N=G[a.kind]??G.quiz,P=te[a.status]??te.draft;return e.jsxs("li",{"data-testid":`assessment-row-${a.id}`,className:"bg-surface border border-theme rounded-xl p-4 flex flex-col gap-2.5 hover:border-indigo-300 transition-colors",children:[e.jsxs("div",{className:"flex items-start justify-between gap-2",children:[e.jsxs("div",{className:"min-w-0",children:[e.jsx("p",{className:"font-bold text-sm text-main truncate",title:a.title,children:a.title}),a.description&&e.jsx("p",{className:"text-xs text-muted mt-0.5 line-clamp-2",children:a.description})]}),e.jsxs("div",{className:"flex flex-col items-end gap-1 shrink-0",children:[e.jsx("span",{className:`px-2 py-0.5 rounded-full text-2xs font-bold border ${N.cls}`,children:N[c?"zh":"en"]}),e.jsx("span",{className:`px-2 py-0.5 rounded-full text-2xs font-semibold border ${P.cls}`,children:P[c?"zh":"en"]})]})]}),e.jsxs("div",{className:"flex items-center gap-3 text-2xs text-muted flex-wrap",children:[e.jsxs("span",{children:[a.itemCount," ",c?"题":"items"]}),e.jsxs("span",{children:[c?"满分":"Total"," ",a.totalScore]}),a.settings.anonymous&&e.jsxs("span",{children:["🔒 ",c?"匿名":"Anonymous"]}),a.settings.maxAttempts>1&&e.jsx("span",{children:c?`可作答 ${a.settings.maxAttempts} 次`:`${a.settings.maxAttempts} attempts`}),e.jsxs("span",{className:"ml-auto flex items-center gap-2",children:[a.status==="draft"&&e.jsx("button",{type:"button",onClick:async()=>{if(window.confirm(c?`删除「${a.title}」?`:`Delete "${a.title}"?`))try{await k.remove(a.id),S()}catch(I){v(I?.message??String(I))}},"aria-label":c?`删除 ${a.title}`:`Delete ${a.title}`,className:"p-1 rounded-md text-muted hover:text-rose-500 hover:bg-rose-500/10 cursor-pointer",children:e.jsx(ee,{size:13})}),e.jsxs("button",{type:"button",onClick:()=>w(a.id),className:"inline-flex items-center gap-1 px-2.5 py-1 rounded-lg border border-theme text-2xs font-semibold text-main hover:bg-surface-secondary cursor-pointer",children:[a.status==="draft"?c?"继续编辑":"Edit":c?"查看与批改":"Open",e.jsx(ye,{size:12})]})]})]}),e.jsx(ae,{slot:"assessment.list.card",slotProps:{assessment:Ne(a),viewer:"teacher",lang:i},lang:i})]},a.id)})})})]}),g&&e.jsx(Ie,{lang:i,onClose:()=>j(!1),onCreated:a=>{j(!1),w(a)}})]})}function Ie({lang:l,onClose:i,onCreated:c}){const t=l==="zh",[h,f]=n.useState("quiz"),[b,y]=n.useState(""),[x,o]=n.useState(""),[v,u]=n.useState(!1),[w,C]=n.useState(null),A=async g=>{if(g.preventDefault(),!b.trim()){C(t?"请填写标题":"Title is required");return}u(!0),C(null);try{const j=await k.create({kind:h,title:b.trim(),description:x.trim()||null});c(j.id)}catch(j){C(j?.message??String(j))}finally{u(!1)}};return e.jsx("div",{className:"fixed inset-0 z-50 flex items-center justify-center bg-black/40 p-4",children:e.jsxs("div",{role:"dialog","aria-modal":"true","aria-labelledby":"create-assessment-title",className:"w-full max-w-md bg-surface border border-theme rounded-2xl shadow-2xl p-5 space-y-4",children:[e.jsx("h2",{id:"create-assessment-title",className:"text-sm font-bold text-main",children:t?"新建测评":"New assessment"}),e.jsxs("form",{onSubmit:A,className:"space-y-3",children:[e.jsxs("fieldset",{children:[e.jsx("legend",{className:"text-xs font-semibold text-muted mb-1",children:t?"类型":"Kind"}),e.jsx("div",{className:"grid grid-cols-3 gap-2",children:["quiz","exam","survey"].map(g=>e.jsxs("label",{className:`flex flex-col items-center gap-1 px-2 py-2 rounded-xl border text-xs cursor-pointer ${h===g?"border-indigo-500 bg-indigo-50 text-indigo-700 dark:bg-indigo-950/40 dark:text-indigo-300":"border-theme text-muted hover:text-main"}`,children:[e.jsx("input",{type:"radio",name:"assessment-kind",value:g,checked:h===g,onChange:()=>f(g),className:"sr-only"}),e.jsx("span",{className:"font-bold",children:G[g][t?"zh":"en"]}),e.jsx("span",{className:"text-2xs text-center leading-snug",children:g==="quiz"?t?"计分,可多次作答":"Scored, retakes allowed":g==="exam"?t?"一次作答,发布成绩后可见":"Single attempt, released later":t?"不计分,可匿名":"Not scored, can be anonymous"})]},g))})]}),e.jsxs("div",{children:[e.jsx("label",{htmlFor:"assessment-title",className:"block text-xs font-semibold text-muted mb-1",children:t?"标题":"Title"}),e.jsx("input",{id:"assessment-title",value:b,onChange:g=>y(g.target.value),className:"w-full px-2.5 py-1.5 border border-theme rounded-lg text-xs bg-surface text-main focus:outline-none focus:ring-1 focus:ring-indigo-500",placeholder:t?"例如:第三单元小测":"e.g. Unit 3 quiz"})]}),e.jsxs("div",{children:[e.jsx("label",{htmlFor:"assessment-description",className:"block text-xs font-semibold text-muted mb-1",children:t?"说明(可选)":"Description (optional)"}),e.jsx("textarea",{id:"assessment-description",value:x,onChange:g=>o(g.target.value),rows:2,className:"w-full px-2.5 py-1.5 border border-theme rounded-lg text-xs bg-surface text-main focus:outline-none focus:ring-1 focus:ring-indigo-500 resize-none"})]}),w&&e.jsx("p",{role:"alert",className:"text-xs text-rose-600",children:w}),e.jsxs("div",{className:"flex justify-end gap-2 pt-1",children:[e.jsx("button",{type:"button",onClick:i,className:"px-3 py-1.5 rounded-xl border border-theme text-xs font-semibold text-main hover:bg-surface-secondary cursor-pointer",children:t?"取消":"Cancel"}),e.jsxs("button",{type:"submit",disabled:v,className:"inline-flex items-center gap-1.5 px-3 py-1.5 rounded-xl bg-indigo-600 text-white text-xs font-bold hover:bg-indigo-700 disabled:opacity-50 cursor-pointer",children:[v&&e.jsx(T,{size:13,className:"animate-spin"}),t?"创建并开始组卷":"Create & build"]})]})]})]})})}export{Re as AssessmentCenter};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{r as l,j as e}from"./vendor-react-Kv_JATwr.js";import{Q as De,E as M,c as Ae}from"./index-2676X8YB.js";import{am as pe,ak as Le,bu as U,bt as Pe,ac as Te,X as I,V as Me,a9 as ge,U as fe,z as Ee,ad as Re,c as be,ae as Oe,C as Fe,Z as Ue}from"./vendor-icons-CXrZ3R2V.js";import"./vendor-utils-aM7aYvPP.js";import"./vendor-pdf-C1Vh2Aqq.js";import"./vendor-motion-BFAPOs-b.js";import"./vendor-charts-SrpBsvM3.js";function Be({computerLabs:h,onRefresh:y,lang:a,classes:b}){const[B,w]=l.useState(!1),[E,J]=l.useState(null),[q,V]=l.useState(""),[N,H]=l.useState(5),[S,Q]=l.useState(6),[R,p]=l.useState(""),[ee,te]=l.useState(!1),[se,ae]=l.useState(h.length>0?h[0].id:null),i=h.find(t=>t.id===(se||h[0]?.id)),[je,ne]=l.useState([]),[d,X]=l.useState(()=>{const t=h[0];if(!t)return null;const s=(b||[]).find(o=>o.lab_id===t.id);return s?s.id:null}),[D,re]=l.useState([]),[g,_]=l.useState([]),[ie,oe]=l.useState(!1),[Z,le]=l.useState(!1),[de,G]=l.useState(""),[K,k]=l.useState(!1),[f,v]=l.useState(null),[ye,C]=l.useState(null),[x,L]=l.useState(null),[W,P]=l.useState(null);l.useEffect(()=>{b&&b.length>0?ne(b):fetch("/api/classes?pageSize=all").then(t=>t.ok?t.json():{data:[]}).then(t=>{const s=De(t);(s.length>0||Array.isArray(t?.data))&&ne(s)}).catch(t=>console.error("Failed to fetch classes in ComputerLabManager:",t))},[b]);const $=b&&b.length>0?b:je,ce=l.useMemo(()=>i?$.filter(t=>t.lab_id===i.id):[],[$,i]);l.useEffect(()=>{if(!i)return;if(d){$.find(s=>s.id===d);return}const t=$.find(s=>s.lab_id===i.id);t&&X(t.id)},[i?.id,$]);const xe=l.useCallback(async(t,s)=>{oe(!0),p(""),v(null),C(null);try{const[o,n]=await Promise.all([fetch(`/api/classes/${t}/seats`),fetch(`/api/classes/${t}/students`)]);let r=[];if(n.ok&&(r=await n.json(),re(r)),o.ok){const u=(await o.json()).seats||[],A=new Map(r.map(j=>[j.id,j])),O=u.map(j=>{const F=A.get(j.student_id);return{...j,student_name:j.student_name||F?.name||"",student_number:j.student_number||F?.student_number||""}});_(O)}k(!1)}catch(o){console.error("Error loading seating:",o),p(o.message||"Failed to load class seating data")}finally{oe(!1)}},[]);l.useEffect(()=>{d?xe(d,i?.id):(re([]),_([]),k(!1),v(null),C(null))},[d,xe]);const Y=l.useMemo(()=>{const t=new Map;for(const s of g)t.set(`${s.row_idx}_${s.col_idx}`,s);return t},[g]),ue=l.useMemo(()=>new Set(g.map(t=>t.student_id)),[g]),z=l.useMemo(()=>D.filter(t=>!ue.has(t.id)).sort((t,s)=>String(t.student_number??t.name??"").localeCompare(String(s.student_number??s.name??""),"zh-Hans-CN",{numeric:!0})),[D,ue]),me=(t,s,o)=>{const n=g.find(u=>u.row_idx===s.row&&u.col_idx===s.col),r=!!o&&!!n&&n.student_id!==t.id;let c=g.filter(u=>!(u.row_idx===s.row&&u.col_idx===s.col)&&u.student_id!==t.id);r&&o&&(c=c.filter(u=>!(u.row_idx===o.row&&u.col_idx===o.col)),c=[...c,{...n,row_idx:o.row,col_idx:o.col}]),c=[...c,{student_id:t.id,row_idx:s.row,col_idx:s.col,student_name:t.name,student_number:t.student_number}],_(c),k(!0)},ve=(t,s)=>{if(!d||!i)return;const o=Y.get(`${t}_${s}`);if(f){me(f,{row:t,col:s},ye),v(null),C(null);return}if(o){const n=D.find(r=>r.id===o.student_id)||{id:o.student_id,name:o.student_name||"Student",student_number:o.student_number||""};v(n),C({row:t,col:s})}},we=(t,s,o)=>{t.stopPropagation(),_(n=>n.filter(r=>!(r.row_idx===s&&r.col_idx===o))),k(!0)},Ne=()=>{if(!i||z.length===0)return;const t=[];for(let n=0;n<i.rows;n++)for(let r=0;r<i.cols;r++)Y.has(`${n}_${r}`)||t.push([n,r]);if(t.length===0)return;const o=z.slice(0,t.length).map((n,r)=>{const[c,u]=t[r];return{student_id:n.id,row_idx:c,col_idx:u,student_name:n.name,student_number:n.student_number}});_(n=>[...n,...o]),k(!0),v(null)},Se=()=>{g.length!==0&&confirm(a==="zh"?"确定要清空当前班级在该机房的所有机位排座吗?":"Are you sure you want to clear all seat assignments for this class in this lab?")&&(_([]),k(!0),v(null))},ze=async()=>{if(!(!d||!i))try{le(!0),p(""),G("");const t=await fetch(`/api/classes/${d}/seats`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({lab_id:i.id,seats:g.map(s=>({student_id:s.student_id,row_idx:s.row_idx,col_idx:s.col_idx}))})});if(t.ok)k(!1),G(a==="zh"?"统一机房排座已成功持久化至系统 student_seats 数据表!":"Seating assignments saved to system student_seats table successfully!"),y(),setTimeout(()=>G(""),4e3);else{const s=await t.json();p(s.error||"Failed to save seats")}}catch(t){p(t.message||"Network error saving seats")}finally{le(!1)}},_e=()=>{V(""),H(5),Q(6),p(""),J(null),w(!0)},ke=t=>{V(t.room_number),H(t.rows),Q(t.cols),p(""),J(t.id),w(!0)},Ce=async t=>{if(t.preventDefault(),!q.trim()){p(a==="zh"?"请输入机房编号/名称":"Please enter room name or number");return}if(N<=0||N>15){p(a==="zh"?"行数应在 1 至 15 之间":"Rows should be between 1 and 15");return}if(S<=0||S>15){p(a==="zh"?"列数应在 1 至 15 之间":"Columns should be between 1 and 15");return}try{te(!0),p("");const s=E?`/api/labs/${E}`:"/api/labs",n=await fetch(s,{method:E?"PUT":"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({room_number:q.trim(),rows:N,cols:S})});if(n.ok)y(),w(!1),J(null);else{const r=await n.json();p(r.error||"Server error saving lab")}}catch(s){p(s.message||"Network error")}finally{te(!1)}},$e=async t=>{if(confirm(a==="zh"?"确定要删除该机房吗?删除后对应的座位分配也会被重置。":"Are you sure you want to delete this lab? Seat mappings will be cleared."))try{(await fetch(`/api/labs/${t}`,{method:"DELETE"})).ok&&(y(),se===t&&ae(null))}catch(s){console.error(s)}};return e.jsxs("div",{className:"flex-1 flex flex-col h-full overflow-hidden",id:"lab_manager_root",children:[e.jsxs("div",{className:"flex flex-col md:flex-row md:items-center justify-between pb-4 border-b border-gray-200 gap-4 shrink-0",children:[e.jsxs("div",{children:[e.jsxs("h2",{className:"text-xl font-bold text-gray-800 flex items-center gap-2",children:[e.jsx(pe,{className:"text-indigo-600",size:24}),a==="zh"?"统一机房座位管理":"Unified Computer Lab Seating"]}),e.jsx("p",{className:"text-xs text-gray-500 mt-1",children:a==="zh"?"维护物理机房规格及其行列排列规则,并直接根据班级信息读取并编排系统中已存的统一机房座位数据(student_seats 表)。":"Maintain physical lab layouts and manage unified student seat allocations directly synced with existing system data tables."})]}),e.jsxs("div",{className:"flex items-center gap-2 flex-wrap",children:[e.jsxs("button",{onClick:_e,className:"bg-indigo-600 hover:bg-indigo-700 text-white px-4 py-2 rounded-xl text-xs font-semibold flex items-center gap-1.5 shadow-sm transition-all focus:ring-2 focus:ring-indigo-500 cursor-pointer",children:[e.jsx(Le,{size:14}),a==="zh"?"新增机房规则":"New Lab Config"]}),e.jsx(M,{slot:"computer_lab.toolbar.action",slotProps:{activeLab:i,computerLabs:h,onRefresh:y,lang:a}})]})]}),e.jsxs("div",{className:"flex-1 min-h-0 grid grid-cols-1 lg:grid-cols-12 gap-6 pt-5 overflow-hidden",children:[e.jsxs("div",{className:"lg:col-span-4 flex flex-col min-h-0 bg-white border border-gray-200 rounded-2xl shadow-xs overflow-hidden",children:[e.jsxs("div",{className:"p-4 border-b border-gray-100 bg-gray-50 flex justify-between items-center shrink-0",children:[e.jsx("span",{className:"text-xs font-bold text-gray-700 uppercase tracking-wider block",children:a==="zh"?"机房列表":"Computer Labs"}),e.jsx("span",{className:"bg-gray-200 text-gray-700 font-bold px-2 py-0.5 rounded-full text-xs",children:h.length})]}),e.jsx("div",{className:"flex-1 overflow-y-auto p-4 space-y-3",children:h.length===0?e.jsxs("div",{className:"flex flex-col items-center justify-center p-8 text-center text-gray-400 h-full",children:[e.jsx(pe,{size:40,className:"mb-2 opacity-30"}),e.jsx("p",{className:"text-xs font-medium",children:a==="zh"?"暂无机房编号规则":"No Computer Labs configured yet."}),e.jsx("p",{className:"text-xs text-gray-500 mt-1",children:a==="zh"?"点击右上角按钮添加首个机房规格。":"Click the button above to add your first computer room."})]}):h.map(t=>{const s=i?.id===t.id,o=$.filter(n=>n.lab_id===t.id);return e.jsxs("div",{onClick:()=>{ae(t.id),w(!1)},className:`p-3 rounded-xl border transition-all cursor-pointer flex flex-col gap-2 group ${s?"border-indigo-300 bg-indigo-50/60 shadow-xs ring-1 ring-indigo-200":"border-gray-150 hover:border-gray-300 hover:bg-gray-50"}`,children:[e.jsxs("div",{className:"flex items-center justify-between",children:[e.jsxs("div",{className:"flex items-center gap-3",children:[e.jsx("div",{className:`p-2 rounded-lg ${s?"bg-indigo-600 text-white":"bg-gray-100 text-gray-500 group-hover:bg-gray-200"}`,children:e.jsx(U,{size:16})}),e.jsxs("div",{children:[e.jsx("div",{className:"text-sm font-bold text-gray-800 flex items-center gap-1.5",children:t.room_number}),e.jsxs("div",{className:"text-xs text-gray-400 font-mono mt-0.5",children:[t.rows," ",a==="zh"?"行":"Rows"," × ",t.cols," ",a==="zh"?"列":"Cols",e.jsx("span",{className:"mx-1.5",children:"•"}),t.rows*t.cols," ",a==="zh"?"个机位":"Seats"]})]})]}),e.jsxs("div",{className:"flex items-center gap-1.5 opacity-0 group-hover:opacity-100 focus-within:opacity-100 transition-opacity",children:[e.jsx("button",{onClick:n=>{n.stopPropagation(),ke(t)},className:"p-1 text-gray-500 hover:text-indigo-600 hover:bg-white rounded transition-colors",title:a==="zh"?"编辑规则":"Edit Rule",children:e.jsx(Pe,{size:13})}),e.jsx("button",{onClick:n=>{n.stopPropagation(),$e(t.id)},className:"p-1 text-gray-500 hover:text-rose-600 hover:bg-white rounded transition-colors",title:a==="zh"?"删除机房间":"Delete Lab",children:e.jsx(Te,{size:14})})]})]}),o.length>0&&e.jsxs("div",{className:"flex items-center gap-1 flex-wrap pt-1 border-t border-gray-100/80",children:[e.jsx("span",{className:"text-[10px] text-gray-400 font-medium",children:a==="zh"?"绑定班级:":"Bound:"}),o.map(n=>e.jsx("span",{className:"text-[10px] font-semibold bg-indigo-100/70 text-indigo-700 px-1.5 py-0.5 rounded-md",children:n.name},n.id))]})]},t.id)})})]}),e.jsx("div",{className:"lg:col-span-8 flex flex-col min-h-0 bg-white border border-gray-200 rounded-2xl shadow-xs overflow-hidden",children:B?e.jsxs("form",{onSubmit:Ce,className:"flex-1 flex flex-col min-h-0",children:[e.jsxs("div",{className:"p-4 border-b border-gray-100 bg-gray-50 font-bold text-xs text-gray-700 uppercase tracking-wider shrink-0 flex items-center justify-between",children:[e.jsx("span",{children:E?a==="zh"?"编辑机房规则":"Edit Lab Seating":a==="zh"?"创建全新上机机房":"Create Computer Lab"}),e.jsx("button",{type:"button",onClick:()=>w(!1),className:"text-gray-400 hover:text-gray-600",children:e.jsx(I,{size:16})})]}),e.jsxs("div",{className:"flex-1 overflow-y-auto p-6 space-y-4",children:[R&&e.jsxs("div",{className:"p-3 bg-red-50 border border-red-200 text-red-700 text-xs rounded-xl flex items-center gap-1.5",children:[e.jsx(Me,{size:14}),e.jsx("span",{children:R})]}),e.jsxs("div",{className:"space-y-1",children:[e.jsx("label",{className:"block text-xs font-bold text-gray-500 uppercase tracking-wide",children:a==="zh"?"机房编号 / 场所名称 *":"Lab Name / Number *"}),e.jsx("input",{type:"text",value:q,onChange:t=>V(t.target.value),placeholder:a==="zh"?"例如:305综合机房、软创中心、Lab A":"e.g. Lab 404, Software Sandbox, Suite C",className:"w-full text-xs p-2.5 border border-gray-300 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 rounded-xl mt-1.5",maxLength:32,required:!0})]}),e.jsxs("div",{className:"grid grid-cols-2 gap-4",children:[e.jsxs("div",{className:"space-y-1",children:[e.jsx("label",{className:"block text-xs font-bold text-gray-500 uppercase tracking-wide",children:a==="zh"?"排列行数 (Rows) *":"Rows Direction *"}),e.jsx("input",{type:"number",value:N,onChange:t=>H(Math.max(1,parseInt(t.target.value)||1)),min:1,max:15,className:"w-full text-xs p-2.5 border border-gray-300 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 rounded-xl mt-1.5",required:!0}),e.jsx("span",{className:"text-xs text-gray-400 block mt-0.5",children:a==="zh"?"上下方向机位数 (最大15行)":"Horizontal sets count (Max 15)"})]}),e.jsxs("div",{className:"space-y-1",children:[e.jsx("label",{className:"block text-xs font-bold text-gray-500 uppercase tracking-wide",children:a==="zh"?"排列列数 (Columns) *":"Columns Direction *"}),e.jsx("input",{type:"number",value:S,onChange:t=>Q(Math.max(1,parseInt(t.target.value)||1)),min:1,max:15,className:"w-full text-xs p-2.5 border border-gray-300 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 rounded-xl mt-1.5",required:!0}),e.jsx("span",{className:"text-xs text-gray-400 block mt-0.5",children:a==="zh"?"左右方向机位数 (最大15列)":"Vertical sets count (Max 15)"})]})]}),e.jsxs("div",{className:"border border-dashed border-gray-200 bg-gray-50/50 p-4 rounded-xl",children:[e.jsx("span",{className:"text-xs font-bold text-gray-400 uppercase tracking-wider block mb-2",children:a==="zh"?"布局网格缩略结构":"Layout Aspect Schema"}),e.jsxs("div",{className:"flex flex-col gap-1 items-center justify-center p-3 bg-white rounded-lg border border-gray-100 min-h-[120px]",children:[e.jsxs("div",{className:"flex flex-col gap-1",children:[Array.from({length:Math.min(N,6)}).map((t,s)=>e.jsxs("div",{className:"flex gap-1 justify-center",children:[Array.from({length:Math.min(S,8)}).map((o,n)=>e.jsx("div",{className:"w-4 h-4 rounded bg-indigo-100 border border-indigo-200 shrink-0"},n)),S>8&&e.jsx("div",{className:"w-4 text-xs text-gray-400 flex items-center justify-center",children:"..."})]},s)),N>6&&e.jsx("div",{className:"text-xs text-center text-gray-400 mt-1",children:"..."})]}),e.jsxs("span",{className:"text-xs text-gray-450 mt-3 font-medium",children:["配置容量:",N," × ",S," = ",N*S," 座位"]})]})]})]}),e.jsxs("div",{className:"p-4 border-t border-gray-100 shrink-0 flex items-center justify-end gap-2 bg-gray-50",children:[e.jsx("button",{type:"button",onClick:()=>w(!1),className:"px-4 py-2 text-xs font-semibold text-gray-500 hover:text-gray-700 bg-white hover:bg-gray-100 border border-gray-200 rounded-xl transition-all cursor-pointer",children:a==="zh"?"取消":"Cancel"}),e.jsxs("button",{type:"submit",disabled:ee,className:"px-4 py-2 text-xs font-bold text-white bg-indigo-600 hover:bg-indigo-700 disabled:opacity-50 rounded-xl flex items-center gap-1 shadow-sm transition-all cursor-pointer",children:[e.jsx(ge,{size:14}),e.jsx("span",{children:ee?a==="zh"?"提交中...":"Saving...":a==="zh"?"提交保存":"Save Config"})]})]})]}):e.jsxs("div",{className:"flex-1 flex flex-col min-h-0",children:[e.jsxs("div",{className:"p-3 border-b border-gray-100 bg-gray-50 flex flex-wrap items-center justify-between gap-3 shrink-0",children:[e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsxs("span",{className:"text-xs font-bold text-gray-700 uppercase tracking-wider flex items-center gap-1.5",children:[e.jsx(fe,{size:14,className:"text-indigo-600"}),a==="zh"?"班级机位联动:":"Class Seating Link:"]}),e.jsxs("select",{value:d||"",onChange:t=>X(t.target.value?t.target.value:null),className:"text-xs font-semibold bg-white border border-gray-300 rounded-lg px-2.5 py-1.5 text-gray-700 focus:outline-none focus:ring-1 focus:ring-indigo-500 cursor-pointer shadow-2xs",children:[e.jsx("option",{value:"",children:a==="zh"?"📐 物理规格总览 (无班级)":"📐 Physical Blueprint (No Class)"}),$.map(t=>{const s=t.lab_id===i?.id;return e.jsxs("option",{value:t.id,children:[t.name," ",s?a==="zh"?"★ 已绑定此机房":"★ Bound":""]},t.id)})]})]}),d&&i&&e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsxs("button",{type:"button",onClick:Ne,disabled:z.length===0,className:"px-2.5 py-1.5 text-xs font-semibold bg-indigo-50 hover:bg-indigo-100 text-indigo-700 border border-indigo-200 rounded-lg flex items-center gap-1 transition-colors cursor-pointer disabled:opacity-40 disabled:cursor-not-allowed",title:a==="zh"?"将未排座学生按学号依次填入空闲机位":"Auto assign unplaced students",children:[e.jsx(Ee,{size:13}),e.jsx("span",{children:a==="zh"?"一键排座":"Auto Fill"})]}),e.jsxs("button",{type:"button",onClick:Se,disabled:g.length===0,className:"px-2.5 py-1.5 text-xs font-semibold bg-gray-100 hover:bg-rose-50 text-gray-600 hover:text-rose-600 border border-gray-200 rounded-lg flex items-center gap-1 transition-colors cursor-pointer disabled:opacity-40 disabled:cursor-not-allowed",title:a==="zh"?"清空当前班级排座":"Clear Seating",children:[e.jsx(Re,{size:13}),e.jsx("span",{children:a==="zh"?"清空":"Clear"})]}),e.jsxs("button",{type:"button",onClick:ze,disabled:Z||!K&&!i,className:`px-3 py-1.5 text-xs font-bold rounded-lg flex items-center gap-1.5 shadow-xs transition-all cursor-pointer ${K?"bg-emerald-600 hover:bg-emerald-700 text-white animate-pulse":"bg-indigo-600 hover:bg-indigo-700 text-white disabled:opacity-60 disabled:cursor-not-allowed"}`,children:[Z?e.jsx(be,{size:13,className:"animate-spin"}):e.jsx(Oe,{size:13}),e.jsx("span",{children:Z?a==="zh"?"保存中...":"Saving...":K?a==="zh"?"保存排座 (已修改)":"Save (Modified)":a==="zh"?"已保存":"Saved"})]}),e.jsx(M,{slot:"computer_lab.seating.toolbar",slotProps:{labId:i.id,classId:d,assignedSeats:g,setAssignedSeats:_,lang:a}})]})]}),de&&e.jsxs("div",{className:"bg-emerald-50 border-b border-emerald-200 text-emerald-800 text-xs px-4 py-2 flex items-center gap-1.5 animate-fadeIn",children:[e.jsx(ge,{size:14,className:"text-emerald-600 shrink-0"}),e.jsx("span",{className:"font-medium",children:de})]}),R&&e.jsxs("div",{className:"bg-red-50 border-b border-red-200 text-red-700 text-xs px-4 py-2 flex items-center gap-1.5",children:[e.jsx(Fe,{size:14,className:"text-red-500 shrink-0"}),e.jsx("span",{children:R})]}),i?e.jsxs("div",{className:"flex-1 flex flex-col min-h-0 bg-slate-950 text-white overflow-hidden relative",children:[e.jsx("div",{className:"py-2.5 flex justify-center shrink-0 border-b border-slate-900 bg-slate-950/80 backdrop-blur-xs z-10",children:e.jsx("div",{className:"w-56 bg-slate-900 border border-slate-700 py-1 rounded text-center text-xs font-bold text-slate-300 uppercase tracking-widest select-none shadow-sm flex items-center justify-center gap-1.5",children:e.jsxs("span",{children:["📽️ ",a==="zh"?"讲台 / 教师主荧幕":"Classroom Stage / Screen"]})})}),e.jsx("div",{className:"flex-1 overflow-auto p-4 flex flex-col items-center",children:ie?e.jsxs("div",{className:"flex flex-col items-center justify-center h-48 gap-2 text-slate-400",children:[e.jsx(be,{size:24,className:"animate-spin text-indigo-400"}),e.jsx("span",{className:"text-xs",children:a==="zh"?"正在读取统一机房座位数据...":"Loading lab seating data..."})]}):e.jsx("div",{className:"flex flex-col gap-2.5 py-2 max-w-full",children:Array.from({length:i.rows}).map((t,s)=>e.jsxs("div",{className:"flex gap-2.5 justify-center items-center",children:[e.jsxs("span",{className:"text-xs font-bold font-mono text-slate-500 w-6 text-right select-none pr-0.5",children:["R",s+1]}),Array.from({length:i.cols}).map((o,n)=>{const r=Y.get(`${s}_${n}`),c=!!r,u=f&&(!c||r.student_id!==f.id),A=!!x?.from&&x.from.row===s&&x.from.col===n,O=!!x&&!!W&&W.row===s&&W.col===n&&!A,j=O&&c&&x?.type==="seat"&&r.student_id!==x.studentId,F=O&&(!c||x?.type==="pool");return e.jsx("div",{"data-seat-id":`${s}_${n}`,onClick:()=>ve(s,n),draggable:!!d&&c,onDragStart:m=>{!d||!r||(L({type:"seat",studentId:r.student_id,from:{row:s,col:n}}),m.dataTransfer.setData("text/plain",JSON.stringify({type:"seat",studentId:r.student_id,from:{row:s,col:n}})),m.dataTransfer.effectAllowed="move")},onDragEnd:()=>{L(null),P(null)},onDragOver:m=>{!x||A||(m.preventDefault(),m.dataTransfer.dropEffect="move",P({row:s,col:n}))},onDragLeave:()=>{P(m=>m&&m.row===s&&m.col===n?null:m)},onDrop:m=>{if(m.preventDefault(),x&&!A){const he=x.type==="pool"?z.find(T=>T.id===x.studentId)||D.find(T=>T.id===x.studentId):D.find(T=>T.id===x.studentId);he&&(me(he,{row:s,col:n},x.from),v(null),C(null))}L(null),P(null)},className:`group relative rounded-xl flex flex-col items-center justify-center transition-all select-none shadow-sm ${d&&c?"cursor-grab":"cursor-pointer"} ${d?"w-20 h-14":"w-11 h-11"} ${c?"bg-slate-850 border border-indigo-500/70 hover:border-indigo-400 shadow-indigo-950/50":u?"bg-indigo-950/40 border border-dashed border-indigo-400 hover:bg-indigo-900/50 ring-1 ring-indigo-400/50 animate-pulse":"bg-slate-900 border border-slate-800 hover:border-slate-700 hover:bg-slate-850"}${j?" ring-2 ring-amber-400 border-amber-400 bg-amber-950/40":""}${F&&!j?" ring-2 ring-emerald-400 border-emerald-400 bg-emerald-950/40":""}${A?" opacity-40":""}`,children:c?e.jsxs(e.Fragment,{children:[e.jsxs("div",{className:"w-full h-full p-1.5 flex flex-col justify-between items-center text-center",children:[e.jsxs("div",{className:"flex items-center justify-between w-full px-0.5",children:[e.jsxs("span",{className:"text-[10px] font-mono text-indigo-300/80",children:[s+1,"-",n+1]}),d&&e.jsx("button",{type:"button",onClick:m=>we(m,s,n),className:"opacity-0 group-hover:opacity-100 text-slate-400 hover:text-rose-400 p-0.5 rounded transition-opacity",title:a==="zh"?"移出此座位":"Unseat student",children:e.jsx(I,{size:11})})]}),e.jsx("div",{className:"font-bold text-xs text-white truncate max-w-[70px] leading-tight",children:r.student_name||"Student"}),e.jsx("div",{className:"text-[9px] font-mono text-slate-400 truncate max-w-[70px]",children:r.student_number||`ID:${r.student_id.slice(-4)}`}),e.jsx("div",{className:"w-full min-w-0",onClick:m=>m.stopPropagation(),children:e.jsx(M,{slot:"computer_lab.seat.overlay",slotProps:{labId:i.id,row:s,col:n,seat:r,isOccupied:!0}})})]}),e.jsxs("div",{className:"absolute bottom-16 bg-slate-900 text-white text-xs rounded-lg px-2.5 py-1.5 hidden group-hover:block whitespace-nowrap z-30 font-sans border border-indigo-700 shadow-2xl pointer-events-none",children:[e.jsx("div",{className:"font-bold text-indigo-300",children:r.student_name}),e.jsxs("div",{className:"text-[10px] text-slate-400 font-mono",children:[a==="zh"?"学号:":"No: ",r.student_number||r.student_id]}),e.jsx("div",{className:"text-[10px] text-slate-300 mt-0.5",children:a==="zh"?`第 ${s+1} 排,第 ${n+1} 列`:`Row ${s+1}, Col ${n+1}`})]})]}):e.jsxs(e.Fragment,{children:[e.jsx(U,{size:d?14:15,className:u?"text-indigo-400 animate-bounce":"text-slate-600 opacity-60"}),e.jsxs("span",{className:"text-[10px] font-mono text-slate-500 mt-0.5",children:[s+1,"-",n+1]}),e.jsx("div",{className:"absolute bottom-12 bg-slate-900 text-white text-xs rounded px-2 py-1 hidden group-hover:block whitespace-nowrap z-30 font-sans border border-slate-700 shadow-xl pointer-events-none",children:f?a==="zh"?`点击安排 [${f.name}] 到第 ${s+1} 排第 ${n+1} 列`:`Place [${f.name}] here`:a==="zh"?`第 ${s+1} 排第 ${n+1} 列 (空闲)`:`Row ${s+1} / Column ${n+1} (Empty)`})]})},n)})]},s))})}),d&&i&&!ie&&e.jsx("div",{className:"shrink-0 px-4 pb-2",children:e.jsx(M,{slot:"computer_lab.seating.sidebar",slotProps:{labId:i.id,classId:d,assignedSeats:g,unassignedStudents:z}})}),d?e.jsxs("div",{className:"border-t border-slate-900 bg-slate-900/90 p-3 shrink-0 flex flex-col gap-2",children:[e.jsxs("div",{className:"flex items-center justify-between text-xs",children:[e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsxs("span",{className:"font-bold text-slate-300 flex items-center gap-1.5",children:[e.jsx(fe,{size:13,className:"text-indigo-400"}),a==="zh"?"待排座学生池:":"Unassigned Students Pool:"]}),e.jsxs("span",{className:"text-[11px] font-mono text-slate-400",children:[z.length," ",a==="zh"?"人未排":"unplaced"," / ",D.length," ",a==="zh"?"人总计":"total"]}),f&&e.jsxs("span",{className:"bg-indigo-900/80 text-indigo-300 border border-indigo-700 text-[11px] px-2 py-0.5 rounded-full flex items-center gap-1 animate-pulse",children:[e.jsx("span",{children:a==="zh"?`当前选择: ${f.name} (点击上方空位落座)`:`Selected: ${f.name} (click empty seat)`}),e.jsx("button",{type:"button",onClick:()=>{v(null),C(null)},className:"hover:text-white",children:e.jsx(I,{size:10})})]})]}),e.jsxs("div",{className:"text-[11px] font-mono text-slate-400",children:[a==="zh"?"机房已占:":"Occupied: ",e.jsx("span",{className:"text-indigo-400 font-bold",children:g.length})," /"," ",i.rows*i.cols]})]}),z.length===0?e.jsxs("div",{className:"text-xs text-slate-500 py-1 italic flex items-center gap-1",children:[e.jsx(Ue,{size:13,className:"text-emerald-400"}),e.jsx("span",{children:a==="zh"?"当前班级全部学生均已成功分配机位!":"All students in this class have been assigned seats!"})]}):e.jsx("div",{className:"flex items-center gap-1.5 overflow-x-auto py-1 max-h-16 pr-2",children:z.map(t=>{const s=f?.id===t.id,o=x?.type==="pool"&&x.studentId===t.id;return e.jsxs("div",{className:"shrink-0 flex items-center gap-1","data-unassigned-id":t.id,children:[e.jsxs("button",{type:"button",draggable:!0,onDragStart:n=>{L({type:"pool",studentId:t.id}),n.dataTransfer.setData("text/plain",JSON.stringify({type:"pool",studentId:t.id})),n.dataTransfer.effectAllowed="move"},onDragEnd:()=>{L(null),P(null)},onClick:()=>{v(s?null:t),C(null)},className:`px-2.5 py-1 rounded-lg text-xs font-medium border transition-all cursor-grab active:cursor-grabbing flex items-center gap-1.5 ${s?"bg-indigo-600 text-white border-indigo-400 ring-2 ring-indigo-400/50 shadow-md":"bg-slate-800 text-slate-300 border-slate-700 hover:border-indigo-500 hover:bg-slate-750"}${o?" opacity-40":""}`,children:[e.jsx("span",{children:t.name}),t.student_number&&e.jsx("span",{className:"text-[10px] font-mono text-slate-400",children:t.student_number})]}),e.jsx(M,{slot:"computer_lab.unassigned.item",slotProps:{student:t,classId:d,labId:i?.id}})]},t.id)})})]}):e.jsxs("div",{className:"border-t border-slate-900 bg-slate-900/60 p-3 shrink-0 flex items-center justify-between text-xs text-slate-400 select-none",children:[e.jsxs("div",{className:"flex items-center gap-3",children:[e.jsxs("div",{className:"flex items-center gap-1.5",children:[e.jsx("div",{className:"w-3.5 h-3.5 rounded bg-slate-800 border border-slate-700 flex items-center justify-center p-0.5",children:e.jsx(U,{size:10,className:"text-indigo-400 opacity-60"})}),e.jsx("span",{children:a==="zh"?"标准上机终端":"Console Terminals"})]}),ce.length>0&&e.jsxs("div",{className:"flex items-center gap-1",children:[e.jsx("span",{className:"text-slate-500",children:a==="zh"?"已绑定班级:":"Bound Classes:"}),ce.map(t=>e.jsx("button",{type:"button",onClick:()=>X(t.id),className:"text-indigo-400 hover:text-indigo-300 underline font-semibold text-xs cursor-pointer ml-1",children:t.name},t.id))]})]}),e.jsxs("div",{className:"font-mono text-slate-500",children:[a==="zh"?"总行数:":"Rows: ",e.jsx("span",{className:"text-indigo-400 font-bold",children:i.rows})," |",a==="zh"?" 总列数:":" Columns: ",e.jsx("span",{className:"text-indigo-400 font-bold",children:i.cols})," |",a==="zh"?" 配置席位:":" Total: ",e.jsx("span",{className:"text-white font-bold",children:i.rows*i.cols})]})]})]}):e.jsxs("div",{className:"flex-1 flex flex-col items-center justify-center text-center p-8 text-gray-400 h-full",children:[e.jsx(U,{size:48,className:"opacity-20 mb-3"}),e.jsx("p",{className:"text-xs font-medium",children:a==="zh"?"请选择一个机房以预览其座位图":"Please select a lab config to preview details."})]})]})})]})]})}function Ge(h={}){const y=Ae(),a=h.computerLabs??y?.computerLabs??[],b=h.onRefresh??y?.fetchLabs??(async()=>{}),B=h.lang??y?.lang??"zh",w=h.classes??y?.classes??[];return e.jsx(Be,{computerLabs:a,onRefresh:b,lang:B,classes:w})}export{Ge as ComputerLabView};
|