@tinyweb_dev/oe-exam-sdk 0.2.13 → 1.0.0
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/README.md +12 -0
- package/dist/components/exams/take/ExamTakingPageContainer.js +6 -0
- package/dist/components/exams/take/components/QuestionRenderer.js +13 -3
- package/dist/components/exams/take/components/question-renderers/ChooseThenAnswerGroupRenderer.d.ts +14 -0
- package/dist/components/exams/take/components/question-renderers/ChooseThenAnswerGroupRenderer.js +32 -0
- package/dist/components/exams/take/components/question-renderers/MoversAnswerTheQuestionRenderer.d.ts +1 -1
- package/dist/components/exams/take/components/question-renderers/MoversAnswerTheQuestionRenderer.js +10 -2
- package/dist/components/exams/take/components/question-renderers/SortWordsIntoCategoriesRenderer.d.ts +16 -0
- package/dist/components/exams/take/components/question-renderers/SortWordsIntoCategoriesRenderer.js +23 -0
- package/dist/components/exams/take/components/question-renderers/index.d.ts +2 -0
- package/dist/components/exams/take/components/question-renderers/index.js +2 -0
- package/dist/components/exams/take/utils/answer-transformers.js +34 -1
- package/dist/components/exams/take/utils/question-transformers.d.ts +42 -3
- package/dist/components/exams/take/utils/question-transformers.js +71 -1
- package/dist/components/questions/_shared/config/question-types.config.js +12 -0
- package/dist/components/questions/_shared/types/answer-the-question-group.type.d.ts +5 -1
- package/dist/components/questions/_shared/types/answer-the-question-group.type.js +7 -0
- package/dist/components/questions/_shared/types/answer-the-question.type.d.ts +3 -1
- package/dist/components/questions/_shared/types/answer-the-question.type.js +19 -1
- package/dist/components/questions/_shared/types/choose-then-answer-group.type.d.ts +47 -0
- package/dist/components/questions/_shared/types/choose-then-answer-group.type.js +2 -0
- package/dist/components/questions/_shared/types/index.d.ts +2 -0
- package/dist/components/questions/_shared/types/index.js +2 -0
- package/dist/components/questions/_shared/types/match-word-to-picture-group.type.d.ts +9 -3
- package/dist/components/questions/_shared/types/match-word-to-picture-group.type.js +19 -0
- package/dist/components/questions/_shared/types/sort-words-into-categories.type.d.ts +43 -0
- package/dist/components/questions/_shared/types/sort-words-into-categories.type.js +1 -0
- package/dist/components/questions/components/QuestionSearchDropdown.js +2 -0
- package/dist/components/questions/components/QuestionTypeSelector.js +2 -0
- package/dist/components/questions/creator/question-type-registry.js +4 -0
- package/dist/components/questions/types/answer-the-question/AnswerTheQuestionClient.js +30 -6
- package/dist/components/questions/types/answer-the-question/map-answer-the-question-data.d.ts +2 -0
- package/dist/components/questions/types/answer-the-question/map-answer-the-question-data.js +56 -0
- package/dist/components/questions/types/answer-the-question/transform.js +9 -3
- package/dist/components/questions/types/answer-the-question-group/AnswerTheQuestionGroupClient.js +42 -9
- package/dist/components/questions/types/answer-the-question-group/AnswerTheQuestionGroupCreator.js +7 -5
- package/dist/components/questions/types/answer-the-question-group/map-answer-the-question-group-data.js +18 -4
- package/dist/components/questions/types/answer-the-question-group/transform.js +8 -2
- package/dist/components/questions/types/choose-the-correct-answer/ChooseTheCorrectAnswerClient.js +1 -2
- package/dist/components/questions/types/choose-then-answer-group/ChooseThenAnswerGroupClient.d.ts +2 -0
- package/dist/components/questions/types/choose-then-answer-group/ChooseThenAnswerGroupClient.js +34 -0
- package/dist/components/questions/types/choose-then-answer-group/ChooseThenAnswerGroupCreator.d.ts +2 -0
- package/dist/components/questions/types/choose-then-answer-group/ChooseThenAnswerGroupCreator.js +174 -0
- package/dist/components/questions/types/choose-then-answer-group/index.d.ts +5 -0
- package/dist/components/questions/types/choose-then-answer-group/index.js +5 -0
- package/dist/components/questions/types/choose-then-answer-group/map-choose-then-answer-group-data.d.ts +11 -0
- package/dist/components/questions/types/choose-then-answer-group/map-choose-then-answer-group-data.js +127 -0
- package/dist/components/questions/types/choose-then-answer-group/register.d.ts +3 -0
- package/dist/components/questions/types/choose-then-answer-group/register.js +37 -0
- package/dist/components/questions/types/choose-then-answer-group/transform.d.ts +2 -0
- package/dist/components/questions/types/choose-then-answer-group/transform.js +37 -0
- package/dist/components/questions/types/match-word-to-picture-group/MatchWordToPictureGroupClient.js +39 -10
- package/dist/components/questions/types/match-word-to-picture-group/MatchWordToPictureGroupCreator.js +15 -8
- package/dist/components/questions/types/match-word-to-picture-group/map-match-word-to-picture-group-data.js +8 -3
- package/dist/components/questions/types/match-word-to-picture-group/transform.js +21 -13
- package/dist/components/questions/types/sort-words-into-categories/SortWordsIntoCategoriesBoard.d.ts +12 -0
- package/dist/components/questions/types/sort-words-into-categories/SortWordsIntoCategoriesBoard.js +74 -0
- package/dist/components/questions/types/sort-words-into-categories/SortWordsIntoCategoriesClient.d.ts +2 -0
- package/dist/components/questions/types/sort-words-into-categories/SortWordsIntoCategoriesClient.js +24 -0
- package/dist/components/questions/types/sort-words-into-categories/SortWordsIntoCategoriesCreator.d.ts +2 -0
- package/dist/components/questions/types/sort-words-into-categories/SortWordsIntoCategoriesCreator.js +171 -0
- package/dist/components/questions/types/sort-words-into-categories/index.d.ts +4 -0
- package/dist/components/questions/types/sort-words-into-categories/index.js +4 -0
- package/dist/components/questions/types/sort-words-into-categories/map-sort-words-into-categories-data.d.ts +11 -0
- package/dist/components/questions/types/sort-words-into-categories/map-sort-words-into-categories-data.js +153 -0
- package/dist/components/questions/types/sort-words-into-categories/register.d.ts +3 -0
- package/dist/components/questions/types/sort-words-into-categories/register.js +37 -0
- package/dist/components/questions/types/sort-words-into-categories/transform.d.ts +2 -0
- package/dist/components/questions/types/sort-words-into-categories/transform.js +33 -0
- package/dist/components/questions/viewer/AnswerTheQuestionViewer.d.ts +9 -0
- package/dist/components/questions/viewer/AnswerTheQuestionViewer.js +8 -0
- package/dist/components/questions/viewer/ChooseThenAnswerGroupViewer.d.ts +10 -0
- package/dist/components/questions/viewer/ChooseThenAnswerGroupViewer.js +39 -0
- package/dist/components/questions/viewer/QuestionViewer.js +13 -2
- package/dist/components/questions/viewer/SortWordsIntoCategoriesViewer.d.ts +10 -0
- package/dist/components/questions/viewer/SortWordsIntoCategoriesViewer.js +27 -0
- package/dist/components/questions/viewer/index.d.ts +3 -0
- package/dist/components/questions/viewer/index.js +3 -0
- package/dist/components/results/renderers/ReviewAnswerTheQuestionGroupRenderer.d.ts +2 -1
- package/dist/components/results/renderers/ReviewAnswerTheQuestionGroupRenderer.js +12 -3
- package/dist/components/results/renderers/review-question-body-dedicated.js +1 -1
- package/dist/components/results/renderers/review-question-body-movers.js +24 -2
- package/dist/components/results/review-data.js +28 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/shared/constants/question-skills.js +4 -0
- package/dist/shared/lib/utils/question-reverse-transform.js +98 -2
- package/dist/shared/lib/utils/question-transform-types.d.ts +1 -1
- package/dist/shared/lib/utils/question-transform.js +4 -0
- package/dist/shared/types/common.types.d.ts +1 -1
- package/dist/shared/types/questions/answer-the-question-group.d.ts +3 -0
- package/dist/shared/types/questions/choose-then-answer-group.d.ts +46 -0
- package/dist/shared/types/questions/choose-then-answer-group.js +1 -0
- package/dist/shared/types/questions/index.d.ts +2 -0
- package/dist/shared/types/questions/index.js +3 -0
- package/dist/shared/types/questions/match-word-to-picture-group.d.ts +6 -2
- package/dist/shared/types/questions/sort-words-into-categories.d.ts +37 -0
- package/dist/shared/types/questions/sort-words-into-categories.js +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { SORT_WORDS_INTO_CATEGORIES_DEFAULT_INSTRUCTION } from '../../_shared/types/sort-words-into-categories.type';
|
|
2
|
+
function isRecord(value) {
|
|
3
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
4
|
+
}
|
|
5
|
+
function asRecord(value) {
|
|
6
|
+
return isRecord(value) ? value : {};
|
|
7
|
+
}
|
|
8
|
+
function asString(value, fallback = '') {
|
|
9
|
+
return typeof value === 'string' ? value : fallback;
|
|
10
|
+
}
|
|
11
|
+
export function nextPrefixedId(prefix, existing) {
|
|
12
|
+
let n = existing.length + 1;
|
|
13
|
+
const used = new Set(existing);
|
|
14
|
+
while (used.has(`${prefix}${n}`))
|
|
15
|
+
n += 1;
|
|
16
|
+
return `${prefix}${n}`;
|
|
17
|
+
}
|
|
18
|
+
export function sumGradablePoints(words) {
|
|
19
|
+
return words.reduce((total, word) => {
|
|
20
|
+
if (word.isExample)
|
|
21
|
+
return total;
|
|
22
|
+
return total + (typeof word.points === 'number' ? word.points : 1);
|
|
23
|
+
}, 0);
|
|
24
|
+
}
|
|
25
|
+
export function buildPlacements(words) {
|
|
26
|
+
return words.map((word) => ({
|
|
27
|
+
wordId: word.id,
|
|
28
|
+
categoryId: word.categoryId || '',
|
|
29
|
+
}));
|
|
30
|
+
}
|
|
31
|
+
export function readPlacements(value) {
|
|
32
|
+
const source = Array.isArray(value)
|
|
33
|
+
? value
|
|
34
|
+
: isRecord(value) && Array.isArray(value.placements)
|
|
35
|
+
? value.placements
|
|
36
|
+
: null;
|
|
37
|
+
if (!source)
|
|
38
|
+
return undefined;
|
|
39
|
+
return source.map((item) => {
|
|
40
|
+
const record = asRecord(item);
|
|
41
|
+
return {
|
|
42
|
+
wordId: asString(record.wordId),
|
|
43
|
+
categoryId: asString(record.categoryId),
|
|
44
|
+
};
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
export function padPlacements(words, raw) {
|
|
48
|
+
const source = readPlacements(raw) ?? [];
|
|
49
|
+
const byWord = new Map();
|
|
50
|
+
source.forEach((item) => {
|
|
51
|
+
const record = asRecord(item);
|
|
52
|
+
const wordId = asString(record.wordId);
|
|
53
|
+
if (!wordId || byWord.has(wordId))
|
|
54
|
+
return;
|
|
55
|
+
byWord.set(wordId, asString(record.categoryId));
|
|
56
|
+
});
|
|
57
|
+
return words.map((word) => ({
|
|
58
|
+
wordId: word.id,
|
|
59
|
+
categoryId: byWord.get(word.id) ?? '',
|
|
60
|
+
}));
|
|
61
|
+
}
|
|
62
|
+
export function createEmptyCategory(existingIds) {
|
|
63
|
+
return {
|
|
64
|
+
id: nextPrefixedId('cat_', existingIds),
|
|
65
|
+
name: '',
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
export function createEmptyWord(existingIds, categoryId) {
|
|
69
|
+
return {
|
|
70
|
+
id: nextPrefixedId('w', existingIds),
|
|
71
|
+
text: '',
|
|
72
|
+
categoryId,
|
|
73
|
+
isExample: false,
|
|
74
|
+
points: 1,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
function mapCategories(raw) {
|
|
78
|
+
if (!Array.isArray(raw))
|
|
79
|
+
return [];
|
|
80
|
+
return raw.map((item, index) => {
|
|
81
|
+
const record = asRecord(item);
|
|
82
|
+
return {
|
|
83
|
+
id: asString(record.id, `cat_${index + 1}`),
|
|
84
|
+
name: asString(record.name),
|
|
85
|
+
};
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
function mapWords(rawWords, placements) {
|
|
89
|
+
if (!Array.isArray(rawWords))
|
|
90
|
+
return [];
|
|
91
|
+
const placementMap = new Map(placements.map((item) => [item.wordId, item.categoryId]));
|
|
92
|
+
return rawWords.map((item, index) => {
|
|
93
|
+
const record = asRecord(item);
|
|
94
|
+
const id = asString(record.id, `w${index + 1}`);
|
|
95
|
+
const isExample = record.isExample === true;
|
|
96
|
+
return {
|
|
97
|
+
id,
|
|
98
|
+
text: asString(record.text),
|
|
99
|
+
categoryId: asString(record.categoryId) || placementMap.get(id) || '',
|
|
100
|
+
isExample,
|
|
101
|
+
points: isExample ? 0 : (typeof record.points === 'number' ? record.points : 1),
|
|
102
|
+
};
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
export function mapQuestionToSortWordsIntoCategoriesData(question) {
|
|
106
|
+
const source = asRecord(question);
|
|
107
|
+
const content = asRecord(source.content);
|
|
108
|
+
const answer = asRecord(source.answer);
|
|
109
|
+
const correctAnswer = asRecord(source.correctAnswer ?? source.correct_answer ?? answer.correctAnswer);
|
|
110
|
+
const explanation = asString(source.explanation) || asString(answer.explanation);
|
|
111
|
+
const meta = asRecord(content.meta ?? answer.meta);
|
|
112
|
+
const placements = padPlacements(Array.isArray(content.words)
|
|
113
|
+
? content.words.map((item, index) => ({ id: asString(asRecord(item).id, `w${index + 1}`) }))
|
|
114
|
+
: Array.isArray(answer.words)
|
|
115
|
+
? answer.words.map((item, index) => ({ id: asString(asRecord(item).id, `w${index + 1}`) }))
|
|
116
|
+
: [], correctAnswer.placements ?? answer.placements);
|
|
117
|
+
if (Array.isArray(content.words) || Array.isArray(content.categories)) {
|
|
118
|
+
const words = mapWords(content.words, placements);
|
|
119
|
+
return {
|
|
120
|
+
title: asString(meta.title) || asString(answer.title),
|
|
121
|
+
instruction: asString(meta.instruction) ||
|
|
122
|
+
asString(answer.instruction) ||
|
|
123
|
+
SORT_WORDS_INTO_CATEGORIES_DEFAULT_INSTRUCTION,
|
|
124
|
+
categories: mapCategories(content.categories),
|
|
125
|
+
words,
|
|
126
|
+
explanation,
|
|
127
|
+
points: sumGradablePoints(words),
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
if (Array.isArray(answer.words) || Array.isArray(answer.categories)) {
|
|
131
|
+
const words = mapWords(answer.words, placements);
|
|
132
|
+
return {
|
|
133
|
+
title: asString(answer.title),
|
|
134
|
+
instruction: asString(answer.instruction) || SORT_WORDS_INTO_CATEGORIES_DEFAULT_INSTRUCTION,
|
|
135
|
+
categories: mapCategories(answer.categories),
|
|
136
|
+
words,
|
|
137
|
+
explanation,
|
|
138
|
+
points: typeof answer.points === 'number' ? answer.points : sumGradablePoints(words),
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
const firstCategory = createEmptyCategory([]);
|
|
142
|
+
const secondCategory = createEmptyCategory([firstCategory.id]);
|
|
143
|
+
const firstWord = createEmptyWord([], firstCategory.id);
|
|
144
|
+
const secondWord = createEmptyWord([firstWord.id], secondCategory.id);
|
|
145
|
+
return {
|
|
146
|
+
title: '',
|
|
147
|
+
instruction: SORT_WORDS_INTO_CATEGORIES_DEFAULT_INSTRUCTION,
|
|
148
|
+
categories: [firstCategory, secondCategory],
|
|
149
|
+
words: [firstWord, secondWord],
|
|
150
|
+
explanation,
|
|
151
|
+
points: 2,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { QuestionTypeRegistration } from '../../creator/question-type-registry';
|
|
2
|
+
export { buildPlacements, createEmptyCategory, createEmptyWord, mapQuestionToSortWordsIntoCategoriesData, padPlacements, readPlacements, sumGradablePoints, } from './map-sort-words-into-categories-data';
|
|
3
|
+
export declare const sortWordsIntoCategoriesRegistration: QuestionTypeRegistration;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { SortWordsIntoCategoriesCreator } from './SortWordsIntoCategoriesCreator';
|
|
2
|
+
import { mapQuestionToSortWordsIntoCategoriesData } from './map-sort-words-into-categories-data';
|
|
3
|
+
export { buildPlacements, createEmptyCategory, createEmptyWord, mapQuestionToSortWordsIntoCategoriesData, padPlacements, readPlacements, sumGradablePoints, } from './map-sort-words-into-categories-data';
|
|
4
|
+
export const sortWordsIntoCategoriesRegistration = {
|
|
5
|
+
component: SortWordsIntoCategoriesCreator,
|
|
6
|
+
transformInitialData: (initialData) => mapQuestionToSortWordsIntoCategoriesData(initialData),
|
|
7
|
+
getExtraProps: (ctx) => ({
|
|
8
|
+
onUnsavedChangesChange: ctx.onUnsavedChangesChange,
|
|
9
|
+
validationRef: ctx.validationRef,
|
|
10
|
+
}),
|
|
11
|
+
wrapOnSave: (data, ctx) => {
|
|
12
|
+
const payload = data;
|
|
13
|
+
return {
|
|
14
|
+
type: ctx.questionType,
|
|
15
|
+
content: ctx.state.content,
|
|
16
|
+
points: payload.points ?? ctx.state.points ?? 1,
|
|
17
|
+
level: ctx.state.level,
|
|
18
|
+
difficulty: ctx.state.difficulty,
|
|
19
|
+
skill: ctx.state.skill,
|
|
20
|
+
answer: payload,
|
|
21
|
+
explanation: payload.explanation || '',
|
|
22
|
+
};
|
|
23
|
+
},
|
|
24
|
+
wrapOnChange: (data, ctx) => {
|
|
25
|
+
const payload = data;
|
|
26
|
+
return {
|
|
27
|
+
type: ctx.questionType,
|
|
28
|
+
content: ctx.state.content,
|
|
29
|
+
points: payload.points ?? ctx.state.points ?? 1,
|
|
30
|
+
level: ctx.state.level,
|
|
31
|
+
difficulty: ctx.state.difficulty,
|
|
32
|
+
skill: ctx.state.skill,
|
|
33
|
+
answer: payload,
|
|
34
|
+
explanation: payload.explanation || '',
|
|
35
|
+
};
|
|
36
|
+
},
|
|
37
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { SORT_WORDS_INTO_CATEGORIES_DEFAULT_INSTRUCTION } from '../../_shared/types/sort-words-into-categories.type';
|
|
2
|
+
import { sumGradablePoints } from './map-sort-words-into-categories-data';
|
|
3
|
+
export const transformSortWordsIntoCategories = (question) => {
|
|
4
|
+
const answerData = question.answer;
|
|
5
|
+
const categories = Array.isArray(answerData?.categories) ? answerData.categories : [];
|
|
6
|
+
const words = Array.isArray(answerData?.words) ? answerData.words : [];
|
|
7
|
+
const title = typeof answerData?.title === 'string' ? answerData.title.trim() : '';
|
|
8
|
+
return {
|
|
9
|
+
apiContent: {
|
|
10
|
+
meta: {
|
|
11
|
+
instruction: answerData?.instruction || SORT_WORDS_INTO_CATEGORIES_DEFAULT_INSTRUCTION,
|
|
12
|
+
...(title ? { title } : {}),
|
|
13
|
+
},
|
|
14
|
+
categories: categories.map((item) => ({
|
|
15
|
+
id: item.id || '',
|
|
16
|
+
name: item.name || '',
|
|
17
|
+
})),
|
|
18
|
+
words: words.map((item) => ({
|
|
19
|
+
id: item.id || '',
|
|
20
|
+
text: item.text || '',
|
|
21
|
+
points: item.isExample ? 0 : (typeof item.points === 'number' ? item.points : 1),
|
|
22
|
+
...(item.isExample ? { isExample: true } : {}),
|
|
23
|
+
})),
|
|
24
|
+
},
|
|
25
|
+
apiCorrectAnswer: {
|
|
26
|
+
placements: words.map((item) => ({
|
|
27
|
+
wordId: item.id || '',
|
|
28
|
+
categoryId: item.categoryId || '',
|
|
29
|
+
})),
|
|
30
|
+
},
|
|
31
|
+
totalPoints: sumGradablePoints(words),
|
|
32
|
+
};
|
|
33
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Question } from '../../../shared/types/entities/question.types';
|
|
2
|
+
type AnswerTheQuestionViewerProps = {
|
|
3
|
+
question: Question;
|
|
4
|
+
isReviewMode?: boolean;
|
|
5
|
+
userAnswer?: string;
|
|
6
|
+
onSubmitAnswer?: (answer: string) => void;
|
|
7
|
+
};
|
|
8
|
+
export declare function AnswerTheQuestionViewer({ question, isReviewMode, userAnswer, onSubmitAnswer, }: AnswerTheQuestionViewerProps): import("react").JSX.Element;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { AnswerTheQuestionClient } from '../types/answer-the-question/AnswerTheQuestionClient';
|
|
4
|
+
import { mapQuestionToAnswerTheQuestionData } from '../types/answer-the-question/map-answer-the-question-data';
|
|
5
|
+
export function AnswerTheQuestionViewer({ question, isReviewMode = false, userAnswer, onSubmitAnswer, }) {
|
|
6
|
+
const questionData = mapQuestionToAnswerTheQuestionData(question);
|
|
7
|
+
return (_jsx(AnswerTheQuestionClient, { questionData: questionData, isReviewMode: isReviewMode, userAnswer: userAnswer, autoFillCorrectAnswers: isReviewMode && !userAnswer, explanation: question.explanation || questionData.explanation, onSubmit: isReviewMode ? undefined : onSubmitAnswer }));
|
|
8
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Question } from '../../../shared/types/entities/question.types';
|
|
2
|
+
import type { ChooseThenAnswerStudentItemAnswer } from '../_shared/types/choose-then-answer-group.type';
|
|
3
|
+
export declare function ChooseThenAnswerGroupViewer({ question, isReviewMode, userAnswer, onSubmitAnswer, }: {
|
|
4
|
+
question: Question;
|
|
5
|
+
isReviewMode?: boolean;
|
|
6
|
+
userAnswer?: unknown;
|
|
7
|
+
onSubmitAnswer?: (answer: {
|
|
8
|
+
answer: ChooseThenAnswerStudentItemAnswer[];
|
|
9
|
+
}) => void;
|
|
10
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
import { ChooseThenAnswerGroupClient } from '../types/choose-then-answer-group/ChooseThenAnswerGroupClient';
|
|
5
|
+
import { mapQuestionToChooseThenAnswerGroupData, parseStudentAnswer, } from '../types/choose-then-answer-group/map-choose-then-answer-group-data';
|
|
6
|
+
function readStudentAnswers(value, itemCount) {
|
|
7
|
+
const record = value && typeof value === 'object' && !Array.isArray(value)
|
|
8
|
+
? value
|
|
9
|
+
: null;
|
|
10
|
+
const raw = Array.isArray(record?.answer)
|
|
11
|
+
? record.answer
|
|
12
|
+
: Array.isArray(value)
|
|
13
|
+
? value
|
|
14
|
+
: null;
|
|
15
|
+
if (!raw)
|
|
16
|
+
return undefined;
|
|
17
|
+
return Array.from({ length: itemCount }, (_, index) => parseStudentAnswer(raw[index]));
|
|
18
|
+
}
|
|
19
|
+
export function ChooseThenAnswerGroupViewer({ question, isReviewMode = false, userAnswer, onSubmitAnswer, }) {
|
|
20
|
+
const questionData = mapQuestionToChooseThenAnswerGroupData(question);
|
|
21
|
+
const seed = questionData.items.map((item) => ({
|
|
22
|
+
optionId: item.isExample ? item.optionId : '',
|
|
23
|
+
text: '',
|
|
24
|
+
}));
|
|
25
|
+
const parsedUser = readStudentAnswers(userAnswer, questionData.items.length);
|
|
26
|
+
const questionKey = `${question.id}:${question.type}:${questionData.items.length}`;
|
|
27
|
+
const [cachedKey, setCachedKey] = useState(questionKey);
|
|
28
|
+
const [localAnswers, setLocalAnswers] = useState(() => parsedUser ?? seed);
|
|
29
|
+
if (cachedKey !== questionKey) {
|
|
30
|
+
setCachedKey(questionKey);
|
|
31
|
+
setLocalAnswers(parsedUser ?? seed);
|
|
32
|
+
}
|
|
33
|
+
const handleChange = (index, next) => {
|
|
34
|
+
const updated = localAnswers.map((item, itemIndex) => itemIndex === index ? next : item);
|
|
35
|
+
setLocalAnswers(updated);
|
|
36
|
+
onSubmitAnswer?.({ answer: updated });
|
|
37
|
+
};
|
|
38
|
+
return (_jsx(ChooseThenAnswerGroupClient, { questionData: questionData, isReviewMode: isReviewMode, userAnswers: isReviewMode && !parsedUser ? undefined : (parsedUser ?? localAnswers), onAnswerChange: isReviewMode ? undefined : handleChange }));
|
|
39
|
+
}
|
|
@@ -21,6 +21,8 @@ import { FillMissingWordsInGridClient } from '../types/fill-missing-words-in-gri
|
|
|
21
21
|
import { WordFillStructuredFormClient } from '../types/word-fill-structured-form/WordFillStructuredFormClient';
|
|
22
22
|
import { CrosswordPuzzleClient } from '../types/crossword-puzzle/CrosswordPuzzleClient';
|
|
23
23
|
import { FindWordsInMatrixClient } from '../types/find-words-in-matrix/FindWordsInMatrixClient';
|
|
24
|
+
import { SortWordsIntoCategoriesViewer } from './SortWordsIntoCategoriesViewer';
|
|
25
|
+
import { ChooseThenAnswerGroupViewer } from './ChooseThenAnswerGroupViewer';
|
|
24
26
|
import { parseFillBlankStemLine } from '../_shared/utils/parseFillBlankStem';
|
|
25
27
|
import { WordFillStructuredFormViewer } from './WordFillStructuredFormViewer';
|
|
26
28
|
import { ChooseTheCorrectAnswerGroupViewer } from './ChooseTheCorrectAnswerGroupViewer';
|
|
@@ -31,6 +33,7 @@ import { MatchingWithLinesGroupViewer } from './MatchingWithLinesGroupViewer';
|
|
|
31
33
|
import { CrossOutWordGroupViewer } from './CrossOutWordGroupViewer';
|
|
32
34
|
import { SpontaneousQaGroupViewer } from './SpontaneousQaGroupViewer';
|
|
33
35
|
import { WordOrderAndMatchGroupViewer } from './WordOrderAndMatchGroupViewer';
|
|
36
|
+
import { AnswerTheQuestionViewer } from './AnswerTheQuestionViewer';
|
|
34
37
|
import { AnswerTheQuestionGroupViewer } from './AnswerTheQuestionGroupViewer';
|
|
35
38
|
import { MatchWordToPictureViewer } from './MatchWordToPictureViewer';
|
|
36
39
|
import { MatchWordToPictureGroupViewer } from './MatchWordToPictureGroupViewer';
|
|
@@ -647,6 +650,8 @@ export function QuestionViewer({ question, onSubmitAnswer, isReviewMode = false,
|
|
|
647
650
|
return (_jsx(MatchColumnsToMakeSentencesViewer, { question: question, isReviewMode: isReviewMode, userAnswer: userAnswer, onSubmitAnswer: onSubmitAnswer }));
|
|
648
651
|
case 'CROSS_OUT_WORD_GROUP':
|
|
649
652
|
return (_jsx(CrossOutWordGroupViewer, { question: question, isReviewMode: isReviewMode }));
|
|
653
|
+
case 'ANSWER_THE_QUESTION':
|
|
654
|
+
return (_jsx(AnswerTheQuestionViewer, { question: question, isReviewMode: isReviewMode, userAnswer: typeof userAnswer === 'string' ? userAnswer : undefined, onSubmitAnswer: onSubmitAnswer }));
|
|
650
655
|
case 'ANSWER_THE_QUESTION_GROUP':
|
|
651
656
|
return (_jsx(AnswerTheQuestionGroupViewer, { question: question, isReviewMode: isReviewMode, userAnswer: userAnswer }));
|
|
652
657
|
case 'SPONTANEOUS_QA_GROUP':
|
|
@@ -703,6 +708,10 @@ export function QuestionViewer({ question, onSubmitAnswer, isReviewMode = false,
|
|
|
703
708
|
return (_jsx(CrosswordPuzzleClient, { content: question.content, correctAnswer: question.answer, isReviewMode: isReviewMode, userAnswers: userAnswer }));
|
|
704
709
|
case 'FIND_WORDS_IN_MATRIX':
|
|
705
710
|
return (_jsx(FindWordsInMatrixClient, { content: question.content, correctAnswer: question.answer, showSolution: isReviewMode }));
|
|
711
|
+
case 'SORT_WORDS_INTO_CATEGORIES':
|
|
712
|
+
return (_jsx(SortWordsIntoCategoriesViewer, { question: question, isReviewMode: isReviewMode, userAnswer: userAnswer, onSubmitAnswer: onSubmitAnswer }));
|
|
713
|
+
case 'CHOOSE_THEN_ANSWER_GROUP':
|
|
714
|
+
return (_jsx(ChooseThenAnswerGroupViewer, { question: question, isReviewMode: isReviewMode, userAnswer: userAnswer, onSubmitAnswer: onSubmitAnswer }));
|
|
706
715
|
default:
|
|
707
716
|
return _jsxs("div", { children: ["Lo\u1EA1i c\u00E2u h\u1ECFi kh\u00F4ng \u0111\u01B0\u1EE3c h\u1ED7 tr\u1EE3: ", question.type] });
|
|
708
717
|
}
|
|
@@ -716,7 +725,8 @@ export function QuestionViewer({ question, onSubmitAnswer, isReviewMode = false,
|
|
|
716
725
|
// WFSF and Crossword show title/instruction inside the client — no outer card label.
|
|
717
726
|
if (question.type === 'WORD_FILL_STRUCTURED_FORM' ||
|
|
718
727
|
question.type === 'CROSSWORD_PUZZLE' ||
|
|
719
|
-
question.type === 'MATCH_WORD_TO_PICTURE'
|
|
728
|
+
question.type === 'MATCH_WORD_TO_PICTURE' ||
|
|
729
|
+
question.type === 'ANSWER_THE_QUESTION') {
|
|
720
730
|
return null;
|
|
721
731
|
}
|
|
722
732
|
// Modern fill stems live entirely in the body; title is a short label.
|
|
@@ -741,7 +751,8 @@ export function QuestionViewer({ question, onSubmitAnswer, isReviewMode = false,
|
|
|
741
751
|
// WFSF and Crossword already have their own bordered shell — avoid double Card border.
|
|
742
752
|
if (question.type === 'WORD_FILL_STRUCTURED_FORM' ||
|
|
743
753
|
question.type === 'CROSSWORD_PUZZLE' ||
|
|
744
|
-
question.type === 'MATCH_WORD_TO_PICTURE'
|
|
754
|
+
question.type === 'MATCH_WORD_TO_PICTURE' ||
|
|
755
|
+
question.type === 'ANSWER_THE_QUESTION') {
|
|
745
756
|
return (_jsx("div", { className: "space-y-4", children: renderQuestion() }));
|
|
746
757
|
}
|
|
747
758
|
return (_jsxs(Card, { children: [cardTitle ? (_jsx(CardHeader, { children: _jsxs(CardTitle, { className: "flex items-center justify-between", children: [_jsx("span", { className: "min-w-0 pr-2 text-base font-semibold leading-snug", children: cardTitle }), _jsxs("span", { className: "shrink-0 text-sm font-normal text-gray-500", children: [question.points, " \u0111i\u1EC3m"] })] }) })) : null, _jsx(CardContent, { className: cardTitle ? 'space-y-4' : 'space-y-4 pt-4', children: renderQuestion() })] }));
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Question } from '../../../shared/types/entities/question.types';
|
|
2
|
+
import type { SortWordsPlacement } from '../_shared/types/sort-words-into-categories.type';
|
|
3
|
+
export declare function SortWordsIntoCategoriesViewer({ question, isReviewMode, userAnswer, onSubmitAnswer, }: {
|
|
4
|
+
question: Question;
|
|
5
|
+
isReviewMode?: boolean;
|
|
6
|
+
userAnswer?: unknown;
|
|
7
|
+
onSubmitAnswer?: (answer: {
|
|
8
|
+
placements: SortWordsPlacement[];
|
|
9
|
+
}) => void;
|
|
10
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
import { SortWordsIntoCategoriesClient } from '../types/sort-words-into-categories/SortWordsIntoCategoriesClient';
|
|
5
|
+
import { mapQuestionToSortWordsIntoCategoriesData, readPlacements, } from '../types/sort-words-into-categories/map-sort-words-into-categories-data';
|
|
6
|
+
export function SortWordsIntoCategoriesViewer({ question, isReviewMode = false, userAnswer, onSubmitAnswer, }) {
|
|
7
|
+
const questionData = mapQuestionToSortWordsIntoCategoriesData(question);
|
|
8
|
+
const parsedUser = readPlacements(userAnswer);
|
|
9
|
+
const questionKey = `${question.id}:${question.type}:${questionData.words.map((word) => word.id).join(',')}`;
|
|
10
|
+
const [cachedKey, setCachedKey] = useState(questionKey);
|
|
11
|
+
const [localPlacements, setLocalPlacements] = useState(() => parsedUser ?? questionData.words.map((word) => ({
|
|
12
|
+
wordId: word.id,
|
|
13
|
+
categoryId: word.isExample ? word.categoryId : '',
|
|
14
|
+
})));
|
|
15
|
+
if (cachedKey !== questionKey) {
|
|
16
|
+
setCachedKey(questionKey);
|
|
17
|
+
setLocalPlacements(parsedUser ?? questionData.words.map((word) => ({
|
|
18
|
+
wordId: word.id,
|
|
19
|
+
categoryId: word.isExample ? word.categoryId : '',
|
|
20
|
+
})));
|
|
21
|
+
}
|
|
22
|
+
const handleChange = (next) => {
|
|
23
|
+
setLocalPlacements(next);
|
|
24
|
+
onSubmitAnswer?.({ placements: next });
|
|
25
|
+
};
|
|
26
|
+
return (_jsx(SortWordsIntoCategoriesClient, { questionData: questionData, isReviewMode: isReviewMode, userPlacements: isReviewMode && !parsedUser ? undefined : (parsedUser ?? localPlacements), onPlacementsChange: isReviewMode ? undefined : handleChange }));
|
|
27
|
+
}
|
|
@@ -9,8 +9,11 @@ export { MatchingWithLinesGroupViewer } from './MatchingWithLinesGroupViewer';
|
|
|
9
9
|
export { CrossOutWordGroupViewer } from './CrossOutWordGroupViewer';
|
|
10
10
|
export { SpontaneousQaGroupViewer } from './SpontaneousQaGroupViewer';
|
|
11
11
|
export { WordOrderAndMatchGroupViewer } from './WordOrderAndMatchGroupViewer';
|
|
12
|
+
export { AnswerTheQuestionViewer } from './AnswerTheQuestionViewer';
|
|
12
13
|
export { AnswerTheQuestionGroupViewer } from './AnswerTheQuestionGroupViewer';
|
|
13
14
|
export { MatchWordToPictureViewer } from './MatchWordToPictureViewer';
|
|
14
15
|
export { MatchWordToPictureGroupViewer } from './MatchWordToPictureGroupViewer';
|
|
15
16
|
export { MatchColumnsToMakeSentencesViewer } from './MatchColumnsToMakeSentencesViewer';
|
|
16
17
|
export { WordOrderingGroupViewer } from './WordOrderingGroupViewer';
|
|
18
|
+
export { SortWordsIntoCategoriesViewer } from './SortWordsIntoCategoriesViewer';
|
|
19
|
+
export { ChooseThenAnswerGroupViewer } from './ChooseThenAnswerGroupViewer';
|
|
@@ -9,8 +9,11 @@ export { MatchingWithLinesGroupViewer } from './MatchingWithLinesGroupViewer';
|
|
|
9
9
|
export { CrossOutWordGroupViewer } from './CrossOutWordGroupViewer';
|
|
10
10
|
export { SpontaneousQaGroupViewer } from './SpontaneousQaGroupViewer';
|
|
11
11
|
export { WordOrderAndMatchGroupViewer } from './WordOrderAndMatchGroupViewer';
|
|
12
|
+
export { AnswerTheQuestionViewer } from './AnswerTheQuestionViewer';
|
|
12
13
|
export { AnswerTheQuestionGroupViewer } from './AnswerTheQuestionGroupViewer';
|
|
13
14
|
export { MatchWordToPictureViewer } from './MatchWordToPictureViewer';
|
|
14
15
|
export { MatchWordToPictureGroupViewer } from './MatchWordToPictureGroupViewer';
|
|
15
16
|
export { MatchColumnsToMakeSentencesViewer } from './MatchColumnsToMakeSentencesViewer';
|
|
16
17
|
export { WordOrderingGroupViewer } from './WordOrderingGroupViewer';
|
|
18
|
+
export { SortWordsIntoCategoriesViewer } from './SortWordsIntoCategoriesViewer';
|
|
19
|
+
export { ChooseThenAnswerGroupViewer } from './ChooseThenAnswerGroupViewer';
|
|
@@ -11,9 +11,10 @@ interface ReviewAnswerTheQuestionGroupRendererProps {
|
|
|
11
11
|
questionCount: number;
|
|
12
12
|
instruction: string;
|
|
13
13
|
groupContent?: string;
|
|
14
|
+
imageUrl?: string | string[];
|
|
14
15
|
questions: AnswerTheQuestionQuestion[];
|
|
15
16
|
answers: Record<string, string>;
|
|
16
17
|
itemMeta: Record<string, ReviewAnswerTheQuestionGroupItemMeta>;
|
|
17
18
|
}
|
|
18
|
-
export declare function ReviewAnswerTheQuestionGroupRenderer({ partNumber, partName, questionCount, instruction, groupContent, questions, answers, itemMeta, }: ReviewAnswerTheQuestionGroupRendererProps): import("react").JSX.Element;
|
|
19
|
+
export declare function ReviewAnswerTheQuestionGroupRenderer({ partNumber, partName, questionCount, instruction, groupContent, imageUrl, questions, answers, itemMeta, }: ReviewAnswerTheQuestionGroupRendererProps): import("react").JSX.Element;
|
|
19
20
|
export {};
|
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { BookOpen, Check, Sparkles, Star, X } from 'lucide-react';
|
|
4
|
+
import { ResolvedImage } from '../../../components/common/ResolvedImage';
|
|
4
5
|
import CambridgeYlePartBanner from '../../../components/themes/cambridge-yle/CambridgeYlePartBanner';
|
|
5
6
|
import CambridgeYleInstructionBanner from '../../../components/themes/cambridge-yle/CambridgeYleInstructionBanner';
|
|
6
|
-
|
|
7
|
-
|
|
7
|
+
import { toAnswerTheQuestionImageUrls } from '../../questions/_shared/types/answer-the-question.type';
|
|
8
|
+
function ReviewImages({ imageUrl, alt, }) {
|
|
9
|
+
const urls = toAnswerTheQuestionImageUrls(imageUrl);
|
|
10
|
+
if (urls.length === 0) {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
return (_jsx("div", { className: "flex flex-wrap justify-center gap-3", children: urls.map((url, imageIndex) => (_jsx(ResolvedImage, { src: url, alt: `${alt} ${imageIndex + 1}`, className: "max-h-56 w-auto rounded-lg object-contain" }, `${alt}-${imageIndex}`))) }));
|
|
14
|
+
}
|
|
15
|
+
export function ReviewAnswerTheQuestionGroupRenderer({ partNumber, partName, questionCount, instruction, groupContent, imageUrl, questions, answers, itemMeta, }) {
|
|
16
|
+
return (_jsxs("div", { className: "space-y-4", children: [_jsx(CambridgeYlePartBanner, { partNumber: partNumber, partName: partName, questionCount: questionCount }), _jsx(CambridgeYleInstructionBanner, { instruction: instruction }), _jsx(ReviewImages, { imageUrl: imageUrl, alt: "Shared illustration" }), groupContent && (_jsxs("div", { className: "rounded-xl border border-gray-200 bg-white p-5 shadow-xs", children: [_jsxs("div", { className: "mb-2.5 flex items-center gap-2 text-xs font-bold uppercase tracking-wider text-indigo-700", children: [_jsx(BookOpen, { className: "h-4 w-4" }), _jsx("span", { children: "B\u00E0i \u0111\u1ECDc (Reading Passage)" })] }), _jsx("div", { className: "prose prose-sm max-w-none leading-relaxed text-gray-800", dangerouslySetInnerHTML: { __html: groupContent } })] })), questions.map((question) => {
|
|
8
17
|
const meta = itemMeta[question.id];
|
|
9
18
|
const studentAnswer = answers[question.id] || '';
|
|
10
19
|
const expectedAnswers = meta?.expectedAnswers || [];
|
|
@@ -16,7 +25,7 @@ export function ReviewAnswerTheQuestionGroupRenderer({ partNumber, partName, que
|
|
|
16
25
|
? 'bg-green-500'
|
|
17
26
|
: isCorrect === false
|
|
18
27
|
? 'bg-red-500'
|
|
19
|
-
: 'bg-blue-500'}`, children: question.questionNumber }), _jsxs("span", { className: "text-sm font-medium text-gray-700", children: ["C\u00E2u ", question.questionNumber] }), question.isExample && (_jsxs("span", { className: "inline-flex items-center gap-1 rounded-full bg-amber-100 px-2 py-0.5 text-xs font-semibold text-amber-700", children: [_jsx(Star, { className: "h-3 w-3" }), "Example"] })), _jsxs("span", { className: "inline-flex items-center gap-1 rounded-full bg-violet-100 px-2 py-0.5 text-xs font-semibold text-violet-700", children: [_jsx(Sparkles, { className: "h-3 w-3" }), "AI"] })] }), isCorrect === true && _jsx(Check, { className: "h-4 w-4 text-green-600" }), isCorrect === false && _jsx(X, { className: "h-4 w-4 text-red-600" })] }), _jsxs("div", { className: "space-y-3 p-4", children: [_jsx("p", { className: "text-sm font-semibold leading-relaxed text-gray-800", children: question.question }), _jsx("div", { className: `rounded-lg border px-3 py-2 text-sm ${isCorrect === true
|
|
28
|
+
: 'bg-blue-500'}`, children: question.questionNumber }), _jsxs("span", { className: "text-sm font-medium text-gray-700", children: ["C\u00E2u ", question.questionNumber] }), question.isExample && (_jsxs("span", { className: "inline-flex items-center gap-1 rounded-full bg-amber-100 px-2 py-0.5 text-xs font-semibold text-amber-700", children: [_jsx(Star, { className: "h-3 w-3" }), "Example"] })), _jsxs("span", { className: "inline-flex items-center gap-1 rounded-full bg-violet-100 px-2 py-0.5 text-xs font-semibold text-violet-700", children: [_jsx(Sparkles, { className: "h-3 w-3" }), "AI"] })] }), isCorrect === true && _jsx(Check, { className: "h-4 w-4 text-green-600" }), isCorrect === false && _jsx(X, { className: "h-4 w-4 text-red-600" })] }), _jsxs("div", { className: "space-y-3 p-4", children: [_jsx(ReviewImages, { imageUrl: question.imageUrl, alt: `Hình ảnh câu ${question.questionNumber}` }), _jsx("p", { className: "text-sm font-semibold leading-relaxed text-gray-800", children: question.question }), _jsx("div", { className: `rounded-lg border px-3 py-2 text-sm ${isCorrect === true
|
|
20
29
|
? 'border-green-200 bg-green-50 text-green-800'
|
|
21
30
|
: isCorrect === false
|
|
22
31
|
? 'border-red-200 bg-red-50 text-red-800'
|
|
@@ -115,7 +115,7 @@ export function renderDedicatedReviewBody(ctx) {
|
|
|
115
115
|
feedbackVi: typeof feedback.vi === 'string' ? feedback.vi : undefined,
|
|
116
116
|
}];
|
|
117
117
|
}));
|
|
118
|
-
return (_jsx(ReviewAnswerTheQuestionGroupRenderer, { partNumber: partNo, partName: part.name, questionCount: questionCount, instruction: part.instructions || data.instruction, groupContent: data.groupContent, questions: data.questions, answers: answers, itemMeta: itemMeta }));
|
|
118
|
+
return (_jsx(ReviewAnswerTheQuestionGroupRenderer, { partNumber: partNo, partName: part.name, questionCount: questionCount, instruction: part.instructions || data.instruction, groupContent: data.groupContent, imageUrl: data.imageUrl, questions: data.questions, answers: answers, itemMeta: itemMeta }));
|
|
119
119
|
}
|
|
120
120
|
case 'SPONTANEOUS_QA_GROUP': {
|
|
121
121
|
const parent = questions[0];
|
|
@@ -4,8 +4,8 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
4
4
|
* Movers fallback review cases (isReviewMode=true).
|
|
5
5
|
* Source: oe-exam-fe ReviewQuestionRenderer.tsx
|
|
6
6
|
*/
|
|
7
|
-
import { MoversAnswerTheQuestionRenderer, MoversFillWordHintRenderer, MoversGridFillRenderer, MoversLabelThePictureRenderer, MoversMatchByWritingAnswerRenderer, MoversMatchWordToPictureRenderer, MoversPictureChooseRenderer, MoversPictureFillBlankChooseRenderer, MoversReadingPassageRenderer, MoversTrueFalseRenderer, MoversTrueFalseCorrectGroupRenderer, MoversFillInBlankGroupRenderer, MoversMatchingWithLinesGroupRenderer, MoversCrossOutWordGroupRenderer, MoversWordFillParagraphRenderer, MoversWordFillStructuredFormRenderer, MoversWordOrderingRenderer, MoversWordOrderingGroupRenderer, MoversWriteSentencesRenderer, MoversWritingRenderer, MoversCrosswordPuzzleRenderer, } from '../../../components/exams/take/components/question-renderers';
|
|
8
|
-
import { transformAnswerTheQuestion, transformFillWordHint, transformGridFill, transformLabelPicture, transformMatchByWritingAnswer, transformMatchWordToPicture, transformPictureChoose, transformPictureFillBlankChoose, transformReadingPassage, transformTrueFalse, transformTrueFalseCorrectGroup, transformFillInBlankGroup, transformMatchingWithLinesGroup, transformWordFillParagraph, transformWordFillStructuredForm, transformWordOrdering, transformWordOrderingGroupTake, transformWriteSentences, transformWriteShortLetter, transformCrosswordPuzzle, } from '../../../components/exams/take/utils/question-transformers';
|
|
7
|
+
import { MoversAnswerTheQuestionRenderer, MoversFillWordHintRenderer, MoversGridFillRenderer, MoversLabelThePictureRenderer, MoversMatchByWritingAnswerRenderer, MoversMatchWordToPictureRenderer, MoversPictureChooseRenderer, MoversPictureFillBlankChooseRenderer, MoversReadingPassageRenderer, MoversTrueFalseRenderer, MoversTrueFalseCorrectGroupRenderer, MoversFillInBlankGroupRenderer, MoversMatchingWithLinesGroupRenderer, MoversCrossOutWordGroupRenderer, MoversWordFillParagraphRenderer, MoversWordFillStructuredFormRenderer, MoversWordOrderingRenderer, MoversWordOrderingGroupRenderer, MoversWriteSentencesRenderer, MoversWritingRenderer, MoversCrosswordPuzzleRenderer, ChooseThenAnswerGroupRenderer, SortWordsIntoCategoriesRenderer, } from '../../../components/exams/take/components/question-renderers';
|
|
8
|
+
import { transformAnswerTheQuestion, transformFillWordHint, transformGridFill, transformLabelPicture, transformMatchByWritingAnswer, transformMatchWordToPicture, transformPictureChoose, transformPictureFillBlankChoose, transformReadingPassage, transformTrueFalse, transformTrueFalseCorrectGroup, transformFillInBlankGroup, transformMatchingWithLinesGroup, transformWordFillParagraph, transformWordFillStructuredForm, transformWordOrdering, transformWordOrderingGroupTake, transformWriteSentences, transformWriteShortLetter, transformCrosswordPuzzle, transformChooseThenAnswerGroup, transformSortWordsIntoCategories, } from '../../../components/exams/take/utils/question-transformers';
|
|
9
9
|
import { normalizeCrossOutWordGroupAnswer, transformCrossOutWordGroup, } from '../../../components/exams/take/utils/cross-out-word-group.transformer';
|
|
10
10
|
function asApiQuestions(questions) {
|
|
11
11
|
return questions;
|
|
@@ -110,6 +110,28 @@ export function renderMoversReviewBody(ctx) {
|
|
|
110
110
|
});
|
|
111
111
|
return (_jsx(MoversCrossOutWordGroupRenderer, { partNumber: partNo, partName: part.name, instruction: part.instructions || data.instruction, questionCount: questionCount, questions: data.questions, answers: answers, onAnswerChange: handleAnswerChange, isReviewMode: true, correctAnswerMap: correctAnswerMap }));
|
|
112
112
|
}
|
|
113
|
+
case 'CHOOSE_THEN_ANSWER_GROUP': {
|
|
114
|
+
const data = transformChooseThenAnswerGroup(apiQuestions);
|
|
115
|
+
const gradableCount = data.questions.filter((item) => !item.isExample).length;
|
|
116
|
+
return (_jsx(ChooseThenAnswerGroupRenderer, { partNumber: partNo, partName: part.name, questionCount: gradableCount || questionCount, instruction: part.instructions || data.instruction, data: data, answers: answers, onAnswerChange: () => undefined, isReviewMode: true }));
|
|
117
|
+
}
|
|
118
|
+
case 'SORT_WORDS_INTO_CATEGORIES': {
|
|
119
|
+
const data = transformSortWordsIntoCategories(apiQuestions);
|
|
120
|
+
const firstQuestion = apiQuestions[0];
|
|
121
|
+
const stored = firstQuestion?.id ? answers[firstQuestion.id] : undefined;
|
|
122
|
+
const storedRecord = stored && typeof stored === 'object' && !Array.isArray(stored)
|
|
123
|
+
? stored
|
|
124
|
+
: undefined;
|
|
125
|
+
const storedPlacements = Array.isArray(storedRecord?.placements)
|
|
126
|
+
? storedRecord.placements
|
|
127
|
+
: Array.isArray(stored)
|
|
128
|
+
? stored
|
|
129
|
+
: [];
|
|
130
|
+
const gradableCount = data.words.filter((word) => !word.isExample).length;
|
|
131
|
+
return (_jsx(SortWordsIntoCategoriesRenderer, { partNumber: partNo, partName: part.name, questionCount: gradableCount || questionCount, instruction: part.instructions || data.instruction, data: data, answers: {
|
|
132
|
+
[data.questionId]: { placements: storedPlacements },
|
|
133
|
+
}, onAnswerChange: () => undefined, isReviewMode: true }));
|
|
134
|
+
}
|
|
113
135
|
case 'TRUE_FALSE_CORRECT_GROUP': {
|
|
114
136
|
const data = transformTrueFalseCorrectGroup(apiQuestions);
|
|
115
137
|
const correctAnswerMap = {};
|
|
@@ -9,6 +9,7 @@ const FLATTENED_ANSWER_TYPES = new Set([
|
|
|
9
9
|
'ANSWER_THE_QUESTION_GROUP',
|
|
10
10
|
'CROSS_OUT_WORD_GROUP',
|
|
11
11
|
'TRUE_FALSE_CORRECT_GROUP',
|
|
12
|
+
'CHOOSE_THEN_ANSWER_GROUP',
|
|
12
13
|
'FILL_IN_BLANK_GROUP',
|
|
13
14
|
'MATCHING_WITH_LINES_GROUP',
|
|
14
15
|
]);
|
|
@@ -186,6 +187,26 @@ function transformStudentAnswer(questionId, questionType, value) {
|
|
|
186
187
|
normalizeCrossOutWordGroupAnswer(item),
|
|
187
188
|
]));
|
|
188
189
|
}
|
|
190
|
+
if (questionType === 'CHOOSE_THEN_ANSWER_GROUP') {
|
|
191
|
+
const answer = Array.isArray(record.answer)
|
|
192
|
+
? record.answer
|
|
193
|
+
: Array.isArray(value)
|
|
194
|
+
? value
|
|
195
|
+
: [];
|
|
196
|
+
return Object.fromEntries(answer.map((item, index) => {
|
|
197
|
+
if (item && typeof item === 'object' && !Array.isArray(item)) {
|
|
198
|
+
const obj = item;
|
|
199
|
+
return [
|
|
200
|
+
`${questionId}-item-${index}`,
|
|
201
|
+
{
|
|
202
|
+
optionId: typeof obj.optionId === 'string' ? obj.optionId : '',
|
|
203
|
+
text: typeof obj.text === 'string' ? obj.text : '',
|
|
204
|
+
},
|
|
205
|
+
];
|
|
206
|
+
}
|
|
207
|
+
return [`${questionId}-item-${index}`, { optionId: '', text: '' }];
|
|
208
|
+
}));
|
|
209
|
+
}
|
|
189
210
|
if (questionType === 'TRUE_FALSE_CORRECT_GROUP') {
|
|
190
211
|
const answer = Array.isArray(record.answer)
|
|
191
212
|
? record.answer
|
|
@@ -282,6 +303,13 @@ function transformStudentAnswer(questionId, questionType, value) {
|
|
|
282
303
|
}
|
|
283
304
|
if (questionType === 'READ_AND_COLOR_OBJECTS' || questionType === 'IMAGE_OBJECT_MATCHING' || questionType === 'CROSSWORD_PUZZLE')
|
|
284
305
|
return record.cells ?? record.answers ?? value;
|
|
306
|
+
if (questionType === 'SORT_WORDS_INTO_CATEGORIES') {
|
|
307
|
+
if (Array.isArray(record.placements))
|
|
308
|
+
return record;
|
|
309
|
+
if (Array.isArray(value))
|
|
310
|
+
return { placements: value };
|
|
311
|
+
return value;
|
|
312
|
+
}
|
|
285
313
|
if (questionType === 'WRITE_A_SHORT_LETTER')
|
|
286
314
|
return typeof value === 'string' ? value : record.answer ?? '';
|
|
287
315
|
return record.answer ?? value;
|
package/dist/index.d.ts
CHANGED
|
@@ -26,4 +26,4 @@ export { validateQuestion } from './components/questions/_shared/utils/question-
|
|
|
26
26
|
export { buildAnswerPayload } from './components/questions/_shared/utils/answer-builder';
|
|
27
27
|
export type { QuestionFormState } from './components/questions/_shared/hooks/useQuestionFormState';
|
|
28
28
|
export * from './components/questions/_shared/types';
|
|
29
|
-
export { QuestionViewer, QuestionGroupViewer, WordFillStructuredFormViewer, ChooseTheCorrectAnswerGroupViewer, TrueFalseGroupViewer, TrueFalseCorrectGroupViewer, FillInBlankGroupViewer, MatchingWithLinesGroupViewer, CrossOutWordGroupViewer, SpontaneousQaGroupViewer, WordOrderAndMatchGroupViewer, AnswerTheQuestionGroupViewer, MatchWordToPictureViewer, MatchWordToPictureGroupViewer, MatchColumnsToMakeSentencesViewer, WordOrderingGroupViewer, } from './components/questions/viewer';
|
|
29
|
+
export { QuestionViewer, QuestionGroupViewer, WordFillStructuredFormViewer, ChooseTheCorrectAnswerGroupViewer, TrueFalseGroupViewer, TrueFalseCorrectGroupViewer, FillInBlankGroupViewer, MatchingWithLinesGroupViewer, CrossOutWordGroupViewer, SpontaneousQaGroupViewer, WordOrderAndMatchGroupViewer, AnswerTheQuestionGroupViewer, MatchWordToPictureViewer, MatchWordToPictureGroupViewer, MatchColumnsToMakeSentencesViewer, WordOrderingGroupViewer, SortWordsIntoCategoriesViewer, ChooseThenAnswerGroupViewer, } from './components/questions/viewer';
|
package/dist/index.js
CHANGED
|
@@ -21,4 +21,4 @@ export { QUESTION_TYPE_CONFIG, QUESTION_TYPES_WITH_DEDICATED_COMPONENTS } from '
|
|
|
21
21
|
export { validateQuestion } from './components/questions/_shared/utils/question-validation';
|
|
22
22
|
export { buildAnswerPayload } from './components/questions/_shared/utils/answer-builder';
|
|
23
23
|
export * from './components/questions/_shared/types';
|
|
24
|
-
export { QuestionViewer, QuestionGroupViewer, WordFillStructuredFormViewer, ChooseTheCorrectAnswerGroupViewer, TrueFalseGroupViewer, TrueFalseCorrectGroupViewer, FillInBlankGroupViewer, MatchingWithLinesGroupViewer, CrossOutWordGroupViewer, SpontaneousQaGroupViewer, WordOrderAndMatchGroupViewer, AnswerTheQuestionGroupViewer, MatchWordToPictureViewer, MatchWordToPictureGroupViewer, MatchColumnsToMakeSentencesViewer, WordOrderingGroupViewer, } from './components/questions/viewer';
|
|
24
|
+
export { QuestionViewer, QuestionGroupViewer, WordFillStructuredFormViewer, ChooseTheCorrectAnswerGroupViewer, TrueFalseGroupViewer, TrueFalseCorrectGroupViewer, FillInBlankGroupViewer, MatchingWithLinesGroupViewer, CrossOutWordGroupViewer, SpontaneousQaGroupViewer, WordOrderAndMatchGroupViewer, AnswerTheQuestionGroupViewer, MatchWordToPictureViewer, MatchWordToPictureGroupViewer, MatchColumnsToMakeSentencesViewer, WordOrderingGroupViewer, SortWordsIntoCategoriesViewer, ChooseThenAnswerGroupViewer, } from './components/questions/viewer';
|
|
@@ -84,6 +84,8 @@ export const QUESTION_TYPE_SKILLS = {
|
|
|
84
84
|
'WORD_FILL_STRUCTURED_FORM': SkillEnum.READING,
|
|
85
85
|
'CROSSWORD_PUZZLE': SkillEnum.READING,
|
|
86
86
|
'FIND_WORDS_IN_MATRIX': SkillEnum.READING,
|
|
87
|
+
'SORT_WORDS_INTO_CATEGORIES': SkillEnum.READING,
|
|
88
|
+
'CHOOSE_THEN_ANSWER_GROUP': SkillEnum.READING,
|
|
87
89
|
'MATCH_COLUMNS_TO_MAKE_SENTENCES': SkillEnum.READING,
|
|
88
90
|
'MATCH_WORD_TO_PICTURE_GROUP': SkillEnum.READING,
|
|
89
91
|
'WORD_ORDERING_GROUP': SkillEnum.READING,
|
|
@@ -219,6 +221,8 @@ export const QUESTION_TYPE_LABELS = {
|
|
|
219
221
|
'WORD_FILL_STRUCTURED_FORM': 'Điền từ vào biểu mẫu có cấu trúc',
|
|
220
222
|
'CROSSWORD_PUZZLE': 'Giải ô chữ (Crossword)',
|
|
221
223
|
'FIND_WORDS_IN_MATRIX': 'Tìm từ trong ma trận (Word Search)',
|
|
224
|
+
'SORT_WORDS_INTO_CATEGORIES': 'Sắp xếp từ vào nhóm',
|
|
225
|
+
'CHOOSE_THEN_ANSWER_GROUP': 'Chọn đáp án rồi trả lời',
|
|
222
226
|
'MATCH_COLUMNS_TO_MAKE_SENTENCES': 'Ghép cột thành câu',
|
|
223
227
|
'MATCH_WORD_TO_PICTURE_GROUP': 'Nối từ với tranh (nhóm)',
|
|
224
228
|
'WORD_ORDERING_GROUP': 'Sắp xếp từ (nhóm)',
|