@tinyweb_dev/oe-exam-sdk 1.0.7 → 1.0.9
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/take/components/question-renderers/ChooseThenAnswerGroupRenderer.js +1 -1
- package/dist/components/questions/_shared/types/answer-the-question-group.type.d.ts +3 -0
- package/dist/components/questions/_shared/types/choose-the-correct-answer-group.type.d.ts +2 -2
- package/dist/components/questions/_shared/types/choose-the-correct-answer-group.type.js +5 -1
- package/dist/components/questions/_shared/types/choose-then-answer-group.type.d.ts +17 -0
- package/dist/components/questions/_shared/types/cross-out-word-group.type.d.ts +1 -0
- package/dist/components/questions/_shared/types/true-false-correct-group.type.d.ts +1 -0
- package/dist/components/questions/_shared/types/true-false-group.type.d.ts +1 -0
- package/dist/components/questions/types/answer-the-question-group/AnswerTheQuestionGroupClient.d.ts +1 -1
- package/dist/components/questions/types/answer-the-question-group/AnswerTheQuestionGroupClient.js +13 -15
- package/dist/components/questions/types/answer-the-question-group/AnswerTheQuestionGroupCreator.js +2 -0
- package/dist/components/questions/types/answer-the-question-group/map-answer-the-question-group-data.js +2 -0
- package/dist/components/questions/types/answer-the-question-group/transform.js +12 -5
- package/dist/components/questions/types/choose-the-correct-answer-group/ChooseAnswerGroupImageUpload.d.ts +7 -0
- package/dist/components/questions/types/choose-the-correct-answer-group/ChooseAnswerGroupImageUpload.js +4 -1
- package/dist/components/questions/types/choose-the-correct-answer-group/ChooseTheCorrectAnswerGroupClient.js +24 -22
- package/dist/components/questions/types/choose-the-correct-answer-group/ChooseTheCorrectAnswerGroupCreator.js +45 -58
- package/dist/components/questions/types/choose-the-correct-answer-group/ChooseTheCorrectAnswerGroupItemEditor.d.ts +6 -0
- package/dist/components/questions/types/choose-the-correct-answer-group/ChooseTheCorrectAnswerGroupItemEditor.js +145 -0
- package/dist/components/questions/types/choose-the-correct-answer-group/map-choose-the-correct-answer-group-data.js +6 -15
- package/dist/components/questions/types/choose-the-correct-answer-group/transform.js +5 -4
- package/dist/components/questions/types/choose-then-answer-group/ChooseThenAnswerGroupClient.d.ts +1 -1
- package/dist/components/questions/types/choose-then-answer-group/ChooseThenAnswerGroupClient.js +75 -11
- package/dist/components/questions/types/choose-then-answer-group/ChooseThenAnswerGroupCreator.js +54 -6
- package/dist/components/questions/types/choose-then-answer-group/map-choose-then-answer-group-data.d.ts +4 -1
- package/dist/components/questions/types/choose-then-answer-group/map-choose-then-answer-group-data.js +39 -2
- package/dist/components/questions/types/choose-then-answer-group/transform.js +27 -14
- package/dist/components/questions/types/cross-out-word-group/CrossOutWordGroupClient.d.ts +1 -1
- package/dist/components/questions/types/cross-out-word-group/CrossOutWordGroupClient.js +54 -3
- package/dist/components/questions/types/crossword-puzzle/CrosswordPuzzleClient.d.ts +1 -1
- package/dist/components/questions/types/crossword-puzzle/CrosswordPuzzleClient.js +15 -5
- package/dist/components/questions/types/find-words-in-matrix/FindWordsInMatrixClient.d.ts +12 -2
- package/dist/components/questions/types/find-words-in-matrix/FindWordsInMatrixClient.js +144 -45
- package/dist/components/questions/types/spontaneous-qa-group/SpontaneousQaGroupClient.d.ts +1 -1
- package/dist/components/questions/types/spontaneous-qa-group/SpontaneousQaGroupClient.js +79 -3
- package/dist/components/questions/types/spontaneous-qa-group/index.d.ts +1 -0
- package/dist/components/questions/types/spontaneous-qa-group/index.js +1 -0
- package/dist/components/questions/types/spontaneous-qa-group/map-spontaneous-qa-group-data.d.ts +2 -0
- package/dist/components/questions/types/spontaneous-qa-group/map-spontaneous-qa-group-data.js +74 -0
- package/dist/components/questions/types/true-false-correct-group/TrueFalseCorrectGroupClient.d.ts +1 -1
- package/dist/components/questions/types/true-false-correct-group/TrueFalseCorrectGroupClient.js +26 -4
- package/dist/components/questions/types/true-false-group/TrueFalseGroupClient.d.ts +1 -1
- package/dist/components/questions/types/true-false-group/TrueFalseGroupClient.js +13 -3
- package/dist/components/questions/viewer/AnswerTheQuestionGroupViewer.d.ts +3 -2
- package/dist/components/questions/viewer/AnswerTheQuestionGroupViewer.js +12 -5
- package/dist/components/questions/viewer/ChooseThenAnswerGroupViewer.d.ts +3 -2
- package/dist/components/questions/viewer/ChooseThenAnswerGroupViewer.js +4 -3
- package/dist/components/questions/viewer/CrossOutWordGroupViewer.d.ts +3 -1
- package/dist/components/questions/viewer/CrossOutWordGroupViewer.js +15 -2
- package/dist/components/questions/viewer/QuestionViewer.d.ts +9 -1
- package/dist/components/questions/viewer/QuestionViewer.js +22 -9
- package/dist/components/questions/viewer/SpontaneousQaGroupViewer.js +4 -79
- package/dist/components/questions/viewer/TrueFalseCorrectGroupViewer.d.ts +3 -2
- package/dist/components/questions/viewer/TrueFalseCorrectGroupViewer.js +14 -4
- package/dist/components/questions/viewer/TrueFalseGroupViewer.d.ts +5 -2
- package/dist/components/questions/viewer/TrueFalseGroupViewer.js +14 -4
- package/dist/components/questions/viewer/WordOrderAndMatchGroupViewer.d.ts +6 -2
- package/dist/components/questions/viewer/WordOrderAndMatchGroupViewer.js +26 -6
- package/dist/components/results/renderers/ReviewAnswerTheQuestionGroupRenderer.js +1 -1
- package/dist/shared/lib/utils/question-reverse-transform.js +12 -4
- package/dist/shared/types/questions/answer-the-question-group.d.ts +8 -3
- package/dist/shared/types/questions/answer-the-question-group.js +5 -0
- package/dist/shared/types/questions/choose-the-correct-answer-group.d.ts +1 -1
- package/dist/shared/types/questions/choose-then-answer-group.d.ts +3 -0
- package/package.json +1 -1
|
@@ -2,7 +2,20 @@
|
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
3
|
import { CrossOutWordGroupClient } from '../types/cross-out-word-group/CrossOutWordGroupClient';
|
|
4
4
|
import { mapQuestionToCrossOutWordGroupData } from '../types/cross-out-word-group/map-cross-out-word-group-data';
|
|
5
|
-
|
|
5
|
+
function readCrossOutAnswers(value) {
|
|
6
|
+
if (Array.isArray(value)) {
|
|
7
|
+
return value;
|
|
8
|
+
}
|
|
9
|
+
if (value && typeof value === 'object' && Array.isArray(value.answer)) {
|
|
10
|
+
return value.answer;
|
|
11
|
+
}
|
|
12
|
+
return undefined;
|
|
13
|
+
}
|
|
14
|
+
export function CrossOutWordGroupViewer({ question, isReviewMode = false, userAnswer, onSubmitAnswer, }) {
|
|
6
15
|
const questionData = mapQuestionToCrossOutWordGroupData(question);
|
|
7
|
-
|
|
16
|
+
const storedAnswers = readCrossOutAnswers(userAnswer);
|
|
17
|
+
const userAnswers = isReviewMode && !storedAnswers
|
|
18
|
+
? questionData.items.map((item) => item.correctAnswer)
|
|
19
|
+
: storedAnswers;
|
|
20
|
+
return (_jsx(CrossOutWordGroupClient, { questionData: questionData, isReviewMode: isReviewMode, userAnswers: userAnswers, onAnswerChange: isReviewMode ? undefined : onSubmitAnswer }));
|
|
8
21
|
}
|
|
@@ -4,6 +4,14 @@ interface QuestionViewerProps {
|
|
|
4
4
|
onSubmitAnswer?: (answer: any) => void;
|
|
5
5
|
isReviewMode?: boolean;
|
|
6
6
|
userAnswer?: any;
|
|
7
|
+
gradeDetails?: Array<{
|
|
8
|
+
itemId: string;
|
|
9
|
+
isCorrect?: boolean;
|
|
10
|
+
userAnswer?: unknown;
|
|
11
|
+
correctAnswer?: unknown;
|
|
12
|
+
partialScore?: number;
|
|
13
|
+
feedback?: string;
|
|
14
|
+
}>;
|
|
7
15
|
}
|
|
8
|
-
export declare function QuestionViewer({ question, onSubmitAnswer, isReviewMode, userAnswer, }: QuestionViewerProps): import("react").JSX.Element;
|
|
16
|
+
export declare function QuestionViewer({ question, onSubmitAnswer, isReviewMode, userAnswer, gradeDetails, }: QuestionViewerProps): import("react").JSX.Element;
|
|
9
17
|
export {};
|
|
@@ -38,7 +38,7 @@ import { MatchWordToPictureViewer } from './MatchWordToPictureViewer';
|
|
|
38
38
|
import { MatchWordToPictureGroupViewer } from './MatchWordToPictureGroupViewer';
|
|
39
39
|
import { MatchColumnsToMakeSentencesViewer } from './MatchColumnsToMakeSentencesViewer';
|
|
40
40
|
import { WordOrderingGroupViewer } from './WordOrderingGroupViewer';
|
|
41
|
-
export function QuestionViewer({ question, onSubmitAnswer, isReviewMode = false, userAnswer, }) {
|
|
41
|
+
export function QuestionViewer({ question, onSubmitAnswer, isReviewMode = false, userAnswer, gradeDetails, }) {
|
|
42
42
|
const [selectedAnswer, setSelectedAnswer] = useState(userAnswer || null);
|
|
43
43
|
const [playCount, setPlayCount] = useState(0);
|
|
44
44
|
const audioRef = useRef(null);
|
|
@@ -477,6 +477,19 @@ export function QuestionViewer({ question, onSubmitAnswer, isReviewMode = false,
|
|
|
477
477
|
}
|
|
478
478
|
return (_jsxs("div", { className: "space-y-3", children: [content.title ? _jsx("h3", { className: "text-center text-lg font-bold uppercase text-slate-800", children: content.title }) : null, content.imageUrl ? (_jsx("div", { className: "flex justify-center", children: _jsx("img", { src: String(content.imageUrl), alt: "", className: "max-h-[180px] rounded-lg object-contain" }) })) : null, _jsx("div", { className: "space-y-2 text-base leading-relaxed text-slate-800", children: paragraph.split('\n').map((line, li) => (_jsx("div", { className: "flex flex-wrap items-baseline", children: renderLine(line, `wfp-${li}`) }, `wfp-l${li}`))) }), isReviewMode && correctAnswers.length > 0 ? (_jsxs("div", { className: "rounded-lg border border-emerald-200 bg-emerald-50/80 p-2.5 text-sm text-emerald-800", children: [_jsx("span", { className: "font-semibold", children: "\u0110\u00E1p \u00E1n: " }), correctAnswers.map((a, i) => (_jsxs("span", { className: "mr-2", children: ["(", i + 1, ") ", _jsx("span", { className: "font-bold underline", children: a || '—' })] }, `wfp-a${i}`)))] })) : null] }));
|
|
479
479
|
};
|
|
480
|
+
const renderCrosswordPuzzle = () => {
|
|
481
|
+
const raw = userAnswer && typeof userAnswer === 'object' && !Array.isArray(userAnswer)
|
|
482
|
+
? userAnswer
|
|
483
|
+
: {};
|
|
484
|
+
const answers = raw.answers && typeof raw.answers === 'object' && !Array.isArray(raw.answers)
|
|
485
|
+
? raw.answers
|
|
486
|
+
: raw;
|
|
487
|
+
return (_jsx(CrosswordPuzzleClient, { content: question.content, correctAnswer: question.correctAnswer ?? question.answer, isReviewMode: isReviewMode, userAnswers: answers, onAnswerChange: isReviewMode
|
|
488
|
+
? undefined
|
|
489
|
+
: (key, value) => {
|
|
490
|
+
onSubmitAnswer?.({ answers: { ...answers, [key]: value } });
|
|
491
|
+
} }));
|
|
492
|
+
};
|
|
480
493
|
const renderQuestion = () => {
|
|
481
494
|
const resolvedType = String(question.type ||
|
|
482
495
|
question.questionType ||
|
|
@@ -488,9 +501,9 @@ export function QuestionViewer({ question, onSubmitAnswer, isReviewMode = false,
|
|
|
488
501
|
case 'CHOOSE_THE_CORRECT_ANSWER_GROUP':
|
|
489
502
|
return (_jsx(ChooseTheCorrectAnswerGroupViewer, { question: question, isReviewMode: isReviewMode, userAnswer: userAnswer, onSubmitAnswer: onSubmitAnswer }));
|
|
490
503
|
case 'TRUE_FALSE_GROUP':
|
|
491
|
-
return (_jsx(TrueFalseGroupViewer, { question: question, isReviewMode: isReviewMode, userAnswer: userAnswer }));
|
|
504
|
+
return (_jsx(TrueFalseGroupViewer, { question: question, isReviewMode: isReviewMode, userAnswer: userAnswer, onSubmitAnswer: onSubmitAnswer }));
|
|
492
505
|
case 'TRUE_FALSE_CORRECT_GROUP':
|
|
493
|
-
return (_jsx(TrueFalseCorrectGroupViewer, { question: question, isReviewMode: isReviewMode, userAnswer: userAnswer }));
|
|
506
|
+
return (_jsx(TrueFalseCorrectGroupViewer, { question: question, isReviewMode: isReviewMode, userAnswer: userAnswer, onSubmitAnswer: onSubmitAnswer }));
|
|
494
507
|
case 'FILL_IN_BLANK_GROUP':
|
|
495
508
|
return (_jsx(FillInBlankGroupViewer, { question: question, isReviewMode: isReviewMode, userAnswer: userAnswer, onSubmitAnswer: onSubmitAnswer }));
|
|
496
509
|
case 'MATCHING_WITH_LINES_GROUP':
|
|
@@ -498,11 +511,11 @@ export function QuestionViewer({ question, onSubmitAnswer, isReviewMode = false,
|
|
|
498
511
|
case 'MATCH_COLUMNS_TO_MAKE_SENTENCES':
|
|
499
512
|
return (_jsx(MatchColumnsToMakeSentencesViewer, { question: question, isReviewMode: isReviewMode, userAnswer: userAnswer, onSubmitAnswer: onSubmitAnswer }));
|
|
500
513
|
case 'CROSS_OUT_WORD_GROUP':
|
|
501
|
-
return (_jsx(CrossOutWordGroupViewer, { question: question, isReviewMode: isReviewMode }));
|
|
514
|
+
return (_jsx(CrossOutWordGroupViewer, { question: question, isReviewMode: isReviewMode, userAnswer: userAnswer, onSubmitAnswer: onSubmitAnswer }));
|
|
502
515
|
case 'ANSWER_THE_QUESTION':
|
|
503
516
|
return (_jsx(AnswerTheQuestionViewer, { question: question, isReviewMode: isReviewMode, userAnswer: typeof userAnswer === 'string' ? userAnswer : undefined, onSubmitAnswer: onSubmitAnswer }));
|
|
504
517
|
case 'ANSWER_THE_QUESTION_GROUP':
|
|
505
|
-
return (_jsx(AnswerTheQuestionGroupViewer, { question: question, isReviewMode: isReviewMode, userAnswer: userAnswer }));
|
|
518
|
+
return (_jsx(AnswerTheQuestionGroupViewer, { question: question, isReviewMode: isReviewMode, userAnswer: userAnswer, onSubmitAnswer: onSubmitAnswer }));
|
|
506
519
|
case 'SPEAKING_CUE_CARD':
|
|
507
520
|
return (_jsx(SpeakingCueCardClient, { questionData: mapQuestionToSpeakingCueCardData(question), isReviewMode: isReviewMode }));
|
|
508
521
|
case 'SPONTANEOUS_QA':
|
|
@@ -511,7 +524,7 @@ export function QuestionViewer({ question, onSubmitAnswer, isReviewMode = false,
|
|
|
511
524
|
return (_jsx(SpontaneousQaGroupViewer, { question: question, isReviewMode: isReviewMode }));
|
|
512
525
|
case 'WORD_ORDER_AND_MATCH_GROUP':
|
|
513
526
|
case 'WORD_ORDER_AND_MATCH':
|
|
514
|
-
return (_jsx(WordOrderAndMatchGroupViewer, { question: question, isReviewMode: isReviewMode, userAnswer: userAnswer }));
|
|
527
|
+
return (_jsx(WordOrderAndMatchGroupViewer, { question: question, isReviewMode: isReviewMode, userAnswer: userAnswer, onSubmitAnswer: onSubmitAnswer }));
|
|
515
528
|
case 'WORD_ORDERING_GROUP':
|
|
516
529
|
return (_jsx(WordOrderingGroupViewer, { question: question, isReviewMode: isReviewMode, userAnswer: userAnswer, onSubmitAnswer: onSubmitAnswer }));
|
|
517
530
|
case 'MATCH_WORD_TO_PICTURE_GROUP':
|
|
@@ -551,13 +564,13 @@ export function QuestionViewer({ question, onSubmitAnswer, isReviewMode = false,
|
|
|
551
564
|
case 'WORD_FILL_STRUCTURED_FORM':
|
|
552
565
|
return (_jsx(WordFillStructuredFormViewer, { question: question, isReviewMode: isReviewMode, userAnswer: userAnswer, onSubmitAnswer: onSubmitAnswer }));
|
|
553
566
|
case 'CROSSWORD_PUZZLE':
|
|
554
|
-
return (
|
|
567
|
+
return renderCrosswordPuzzle();
|
|
555
568
|
case 'FIND_WORDS_IN_MATRIX':
|
|
556
|
-
return (_jsx(FindWordsInMatrixClient, { content: question.content, correctAnswer: question.answer, showSolution: isReviewMode }));
|
|
569
|
+
return (_jsx(FindWordsInMatrixClient, { content: question.content, correctAnswer: question.correctAnswer ?? question.answer, showSolution: isReviewMode, userAnswer: userAnswer, onAnswerChange: isReviewMode ? undefined : onSubmitAnswer }));
|
|
557
570
|
case 'SORT_WORDS_INTO_CATEGORIES':
|
|
558
571
|
return (_jsx(SortWordsIntoCategoriesViewer, { question: question, isReviewMode: isReviewMode, userAnswer: userAnswer, onSubmitAnswer: onSubmitAnswer }));
|
|
559
572
|
case 'CHOOSE_THEN_ANSWER_GROUP':
|
|
560
|
-
return (_jsx(ChooseThenAnswerGroupViewer, { question: question, isReviewMode: isReviewMode, userAnswer: userAnswer, onSubmitAnswer: onSubmitAnswer }));
|
|
573
|
+
return (_jsx(ChooseThenAnswerGroupViewer, { question: question, isReviewMode: isReviewMode, userAnswer: userAnswer, gradeDetails: gradeDetails, onSubmitAnswer: onSubmitAnswer }));
|
|
561
574
|
default:
|
|
562
575
|
return _jsxs("div", { children: ["Lo\u1EA1i c\u00E2u h\u1ECFi kh\u00F4ng \u0111\u01B0\u1EE3c h\u1ED7 tr\u1EE3: ", question.type] });
|
|
563
576
|
}
|
|
@@ -1,82 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
}
|
|
6
|
-
function asRecord(value) {
|
|
7
|
-
return isRecord(value) ? value : {};
|
|
8
|
-
}
|
|
9
|
-
function asString(value, fallback = '') {
|
|
10
|
-
return typeof value === 'string' ? value : fallback;
|
|
11
|
-
}
|
|
12
|
-
function mapItem(item, index) {
|
|
13
|
-
const record = asRecord(item);
|
|
14
|
-
const content = asRecord(record.content);
|
|
15
|
-
const correctAnswer = asRecord(record.correctAnswer);
|
|
16
|
-
const expected = Array.isArray(correctAnswer.expectedAnswers)
|
|
17
|
-
? correctAnswer.expectedAnswers.map((value) => String(value ?? ''))
|
|
18
|
-
: [];
|
|
19
|
-
return {
|
|
20
|
-
questionText: asString(content.questionText) ||
|
|
21
|
-
asString(content.question) ||
|
|
22
|
-
asString(record.questionText),
|
|
23
|
-
audioUrl: asString(content.audioUrl) || undefined,
|
|
24
|
-
expectedAnswers: expected,
|
|
25
|
-
isExample: record.isExample === true,
|
|
26
|
-
points: typeof record.points === 'number' ? record.points : 1,
|
|
27
|
-
questionNumber: typeof record.questionNumber === 'number' ? record.questionNumber : index + 1,
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
function isNestedPart(item) {
|
|
31
|
-
const record = asRecord(item);
|
|
32
|
-
return Array.isArray(record.questions);
|
|
33
|
-
}
|
|
34
|
-
function resolveGroup(question) {
|
|
35
|
-
const content = asRecord(question.content);
|
|
36
|
-
const list = asRecord(question.spontaneousQAList ?? content.spontaneousQAList ?? content);
|
|
37
|
-
const itemsRaw = Array.isArray(list.items)
|
|
38
|
-
? list.items
|
|
39
|
-
: Array.isArray(content.items)
|
|
40
|
-
? content.items
|
|
41
|
-
: [];
|
|
42
|
-
const instruction = asString(asRecord(list.meta).instruction) ||
|
|
43
|
-
asString(asRecord(content.meta).instruction) ||
|
|
44
|
-
asString(question.explanation) ||
|
|
45
|
-
'Talk about the topic.';
|
|
46
|
-
if (itemsRaw.some(isNestedPart)) {
|
|
47
|
-
return {
|
|
48
|
-
instruction,
|
|
49
|
-
parts: itemsRaw.map((item, index) => {
|
|
50
|
-
const record = asRecord(item);
|
|
51
|
-
const questions = Array.isArray(record.questions)
|
|
52
|
-
? record.questions.map(mapItem)
|
|
53
|
-
: [];
|
|
54
|
-
return {
|
|
55
|
-
partNumber: typeof record.partNumber === 'number' ? record.partNumber : index + 1,
|
|
56
|
-
partTitle: asString(record.partTitle) || `PART ${index + 1}`,
|
|
57
|
-
topic: asString(record.topic),
|
|
58
|
-
questions,
|
|
59
|
-
};
|
|
60
|
-
}),
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
return {
|
|
64
|
-
instruction,
|
|
65
|
-
parts: [
|
|
66
|
-
{
|
|
67
|
-
partNumber: 1,
|
|
68
|
-
partTitle: '',
|
|
69
|
-
topic: '',
|
|
70
|
-
questions: itemsRaw.map(mapItem),
|
|
71
|
-
},
|
|
72
|
-
],
|
|
73
|
-
};
|
|
74
|
-
}
|
|
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
|
-
}
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { SpontaneousQaGroupClient } from '../types/spontaneous-qa-group/SpontaneousQaGroupClient';
|
|
4
|
+
import { mapQuestionToSpontaneousQaGroupData } from '../types/spontaneous-qa-group/map-spontaneous-qa-group-data';
|
|
78
5
|
export function SpontaneousQaGroupViewer({ question, isReviewMode = false, }) {
|
|
79
|
-
|
|
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, isReviewMode: isReviewMode }, `${part.partNumber}-${item.questionNumber}-${index}`))) })] }, `part-${part.partNumber}`)))] }));
|
|
6
|
+
return (_jsx(SpontaneousQaGroupClient, { questionData: mapQuestionToSpontaneousQaGroupData(question), isReviewMode: isReviewMode }));
|
|
82
7
|
}
|
|
@@ -3,7 +3,8 @@ import type { TrueFalseCorrectStudentItemAnswer } from '../_shared/types/true-fa
|
|
|
3
3
|
type TrueFalseCorrectGroupViewerProps = {
|
|
4
4
|
question: Question;
|
|
5
5
|
isReviewMode?: boolean;
|
|
6
|
-
userAnswer?:
|
|
6
|
+
userAnswer?: unknown;
|
|
7
|
+
onSubmitAnswer?: (answer: TrueFalseCorrectStudentItemAnswer[]) => void;
|
|
7
8
|
};
|
|
8
|
-
export declare function TrueFalseCorrectGroupViewer({ question, isReviewMode, userAnswer, }: TrueFalseCorrectGroupViewerProps): import("react").JSX.Element;
|
|
9
|
+
export declare function TrueFalseCorrectGroupViewer({ question, isReviewMode, userAnswer, onSubmitAnswer, }: TrueFalseCorrectGroupViewerProps): import("react").JSX.Element;
|
|
9
10
|
export {};
|
|
@@ -2,13 +2,23 @@
|
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
3
|
import { TrueFalseCorrectGroupClient } from '../types/true-false-correct-group/TrueFalseCorrectGroupClient';
|
|
4
4
|
import { mapQuestionToTrueFalseCorrectGroupData } from '../types/true-false-correct-group/map-true-false-correct-group-data';
|
|
5
|
-
|
|
5
|
+
function readStudentAnswers(value) {
|
|
6
|
+
if (Array.isArray(value)) {
|
|
7
|
+
return value;
|
|
8
|
+
}
|
|
9
|
+
if (value && typeof value === 'object' && Array.isArray(value.answer)) {
|
|
10
|
+
return value.answer;
|
|
11
|
+
}
|
|
12
|
+
return undefined;
|
|
13
|
+
}
|
|
14
|
+
export function TrueFalseCorrectGroupViewer({ question, isReviewMode = false, userAnswer, onSubmitAnswer, }) {
|
|
6
15
|
const questionData = mapQuestionToTrueFalseCorrectGroupData(question);
|
|
7
|
-
const
|
|
16
|
+
const storedAnswers = readStudentAnswers(userAnswer);
|
|
17
|
+
const reviewAnswers = isReviewMode && !storedAnswers
|
|
8
18
|
? questionData.items.map((item) => ({
|
|
9
19
|
isTrue: item.isTrue,
|
|
10
20
|
correction: item.correction,
|
|
11
21
|
}))
|
|
12
|
-
:
|
|
13
|
-
return (_jsx(TrueFalseCorrectGroupClient, { questionData: questionData, isReviewMode: isReviewMode, userAnswers: reviewAnswers }));
|
|
22
|
+
: storedAnswers;
|
|
23
|
+
return (_jsx(TrueFalseCorrectGroupClient, { questionData: questionData, isReviewMode: isReviewMode, userAnswers: reviewAnswers, onAnswerChange: isReviewMode ? undefined : onSubmitAnswer }));
|
|
14
24
|
}
|
|
@@ -2,7 +2,10 @@ import type { Question } from '../../../shared/types/entities/question.types';
|
|
|
2
2
|
type TrueFalseGroupViewerProps = {
|
|
3
3
|
question: Question;
|
|
4
4
|
isReviewMode?: boolean;
|
|
5
|
-
userAnswer?: boolean[]
|
|
5
|
+
userAnswer?: boolean[] | {
|
|
6
|
+
answer?: boolean[];
|
|
7
|
+
};
|
|
8
|
+
onSubmitAnswer?: (answer: boolean[]) => void;
|
|
6
9
|
};
|
|
7
|
-
export declare function TrueFalseGroupViewer({ question, isReviewMode, userAnswer, }: TrueFalseGroupViewerProps): import("react").JSX.Element;
|
|
10
|
+
export declare function TrueFalseGroupViewer({ question, isReviewMode, userAnswer, onSubmitAnswer, }: TrueFalseGroupViewerProps): import("react").JSX.Element;
|
|
8
11
|
export {};
|
|
@@ -2,10 +2,20 @@
|
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
3
|
import { TrueFalseGroupClient } from '../types/true-false-group/TrueFalseGroupClient';
|
|
4
4
|
import { mapQuestionToTrueFalseGroupData } from '../types/true-false-group/map-true-false-group-data';
|
|
5
|
-
|
|
5
|
+
function readBooleanAnswers(value) {
|
|
6
|
+
if (Array.isArray(value)) {
|
|
7
|
+
return value;
|
|
8
|
+
}
|
|
9
|
+
if (value && typeof value === 'object' && Array.isArray(value.answer)) {
|
|
10
|
+
return value.answer;
|
|
11
|
+
}
|
|
12
|
+
return undefined;
|
|
13
|
+
}
|
|
14
|
+
export function TrueFalseGroupViewer({ question, isReviewMode = false, userAnswer, onSubmitAnswer, }) {
|
|
6
15
|
const questionData = mapQuestionToTrueFalseGroupData(question);
|
|
7
|
-
const
|
|
16
|
+
const storedAnswers = readBooleanAnswers(userAnswer);
|
|
17
|
+
const reviewAnswers = isReviewMode && !storedAnswers
|
|
8
18
|
? questionData.items.map((item) => item.correctAnswer)
|
|
9
|
-
:
|
|
10
|
-
return (_jsx(TrueFalseGroupClient, { questionData: questionData, isReviewMode: isReviewMode, userAnswers: reviewAnswers }));
|
|
19
|
+
: storedAnswers;
|
|
20
|
+
return (_jsx(TrueFalseGroupClient, { questionData: questionData, isReviewMode: isReviewMode, userAnswers: reviewAnswers, onAnswerChange: isReviewMode ? undefined : onSubmitAnswer }));
|
|
11
21
|
}
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import type { Question } from '../../../shared/types/entities/question.types';
|
|
2
|
-
export declare function WordOrderAndMatchGroupViewer({ question, isReviewMode, userAnswer, }: {
|
|
2
|
+
export declare function WordOrderAndMatchGroupViewer({ question, isReviewMode, userAnswer, onSubmitAnswer, }: {
|
|
3
3
|
question: Question;
|
|
4
4
|
isReviewMode?: boolean;
|
|
5
|
-
userAnswer?:
|
|
5
|
+
userAnswer?: unknown;
|
|
6
|
+
onSubmitAnswer?: (answer: Array<{
|
|
7
|
+
orderedPhrase: string;
|
|
8
|
+
matchLabel: string;
|
|
9
|
+
}>) => void;
|
|
6
10
|
}): import("react").JSX.Element;
|
|
@@ -76,20 +76,40 @@ function resolveGroup(question) {
|
|
|
76
76
|
items: itemsRaw.map((item, idx) => mapItem(item, idx, answersRaw[idx])),
|
|
77
77
|
};
|
|
78
78
|
}
|
|
79
|
-
|
|
79
|
+
function readMatchAnswers(value) {
|
|
80
|
+
if (Array.isArray(value)) {
|
|
81
|
+
return value;
|
|
82
|
+
}
|
|
83
|
+
if (value && typeof value === 'object' && Array.isArray(value.answer)) {
|
|
84
|
+
return value.answer;
|
|
85
|
+
}
|
|
86
|
+
return undefined;
|
|
87
|
+
}
|
|
88
|
+
export function WordOrderAndMatchGroupViewer({ question, isReviewMode = false, userAnswer, onSubmitAnswer, }) {
|
|
80
89
|
const { instruction, matchOptions, items } = resolveGroup(question);
|
|
81
90
|
const questionData = {
|
|
82
91
|
instruction,
|
|
83
92
|
matchOptions,
|
|
84
93
|
items,
|
|
85
94
|
};
|
|
86
|
-
const
|
|
95
|
+
const storedAnswers = readMatchAnswers(userAnswer);
|
|
96
|
+
const autoUserAnswers = isReviewMode && !storedAnswers
|
|
87
97
|
? items.map((item) => ({
|
|
88
98
|
orderedPhrase: item.orderedPhrase,
|
|
89
99
|
matchLabel: item.matchLabel,
|
|
90
100
|
}))
|
|
91
|
-
:
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
101
|
+
: storedAnswers;
|
|
102
|
+
return (_jsx(WordOrderAndMatchGroupClient, { questionData: questionData, isReviewMode: isReviewMode, userAnswers: autoUserAnswers, onAnswerChange: isReviewMode
|
|
103
|
+
? undefined
|
|
104
|
+
: (itemIndex, next) => {
|
|
105
|
+
const current = items.map((_, index) => {
|
|
106
|
+
const existing = storedAnswers?.[index];
|
|
107
|
+
return {
|
|
108
|
+
orderedPhrase: existing?.orderedPhrase || '',
|
|
109
|
+
matchLabel: existing?.matchLabel || '',
|
|
110
|
+
};
|
|
111
|
+
});
|
|
112
|
+
current[itemIndex] = next;
|
|
113
|
+
onSubmitAnswer?.(current);
|
|
114
|
+
} }));
|
|
95
115
|
}
|
|
@@ -31,6 +31,6 @@ export function ReviewAnswerTheQuestionGroupRenderer({ partNumber, partName, que
|
|
|
31
31
|
? 'border-red-200 bg-red-50 text-red-800'
|
|
32
32
|
: 'border-gray-200 bg-gray-50 text-gray-700'}`, children: question.isExample
|
|
33
33
|
? (question.exampleAnswer || '(Câu mẫu)')
|
|
34
|
-
: (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
|
|
34
|
+
: (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" }), _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));
|
|
35
35
|
})] }));
|
|
36
36
|
}
|
|
@@ -231,7 +231,7 @@ const reverseChooseTheCorrectAnswerGroup = (apiQuestion) => {
|
|
|
231
231
|
});
|
|
232
232
|
const isExample = item.isExample === true;
|
|
233
233
|
const imageUrl = normalizeChooseAnswerGroupImageUrl(itemContent.imageUrl);
|
|
234
|
-
const audioUrl =
|
|
234
|
+
const audioUrl = normalizeChooseAnswerGroupImageUrl(itemContent.audioUrl);
|
|
235
235
|
return {
|
|
236
236
|
question: asString(itemContent.question),
|
|
237
237
|
optionType: asString(itemContent.optionType, 'text') === 'image' ? 'image' : 'text',
|
|
@@ -251,17 +251,17 @@ const reverseChooseTheCorrectAnswerGroup = (apiQuestion) => {
|
|
|
251
251
|
points: isExample ? 0 : asNumber(item.points, 1),
|
|
252
252
|
questionNumber: asNumber(item.questionNumber, index + 1),
|
|
253
253
|
...(imageUrl !== undefined ? { imageUrl } : {}),
|
|
254
|
-
...(audioUrl ? { audioUrl } : {}),
|
|
254
|
+
...(audioUrl !== undefined ? { audioUrl } : {}),
|
|
255
255
|
};
|
|
256
256
|
});
|
|
257
|
-
const audioUrl =
|
|
257
|
+
const audioUrl = normalizeChooseAnswerGroupImageUrl(asRecord(content.meta).audioUrl);
|
|
258
258
|
const imageUrl = normalizeChooseAnswerGroupImageUrl(asRecord(content.meta).imageUrl);
|
|
259
259
|
return {
|
|
260
260
|
content: '',
|
|
261
261
|
answer: {
|
|
262
262
|
instruction: asString(asRecord(content.meta).instruction, 'Choose the correct answer.'),
|
|
263
263
|
passage: asString(asRecord(content.meta).passage),
|
|
264
|
-
...(audioUrl ? { audioUrl } : {}),
|
|
264
|
+
...(audioUrl !== undefined ? { audioUrl } : {}),
|
|
265
265
|
...(imageUrl !== undefined ? { imageUrl } : {}),
|
|
266
266
|
items,
|
|
267
267
|
explanation: asString(apiQuestion.explanation),
|
|
@@ -315,6 +315,7 @@ const reverseAnswerTheQuestionGroup = (apiQuestion) => {
|
|
|
315
315
|
items,
|
|
316
316
|
explanation: asString(apiQuestion.explanation),
|
|
317
317
|
points: items.reduce((total, item) => (item.isExample ? total : total + item.points), 0),
|
|
318
|
+
...(meta.gradingType === 'MAPPING' ? { gradingType: 'MAPPING' } : {}),
|
|
318
319
|
},
|
|
319
320
|
};
|
|
320
321
|
};
|
|
@@ -404,6 +405,13 @@ const reverseChooseThenAnswerGroup = (apiQuestion) => {
|
|
|
404
405
|
question: asString(itemContent.question),
|
|
405
406
|
options,
|
|
406
407
|
optionId: asString(rawAnswer.optionId) || options[0]?.id || '',
|
|
408
|
+
expectedAnswers: (() => {
|
|
409
|
+
const raw = asArray(rawAnswer.expectedAnswers)
|
|
410
|
+
.filter((value) => typeof value === 'string')
|
|
411
|
+
.map((value) => value.trim())
|
|
412
|
+
.filter((value) => value !== '');
|
|
413
|
+
return raw.length > 0 ? raw : [''];
|
|
414
|
+
})(),
|
|
407
415
|
isExample,
|
|
408
416
|
points: isExample ? 0 : asNumber(item.points, 1),
|
|
409
417
|
questionNumber: asNumber(item.questionNumber, index + 1),
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
export declare const
|
|
1
|
+
export declare const ANSWER_THE_QUESTION_GROUP_GRADING_TYPES: readonly ["AI", "MAPPING"];
|
|
2
|
+
export type AnswerTheQuestionGroupGradingType = (typeof ANSWER_THE_QUESTION_GROUP_GRADING_TYPES)[number];
|
|
3
|
+
/** Default for newly created groups. */
|
|
4
|
+
export declare const ANSWER_THE_QUESTION_GROUP_GRADING_TYPE: AnswerTheQuestionGroupGradingType;
|
|
5
|
+
export declare function resolveAnswerTheQuestionGroupGradingType(value: unknown): AnswerTheQuestionGroupGradingType;
|
|
2
6
|
export interface AnswerTheQuestionGroupItemContent {
|
|
3
7
|
question: string;
|
|
4
8
|
imageUrl?: string | string[];
|
|
@@ -20,8 +24,9 @@ export interface AnswerTheQuestionGroupContent {
|
|
|
20
24
|
passage?: string;
|
|
21
25
|
audioUrl?: string;
|
|
22
26
|
imageUrl?: string | string[];
|
|
23
|
-
|
|
24
|
-
|
|
27
|
+
/** AI = LLM short-answer; MAPPING = exact-match expectedAnswers. */
|
|
28
|
+
gradingType: AnswerTheQuestionGroupGradingType;
|
|
29
|
+
isAiGraded: boolean;
|
|
25
30
|
};
|
|
26
31
|
items: AnswerTheQuestionGroupItem[];
|
|
27
32
|
}
|
|
@@ -1 +1,6 @@
|
|
|
1
|
+
export const ANSWER_THE_QUESTION_GROUP_GRADING_TYPES = ['AI', 'MAPPING'];
|
|
2
|
+
/** Default for newly created groups. */
|
|
1
3
|
export const ANSWER_THE_QUESTION_GROUP_GRADING_TYPE = 'AI';
|
|
4
|
+
export function resolveAnswerTheQuestionGroupGradingType(value) {
|
|
5
|
+
return value === 'MAPPING' ? 'MAPPING' : 'AI';
|
|
6
|
+
}
|
|
@@ -4,6 +4,7 @@ export interface ChooseThenAnswerOption {
|
|
|
4
4
|
}
|
|
5
5
|
export interface ChooseThenAnswerItemAnswer {
|
|
6
6
|
optionId: string;
|
|
7
|
+
expectedAnswers?: string[];
|
|
7
8
|
}
|
|
8
9
|
export interface ChooseThenAnswerStudentItemAnswer {
|
|
9
10
|
optionId: string;
|
|
@@ -27,6 +28,8 @@ export interface ChooseThenAnswerGroupContent {
|
|
|
27
28
|
meta?: {
|
|
28
29
|
instruction?: string;
|
|
29
30
|
title?: string;
|
|
31
|
+
gradingType?: 'AI';
|
|
32
|
+
isAiGraded?: boolean;
|
|
30
33
|
};
|
|
31
34
|
items: ChooseThenAnswerGroupItem[];
|
|
32
35
|
}
|