@tinyweb_dev/oe-exam-sdk 0.2.8 → 0.2.10
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 +7 -3
- package/dist/components/exams/take/components/question-renderers/MoversChooseBestAnswerRenderer.js +5 -4
- package/dist/components/exams/take/components/question-renderers/MoversWordOrderingGroupRenderer.d.ts +13 -0
- package/dist/components/exams/take/components/question-renderers/MoversWordOrderingGroupRenderer.js +30 -0
- package/dist/components/exams/take/components/question-renderers/index.d.ts +1 -0
- package/dist/components/exams/take/components/question-renderers/index.js +1 -0
- package/dist/components/exams/take/utils/answer-transformers.js +8 -1
- package/dist/components/exams/take/utils/question-transformers.d.ts +16 -1
- package/dist/components/exams/take/utils/question-transformers.js +49 -4
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.js +1 -0
- package/dist/components/questions/_shared/config/question-types.config.js +18 -0
- package/dist/components/questions/_shared/types/choose-the-correct-answer-group.type.d.ts +1 -0
- package/dist/components/questions/_shared/types/index.d.ts +1 -0
- package/dist/components/questions/_shared/types/index.js +1 -0
- package/dist/components/questions/_shared/types/match-columns-to-make-sentences.type.d.ts +56 -0
- package/dist/components/questions/_shared/types/match-columns-to-make-sentences.type.js +2 -0
- package/dist/components/questions/_shared/types/match-word-to-picture-group.type.d.ts +35 -0
- package/dist/components/questions/_shared/types/match-word-to-picture-group.type.js +2 -0
- package/dist/components/questions/_shared/types/word-ordering-group.type.d.ts +34 -0
- package/dist/components/questions/_shared/types/word-ordering-group.type.js +2 -0
- package/dist/components/questions/components/QuestionSearchDropdown.js +3 -0
- package/dist/components/questions/components/QuestionTypeSelector.js +3 -0
- package/dist/components/questions/creator/question-type-registry.js +6 -0
- package/dist/components/questions/types/choose-the-correct-answer/ChooseTheCorrectAnswerClient.js +6 -4
- package/dist/components/questions/types/choose-the-correct-answer/ChooseTheCorrectAnswerCreator.js +16 -4
- package/dist/components/questions/types/choose-the-correct-answer-group/ChooseTheCorrectAnswerGroupClient.js +2 -3
- package/dist/components/questions/types/choose-the-correct-answer-group/ChooseTheCorrectAnswerGroupCreator.js +6 -4
- package/dist/components/questions/types/choose-the-correct-answer-group/map-choose-the-correct-answer-group-data.js +16 -2
- package/dist/components/questions/types/choose-the-correct-answer-group/transform.js +4 -0
- package/dist/components/questions/types/match-columns-to-make-sentences/MatchColumnsToMakeSentencesClient.d.ts +2 -0
- package/dist/components/questions/types/match-columns-to-make-sentences/MatchColumnsToMakeSentencesClient.js +74 -0
- package/dist/components/questions/types/match-columns-to-make-sentences/MatchColumnsToMakeSentencesCreator.d.ts +2 -0
- package/dist/components/questions/types/match-columns-to-make-sentences/MatchColumnsToMakeSentencesCreator.js +222 -0
- package/dist/components/questions/types/match-columns-to-make-sentences/index.d.ts +5 -0
- package/dist/components/questions/types/match-columns-to-make-sentences/index.js +5 -0
- package/dist/components/questions/types/match-columns-to-make-sentences/map-match-columns-to-make-sentences-data.d.ts +8 -0
- package/dist/components/questions/types/match-columns-to-make-sentences/map-match-columns-to-make-sentences-data.js +199 -0
- package/dist/components/questions/types/match-columns-to-make-sentences/register.d.ts +2 -0
- package/dist/components/questions/types/match-columns-to-make-sentences/register.js +36 -0
- package/dist/components/questions/types/match-columns-to-make-sentences/transform.d.ts +2 -0
- package/dist/components/questions/types/match-columns-to-make-sentences/transform.js +66 -0
- package/dist/components/questions/types/match-word-to-picture-group/MatchWordToPictureGroupClient.d.ts +2 -0
- package/dist/components/questions/types/match-word-to-picture-group/MatchWordToPictureGroupClient.js +65 -0
- package/dist/components/questions/types/match-word-to-picture-group/MatchWordToPictureGroupCreator.d.ts +2 -0
- package/dist/components/questions/types/match-word-to-picture-group/MatchWordToPictureGroupCreator.js +95 -0
- package/dist/components/questions/types/match-word-to-picture-group/MatchWordToPictureGroupImageField.d.ts +6 -0
- package/dist/components/questions/types/match-word-to-picture-group/MatchWordToPictureGroupImageField.js +16 -0
- package/dist/components/questions/types/match-word-to-picture-group/index.d.ts +5 -0
- package/dist/components/questions/types/match-word-to-picture-group/index.js +5 -0
- package/dist/components/questions/types/match-word-to-picture-group/map-match-word-to-picture-group-data.d.ts +4 -0
- package/dist/components/questions/types/match-word-to-picture-group/map-match-word-to-picture-group-data.js +77 -0
- package/dist/components/questions/types/match-word-to-picture-group/register.d.ts +2 -0
- package/dist/components/questions/types/match-word-to-picture-group/register.js +36 -0
- package/dist/components/questions/types/match-word-to-picture-group/transform.d.ts +2 -0
- package/dist/components/questions/types/match-word-to-picture-group/transform.js +36 -0
- package/dist/components/questions/types/word-ordering-group/WordOrderingGroupClient.d.ts +2 -0
- package/dist/components/questions/types/word-ordering-group/WordOrderingGroupClient.js +25 -0
- package/dist/components/questions/types/word-ordering-group/WordOrderingGroupCreator.d.ts +2 -0
- package/dist/components/questions/types/word-ordering-group/WordOrderingGroupCreator.js +106 -0
- package/dist/components/questions/types/word-ordering-group/index.d.ts +5 -0
- package/dist/components/questions/types/word-ordering-group/index.js +5 -0
- package/dist/components/questions/types/word-ordering-group/map-word-ordering-group-data.d.ts +4 -0
- package/dist/components/questions/types/word-ordering-group/map-word-ordering-group-data.js +74 -0
- package/dist/components/questions/types/word-ordering-group/register.d.ts +2 -0
- package/dist/components/questions/types/word-ordering-group/register.js +36 -0
- package/dist/components/questions/types/word-ordering-group/transform.d.ts +2 -0
- package/dist/components/questions/types/word-ordering-group/transform.js +33 -0
- package/dist/components/questions/viewer/MatchColumnsToMakeSentencesViewer.d.ts +8 -0
- package/dist/components/questions/viewer/MatchColumnsToMakeSentencesViewer.js +49 -0
- package/dist/components/questions/viewer/MatchWordToPictureGroupViewer.js +2 -2
- package/dist/components/questions/viewer/QuestionViewer.js +6 -0
- package/dist/components/questions/viewer/WordOrderingGroupViewer.d.ts +7 -0
- package/dist/components/questions/viewer/WordOrderingGroupViewer.js +29 -0
- package/dist/components/questions/viewer/index.d.ts +2 -0
- package/dist/components/questions/viewer/index.js +2 -0
- package/dist/components/results/renderers/ReviewChooseBestAnswerRenderer.js +3 -2
- package/dist/components/results/renderers/review-question-body-dedicated.js +1 -1
- package/dist/components/results/renderers/review-question-body-movers.js +6 -2
- package/dist/components/results/review-data.js +13 -0
- package/dist/components/shared/RichTextEditor.d.ts +4 -2
- package/dist/components/shared/RichTextEditor.js +34 -11
- package/dist/components/shared/RichTextHtml.d.ts +8 -0
- package/dist/components/shared/RichTextHtml.js +9 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/shared/constants/question-skills.js +6 -0
- package/dist/shared/lib/rich-text.d.ts +3 -0
- package/dist/shared/lib/rich-text.js +24 -0
- package/dist/shared/lib/utils/question-reverse-transform.js +111 -2
- package/dist/shared/lib/utils/question-transform.js +6 -0
- package/dist/shared/lib/validation/choose-correct-answer.validation.js +4 -2
- package/dist/shared/types/common.types.d.ts +1 -1
- package/dist/shared/types/questions/choose-the-correct-answer-group.d.ts +1 -0
- package/dist/shared/types/questions/index.d.ts +3 -0
- package/dist/shared/types/questions/index.js +6 -0
- package/dist/shared/types/questions/match-columns-to-make-sentences.d.ts +50 -0
- package/dist/shared/types/questions/match-columns-to-make-sentences.js +1 -0
- package/dist/shared/types/questions/match-word-to-picture-group.d.ts +31 -0
- package/dist/shared/types/questions/match-word-to-picture-group.js +1 -0
- package/dist/shared/types/questions/word-ordering-group.d.ts +28 -0
- package/dist/shared/types/questions/word-ordering-group.js +1 -0
- package/package.json +1 -1
|
@@ -637,6 +637,12 @@ function ExamTakingPageContainerInner({ roomId, api, onNavigate, resultPath = (a
|
|
|
637
637
|
map.set(`${question.id}-item-${index}`, question.id);
|
|
638
638
|
});
|
|
639
639
|
}
|
|
640
|
+
if (part.questionType === 'WORD_ORDERING_GROUP' &&
|
|
641
|
+
Array.isArray(content?.items)) {
|
|
642
|
+
content.items.forEach((_, index) => {
|
|
643
|
+
map.set(`${question.id}-item-${index}`, question.id);
|
|
644
|
+
});
|
|
645
|
+
}
|
|
640
646
|
});
|
|
641
647
|
});
|
|
642
648
|
return map;
|
|
@@ -8,9 +8,9 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
8
8
|
* 2. Transforms data to renderer-specific props
|
|
9
9
|
* 3. Renders the correct component for each question type
|
|
10
10
|
*/
|
|
11
|
-
import { MoversListenAndWriteRenderer, MoversChooseBestAnswerRenderer, MoversChooseAdjectiveRenderer, MoversWritingRenderer, MoversColoringRenderer, MoversPictureChooseRenderer, MoversPictureFillBlankChooseRenderer, MoversReadingPassageRenderer, MoversLabelThePictureRenderer, MoversFillWordHintRenderer, MoversGridFillRenderer, MoversTrueFalseRenderer, MoversTrueFalseCorrectGroupRenderer, MoversFillInBlankGroupRenderer, MoversCrossOutWordGroupRenderer, MoversAnswerTheQuestionRenderer, MoversWriteSentencesRenderer, MoversWordOrderingRenderer, MoversWordFillParagraphRenderer, MoversMatchByWritingAnswerRenderer, MoversMatchWordToPictureRenderer, MoversWordFillStructuredFormRenderer, MoversWordOrderAndMatchGroupRenderer, MoversMatchingWithLinesGroupRenderer, MoversCrosswordPuzzleRenderer, FindWordsInMatrixRenderer, } from './question-renderers';
|
|
11
|
+
import { MoversListenAndWriteRenderer, MoversChooseBestAnswerRenderer, MoversChooseAdjectiveRenderer, MoversWritingRenderer, MoversColoringRenderer, MoversPictureChooseRenderer, MoversPictureFillBlankChooseRenderer, MoversReadingPassageRenderer, MoversLabelThePictureRenderer, MoversFillWordHintRenderer, MoversGridFillRenderer, MoversTrueFalseRenderer, MoversTrueFalseCorrectGroupRenderer, MoversFillInBlankGroupRenderer, MoversCrossOutWordGroupRenderer, MoversAnswerTheQuestionRenderer, MoversWriteSentencesRenderer, MoversWordOrderingRenderer, MoversWordOrderingGroupRenderer, MoversWordFillParagraphRenderer, MoversMatchByWritingAnswerRenderer, MoversMatchWordToPictureRenderer, MoversWordFillStructuredFormRenderer, MoversWordOrderAndMatchGroupRenderer, MoversMatchingWithLinesGroupRenderer, MoversCrosswordPuzzleRenderer, FindWordsInMatrixRenderer, } from './question-renderers';
|
|
12
12
|
import { SpeakingQuestionRenderer } from './speaking/renderers';
|
|
13
|
-
import { transformFillInBlank, transformWriteCorrectVerbForm, transformChooseCorrectAnswer, transformChooseTheCorrectAnswerGroup, transformChooseCorrectAdjective, transformWriteShortLetter, transformColorObjects, transformPictureChoose, transformPictureFillBlankChoose, transformReadingPassage, transformLabelPicture, transformFillWordHint, transformGridFill, transformAnswerTheQuestion, transformAnswerTheQuestionGroup, transformTrueFalse, transformTrueFalseGroup, transformTrueFalseCorrectGroup, transformFillInBlankGroup, transformWordOrdering, transformWordFillParagraph, transformMatchByWritingAnswer, transformMatchWordToPicture, transformWriteSentences, transformWordFillStructuredForm, transformWordOrderAndMatchGroup, transformMatchingWithLinesGroup, transformCrosswordPuzzle, transformFindWordsInMatrix, } from '../utils/question-transformers';
|
|
13
|
+
import { transformFillInBlank, transformWriteCorrectVerbForm, transformChooseCorrectAnswer, transformChooseTheCorrectAnswerGroup, transformChooseCorrectAdjective, transformWriteShortLetter, transformColorObjects, transformPictureChoose, transformPictureFillBlankChoose, transformReadingPassage, transformLabelPicture, transformFillWordHint, transformGridFill, transformAnswerTheQuestion, transformAnswerTheQuestionGroup, transformTrueFalse, transformTrueFalseGroup, transformTrueFalseCorrectGroup, transformFillInBlankGroup, transformWordOrdering, transformWordOrderingGroupTake, transformWordFillParagraph, transformMatchByWritingAnswer, transformMatchWordToPicture, transformWriteSentences, transformWordFillStructuredForm, transformWordOrderAndMatchGroup, transformMatchingWithLinesGroup, transformCrosswordPuzzle, transformFindWordsInMatrix, } from '../utils/question-transformers';
|
|
14
14
|
import { transformCrossOutWordGroup } from '../utils/cross-out-word-group.transformer';
|
|
15
15
|
import { normalizeColoringAssignments } from '../utils/coloring-assignments';
|
|
16
16
|
export function QuestionRenderer({ part, answers, onAnswerChange, isReviewMode = false, onExit, onPartComplete, onPartBack, speakingTheme, initialQuestionIndex, skipTeacherVideo, onTeacherIntroPlayed, currentQuestionIndex, }) {
|
|
@@ -55,7 +55,7 @@ export function QuestionRenderer({ part, answers, onAnswerChange, isReviewMode =
|
|
|
55
55
|
}
|
|
56
56
|
case 'CHOOSE_THE_CORRECT_ANSWER_GROUP': {
|
|
57
57
|
const data = transformChooseTheCorrectAnswerGroup(questions);
|
|
58
|
-
return (_jsx(MoversChooseBestAnswerRenderer, { partNumber: partNo, partName: part.name, questionCount: questionCount, instruction: part.instructions || data.instruction, audioUrl: data.audioUrl || part.audioUrl, example: data.example, questions: getRenderQuestions(data.questions), answers: answers, onAnswerChange: onAnswerChange, isReviewMode: isReviewMode }));
|
|
58
|
+
return (_jsx(MoversChooseBestAnswerRenderer, { partNumber: partNo, partName: part.name, questionCount: questionCount, instruction: part.instructions || data.instruction, audioUrl: data.audioUrl || part.audioUrl, imageUrl: data.imageUrl, example: data.example, questions: getRenderQuestions(data.questions), answers: answers, onAnswerChange: onAnswerChange, isReviewMode: isReviewMode }));
|
|
59
59
|
}
|
|
60
60
|
case 'CHOOSE_CORRECT_ADJECTIVE': {
|
|
61
61
|
const data = transformChooseCorrectAdjective(questions);
|
|
@@ -181,6 +181,10 @@ export function QuestionRenderer({ part, answers, onAnswerChange, isReviewMode =
|
|
|
181
181
|
const data = transformTrueFalse(questions);
|
|
182
182
|
return (_jsx(MoversTrueFalseRenderer, { partNumber: partNo, partName: part.name, questionCount: questionCount, instruction: part.instructions || data.instruction, imageUrl: data.imageUrl, groupContent: data.groupContent, questions: getRenderQuestions(data.questions), answers: answers, onAnswerChange: onAnswerChange, isReviewMode: isReviewMode }));
|
|
183
183
|
}
|
|
184
|
+
case 'WORD_ORDERING_GROUP': {
|
|
185
|
+
const data = transformWordOrderingGroupTake(questions);
|
|
186
|
+
return (_jsx(MoversWordOrderingGroupRenderer, { partNumber: partNo, partName: part.name, questionCount: data.questions.filter((q) => !q.isExample).length, instruction: part.instructions || data.instruction, data: data, answers: answers, onAnswerChange: onAnswerChange, isReviewMode: isReviewMode }));
|
|
187
|
+
}
|
|
184
188
|
case 'WORD_ORDERING': {
|
|
185
189
|
const data = transformWordOrdering(questions);
|
|
186
190
|
return (_jsx(MoversWordOrderingRenderer, { partNumber: partNo, partName: part.name, questionCount: questionCount, instruction: part.instructions || data.instruction, questions: getRenderQuestions(data.questions), answers: answers, onAnswerChange: onAnswerChange, isReviewMode: isReviewMode }));
|
package/dist/components/exams/take/components/question-renderers/MoversChooseBestAnswerRenderer.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { cn } from '../../../../../shared/lib/utils';
|
|
4
4
|
import { ResolvedImage } from '../../../../../components/common/ResolvedImage';
|
|
5
|
+
import { RichTextHtml } from '../../../../../components/shared/RichTextHtml';
|
|
5
6
|
import CambridgeYlePartBanner from '../../../../../components/themes/cambridge-yle/CambridgeYlePartBanner';
|
|
6
7
|
import CambridgeYleInstructionBanner from '../../../../../components/themes/cambridge-yle/CambridgeYleInstructionBanner';
|
|
7
8
|
import CambridgeYleAudioPlayer from '../../../../../components/themes/cambridge-yle/CambridgeYleAudioPlayer';
|
|
@@ -42,14 +43,14 @@ function normalizeRenderableText(value) {
|
|
|
42
43
|
}
|
|
43
44
|
// Example Section Component
|
|
44
45
|
function ExampleSection({ example }) {
|
|
45
|
-
return (_jsxs("div", { className: "rounded-lg border-2 border-orange-300 bg-white p-4", children: [_jsx("p", { className: "mb-3 text-sm font-semibold text-gray-700", children: "Example:" }), _jsxs("
|
|
46
|
+
return (_jsxs("div", { className: "rounded-lg border-2 border-orange-300 bg-white p-4", children: [_jsx("p", { className: "mb-3 text-sm font-semibold text-gray-700", children: "Example:" }), _jsxs("div", { className: "mb-3 flex items-start gap-1.5 text-[19px] font-bold text-[#001590]", children: [_jsxs("span", { children: [example.questionNumber, "."] }), _jsx(RichTextHtml, { html: normalizeRenderableText(example.questionText), className: "flex-1 font-bold text-[#001590]" })] }), example.audioUrl && (_jsx("div", { className: "mb-3", children: _jsx(CambridgeYleAudioPlayer, { audioSrc: example.audioUrl, compact: true, dense: true }) })), _jsx(ChooseBestAnswerImages, { imageUrl: example.imageUrl, className: "mb-3" }), example.optionType === 'image' ? (_jsx("div", { className: "grid gap-3 px-2", style: { gridTemplateColumns: `repeat(${example.options.length}, minmax(0, 1fr))` }, children: example.options.map((option) => {
|
|
46
47
|
const isCorrect = option.id === example.correctOptionId;
|
|
47
48
|
return (_jsxs("div", { className: `relative flex flex-col items-center gap-1 rounded-[16px] border-[3px] p-4 pt-6 mt-3 transition-colors ${isCorrect ? 'border-green-500 bg-green-50' : 'border-blue-200 bg-white'}`, children: [_jsx("div", { className: "absolute -top-[14px] left-1/2 -translate-x-1/2 bg-blue-600 text-white w-[28px] h-[28px] rounded-full flex items-center justify-center font-bold text-[15px] shadow-sm", children: option.label }), _jsx(ResolvedImage, { src: getChooseBestAnswerOptionImageSrc(option), alt: `Option ${option.label}`, className: "w-40 rounded object-contain mb-2" }), _jsx("div", { className: cn("w-7 h-7 rounded-full flex items-center justify-center border-[3px] transition-all mt-auto", isCorrect
|
|
48
49
|
? "bg-green-500 border-green-500 text-white"
|
|
49
50
|
: "border-gray-300 bg-white text-transparent"), children: _jsx("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "3.5", strokeLinecap: "round", strokeLinejoin: "round", children: _jsx("polyline", { points: "20 6 9 17 4 12" }) }) })] }, option.id));
|
|
50
51
|
}) })) : (_jsx("div", { className: "flex flex-col gap-2 pl-2", children: example.options.map((option) => {
|
|
51
52
|
const isCorrect = option.id === example.correctOptionId;
|
|
52
|
-
return (_jsxs("div", { className: `flex items-center gap-3 rounded-lg border-2 px-3 py-2 text-left transition-colors ${isCorrect ? 'border-green-500 bg-green-50' : 'border-blue-200 bg-white'}`, children: [_jsxs("span", { className: "text-sm font-medium text-gray-600", children: [option.label, "."] }), _jsx(
|
|
53
|
+
return (_jsxs("div", { className: `flex items-center gap-3 rounded-lg border-2 px-3 py-2 text-left transition-colors ${isCorrect ? 'border-green-500 bg-green-50' : 'border-blue-200 bg-white'}`, children: [_jsxs("span", { className: "text-sm font-medium text-gray-600", children: [option.label, "."] }), _jsx(RichTextHtml, { html: normalizeRenderableText(option.text), className: "flex-1 text-sm text-gray-700" }), _jsx("div", { className: `flex h-5 w-5 items-center justify-center border-2 rounded-full ${isCorrect ? 'border-green-500 bg-green-500' : 'border-gray-300 bg-white'}`, children: isCorrect && (_jsx("div", { className: "h-2 w-2 rounded-full bg-white" })) })] }, option.id));
|
|
53
54
|
}) }))] }));
|
|
54
55
|
}
|
|
55
56
|
// Single Question Component
|
|
@@ -99,9 +100,9 @@ function QuestionItem({ question, selectedOptionIds, onSelect, isReviewMode, id,
|
|
|
99
100
|
}
|
|
100
101
|
return selectedOptionIds.includes(optionId) || correctOptionIds.includes(optionId);
|
|
101
102
|
};
|
|
102
|
-
return (_jsxs("div", { id: id, className: "mb-6", children: [_jsxs("
|
|
103
|
+
return (_jsxs("div", { id: id, className: "mb-6", children: [_jsxs("div", { className: "mb-3 flex items-start gap-1.5 text-[19px] font-bold text-[#001590]", children: [_jsxs("span", { children: [question.questionNumber, "."] }), _jsx(RichTextHtml, { html: normalizeRenderableText(question.questionText), className: "flex-1 font-bold text-[#001590]" })] }), isMultipleAnswers && (_jsx("p", { className: "mb-3 pl-2 text-sm font-medium text-blue-600", children: "Ch\u1ECDn nhi\u1EC1u \u0111\u00E1p \u00E1n" })), question.audioUrl && (_jsx("div", { className: "mb-3", children: _jsx(CambridgeYleAudioPlayer, { audioSrc: question.audioUrl, compact: true, dense: true }) })), _jsx(ChooseBestAnswerImages, { imageUrl: question.imageUrl, className: "mb-3" }), question.optionType === 'image' ? (_jsx("div", { className: "grid gap-3 px-2", style: { gridTemplateColumns: `repeat(${question.options.length}, minmax(0, 1fr))` }, children: question.options.map((option) => (_jsxs("button", { type: "button", onClick: () => !isReviewMode && onSelect(option.id), disabled: isReviewMode, className: `relative flex flex-col items-center gap-1 rounded-[16px] border-[3px] p-4 pt-6 mt-3 transition-colors ${getOptionStyle(option.id)} ${isReviewMode ? 'cursor-default' : 'cursor-pointer'}`, children: [_jsx("div", { className: "absolute -top-[14px] left-1/2 -translate-x-1/2 bg-blue-600 text-white w-[28px] h-[28px] rounded-full flex items-center justify-center font-bold text-[15px] shadow-sm", children: option.label }), _jsx(ResolvedImage, { src: getChooseBestAnswerOptionImageSrc(option), alt: `Option ${option.label}`, className: "w-35 rounded object-contain mb-2" }), _jsx("div", { className: cn("w-7 h-7 rounded-full flex items-center justify-center border-[3px] transition-all mt-auto", showIndicatorMark(option.id)
|
|
103
104
|
? "bg-blue-600 border-blue-600 text-white"
|
|
104
|
-
: "border-gray-300 bg-white text-transparent"), children: _jsx("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "3.5", strokeLinecap: "round", strokeLinejoin: "round", children: _jsx("polyline", { points: "20 6 9 17 4 12" }) }) })] }, option.id))) })) : (_jsx("div", { className: "flex flex-col gap-2 pl-2", children: question.options.map((option) => (_jsxs("button", { type: "button", onClick: () => !isReviewMode && onSelect(option.id), disabled: isReviewMode, className: `flex items-center gap-3 rounded-lg border-2 px-3 py-2 text-left transition-colors ${getOptionStyle(option.id)} ${isReviewMode ? 'cursor-default' : 'cursor-pointer'}`, children: [_jsxs("span", { className: "text-sm font-medium text-gray-600", children: [option.label, "."] }), _jsx(
|
|
105
|
+
: "border-gray-300 bg-white text-transparent"), children: _jsx("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "3.5", strokeLinecap: "round", strokeLinejoin: "round", children: _jsx("polyline", { points: "20 6 9 17 4 12" }) }) })] }, option.id))) })) : (_jsx("div", { className: "flex flex-col gap-2 pl-2", children: question.options.map((option) => (_jsxs("button", { type: "button", onClick: () => !isReviewMode && onSelect(option.id), disabled: isReviewMode, className: `flex items-center gap-3 rounded-lg border-2 px-3 py-2 text-left transition-colors ${getOptionStyle(option.id)} ${isReviewMode ? 'cursor-default' : 'cursor-pointer'}`, children: [_jsxs("span", { className: "text-sm font-medium text-gray-600", children: [option.label, "."] }), _jsx(RichTextHtml, { html: normalizeRenderableText(option.text), className: "flex-1 text-sm text-gray-700" }), _jsx("div", { className: `flex h-5 w-5 items-center justify-center border-2 ${isMultipleAnswers ? 'rounded-md' : 'rounded-full'} ${getIndicatorStyle(option.id)}`, children: showIndicatorMark(option.id) && (_jsx("div", { className: `${isMultipleAnswers ? 'h-2.5 w-2.5 rounded-sm' : 'h-2 w-2 rounded-full'} bg-white` })) })] }, option.id))) }))] }));
|
|
105
106
|
}
|
|
106
107
|
// Article Card for ARTICLES groupType (e.g., GN QE RWL Part 2)
|
|
107
108
|
function ArticleCard({ article, label, }) {
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { WordOrderingGroupPartData } from '../../utils/question-transformers';
|
|
2
|
+
interface MoversWordOrderingGroupRendererProps {
|
|
3
|
+
partNumber: number;
|
|
4
|
+
partName?: string;
|
|
5
|
+
questionCount: number;
|
|
6
|
+
instruction: string;
|
|
7
|
+
data: WordOrderingGroupPartData;
|
|
8
|
+
answers: Record<string, string[] | undefined>;
|
|
9
|
+
onAnswerChange: (questionId: string, value: string[]) => void;
|
|
10
|
+
isReviewMode?: boolean;
|
|
11
|
+
}
|
|
12
|
+
export declare function MoversWordOrderingGroupRenderer({ partNumber, partName, questionCount, instruction, data, answers, onAnswerChange, isReviewMode, }: MoversWordOrderingGroupRendererProps): import("react").JSX.Element;
|
|
13
|
+
export {};
|
package/dist/components/exams/take/components/question-renderers/MoversWordOrderingGroupRenderer.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import CambridgeYlePartBanner from '../../../../../components/themes/cambridge-yle/CambridgeYlePartBanner';
|
|
4
|
+
import CambridgeYleInstructionBanner from '../../../../../components/themes/cambridge-yle/CambridgeYleInstructionBanner';
|
|
5
|
+
import { WordOrderingGroupClient } from '../../../../questions/types/word-ordering-group/WordOrderingGroupClient';
|
|
6
|
+
export function MoversWordOrderingGroupRenderer({ partNumber, partName, questionCount, instruction, data, answers, onAnswerChange, isReviewMode = false, }) {
|
|
7
|
+
const userAnswers = data.questions.map((question) => {
|
|
8
|
+
if (question.isExample)
|
|
9
|
+
return question.answer;
|
|
10
|
+
return Array.isArray(answers[question.id]) ? answers[question.id] || [] : [];
|
|
11
|
+
});
|
|
12
|
+
return (_jsxs("div", { className: "w-full space-y-4", children: [_jsx(CambridgeYlePartBanner, { partNumber: partNumber, partName: partName, questionCount: questionCount }), _jsx(CambridgeYleInstructionBanner, { instruction: instruction }), _jsx("div", { className: "mt-4", children: _jsx(WordOrderingGroupClient, { questionData: {
|
|
13
|
+
instruction: data.instruction,
|
|
14
|
+
items: data.questions.map((question) => ({
|
|
15
|
+
words: question.words,
|
|
16
|
+
answer: question.answer,
|
|
17
|
+
isExample: question.isExample,
|
|
18
|
+
points: question.points,
|
|
19
|
+
questionNumber: question.questionNumber,
|
|
20
|
+
})),
|
|
21
|
+
explanation: data.explanation,
|
|
22
|
+
}, isReviewMode: isReviewMode, userAnswers: userAnswers, onAnswerChange: (next) => {
|
|
23
|
+
next.forEach((value, index) => {
|
|
24
|
+
const target = data.questions[index];
|
|
25
|
+
if (target && !target.isExample) {
|
|
26
|
+
onAnswerChange(target.id, value);
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
} }) })] }));
|
|
30
|
+
}
|
|
@@ -16,6 +16,7 @@ export { MoversCrossOutWordGroupRenderer } from './MoversCrossOutWordGroupRender
|
|
|
16
16
|
export { MoversAnswerTheQuestionRenderer } from './MoversAnswerTheQuestionRenderer';
|
|
17
17
|
export { MoversWriteSentencesRenderer } from './MoversWriteSentencesRenderer';
|
|
18
18
|
export { MoversWordOrderingRenderer } from './MoversWordOrderingRenderer';
|
|
19
|
+
export { MoversWordOrderingGroupRenderer } from './MoversWordOrderingGroupRenderer';
|
|
19
20
|
export { MoversWordFillParagraphRenderer } from './MoversWordFillParagraphRenderer';
|
|
20
21
|
export { MoversMatchByWritingAnswerRenderer } from './MoversMatchByWritingAnswerRenderer';
|
|
21
22
|
export { MoversMatchWordToPictureRenderer } from './MoversMatchWordToPictureRenderer';
|
|
@@ -20,6 +20,7 @@ export { MoversCrossOutWordGroupRenderer } from './MoversCrossOutWordGroupRender
|
|
|
20
20
|
export { MoversAnswerTheQuestionRenderer } from './MoversAnswerTheQuestionRenderer';
|
|
21
21
|
export { MoversWriteSentencesRenderer } from './MoversWriteSentencesRenderer';
|
|
22
22
|
export { MoversWordOrderingRenderer } from './MoversWordOrderingRenderer';
|
|
23
|
+
export { MoversWordOrderingGroupRenderer } from './MoversWordOrderingGroupRenderer';
|
|
23
24
|
export { MoversWordFillParagraphRenderer } from './MoversWordFillParagraphRenderer';
|
|
24
25
|
export { MoversMatchByWritingAnswerRenderer } from './MoversMatchByWritingAnswerRenderer';
|
|
25
26
|
export { MoversMatchWordToPictureRenderer } from './MoversMatchWordToPictureRenderer';
|
|
@@ -92,7 +92,8 @@ function findParentQuestion(subQuestionId, parts) {
|
|
|
92
92
|
part.questionType === 'MATCHING_WITH_LINES_GROUP' ||
|
|
93
93
|
part.questionType === 'WORD_ORDER_AND_MATCH_GROUP' ||
|
|
94
94
|
part.questionType === 'SPONTANEOUS_QA_GROUP' ||
|
|
95
|
-
part.questionType === 'ANSWER_THE_QUESTION_GROUP'
|
|
95
|
+
part.questionType === 'ANSWER_THE_QUESTION_GROUP' ||
|
|
96
|
+
part.questionType === 'WORD_ORDERING_GROUP') {
|
|
96
97
|
const parent = part.questions.find((question) => subQuestionId.startsWith(`${question.id}-item-`));
|
|
97
98
|
if (parent) {
|
|
98
99
|
return {
|
|
@@ -397,6 +398,12 @@ export function transformAnswersForSubmission(answers, parts) {
|
|
|
397
398
|
}
|
|
398
399
|
return { answers: [] };
|
|
399
400
|
}
|
|
401
|
+
if (parentType === 'WORD_ORDERING_GROUP') {
|
|
402
|
+
if (Array.isArray(raw)) {
|
|
403
|
+
return raw.filter((token) => typeof token === 'string');
|
|
404
|
+
}
|
|
405
|
+
return [];
|
|
406
|
+
}
|
|
400
407
|
if (parentType === 'WORD_ORDER_AND_MATCH_GROUP') {
|
|
401
408
|
if (raw && typeof raw === 'object' && !Array.isArray(raw)) {
|
|
402
409
|
const obj = raw;
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Each transformer handles specific question type data structure.
|
|
6
6
|
*/
|
|
7
7
|
import type { FillBlankQuestion, ChooseBestAnswerQuestion, PictureChooseSubQuestion, PictureFillBlankSubQuestion, ReadingPassageQuestion, LabelThePictureLabel, FillWordHintSubQuestion, ColoringHintItem, ColoringRegion } from '../types';
|
|
8
|
-
export type QuestionType = 'FILL_IN_BLANK' | 'WRITE_CORRECT_VERB_FORM' | 'CHOOSE_THE_CORRECT_ANSWER' | 'CHOOSE_THE_CORRECT_ANSWER_GROUP' | 'CHOOSE_CORRECT_ADJECTIVE' | 'WRITE_A_SHORT_LETTER' | 'READ_AND_COLOR_OBJECTS' | 'LOOK_PICTURE_CHOOSE_CORRECT_ANSWER' | 'LOOK_PICTURE_FILL_BLANK_CHOOSE_ANSWER' | 'READ_PASSAGE_AND_ANSWER_QUESTIONS' | 'LABEL_THE_PICTURE' | 'LOOK_PICTURE_FILL_WORD_HINT' | 'FILL_MISSING_WORDS_IN_GRID' | 'IMAGE_OBJECT_MATCHING' | 'ANSWER_THE_QUESTION' | 'ANSWER_THE_QUESTION_GROUP' | 'TRUE_FALSE' | 'TRUE_FALSE_GROUP' | 'TRUE_FALSE_CORRECT_GROUP' | 'FILL_IN_BLANK_GROUP' | 'MATCHING_WITH_LINES_GROUP' | 'CROSS_OUT_WORD_GROUP' | 'WORD_ORDERING' | 'WORD_FILL_PARAGRAPH' | 'MATCH_BY_WRITING_ANSWER' | 'MATCH_WORD_TO_PICTURE' | 'WRITE_SENTENCES' | 'WORD_FILL_STRUCTURED_FORM' | 'CROSSWORD_PUZZLE' | 'FIND_WORDS_IN_MATRIX' | 'WORD_ORDER_AND_MATCH_GROUP' | 'LISTEN_AND_CHOOSE_OBJECTS_IN_SCENE' | 'LISTEN_AND_CHOOSE_FROM_ANSWER_GROUP' | 'LISTEN_AND_DRAG_OBJECTS_INTO_SCENE' | 'LISTEN_AND_SPEAK_ANSWER' | 'LISTEN_AND_SPEAK_COMPARE_IMAGES' | 'LISTEN_AND_SPEAK_WITH_STORY_IMAGES' | 'LISTEN_AND_SPEAK_IMAGE_GROUP' | 'LISTEN_AND_SPEAK_ODD_ONE_OUT' | 'LISTEN_AND_SPEAK_QUESTION_LIST' | 'LISTEN_AND_SPEAK_INFO_EXCHANGE' | 'READ_DISPLAYED_CONTENT' | 'SPEAKING_DESCRIBE_IMAGE' | 'SPONTANEOUS_QA' | 'SPONTANEOUS_QA_GROUP' | 'SPEAKING_CONVERSATION' | 'GN_SPEAKING_INTERVIEW' | 'SPEAKING_CUE_CARD';
|
|
8
|
+
export type QuestionType = 'FILL_IN_BLANK' | 'WRITE_CORRECT_VERB_FORM' | 'CHOOSE_THE_CORRECT_ANSWER' | 'CHOOSE_THE_CORRECT_ANSWER_GROUP' | 'CHOOSE_CORRECT_ADJECTIVE' | 'WRITE_A_SHORT_LETTER' | 'READ_AND_COLOR_OBJECTS' | 'LOOK_PICTURE_CHOOSE_CORRECT_ANSWER' | 'LOOK_PICTURE_FILL_BLANK_CHOOSE_ANSWER' | 'READ_PASSAGE_AND_ANSWER_QUESTIONS' | 'LABEL_THE_PICTURE' | 'LOOK_PICTURE_FILL_WORD_HINT' | 'FILL_MISSING_WORDS_IN_GRID' | 'IMAGE_OBJECT_MATCHING' | 'ANSWER_THE_QUESTION' | 'ANSWER_THE_QUESTION_GROUP' | 'TRUE_FALSE' | 'TRUE_FALSE_GROUP' | 'TRUE_FALSE_CORRECT_GROUP' | 'FILL_IN_BLANK_GROUP' | 'MATCHING_WITH_LINES_GROUP' | 'CROSS_OUT_WORD_GROUP' | 'WORD_ORDERING' | 'WORD_ORDERING_GROUP' | 'WORD_FILL_PARAGRAPH' | 'MATCH_BY_WRITING_ANSWER' | 'MATCH_WORD_TO_PICTURE' | 'WRITE_SENTENCES' | 'WORD_FILL_STRUCTURED_FORM' | 'CROSSWORD_PUZZLE' | 'FIND_WORDS_IN_MATRIX' | 'WORD_ORDER_AND_MATCH_GROUP' | 'LISTEN_AND_CHOOSE_OBJECTS_IN_SCENE' | 'LISTEN_AND_CHOOSE_FROM_ANSWER_GROUP' | 'LISTEN_AND_DRAG_OBJECTS_INTO_SCENE' | 'LISTEN_AND_SPEAK_ANSWER' | 'LISTEN_AND_SPEAK_COMPARE_IMAGES' | 'LISTEN_AND_SPEAK_WITH_STORY_IMAGES' | 'LISTEN_AND_SPEAK_IMAGE_GROUP' | 'LISTEN_AND_SPEAK_ODD_ONE_OUT' | 'LISTEN_AND_SPEAK_QUESTION_LIST' | 'LISTEN_AND_SPEAK_INFO_EXCHANGE' | 'READ_DISPLAYED_CONTENT' | 'SPEAKING_DESCRIBE_IMAGE' | 'SPONTANEOUS_QA' | 'SPONTANEOUS_QA_GROUP' | 'SPEAKING_CONVERSATION' | 'GN_SPEAKING_INTERVIEW' | 'SPEAKING_CUE_CARD';
|
|
9
9
|
export interface ApiQuestion {
|
|
10
10
|
id: string;
|
|
11
11
|
content: any;
|
|
@@ -162,6 +162,7 @@ export declare function transformChooseTheCorrectAnswerGroup(questions: ApiQuest
|
|
|
162
162
|
example?: import('../types').ChooseBestAnswerExample;
|
|
163
163
|
instruction: string;
|
|
164
164
|
audioUrl?: string;
|
|
165
|
+
imageUrl?: string;
|
|
165
166
|
};
|
|
166
167
|
export interface ChooseAdjectiveOption {
|
|
167
168
|
id: string;
|
|
@@ -550,3 +551,17 @@ export interface MatchingWithLinesGroupPartData {
|
|
|
550
551
|
explanation?: string;
|
|
551
552
|
}
|
|
552
553
|
export declare function transformMatchingWithLinesGroup(questions: ApiQuestion[]): MatchingWithLinesGroupPartData;
|
|
554
|
+
export interface WordOrderingGroupQuestionItem {
|
|
555
|
+
id: string;
|
|
556
|
+
questionNumber: number;
|
|
557
|
+
words: string[];
|
|
558
|
+
answer: string[];
|
|
559
|
+
isExample: boolean;
|
|
560
|
+
points: number;
|
|
561
|
+
}
|
|
562
|
+
export interface WordOrderingGroupPartData {
|
|
563
|
+
questions: WordOrderingGroupQuestionItem[];
|
|
564
|
+
instruction: string;
|
|
565
|
+
explanation?: string;
|
|
566
|
+
}
|
|
567
|
+
export declare function transformWordOrderingGroupTake(questions: ApiQuestion[]): WordOrderingGroupPartData;
|
|
@@ -376,6 +376,16 @@ export function transformChooseCorrectAnswer(questions, optionsConfig) {
|
|
|
376
376
|
instruction: firstContent?.instruction || 'CHOOSE THE CORRECT ANSWER',
|
|
377
377
|
};
|
|
378
378
|
}
|
|
379
|
+
function asChooseAnswerOptionId(value) {
|
|
380
|
+
if (typeof value === 'string') {
|
|
381
|
+
return value.trim();
|
|
382
|
+
}
|
|
383
|
+
if (Array.isArray(value)) {
|
|
384
|
+
const first = value.find((item) => typeof item === 'string' && item.trim() !== '');
|
|
385
|
+
return typeof first === 'string' ? first.trim() : '';
|
|
386
|
+
}
|
|
387
|
+
return '';
|
|
388
|
+
}
|
|
379
389
|
export function transformChooseTheCorrectAnswerGroup(questions) {
|
|
380
390
|
const parent = questions[0];
|
|
381
391
|
const content = parent?.content;
|
|
@@ -416,13 +426,20 @@ export function transformChooseTheCorrectAnswerGroup(questions) {
|
|
|
416
426
|
return trimmed !== '' ? trimmed : undefined;
|
|
417
427
|
};
|
|
418
428
|
const groupAudioUrl = toAudioUrl(content?.meta?.audioUrl);
|
|
429
|
+
const groupImageUrlRaw = toQuestionImageUrl(content?.meta?.imageUrl);
|
|
430
|
+
const groupImageUrl = Array.isArray(groupImageUrlRaw)
|
|
431
|
+
? groupImageUrlRaw[0]
|
|
432
|
+
: groupImageUrlRaw;
|
|
419
433
|
let example;
|
|
420
434
|
if (exampleItem) {
|
|
421
435
|
const exampleIndex = items.indexOf(exampleItem);
|
|
422
436
|
const exOptions = mapOptions(exampleItem.content?.options, `${parent.id}-ex`);
|
|
423
|
-
const exCorrect = exampleItem.correctAnswer?.answer
|
|
437
|
+
const exCorrect = asChooseAnswerOptionId(exampleItem.correctAnswer?.answer)
|
|
438
|
+
|| asChooseAnswerOptionId(parentCorrect[exampleIndex])
|
|
439
|
+
|| exOptions[0]?.id
|
|
440
|
+
|| '';
|
|
424
441
|
const exampleImageUrl = toQuestionImageUrl(exampleItem.content?.imageUrl);
|
|
425
|
-
const exampleAudioUrl =
|
|
442
|
+
const exampleAudioUrl = toAudioUrl(exampleItem.content?.audioUrl);
|
|
426
443
|
example = {
|
|
427
444
|
questionNumber: exampleItem.questionNumber || exampleIndex + 1,
|
|
428
445
|
questionText: exampleItem.content?.question || '',
|
|
@@ -436,9 +453,11 @@ export function transformChooseTheCorrectAnswerGroup(questions) {
|
|
|
436
453
|
const transformed = regularItems.map((item) => {
|
|
437
454
|
const itemIndex = items.indexOf(item);
|
|
438
455
|
const options = mapOptions(item.content?.options, `${parent.id}-item-${itemIndex}`);
|
|
439
|
-
const correctId = item.correctAnswer?.answer
|
|
456
|
+
const correctId = asChooseAnswerOptionId(item.correctAnswer?.answer)
|
|
457
|
+
|| asChooseAnswerOptionId(parentCorrect[itemIndex])
|
|
458
|
+
|| '';
|
|
440
459
|
const itemImageUrl = toQuestionImageUrl(item.content?.imageUrl);
|
|
441
|
-
const itemAudioUrl =
|
|
460
|
+
const itemAudioUrl = toAudioUrl(item.content?.audioUrl);
|
|
442
461
|
return {
|
|
443
462
|
id: `${parent.id}-item-${itemIndex}`,
|
|
444
463
|
questionNumber: item.questionNumber || itemIndex + 1,
|
|
@@ -456,6 +475,7 @@ export function transformChooseTheCorrectAnswerGroup(questions) {
|
|
|
456
475
|
example,
|
|
457
476
|
instruction: content?.meta?.instruction || 'Choose the correct answer.',
|
|
458
477
|
...(groupAudioUrl ? { audioUrl: groupAudioUrl } : {}),
|
|
478
|
+
...(groupImageUrl ? { imageUrl: groupImageUrl } : {}),
|
|
459
479
|
};
|
|
460
480
|
}
|
|
461
481
|
export function transformChooseCorrectAdjective(questions) {
|
|
@@ -1392,3 +1412,28 @@ export function transformMatchingWithLinesGroup(questions) {
|
|
|
1392
1412
|
explanation: parent?.explanation,
|
|
1393
1413
|
};
|
|
1394
1414
|
}
|
|
1415
|
+
export function transformWordOrderingGroupTake(questions) {
|
|
1416
|
+
const parent = questions[0];
|
|
1417
|
+
const content = (parent?.content || {});
|
|
1418
|
+
const items = Array.isArray(content.items) ? content.items : [];
|
|
1419
|
+
const parentCorrect = Array.isArray(parent?.correct_answer?.answer)
|
|
1420
|
+
? parent.correct_answer.answer
|
|
1421
|
+
: [];
|
|
1422
|
+
return {
|
|
1423
|
+
questions: items.map((item, index) => {
|
|
1424
|
+
const isExample = item.isExample === true;
|
|
1425
|
+
const fromItem = Array.isArray(item.correctAnswer?.answer) ? item.correctAnswer.answer : [];
|
|
1426
|
+
const fromParent = Array.isArray(parentCorrect[index]) ? parentCorrect[index] : [];
|
|
1427
|
+
return {
|
|
1428
|
+
id: `${parent?.id || 'q'}-item-${index}`,
|
|
1429
|
+
questionNumber: item.questionNumber || index + 1,
|
|
1430
|
+
words: Array.isArray(item.content?.words) ? item.content.words : [],
|
|
1431
|
+
answer: fromItem.length ? fromItem : fromParent,
|
|
1432
|
+
isExample,
|
|
1433
|
+
points: isExample ? 0 : (typeof item.points === 'number' ? item.points : 1),
|
|
1434
|
+
};
|
|
1435
|
+
}),
|
|
1436
|
+
instruction: content.meta?.instruction || 'Order the words.',
|
|
1437
|
+
explanation: parent?.explanation,
|
|
1438
|
+
};
|
|
1439
|
+
}
|
package/dist/components/index.js
CHANGED
|
@@ -347,6 +347,21 @@ export const QUESTION_TYPE_CONFIG = {
|
|
|
347
347
|
requiresContent: true,
|
|
348
348
|
hasDedicatedComponent: true,
|
|
349
349
|
},
|
|
350
|
+
MATCH_COLUMNS_TO_MAKE_SENTENCES: {
|
|
351
|
+
label: 'Ghép cột thành câu',
|
|
352
|
+
requiresContent: true,
|
|
353
|
+
hasDedicatedComponent: true,
|
|
354
|
+
},
|
|
355
|
+
MATCH_WORD_TO_PICTURE_GROUP: {
|
|
356
|
+
label: 'Nối từ với tranh (nhóm)',
|
|
357
|
+
requiresContent: true,
|
|
358
|
+
hasDedicatedComponent: true,
|
|
359
|
+
},
|
|
360
|
+
WORD_ORDERING_GROUP: {
|
|
361
|
+
label: 'Sắp xếp từ (nhóm)',
|
|
362
|
+
requiresContent: true,
|
|
363
|
+
hasDedicatedComponent: true,
|
|
364
|
+
},
|
|
350
365
|
};
|
|
351
366
|
export const QUESTION_TYPES_WITH_DEDICATED_COMPONENTS = [
|
|
352
367
|
'FILL_IN_BLANK',
|
|
@@ -398,6 +413,9 @@ export const QUESTION_TYPES_WITH_DEDICATED_COMPONENTS = [
|
|
|
398
413
|
'WORD_FILL_STRUCTURED_FORM',
|
|
399
414
|
'CROSSWORD_PUZZLE',
|
|
400
415
|
'FIND_WORDS_IN_MATRIX',
|
|
416
|
+
'MATCH_COLUMNS_TO_MAKE_SENTENCES',
|
|
417
|
+
'MATCH_WORD_TO_PICTURE_GROUP',
|
|
418
|
+
'WORD_ORDERING_GROUP',
|
|
401
419
|
];
|
|
402
420
|
export function getQuestionTypeLabel(type) {
|
|
403
421
|
return QUESTION_TYPE_CONFIG[type]?.label || type;
|
|
@@ -24,6 +24,7 @@ export interface ChooseTheCorrectAnswerGroupData {
|
|
|
24
24
|
instruction: string;
|
|
25
25
|
passage?: string;
|
|
26
26
|
audioUrl?: string;
|
|
27
|
+
imageUrl?: ChooseTheCorrectAnswerGroupImageUrl;
|
|
27
28
|
items: ChooseTheCorrectAnswerGroupItemData[];
|
|
28
29
|
explanation?: string;
|
|
29
30
|
points?: number;
|
|
@@ -15,4 +15,5 @@ export * from './true-false-correct-group.type';
|
|
|
15
15
|
export * from './fill-in-blank-group.type';
|
|
16
16
|
export * from './match-word-to-picture.type';
|
|
17
17
|
export * from './matching-with-lines-group.type';
|
|
18
|
+
export * from './match-columns-to-make-sentences.type';
|
|
18
19
|
export * from './spontaneous-qa-group.type';
|
|
@@ -15,4 +15,5 @@ export * from './true-false-correct-group.type';
|
|
|
15
15
|
export * from './fill-in-blank-group.type';
|
|
16
16
|
export * from './match-word-to-picture.type';
|
|
17
17
|
export * from './matching-with-lines-group.type';
|
|
18
|
+
export * from './match-columns-to-make-sentences.type';
|
|
18
19
|
export * from './spontaneous-qa-group.type';
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { MutableRefObject } from 'react';
|
|
2
|
+
export declare const MATCH_COLUMNS_TO_MAKE_SENTENCE_ITEM_TYPE: "MATCH_COLUMNS_TO_MAKE_SENTENCE";
|
|
3
|
+
export declare const MATCH_COLUMNS_DEFAULT_INSTRUCTION = "Match the information to make sentences. You can use the same words more than once.";
|
|
4
|
+
export type MatchColumnsColumnRole = 'stem' | 'choice';
|
|
5
|
+
export interface MatchColumnsFragment {
|
|
6
|
+
id: string;
|
|
7
|
+
text: string;
|
|
8
|
+
}
|
|
9
|
+
export interface MatchColumnsColumn {
|
|
10
|
+
id: string;
|
|
11
|
+
label?: string;
|
|
12
|
+
role: MatchColumnsColumnRole;
|
|
13
|
+
fragments: MatchColumnsFragment[];
|
|
14
|
+
}
|
|
15
|
+
export interface MatchColumnsItemAnswer {
|
|
16
|
+
fragmentIds: string[];
|
|
17
|
+
sentence: string;
|
|
18
|
+
}
|
|
19
|
+
export interface MatchColumnsStudentItemAnswer {
|
|
20
|
+
fragmentIds: string[];
|
|
21
|
+
sentence: string;
|
|
22
|
+
}
|
|
23
|
+
export interface MatchColumnsGroupItemData {
|
|
24
|
+
stemFragmentId: string;
|
|
25
|
+
fragmentIds: string[];
|
|
26
|
+
sentence: string;
|
|
27
|
+
isExample?: boolean;
|
|
28
|
+
points: number;
|
|
29
|
+
questionNumber: number;
|
|
30
|
+
}
|
|
31
|
+
export interface MatchColumnsGroupData {
|
|
32
|
+
title?: string;
|
|
33
|
+
instruction: string;
|
|
34
|
+
columns: MatchColumnsColumn[];
|
|
35
|
+
items: MatchColumnsGroupItemData[];
|
|
36
|
+
explanation?: string;
|
|
37
|
+
points?: number;
|
|
38
|
+
}
|
|
39
|
+
export interface MatchColumnsGroupCreatorProps {
|
|
40
|
+
initialData?: MatchColumnsGroupData;
|
|
41
|
+
onSave?: (data: MatchColumnsGroupData) => void;
|
|
42
|
+
onCancel?: () => void;
|
|
43
|
+
onChange?: (data: MatchColumnsGroupData) => void;
|
|
44
|
+
externalErrors?: string[];
|
|
45
|
+
onUnsavedChangesChange?: (hasUnsavedChanges: boolean) => void;
|
|
46
|
+
validationRef?: MutableRefObject<{
|
|
47
|
+
triggerValidation: () => Promise<boolean>;
|
|
48
|
+
getFormData?: () => MatchColumnsGroupData;
|
|
49
|
+
} | null>;
|
|
50
|
+
}
|
|
51
|
+
export interface MatchColumnsGroupClientProps {
|
|
52
|
+
questionData: MatchColumnsGroupData;
|
|
53
|
+
isReviewMode?: boolean;
|
|
54
|
+
userAnswers?: MatchColumnsStudentItemAnswer[];
|
|
55
|
+
onAnswerChange?: (answers: MatchColumnsStudentItemAnswer[]) => void;
|
|
56
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { MutableRefObject } from 'react';
|
|
2
|
+
export declare const MATCH_WORD_TO_PICTURE_GROUP_DEFAULT_INSTRUCTION = "Look and complete.";
|
|
3
|
+
export declare const MATCH_WORD_TO_PICTURE_ITEM_TYPE: "MATCH_WORD_TO_PICTURE";
|
|
4
|
+
export interface MatchWordToPictureGroupItemData {
|
|
5
|
+
imageUrl: string;
|
|
6
|
+
correctAnswer: string;
|
|
7
|
+
isExample?: boolean;
|
|
8
|
+
points: number;
|
|
9
|
+
questionNumber: number;
|
|
10
|
+
}
|
|
11
|
+
export interface MatchWordToPictureGroupData {
|
|
12
|
+
instruction: string;
|
|
13
|
+
wordBank: string[];
|
|
14
|
+
items: MatchWordToPictureGroupItemData[];
|
|
15
|
+
explanation?: string;
|
|
16
|
+
points?: number;
|
|
17
|
+
}
|
|
18
|
+
export interface MatchWordToPictureGroupCreatorProps {
|
|
19
|
+
initialData?: MatchWordToPictureGroupData;
|
|
20
|
+
onSave?: (data: MatchWordToPictureGroupData) => void;
|
|
21
|
+
onCancel?: () => void;
|
|
22
|
+
onChange?: (data: MatchWordToPictureGroupData) => void;
|
|
23
|
+
externalErrors?: string[];
|
|
24
|
+
onUnsavedChangesChange?: (hasUnsavedChanges: boolean) => void;
|
|
25
|
+
validationRef?: MutableRefObject<{
|
|
26
|
+
triggerValidation: () => Promise<boolean>;
|
|
27
|
+
getFormData?: () => MatchWordToPictureGroupData;
|
|
28
|
+
} | null>;
|
|
29
|
+
}
|
|
30
|
+
export interface MatchWordToPictureGroupClientProps {
|
|
31
|
+
questionData: MatchWordToPictureGroupData;
|
|
32
|
+
isReviewMode?: boolean;
|
|
33
|
+
userAnswers?: string[];
|
|
34
|
+
onAnswerChange?: (answers: string[]) => void;
|
|
35
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { MutableRefObject } from 'react';
|
|
2
|
+
export declare const WORD_ORDERING_GROUP_DEFAULT_INSTRUCTION = "Order the words.";
|
|
3
|
+
export declare const WORD_ORDERING_ITEM_TYPE: "WORD_ORDERING";
|
|
4
|
+
export interface WordOrderingGroupItemData {
|
|
5
|
+
words: string[];
|
|
6
|
+
answer: string[];
|
|
7
|
+
isExample?: boolean;
|
|
8
|
+
points: number;
|
|
9
|
+
questionNumber: number;
|
|
10
|
+
}
|
|
11
|
+
export interface WordOrderingGroupData {
|
|
12
|
+
instruction: string;
|
|
13
|
+
items: WordOrderingGroupItemData[];
|
|
14
|
+
explanation?: string;
|
|
15
|
+
points?: number;
|
|
16
|
+
}
|
|
17
|
+
export interface WordOrderingGroupCreatorProps {
|
|
18
|
+
initialData?: WordOrderingGroupData;
|
|
19
|
+
onSave?: (data: WordOrderingGroupData) => void;
|
|
20
|
+
onCancel?: () => void;
|
|
21
|
+
onChange?: (data: WordOrderingGroupData) => void;
|
|
22
|
+
externalErrors?: string[];
|
|
23
|
+
onUnsavedChangesChange?: (hasUnsavedChanges: boolean) => void;
|
|
24
|
+
validationRef?: MutableRefObject<{
|
|
25
|
+
triggerValidation: () => Promise<boolean>;
|
|
26
|
+
getFormData?: () => WordOrderingGroupData;
|
|
27
|
+
} | null>;
|
|
28
|
+
}
|
|
29
|
+
export interface WordOrderingGroupClientProps {
|
|
30
|
+
questionData: WordOrderingGroupData;
|
|
31
|
+
isReviewMode?: boolean;
|
|
32
|
+
userAnswers?: string[][];
|
|
33
|
+
onAnswerChange?: (answers: string[][]) => void;
|
|
34
|
+
}
|
|
@@ -68,6 +68,9 @@ const getQuestionTypeName = (type) => {
|
|
|
68
68
|
'READ_PASSAGE_AND_ANSWER_QUESTIONS': 'Đọc hiểu',
|
|
69
69
|
'MATCH_WORD_TO_PICTURE': 'Nối từ với tranh',
|
|
70
70
|
'MATCHING_WITH_LINES_GROUP': 'Nối bằng đường kẻ (nhóm)',
|
|
71
|
+
'MATCH_COLUMNS_TO_MAKE_SENTENCES': 'Ghép cột thành câu',
|
|
72
|
+
'MATCH_WORD_TO_PICTURE_GROUP': 'Nối từ với tranh (nhóm)',
|
|
73
|
+
'WORD_ORDERING_GROUP': 'Sắp xếp từ (nhóm)',
|
|
71
74
|
};
|
|
72
75
|
return type ? typeMap[type] || type.replace(/_/g, ' ').toLowerCase() : 'Chưa xác định';
|
|
73
76
|
};
|
|
@@ -28,6 +28,9 @@ export function QuestionTypeSelector({ value, onValueChange, disabled, }) {
|
|
|
28
28
|
'MATCH_BY_WRITING_ANSWER',
|
|
29
29
|
'MATCH_WORD_TO_PICTURE',
|
|
30
30
|
'MATCHING_WITH_LINES_GROUP',
|
|
31
|
+
'MATCH_COLUMNS_TO_MAKE_SENTENCES',
|
|
32
|
+
'MATCH_WORD_TO_PICTURE_GROUP',
|
|
33
|
+
'WORD_ORDERING_GROUP',
|
|
31
34
|
'WRITE_SENTENCES',
|
|
32
35
|
'FILL_IN_BLANK_GROUP',
|
|
33
36
|
];
|
|
@@ -49,6 +49,9 @@ import { wordFillStructuredFormRegistration } from '../types/word-fill-structure
|
|
|
49
49
|
import { wordOrderAndMatchGroupRegistration } from '../types/word-order-and-match-group/register';
|
|
50
50
|
import { crosswordPuzzleRegistration } from '../types/crossword-puzzle/register';
|
|
51
51
|
import { findWordsInMatrixRegistration } from '../types/find-words-in-matrix/register';
|
|
52
|
+
import { matchColumnsToMakeSentencesRegistration } from '../types/match-columns-to-make-sentences/register';
|
|
53
|
+
import { matchWordToPictureGroupRegistration } from '../types/match-word-to-picture-group/register';
|
|
54
|
+
import { wordOrderingGroupRegistration } from '../types/word-ordering-group/register';
|
|
52
55
|
// ─── Registry map ───────────────────────────────────────────────────────────
|
|
53
56
|
export const questionTypeRegistry = {
|
|
54
57
|
CHOOSE_THE_CORRECT_ANSWER: chooseTheCorrectAnswerRegistration,
|
|
@@ -101,4 +104,7 @@ export const questionTypeRegistry = {
|
|
|
101
104
|
WORD_FILL_STRUCTURED_FORM: wordFillStructuredFormRegistration,
|
|
102
105
|
CROSSWORD_PUZZLE: crosswordPuzzleRegistration,
|
|
103
106
|
FIND_WORDS_IN_MATRIX: findWordsInMatrixRegistration,
|
|
107
|
+
MATCH_COLUMNS_TO_MAKE_SENTENCES: matchColumnsToMakeSentencesRegistration,
|
|
108
|
+
MATCH_WORD_TO_PICTURE_GROUP: matchWordToPictureGroupRegistration,
|
|
109
|
+
WORD_ORDERING_GROUP: wordOrderingGroupRegistration,
|
|
104
110
|
};
|