@tinyweb_dev/oe-exam-sdk 0.2.4 → 0.2.6
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 +19 -3
- package/dist/components/exams/take/components/question-renderers/ChooseBestAnswerImages.d.ts +10 -0
- package/dist/components/exams/take/components/question-renderers/ChooseBestAnswerImages.js +38 -0
- package/dist/components/exams/take/components/question-renderers/MoversChooseBestAnswerRenderer.js +5 -11
- package/dist/components/exams/take/components/question-renderers/MoversFillInBlankGroupRenderer.d.ts +22 -0
- package/dist/components/exams/take/components/question-renderers/MoversFillInBlankGroupRenderer.js +60 -0
- package/dist/components/exams/take/components/question-renderers/MoversMatchWordToPictureRenderer.d.ts +16 -0
- package/dist/components/exams/take/components/question-renderers/MoversMatchWordToPictureRenderer.js +75 -0
- package/dist/components/exams/take/components/question-renderers/MoversMatchingWithLinesGroupRenderer.d.ts +13 -0
- package/dist/components/exams/take/components/question-renderers/MoversMatchingWithLinesGroupRenderer.js +31 -0
- package/dist/components/exams/take/components/question-renderers/MoversTrueFalseCorrectGroupRenderer.d.ts +18 -0
- package/dist/components/exams/take/components/question-renderers/MoversTrueFalseCorrectGroupRenderer.js +92 -0
- package/dist/components/exams/take/components/question-renderers/MoversWritingRenderer.d.ts +2 -1
- package/dist/components/exams/take/components/question-renderers/MoversWritingRenderer.js +2 -2
- package/dist/components/exams/take/components/question-renderers/index.d.ts +4 -0
- package/dist/components/exams/take/components/question-renderers/index.js +4 -0
- package/dist/components/exams/take/exam-taking.utils.d.ts +1 -0
- package/dist/components/exams/take/exam-taking.utils.js +9 -1
- package/dist/components/exams/take/types.d.ts +3 -2
- package/dist/components/exams/take/utils/answer-transformers.js +28 -0
- package/dist/components/exams/take/utils/question-transformers.d.ts +73 -1
- package/dist/components/exams/take/utils/question-transformers.js +188 -0
- package/dist/components/questions/_shared/config/question-types.config.js +24 -0
- package/dist/components/questions/_shared/types/choose-the-correct-answer-group.type.d.ts +9 -0
- package/dist/components/questions/_shared/types/choose-the-correct-answer-group.type.js +49 -1
- package/dist/components/questions/_shared/types/fill-in-blank-group.type.d.ts +50 -0
- package/dist/components/questions/_shared/types/fill-in-blank-group.type.js +2 -0
- package/dist/components/questions/_shared/types/index.d.ts +4 -0
- package/dist/components/questions/_shared/types/index.js +4 -0
- package/dist/components/questions/_shared/types/match-word-to-picture.type.d.ts +62 -0
- package/dist/components/questions/_shared/types/match-word-to-picture.type.js +1 -0
- package/dist/components/questions/_shared/types/matching-with-lines-group.type.d.ts +40 -0
- package/dist/components/questions/_shared/types/matching-with-lines-group.type.js +1 -0
- package/dist/components/questions/_shared/types/true-false-correct-group.type.d.ts +42 -0
- package/dist/components/questions/_shared/types/true-false-correct-group.type.js +2 -0
- package/dist/components/questions/_shared/types/write-a-short-letter.type.d.ts +4 -0
- package/dist/components/questions/components/QuestionSearchDropdown.js +6 -0
- package/dist/components/questions/components/QuestionTypeSelector.js +3 -0
- package/dist/components/questions/creator/question-type-registry.js +8 -0
- package/dist/components/questions/types/choose-the-correct-answer-group/ChooseAnswerGroupImageUpload.d.ts +13 -0
- package/dist/components/questions/types/choose-the-correct-answer-group/ChooseAnswerGroupImageUpload.js +32 -0
- package/dist/components/questions/types/choose-the-correct-answer-group/ChooseTheCorrectAnswerGroupClient.d.ts +1 -1
- package/dist/components/questions/types/choose-the-correct-answer-group/ChooseTheCorrectAnswerGroupClient.js +42 -6
- package/dist/components/questions/types/choose-the-correct-answer-group/ChooseTheCorrectAnswerGroupCreator.js +40 -20
- package/dist/components/questions/types/choose-the-correct-answer-group/map-choose-the-correct-answer-group-data.d.ts +1 -1
- package/dist/components/questions/types/choose-the-correct-answer-group/map-choose-the-correct-answer-group-data.js +5 -0
- package/dist/components/questions/types/choose-the-correct-answer-group/transform.js +4 -0
- package/dist/components/questions/types/fill-in-blank-group/FillInBlankGroupClient.d.ts +2 -0
- package/dist/components/questions/types/fill-in-blank-group/FillInBlankGroupClient.js +64 -0
- package/dist/components/questions/types/fill-in-blank-group/FillInBlankGroupCreator.d.ts +2 -0
- package/dist/components/questions/types/fill-in-blank-group/FillInBlankGroupCreator.js +158 -0
- package/dist/components/questions/types/fill-in-blank-group/FillInBlankGroupMediaField.d.ts +7 -0
- package/dist/components/questions/types/fill-in-blank-group/FillInBlankGroupMediaField.js +30 -0
- package/dist/components/questions/types/fill-in-blank-group/blank-utils.d.ts +20 -0
- package/dist/components/questions/types/fill-in-blank-group/blank-utils.js +99 -0
- package/dist/components/questions/types/fill-in-blank-group/index.d.ts +5 -0
- package/dist/components/questions/types/fill-in-blank-group/index.js +5 -0
- package/dist/components/questions/types/fill-in-blank-group/map-fill-in-blank-group-data.d.ts +4 -0
- package/dist/components/questions/types/fill-in-blank-group/map-fill-in-blank-group-data.js +102 -0
- package/dist/components/questions/types/fill-in-blank-group/register.d.ts +3 -0
- package/dist/components/questions/types/fill-in-blank-group/register.js +37 -0
- package/dist/components/questions/types/fill-in-blank-group/transform.d.ts +2 -0
- package/dist/components/questions/types/fill-in-blank-group/transform.js +68 -0
- package/dist/components/questions/types/match-word-to-picture/MatchWordToPictureClient.d.ts +2 -0
- package/dist/components/questions/types/match-word-to-picture/MatchWordToPictureClient.js +31 -0
- package/dist/components/questions/types/match-word-to-picture/MatchWordToPictureCreator.d.ts +2 -0
- package/dist/components/questions/types/match-word-to-picture/MatchWordToPictureCreator.js +267 -0
- package/dist/components/questions/types/match-word-to-picture/MatchWordToPictureGroupClient.d.ts +2 -0
- package/dist/components/questions/types/match-word-to-picture/MatchWordToPictureGroupClient.js +65 -0
- package/dist/components/questions/types/match-word-to-picture/WordBankEditor.d.ts +8 -0
- package/dist/components/questions/types/match-word-to-picture/WordBankEditor.js +15 -0
- package/dist/components/questions/types/match-word-to-picture/index.d.ts +6 -0
- package/dist/components/questions/types/match-word-to-picture/index.js +6 -0
- package/dist/components/questions/types/match-word-to-picture/map-match-word-to-picture-data.d.ts +2 -0
- package/dist/components/questions/types/match-word-to-picture/map-match-word-to-picture-data.js +53 -0
- package/dist/components/questions/types/match-word-to-picture/map-match-word-to-picture-group-data.d.ts +4 -0
- package/dist/components/questions/types/match-word-to-picture/map-match-word-to-picture-group-data.js +75 -0
- package/dist/components/questions/types/match-word-to-picture/register.d.ts +2 -0
- package/dist/components/questions/types/match-word-to-picture/register.js +55 -0
- package/dist/components/questions/types/match-word-to-picture/transform.d.ts +11 -0
- package/dist/components/questions/types/match-word-to-picture/transform.js +43 -0
- package/dist/components/questions/types/matching-with-lines-group/MatchingWithLinesBoard.d.ts +20 -0
- package/dist/components/questions/types/matching-with-lines-group/MatchingWithLinesBoard.js +109 -0
- package/dist/components/questions/types/matching-with-lines-group/MatchingWithLinesGroupClient.d.ts +2 -0
- package/dist/components/questions/types/matching-with-lines-group/MatchingWithLinesGroupClient.js +30 -0
- package/dist/components/questions/types/matching-with-lines-group/MatchingWithLinesGroupCreator.d.ts +2 -0
- package/dist/components/questions/types/matching-with-lines-group/MatchingWithLinesGroupCreator.js +180 -0
- package/dist/components/questions/types/matching-with-lines-group/MatchingWithLinesImageField.d.ts +6 -0
- package/dist/components/questions/types/matching-with-lines-group/MatchingWithLinesImageField.js +26 -0
- package/dist/components/questions/types/matching-with-lines-group/index.d.ts +4 -0
- package/dist/components/questions/types/matching-with-lines-group/index.js +4 -0
- package/dist/components/questions/types/matching-with-lines-group/map-matching-with-lines-group-data.d.ts +8 -0
- package/dist/components/questions/types/matching-with-lines-group/map-matching-with-lines-group-data.js +104 -0
- package/dist/components/questions/types/matching-with-lines-group/register.d.ts +3 -0
- package/dist/components/questions/types/matching-with-lines-group/register.js +37 -0
- package/dist/components/questions/types/matching-with-lines-group/transform.d.ts +2 -0
- package/dist/components/questions/types/matching-with-lines-group/transform.js +44 -0
- package/dist/components/questions/types/true-false-correct-group/TrueFalseCorrectGroupClient.d.ts +2 -0
- package/dist/components/questions/types/true-false-correct-group/TrueFalseCorrectGroupClient.js +25 -0
- package/dist/components/questions/types/true-false-correct-group/TrueFalseCorrectGroupCreator.d.ts +2 -0
- package/dist/components/questions/types/true-false-correct-group/TrueFalseCorrectGroupCreator.js +144 -0
- package/dist/components/questions/types/true-false-correct-group/index.d.ts +5 -0
- package/dist/components/questions/types/true-false-correct-group/index.js +5 -0
- package/dist/components/questions/types/true-false-correct-group/map-true-false-correct-group-data.d.ts +4 -0
- package/dist/components/questions/types/true-false-correct-group/map-true-false-correct-group-data.js +79 -0
- package/dist/components/questions/types/true-false-correct-group/register.d.ts +2 -0
- package/dist/components/questions/types/true-false-correct-group/register.js +36 -0
- package/dist/components/questions/types/true-false-correct-group/transform.d.ts +2 -0
- package/dist/components/questions/types/true-false-correct-group/transform.js +49 -0
- package/dist/components/questions/types/write-a-short-letter/WriteAShortLetterClient.js +11 -1
- package/dist/components/questions/types/write-a-short-letter/WriteAShortLetterCreator.js +8 -2
- package/dist/components/questions/types/write-a-short-letter/register.js +4 -0
- package/dist/components/questions/types/write-a-short-letter/transform.js +4 -0
- package/dist/components/questions/viewer/ChooseTheCorrectAnswerGroupViewer.d.ts +2 -1
- package/dist/components/questions/viewer/ChooseTheCorrectAnswerGroupViewer.js +2 -2
- package/dist/components/questions/viewer/FillInBlankGroupViewer.d.ts +10 -0
- package/dist/components/questions/viewer/FillInBlankGroupViewer.js +14 -0
- package/dist/components/questions/viewer/MatchWordToPictureGroupViewer.d.ts +9 -0
- package/dist/components/questions/viewer/MatchWordToPictureGroupViewer.js +11 -0
- package/dist/components/questions/viewer/MatchWordToPictureViewer.d.ts +9 -0
- package/dist/components/questions/viewer/MatchWordToPictureViewer.js +8 -0
- package/dist/components/questions/viewer/MatchingWithLinesGroupViewer.d.ts +7 -0
- package/dist/components/questions/viewer/MatchingWithLinesGroupViewer.js +48 -0
- package/dist/components/questions/viewer/QuestionViewer.js +24 -3
- package/dist/components/questions/viewer/TrueFalseCorrectGroupViewer.d.ts +9 -0
- package/dist/components/questions/viewer/TrueFalseCorrectGroupViewer.js +14 -0
- package/dist/components/questions/viewer/index.d.ts +5 -0
- package/dist/components/questions/viewer/index.js +5 -0
- package/dist/components/results/renderers/ReviewChooseBestAnswerRenderer.js +4 -9
- package/dist/components/results/renderers/review-question-body-movers.js +74 -3
- package/dist/components/results/review-data.js +59 -0
- package/dist/components/themes/english-certification/EcWritingTask.js +2 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/shared/constants/question-skills.js +8 -0
- package/dist/shared/lib/hooks/usePresignedFileUrl.d.ts +3 -1
- package/dist/shared/lib/hooks/usePresignedFileUrl.js +30 -12
- package/dist/shared/lib/utils/question-reverse-transform.js +142 -0
- package/dist/shared/lib/utils/question-transform.js +8 -0
- 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/fill-in-blank-group.d.ts +45 -0
- package/dist/shared/types/questions/fill-in-blank-group.js +1 -0
- package/dist/shared/types/questions/index.d.ts +4 -0
- package/dist/shared/types/questions/index.js +8 -0
- package/dist/shared/types/questions/match-word-to-picture.d.ts +35 -0
- package/dist/shared/types/questions/match-word-to-picture.js +5 -0
- package/dist/shared/types/questions/matching-with-lines-group.d.ts +39 -0
- package/dist/shared/types/questions/matching-with-lines-group.js +1 -0
- package/dist/shared/types/questions/true-false-correct-group.d.ts +39 -0
- package/dist/shared/types/questions/true-false-correct-group.js +1 -0
- package/package.json +7 -2
|
@@ -36,6 +36,7 @@ function WriteAShortLetterCreatorContent({ initialData, onChange, externalErrors
|
|
|
36
36
|
const [maxWords, setMaxWords] = useState(initialData?.maxWords);
|
|
37
37
|
const [instruction, setInstruction] = useState(initialData?.instructions?.mainInstruction || '');
|
|
38
38
|
const [guideImages, setGuideImages] = useState(initialData?.guideImages || []);
|
|
39
|
+
const [passage, setPassage] = useState(initialData?.passage || '');
|
|
39
40
|
const [points, setPoints] = useState(initialData?.points || 10);
|
|
40
41
|
// Store presigned URLs keyed by imageKey (NOT index) to avoid index races
|
|
41
42
|
// when the user uploads several images quickly.
|
|
@@ -80,7 +81,7 @@ function WriteAShortLetterCreatorContent({ initialData, onChange, externalErrors
|
|
|
80
81
|
},
|
|
81
82
|
};
|
|
82
83
|
}
|
|
83
|
-
}, [validationRef, informations, minWords, maxWords, instruction, guideImages, points]);
|
|
84
|
+
}, [validationRef, informations, minWords, maxWords, instruction, guideImages, passage, points]);
|
|
84
85
|
// Sync state from initialData when it changes
|
|
85
86
|
useEffect(() => {
|
|
86
87
|
const incomingStr = JSON.stringify(initialData);
|
|
@@ -106,6 +107,7 @@ function WriteAShortLetterCreatorContent({ initialData, onChange, externalErrors
|
|
|
106
107
|
setMaxWords(initialData?.maxWords);
|
|
107
108
|
setInstruction(initialData?.instructions?.mainInstruction ?? '');
|
|
108
109
|
setGuideImages(initialData?.guideImages ?? []);
|
|
110
|
+
setPassage(initialData?.passage ?? '');
|
|
109
111
|
setPoints(initialData?.points ?? 10);
|
|
110
112
|
// Clear stale preview URLs from the previous question's uploads
|
|
111
113
|
setGuideImagePreviewUrls({});
|
|
@@ -133,6 +135,7 @@ function WriteAShortLetterCreatorContent({ initialData, onChange, externalErrors
|
|
|
133
135
|
maxWords,
|
|
134
136
|
instruction,
|
|
135
137
|
guideImages,
|
|
138
|
+
passage,
|
|
136
139
|
points,
|
|
137
140
|
});
|
|
138
141
|
previousPayloadRef.current = currentPayload;
|
|
@@ -151,6 +154,7 @@ function WriteAShortLetterCreatorContent({ initialData, onChange, externalErrors
|
|
|
151
154
|
mainInstruction: instruction || '',
|
|
152
155
|
},
|
|
153
156
|
guideImages: guideImages.length > 0 ? guideImages : undefined,
|
|
157
|
+
...(passage ? { passage } : {}),
|
|
154
158
|
points,
|
|
155
159
|
};
|
|
156
160
|
// Serialize payload for comparison
|
|
@@ -169,6 +173,7 @@ function WriteAShortLetterCreatorContent({ initialData, onChange, externalErrors
|
|
|
169
173
|
maxWords,
|
|
170
174
|
instructions: { mainInstruction: instruction || '' },
|
|
171
175
|
guideImages,
|
|
176
|
+
...(passage ? { passage } : {}),
|
|
172
177
|
points,
|
|
173
178
|
});
|
|
174
179
|
recentEmittedInitialDataRef.current = [
|
|
@@ -177,7 +182,7 @@ function WriteAShortLetterCreatorContent({ initialData, onChange, externalErrors
|
|
|
177
182
|
].slice(-10);
|
|
178
183
|
onChangeRef.current?.(payload);
|
|
179
184
|
}
|
|
180
|
-
}, [informations, minWords, maxWords, instruction, guideImages, points]);
|
|
185
|
+
}, [informations, minWords, maxWords, instruction, guideImages, passage, points]);
|
|
181
186
|
// Get validation errors
|
|
182
187
|
const getErrors = () => {
|
|
183
188
|
const errors = {};
|
|
@@ -287,6 +292,7 @@ function WriteAShortLetterCreatorContent({ initialData, onChange, externalErrors
|
|
|
287
292
|
mainInstruction: instruction || '',
|
|
288
293
|
},
|
|
289
294
|
guideImages: guideImages.length > 0 ? guideImages : undefined,
|
|
295
|
+
...(passage ? { passage } : {}),
|
|
290
296
|
};
|
|
291
297
|
return (_jsxs("div", { className: "space-y-6", children: [_jsx(Card, { children: _jsx(CardHeader, { children: _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { children: [_jsx(CardTitle, { children: "Write a Short Letter (Preview)" }), _jsx("p", { className: "mt-1 text-sm text-gray-500", children: "Ch\u1EBF \u0111\u1ED9 xem tr\u01B0\u1EDBc - Hi\u1EC3n th\u1ECB \u0111\u00E1p \u00E1n \u0111\u00FAng" })] }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => setIsClientMode(false), children: [_jsx(Pencil, { className: "mr-2 h-4 w-4" }), "Quay l\u1EA1i Edit Mode"] })] }) }) }), _jsx(WriteAShortLetterClient, { questionData: questionData, onSubmit: () => { }, isReviewMode: true })] }));
|
|
292
298
|
}
|
|
@@ -8,6 +8,7 @@ export const writeAShortLetterRegistration = {
|
|
|
8
8
|
? parseFloat(initialData.total_points)
|
|
9
9
|
: (initialData?.points || 10); // WRITE_A_SHORT_LETTER defaults to 10 points
|
|
10
10
|
let creatorData = { points: apiPoints };
|
|
11
|
+
const passage = answer?.passage || content?.passage;
|
|
11
12
|
// Priority 1: Data from store (answer has informations directly)
|
|
12
13
|
if (answer?.informations && Array.isArray(answer.informations)) {
|
|
13
14
|
creatorData = {
|
|
@@ -16,6 +17,7 @@ export const writeAShortLetterRegistration = {
|
|
|
16
17
|
maxWords: answer.maxWords,
|
|
17
18
|
instructions: answer.instructions || { mainInstruction: '' },
|
|
18
19
|
guideImages: answer.guideImages || [],
|
|
20
|
+
...(passage ? { passage } : {}),
|
|
19
21
|
points: apiPoints,
|
|
20
22
|
};
|
|
21
23
|
}
|
|
@@ -27,6 +29,7 @@ export const writeAShortLetterRegistration = {
|
|
|
27
29
|
maxWords: content.maxWords,
|
|
28
30
|
instructions: content.instructions || { mainInstruction: '' },
|
|
29
31
|
guideImages: content.guideImages || [],
|
|
32
|
+
...(passage ? { passage } : {}),
|
|
30
33
|
points: apiPoints,
|
|
31
34
|
};
|
|
32
35
|
}
|
|
@@ -42,6 +45,7 @@ export const writeAShortLetterRegistration = {
|
|
|
42
45
|
maxWords: content?.maxWords ?? answer?.maxWords,
|
|
43
46
|
instructions: content?.instructions || answer?.instructions || { mainInstruction: '' },
|
|
44
47
|
guideImages: content?.guideImages || answer?.guideImages || [],
|
|
48
|
+
...(passage ? { passage } : {}),
|
|
45
49
|
points: apiPoints,
|
|
46
50
|
};
|
|
47
51
|
}
|
|
@@ -9,6 +9,7 @@ export const transformWriteAShortLetter = (question) => {
|
|
|
9
9
|
const guideImages = Array.isArray(answerData?.guideImages)
|
|
10
10
|
? answerData.guideImages.filter((url) => typeof url === 'string' && url.trim() !== '')
|
|
11
11
|
: [];
|
|
12
|
+
const passage = typeof answerData?.passage === 'string' ? answerData.passage.trim() : '';
|
|
12
13
|
const apiContent = {
|
|
13
14
|
informations,
|
|
14
15
|
minWords: answerData?.minWords || 20,
|
|
@@ -18,6 +19,9 @@ export const transformWriteAShortLetter = (question) => {
|
|
|
18
19
|
if (guideImages.length > 0) {
|
|
19
20
|
apiContent.guideImages = guideImages;
|
|
20
21
|
}
|
|
22
|
+
if (passage) {
|
|
23
|
+
apiContent.passage = passage;
|
|
24
|
+
}
|
|
21
25
|
return {
|
|
22
26
|
apiContent,
|
|
23
27
|
apiCorrectAnswer: {},
|
|
@@ -3,6 +3,7 @@ type ChooseTheCorrectAnswerGroupViewerProps = {
|
|
|
3
3
|
question: Question;
|
|
4
4
|
isReviewMode?: boolean;
|
|
5
5
|
userAnswer?: string[];
|
|
6
|
+
onSubmitAnswer?: (answer: string[]) => void;
|
|
6
7
|
};
|
|
7
|
-
export declare function ChooseTheCorrectAnswerGroupViewer({ question, isReviewMode, userAnswer, }: ChooseTheCorrectAnswerGroupViewerProps): import("react").JSX.Element;
|
|
8
|
+
export declare function ChooseTheCorrectAnswerGroupViewer({ question, isReviewMode, userAnswer, onSubmitAnswer, }: ChooseTheCorrectAnswerGroupViewerProps): import("react").JSX.Element;
|
|
8
9
|
export {};
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
3
|
import { ChooseTheCorrectAnswerGroupClient } from '../types/choose-the-correct-answer-group/ChooseTheCorrectAnswerGroupClient';
|
|
4
4
|
import { mapQuestionToChooseAnswerGroupData } from '../types/choose-the-correct-answer-group/map-choose-the-correct-answer-group-data';
|
|
5
|
-
export function ChooseTheCorrectAnswerGroupViewer({ question, isReviewMode = false, userAnswer, }) {
|
|
5
|
+
export function ChooseTheCorrectAnswerGroupViewer({ question, isReviewMode = false, userAnswer, onSubmitAnswer, }) {
|
|
6
6
|
const questionData = mapQuestionToChooseAnswerGroupData(question);
|
|
7
7
|
const reviewAnswers = isReviewMode && !userAnswer
|
|
8
8
|
? questionData.items.map((item) => item.correctAnswer)
|
|
9
9
|
: userAnswer;
|
|
10
|
-
return (_jsx(ChooseTheCorrectAnswerGroupClient, { questionData: questionData, isReviewMode: isReviewMode, userAnswers: reviewAnswers }));
|
|
10
|
+
return (_jsx(ChooseTheCorrectAnswerGroupClient, { questionData: questionData, isReviewMode: isReviewMode, userAnswers: reviewAnswers, onAnswerChange: isReviewMode ? undefined : onSubmitAnswer }));
|
|
11
11
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Question } from '../../../shared/types/entities/question.types';
|
|
2
|
+
import type { FillInBlankGroupStudentItemAnswer } from '../_shared/types/fill-in-blank-group.type';
|
|
3
|
+
type FillInBlankGroupViewerProps = {
|
|
4
|
+
question: Question;
|
|
5
|
+
isReviewMode?: boolean;
|
|
6
|
+
userAnswer?: FillInBlankGroupStudentItemAnswer[];
|
|
7
|
+
onSubmitAnswer?: (answer: FillInBlankGroupStudentItemAnswer[]) => void;
|
|
8
|
+
};
|
|
9
|
+
export declare function FillInBlankGroupViewer({ question, isReviewMode, userAnswer, onSubmitAnswer, }: FillInBlankGroupViewerProps): import("react").JSX.Element;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { FillInBlankGroupClient } from '../types/fill-in-blank-group/FillInBlankGroupClient';
|
|
4
|
+
import { mapQuestionToFillInBlankGroupData } from '../types/fill-in-blank-group/map-fill-in-blank-group-data';
|
|
5
|
+
export function FillInBlankGroupViewer({ question, isReviewMode = false, userAnswer, onSubmitAnswer, }) {
|
|
6
|
+
const questionData = mapQuestionToFillInBlankGroupData(question);
|
|
7
|
+
const studentAnswers = Array.isArray(userAnswer) ? userAnswer : undefined;
|
|
8
|
+
const reviewAnswers = isReviewMode && !studentAnswers
|
|
9
|
+
? questionData.items.map((item) => ({
|
|
10
|
+
answers: item.answers.map((alts) => alts[0] || ''),
|
|
11
|
+
}))
|
|
12
|
+
: studentAnswers;
|
|
13
|
+
return (_jsx(FillInBlankGroupClient, { questionData: questionData, isReviewMode: isReviewMode, userAnswers: reviewAnswers, onAnswerChange: isReviewMode ? undefined : onSubmitAnswer }));
|
|
14
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Question } from '../../../shared/types/entities/question.types';
|
|
2
|
+
type MatchWordToPictureGroupViewerProps = {
|
|
3
|
+
question: Question;
|
|
4
|
+
isReviewMode?: boolean;
|
|
5
|
+
userAnswer?: string[];
|
|
6
|
+
onSubmitAnswer?: (answer: string[]) => void;
|
|
7
|
+
};
|
|
8
|
+
export declare function MatchWordToPictureGroupViewer({ question, isReviewMode, userAnswer, onSubmitAnswer, }: MatchWordToPictureGroupViewerProps): import("react").JSX.Element;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { MatchWordToPictureGroupClient } from '../types/match-word-to-picture/MatchWordToPictureGroupClient';
|
|
4
|
+
import { mapQuestionToMatchWordToPictureGroupData } from '../types/match-word-to-picture/map-match-word-to-picture-group-data';
|
|
5
|
+
export function MatchWordToPictureGroupViewer({ question, isReviewMode = false, userAnswer, onSubmitAnswer, }) {
|
|
6
|
+
const questionData = mapQuestionToMatchWordToPictureGroupData(question);
|
|
7
|
+
const reviewAnswers = isReviewMode && !userAnswer
|
|
8
|
+
? questionData.items.map((item) => item.correctAnswer)
|
|
9
|
+
: userAnswer;
|
|
10
|
+
return (_jsx(MatchWordToPictureGroupClient, { questionData: questionData, isReviewMode: isReviewMode, userAnswers: reviewAnswers, onAnswerChange: isReviewMode ? undefined : onSubmitAnswer }));
|
|
11
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Question } from '../../../shared/types/entities/question.types';
|
|
2
|
+
type MatchWordToPictureViewerProps = {
|
|
3
|
+
question: Question;
|
|
4
|
+
isReviewMode?: boolean;
|
|
5
|
+
userAnswer?: string;
|
|
6
|
+
onSubmitAnswer?: (answer: string) => void;
|
|
7
|
+
};
|
|
8
|
+
export declare function MatchWordToPictureViewer({ question, isReviewMode, userAnswer, onSubmitAnswer, }: MatchWordToPictureViewerProps): import("react").JSX.Element;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { MatchWordToPictureClient } from '../types/match-word-to-picture/MatchWordToPictureClient';
|
|
4
|
+
import { mapQuestionToMatchWordToPictureData } from '../types/match-word-to-picture/map-match-word-to-picture-data';
|
|
5
|
+
export function MatchWordToPictureViewer({ question, isReviewMode = false, userAnswer, onSubmitAnswer, }) {
|
|
6
|
+
const questionData = mapQuestionToMatchWordToPictureData(question);
|
|
7
|
+
return (_jsx(MatchWordToPictureClient, { questionData: questionData, isReviewMode: isReviewMode, userAnswer: userAnswer, autoFillCorrectAnswers: isReviewMode && !userAnswer, explanation: question.explanation, onSubmit: isReviewMode ? undefined : onSubmitAnswer }));
|
|
8
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Question } from '../../../shared/types/entities/question.types';
|
|
2
|
+
export declare function MatchingWithLinesGroupViewer({ question, isReviewMode, userAnswer, onSubmitAnswer, }: {
|
|
3
|
+
question: Question;
|
|
4
|
+
isReviewMode?: boolean;
|
|
5
|
+
userAnswer?: unknown;
|
|
6
|
+
onSubmitAnswer?: (answer: string[]) => void;
|
|
7
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
import { MatchingWithLinesGroupClient } from '../types/matching-with-lines-group/MatchingWithLinesGroupClient';
|
|
5
|
+
import { mapQuestionToMatchingWithLinesGroupData } from '../types/matching-with-lines-group/map-matching-with-lines-group-data';
|
|
6
|
+
function isRecord(value) {
|
|
7
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
8
|
+
}
|
|
9
|
+
function emptyAnswers(length) {
|
|
10
|
+
return Array.from({ length }, () => '');
|
|
11
|
+
}
|
|
12
|
+
function parseMatchingAnswers(value, length) {
|
|
13
|
+
const source = Array.isArray(value)
|
|
14
|
+
? value
|
|
15
|
+
: isRecord(value) && Array.isArray(value.answer)
|
|
16
|
+
? value.answer
|
|
17
|
+
: null;
|
|
18
|
+
if (!source)
|
|
19
|
+
return undefined;
|
|
20
|
+
return Array.from({ length }, (_, index) => typeof source[index] === 'string' ? source[index] : '');
|
|
21
|
+
}
|
|
22
|
+
export function MatchingWithLinesGroupViewer({ question, isReviewMode = false, userAnswer, onSubmitAnswer, }) {
|
|
23
|
+
const questionData = mapQuestionToMatchingWithLinesGroupData(question);
|
|
24
|
+
const itemCount = questionData.items.length;
|
|
25
|
+
const parsedUser = parseMatchingAnswers(userAnswer, itemCount);
|
|
26
|
+
const questionKey = `${question.id}:${question.type}:${itemCount}:${questionData.rightItems
|
|
27
|
+
.map((item) => `${item.id}:${item.text}`)
|
|
28
|
+
.join(',')}`;
|
|
29
|
+
const [cachedKey, setCachedKey] = useState(questionKey);
|
|
30
|
+
const [localAnswers, setLocalAnswers] = useState(() => parsedUser ?? emptyAnswers(itemCount));
|
|
31
|
+
if (cachedKey !== questionKey) {
|
|
32
|
+
setCachedKey(questionKey);
|
|
33
|
+
setLocalAnswers(parsedUser ?? emptyAnswers(itemCount));
|
|
34
|
+
}
|
|
35
|
+
const userAnswers = isReviewMode && !parsedUser
|
|
36
|
+
? questionData.items.map((item) => item.correctAnswer)
|
|
37
|
+
: parsedUser ?? localAnswers;
|
|
38
|
+
const handleAnswersChange = (next) => {
|
|
39
|
+
const padded = questionData.items.map((item, index) => {
|
|
40
|
+
if (item.isExample)
|
|
41
|
+
return item.correctAnswer || '';
|
|
42
|
+
return typeof next[index] === 'string' ? next[index] : '';
|
|
43
|
+
});
|
|
44
|
+
setLocalAnswers(padded);
|
|
45
|
+
onSubmitAnswer?.(padded);
|
|
46
|
+
};
|
|
47
|
+
return (_jsx(MatchingWithLinesGroupClient, { questionData: questionData, isReviewMode: isReviewMode, userAnswers: userAnswers, onAnswersChange: isReviewMode ? undefined : handleAnswersChange }));
|
|
48
|
+
}
|
|
@@ -25,10 +25,15 @@ import { parseFillBlankStemLine } from '../_shared/utils/parseFillBlankStem';
|
|
|
25
25
|
import { WordFillStructuredFormViewer } from './WordFillStructuredFormViewer';
|
|
26
26
|
import { ChooseTheCorrectAnswerGroupViewer } from './ChooseTheCorrectAnswerGroupViewer';
|
|
27
27
|
import { TrueFalseGroupViewer } from './TrueFalseGroupViewer';
|
|
28
|
+
import { TrueFalseCorrectGroupViewer } from './TrueFalseCorrectGroupViewer';
|
|
29
|
+
import { FillInBlankGroupViewer } from './FillInBlankGroupViewer';
|
|
30
|
+
import { MatchingWithLinesGroupViewer } from './MatchingWithLinesGroupViewer';
|
|
28
31
|
import { CrossOutWordGroupViewer } from './CrossOutWordGroupViewer';
|
|
29
32
|
import { SpontaneousQaGroupViewer } from './SpontaneousQaGroupViewer';
|
|
30
33
|
import { WordOrderAndMatchGroupViewer } from './WordOrderAndMatchGroupViewer';
|
|
31
34
|
import { AnswerTheQuestionGroupViewer } from './AnswerTheQuestionGroupViewer';
|
|
35
|
+
import { MatchWordToPictureViewer } from './MatchWordToPictureViewer';
|
|
36
|
+
import { MatchWordToPictureGroupViewer } from './MatchWordToPictureGroupViewer';
|
|
32
37
|
export function QuestionViewer({ question, onSubmitAnswer, isReviewMode = false, userAnswer, }) {
|
|
33
38
|
const [selectedAnswer, setSelectedAnswer] = useState(userAnswer || null);
|
|
34
39
|
const [playCount, setPlayCount] = useState(0);
|
|
@@ -338,6 +343,7 @@ export function QuestionViewer({ question, onSubmitAnswer, isReviewMode = false,
|
|
|
338
343
|
: Array.isArray(answer?.guideImages)
|
|
339
344
|
? answer.guideImages
|
|
340
345
|
: [];
|
|
346
|
+
const passage = content?.passage ?? answer?.passage;
|
|
341
347
|
const contexts = content?.contexts || answer?.contexts || [];
|
|
342
348
|
const minWords = content?.minWords ?? answer?.minWords ?? 20;
|
|
343
349
|
const maxWords = content?.maxWords ?? answer?.maxWords ?? null;
|
|
@@ -345,6 +351,7 @@ export function QuestionViewer({ question, onSubmitAnswer, isReviewMode = false,
|
|
|
345
351
|
contexts,
|
|
346
352
|
informations,
|
|
347
353
|
guideImages,
|
|
354
|
+
passage,
|
|
348
355
|
minWords,
|
|
349
356
|
maxWords,
|
|
350
357
|
instructions: {
|
|
@@ -625,9 +632,15 @@ export function QuestionViewer({ question, onSubmitAnswer, isReviewMode = false,
|
|
|
625
632
|
case 'CHOOSE_THE_CORRECT_ANSWER':
|
|
626
633
|
return renderChooseTheCorrectAnswer();
|
|
627
634
|
case 'CHOOSE_THE_CORRECT_ANSWER_GROUP':
|
|
628
|
-
return (_jsx(ChooseTheCorrectAnswerGroupViewer, { question: question, isReviewMode: isReviewMode, userAnswer: userAnswer }));
|
|
635
|
+
return (_jsx(ChooseTheCorrectAnswerGroupViewer, { question: question, isReviewMode: isReviewMode, userAnswer: userAnswer, onSubmitAnswer: onSubmitAnswer }));
|
|
629
636
|
case 'TRUE_FALSE_GROUP':
|
|
630
637
|
return (_jsx(TrueFalseGroupViewer, { question: question, isReviewMode: isReviewMode, userAnswer: userAnswer }));
|
|
638
|
+
case 'TRUE_FALSE_CORRECT_GROUP':
|
|
639
|
+
return (_jsx(TrueFalseCorrectGroupViewer, { question: question, isReviewMode: isReviewMode, userAnswer: userAnswer }));
|
|
640
|
+
case 'FILL_IN_BLANK_GROUP':
|
|
641
|
+
return (_jsx(FillInBlankGroupViewer, { question: question, isReviewMode: isReviewMode, userAnswer: userAnswer, onSubmitAnswer: onSubmitAnswer }));
|
|
642
|
+
case 'MATCHING_WITH_LINES_GROUP':
|
|
643
|
+
return (_jsx(MatchingWithLinesGroupViewer, { question: question, isReviewMode: isReviewMode, userAnswer: userAnswer, onSubmitAnswer: onSubmitAnswer }));
|
|
631
644
|
case 'CROSS_OUT_WORD_GROUP':
|
|
632
645
|
return (_jsx(CrossOutWordGroupViewer, { question: question, isReviewMode: isReviewMode }));
|
|
633
646
|
case 'ANSWER_THE_QUESTION_GROUP':
|
|
@@ -637,6 +650,10 @@ export function QuestionViewer({ question, onSubmitAnswer, isReviewMode = false,
|
|
|
637
650
|
case 'WORD_ORDER_AND_MATCH_GROUP':
|
|
638
651
|
case 'WORD_ORDER_AND_MATCH':
|
|
639
652
|
return (_jsx(WordOrderAndMatchGroupViewer, { question: question, isReviewMode: isReviewMode, userAnswer: userAnswer }));
|
|
653
|
+
case 'MATCH_WORD_TO_PICTURE_GROUP':
|
|
654
|
+
return (_jsx(MatchWordToPictureGroupViewer, { question: question, isReviewMode: isReviewMode, userAnswer: userAnswer, onSubmitAnswer: onSubmitAnswer }));
|
|
655
|
+
case 'MATCH_WORD_TO_PICTURE':
|
|
656
|
+
return (_jsx(MatchWordToPictureViewer, { question: question, isReviewMode: isReviewMode, userAnswer: userAnswer, onSubmitAnswer: onSubmitAnswer }));
|
|
640
657
|
case 'FILL_IN_BLANK':
|
|
641
658
|
return renderFillInBlank();
|
|
642
659
|
case 'TRUE_FALSE':
|
|
@@ -691,7 +708,9 @@ export function QuestionViewer({ question, onSubmitAnswer, isReviewMode = false,
|
|
|
691
708
|
*/
|
|
692
709
|
const resolveCardTitle = () => {
|
|
693
710
|
// WFSF and Crossword show title/instruction inside the client — no outer card label.
|
|
694
|
-
if (question.type === 'WORD_FILL_STRUCTURED_FORM' ||
|
|
711
|
+
if (question.type === 'WORD_FILL_STRUCTURED_FORM' ||
|
|
712
|
+
question.type === 'CROSSWORD_PUZZLE' ||
|
|
713
|
+
question.type === 'MATCH_WORD_TO_PICTURE') {
|
|
695
714
|
return null;
|
|
696
715
|
}
|
|
697
716
|
// Modern fill stems live entirely in the body; title is a short label.
|
|
@@ -714,7 +733,9 @@ export function QuestionViewer({ question, onSubmitAnswer, isReviewMode = false,
|
|
|
714
733
|
};
|
|
715
734
|
const cardTitle = resolveCardTitle();
|
|
716
735
|
// WFSF and Crossword already have their own bordered shell — avoid double Card border.
|
|
717
|
-
if (question.type === 'WORD_FILL_STRUCTURED_FORM' ||
|
|
736
|
+
if (question.type === 'WORD_FILL_STRUCTURED_FORM' ||
|
|
737
|
+
question.type === 'CROSSWORD_PUZZLE' ||
|
|
738
|
+
question.type === 'MATCH_WORD_TO_PICTURE') {
|
|
718
739
|
return (_jsx("div", { className: "space-y-4", children: renderQuestion() }));
|
|
719
740
|
}
|
|
720
741
|
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,9 @@
|
|
|
1
|
+
import type { Question } from '../../../shared/types/entities/question.types';
|
|
2
|
+
import type { TrueFalseCorrectStudentItemAnswer } from '../_shared/types/true-false-correct-group.type';
|
|
3
|
+
type TrueFalseCorrectGroupViewerProps = {
|
|
4
|
+
question: Question;
|
|
5
|
+
isReviewMode?: boolean;
|
|
6
|
+
userAnswer?: TrueFalseCorrectStudentItemAnswer[];
|
|
7
|
+
};
|
|
8
|
+
export declare function TrueFalseCorrectGroupViewer({ question, isReviewMode, userAnswer, }: TrueFalseCorrectGroupViewerProps): import("react").JSX.Element;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { TrueFalseCorrectGroupClient } from '../types/true-false-correct-group/TrueFalseCorrectGroupClient';
|
|
4
|
+
import { mapQuestionToTrueFalseCorrectGroupData } from '../types/true-false-correct-group/map-true-false-correct-group-data';
|
|
5
|
+
export function TrueFalseCorrectGroupViewer({ question, isReviewMode = false, userAnswer, }) {
|
|
6
|
+
const questionData = mapQuestionToTrueFalseCorrectGroupData(question);
|
|
7
|
+
const reviewAnswers = isReviewMode && !userAnswer
|
|
8
|
+
? questionData.items.map((item) => ({
|
|
9
|
+
isTrue: item.isTrue,
|
|
10
|
+
correction: item.correction,
|
|
11
|
+
}))
|
|
12
|
+
: userAnswer;
|
|
13
|
+
return (_jsx(TrueFalseCorrectGroupClient, { questionData: questionData, isReviewMode: isReviewMode, userAnswers: reviewAnswers }));
|
|
14
|
+
}
|
|
@@ -3,7 +3,12 @@ export { QuestionGroupViewer } from './QuestionGroupViewer';
|
|
|
3
3
|
export { WordFillStructuredFormViewer } from './WordFillStructuredFormViewer';
|
|
4
4
|
export { ChooseTheCorrectAnswerGroupViewer } from './ChooseTheCorrectAnswerGroupViewer';
|
|
5
5
|
export { TrueFalseGroupViewer } from './TrueFalseGroupViewer';
|
|
6
|
+
export { TrueFalseCorrectGroupViewer } from './TrueFalseCorrectGroupViewer';
|
|
7
|
+
export { FillInBlankGroupViewer } from './FillInBlankGroupViewer';
|
|
8
|
+
export { MatchingWithLinesGroupViewer } from './MatchingWithLinesGroupViewer';
|
|
6
9
|
export { CrossOutWordGroupViewer } from './CrossOutWordGroupViewer';
|
|
7
10
|
export { SpontaneousQaGroupViewer } from './SpontaneousQaGroupViewer';
|
|
8
11
|
export { WordOrderAndMatchGroupViewer } from './WordOrderAndMatchGroupViewer';
|
|
9
12
|
export { AnswerTheQuestionGroupViewer } from './AnswerTheQuestionGroupViewer';
|
|
13
|
+
export { MatchWordToPictureViewer } from './MatchWordToPictureViewer';
|
|
14
|
+
export { MatchWordToPictureGroupViewer } from './MatchWordToPictureGroupViewer';
|
|
@@ -3,7 +3,12 @@ export { QuestionGroupViewer } from './QuestionGroupViewer';
|
|
|
3
3
|
export { WordFillStructuredFormViewer } from './WordFillStructuredFormViewer';
|
|
4
4
|
export { ChooseTheCorrectAnswerGroupViewer } from './ChooseTheCorrectAnswerGroupViewer';
|
|
5
5
|
export { TrueFalseGroupViewer } from './TrueFalseGroupViewer';
|
|
6
|
+
export { TrueFalseCorrectGroupViewer } from './TrueFalseCorrectGroupViewer';
|
|
7
|
+
export { FillInBlankGroupViewer } from './FillInBlankGroupViewer';
|
|
8
|
+
export { MatchingWithLinesGroupViewer } from './MatchingWithLinesGroupViewer';
|
|
6
9
|
export { CrossOutWordGroupViewer } from './CrossOutWordGroupViewer';
|
|
7
10
|
export { SpontaneousQaGroupViewer } from './SpontaneousQaGroupViewer';
|
|
8
11
|
export { WordOrderAndMatchGroupViewer } from './WordOrderAndMatchGroupViewer';
|
|
9
12
|
export { AnswerTheQuestionGroupViewer } from './AnswerTheQuestionGroupViewer';
|
|
13
|
+
export { MatchWordToPictureViewer } from './MatchWordToPictureViewer';
|
|
14
|
+
export { MatchWordToPictureGroupViewer } from './MatchWordToPictureGroupViewer';
|
|
@@ -1,16 +1,11 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs
|
|
3
|
-
import { Check, X
|
|
4
|
-
import { useState } from 'react';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Check, X } from 'lucide-react';
|
|
5
4
|
import { ResolvedImage } from '../../../components/common/ResolvedImage';
|
|
6
5
|
import CambridgeYlePartBanner from '../../../components/themes/cambridge-yle/CambridgeYlePartBanner';
|
|
7
6
|
import CambridgeYleInstructionBanner from '../../../components/themes/cambridge-yle/CambridgeYleInstructionBanner';
|
|
8
7
|
import CambridgeYleAudioPlayer from '../../../components/themes/cambridge-yle/CambridgeYleAudioPlayer';
|
|
9
|
-
|
|
10
|
-
function QuestionImage({ imageUrl }) {
|
|
11
|
-
const [isZoomed, setIsZoomed] = useState(false);
|
|
12
|
-
return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "relative flex items-center justify-center", children: [_jsx(ResolvedImage, { src: imageUrl, alt: "Question illustration", className: "max-h-[250px] cursor-pointer rounded-lg object-contain shadow-md", onClick: () => setIsZoomed(true) }), _jsx("button", { type: "button", onClick: () => setIsZoomed(true), className: "absolute right-2 top-2 rounded-full bg-white/80 p-1.5 shadow-md hover:bg-white", "aria-label": "Zoom image", children: _jsx(ZoomIn, { className: "h-4 w-4 text-gray-600" }) })] }), isZoomed && (_jsx("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/70", onClick: () => setIsZoomed(false), children: _jsx(ResolvedImage, { src: imageUrl, alt: "Question illustration (zoomed)", className: "max-h-[90vh] max-w-[90vw] rounded-lg object-contain", showLoadingState: false }) }))] }));
|
|
13
|
-
}
|
|
8
|
+
import { ChooseBestAnswerImages, getChooseBestAnswerOptionImageSrc } from '../../../components/exams/take/components/question-renderers/ChooseBestAnswerImages';
|
|
14
9
|
// Single Question Component for Review Mode
|
|
15
10
|
function ReviewQuestionItem({ question, selectedOptionIds, isCorrect, correctOptionIds, }) {
|
|
16
11
|
const hasStudentAnswered = selectedOptionIds.length > 0;
|
|
@@ -48,7 +43,7 @@ function ReviewQuestionItem({ question, selectedOptionIds, isCorrect, correctOpt
|
|
|
48
43
|
}
|
|
49
44
|
return _jsx("div", { className: `flex h-5 w-5 items-center justify-center border-2 border-gray-300 bg-white ${shapeClass}` });
|
|
50
45
|
};
|
|
51
|
-
return (_jsxs("div", { className: "mb-4", children: [_jsxs("p", { className: "mb-2 text-lg font-bold text-gray-900", children: [question.questionNumber, ". ", question.questionText] }), isMultipleAnswers && (_jsx("p", { className: "mb-3 pl-2 text-sm font-medium text-blue-600", children: "Ch\u1ECDn nhi\u1EC1u \u0111\u00E1p \u00E1n" })),
|
|
46
|
+
return (_jsxs("div", { className: "mb-4", children: [_jsxs("p", { className: "mb-2 text-lg font-bold text-gray-900", children: [question.questionNumber, ". ", question.questionText] }), isMultipleAnswers && (_jsx("p", { className: "mb-3 pl-2 text-sm font-medium text-blue-600", children: "Ch\u1ECDn nhi\u1EC1u \u0111\u00E1p \u00E1n" })), _jsx(ChooseBestAnswerImages, { imageUrl: question.imageUrl, className: "mb-3" }), isImageOptions ? (_jsx("div", { className: "grid gap-3 px-2", style: { gridTemplateColumns: `repeat(${question.options.length}, minmax(0, 1fr))` }, children: question.options.map((option) => (_jsxs("div", { className: `relative flex flex-col items-center gap-1 rounded-lg border-2 p-2 cursor-default ${getOptionStyle(option.id)}`, children: [_jsx("span", { className: "text-xs font-medium text-gray-600", children: option.label }), _jsx(ResolvedImage, { src: getChooseBestAnswerOptionImageSrc(option), alt: `Option ${option.label}`, className: "h-28 w-28 rounded object-contain" }), renderIndicator(option.id)] }, option.id))) })) : (
|
|
52
47
|
/* Options — text variant */
|
|
53
48
|
_jsx("div", { className: "flex flex-col gap-2 pl-2", children: question.options.map((option) => (_jsxs("div", { className: `flex items-center gap-3 rounded-lg border-2 px-3 py-2 cursor-default ${getOptionStyle(option.id)}`, children: [_jsxs("span", { className: "text-sm font-medium text-gray-600", children: [option.label, "."] }), _jsx("span", { className: "flex-1 text-sm text-gray-700", children: option.text }), renderIndicator(option.id)] }, option.id))) }))] }));
|
|
54
49
|
}
|
|
@@ -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, MoversPictureChooseRenderer, MoversPictureFillBlankChooseRenderer, MoversReadingPassageRenderer, MoversTrueFalseRenderer, MoversCrossOutWordGroupRenderer, MoversWordFillParagraphRenderer, MoversWordFillStructuredFormRenderer, MoversWordOrderingRenderer, MoversWriteSentencesRenderer, MoversWritingRenderer, MoversCrosswordPuzzleRenderer, } from '../../../components/exams/take/components/question-renderers';
|
|
8
|
-
import { transformAnswerTheQuestion, transformFillWordHint, transformGridFill, transformLabelPicture, transformMatchByWritingAnswer, transformPictureChoose, transformPictureFillBlankChoose, transformReadingPassage, transformTrueFalse, transformWordFillParagraph, transformWordFillStructuredForm, transformWordOrdering, 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, 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, transformWriteSentences, transformWriteShortLetter, transformCrosswordPuzzle, } 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;
|
|
@@ -26,7 +26,7 @@ export function renderMoversReviewBody(ctx) {
|
|
|
26
26
|
const data = transformWriteShortLetter(firstQuestion);
|
|
27
27
|
const writingAnswer = answers[firstQuestion.id] || '';
|
|
28
28
|
const gradingInfo = essayGradingMap[firstQuestion.id];
|
|
29
|
-
return (_jsx(MoversWritingRenderer, { partNumber: partNo, partName: part.name, prompt: data.prompt, requirements: data.requirements, minWords: data.minWords, maxWords: data.maxWords, answer: writingAnswer, onAnswerChange: handleAnswerChange, isReviewMode: true, teacherScore: gradingInfo?.score, maxScore: gradingInfo?.maxScore, teacherFeedback: gradingInfo?.teacherFeedback, writingAssessment: gradingInfo?.writingAssessment }));
|
|
29
|
+
return (_jsx(MoversWritingRenderer, { partNumber: partNo, partName: part.name, prompt: data.prompt, requirements: data.requirements, minWords: data.minWords, maxWords: data.maxWords, answer: writingAnswer, onAnswerChange: handleAnswerChange, isReviewMode: true, guideImages: data.guideImages, passage: data.passage, teacherScore: gradingInfo?.score, maxScore: gradingInfo?.maxScore, teacherFeedback: gradingInfo?.teacherFeedback, writingAssessment: gradingInfo?.writingAssessment }));
|
|
30
30
|
}
|
|
31
31
|
case 'LOOK_PICTURE_CHOOSE_CORRECT_ANSWER': {
|
|
32
32
|
const data = transformPictureChoose(apiQuestions);
|
|
@@ -110,6 +110,73 @@ 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 'TRUE_FALSE_CORRECT_GROUP': {
|
|
114
|
+
const data = transformTrueFalseCorrectGroup(apiQuestions);
|
|
115
|
+
const correctAnswerMap = {};
|
|
116
|
+
data.questions.forEach((question) => {
|
|
117
|
+
if (question.exampleCorrectAnswer) {
|
|
118
|
+
correctAnswerMap[question.id] = question.exampleCorrectAnswer;
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
const parent = apiQuestions[0];
|
|
122
|
+
const rawCorrect = parent?.id ? rawCorrectAnswerMap[parent.id] : undefined;
|
|
123
|
+
const rawCorrectRecord = rawCorrect && typeof rawCorrect === 'object' && !Array.isArray(rawCorrect)
|
|
124
|
+
? rawCorrect
|
|
125
|
+
: undefined;
|
|
126
|
+
const correctArr = Array.isArray(rawCorrectRecord?.answer)
|
|
127
|
+
? rawCorrectRecord.answer
|
|
128
|
+
: Array.isArray(parent?.correct_answer?.answer)
|
|
129
|
+
? parent.correct_answer.answer
|
|
130
|
+
: [];
|
|
131
|
+
data.questions.forEach((question, index) => {
|
|
132
|
+
if (!question.isExample && correctArr[index] && typeof correctArr[index] === 'object') {
|
|
133
|
+
const obj = correctArr[index];
|
|
134
|
+
const isTrue = obj.isTrue === true ? true : obj.isTrue === false ? false : null;
|
|
135
|
+
if (isTrue !== null) {
|
|
136
|
+
correctAnswerMap[question.id] = {
|
|
137
|
+
isTrue,
|
|
138
|
+
correction: typeof obj.correction === 'string' ? obj.correction : '',
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
return (_jsx(MoversTrueFalseCorrectGroupRenderer, { partNumber: partNo, partName: part.name, instruction: part.instructions || data.instruction, questionCount: questionCount, questions: data.questions, answers: answers, onAnswerChange: handleAnswerChange, isReviewMode: true, correctAnswerMap: correctAnswerMap }));
|
|
144
|
+
}
|
|
145
|
+
case 'FILL_IN_BLANK_GROUP': {
|
|
146
|
+
const data = transformFillInBlankGroup(apiQuestions);
|
|
147
|
+
const correctAnswerMap = {};
|
|
148
|
+
const parent = apiQuestions[0];
|
|
149
|
+
const rawCorrect = parent?.id ? rawCorrectAnswerMap[parent.id] : undefined;
|
|
150
|
+
const rawCorrectRecord = rawCorrect && typeof rawCorrect === 'object' && !Array.isArray(rawCorrect)
|
|
151
|
+
? rawCorrect
|
|
152
|
+
: undefined;
|
|
153
|
+
const correctArr = Array.isArray(rawCorrectRecord?.answer)
|
|
154
|
+
? rawCorrectRecord.answer
|
|
155
|
+
: Array.isArray(parent?.correct_answer?.answer)
|
|
156
|
+
? parent.correct_answer.answer
|
|
157
|
+
: [];
|
|
158
|
+
data.questions.forEach((question, index) => {
|
|
159
|
+
const rawItem = correctArr[index];
|
|
160
|
+
if (!rawItem || typeof rawItem !== 'object' || Array.isArray(rawItem)) {
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
const obj = rawItem;
|
|
164
|
+
if (!Array.isArray(obj.answers)) {
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
correctAnswerMap[question.id] = {
|
|
168
|
+
answers: obj.answers.map((blank) => (Array.isArray(blank)
|
|
169
|
+
? blank.map((alt) => String(alt ?? '').trim()).filter(Boolean)
|
|
170
|
+
: [String(blank ?? '').trim()].filter(Boolean))),
|
|
171
|
+
caseSensitive: obj.caseSensitive === true,
|
|
172
|
+
};
|
|
173
|
+
});
|
|
174
|
+
return (_jsx(MoversFillInBlankGroupRenderer, { partNumber: partNo, partName: part.name, instruction: part.instructions || data.instruction, questionCount: data.questions.filter((question) => !question.isExample).length, audioUrl: data.audioUrl, imageUrl: data.imageUrl, showHints: data.showHints, hints: data.hints, questions: data.questions, answers: answers, onAnswerChange: handleAnswerChange, isReviewMode: true, correctAnswerMap: correctAnswerMap }));
|
|
175
|
+
}
|
|
176
|
+
case 'MATCHING_WITH_LINES_GROUP': {
|
|
177
|
+
const data = transformMatchingWithLinesGroup(apiQuestions);
|
|
178
|
+
return (_jsx(MoversMatchingWithLinesGroupRenderer, { partNumber: partNo, partName: part.name, instruction: part.instructions || data.instruction, questionCount: questionCount, data: data, answers: answers, onAnswerChange: handleAnswerChange, isReviewMode: true }));
|
|
179
|
+
}
|
|
113
180
|
case 'TRUE_FALSE': {
|
|
114
181
|
const data = transformTrueFalse(apiQuestions);
|
|
115
182
|
return (_jsx(MoversTrueFalseRenderer, { partNumber: partNo, partName: part.name, instruction: part.instructions || data.instruction, questionCount: questionCount, imageUrl: data.imageUrl, questions: data.questions, answers: answers, onAnswerChange: handleAnswerChange, isReviewMode: true, correctAnswerTextMap: correctAnswerTextMap }));
|
|
@@ -118,6 +185,10 @@ export function renderMoversReviewBody(ctx) {
|
|
|
118
185
|
const data = transformMatchByWritingAnswer(apiQuestions);
|
|
119
186
|
return (_jsx(MoversMatchByWritingAnswerRenderer, { partNumber: partNo, partName: part.name, instruction: part.instructions || data.instruction, questionCount: questionCount, audioUrl: part.audioUrl, options: data.options, questions: data.questions, answers: answers, onAnswerChange: handleAnswerChange, isReviewMode: true, viewMode: data.viewMode, groupImageUrl: data.groupImageUrl, correctAnswerTextMap: correctAnswerTextMap, isCorrectMap: isCorrectMap }));
|
|
120
187
|
}
|
|
188
|
+
case 'MATCH_WORD_TO_PICTURE': {
|
|
189
|
+
const data = transformMatchWordToPicture(apiQuestions);
|
|
190
|
+
return (_jsx(MoversMatchWordToPictureRenderer, { partNumber: partNo, partName: part.name, instruction: part.instructions || data.instruction, questionCount: questionCount, wordBank: data.wordBank, questions: data.questions, answers: answers, onAnswerChange: handleAnswerChange, isReviewMode: true, correctAnswerTextMap: correctAnswerTextMap, isCorrectMap: isCorrectMap }));
|
|
191
|
+
}
|
|
121
192
|
case 'WORD_ORDERING': {
|
|
122
193
|
const data = transformWordOrdering(apiQuestions);
|
|
123
194
|
return (_jsx(MoversWordOrderingRenderer, { partNumber: partNo, partName: part.name, instruction: part.instructions || data.instruction, questionCount: questionCount, questions: data.questions, answers: answers, onAnswerChange: handleAnswerChange, isReviewMode: true, rawCorrectAnswerMap: rawCorrectAnswerMap, isCorrectMap: isCorrectMap }));
|
|
@@ -8,6 +8,9 @@ const FLATTENED_ANSWER_TYPES = new Set([
|
|
|
8
8
|
'CHOOSE_THE_CORRECT_ANSWER_GROUP',
|
|
9
9
|
'ANSWER_THE_QUESTION_GROUP',
|
|
10
10
|
'CROSS_OUT_WORD_GROUP',
|
|
11
|
+
'TRUE_FALSE_CORRECT_GROUP',
|
|
12
|
+
'FILL_IN_BLANK_GROUP',
|
|
13
|
+
'MATCHING_WITH_LINES_GROUP',
|
|
11
14
|
]);
|
|
12
15
|
export function transformResultAnswersForReview(answers, templateParts = []) {
|
|
13
16
|
const groups = groupAnswers(answers);
|
|
@@ -183,6 +186,62 @@ function transformStudentAnswer(questionId, questionType, value) {
|
|
|
183
186
|
normalizeCrossOutWordGroupAnswer(item),
|
|
184
187
|
]));
|
|
185
188
|
}
|
|
189
|
+
if (questionType === 'TRUE_FALSE_CORRECT_GROUP') {
|
|
190
|
+
const answer = Array.isArray(record.answer)
|
|
191
|
+
? record.answer
|
|
192
|
+
: Array.isArray(value)
|
|
193
|
+
? value
|
|
194
|
+
: [];
|
|
195
|
+
return Object.fromEntries(answer.map((item, index) => {
|
|
196
|
+
if (item && typeof item === 'object' && !Array.isArray(item)) {
|
|
197
|
+
const obj = item;
|
|
198
|
+
const isTrue = obj.isTrue === true ? true : obj.isTrue === false ? false : null;
|
|
199
|
+
return [
|
|
200
|
+
`${questionId}-item-${index}`,
|
|
201
|
+
{
|
|
202
|
+
isTrue,
|
|
203
|
+
correction: typeof obj.correction === 'string' ? obj.correction : '',
|
|
204
|
+
},
|
|
205
|
+
];
|
|
206
|
+
}
|
|
207
|
+
return [`${questionId}-item-${index}`, { isTrue: null, correction: '' }];
|
|
208
|
+
}));
|
|
209
|
+
}
|
|
210
|
+
if (questionType === 'FILL_IN_BLANK_GROUP') {
|
|
211
|
+
const answer = Array.isArray(record.answer)
|
|
212
|
+
? record.answer
|
|
213
|
+
: Array.isArray(value)
|
|
214
|
+
? value
|
|
215
|
+
: [];
|
|
216
|
+
return Object.fromEntries(answer.map((item, index) => {
|
|
217
|
+
if (item && typeof item === 'object' && !Array.isArray(item) && Array.isArray(item.answers)) {
|
|
218
|
+
return [
|
|
219
|
+
`${questionId}-item-${index}`,
|
|
220
|
+
{
|
|
221
|
+
answers: item.answers.map((value) => String(value ?? '')),
|
|
222
|
+
},
|
|
223
|
+
];
|
|
224
|
+
}
|
|
225
|
+
if (Array.isArray(item)) {
|
|
226
|
+
return [`${questionId}-item-${index}`, { answers: item.map((value) => String(value ?? '')) }];
|
|
227
|
+
}
|
|
228
|
+
if (typeof item === 'string') {
|
|
229
|
+
return [`${questionId}-item-${index}`, { answers: [item] }];
|
|
230
|
+
}
|
|
231
|
+
return [`${questionId}-item-${index}`, { answers: [] }];
|
|
232
|
+
}));
|
|
233
|
+
}
|
|
234
|
+
if (questionType === 'MATCHING_WITH_LINES_GROUP') {
|
|
235
|
+
const answer = Array.isArray(record.answer)
|
|
236
|
+
? record.answer
|
|
237
|
+
: Array.isArray(value)
|
|
238
|
+
? value
|
|
239
|
+
: [];
|
|
240
|
+
return Object.fromEntries(answer.map((item, index) => [
|
|
241
|
+
`${questionId}-item-${index}`,
|
|
242
|
+
typeof item === 'string' ? item : '',
|
|
243
|
+
]));
|
|
244
|
+
}
|
|
186
245
|
if (questionType === 'CHOOSE_THE_CORRECT_ANSWER' || questionType === 'CHOOSE_CORRECT_ADJECTIVE') {
|
|
187
246
|
const rawAns = record.answer ?? record.optionId ?? record.optionIds;
|
|
188
247
|
const ids = getOptionIds(questionId, rawAns);
|