@tinyweb_dev/oe-exam-sdk 0.1.30 → 0.1.31
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/dist/components/exams/ExamPreviewDialog.d.ts +9 -3
- package/dist/components/exams/ExamPreviewDialog.js +23 -5
- package/dist/components/exams/ExamPreviewPartContent.d.ts +2 -1
- package/dist/components/exams/ExamPreviewPartContent.js +3 -2
- package/dist/components/exams/ExamPreviewSidebar.d.ts +3 -1
- package/dist/components/exams/ExamPreviewSidebar.js +7 -2
- package/dist/components/exams/exam-preview.mode.d.ts +4 -0
- package/dist/components/exams/exam-preview.mode.js +5 -0
- package/dist/components/exams/index.d.ts +1 -0
- package/dist/components/exams/index.js +1 -0
- package/dist/components/exams/take/components/QuestionRenderer.js +5 -1
- package/dist/components/exams/take/utils/answer-transformers.js +2 -1
- package/dist/components/exams/take/utils/question-transformers.d.ts +6 -1
- package/dist/components/exams/take/utils/question-transformers.js +33 -0
- package/dist/components/questions/_shared/config/question-types.config.js +6 -0
- package/dist/components/questions/_shared/types/answer-the-question-group.type.d.ts +32 -0
- package/dist/components/questions/_shared/types/answer-the-question-group.type.js +1 -0
- package/dist/components/questions/creator/question-type-registry.js +2 -0
- package/dist/components/questions/types/answer-the-question-group/AnswerTheQuestionGroupClient.d.ts +2 -0
- package/dist/components/questions/types/answer-the-question-group/AnswerTheQuestionGroupClient.js +13 -0
- package/dist/components/questions/types/answer-the-question-group/AnswerTheQuestionGroupCreator.d.ts +2 -0
- package/dist/components/questions/types/answer-the-question-group/AnswerTheQuestionGroupCreator.js +183 -0
- package/dist/components/questions/types/answer-the-question-group/index.d.ts +5 -0
- package/dist/components/questions/types/answer-the-question-group/index.js +5 -0
- package/dist/components/questions/types/answer-the-question-group/map-answer-the-question-group-data.d.ts +4 -0
- package/dist/components/questions/types/answer-the-question-group/map-answer-the-question-group-data.js +83 -0
- package/dist/components/questions/types/answer-the-question-group/register.d.ts +2 -0
- package/dist/components/questions/types/answer-the-question-group/register.js +36 -0
- package/dist/components/questions/types/answer-the-question-group/transform.d.ts +2 -0
- package/dist/components/questions/types/answer-the-question-group/transform.js +72 -0
- package/dist/components/questions/types/speaking-conversation/SpeakingConversationPreviewClient.js +1 -1
- package/dist/components/questions/viewer/QuestionViewer.js +2 -2
- package/dist/components/questions/viewer/SpontaneousQaGroupViewer.js +4 -4
- package/dist/components/results/ReviewQuestionRenderer.js +1 -0
- package/dist/components/results/renderers/ReviewAnswerTheQuestionGroupRenderer.d.ts +19 -0
- package/dist/components/results/renderers/ReviewAnswerTheQuestionGroupRenderer.js +27 -0
- package/dist/components/results/renderers/review-question-body-dedicated.d.ts +1 -0
- package/dist/components/results/renderers/review-question-body-dedicated.js +47 -2
- package/dist/components/results/review-data.js +12 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/shared/constants/question-skills.js +2 -0
- package/dist/shared/lib/i18n/messages/en/admin.d.ts +5 -0
- package/dist/shared/lib/i18n/messages/en/admin.js +5 -0
- package/dist/shared/lib/i18n/messages/en.d.ts +5 -0
- package/dist/shared/lib/i18n/messages/vi/admin.d.ts +5 -0
- package/dist/shared/lib/i18n/messages/vi/admin.js +5 -0
- package/dist/shared/lib/i18n/messages/vi.d.ts +5 -0
- package/dist/shared/lib/utils/question-reverse-transform.js +39 -0
- package/dist/shared/lib/utils/question-transform.js +2 -0
- package/dist/shared/types/common.types.d.ts +1 -1
- package/dist/shared/types/questions/answer-the-question-group.d.ts +39 -0
- package/dist/shared/types/questions/answer-the-question-group.js +1 -0
- package/dist/shared/types/questions/index.d.ts +1 -0
- package/dist/shared/types/questions/index.js +2 -0
- package/package.json +1 -1
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { AnswerTheQuestionGroupData, AnswerTheQuestionGroupItemData } from '../../_shared/types/answer-the-question-group.type';
|
|
2
|
+
export declare function createEmptyAnswerTheQuestionGroupItem(questionNumber: number): AnswerTheQuestionGroupItemData;
|
|
3
|
+
export declare function mapAnswerTheQuestionGroupItem(item: unknown, index: number, fallbackAnswers?: string[]): AnswerTheQuestionGroupItemData;
|
|
4
|
+
export declare function mapQuestionToAnswerTheQuestionGroupData(question: Record<string, unknown> | null | undefined): AnswerTheQuestionGroupData;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
const DEFAULT_INSTRUCTION = 'Read the text again and answer the questions.';
|
|
2
|
+
function isRecord(value) {
|
|
3
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
4
|
+
}
|
|
5
|
+
function asRecord(value) {
|
|
6
|
+
return isRecord(value) ? value : {};
|
|
7
|
+
}
|
|
8
|
+
function asString(value, fallback = '') {
|
|
9
|
+
return typeof value === 'string' ? value : fallback;
|
|
10
|
+
}
|
|
11
|
+
function normalizeExpectedAnswers(value) {
|
|
12
|
+
if (!Array.isArray(value)) {
|
|
13
|
+
return [];
|
|
14
|
+
}
|
|
15
|
+
return value
|
|
16
|
+
.filter((item) => typeof item === 'string')
|
|
17
|
+
.map((item) => item.trim())
|
|
18
|
+
.filter((item) => item !== '');
|
|
19
|
+
}
|
|
20
|
+
export function createEmptyAnswerTheQuestionGroupItem(questionNumber) {
|
|
21
|
+
return {
|
|
22
|
+
question: '',
|
|
23
|
+
expectedAnswers: [''],
|
|
24
|
+
isExample: false,
|
|
25
|
+
points: 1,
|
|
26
|
+
questionNumber,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
export function mapAnswerTheQuestionGroupItem(item, index, fallbackAnswers) {
|
|
30
|
+
const record = asRecord(item);
|
|
31
|
+
const content = asRecord(record.content);
|
|
32
|
+
const correctAnswer = asRecord(record.correctAnswer);
|
|
33
|
+
const isExample = record.isExample === true;
|
|
34
|
+
const nested = normalizeExpectedAnswers(correctAnswer.expectedAnswers);
|
|
35
|
+
const expectedAnswers = nested.length > 0
|
|
36
|
+
? nested
|
|
37
|
+
: (normalizeExpectedAnswers(fallbackAnswers).length > 0
|
|
38
|
+
? normalizeExpectedAnswers(fallbackAnswers)
|
|
39
|
+
: ['']);
|
|
40
|
+
return {
|
|
41
|
+
question: asString(content.question) || asString(record.question),
|
|
42
|
+
expectedAnswers,
|
|
43
|
+
isExample,
|
|
44
|
+
points: isExample ? 0 : (typeof record.points === 'number' ? record.points : 1),
|
|
45
|
+
questionNumber: typeof record.questionNumber === 'number' ? record.questionNumber : index + 1,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
export function mapQuestionToAnswerTheQuestionGroupData(question) {
|
|
49
|
+
const source = asRecord(question);
|
|
50
|
+
const content = asRecord(source.content);
|
|
51
|
+
const answer = asRecord(source.answer);
|
|
52
|
+
const correctAnswer = asRecord(source.correctAnswer ?? source.correct_answer ?? answer.correctAnswer);
|
|
53
|
+
const explanation = String(source.explanation || answer.explanation || '');
|
|
54
|
+
const fallbackAnswers = Array.isArray(correctAnswer.answer)
|
|
55
|
+
? correctAnswer.answer
|
|
56
|
+
: [];
|
|
57
|
+
if (Array.isArray(content.items)) {
|
|
58
|
+
const items = content.items.map((item, index) => mapAnswerTheQuestionGroupItem(item, index, fallbackAnswers[index]?.expectedAnswers));
|
|
59
|
+
return {
|
|
60
|
+
instruction: asString(asRecord(content.meta).instruction) || DEFAULT_INSTRUCTION,
|
|
61
|
+
passage: asString(asRecord(content.meta).passage),
|
|
62
|
+
items,
|
|
63
|
+
explanation,
|
|
64
|
+
points: items.reduce((total, item) => (item.isExample ? total : total + (item.points || 0)), 0),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
if (Array.isArray(answer.items)) {
|
|
68
|
+
return {
|
|
69
|
+
instruction: asString(answer.instruction, DEFAULT_INSTRUCTION),
|
|
70
|
+
passage: asString(answer.passage),
|
|
71
|
+
items: answer.items,
|
|
72
|
+
explanation,
|
|
73
|
+
points: typeof answer.points === 'number' ? answer.points : 1,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
instruction: DEFAULT_INSTRUCTION,
|
|
78
|
+
passage: '',
|
|
79
|
+
items: [createEmptyAnswerTheQuestionGroupItem(1)],
|
|
80
|
+
explanation,
|
|
81
|
+
points: 1,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { AnswerTheQuestionGroupCreator } from './AnswerTheQuestionGroupCreator';
|
|
2
|
+
import { mapQuestionToAnswerTheQuestionGroupData } from './map-answer-the-question-group-data';
|
|
3
|
+
export const answerTheQuestionGroupRegistration = {
|
|
4
|
+
component: AnswerTheQuestionGroupCreator,
|
|
5
|
+
transformInitialData: (initialData) => mapQuestionToAnswerTheQuestionGroupData(initialData),
|
|
6
|
+
getExtraProps: (ctx) => ({
|
|
7
|
+
onUnsavedChangesChange: ctx.onUnsavedChangesChange,
|
|
8
|
+
validationRef: ctx.validationRef,
|
|
9
|
+
}),
|
|
10
|
+
wrapOnSave: (data, ctx) => {
|
|
11
|
+
const payload = data;
|
|
12
|
+
return {
|
|
13
|
+
type: ctx.questionType,
|
|
14
|
+
content: ctx.state.content,
|
|
15
|
+
points: payload.points ?? ctx.state.points ?? 1,
|
|
16
|
+
level: ctx.state.level,
|
|
17
|
+
difficulty: ctx.state.difficulty,
|
|
18
|
+
skill: ctx.state.skill,
|
|
19
|
+
answer: payload,
|
|
20
|
+
explanation: payload.explanation || '',
|
|
21
|
+
};
|
|
22
|
+
},
|
|
23
|
+
wrapOnChange: (data, ctx) => {
|
|
24
|
+
const payload = data;
|
|
25
|
+
return {
|
|
26
|
+
type: ctx.questionType,
|
|
27
|
+
content: ctx.state.content,
|
|
28
|
+
points: payload.points ?? ctx.state.points ?? 1,
|
|
29
|
+
level: ctx.state.level,
|
|
30
|
+
difficulty: ctx.state.difficulty,
|
|
31
|
+
skill: ctx.state.skill,
|
|
32
|
+
answer: payload,
|
|
33
|
+
explanation: payload.explanation || '',
|
|
34
|
+
};
|
|
35
|
+
},
|
|
36
|
+
};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
const CHILD_QUESTION_TYPE = 'ANSWER_THE_QUESTION';
|
|
2
|
+
const GRADING_TYPE = 'AI';
|
|
3
|
+
const DEFAULT_INSTRUCTION = 'Read the text again and answer the questions.';
|
|
4
|
+
function normalizeExpectedAnswers(value) {
|
|
5
|
+
if (!Array.isArray(value)) {
|
|
6
|
+
return [];
|
|
7
|
+
}
|
|
8
|
+
return value
|
|
9
|
+
.filter((item) => typeof item === 'string')
|
|
10
|
+
.map((item) => item.trim())
|
|
11
|
+
.filter((item) => item !== '');
|
|
12
|
+
}
|
|
13
|
+
function sumGradablePoints(items) {
|
|
14
|
+
return items.reduce((total, item) => {
|
|
15
|
+
if (item.isExample) {
|
|
16
|
+
return total;
|
|
17
|
+
}
|
|
18
|
+
const points = typeof item.points === 'number' ? item.points : 1;
|
|
19
|
+
return total + points;
|
|
20
|
+
}, 0);
|
|
21
|
+
}
|
|
22
|
+
function isContentEmpty(html) {
|
|
23
|
+
return !html || html === '<p></p>' || html === '<p><br></p>' || html.trim() === '';
|
|
24
|
+
}
|
|
25
|
+
export const transformAnswerTheQuestionGroup = (question) => {
|
|
26
|
+
const answerData = question.answer;
|
|
27
|
+
const items = Array.isArray(answerData?.items) ? answerData.items : [];
|
|
28
|
+
if (items.length === 0) {
|
|
29
|
+
return {
|
|
30
|
+
apiContent: {
|
|
31
|
+
meta: {
|
|
32
|
+
instruction: DEFAULT_INSTRUCTION,
|
|
33
|
+
gradingType: GRADING_TYPE,
|
|
34
|
+
isAiGraded: true,
|
|
35
|
+
},
|
|
36
|
+
items: [],
|
|
37
|
+
},
|
|
38
|
+
apiCorrectAnswer: { answer: [] },
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
const apiItems = items.map((item, index) => {
|
|
42
|
+
const expectedAnswers = normalizeExpectedAnswers(item.expectedAnswers);
|
|
43
|
+
return {
|
|
44
|
+
questionType: CHILD_QUESTION_TYPE,
|
|
45
|
+
...(item.isExample ? { isExample: true } : {}),
|
|
46
|
+
content: {
|
|
47
|
+
question: item.question || '',
|
|
48
|
+
},
|
|
49
|
+
correctAnswer: { expectedAnswers },
|
|
50
|
+
points: item.isExample ? 0 : (typeof item.points === 'number' ? item.points : 1),
|
|
51
|
+
questionNumber: item.questionNumber || index + 1,
|
|
52
|
+
};
|
|
53
|
+
});
|
|
54
|
+
const passage = answerData?.passage?.trim();
|
|
55
|
+
return {
|
|
56
|
+
apiContent: {
|
|
57
|
+
meta: {
|
|
58
|
+
instruction: answerData?.instruction || DEFAULT_INSTRUCTION,
|
|
59
|
+
...(!isContentEmpty(passage) ? { passage } : {}),
|
|
60
|
+
gradingType: GRADING_TYPE,
|
|
61
|
+
isAiGraded: true,
|
|
62
|
+
},
|
|
63
|
+
items: apiItems,
|
|
64
|
+
},
|
|
65
|
+
apiCorrectAnswer: {
|
|
66
|
+
answer: apiItems.map((item) => ({
|
|
67
|
+
expectedAnswers: item.correctAnswer.expectedAnswers,
|
|
68
|
+
})),
|
|
69
|
+
},
|
|
70
|
+
totalPoints: sumGradablePoints(items),
|
|
71
|
+
};
|
|
72
|
+
};
|
package/dist/components/questions/types/speaking-conversation/SpeakingConversationPreviewClient.js
CHANGED
|
@@ -23,7 +23,7 @@ function PreviewImageItem({ url, label }) {
|
|
|
23
23
|
// eslint-disable-next-line @next/next/no-img-element
|
|
24
24
|
_jsx("img", { src: previewUrl, alt: label || 'Image', className: "w-full h-full object-cover" })) : (_jsx("div", { className: "w-full h-full flex items-center justify-center text-gray-300 text-xs", children: "\u0110ang t\u1EA3i..." })) }), label && _jsx("span", { className: "text-xs text-center text-gray-600 font-medium", children: label })] }));
|
|
25
25
|
}
|
|
26
|
-
export function SpeakingConversationPreviewClient({ questionData, isReviewMode =
|
|
26
|
+
export function SpeakingConversationPreviewClient({ questionData, isReviewMode = false, }) {
|
|
27
27
|
const isPartnerQuestion = questionData.responder === 'PARTNER';
|
|
28
28
|
const hasImages = questionData.images && questionData.images.length > 0;
|
|
29
29
|
const gridCols = !hasImages || questionData.images.length <= 2
|
|
@@ -343,7 +343,7 @@ export function QuestionViewer({ question, onSubmitAnswer, isReviewMode = false,
|
|
|
343
343
|
regionNodes: answer?.regionNodes || [],
|
|
344
344
|
instructions: answer?.instructions || [],
|
|
345
345
|
};
|
|
346
|
-
return (_jsx(ReadAndColorObjectsClient, { questionData: questionData, onSubmit: (userConnections) => {
|
|
346
|
+
return (_jsx(ReadAndColorObjectsClient, { questionData: questionData, isReviewMode: isReviewMode, onSubmit: (userConnections) => {
|
|
347
347
|
setSelectedAnswer(userConnections);
|
|
348
348
|
onSubmitAnswer?.(userConnections);
|
|
349
349
|
} }));
|
|
@@ -600,7 +600,7 @@ export function QuestionViewer({ question, onSubmitAnswer, isReviewMode = false,
|
|
|
600
600
|
case 'CROSS_OUT_WORD_GROUP':
|
|
601
601
|
return (_jsx(CrossOutWordGroupViewer, { question: question, isReviewMode: isReviewMode }));
|
|
602
602
|
case 'SPONTANEOUS_QA_GROUP':
|
|
603
|
-
return _jsx(SpontaneousQaGroupViewer, { question: question });
|
|
603
|
+
return (_jsx(SpontaneousQaGroupViewer, { question: question, isReviewMode: isReviewMode }));
|
|
604
604
|
case 'WORD_ORDER_AND_MATCH_GROUP':
|
|
605
605
|
case 'WORD_ORDER_AND_MATCH':
|
|
606
606
|
return (_jsx(WordOrderAndMatchGroupViewer, { question: question, isReviewMode: isReviewMode, userAnswer: userAnswer }));
|
|
@@ -72,11 +72,11 @@ function resolveGroup(question) {
|
|
|
72
72
|
],
|
|
73
73
|
};
|
|
74
74
|
}
|
|
75
|
-
function QaPromptCard({ item, index }) {
|
|
76
|
-
return (_jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-gray-100 bg-gray-50 px-4 py-2.5", children: [_jsxs("span", { className: "text-sm font-medium text-gray-700", children: ["C\u00E2u ", item.questionNumber || index + 1] }), _jsxs("span", { className: "text-xs text-gray-500", children: [item.points || 0, " \u0111i\u1EC3m"] })] }), _jsxs("div", { className: "space-y-2 p-4", children: [_jsx("p", { className: "text-sm font-semibold text-gray-800", children: item.questionText || 'Câu hỏi chưa được nhập' }), item.audioUrl ? (_jsxs("p", { className: "text-xs text-gray-500", children: ["Audio: ", item.audioUrl] })) : null, item.expectedAnswers.length > 0 ? (_jsxs("div", { className: "rounded-lg border border-emerald-100 bg-emerald-50 px-3 py-2 text-sm text-emerald-800", children: [_jsx("p", { className: "mb-1 font-medium", children: "G\u1EE3i \u00FD \u0111\u00E1p \u00E1n" }), _jsx("ul", { className: "list-disc space-y-1 pl-4", children: item.expectedAnswers.map((answer) => (_jsx("li", { children: answer }, answer))) })] })) : null] })] }));
|
|
75
|
+
function QaPromptCard({ item, index, isReviewMode = false, }) {
|
|
76
|
+
return (_jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-gray-100 bg-gray-50 px-4 py-2.5", children: [_jsxs("span", { className: "text-sm font-medium text-gray-700", children: ["C\u00E2u ", item.questionNumber || index + 1] }), _jsxs("span", { className: "text-xs text-gray-500", children: [item.points || 0, " \u0111i\u1EC3m"] })] }), _jsxs("div", { className: "space-y-2 p-4", children: [_jsx("p", { className: "text-sm font-semibold text-gray-800", children: item.questionText || 'Câu hỏi chưa được nhập' }), item.audioUrl ? (_jsxs("p", { className: "text-xs text-gray-500", children: ["Audio: ", item.audioUrl] })) : null, isReviewMode && item.expectedAnswers.length > 0 ? (_jsxs("div", { className: "rounded-lg border border-emerald-100 bg-emerald-50 px-3 py-2 text-sm text-emerald-800", children: [_jsx("p", { className: "mb-1 font-medium", children: "G\u1EE3i \u00FD \u0111\u00E1p \u00E1n" }), _jsx("ul", { className: "list-disc space-y-1 pl-4", children: item.expectedAnswers.map((answer) => (_jsx("li", { children: answer }, answer))) })] })) : null] })] }));
|
|
77
77
|
}
|
|
78
|
-
export function SpontaneousQaGroupViewer({ question, isReviewMode, }) {
|
|
78
|
+
export function SpontaneousQaGroupViewer({ question, isReviewMode = false, }) {
|
|
79
79
|
const { instruction, parts } = resolveGroup(question);
|
|
80
80
|
const showPartChrome = parts.length > 1 || parts.some((part) => part.partTitle || part.topic);
|
|
81
|
-
return (_jsxs("div", { className: "space-y-5", children: [instruction ? (_jsx("div", { className: "rounded-xl border border-indigo-100 bg-indigo-50 px-4 py-3 text-sm font-medium text-indigo-800", children: instruction })) : null, parts.map((part) => (_jsxs("section", { className: "space-y-3", children: [showPartChrome ? (_jsxs("div", { children: [part.partTitle ? (_jsx("h3", { className: "text-base font-bold tracking-wide text-sky-600", children: part.partTitle })) : null, part.topic ? (_jsx("p", { className: "text-sm font-semibold text-slate-600", children: part.topic })) : null] })) : null, _jsx("div", { className: "space-y-3", children: part.questions.map((item, index) => (_jsx(QaPromptCard, { item: item, index: index }, `${part.partNumber}-${item.questionNumber}-${index}`))) })] }, `part-${part.partNumber}`)))] }));
|
|
81
|
+
return (_jsxs("div", { className: "space-y-5", children: [instruction ? (_jsx("div", { className: "rounded-xl border border-indigo-100 bg-indigo-50 px-4 py-3 text-sm font-medium text-indigo-800", children: instruction })) : null, parts.map((part) => (_jsxs("section", { className: "space-y-3", children: [showPartChrome ? (_jsxs("div", { children: [part.partTitle ? (_jsx("h3", { className: "text-base font-bold tracking-wide text-sky-600", children: part.partTitle })) : null, part.topic ? (_jsx("p", { className: "text-sm font-semibold text-slate-600", children: part.topic })) : null] })) : null, _jsx("div", { className: "space-y-3", children: part.questions.map((item, index) => (_jsx(QaPromptCard, { item: item, index: index, isReviewMode: isReviewMode }, `${part.partNumber}-${item.questionNumber}-${index}`))) })] }, `part-${part.partNumber}`)))] }));
|
|
82
82
|
}
|
|
@@ -34,6 +34,7 @@ function renderQuestionBody({ part, reviewData, hidePerQuestionGrading = false,
|
|
|
34
34
|
correctAnswerTextMap: reviewData.correctAnswerTextMap,
|
|
35
35
|
correctOptionIdMap: reviewData.correctOptionIdMap,
|
|
36
36
|
rawCorrectAnswerMap: reviewData.rawCorrectAnswerMap,
|
|
37
|
+
rawStudentAnswerMap: reviewData.rawStudentAnswerMap,
|
|
37
38
|
questionScoreMap: reviewData.questionScoreMap,
|
|
38
39
|
hidePerQuestionGrading,
|
|
39
40
|
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { AnswerTheQuestionQuestion } from '../../../components/exams/take/utils/question-transformers';
|
|
2
|
+
export interface ReviewAnswerTheQuestionGroupItemMeta {
|
|
3
|
+
expectedAnswers: string[];
|
|
4
|
+
isCorrect: boolean | null;
|
|
5
|
+
feedbackEn?: string;
|
|
6
|
+
feedbackVi?: string;
|
|
7
|
+
}
|
|
8
|
+
interface ReviewAnswerTheQuestionGroupRendererProps {
|
|
9
|
+
partNumber: number;
|
|
10
|
+
partName?: string;
|
|
11
|
+
questionCount: number;
|
|
12
|
+
instruction: string;
|
|
13
|
+
groupContent?: string;
|
|
14
|
+
questions: AnswerTheQuestionQuestion[];
|
|
15
|
+
answers: Record<string, string>;
|
|
16
|
+
itemMeta: Record<string, ReviewAnswerTheQuestionGroupItemMeta>;
|
|
17
|
+
}
|
|
18
|
+
export declare function ReviewAnswerTheQuestionGroupRenderer({ partNumber, partName, questionCount, instruction, groupContent, questions, answers, itemMeta, }: ReviewAnswerTheQuestionGroupRendererProps): import("react").JSX.Element;
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { BookOpen, Check, Sparkles, Star, X } from 'lucide-react';
|
|
4
|
+
import CambridgeYlePartBanner from '../../../components/themes/cambridge-yle/CambridgeYlePartBanner';
|
|
5
|
+
import CambridgeYleInstructionBanner from '../../../components/themes/cambridge-yle/CambridgeYleInstructionBanner';
|
|
6
|
+
export function ReviewAnswerTheQuestionGroupRenderer({ partNumber, partName, questionCount, instruction, groupContent, questions, answers, itemMeta, }) {
|
|
7
|
+
return (_jsxs("div", { className: "space-y-4", children: [_jsx(CambridgeYlePartBanner, { partNumber: partNumber, partName: partName, questionCount: questionCount }), _jsx(CambridgeYleInstructionBanner, { instruction: instruction }), groupContent && (_jsxs("div", { className: "rounded-xl border border-gray-200 bg-white p-5 shadow-xs", children: [_jsxs("div", { className: "mb-2.5 flex items-center gap-2 text-xs font-bold uppercase tracking-wider text-indigo-700", children: [_jsx(BookOpen, { className: "h-4 w-4" }), _jsx("span", { children: "B\u00E0i \u0111\u1ECDc (Reading Passage)" })] }), _jsx("div", { className: "prose prose-sm max-w-none leading-relaxed text-gray-800", dangerouslySetInnerHTML: { __html: groupContent } })] })), questions.map((question) => {
|
|
8
|
+
const meta = itemMeta[question.id];
|
|
9
|
+
const studentAnswer = answers[question.id] || '';
|
|
10
|
+
const expectedAnswers = meta?.expectedAnswers || [];
|
|
11
|
+
const isCorrect = question.isExample ? true : meta?.isCorrect;
|
|
12
|
+
const feedback = meta?.feedbackVi || meta?.feedbackEn;
|
|
13
|
+
return (_jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-gray-100 bg-gradient-to-r from-gray-50 to-white px-4 py-2.5", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: `flex h-7 w-7 items-center justify-center rounded-full text-sm font-bold text-white ${question.isExample
|
|
14
|
+
? 'bg-orange-400'
|
|
15
|
+
: isCorrect === true
|
|
16
|
+
? 'bg-green-500'
|
|
17
|
+
: isCorrect === false
|
|
18
|
+
? 'bg-red-500'
|
|
19
|
+
: 'bg-blue-500'}`, children: question.questionNumber }), _jsxs("span", { className: "text-sm font-medium text-gray-700", children: ["C\u00E2u ", question.questionNumber] }), question.isExample && (_jsxs("span", { className: "inline-flex items-center gap-1 rounded-full bg-amber-100 px-2 py-0.5 text-xs font-semibold text-amber-700", children: [_jsx(Star, { className: "h-3 w-3" }), "Example"] })), _jsxs("span", { className: "inline-flex items-center gap-1 rounded-full bg-violet-100 px-2 py-0.5 text-xs font-semibold text-violet-700", children: [_jsx(Sparkles, { className: "h-3 w-3" }), "AI"] })] }), isCorrect === true && _jsx(Check, { className: "h-4 w-4 text-green-600" }), isCorrect === false && _jsx(X, { className: "h-4 w-4 text-red-600" })] }), _jsxs("div", { className: "space-y-3 p-4", children: [_jsx("p", { className: "text-sm font-semibold leading-relaxed text-gray-800", children: question.question }), _jsx("div", { className: `rounded-lg border px-3 py-2 text-sm ${isCorrect === true
|
|
20
|
+
? 'border-green-200 bg-green-50 text-green-800'
|
|
21
|
+
: isCorrect === false
|
|
22
|
+
? 'border-red-200 bg-red-50 text-red-800'
|
|
23
|
+
: 'border-gray-200 bg-gray-50 text-gray-700'}`, children: question.isExample
|
|
24
|
+
? (question.exampleAnswer || '(Câu mẫu)')
|
|
25
|
+
: (studentAnswer || '(Chưa trả lời)') }), expectedAnswers.length > 0 && (_jsxs("div", { className: "space-y-1", children: [_jsx("p", { className: "text-xs font-medium text-gray-500", children: "\u0110\u00E1p \u00E1n m\u1EABu (AI)" }), _jsx("ul", { className: "list-disc space-y-1 pl-5 text-sm text-gray-700", children: expectedAnswers.map((answer) => (_jsx("li", { children: answer }, answer))) })] })), feedback && (_jsx("p", { className: "text-xs text-violet-700", children: feedback }))] })] }, question.id));
|
|
26
|
+
})] }));
|
|
27
|
+
}
|
|
@@ -6,6 +6,7 @@ export interface DedicatedBodyContext {
|
|
|
6
6
|
correctAnswerTextMap: Record<string, string>;
|
|
7
7
|
correctOptionIdMap: Record<string, string>;
|
|
8
8
|
rawCorrectAnswerMap: Record<string, unknown>;
|
|
9
|
+
rawStudentAnswerMap: Record<string, unknown>;
|
|
9
10
|
questionScoreMap: Record<string, {
|
|
10
11
|
score: number | null;
|
|
11
12
|
maxScore: number;
|
|
@@ -4,8 +4,9 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
4
4
|
* Dedicated review renderer cases for ReviewQuestionRenderer.
|
|
5
5
|
* Source: oe-exam-fe ReviewQuestionRenderer.tsx
|
|
6
6
|
*/
|
|
7
|
-
import { transformChooseCorrectAdjective, transformChooseCorrectAnswer, transformChooseTheCorrectAnswerGroup, transformColorObjects, transformFillInBlank, transformWriteCorrectVerbForm, } from '../../../components/exams/take/utils/question-transformers';
|
|
7
|
+
import { transformAnswerTheQuestionGroup, transformChooseCorrectAdjective, transformChooseCorrectAnswer, transformChooseTheCorrectAnswerGroup, transformColorObjects, transformFillInBlank, transformWriteCorrectVerbForm, } from '../../../components/exams/take/utils/question-transformers';
|
|
8
8
|
import { normalizeColoringAssignments } from '../../../components/exams/take/utils/coloring-assignments';
|
|
9
|
+
import { ReviewAnswerTheQuestionGroupRenderer } from './ReviewAnswerTheQuestionGroupRenderer';
|
|
9
10
|
import { ReviewChooseAdjectiveRenderer } from './ReviewChooseAdjectiveRenderer';
|
|
10
11
|
import { ReviewChooseBestAnswerRenderer } from './ReviewChooseBestAnswerRenderer';
|
|
11
12
|
import { ReviewColoringRenderer } from './ReviewColoringRenderer';
|
|
@@ -20,7 +21,7 @@ function asApiQuestions(questions) {
|
|
|
20
21
|
return questions;
|
|
21
22
|
}
|
|
22
23
|
export function renderDedicatedReviewBody(ctx) {
|
|
23
|
-
const { part, answers, isCorrectMap, correctAnswerTextMap, correctOptionIdMap, rawCorrectAnswerMap, questionScoreMap, hidePerQuestionGrading, } = ctx;
|
|
24
|
+
const { part, answers, isCorrectMap, correctAnswerTextMap, correctOptionIdMap, rawCorrectAnswerMap, rawStudentAnswerMap, questionScoreMap, hidePerQuestionGrading, } = ctx;
|
|
24
25
|
const { partNo, questionType, questions } = part;
|
|
25
26
|
const questionCount = questions.length;
|
|
26
27
|
const apiQuestions = asApiQuestions(questions);
|
|
@@ -72,6 +73,50 @@ export function renderDedicatedReviewBody(ctx) {
|
|
|
72
73
|
case 'LISTEN_AND_DRAG_OBJECTS_INTO_SCENE': {
|
|
73
74
|
return (_jsx(ReviewListenDragObjectsRenderer, { partNumber: partNo, questionCount: questionCount, partName: part.name, questions: questions, studentAnswers: answers, rawCorrectAnswerMap: rawCorrectAnswerMap, isCorrectMap: isCorrectMap, questionScoreMap: questionScoreMap }));
|
|
74
75
|
}
|
|
76
|
+
case 'ANSWER_THE_QUESTION_GROUP': {
|
|
77
|
+
const parent = questions[0];
|
|
78
|
+
if (!parent) {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
const data = transformAnswerTheQuestionGroup(apiQuestions);
|
|
82
|
+
const parentId = parent.id || '';
|
|
83
|
+
const parentContent = asRecord(parent.content);
|
|
84
|
+
const items = Array.isArray(parentContent.items) ? parentContent.items : [];
|
|
85
|
+
const parentCorrect = asRecord(rawCorrectAnswerMap[parentId]);
|
|
86
|
+
const parentCorrectAnswers = Array.isArray(parentCorrect.answer)
|
|
87
|
+
? parentCorrect.answer
|
|
88
|
+
: [];
|
|
89
|
+
const rawStudent = asRecord(rawStudentAnswerMap[parentId]);
|
|
90
|
+
const llmAssessment = asRecord(rawStudent.llmAssessment);
|
|
91
|
+
const llmItems = Array.isArray(llmAssessment.items) ? llmAssessment.items : [];
|
|
92
|
+
const llmByKey = new Map(llmItems
|
|
93
|
+
.map((item) => asRecord(item))
|
|
94
|
+
.filter((item) => typeof item.key === 'string')
|
|
95
|
+
.map((item) => [String(item.key), item]));
|
|
96
|
+
const itemMeta = Object.fromEntries(items.map((rawItem, index) => {
|
|
97
|
+
const item = asRecord(rawItem);
|
|
98
|
+
const itemCorrect = asRecord(item.correctAnswer);
|
|
99
|
+
const itemId = `${parentId}-item-${index}`;
|
|
100
|
+
const nested = Array.isArray(itemCorrect.expectedAnswers)
|
|
101
|
+
? itemCorrect.expectedAnswers.filter((value) => typeof value === 'string' && value.trim() !== '')
|
|
102
|
+
: [];
|
|
103
|
+
const fallbackRaw = asRecord(parentCorrectAnswers[index]);
|
|
104
|
+
const fallback = Array.isArray(fallbackRaw.expectedAnswers)
|
|
105
|
+
? fallbackRaw.expectedAnswers.filter((value) => typeof value === 'string' && value.trim() !== '')
|
|
106
|
+
: [];
|
|
107
|
+
const llmItem = asRecord(llmByKey.get(String(index)));
|
|
108
|
+
const feedback = asRecord(llmItem.feedback);
|
|
109
|
+
return [itemId, {
|
|
110
|
+
expectedAnswers: nested.length > 0 ? nested : fallback,
|
|
111
|
+
isCorrect: item.isExample === true
|
|
112
|
+
? true
|
|
113
|
+
: (typeof llmItem.isCorrect === 'boolean' ? llmItem.isCorrect : null),
|
|
114
|
+
feedbackEn: typeof feedback.en === 'string' ? feedback.en : undefined,
|
|
115
|
+
feedbackVi: typeof feedback.vi === 'string' ? feedback.vi : undefined,
|
|
116
|
+
}];
|
|
117
|
+
}));
|
|
118
|
+
return (_jsx(ReviewAnswerTheQuestionGroupRenderer, { partNumber: partNo, partName: part.name, questionCount: questionCount, instruction: part.instructions || data.instruction, groupContent: data.groupContent, questions: data.questions, answers: answers, itemMeta: itemMeta }));
|
|
119
|
+
}
|
|
75
120
|
case 'SPONTANEOUS_QA_GROUP': {
|
|
76
121
|
const parent = questions[0];
|
|
77
122
|
if (!parent) {
|
|
@@ -6,6 +6,7 @@ const FLATTENED_ANSWER_TYPES = new Set([
|
|
|
6
6
|
'READ_AND_COLOR_OBJECTS',
|
|
7
7
|
'IMAGE_OBJECT_MATCHING',
|
|
8
8
|
'CHOOSE_THE_CORRECT_ANSWER_GROUP',
|
|
9
|
+
'ANSWER_THE_QUESTION_GROUP',
|
|
9
10
|
'CROSS_OUT_WORD_GROUP',
|
|
10
11
|
]);
|
|
11
12
|
export function transformResultAnswersForReview(answers, templateParts = []) {
|
|
@@ -156,6 +157,17 @@ function transformStudentAnswer(questionId, questionType, value) {
|
|
|
156
157
|
typeof item === 'string' ? item : '',
|
|
157
158
|
]));
|
|
158
159
|
}
|
|
160
|
+
if (questionType === 'ANSWER_THE_QUESTION_GROUP') {
|
|
161
|
+
const answer = Array.isArray(record.answer)
|
|
162
|
+
? record.answer
|
|
163
|
+
: Array.isArray(value)
|
|
164
|
+
? value
|
|
165
|
+
: [];
|
|
166
|
+
return Object.fromEntries(answer.map((item, index) => [
|
|
167
|
+
`${questionId}-item-${index}`,
|
|
168
|
+
typeof item === 'string' ? item : '',
|
|
169
|
+
]));
|
|
170
|
+
}
|
|
159
171
|
if (questionType === 'CROSS_OUT_WORD_GROUP') {
|
|
160
172
|
const answer = Array.isArray(record.answer)
|
|
161
173
|
? record.answer
|
package/dist/index.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ export * as Themes from './components/themes';
|
|
|
7
7
|
export * as Shared from './shared';
|
|
8
8
|
export { buildExamTemplatePayload, createContestRoomsApi, createExamCreateApi, createExamEntryApi, createExamQuestionsApi, createExamQuestionsFetchApi, createExamTakingApi, createMockContestRoomsApi, createMockCreateExamApi, createMockExamEntryApi, createMockExamQuestionsApi, createMockExamTakingApi, createMockResultReviewApi, createResultReviewApi } from './api';
|
|
9
9
|
export type { AnswerGradingStatusItem, AnswersGradingStatusResult, ContestRoomListItem, ContestRoomRegistrationStatus, ContestRoomVisibility, ContestRoomsContestDetail, ContestRoomsFetchApiOptions, ContestRoomsSdkApi, CreateExamFetchApiOptions, CreateExamPayload, CreateExamSdkApi, ExamFormData, ExamLevelOption, ExamQuestionsSdkApi, ExamResponse, ExamTakingAnswerValue, ExamTakingAttempt, ExamTakingFetchApiOptions, ExamTakingQuestion, ExamTakingQuestionsResult, ExamTakingRoom, ExamTakingSdkApi, ExamTakingTemplatePart, ExamTemplatePayload, ExamWithTemplate, ExistingQuestionBank, FetchApiOptions, GradingSummary, QuestionSearchItem, SaveExamInput, SaveExamTakingAnswersInput, SaveQuestionInput, SaveQuestionResult, SearchQuestionBankInput, SearchQuestionBankResult, SubmitExamTakingAttemptResult, UpdateExamTemplateInput, UpdatePartAudioInput, UploadAudioAnswerResult } from './api';
|
|
10
|
-
export { CreateExamPageContainer, ExamPreviewDialog, ExamQuestionsPageContainer, ExamTakingPageContainer, ExamViewDialog, StudentContestRoomsPageContainer, StudentExamEntryPageContainer } from './components/exams';
|
|
10
|
+
export { CreateExamPageContainer, ExamPreviewDialog, ExamPreviewMode, ExamQuestionsPageContainer, ExamTakingPageContainer, ExamViewDialog, StudentContestRoomsPageContainer, StudentExamEntryPageContainer } from './components/exams';
|
|
11
11
|
export type { CreateExamPageContainerProps, CreateExamTexts, ExamPreviewDialogProps, ExamQuestionsPageContainerProps, ExamTakingPageContainerProps, ExamViewDialogExam, ExamViewDialogProps, StudentContestRoomsPageContainerProps, StudentExamEntryPageContainerProps } from './components/exams';
|
|
12
12
|
export { ResultReviewPageContainer } from './components/results';
|
|
13
13
|
export type { ResultReviewPageContainerProps } from './components/results';
|
package/dist/index.js
CHANGED
|
@@ -10,7 +10,7 @@ export * as Results from './components/results';
|
|
|
10
10
|
export * as Themes from './components/themes';
|
|
11
11
|
export * as Shared from './shared';
|
|
12
12
|
export { buildExamTemplatePayload, createContestRoomsApi, createExamCreateApi, createExamEntryApi, createExamQuestionsApi, createExamQuestionsFetchApi, createExamTakingApi, createMockContestRoomsApi, createMockCreateExamApi, createMockExamEntryApi, createMockExamQuestionsApi, createMockExamTakingApi, createMockResultReviewApi, createResultReviewApi } from './api';
|
|
13
|
-
export { CreateExamPageContainer, ExamPreviewDialog, ExamQuestionsPageContainer, ExamTakingPageContainer, ExamViewDialog, StudentContestRoomsPageContainer, StudentExamEntryPageContainer } from './components/exams';
|
|
13
|
+
export { CreateExamPageContainer, ExamPreviewDialog, ExamPreviewMode, ExamQuestionsPageContainer, ExamTakingPageContainer, ExamViewDialog, StudentContestRoomsPageContainer, StudentExamEntryPageContainer } from './components/exams';
|
|
14
14
|
export { ResultReviewPageContainer } from './components/results';
|
|
15
15
|
export { QuestionCreator } from './components/questions/creator/QuestionCreator';
|
|
16
16
|
export { QuestionGroupCreator } from './components/questions/creator/QuestionGroupCreator';
|
|
@@ -48,6 +48,7 @@ export const QUESTION_TYPE_SKILLS = {
|
|
|
48
48
|
'FILL_MISSING_WORDS_IN_GRID': SkillEnum.READING,
|
|
49
49
|
'READ_PASSAGE_AND_ANSWER_QUESTIONS': SkillEnum.READING,
|
|
50
50
|
'ANSWER_THE_QUESTION': SkillEnum.READING,
|
|
51
|
+
'ANSWER_THE_QUESTION_GROUP': SkillEnum.READING,
|
|
51
52
|
'WORD_ORDERING': SkillEnum.READING,
|
|
52
53
|
'WORD_FILL_PARAGRAPH': SkillEnum.READING,
|
|
53
54
|
'MATCH_BY_WRITING_ANSWER': SkillEnum.READING,
|
|
@@ -198,6 +199,7 @@ export const QUESTION_TYPE_LABELS = {
|
|
|
198
199
|
'READING_PASSAGE_FILL': 'Đọc - Điền đoạn văn',
|
|
199
200
|
'FILL_BLANK': 'Điền chỗ trống',
|
|
200
201
|
'ANSWER_THE_QUESTION': 'Nghe - Đọc câu hỏi và viết đáp án',
|
|
202
|
+
'ANSWER_THE_QUESTION_GROUP': 'Đọc và trả lời câu hỏi (AI)',
|
|
201
203
|
'WORD_ORDERING': 'Sắp xếp từ',
|
|
202
204
|
'WORD_FILL_PARAGRAPH': 'Điền từ vào đoạn văn',
|
|
203
205
|
'MATCH_BY_WRITING_ANSWER': 'Ghép đáp án bằng cách viết',
|
|
@@ -1477,6 +1477,9 @@ declare const admin: {
|
|
|
1477
1477
|
readonly 'admin.exams.template.loading': "Loading exam information...";
|
|
1478
1478
|
readonly 'admin.exams.template.loadingFallback': "Loading...";
|
|
1479
1479
|
readonly 'admin.exams.preview.title': "Preview Exam: {name}";
|
|
1480
|
+
readonly 'admin.exams.preview.mode.label': "Preview mode:";
|
|
1481
|
+
readonly 'admin.exams.preview.mode.student': "Student View";
|
|
1482
|
+
readonly 'admin.exams.preview.mode.answers': "Answer Key";
|
|
1480
1483
|
readonly 'admin.exams.preview.examInfo': "Exam Information";
|
|
1481
1484
|
readonly 'admin.exams.preview.duration': "Duration:";
|
|
1482
1485
|
readonly 'admin.exams.preview.questionCount': "Questions:";
|
|
@@ -1503,6 +1506,8 @@ declare const admin: {
|
|
|
1503
1506
|
readonly 'admin.exams.preview.next': "Next";
|
|
1504
1507
|
readonly 'admin.exams.preview.questionLabel': "Question {number}";
|
|
1505
1508
|
readonly 'admin.exams.preview.footerNote': "This is a preview mode to check the layout and content of the exam.";
|
|
1509
|
+
readonly 'admin.exams.preview.footerNoteStudent': "Preview mode: Student test taking (You can try answering questions).";
|
|
1510
|
+
readonly 'admin.exams.preview.footerNoteAnswers': "Preview mode: Showing correct answers and explanations.";
|
|
1506
1511
|
readonly 'admin.exams.preview.close': "Close";
|
|
1507
1512
|
readonly 'admin.exams.preview.loadError': "Unable to load questions. Please try again.";
|
|
1508
1513
|
readonly 'admin.exams.preview.points': "{count} pts";
|
|
@@ -1624,6 +1624,9 @@ const admin = {
|
|
|
1624
1624
|
'admin.exams.template.loadingFallback': 'Loading...',
|
|
1625
1625
|
// Preview dialog
|
|
1626
1626
|
'admin.exams.preview.title': 'Preview Exam: {name}',
|
|
1627
|
+
'admin.exams.preview.mode.label': 'Preview mode:',
|
|
1628
|
+
'admin.exams.preview.mode.student': 'Student View',
|
|
1629
|
+
'admin.exams.preview.mode.answers': 'Answer Key',
|
|
1627
1630
|
'admin.exams.preview.examInfo': 'Exam Information',
|
|
1628
1631
|
'admin.exams.preview.duration': 'Duration:',
|
|
1629
1632
|
'admin.exams.preview.questionCount': 'Questions:',
|
|
@@ -1650,6 +1653,8 @@ const admin = {
|
|
|
1650
1653
|
'admin.exams.preview.next': 'Next',
|
|
1651
1654
|
'admin.exams.preview.questionLabel': 'Question {number}',
|
|
1652
1655
|
'admin.exams.preview.footerNote': 'This is a preview mode to check the layout and content of the exam.',
|
|
1656
|
+
'admin.exams.preview.footerNoteStudent': 'Preview mode: Student test taking (You can try answering questions).',
|
|
1657
|
+
'admin.exams.preview.footerNoteAnswers': 'Preview mode: Showing correct answers and explanations.',
|
|
1653
1658
|
'admin.exams.preview.close': 'Close',
|
|
1654
1659
|
'admin.exams.preview.loadError': 'Unable to load questions. Please try again.',
|
|
1655
1660
|
'admin.exams.preview.points': '{count} pts',
|
|
@@ -1634,6 +1634,9 @@ declare const en: {
|
|
|
1634
1634
|
readonly 'admin.exams.template.loading': "Loading exam information...";
|
|
1635
1635
|
readonly 'admin.exams.template.loadingFallback': "Loading...";
|
|
1636
1636
|
readonly 'admin.exams.preview.title': "Preview Exam: {name}";
|
|
1637
|
+
readonly 'admin.exams.preview.mode.label': "Preview mode:";
|
|
1638
|
+
readonly 'admin.exams.preview.mode.student': "Student View";
|
|
1639
|
+
readonly 'admin.exams.preview.mode.answers': "Answer Key";
|
|
1637
1640
|
readonly 'admin.exams.preview.examInfo': "Exam Information";
|
|
1638
1641
|
readonly 'admin.exams.preview.duration': "Duration:";
|
|
1639
1642
|
readonly 'admin.exams.preview.questionCount': "Questions:";
|
|
@@ -1660,6 +1663,8 @@ declare const en: {
|
|
|
1660
1663
|
readonly 'admin.exams.preview.next': "Next";
|
|
1661
1664
|
readonly 'admin.exams.preview.questionLabel': "Question {number}";
|
|
1662
1665
|
readonly 'admin.exams.preview.footerNote': "This is a preview mode to check the layout and content of the exam.";
|
|
1666
|
+
readonly 'admin.exams.preview.footerNoteStudent': "Preview mode: Student test taking (You can try answering questions).";
|
|
1667
|
+
readonly 'admin.exams.preview.footerNoteAnswers': "Preview mode: Showing correct answers and explanations.";
|
|
1663
1668
|
readonly 'admin.exams.preview.close': "Close";
|
|
1664
1669
|
readonly 'admin.exams.preview.loadError': "Unable to load questions. Please try again.";
|
|
1665
1670
|
readonly 'admin.exams.preview.points': "{count} pts";
|
|
@@ -1477,6 +1477,9 @@ declare const admin: {
|
|
|
1477
1477
|
readonly 'admin.exams.template.loading': "Đang tải thông tin đề thi...";
|
|
1478
1478
|
readonly 'admin.exams.template.loadingFallback': "Đang tải...";
|
|
1479
1479
|
readonly 'admin.exams.preview.title': "Xem trước đề thi: {name}";
|
|
1480
|
+
readonly 'admin.exams.preview.mode.label': "Chế độ xem:";
|
|
1481
|
+
readonly 'admin.exams.preview.mode.student': "Học sinh làm bài";
|
|
1482
|
+
readonly 'admin.exams.preview.mode.answers': "Xem đáp án đúng";
|
|
1480
1483
|
readonly 'admin.exams.preview.examInfo': "Thông tin đề thi";
|
|
1481
1484
|
readonly 'admin.exams.preview.duration': "Thời gian:";
|
|
1482
1485
|
readonly 'admin.exams.preview.questionCount': "Số câu hỏi:";
|
|
@@ -1503,6 +1506,8 @@ declare const admin: {
|
|
|
1503
1506
|
readonly 'admin.exams.preview.next': "Tiếp";
|
|
1504
1507
|
readonly 'admin.exams.preview.questionLabel': "Câu {number}";
|
|
1505
1508
|
readonly 'admin.exams.preview.footerNote': "Đây là chế độ xem trước để kiểm tra bố cục và nội dung đề thi.";
|
|
1509
|
+
readonly 'admin.exams.preview.footerNoteStudent': "Chế độ xem trước: Học sinh làm bài (Có thể thao tác thử trên câu hỏi).";
|
|
1510
|
+
readonly 'admin.exams.preview.footerNoteAnswers': "Chế độ xem trước: Đang hiển thị đáp án đúng và hướng dẫn giải.";
|
|
1506
1511
|
readonly 'admin.exams.preview.close': "Đóng";
|
|
1507
1512
|
readonly 'admin.exams.preview.loadError': "Không thể tải câu hỏi. Vui lòng thử lại.";
|
|
1508
1513
|
readonly 'admin.exams.preview.points': "{count} điểm";
|
|
@@ -1624,6 +1624,9 @@ const admin = {
|
|
|
1624
1624
|
'admin.exams.template.loadingFallback': 'Đang tải...',
|
|
1625
1625
|
// Preview dialog
|
|
1626
1626
|
'admin.exams.preview.title': 'Xem trước đề thi: {name}',
|
|
1627
|
+
'admin.exams.preview.mode.label': 'Chế độ xem:',
|
|
1628
|
+
'admin.exams.preview.mode.student': 'Học sinh làm bài',
|
|
1629
|
+
'admin.exams.preview.mode.answers': 'Xem đáp án đúng',
|
|
1627
1630
|
'admin.exams.preview.examInfo': 'Thông tin đề thi',
|
|
1628
1631
|
'admin.exams.preview.duration': 'Thời gian:',
|
|
1629
1632
|
'admin.exams.preview.questionCount': 'Số câu hỏi:',
|
|
@@ -1650,6 +1653,8 @@ const admin = {
|
|
|
1650
1653
|
'admin.exams.preview.next': 'Tiếp',
|
|
1651
1654
|
'admin.exams.preview.questionLabel': 'Câu {number}',
|
|
1652
1655
|
'admin.exams.preview.footerNote': 'Đây là chế độ xem trước để kiểm tra bố cục và nội dung đề thi.',
|
|
1656
|
+
'admin.exams.preview.footerNoteStudent': 'Chế độ xem trước: Học sinh làm bài (Có thể thao tác thử trên câu hỏi).',
|
|
1657
|
+
'admin.exams.preview.footerNoteAnswers': 'Chế độ xem trước: Đang hiển thị đáp án đúng và hướng dẫn giải.',
|
|
1653
1658
|
'admin.exams.preview.close': 'Đóng',
|
|
1654
1659
|
'admin.exams.preview.loadError': 'Không thể tải câu hỏi. Vui lòng thử lại.',
|
|
1655
1660
|
'admin.exams.preview.points': '{count} điểm',
|
|
@@ -1634,6 +1634,9 @@ declare const vi: {
|
|
|
1634
1634
|
readonly 'admin.exams.template.loading': "Đang tải thông tin đề thi...";
|
|
1635
1635
|
readonly 'admin.exams.template.loadingFallback': "Đang tải...";
|
|
1636
1636
|
readonly 'admin.exams.preview.title': "Xem trước đề thi: {name}";
|
|
1637
|
+
readonly 'admin.exams.preview.mode.label': "Chế độ xem:";
|
|
1638
|
+
readonly 'admin.exams.preview.mode.student': "Học sinh làm bài";
|
|
1639
|
+
readonly 'admin.exams.preview.mode.answers': "Xem đáp án đúng";
|
|
1637
1640
|
readonly 'admin.exams.preview.examInfo': "Thông tin đề thi";
|
|
1638
1641
|
readonly 'admin.exams.preview.duration': "Thời gian:";
|
|
1639
1642
|
readonly 'admin.exams.preview.questionCount': "Số câu hỏi:";
|
|
@@ -1660,6 +1663,8 @@ declare const vi: {
|
|
|
1660
1663
|
readonly 'admin.exams.preview.next': "Tiếp";
|
|
1661
1664
|
readonly 'admin.exams.preview.questionLabel': "Câu {number}";
|
|
1662
1665
|
readonly 'admin.exams.preview.footerNote': "Đây là chế độ xem trước để kiểm tra bố cục và nội dung đề thi.";
|
|
1666
|
+
readonly 'admin.exams.preview.footerNoteStudent': "Chế độ xem trước: Học sinh làm bài (Có thể thao tác thử trên câu hỏi).";
|
|
1667
|
+
readonly 'admin.exams.preview.footerNoteAnswers': "Chế độ xem trước: Đang hiển thị đáp án đúng và hướng dẫn giải.";
|
|
1663
1668
|
readonly 'admin.exams.preview.close': "Đóng";
|
|
1664
1669
|
readonly 'admin.exams.preview.loadError': "Không thể tải câu hỏi. Vui lòng thử lại.";
|
|
1665
1670
|
readonly 'admin.exams.preview.points': "{count} điểm";
|