@tinyweb_dev/oe-exam-sdk 0.2.13 → 0.2.14
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/ExamTakingPageContainer.js +6 -0
- package/dist/components/exams/take/components/QuestionRenderer.js +13 -3
- package/dist/components/exams/take/components/question-renderers/ChooseThenAnswerGroupRenderer.d.ts +14 -0
- package/dist/components/exams/take/components/question-renderers/ChooseThenAnswerGroupRenderer.js +32 -0
- package/dist/components/exams/take/components/question-renderers/MoversAnswerTheQuestionRenderer.d.ts +1 -1
- package/dist/components/exams/take/components/question-renderers/MoversAnswerTheQuestionRenderer.js +10 -2
- package/dist/components/exams/take/components/question-renderers/SortWordsIntoCategoriesRenderer.d.ts +16 -0
- package/dist/components/exams/take/components/question-renderers/SortWordsIntoCategoriesRenderer.js +23 -0
- package/dist/components/exams/take/components/question-renderers/index.d.ts +2 -0
- package/dist/components/exams/take/components/question-renderers/index.js +2 -0
- package/dist/components/exams/take/utils/answer-transformers.js +34 -1
- package/dist/components/exams/take/utils/question-transformers.d.ts +42 -3
- package/dist/components/exams/take/utils/question-transformers.js +71 -1
- package/dist/components/questions/_shared/config/question-types.config.js +12 -0
- package/dist/components/questions/_shared/types/answer-the-question-group.type.d.ts +5 -1
- package/dist/components/questions/_shared/types/answer-the-question-group.type.js +7 -0
- package/dist/components/questions/_shared/types/answer-the-question.type.d.ts +3 -1
- package/dist/components/questions/_shared/types/answer-the-question.type.js +19 -1
- package/dist/components/questions/_shared/types/choose-then-answer-group.type.d.ts +47 -0
- package/dist/components/questions/_shared/types/choose-then-answer-group.type.js +2 -0
- package/dist/components/questions/_shared/types/index.d.ts +2 -0
- package/dist/components/questions/_shared/types/index.js +2 -0
- package/dist/components/questions/_shared/types/match-word-to-picture-group.type.d.ts +9 -3
- package/dist/components/questions/_shared/types/match-word-to-picture-group.type.js +19 -0
- package/dist/components/questions/_shared/types/sort-words-into-categories.type.d.ts +43 -0
- package/dist/components/questions/_shared/types/sort-words-into-categories.type.js +1 -0
- package/dist/components/questions/components/QuestionSearchDropdown.js +2 -0
- package/dist/components/questions/components/QuestionTypeSelector.js +2 -0
- package/dist/components/questions/creator/question-type-registry.js +4 -0
- package/dist/components/questions/types/answer-the-question/AnswerTheQuestionClient.js +30 -6
- package/dist/components/questions/types/answer-the-question/map-answer-the-question-data.d.ts +2 -0
- package/dist/components/questions/types/answer-the-question/map-answer-the-question-data.js +56 -0
- package/dist/components/questions/types/answer-the-question/transform.js +9 -3
- package/dist/components/questions/types/answer-the-question-group/AnswerTheQuestionGroupClient.js +42 -9
- package/dist/components/questions/types/answer-the-question-group/AnswerTheQuestionGroupCreator.js +7 -5
- package/dist/components/questions/types/answer-the-question-group/map-answer-the-question-group-data.js +18 -4
- package/dist/components/questions/types/answer-the-question-group/transform.js +8 -2
- package/dist/components/questions/types/choose-then-answer-group/ChooseThenAnswerGroupClient.d.ts +2 -0
- package/dist/components/questions/types/choose-then-answer-group/ChooseThenAnswerGroupClient.js +34 -0
- package/dist/components/questions/types/choose-then-answer-group/ChooseThenAnswerGroupCreator.d.ts +2 -0
- package/dist/components/questions/types/choose-then-answer-group/ChooseThenAnswerGroupCreator.js +174 -0
- package/dist/components/questions/types/choose-then-answer-group/index.d.ts +5 -0
- package/dist/components/questions/types/choose-then-answer-group/index.js +5 -0
- package/dist/components/questions/types/choose-then-answer-group/map-choose-then-answer-group-data.d.ts +11 -0
- package/dist/components/questions/types/choose-then-answer-group/map-choose-then-answer-group-data.js +127 -0
- package/dist/components/questions/types/choose-then-answer-group/register.d.ts +3 -0
- package/dist/components/questions/types/choose-then-answer-group/register.js +37 -0
- package/dist/components/questions/types/choose-then-answer-group/transform.d.ts +2 -0
- package/dist/components/questions/types/choose-then-answer-group/transform.js +37 -0
- package/dist/components/questions/types/match-word-to-picture-group/MatchWordToPictureGroupClient.js +39 -10
- package/dist/components/questions/types/match-word-to-picture-group/MatchWordToPictureGroupCreator.js +15 -8
- package/dist/components/questions/types/match-word-to-picture-group/map-match-word-to-picture-group-data.js +8 -3
- package/dist/components/questions/types/match-word-to-picture-group/transform.js +21 -13
- package/dist/components/questions/types/sort-words-into-categories/SortWordsIntoCategoriesBoard.d.ts +12 -0
- package/dist/components/questions/types/sort-words-into-categories/SortWordsIntoCategoriesBoard.js +74 -0
- package/dist/components/questions/types/sort-words-into-categories/SortWordsIntoCategoriesClient.d.ts +2 -0
- package/dist/components/questions/types/sort-words-into-categories/SortWordsIntoCategoriesClient.js +24 -0
- package/dist/components/questions/types/sort-words-into-categories/SortWordsIntoCategoriesCreator.d.ts +2 -0
- package/dist/components/questions/types/sort-words-into-categories/SortWordsIntoCategoriesCreator.js +171 -0
- package/dist/components/questions/types/sort-words-into-categories/index.d.ts +4 -0
- package/dist/components/questions/types/sort-words-into-categories/index.js +4 -0
- package/dist/components/questions/types/sort-words-into-categories/map-sort-words-into-categories-data.d.ts +11 -0
- package/dist/components/questions/types/sort-words-into-categories/map-sort-words-into-categories-data.js +153 -0
- package/dist/components/questions/types/sort-words-into-categories/register.d.ts +3 -0
- package/dist/components/questions/types/sort-words-into-categories/register.js +37 -0
- package/dist/components/questions/types/sort-words-into-categories/transform.d.ts +2 -0
- package/dist/components/questions/types/sort-words-into-categories/transform.js +33 -0
- package/dist/components/questions/viewer/AnswerTheQuestionViewer.d.ts +9 -0
- package/dist/components/questions/viewer/AnswerTheQuestionViewer.js +8 -0
- package/dist/components/questions/viewer/ChooseThenAnswerGroupViewer.d.ts +10 -0
- package/dist/components/questions/viewer/ChooseThenAnswerGroupViewer.js +39 -0
- package/dist/components/questions/viewer/QuestionViewer.js +13 -2
- package/dist/components/questions/viewer/SortWordsIntoCategoriesViewer.d.ts +10 -0
- package/dist/components/questions/viewer/SortWordsIntoCategoriesViewer.js +27 -0
- package/dist/components/questions/viewer/index.d.ts +3 -0
- package/dist/components/questions/viewer/index.js +3 -0
- package/dist/components/results/renderers/ReviewAnswerTheQuestionGroupRenderer.d.ts +2 -1
- package/dist/components/results/renderers/ReviewAnswerTheQuestionGroupRenderer.js +12 -3
- package/dist/components/results/renderers/review-question-body-dedicated.js +1 -1
- package/dist/components/results/renderers/review-question-body-movers.js +24 -2
- package/dist/components/results/review-data.js +28 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/shared/constants/question-skills.js +4 -0
- package/dist/shared/lib/utils/question-reverse-transform.js +98 -2
- package/dist/shared/lib/utils/question-transform-types.d.ts +1 -1
- package/dist/shared/lib/utils/question-transform.js +4 -0
- package/dist/shared/types/common.types.d.ts +1 -1
- package/dist/shared/types/questions/answer-the-question-group.d.ts +3 -0
- package/dist/shared/types/questions/choose-then-answer-group.d.ts +46 -0
- package/dist/shared/types/questions/choose-then-answer-group.js +1 -0
- package/dist/shared/types/questions/index.d.ts +2 -0
- package/dist/shared/types/questions/index.js +3 -0
- package/dist/shared/types/questions/match-word-to-picture-group.d.ts +6 -2
- package/dist/shared/types/questions/sort-words-into-categories.d.ts +37 -0
- package/dist/shared/types/questions/sort-words-into-categories.js +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Question } from '../../../shared/types/entities/question.types';
|
|
2
|
+
type AnswerTheQuestionViewerProps = {
|
|
3
|
+
question: Question;
|
|
4
|
+
isReviewMode?: boolean;
|
|
5
|
+
userAnswer?: string;
|
|
6
|
+
onSubmitAnswer?: (answer: string) => void;
|
|
7
|
+
};
|
|
8
|
+
export declare function AnswerTheQuestionViewer({ question, isReviewMode, userAnswer, onSubmitAnswer, }: AnswerTheQuestionViewerProps): import("react").JSX.Element;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { AnswerTheQuestionClient } from '../types/answer-the-question/AnswerTheQuestionClient';
|
|
4
|
+
import { mapQuestionToAnswerTheQuestionData } from '../types/answer-the-question/map-answer-the-question-data';
|
|
5
|
+
export function AnswerTheQuestionViewer({ question, isReviewMode = false, userAnswer, onSubmitAnswer, }) {
|
|
6
|
+
const questionData = mapQuestionToAnswerTheQuestionData(question);
|
|
7
|
+
return (_jsx(AnswerTheQuestionClient, { questionData: questionData, isReviewMode: isReviewMode, userAnswer: userAnswer, autoFillCorrectAnswers: isReviewMode && !userAnswer, explanation: question.explanation || questionData.explanation, onSubmit: isReviewMode ? undefined : onSubmitAnswer }));
|
|
8
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
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, }: {
|
|
4
|
+
question: Question;
|
|
5
|
+
isReviewMode?: boolean;
|
|
6
|
+
userAnswer?: unknown;
|
|
7
|
+
onSubmitAnswer?: (answer: {
|
|
8
|
+
answer: ChooseThenAnswerStudentItemAnswer[];
|
|
9
|
+
}) => void;
|
|
10
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { useState } from 'react';
|
|
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';
|
|
6
|
+
function readStudentAnswers(value, itemCount) {
|
|
7
|
+
const record = value && typeof value === 'object' && !Array.isArray(value)
|
|
8
|
+
? value
|
|
9
|
+
: null;
|
|
10
|
+
const raw = Array.isArray(record?.answer)
|
|
11
|
+
? record.answer
|
|
12
|
+
: Array.isArray(value)
|
|
13
|
+
? value
|
|
14
|
+
: null;
|
|
15
|
+
if (!raw)
|
|
16
|
+
return undefined;
|
|
17
|
+
return Array.from({ length: itemCount }, (_, index) => parseStudentAnswer(raw[index]));
|
|
18
|
+
}
|
|
19
|
+
export function ChooseThenAnswerGroupViewer({ question, isReviewMode = false, userAnswer, onSubmitAnswer, }) {
|
|
20
|
+
const questionData = mapQuestionToChooseThenAnswerGroupData(question);
|
|
21
|
+
const seed = questionData.items.map((item) => ({
|
|
22
|
+
optionId: item.isExample ? item.optionId : '',
|
|
23
|
+
text: '',
|
|
24
|
+
}));
|
|
25
|
+
const parsedUser = readStudentAnswers(userAnswer, questionData.items.length);
|
|
26
|
+
const questionKey = `${question.id}:${question.type}:${questionData.items.length}`;
|
|
27
|
+
const [cachedKey, setCachedKey] = useState(questionKey);
|
|
28
|
+
const [localAnswers, setLocalAnswers] = useState(() => parsedUser ?? seed);
|
|
29
|
+
if (cachedKey !== questionKey) {
|
|
30
|
+
setCachedKey(questionKey);
|
|
31
|
+
setLocalAnswers(parsedUser ?? seed);
|
|
32
|
+
}
|
|
33
|
+
const handleChange = (index, next) => {
|
|
34
|
+
const updated = localAnswers.map((item, itemIndex) => itemIndex === index ? next : item);
|
|
35
|
+
setLocalAnswers(updated);
|
|
36
|
+
onSubmitAnswer?.({ answer: updated });
|
|
37
|
+
};
|
|
38
|
+
return (_jsx(ChooseThenAnswerGroupClient, { questionData: questionData, isReviewMode: isReviewMode, userAnswers: isReviewMode && !parsedUser ? undefined : (parsedUser ?? localAnswers), onAnswerChange: isReviewMode ? undefined : handleChange }));
|
|
39
|
+
}
|
|
@@ -21,6 +21,8 @@ import { FillMissingWordsInGridClient } from '../types/fill-missing-words-in-gri
|
|
|
21
21
|
import { WordFillStructuredFormClient } from '../types/word-fill-structured-form/WordFillStructuredFormClient';
|
|
22
22
|
import { CrosswordPuzzleClient } from '../types/crossword-puzzle/CrosswordPuzzleClient';
|
|
23
23
|
import { FindWordsInMatrixClient } from '../types/find-words-in-matrix/FindWordsInMatrixClient';
|
|
24
|
+
import { SortWordsIntoCategoriesViewer } from './SortWordsIntoCategoriesViewer';
|
|
25
|
+
import { ChooseThenAnswerGroupViewer } from './ChooseThenAnswerGroupViewer';
|
|
24
26
|
import { parseFillBlankStemLine } from '../_shared/utils/parseFillBlankStem';
|
|
25
27
|
import { WordFillStructuredFormViewer } from './WordFillStructuredFormViewer';
|
|
26
28
|
import { ChooseTheCorrectAnswerGroupViewer } from './ChooseTheCorrectAnswerGroupViewer';
|
|
@@ -31,6 +33,7 @@ import { MatchingWithLinesGroupViewer } from './MatchingWithLinesGroupViewer';
|
|
|
31
33
|
import { CrossOutWordGroupViewer } from './CrossOutWordGroupViewer';
|
|
32
34
|
import { SpontaneousQaGroupViewer } from './SpontaneousQaGroupViewer';
|
|
33
35
|
import { WordOrderAndMatchGroupViewer } from './WordOrderAndMatchGroupViewer';
|
|
36
|
+
import { AnswerTheQuestionViewer } from './AnswerTheQuestionViewer';
|
|
34
37
|
import { AnswerTheQuestionGroupViewer } from './AnswerTheQuestionGroupViewer';
|
|
35
38
|
import { MatchWordToPictureViewer } from './MatchWordToPictureViewer';
|
|
36
39
|
import { MatchWordToPictureGroupViewer } from './MatchWordToPictureGroupViewer';
|
|
@@ -647,6 +650,8 @@ export function QuestionViewer({ question, onSubmitAnswer, isReviewMode = false,
|
|
|
647
650
|
return (_jsx(MatchColumnsToMakeSentencesViewer, { question: question, isReviewMode: isReviewMode, userAnswer: userAnswer, onSubmitAnswer: onSubmitAnswer }));
|
|
648
651
|
case 'CROSS_OUT_WORD_GROUP':
|
|
649
652
|
return (_jsx(CrossOutWordGroupViewer, { question: question, isReviewMode: isReviewMode }));
|
|
653
|
+
case 'ANSWER_THE_QUESTION':
|
|
654
|
+
return (_jsx(AnswerTheQuestionViewer, { question: question, isReviewMode: isReviewMode, userAnswer: typeof userAnswer === 'string' ? userAnswer : undefined, onSubmitAnswer: onSubmitAnswer }));
|
|
650
655
|
case 'ANSWER_THE_QUESTION_GROUP':
|
|
651
656
|
return (_jsx(AnswerTheQuestionGroupViewer, { question: question, isReviewMode: isReviewMode, userAnswer: userAnswer }));
|
|
652
657
|
case 'SPONTANEOUS_QA_GROUP':
|
|
@@ -703,6 +708,10 @@ export function QuestionViewer({ question, onSubmitAnswer, isReviewMode = false,
|
|
|
703
708
|
return (_jsx(CrosswordPuzzleClient, { content: question.content, correctAnswer: question.answer, isReviewMode: isReviewMode, userAnswers: userAnswer }));
|
|
704
709
|
case 'FIND_WORDS_IN_MATRIX':
|
|
705
710
|
return (_jsx(FindWordsInMatrixClient, { content: question.content, correctAnswer: question.answer, showSolution: isReviewMode }));
|
|
711
|
+
case 'SORT_WORDS_INTO_CATEGORIES':
|
|
712
|
+
return (_jsx(SortWordsIntoCategoriesViewer, { question: question, isReviewMode: isReviewMode, userAnswer: userAnswer, onSubmitAnswer: onSubmitAnswer }));
|
|
713
|
+
case 'CHOOSE_THEN_ANSWER_GROUP':
|
|
714
|
+
return (_jsx(ChooseThenAnswerGroupViewer, { question: question, isReviewMode: isReviewMode, userAnswer: userAnswer, onSubmitAnswer: onSubmitAnswer }));
|
|
706
715
|
default:
|
|
707
716
|
return _jsxs("div", { children: ["Lo\u1EA1i c\u00E2u h\u1ECFi kh\u00F4ng \u0111\u01B0\u1EE3c h\u1ED7 tr\u1EE3: ", question.type] });
|
|
708
717
|
}
|
|
@@ -716,7 +725,8 @@ export function QuestionViewer({ question, onSubmitAnswer, isReviewMode = false,
|
|
|
716
725
|
// WFSF and Crossword show title/instruction inside the client — no outer card label.
|
|
717
726
|
if (question.type === 'WORD_FILL_STRUCTURED_FORM' ||
|
|
718
727
|
question.type === 'CROSSWORD_PUZZLE' ||
|
|
719
|
-
question.type === 'MATCH_WORD_TO_PICTURE'
|
|
728
|
+
question.type === 'MATCH_WORD_TO_PICTURE' ||
|
|
729
|
+
question.type === 'ANSWER_THE_QUESTION') {
|
|
720
730
|
return null;
|
|
721
731
|
}
|
|
722
732
|
// Modern fill stems live entirely in the body; title is a short label.
|
|
@@ -741,7 +751,8 @@ export function QuestionViewer({ question, onSubmitAnswer, isReviewMode = false,
|
|
|
741
751
|
// WFSF and Crossword already have their own bordered shell — avoid double Card border.
|
|
742
752
|
if (question.type === 'WORD_FILL_STRUCTURED_FORM' ||
|
|
743
753
|
question.type === 'CROSSWORD_PUZZLE' ||
|
|
744
|
-
question.type === 'MATCH_WORD_TO_PICTURE'
|
|
754
|
+
question.type === 'MATCH_WORD_TO_PICTURE' ||
|
|
755
|
+
question.type === 'ANSWER_THE_QUESTION') {
|
|
745
756
|
return (_jsx("div", { className: "space-y-4", children: renderQuestion() }));
|
|
746
757
|
}
|
|
747
758
|
return (_jsxs(Card, { children: [cardTitle ? (_jsx(CardHeader, { children: _jsxs(CardTitle, { className: "flex items-center justify-between", children: [_jsx("span", { className: "min-w-0 pr-2 text-base font-semibold leading-snug", children: cardTitle }), _jsxs("span", { className: "shrink-0 text-sm font-normal text-gray-500", children: [question.points, " \u0111i\u1EC3m"] })] }) })) : null, _jsx(CardContent, { className: cardTitle ? 'space-y-4' : 'space-y-4 pt-4', children: renderQuestion() })] }));
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Question } from '../../../shared/types/entities/question.types';
|
|
2
|
+
import type { SortWordsPlacement } from '../_shared/types/sort-words-into-categories.type';
|
|
3
|
+
export declare function SortWordsIntoCategoriesViewer({ question, isReviewMode, userAnswer, onSubmitAnswer, }: {
|
|
4
|
+
question: Question;
|
|
5
|
+
isReviewMode?: boolean;
|
|
6
|
+
userAnswer?: unknown;
|
|
7
|
+
onSubmitAnswer?: (answer: {
|
|
8
|
+
placements: SortWordsPlacement[];
|
|
9
|
+
}) => void;
|
|
10
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
import { SortWordsIntoCategoriesClient } from '../types/sort-words-into-categories/SortWordsIntoCategoriesClient';
|
|
5
|
+
import { mapQuestionToSortWordsIntoCategoriesData, readPlacements, } from '../types/sort-words-into-categories/map-sort-words-into-categories-data';
|
|
6
|
+
export function SortWordsIntoCategoriesViewer({ question, isReviewMode = false, userAnswer, onSubmitAnswer, }) {
|
|
7
|
+
const questionData = mapQuestionToSortWordsIntoCategoriesData(question);
|
|
8
|
+
const parsedUser = readPlacements(userAnswer);
|
|
9
|
+
const questionKey = `${question.id}:${question.type}:${questionData.words.map((word) => word.id).join(',')}`;
|
|
10
|
+
const [cachedKey, setCachedKey] = useState(questionKey);
|
|
11
|
+
const [localPlacements, setLocalPlacements] = useState(() => parsedUser ?? questionData.words.map((word) => ({
|
|
12
|
+
wordId: word.id,
|
|
13
|
+
categoryId: word.isExample ? word.categoryId : '',
|
|
14
|
+
})));
|
|
15
|
+
if (cachedKey !== questionKey) {
|
|
16
|
+
setCachedKey(questionKey);
|
|
17
|
+
setLocalPlacements(parsedUser ?? questionData.words.map((word) => ({
|
|
18
|
+
wordId: word.id,
|
|
19
|
+
categoryId: word.isExample ? word.categoryId : '',
|
|
20
|
+
})));
|
|
21
|
+
}
|
|
22
|
+
const handleChange = (next) => {
|
|
23
|
+
setLocalPlacements(next);
|
|
24
|
+
onSubmitAnswer?.({ placements: next });
|
|
25
|
+
};
|
|
26
|
+
return (_jsx(SortWordsIntoCategoriesClient, { questionData: questionData, isReviewMode: isReviewMode, userPlacements: isReviewMode && !parsedUser ? undefined : (parsedUser ?? localPlacements), onPlacementsChange: isReviewMode ? undefined : handleChange }));
|
|
27
|
+
}
|
|
@@ -9,8 +9,11 @@ export { MatchingWithLinesGroupViewer } from './MatchingWithLinesGroupViewer';
|
|
|
9
9
|
export { CrossOutWordGroupViewer } from './CrossOutWordGroupViewer';
|
|
10
10
|
export { SpontaneousQaGroupViewer } from './SpontaneousQaGroupViewer';
|
|
11
11
|
export { WordOrderAndMatchGroupViewer } from './WordOrderAndMatchGroupViewer';
|
|
12
|
+
export { AnswerTheQuestionViewer } from './AnswerTheQuestionViewer';
|
|
12
13
|
export { AnswerTheQuestionGroupViewer } from './AnswerTheQuestionGroupViewer';
|
|
13
14
|
export { MatchWordToPictureViewer } from './MatchWordToPictureViewer';
|
|
14
15
|
export { MatchWordToPictureGroupViewer } from './MatchWordToPictureGroupViewer';
|
|
15
16
|
export { MatchColumnsToMakeSentencesViewer } from './MatchColumnsToMakeSentencesViewer';
|
|
16
17
|
export { WordOrderingGroupViewer } from './WordOrderingGroupViewer';
|
|
18
|
+
export { SortWordsIntoCategoriesViewer } from './SortWordsIntoCategoriesViewer';
|
|
19
|
+
export { ChooseThenAnswerGroupViewer } from './ChooseThenAnswerGroupViewer';
|
|
@@ -9,8 +9,11 @@ export { MatchingWithLinesGroupViewer } from './MatchingWithLinesGroupViewer';
|
|
|
9
9
|
export { CrossOutWordGroupViewer } from './CrossOutWordGroupViewer';
|
|
10
10
|
export { SpontaneousQaGroupViewer } from './SpontaneousQaGroupViewer';
|
|
11
11
|
export { WordOrderAndMatchGroupViewer } from './WordOrderAndMatchGroupViewer';
|
|
12
|
+
export { AnswerTheQuestionViewer } from './AnswerTheQuestionViewer';
|
|
12
13
|
export { AnswerTheQuestionGroupViewer } from './AnswerTheQuestionGroupViewer';
|
|
13
14
|
export { MatchWordToPictureViewer } from './MatchWordToPictureViewer';
|
|
14
15
|
export { MatchWordToPictureGroupViewer } from './MatchWordToPictureGroupViewer';
|
|
15
16
|
export { MatchColumnsToMakeSentencesViewer } from './MatchColumnsToMakeSentencesViewer';
|
|
16
17
|
export { WordOrderingGroupViewer } from './WordOrderingGroupViewer';
|
|
18
|
+
export { SortWordsIntoCategoriesViewer } from './SortWordsIntoCategoriesViewer';
|
|
19
|
+
export { ChooseThenAnswerGroupViewer } from './ChooseThenAnswerGroupViewer';
|
|
@@ -11,9 +11,10 @@ interface ReviewAnswerTheQuestionGroupRendererProps {
|
|
|
11
11
|
questionCount: number;
|
|
12
12
|
instruction: string;
|
|
13
13
|
groupContent?: string;
|
|
14
|
+
imageUrl?: string | string[];
|
|
14
15
|
questions: AnswerTheQuestionQuestion[];
|
|
15
16
|
answers: Record<string, string>;
|
|
16
17
|
itemMeta: Record<string, ReviewAnswerTheQuestionGroupItemMeta>;
|
|
17
18
|
}
|
|
18
|
-
export declare function ReviewAnswerTheQuestionGroupRenderer({ partNumber, partName, questionCount, instruction, groupContent, questions, answers, itemMeta, }: ReviewAnswerTheQuestionGroupRendererProps): import("react").JSX.Element;
|
|
19
|
+
export declare function ReviewAnswerTheQuestionGroupRenderer({ partNumber, partName, questionCount, instruction, groupContent, imageUrl, questions, answers, itemMeta, }: ReviewAnswerTheQuestionGroupRendererProps): import("react").JSX.Element;
|
|
19
20
|
export {};
|
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { BookOpen, Check, Sparkles, Star, X } from 'lucide-react';
|
|
4
|
+
import { ResolvedImage } from '../../../components/common/ResolvedImage';
|
|
4
5
|
import CambridgeYlePartBanner from '../../../components/themes/cambridge-yle/CambridgeYlePartBanner';
|
|
5
6
|
import CambridgeYleInstructionBanner from '../../../components/themes/cambridge-yle/CambridgeYleInstructionBanner';
|
|
6
|
-
|
|
7
|
-
|
|
7
|
+
import { toAnswerTheQuestionImageUrls } from '../../questions/_shared/types/answer-the-question.type';
|
|
8
|
+
function ReviewImages({ imageUrl, alt, }) {
|
|
9
|
+
const urls = toAnswerTheQuestionImageUrls(imageUrl);
|
|
10
|
+
if (urls.length === 0) {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
return (_jsx("div", { className: "flex flex-wrap justify-center gap-3", children: urls.map((url, imageIndex) => (_jsx(ResolvedImage, { src: url, alt: `${alt} ${imageIndex + 1}`, className: "max-h-56 w-auto rounded-lg object-contain" }, `${alt}-${imageIndex}`))) }));
|
|
14
|
+
}
|
|
15
|
+
export function ReviewAnswerTheQuestionGroupRenderer({ partNumber, partName, questionCount, instruction, groupContent, imageUrl, questions, answers, itemMeta, }) {
|
|
16
|
+
return (_jsxs("div", { className: "space-y-4", children: [_jsx(CambridgeYlePartBanner, { partNumber: partNumber, partName: partName, questionCount: questionCount }), _jsx(CambridgeYleInstructionBanner, { instruction: instruction }), _jsx(ReviewImages, { imageUrl: imageUrl, alt: "Shared illustration" }), 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
17
|
const meta = itemMeta[question.id];
|
|
9
18
|
const studentAnswer = answers[question.id] || '';
|
|
10
19
|
const expectedAnswers = meta?.expectedAnswers || [];
|
|
@@ -16,7 +25,7 @@ export function ReviewAnswerTheQuestionGroupRenderer({ partNumber, partName, que
|
|
|
16
25
|
? 'bg-green-500'
|
|
17
26
|
: isCorrect === false
|
|
18
27
|
? '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
|
|
28
|
+
: '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(ReviewImages, { imageUrl: question.imageUrl, alt: `Hình ảnh câu ${question.questionNumber}` }), _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
29
|
? 'border-green-200 bg-green-50 text-green-800'
|
|
21
30
|
: isCorrect === false
|
|
22
31
|
? 'border-red-200 bg-red-50 text-red-800'
|
|
@@ -115,7 +115,7 @@ export function renderDedicatedReviewBody(ctx) {
|
|
|
115
115
|
feedbackVi: typeof feedback.vi === 'string' ? feedback.vi : undefined,
|
|
116
116
|
}];
|
|
117
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 }));
|
|
118
|
+
return (_jsx(ReviewAnswerTheQuestionGroupRenderer, { partNumber: partNo, partName: part.name, questionCount: questionCount, instruction: part.instructions || data.instruction, groupContent: data.groupContent, imageUrl: data.imageUrl, questions: data.questions, answers: answers, itemMeta: itemMeta }));
|
|
119
119
|
}
|
|
120
120
|
case 'SPONTANEOUS_QA_GROUP': {
|
|
121
121
|
const parent = questions[0];
|
|
@@ -4,8 +4,8 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
4
4
|
* Movers fallback review cases (isReviewMode=true).
|
|
5
5
|
* Source: oe-exam-fe ReviewQuestionRenderer.tsx
|
|
6
6
|
*/
|
|
7
|
-
import { MoversAnswerTheQuestionRenderer, MoversFillWordHintRenderer, MoversGridFillRenderer, MoversLabelThePictureRenderer, MoversMatchByWritingAnswerRenderer, MoversMatchWordToPictureRenderer, MoversPictureChooseRenderer, MoversPictureFillBlankChooseRenderer, MoversReadingPassageRenderer, MoversTrueFalseRenderer, MoversTrueFalseCorrectGroupRenderer, MoversFillInBlankGroupRenderer, MoversMatchingWithLinesGroupRenderer, MoversCrossOutWordGroupRenderer, MoversWordFillParagraphRenderer, MoversWordFillStructuredFormRenderer, MoversWordOrderingRenderer, MoversWordOrderingGroupRenderer, MoversWriteSentencesRenderer, MoversWritingRenderer, MoversCrosswordPuzzleRenderer, } from '../../../components/exams/take/components/question-renderers';
|
|
8
|
-
import { transformAnswerTheQuestion, transformFillWordHint, transformGridFill, transformLabelPicture, transformMatchByWritingAnswer, transformMatchWordToPicture, transformPictureChoose, transformPictureFillBlankChoose, transformReadingPassage, transformTrueFalse, transformTrueFalseCorrectGroup, transformFillInBlankGroup, transformMatchingWithLinesGroup, transformWordFillParagraph, transformWordFillStructuredForm, transformWordOrdering, transformWordOrderingGroupTake, transformWriteSentences, transformWriteShortLetter, transformCrosswordPuzzle, } from '../../../components/exams/take/utils/question-transformers';
|
|
7
|
+
import { MoversAnswerTheQuestionRenderer, MoversFillWordHintRenderer, MoversGridFillRenderer, MoversLabelThePictureRenderer, MoversMatchByWritingAnswerRenderer, MoversMatchWordToPictureRenderer, MoversPictureChooseRenderer, MoversPictureFillBlankChooseRenderer, MoversReadingPassageRenderer, MoversTrueFalseRenderer, MoversTrueFalseCorrectGroupRenderer, MoversFillInBlankGroupRenderer, MoversMatchingWithLinesGroupRenderer, MoversCrossOutWordGroupRenderer, MoversWordFillParagraphRenderer, MoversWordFillStructuredFormRenderer, MoversWordOrderingRenderer, MoversWordOrderingGroupRenderer, MoversWriteSentencesRenderer, MoversWritingRenderer, MoversCrosswordPuzzleRenderer, ChooseThenAnswerGroupRenderer, SortWordsIntoCategoriesRenderer, } from '../../../components/exams/take/components/question-renderers';
|
|
8
|
+
import { transformAnswerTheQuestion, transformFillWordHint, transformGridFill, transformLabelPicture, transformMatchByWritingAnswer, transformMatchWordToPicture, transformPictureChoose, transformPictureFillBlankChoose, transformReadingPassage, transformTrueFalse, transformTrueFalseCorrectGroup, transformFillInBlankGroup, transformMatchingWithLinesGroup, transformWordFillParagraph, transformWordFillStructuredForm, transformWordOrdering, transformWordOrderingGroupTake, transformWriteSentences, transformWriteShortLetter, transformCrosswordPuzzle, transformChooseThenAnswerGroup, transformSortWordsIntoCategories, } from '../../../components/exams/take/utils/question-transformers';
|
|
9
9
|
import { normalizeCrossOutWordGroupAnswer, transformCrossOutWordGroup, } from '../../../components/exams/take/utils/cross-out-word-group.transformer';
|
|
10
10
|
function asApiQuestions(questions) {
|
|
11
11
|
return questions;
|
|
@@ -110,6 +110,28 @@ export function renderMoversReviewBody(ctx) {
|
|
|
110
110
|
});
|
|
111
111
|
return (_jsx(MoversCrossOutWordGroupRenderer, { partNumber: partNo, partName: part.name, instruction: part.instructions || data.instruction, questionCount: questionCount, questions: data.questions, answers: answers, onAnswerChange: handleAnswerChange, isReviewMode: true, correctAnswerMap: correctAnswerMap }));
|
|
112
112
|
}
|
|
113
|
+
case 'CHOOSE_THEN_ANSWER_GROUP': {
|
|
114
|
+
const data = transformChooseThenAnswerGroup(apiQuestions);
|
|
115
|
+
const gradableCount = data.questions.filter((item) => !item.isExample).length;
|
|
116
|
+
return (_jsx(ChooseThenAnswerGroupRenderer, { partNumber: partNo, partName: part.name, questionCount: gradableCount || questionCount, instruction: part.instructions || data.instruction, data: data, answers: answers, onAnswerChange: () => undefined, isReviewMode: true }));
|
|
117
|
+
}
|
|
118
|
+
case 'SORT_WORDS_INTO_CATEGORIES': {
|
|
119
|
+
const data = transformSortWordsIntoCategories(apiQuestions);
|
|
120
|
+
const firstQuestion = apiQuestions[0];
|
|
121
|
+
const stored = firstQuestion?.id ? answers[firstQuestion.id] : undefined;
|
|
122
|
+
const storedRecord = stored && typeof stored === 'object' && !Array.isArray(stored)
|
|
123
|
+
? stored
|
|
124
|
+
: undefined;
|
|
125
|
+
const storedPlacements = Array.isArray(storedRecord?.placements)
|
|
126
|
+
? storedRecord.placements
|
|
127
|
+
: Array.isArray(stored)
|
|
128
|
+
? stored
|
|
129
|
+
: [];
|
|
130
|
+
const gradableCount = data.words.filter((word) => !word.isExample).length;
|
|
131
|
+
return (_jsx(SortWordsIntoCategoriesRenderer, { partNumber: partNo, partName: part.name, questionCount: gradableCount || questionCount, instruction: part.instructions || data.instruction, data: data, answers: {
|
|
132
|
+
[data.questionId]: { placements: storedPlacements },
|
|
133
|
+
}, onAnswerChange: () => undefined, isReviewMode: true }));
|
|
134
|
+
}
|
|
113
135
|
case 'TRUE_FALSE_CORRECT_GROUP': {
|
|
114
136
|
const data = transformTrueFalseCorrectGroup(apiQuestions);
|
|
115
137
|
const correctAnswerMap = {};
|
|
@@ -9,6 +9,7 @@ const FLATTENED_ANSWER_TYPES = new Set([
|
|
|
9
9
|
'ANSWER_THE_QUESTION_GROUP',
|
|
10
10
|
'CROSS_OUT_WORD_GROUP',
|
|
11
11
|
'TRUE_FALSE_CORRECT_GROUP',
|
|
12
|
+
'CHOOSE_THEN_ANSWER_GROUP',
|
|
12
13
|
'FILL_IN_BLANK_GROUP',
|
|
13
14
|
'MATCHING_WITH_LINES_GROUP',
|
|
14
15
|
]);
|
|
@@ -186,6 +187,26 @@ function transformStudentAnswer(questionId, questionType, value) {
|
|
|
186
187
|
normalizeCrossOutWordGroupAnswer(item),
|
|
187
188
|
]));
|
|
188
189
|
}
|
|
190
|
+
if (questionType === 'CHOOSE_THEN_ANSWER_GROUP') {
|
|
191
|
+
const answer = Array.isArray(record.answer)
|
|
192
|
+
? record.answer
|
|
193
|
+
: Array.isArray(value)
|
|
194
|
+
? value
|
|
195
|
+
: [];
|
|
196
|
+
return Object.fromEntries(answer.map((item, index) => {
|
|
197
|
+
if (item && typeof item === 'object' && !Array.isArray(item)) {
|
|
198
|
+
const obj = item;
|
|
199
|
+
return [
|
|
200
|
+
`${questionId}-item-${index}`,
|
|
201
|
+
{
|
|
202
|
+
optionId: typeof obj.optionId === 'string' ? obj.optionId : '',
|
|
203
|
+
text: typeof obj.text === 'string' ? obj.text : '',
|
|
204
|
+
},
|
|
205
|
+
];
|
|
206
|
+
}
|
|
207
|
+
return [`${questionId}-item-${index}`, { optionId: '', text: '' }];
|
|
208
|
+
}));
|
|
209
|
+
}
|
|
189
210
|
if (questionType === 'TRUE_FALSE_CORRECT_GROUP') {
|
|
190
211
|
const answer = Array.isArray(record.answer)
|
|
191
212
|
? record.answer
|
|
@@ -282,6 +303,13 @@ function transformStudentAnswer(questionId, questionType, value) {
|
|
|
282
303
|
}
|
|
283
304
|
if (questionType === 'READ_AND_COLOR_OBJECTS' || questionType === 'IMAGE_OBJECT_MATCHING' || questionType === 'CROSSWORD_PUZZLE')
|
|
284
305
|
return record.cells ?? record.answers ?? value;
|
|
306
|
+
if (questionType === 'SORT_WORDS_INTO_CATEGORIES') {
|
|
307
|
+
if (Array.isArray(record.placements))
|
|
308
|
+
return record;
|
|
309
|
+
if (Array.isArray(value))
|
|
310
|
+
return { placements: value };
|
|
311
|
+
return value;
|
|
312
|
+
}
|
|
285
313
|
if (questionType === 'WRITE_A_SHORT_LETTER')
|
|
286
314
|
return typeof value === 'string' ? value : record.answer ?? '';
|
|
287
315
|
return record.answer ?? value;
|
package/dist/index.d.ts
CHANGED
|
@@ -26,4 +26,4 @@ export { validateQuestion } from './components/questions/_shared/utils/question-
|
|
|
26
26
|
export { buildAnswerPayload } from './components/questions/_shared/utils/answer-builder';
|
|
27
27
|
export type { QuestionFormState } from './components/questions/_shared/hooks/useQuestionFormState';
|
|
28
28
|
export * from './components/questions/_shared/types';
|
|
29
|
-
export { QuestionViewer, QuestionGroupViewer, WordFillStructuredFormViewer, ChooseTheCorrectAnswerGroupViewer, TrueFalseGroupViewer, TrueFalseCorrectGroupViewer, FillInBlankGroupViewer, MatchingWithLinesGroupViewer, CrossOutWordGroupViewer, SpontaneousQaGroupViewer, WordOrderAndMatchGroupViewer, AnswerTheQuestionGroupViewer, MatchWordToPictureViewer, MatchWordToPictureGroupViewer, MatchColumnsToMakeSentencesViewer, WordOrderingGroupViewer, } from './components/questions/viewer';
|
|
29
|
+
export { QuestionViewer, QuestionGroupViewer, WordFillStructuredFormViewer, ChooseTheCorrectAnswerGroupViewer, TrueFalseGroupViewer, TrueFalseCorrectGroupViewer, FillInBlankGroupViewer, MatchingWithLinesGroupViewer, CrossOutWordGroupViewer, SpontaneousQaGroupViewer, WordOrderAndMatchGroupViewer, AnswerTheQuestionGroupViewer, MatchWordToPictureViewer, MatchWordToPictureGroupViewer, MatchColumnsToMakeSentencesViewer, WordOrderingGroupViewer, SortWordsIntoCategoriesViewer, ChooseThenAnswerGroupViewer, } from './components/questions/viewer';
|
package/dist/index.js
CHANGED
|
@@ -21,4 +21,4 @@ export { QUESTION_TYPE_CONFIG, QUESTION_TYPES_WITH_DEDICATED_COMPONENTS } from '
|
|
|
21
21
|
export { validateQuestion } from './components/questions/_shared/utils/question-validation';
|
|
22
22
|
export { buildAnswerPayload } from './components/questions/_shared/utils/answer-builder';
|
|
23
23
|
export * from './components/questions/_shared/types';
|
|
24
|
-
export { QuestionViewer, QuestionGroupViewer, WordFillStructuredFormViewer, ChooseTheCorrectAnswerGroupViewer, TrueFalseGroupViewer, TrueFalseCorrectGroupViewer, FillInBlankGroupViewer, MatchingWithLinesGroupViewer, CrossOutWordGroupViewer, SpontaneousQaGroupViewer, WordOrderAndMatchGroupViewer, AnswerTheQuestionGroupViewer, MatchWordToPictureViewer, MatchWordToPictureGroupViewer, MatchColumnsToMakeSentencesViewer, WordOrderingGroupViewer, } from './components/questions/viewer';
|
|
24
|
+
export { QuestionViewer, QuestionGroupViewer, WordFillStructuredFormViewer, ChooseTheCorrectAnswerGroupViewer, TrueFalseGroupViewer, TrueFalseCorrectGroupViewer, FillInBlankGroupViewer, MatchingWithLinesGroupViewer, CrossOutWordGroupViewer, SpontaneousQaGroupViewer, WordOrderAndMatchGroupViewer, AnswerTheQuestionGroupViewer, MatchWordToPictureViewer, MatchWordToPictureGroupViewer, MatchColumnsToMakeSentencesViewer, WordOrderingGroupViewer, SortWordsIntoCategoriesViewer, ChooseThenAnswerGroupViewer, } from './components/questions/viewer';
|
|
@@ -84,6 +84,8 @@ export const QUESTION_TYPE_SKILLS = {
|
|
|
84
84
|
'WORD_FILL_STRUCTURED_FORM': SkillEnum.READING,
|
|
85
85
|
'CROSSWORD_PUZZLE': SkillEnum.READING,
|
|
86
86
|
'FIND_WORDS_IN_MATRIX': SkillEnum.READING,
|
|
87
|
+
'SORT_WORDS_INTO_CATEGORIES': SkillEnum.READING,
|
|
88
|
+
'CHOOSE_THEN_ANSWER_GROUP': SkillEnum.READING,
|
|
87
89
|
'MATCH_COLUMNS_TO_MAKE_SENTENCES': SkillEnum.READING,
|
|
88
90
|
'MATCH_WORD_TO_PICTURE_GROUP': SkillEnum.READING,
|
|
89
91
|
'WORD_ORDERING_GROUP': SkillEnum.READING,
|
|
@@ -219,6 +221,8 @@ export const QUESTION_TYPE_LABELS = {
|
|
|
219
221
|
'WORD_FILL_STRUCTURED_FORM': 'Điền từ vào biểu mẫu có cấu trúc',
|
|
220
222
|
'CROSSWORD_PUZZLE': 'Giải ô chữ (Crossword)',
|
|
221
223
|
'FIND_WORDS_IN_MATRIX': 'Tìm từ trong ma trận (Word Search)',
|
|
224
|
+
'SORT_WORDS_INTO_CATEGORIES': 'Sắp xếp từ vào nhóm',
|
|
225
|
+
'CHOOSE_THEN_ANSWER_GROUP': 'Chọn đáp án rồi trả lời',
|
|
222
226
|
'MATCH_COLUMNS_TO_MAKE_SENTENCES': 'Ghép cột thành câu',
|
|
223
227
|
'MATCH_WORD_TO_PICTURE_GROUP': 'Nối từ với tranh (nhóm)',
|
|
224
228
|
'WORD_ORDERING_GROUP': 'Sắp xếp từ (nhóm)',
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { fromFillInBlankImageUrls, toFillInBlankImageUrls, } from '../../../components/questions/types/fill-in-blank-group/blank-utils';
|
|
2
|
+
import { fromMatchWordToPictureImageUrls } from '../../../components/questions/_shared/types/match-word-to-picture-group.type';
|
|
2
3
|
const isRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
3
4
|
const asRecord = (value) => isRecord(value) ? value : {};
|
|
4
5
|
const asArray = (value) => Array.isArray(value) ? value : [];
|
|
@@ -302,15 +303,18 @@ const reverseAnswerTheQuestionGroup = (apiQuestion) => {
|
|
|
302
303
|
.filter((value) => value !== '');
|
|
303
304
|
const expectedAnswers = nested.length > 0 ? nested : (fallbackExpected.length > 0 ? fallbackExpected : ['']);
|
|
304
305
|
const isExample = item.isExample === true;
|
|
306
|
+
const imageUrl = normalizeChooseAnswerGroupImageUrl(itemContent.imageUrl);
|
|
305
307
|
return {
|
|
306
308
|
question: asString(itemContent.question),
|
|
307
309
|
expectedAnswers,
|
|
308
310
|
isExample,
|
|
309
311
|
points: isExample ? 0 : asNumber(item.points, 1),
|
|
310
312
|
questionNumber: asNumber(item.questionNumber, index + 1),
|
|
313
|
+
...(imageUrl !== undefined ? { imageUrl } : {}),
|
|
311
314
|
};
|
|
312
315
|
});
|
|
313
316
|
const meta = asRecord(content.meta);
|
|
317
|
+
const audioUrl = asString(meta.audioUrl).trim();
|
|
314
318
|
const imageUrl = asArray(meta.imageUrl)
|
|
315
319
|
.filter((value) => typeof value === 'string')
|
|
316
320
|
.map((value) => value.trim())
|
|
@@ -321,6 +325,7 @@ const reverseAnswerTheQuestionGroup = (apiQuestion) => {
|
|
|
321
325
|
answer: {
|
|
322
326
|
instruction: asString(meta.instruction, 'Read the text again and answer the questions.'),
|
|
323
327
|
passage: asString(meta.passage),
|
|
328
|
+
...(audioUrl ? { audioUrl } : {}),
|
|
324
329
|
...(imageUrl.length ? { imageUrl } : (singleImage ? { imageUrl: [singleImage] } : {})),
|
|
325
330
|
items,
|
|
326
331
|
explanation: asString(apiQuestion.explanation),
|
|
@@ -391,6 +396,45 @@ const reverseTrueFalseCorrectGroup = (apiQuestion) => {
|
|
|
391
396
|
},
|
|
392
397
|
};
|
|
393
398
|
};
|
|
399
|
+
const reverseChooseThenAnswerGroup = (apiQuestion) => {
|
|
400
|
+
const content = asRecord(apiQuestion.content);
|
|
401
|
+
const answer = readAnswer(apiQuestion);
|
|
402
|
+
const fallbackAnswers = asArray(answer.answer);
|
|
403
|
+
const meta = asRecord(content.meta);
|
|
404
|
+
const items = asArray(content.items).map((rawItem, index) => {
|
|
405
|
+
const item = asRecord(rawItem);
|
|
406
|
+
const itemContent = asRecord(item.content);
|
|
407
|
+
const itemAnswer = asRecord(asRecord(item.correctAnswer).answer);
|
|
408
|
+
const fallback = asRecord(fallbackAnswers[index]);
|
|
409
|
+
const rawAnswer = Object.keys(itemAnswer).length > 0 ? itemAnswer : fallback;
|
|
410
|
+
const isExample = item.isExample === true;
|
|
411
|
+
const options = asArray(itemContent.options).map((rawOption, optionIndex) => {
|
|
412
|
+
const option = asRecord(rawOption);
|
|
413
|
+
return {
|
|
414
|
+
id: asString(option.id, `opt-${optionIndex + 1}`),
|
|
415
|
+
text: asString(option.text),
|
|
416
|
+
};
|
|
417
|
+
});
|
|
418
|
+
return {
|
|
419
|
+
question: asString(itemContent.question),
|
|
420
|
+
options,
|
|
421
|
+
optionId: asString(rawAnswer.optionId) || options[0]?.id || '',
|
|
422
|
+
isExample,
|
|
423
|
+
points: isExample ? 0 : asNumber(item.points, 1),
|
|
424
|
+
questionNumber: asNumber(item.questionNumber, index + 1),
|
|
425
|
+
};
|
|
426
|
+
});
|
|
427
|
+
return {
|
|
428
|
+
content: '',
|
|
429
|
+
answer: {
|
|
430
|
+
title: asString(meta.title),
|
|
431
|
+
instruction: asString(meta.instruction, 'Read and choose. Then answer for you.'),
|
|
432
|
+
items,
|
|
433
|
+
explanation: asString(apiQuestion.explanation),
|
|
434
|
+
points: items.reduce((total, item) => (item.isExample ? total : total + item.points), 0),
|
|
435
|
+
},
|
|
436
|
+
};
|
|
437
|
+
};
|
|
394
438
|
const reverseFillInBlankGroup = (apiQuestion) => {
|
|
395
439
|
const content = asRecord(apiQuestion.content);
|
|
396
440
|
const answer = readAnswer(apiQuestion);
|
|
@@ -680,8 +724,9 @@ const reverseMatchWordToPictureGroup = (apiQuestion) => {
|
|
|
680
724
|
const itemContent = asRecord(item.content);
|
|
681
725
|
const itemAnswer = asRecord(item.correctAnswer);
|
|
682
726
|
const isExample = item.isExample === true;
|
|
727
|
+
const itemImageUrl = fromMatchWordToPictureImageUrls(itemContent.imageUrl ?? item.imageUrl);
|
|
683
728
|
return {
|
|
684
|
-
imageUrl:
|
|
729
|
+
...(itemImageUrl ? { imageUrl: itemImageUrl } : {}),
|
|
685
730
|
correctAnswer: asString(itemAnswer.answer) || asString(fallbackAnswers[index]),
|
|
686
731
|
isExample,
|
|
687
732
|
points: isExample ? 0 : (typeof item.points === 'number' ? item.points : 1),
|
|
@@ -693,7 +738,11 @@ const reverseMatchWordToPictureGroup = (apiQuestion) => {
|
|
|
693
738
|
answer: {
|
|
694
739
|
...(asString(meta.title).trim() ? { title: asString(meta.title).trim() } : {}),
|
|
695
740
|
instruction: asString(meta.instruction, 'Look and complete.'),
|
|
696
|
-
...(asString(meta.
|
|
741
|
+
...(asString(meta.passage).trim() ? { passage: asString(meta.passage).trim() } : {}),
|
|
742
|
+
...(asString(meta.audioUrl).trim() ? { audioUrl: asString(meta.audioUrl).trim() } : {}),
|
|
743
|
+
...(fromMatchWordToPictureImageUrls(meta.imageUrl)
|
|
744
|
+
? { imageUrl: fromMatchWordToPictureImageUrls(meta.imageUrl) }
|
|
745
|
+
: {}),
|
|
697
746
|
wordBank: asArray(meta.wordBank).map((word) => asString(word)).filter(Boolean),
|
|
698
747
|
items: mappedItems,
|
|
699
748
|
explanation: asString(apiQuestion.explanation),
|
|
@@ -741,6 +790,7 @@ const handlers = {
|
|
|
741
790
|
ANSWER_THE_QUESTION_GROUP: reverseAnswerTheQuestionGroup,
|
|
742
791
|
TRUE_FALSE_GROUP: reverseTrueFalseGroup,
|
|
743
792
|
TRUE_FALSE_CORRECT_GROUP: reverseTrueFalseCorrectGroup,
|
|
793
|
+
CHOOSE_THEN_ANSWER_GROUP: reverseChooseThenAnswerGroup,
|
|
744
794
|
FILL_IN_BLANK_GROUP: reverseFillInBlankGroup,
|
|
745
795
|
MATCHING_WITH_LINES_GROUP: reverseMatchingWithLinesGroup,
|
|
746
796
|
CROSS_OUT_WORD_GROUP: reverseCrossOutWordGroup,
|
|
@@ -785,6 +835,7 @@ const handlers = {
|
|
|
785
835
|
GN_SPEAKING_INTERVIEW: reverseGroupPayload,
|
|
786
836
|
CROSSWORD_PUZZLE: reverseCrosswordPuzzle,
|
|
787
837
|
FIND_WORDS_IN_MATRIX: reverseFindWordsInMatrix,
|
|
838
|
+
SORT_WORDS_INTO_CATEGORIES: reverseSortWordsIntoCategories,
|
|
788
839
|
MATCH_COLUMNS_TO_MAKE_SENTENCES: reverseMatchColumnsToMakeSentences,
|
|
789
840
|
MATCH_WORD_TO_PICTURE_GROUP: reverseMatchWordToPictureGroup,
|
|
790
841
|
WORD_ORDERING_GROUP: reverseWordOrderingGroup,
|
|
@@ -959,6 +1010,51 @@ function reverseFindWordsInMatrix(apiQuestion) {
|
|
|
959
1010
|
},
|
|
960
1011
|
};
|
|
961
1012
|
}
|
|
1013
|
+
function reverseSortWordsIntoCategories(apiQuestion) {
|
|
1014
|
+
const apiContent = asRecord(apiQuestion.content);
|
|
1015
|
+
const apiAnswer = readAnswer(apiQuestion);
|
|
1016
|
+
const meta = asRecord(apiContent.meta);
|
|
1017
|
+
const categories = asArray(apiContent.categories);
|
|
1018
|
+
const words = asArray(apiContent.words);
|
|
1019
|
+
const placements = asArray(apiAnswer.placements);
|
|
1020
|
+
const placementMap = new Map();
|
|
1021
|
+
placements.forEach((item) => {
|
|
1022
|
+
const record = asRecord(item);
|
|
1023
|
+
const wordId = asString(record.wordId);
|
|
1024
|
+
if (wordId && !placementMap.has(wordId)) {
|
|
1025
|
+
placementMap.set(wordId, asString(record.categoryId));
|
|
1026
|
+
}
|
|
1027
|
+
});
|
|
1028
|
+
const mappedWords = words.map((raw, index) => {
|
|
1029
|
+
const item = asRecord(raw);
|
|
1030
|
+
const id = asString(item.id, `w${index + 1}`);
|
|
1031
|
+
const isExample = item.isExample === true;
|
|
1032
|
+
return {
|
|
1033
|
+
id,
|
|
1034
|
+
text: asString(item.text),
|
|
1035
|
+
categoryId: placementMap.get(id) || '',
|
|
1036
|
+
isExample,
|
|
1037
|
+
points: isExample ? 0 : (typeof item.points === 'number' ? item.points : 1),
|
|
1038
|
+
};
|
|
1039
|
+
});
|
|
1040
|
+
return {
|
|
1041
|
+
content: '',
|
|
1042
|
+
answer: {
|
|
1043
|
+
title: asString(meta.title),
|
|
1044
|
+
instruction: asString(meta.instruction, 'Read and sort the words.'),
|
|
1045
|
+
categories: categories.map((raw, index) => {
|
|
1046
|
+
const item = asRecord(raw);
|
|
1047
|
+
return {
|
|
1048
|
+
id: asString(item.id, `cat_${index + 1}`),
|
|
1049
|
+
name: asString(item.name),
|
|
1050
|
+
};
|
|
1051
|
+
}),
|
|
1052
|
+
words: mappedWords,
|
|
1053
|
+
explanation: asString(apiQuestion.explanation),
|
|
1054
|
+
points: mappedWords.reduce((total, word) => (word.isExample ? total : total + (word.points || 0)), 0),
|
|
1055
|
+
},
|
|
1056
|
+
};
|
|
1057
|
+
}
|
|
962
1058
|
export function transformApiQuestionToFrontend(apiQuestion) {
|
|
963
1059
|
const questionType = String(apiQuestion.questionType ?? apiQuestion.question_type ?? apiQuestion.type ?? '');
|
|
964
1060
|
const handlerResult = (handlers[questionType] ?? defaultReverse)(apiQuestion);
|
|
@@ -312,7 +312,7 @@ export interface AnswerTheQuestionAnswerData {
|
|
|
312
312
|
groupNumber?: number;
|
|
313
313
|
isExample?: boolean;
|
|
314
314
|
suggestedAnswers?: string[];
|
|
315
|
-
imageUrl?: string;
|
|
315
|
+
imageUrl?: string | string[];
|
|
316
316
|
title?: string;
|
|
317
317
|
subtitle?: string;
|
|
318
318
|
groupContent?: string;
|
|
@@ -47,6 +47,8 @@ import { transformGNSpeakingInterview } from '../../../components/questions/type
|
|
|
47
47
|
import { transformWordFillStructuredForm } from '../../../components/questions/types/word-fill-structured-form/transform';
|
|
48
48
|
import { transformCrosswordPuzzle } from '../../../components/questions/types/crossword-puzzle/transform';
|
|
49
49
|
import { transformFindWordsInMatrix } from '../../../components/questions/types/find-words-in-matrix/transform';
|
|
50
|
+
import { transformSortWordsIntoCategories } from '../../../components/questions/types/sort-words-into-categories/transform';
|
|
51
|
+
import { transformChooseThenAnswerGroup } from '../../../components/questions/types/choose-then-answer-group/transform';
|
|
50
52
|
import { transformMatchColumnsToMakeSentences } from '../../../components/questions/types/match-columns-to-make-sentences/transform';
|
|
51
53
|
import { transformMatchWordToPictureGroup } from '../../../components/questions/types/match-word-to-picture-group/transform';
|
|
52
54
|
import { transformWordOrderingGroup } from '../../../components/questions/types/word-ordering-group/transform';
|
|
@@ -103,6 +105,8 @@ const questionTransformHandlers = {
|
|
|
103
105
|
WORD_FILL_STRUCTURED_FORM: transformWordFillStructuredForm,
|
|
104
106
|
CROSSWORD_PUZZLE: transformCrosswordPuzzle,
|
|
105
107
|
FIND_WORDS_IN_MATRIX: transformFindWordsInMatrix,
|
|
108
|
+
SORT_WORDS_INTO_CATEGORIES: transformSortWordsIntoCategories,
|
|
109
|
+
CHOOSE_THEN_ANSWER_GROUP: transformChooseThenAnswerGroup,
|
|
106
110
|
MATCH_COLUMNS_TO_MAKE_SENTENCES: transformMatchColumnsToMakeSentences,
|
|
107
111
|
MATCH_WORD_TO_PICTURE_GROUP: transformMatchWordToPictureGroup,
|
|
108
112
|
WORD_ORDERING_GROUP: transformWordOrderingGroup,
|
|
@@ -29,7 +29,7 @@ export declare enum StudentSelectionType {
|
|
|
29
29
|
}
|
|
30
30
|
export type ResultStatus = 'PENDING' | 'PARTIAL_GRADED' | 'GRADED';
|
|
31
31
|
export type StudentStatus = 'NOT_STARTED' | 'IN_PROGRESS' | 'SUBMITTED';
|
|
32
|
-
export type QuestionType = 'FILL_IN_BLANK' | 'TRUE_FALSE' | 'MATCHING' | 'ORDERING' | 'LISTENING' | 'COLORING' | 'ESSAY' | 'LISTEN_AND_FILL_IN_THE_BLANK_WITH_IMAGE' | 'LISTEN_AND_TICK_ANSWER' | 'LISTEN_AND_FILL_NAME_NUMBER' | 'LISTENING_FILL_BLANK' | 'LISTENING_COLOR' | 'TICK_TRUE_OR_FALSE' | 'TICK_YES_OR_NO' | 'READING_TICK_CROSS' | 'ARRANGE_LETTERS_INTO_WORDS' | 'READ_PASSAGE_AND_COMPLETE_STORY' | 'CLOZE_TEST_WITH_TICK_BOX' | 'READ_AND_CHOOSE_APPROPRIATE_WORD' | 'WRITE_SHORT_PARAGRAPH' | 'LABEL_THE_PICTURE' | 'READING_LETTER_ARRANGE' | 'READING_PASSAGE_FILL' | 'FILL_BLANK' | 'FILL_MISSING_WORDS_IN_GRID' | 'LOOK_PICTURE_CHOOSE_CORRECT_ANSWER' | 'LOOK_PICTURE_FILL_BLANK_CHOOSE_ANSWER' | 'LOOK_PICTURE_FILL_WORD_HINT' | 'LABEL_THE_PICTURE' | 'READ_AND_COLOR_OBJECTS' | 'IMAGE_OBJECT_MATCHING' | 'READ_PASSAGE_AND_ANSWER_QUESTIONS' | 'CHOOSE_THE_CORRECT_ANSWER' | 'CHOOSE_THE_CORRECT_ANSWER_GROUP' | 'TRUE_FALSE_GROUP' | 'TRUE_FALSE_CORRECT_GROUP' | 'FILL_IN_BLANK_GROUP' | 'MATCHING_WITH_LINES_GROUP' | 'CROSS_OUT_WORD_GROUP' | 'SPONTANEOUS_QA_GROUP' | 'WORD_ORDER_AND_MATCH_GROUP' | 'WRITE_A_SHORT_LETTER' | 'WRITE_CORRECT_VERB_FORM' | 'CHOOSE_CORRECT_ADJECTIVE' | 'ANSWER_THE_QUESTION' | 'ANSWER_THE_QUESTION_GROUP' | 'WORD_ORDERING' | 'WORD_FILL_PARAGRAPH' | 'MATCH_BY_WRITING_ANSWER' | 'MATCH_WORD_TO_PICTURE' | 'WRITE_SENTENCES' | 'LISTEN_AND_CHOOSE_FROM_ANSWER_GROUP' | 'LISTEN_AND_CHOOSE_OBJECTS_IN_SCENE' | 'LISTEN_AND_DRAG_OBJECTS_INTO_SCENE' | 'LISTEN_AND_SPEAK_ANSWER' | 'LISTEN_AND_SPEAK_IMAGE_GROUP' | 'LISTEN_AND_SPEAK_QUESTION_LIST' | 'LISTEN_AND_SPEAK_COMPARE_IMAGES' | 'LISTEN_AND_SPEAK_WITH_STORY_IMAGES' | 'LISTEN_AND_SPEAK_ODD_ONE_OUT' | 'LISTEN_AND_SPEAK_INFO_EXCHANGE' | 'READ_DISPLAYED_CONTENT' | 'SPEAKING_DESCRIBE_IMAGE' | 'SPONTANEOUS_QA' | 'SPEAKING_CONVERSATION' | 'GN_SPEAKING_INTERVIEW' | 'SPEAKING_CUE_CARD' | 'WORD_FILL_STRUCTURED_FORM' | 'CROSSWORD_PUZZLE' | 'FIND_WORDS_IN_MATRIX' | 'MATCH_COLUMNS_TO_MAKE_SENTENCES' | 'MATCH_WORD_TO_PICTURE_GROUP' | 'WORD_ORDERING_GROUP';
|
|
32
|
+
export type QuestionType = 'FILL_IN_BLANK' | 'TRUE_FALSE' | 'MATCHING' | 'ORDERING' | 'LISTENING' | 'COLORING' | 'ESSAY' | 'LISTEN_AND_FILL_IN_THE_BLANK_WITH_IMAGE' | 'LISTEN_AND_TICK_ANSWER' | 'LISTEN_AND_FILL_NAME_NUMBER' | 'LISTENING_FILL_BLANK' | 'LISTENING_COLOR' | 'TICK_TRUE_OR_FALSE' | 'TICK_YES_OR_NO' | 'READING_TICK_CROSS' | 'ARRANGE_LETTERS_INTO_WORDS' | 'READ_PASSAGE_AND_COMPLETE_STORY' | 'CLOZE_TEST_WITH_TICK_BOX' | 'READ_AND_CHOOSE_APPROPRIATE_WORD' | 'WRITE_SHORT_PARAGRAPH' | 'LABEL_THE_PICTURE' | 'READING_LETTER_ARRANGE' | 'READING_PASSAGE_FILL' | 'FILL_BLANK' | 'FILL_MISSING_WORDS_IN_GRID' | 'LOOK_PICTURE_CHOOSE_CORRECT_ANSWER' | 'LOOK_PICTURE_FILL_BLANK_CHOOSE_ANSWER' | 'LOOK_PICTURE_FILL_WORD_HINT' | 'LABEL_THE_PICTURE' | 'READ_AND_COLOR_OBJECTS' | 'IMAGE_OBJECT_MATCHING' | 'READ_PASSAGE_AND_ANSWER_QUESTIONS' | 'CHOOSE_THE_CORRECT_ANSWER' | 'CHOOSE_THE_CORRECT_ANSWER_GROUP' | 'TRUE_FALSE_GROUP' | 'TRUE_FALSE_CORRECT_GROUP' | 'FILL_IN_BLANK_GROUP' | 'MATCHING_WITH_LINES_GROUP' | 'CROSS_OUT_WORD_GROUP' | 'SPONTANEOUS_QA_GROUP' | 'WORD_ORDER_AND_MATCH_GROUP' | 'WRITE_A_SHORT_LETTER' | 'WRITE_CORRECT_VERB_FORM' | 'CHOOSE_CORRECT_ADJECTIVE' | 'ANSWER_THE_QUESTION' | 'ANSWER_THE_QUESTION_GROUP' | 'WORD_ORDERING' | 'WORD_FILL_PARAGRAPH' | 'MATCH_BY_WRITING_ANSWER' | 'MATCH_WORD_TO_PICTURE' | 'WRITE_SENTENCES' | 'LISTEN_AND_CHOOSE_FROM_ANSWER_GROUP' | 'LISTEN_AND_CHOOSE_OBJECTS_IN_SCENE' | 'LISTEN_AND_DRAG_OBJECTS_INTO_SCENE' | 'LISTEN_AND_SPEAK_ANSWER' | 'LISTEN_AND_SPEAK_IMAGE_GROUP' | 'LISTEN_AND_SPEAK_QUESTION_LIST' | 'LISTEN_AND_SPEAK_COMPARE_IMAGES' | 'LISTEN_AND_SPEAK_WITH_STORY_IMAGES' | 'LISTEN_AND_SPEAK_ODD_ONE_OUT' | 'LISTEN_AND_SPEAK_INFO_EXCHANGE' | 'READ_DISPLAYED_CONTENT' | 'SPEAKING_DESCRIBE_IMAGE' | 'SPONTANEOUS_QA' | 'SPEAKING_CONVERSATION' | 'GN_SPEAKING_INTERVIEW' | 'SPEAKING_CUE_CARD' | 'WORD_FILL_STRUCTURED_FORM' | 'CROSSWORD_PUZZLE' | 'FIND_WORDS_IN_MATRIX' | 'SORT_WORDS_INTO_CATEGORIES' | 'CHOOSE_THEN_ANSWER_GROUP' | 'MATCH_COLUMNS_TO_MAKE_SENTENCES' | 'MATCH_WORD_TO_PICTURE_GROUP' | 'WORD_ORDERING_GROUP';
|
|
33
33
|
export type Level = 'Pre-A1' | 'A1' | 'A2' | 'B1' | 'B2' | 'C1' | 'C2';
|
|
34
34
|
export type Difficulty = 'EASY' | 'MEDIUM' | 'HARD';
|
|
35
35
|
export type Skill = 'LISTENING' | 'READING' | 'WRITING' | 'SPEAKING';
|