@tinyweb_dev/oe-exam-sdk 1.0.8 → 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-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/ChooseTheCorrectAnswerGroupClient.js +18 -20
- 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/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/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 +8 -0
- 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-then-answer-group.d.ts +3 -0
- package/package.json +1 -1
|
@@ -2,18 +2,28 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { Check, CircleHelp, Lightbulb, Star, X } from 'lucide-react';
|
|
4
4
|
import { cn } from '../../../../shared/lib/utils';
|
|
5
|
-
export function TrueFalseGroupClient({ questionData, isReviewMode = false, userAnswers = [], }) {
|
|
5
|
+
export function TrueFalseGroupClient({ questionData, isReviewMode = false, userAnswers = [], onAnswerChange, }) {
|
|
6
6
|
const items = questionData.items || [];
|
|
7
|
+
const handleSelect = (itemIndex, value) => {
|
|
8
|
+
const item = items[itemIndex];
|
|
9
|
+
if (isReviewMode || item?.isExample || !onAnswerChange) {
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
const next = items.map((_, index) => userAnswers[index]);
|
|
13
|
+
next[itemIndex] = value;
|
|
14
|
+
onAnswerChange(next);
|
|
15
|
+
};
|
|
7
16
|
return (_jsxs("div", { className: "space-y-4", children: [questionData.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: questionData.instruction })), items.map((item, itemIndex) => {
|
|
8
|
-
const userValue = userAnswers[itemIndex];
|
|
17
|
+
const userValue = item.isExample ? item.correctAnswer : userAnswers[itemIndex];
|
|
9
18
|
const correctAnswer = item.correctAnswer;
|
|
19
|
+
const canSelect = !isReviewMode && !item.isExample && Boolean(onAnswerChange);
|
|
10
20
|
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-3 py-2", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-7 w-7 items-center justify-center rounded-lg bg-gradient-to-br from-blue-500 to-indigo-600 text-white", children: _jsx(CircleHelp, { className: "h-3.5 w-3.5" }) }), _jsxs("span", { className: "text-sm font-medium text-gray-700", children: ["C\u00E2u ", item.questionNumber || itemIndex + 1] }), item.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: "text-xs text-gray-500", children: [item.points || 0, " \u0111i\u1EC3m"] })] }), _jsxs("div", { className: "space-y-3 p-3", children: [_jsx("p", { className: "text-sm font-semibold leading-relaxed text-gray-800", children: item.question || 'Câu hỏi chưa được nhập' }), _jsx("div", { className: "flex items-center gap-3", children: [
|
|
11
21
|
{ value: true, label: 'True' },
|
|
12
22
|
{ value: false, label: 'False' },
|
|
13
23
|
].map((opt) => {
|
|
14
24
|
const isCorrect = opt.value === correctAnswer;
|
|
15
25
|
const isSelected = opt.value === userValue;
|
|
16
|
-
return (_jsxs("
|
|
26
|
+
return (_jsxs("button", { type: "button", disabled: !canSelect, onClick: () => handleSelect(itemIndex, opt.value), className: cn('flex flex-1 items-center justify-center gap-2 rounded-lg border-2 px-4 py-2 text-sm font-semibold transition-colors', canSelect ? 'cursor-pointer hover:border-blue-300' : 'cursor-default', isReviewMode && isCorrect
|
|
17
27
|
? 'border-green-500 bg-green-50 text-green-700'
|
|
18
28
|
: isReviewMode && isSelected && !isCorrect
|
|
19
29
|
? 'border-red-500 bg-red-50 text-red-700'
|
|
@@ -2,7 +2,8 @@ import type { Question } from '../../../shared/types/entities/question.types';
|
|
|
2
2
|
type AnswerTheQuestionGroupViewerProps = {
|
|
3
3
|
question: Question;
|
|
4
4
|
isReviewMode?: boolean;
|
|
5
|
-
userAnswer?:
|
|
5
|
+
userAnswer?: unknown;
|
|
6
|
+
onSubmitAnswer?: (answer: string[]) => void;
|
|
6
7
|
};
|
|
7
|
-
export declare function AnswerTheQuestionGroupViewer({ question, isReviewMode, userAnswer, }: AnswerTheQuestionGroupViewerProps): import("react").JSX.Element;
|
|
8
|
+
export declare function AnswerTheQuestionGroupViewer({ question, isReviewMode, userAnswer, onSubmitAnswer, }: AnswerTheQuestionGroupViewerProps): import("react").JSX.Element;
|
|
8
9
|
export {};
|
|
@@ -2,10 +2,17 @@
|
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
3
|
import { AnswerTheQuestionGroupClient } from '../types/answer-the-question-group/AnswerTheQuestionGroupClient';
|
|
4
4
|
import { mapQuestionToAnswerTheQuestionGroupData } from '../types/answer-the-question-group/map-answer-the-question-group-data';
|
|
5
|
-
|
|
5
|
+
function readStringAnswers(value) {
|
|
6
|
+
if (Array.isArray(value)) {
|
|
7
|
+
return value.map((item) => String(item ?? ''));
|
|
8
|
+
}
|
|
9
|
+
if (value && typeof value === 'object' && Array.isArray(value.answer)) {
|
|
10
|
+
return value.answer.map((item) => String(item ?? ''));
|
|
11
|
+
}
|
|
12
|
+
return undefined;
|
|
13
|
+
}
|
|
14
|
+
export function AnswerTheQuestionGroupViewer({ question, isReviewMode = false, userAnswer, onSubmitAnswer, }) {
|
|
6
15
|
const questionData = mapQuestionToAnswerTheQuestionGroupData(question);
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
: userAnswer;
|
|
10
|
-
return (_jsx(AnswerTheQuestionGroupClient, { questionData: questionData, isReviewMode: isReviewMode, userAnswers: reviewAnswers }));
|
|
16
|
+
const storedAnswers = readStringAnswers(userAnswer);
|
|
17
|
+
return (_jsx(AnswerTheQuestionGroupClient, { questionData: questionData, isReviewMode: isReviewMode, userAnswers: storedAnswers, onAnswerChange: isReviewMode ? undefined : onSubmitAnswer }));
|
|
11
18
|
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import type { Question } from '../../../shared/types/entities/question.types';
|
|
2
|
-
import type { ChooseThenAnswerStudentItemAnswer } from '../_shared/types/choose-then-answer-group.type';
|
|
3
|
-
export declare function ChooseThenAnswerGroupViewer({ question, isReviewMode, userAnswer, onSubmitAnswer, }: {
|
|
2
|
+
import type { ChooseThenAnswerGradeDetail, ChooseThenAnswerStudentItemAnswer } from '../_shared/types/choose-then-answer-group.type';
|
|
3
|
+
export declare function ChooseThenAnswerGroupViewer({ question, isReviewMode, userAnswer, gradeDetails, onSubmitAnswer, }: {
|
|
4
4
|
question: Question;
|
|
5
5
|
isReviewMode?: boolean;
|
|
6
6
|
userAnswer?: unknown;
|
|
7
|
+
gradeDetails?: ChooseThenAnswerGradeDetail[];
|
|
7
8
|
onSubmitAnswer?: (answer: {
|
|
8
9
|
answer: ChooseThenAnswerStudentItemAnswer[];
|
|
9
10
|
}) => void;
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
3
|
import { useState } from 'react';
|
|
4
4
|
import { ChooseThenAnswerGroupClient } from '../types/choose-then-answer-group/ChooseThenAnswerGroupClient';
|
|
5
|
-
import { mapQuestionToChooseThenAnswerGroupData, parseStudentAnswer, } from '../types/choose-then-answer-group/map-choose-then-answer-group-data';
|
|
5
|
+
import { mapQuestionToChooseThenAnswerGroupData, parseStudentAnswer, toCtaItemResults, } from '../types/choose-then-answer-group/map-choose-then-answer-group-data';
|
|
6
6
|
function readStudentAnswers(value, itemCount) {
|
|
7
7
|
const record = value && typeof value === 'object' && !Array.isArray(value)
|
|
8
8
|
? value
|
|
@@ -16,7 +16,7 @@ function readStudentAnswers(value, itemCount) {
|
|
|
16
16
|
return undefined;
|
|
17
17
|
return Array.from({ length: itemCount }, (_, index) => parseStudentAnswer(raw[index]));
|
|
18
18
|
}
|
|
19
|
-
export function ChooseThenAnswerGroupViewer({ question, isReviewMode = false, userAnswer, onSubmitAnswer, }) {
|
|
19
|
+
export function ChooseThenAnswerGroupViewer({ question, isReviewMode = false, userAnswer, gradeDetails, onSubmitAnswer, }) {
|
|
20
20
|
const questionData = mapQuestionToChooseThenAnswerGroupData(question);
|
|
21
21
|
const seed = questionData.items.map((item) => ({
|
|
22
22
|
optionId: item.isExample ? item.optionId : '',
|
|
@@ -35,5 +35,6 @@ export function ChooseThenAnswerGroupViewer({ question, isReviewMode = false, us
|
|
|
35
35
|
setLocalAnswers(updated);
|
|
36
36
|
onSubmitAnswer?.({ answer: updated });
|
|
37
37
|
};
|
|
38
|
-
|
|
38
|
+
const displayedAnswers = isReviewMode && !parsedUser ? [] : (parsedUser ?? localAnswers);
|
|
39
|
+
return (_jsx(ChooseThenAnswerGroupClient, { questionData: questionData, isReviewMode: isReviewMode, userAnswers: isReviewMode && !parsedUser ? undefined : displayedAnswers, itemResults: toCtaItemResults(gradeDetails, questionData.items, displayedAnswers), onAnswerChange: isReviewMode ? undefined : handleChange }));
|
|
39
40
|
}
|
|
@@ -2,6 +2,8 @@ import type { Question } from '../../../shared/types/entities/question.types';
|
|
|
2
2
|
type CrossOutWordGroupViewerProps = {
|
|
3
3
|
question: Question;
|
|
4
4
|
isReviewMode?: boolean;
|
|
5
|
+
userAnswer?: unknown;
|
|
6
|
+
onSubmitAnswer?: (answer: unknown[]) => void;
|
|
5
7
|
};
|
|
6
|
-
export declare function CrossOutWordGroupViewer({ question, isReviewMode, }: CrossOutWordGroupViewerProps): import("react").JSX.Element;
|
|
8
|
+
export declare function CrossOutWordGroupViewer({ question, isReviewMode, userAnswer, onSubmitAnswer, }: CrossOutWordGroupViewerProps): import("react").JSX.Element;
|
|
7
9
|
export {};
|
|
@@ -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
|
}
|
|
@@ -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
|
}
|
|
@@ -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
|
}
|