@tinyweb_dev/oe-exam-sdk 1.0.8 → 1.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/exams/take/components/QuestionRenderer.js +1 -5
- package/dist/components/exams/take/components/question-renderers/ChooseThenAnswerGroupRenderer.js +1 -1
- package/dist/components/exams/take/components/question-renderers/MoversCrossOutWordGroupRenderer.js +24 -10
- package/dist/components/exams/take/components/question-renderers/MoversCrosswordPuzzleRenderer.js +1 -1
- package/dist/components/exams/take/components/question-renderers/MoversFillInBlankGroupRenderer.js +2 -1
- package/dist/components/exams/take/components/speaking/renderers/SpeakingSpontaneousQaGroupRenderer.js +12 -2
- package/dist/components/exams/take/hooks/useSpontaneousQaGroupRendererSetup.js +2 -1
- package/dist/components/exams/take/utils/answer-transformers.js +16 -1
- package/dist/components/exams/take/utils/cross-out-word-group.transformer.js +4 -6
- package/dist/components/exams/take/utils/question-transformers.d.ts +5 -0
- package/dist/components/exams/take/utils/question-transformers.js +6 -0
- package/dist/components/questions/_shared/components/compact/KidChoiceRow.d.ts +12 -0
- package/dist/components/questions/_shared/components/compact/KidChoiceRow.js +33 -0
- package/dist/components/questions/_shared/components/compact/KidInstruction.d.ts +5 -0
- package/dist/components/questions/_shared/components/compact/KidInstruction.js +9 -0
- package/dist/components/questions/_shared/components/compact/KidPrompt.d.ts +5 -0
- package/dist/components/questions/_shared/components/compact/KidPrompt.js +9 -0
- package/dist/components/questions/_shared/components/compact/KidQuestionCard.d.ts +12 -0
- package/dist/components/questions/_shared/components/compact/KidQuestionCard.js +7 -0
- package/dist/components/questions/_shared/components/compact/index.d.ts +4 -0
- package/dist/components/questions/_shared/components/compact/index.js +4 -0
- package/dist/components/questions/_shared/types/answer-the-question-group.type.d.ts +3 -0
- package/dist/components/questions/_shared/types/choose-then-answer-group.type.d.ts +17 -0
- package/dist/components/questions/_shared/types/cross-out-word-group.type.d.ts +1 -0
- package/dist/components/questions/_shared/types/crossword-puzzle.type.d.ts +5 -1
- package/dist/components/questions/_shared/types/spontaneous-qa-group.type.d.ts +17 -0
- package/dist/components/questions/_shared/types/true-false-correct-group.type.d.ts +1 -0
- package/dist/components/questions/_shared/types/true-false-group.type.d.ts +1 -0
- package/dist/components/questions/types/answer-the-question/AnswerTheQuestionClient.js +18 -19
- package/dist/components/questions/types/answer-the-question-group/AnswerTheQuestionGroupClient.d.ts +1 -1
- package/dist/components/questions/types/answer-the-question-group/AnswerTheQuestionGroupClient.js +16 -17
- package/dist/components/questions/types/answer-the-question-group/AnswerTheQuestionGroupCreator.js +7 -4
- package/dist/components/questions/types/answer-the-question-group/map-answer-the-question-group-data.js +2 -0
- package/dist/components/questions/types/answer-the-question-group/transform.js +12 -5
- package/dist/components/questions/types/arrange-letters-into-words/ArrangeLettersIntoWordsClient.js +17 -11
- package/dist/components/questions/types/choose-correct-adjective/ChooseCorrectAdjectiveClient.js +15 -42
- package/dist/components/questions/types/choose-the-correct-answer/ChooseTheCorrectAnswerClient.js +15 -36
- package/dist/components/questions/types/choose-the-correct-answer-group/ChooseTheCorrectAnswerGroupClient.js +9 -32
- package/dist/components/questions/types/choose-then-answer-group/ChooseThenAnswerGroupClient.d.ts +1 -1
- package/dist/components/questions/types/choose-then-answer-group/ChooseThenAnswerGroupClient.js +76 -11
- package/dist/components/questions/types/choose-then-answer-group/ChooseThenAnswerGroupCreator.js +54 -6
- package/dist/components/questions/types/choose-then-answer-group/map-choose-then-answer-group-data.d.ts +4 -1
- package/dist/components/questions/types/choose-then-answer-group/map-choose-then-answer-group-data.js +39 -2
- package/dist/components/questions/types/choose-then-answer-group/transform.js +27 -14
- package/dist/components/questions/types/cross-out-word-group/CrossOutWordGroupClient.d.ts +1 -1
- package/dist/components/questions/types/cross-out-word-group/CrossOutWordGroupClient.js +76 -7
- package/dist/components/questions/types/cross-out-word-group/CrossOutWordGroupCreator.js +24 -44
- package/dist/components/questions/types/cross-out-word-group/index.d.ts +1 -0
- package/dist/components/questions/types/cross-out-word-group/index.js +1 -0
- package/dist/components/questions/types/cross-out-word-group/map-cross-out-word-group-data.js +9 -9
- package/dist/components/questions/types/cross-out-word-group/tokenize.d.ts +2 -0
- package/dist/components/questions/types/cross-out-word-group/tokenize.js +11 -0
- package/dist/components/questions/types/cross-out-word-group/transform.js +2 -4
- package/dist/components/questions/types/crossword-puzzle/CrosswordPuzzleClient.d.ts +2 -1
- package/dist/components/questions/types/crossword-puzzle/CrosswordPuzzleClient.js +20 -8
- package/dist/components/questions/types/crossword-puzzle/CrosswordPuzzleCreator.js +68 -11
- package/dist/components/questions/types/crossword-puzzle/transform.js +59 -30
- package/dist/components/questions/types/fill-in-blank/FillInBlankClient.js +26 -31
- package/dist/components/questions/types/fill-in-blank-group/FillInBlankAnswerKey.d.ts +6 -0
- package/dist/components/questions/types/fill-in-blank-group/FillInBlankAnswerKey.js +17 -0
- package/dist/components/questions/types/fill-in-blank-group/FillInBlankGroupClient.js +32 -28
- package/dist/components/questions/types/fill-in-blank-group/FillInBlankGroupCreator.js +109 -26
- package/dist/components/questions/types/fill-in-blank-group/FillInBlankGroupMediaField.js +21 -11
- package/dist/components/questions/types/fill-in-blank-group/blank-utils.d.ts +1 -0
- package/dist/components/questions/types/fill-in-blank-group/blank-utils.js +4 -1
- package/dist/components/questions/types/fill-missing-words-in-grid/FillMissingWordsInGridClient.js +32 -31
- package/dist/components/questions/types/find-words-in-matrix/FindWordsInMatrixClient.d.ts +12 -2
- package/dist/components/questions/types/find-words-in-matrix/FindWordsInMatrixClient.js +188 -46
- package/dist/components/questions/types/find-words-in-matrix/FindWordsInMatrixCreator.js +6 -2
- package/dist/components/questions/types/find-words-in-matrix/transform.js +5 -0
- package/dist/components/questions/types/label-the-picture/LabelThePictureClient.js +4 -3
- package/dist/components/questions/types/listen-and-choose-from-answer-group/ListenAndChooseFromAnswerGroupClient.js +11 -30
- package/dist/components/questions/types/listen-and-speak-answer/ListenAndSpeakAnswerClient.js +7 -6
- package/dist/components/questions/types/listen-and-speak-compare-images/ListenAndSpeakCompareImagesClient.js +2 -1
- package/dist/components/questions/types/listen-and-speak-image-group/ListenAndSpeakImageGroupClient.js +14 -13
- package/dist/components/questions/types/listen-and-speak-info-exchange/ListenAndSpeakInfoExchangeClient.js +4 -3
- package/dist/components/questions/types/listen-and-speak-odd-one-out/ListenAndSpeakOddOneOutClient.js +4 -3
- package/dist/components/questions/types/listen-and-speak-question-list/ListenAndSpeakQuestionListClient.js +11 -10
- package/dist/components/questions/types/listen-and-speak-with-story-images/ListenAndSpeakWithStoryImagesClient.js +4 -3
- package/dist/components/questions/types/match-by-writing-answer/MatchByWritingAnswerClient.js +13 -11
- package/dist/components/questions/types/match-columns-to-make-sentences/MatchColumnsToMakeSentencesClient.js +17 -14
- package/dist/components/questions/types/match-word-to-picture/MatchWordToPictureClient.js +16 -12
- package/dist/components/questions/types/match-word-to-picture/MatchWordToPictureGroupClient.js +14 -14
- package/dist/components/questions/types/match-word-to-picture-group/MatchWordToPictureGroupClient.js +13 -13
- package/dist/components/questions/types/matching-with-lines-group/MatchingWithLinesGroupClient.js +7 -6
- package/dist/components/questions/types/read-passage-and-answer-questions/ReadPassageAndAnswerQuestionsClient.js +8 -26
- package/dist/components/questions/types/sort-words-into-categories/SortWordsIntoCategoriesClient.js +2 -1
- package/dist/components/questions/types/speaking-conversation/SpeakingConversationPreviewClient.js +6 -5
- package/dist/components/questions/types/speaking-cue-card/SpeakingCueCardClient.js +2 -1
- package/dist/components/questions/types/speaking-describe-image/SpeakingDescribeImageClient.js +5 -4
- package/dist/components/questions/types/spontaneous-qa-group/SpontaneousQaGroupClient.d.ts +1 -1
- package/dist/components/questions/types/spontaneous-qa-group/SpontaneousQaGroupClient.js +54 -11
- package/dist/components/questions/types/spontaneous-qa-group/SpontaneousQaGroupCreator.js +11 -7
- package/dist/components/questions/types/spontaneous-qa-group/creator-helpers.js +8 -2
- package/dist/components/questions/types/spontaneous-qa-group/flatten.d.ts +1 -0
- package/dist/components/questions/types/spontaneous-qa-group/flatten.js +4 -2
- package/dist/components/questions/types/spontaneous-qa-group/map-spontaneous-qa-group-data.js +5 -3
- package/dist/components/questions/types/spontaneous-qa-group/register.js +5 -2
- package/dist/components/questions/types/spontaneous-qa-group/transform.js +6 -2
- package/dist/components/questions/types/true-false/TrueFalseClient.js +5 -54
- package/dist/components/questions/types/true-false-correct-group/TrueFalseCorrectGroupClient.d.ts +1 -1
- package/dist/components/questions/types/true-false-correct-group/TrueFalseCorrectGroupClient.js +39 -19
- package/dist/components/questions/types/true-false-group/TrueFalseGroupClient.d.ts +1 -1
- package/dist/components/questions/types/true-false-group/TrueFalseGroupClient.js +20 -19
- package/dist/components/questions/types/word-fill-paragraph/WordFillParagraphClient.js +76 -81
- package/dist/components/questions/types/word-fill-structured-form/WordFillStructuredFormClient.js +17 -16
- package/dist/components/questions/types/word-order-and-match-group/WordOrderAndMatchGroupClient.js +31 -36
- package/dist/components/questions/types/word-ordering/WordOrderingClient.js +15 -14
- package/dist/components/questions/types/word-ordering-group/WordOrderingGroupClient.js +9 -8
- package/dist/components/questions/types/write-correct-verb-form/WriteCorrectVerbFormClient.js +16 -13
- package/dist/components/questions/viewer/AnswerTheQuestionGroupViewer.d.ts +3 -2
- package/dist/components/questions/viewer/AnswerTheQuestionGroupViewer.js +12 -5
- package/dist/components/questions/viewer/ChooseThenAnswerGroupViewer.d.ts +3 -2
- package/dist/components/questions/viewer/ChooseThenAnswerGroupViewer.js +4 -3
- package/dist/components/questions/viewer/CrossOutWordGroupViewer.d.ts +3 -1
- package/dist/components/questions/viewer/CrossOutWordGroupViewer.js +12 -2
- package/dist/components/questions/viewer/FillInBlankGroupViewer.d.ts +6 -2
- package/dist/components/questions/viewer/FillInBlankGroupViewer.js +13 -7
- package/dist/components/questions/viewer/QuestionViewer.d.ts +9 -1
- package/dist/components/questions/viewer/QuestionViewer.js +23 -10
- package/dist/components/questions/viewer/SpontaneousQaGroupViewer.d.ts +7 -1
- package/dist/components/questions/viewer/SpontaneousQaGroupViewer.js +14 -2
- package/dist/components/questions/viewer/TrueFalseCorrectGroupViewer.d.ts +3 -2
- package/dist/components/questions/viewer/TrueFalseCorrectGroupViewer.js +14 -4
- package/dist/components/questions/viewer/TrueFalseGroupViewer.d.ts +5 -2
- package/dist/components/questions/viewer/TrueFalseGroupViewer.js +14 -4
- package/dist/components/questions/viewer/WordOrderAndMatchGroupViewer.d.ts +6 -2
- package/dist/components/questions/viewer/WordOrderAndMatchGroupViewer.js +26 -6
- package/dist/components/results/renderers/ReviewAnswerTheQuestionGroupRenderer.js +1 -1
- package/dist/components/results/renderers/review-question-body-movers.js +1 -1
- package/dist/components/results/review-data.js +8 -1
- package/dist/shared/lib/utils/question-reverse-transform.js +39 -5
- package/dist/shared/types/questions/answer-the-question-group.d.ts +8 -3
- package/dist/shared/types/questions/answer-the-question-group.js +5 -0
- package/dist/shared/types/questions/choose-then-answer-group.d.ts +3 -0
- package/dist/shared/types/questions/crossword-puzzle.d.ts +1 -0
- package/dist/shared/types/questions/find-words-in-matrix.d.ts +1 -0
- package/package.json +1 -1
|
@@ -102,7 +102,7 @@ export function QuestionRenderer({ part, answers, onAnswerChange, isReviewMode =
|
|
|
102
102
|
}
|
|
103
103
|
case 'CROSSWORD_PUZZLE': {
|
|
104
104
|
const data = transformCrosswordPuzzle(questions);
|
|
105
|
-
return (_jsx(MoversCrosswordPuzzleRenderer, { partNumber: partNo, partName: part.name, questionCount: questionCount, instruction: part.instructions || data.instruction, title: data.title, gridSize: data.gridSize, clues: data.clues, answers: answers, correctAnswers: data.correctAnswers, onAnswerChange: onAnswerChange, isReviewMode: isReviewMode }));
|
|
105
|
+
return (_jsx(MoversCrosswordPuzzleRenderer, { partNumber: partNo, partName: part.name, questionCount: questionCount, instruction: part.instructions || data.instruction, title: data.title, gridSize: data.gridSize, markedColumn: data.markedColumn, hiddenWord: data.hiddenWord, clues: data.clues, answers: answers, correctAnswers: data.correctAnswers, onAnswerChange: onAnswerChange, isReviewMode: isReviewMode, caseSensitive: data.caseSensitive }));
|
|
106
106
|
}
|
|
107
107
|
case 'IMAGE_OBJECT_MATCHING': {
|
|
108
108
|
// Reuse coloring renderer — same content structure as READ_AND_COLOR_OBJECTS
|
|
@@ -125,10 +125,6 @@ export function QuestionRenderer({ part, answers, onAnswerChange, isReviewMode =
|
|
|
125
125
|
onAnswerChange(regionId, hintItemId);
|
|
126
126
|
}, isReviewMode: isReviewMode }));
|
|
127
127
|
}
|
|
128
|
-
case 'CROSSWORD_PUZZLE': {
|
|
129
|
-
const data = transformCrosswordPuzzle(questions);
|
|
130
|
-
return (_jsx(MoversCrosswordPuzzleRenderer, { partNumber: partNo, partName: part.name, questionCount: questionCount, instruction: part.instructions || data.instruction, title: data.title, gridSize: data.gridSize, clues: data.clues, answers: answers, correctAnswers: data.correctAnswers, onAnswerChange: onAnswerChange, isReviewMode: isReviewMode }));
|
|
131
|
-
}
|
|
132
128
|
case 'SORT_WORDS_INTO_CATEGORIES': {
|
|
133
129
|
const data = transformSortWordsIntoCategories(questions);
|
|
134
130
|
const gradableCount = data.words.filter((word) => !word.isExample).length;
|
package/dist/components/exams/take/components/question-renderers/ChooseThenAnswerGroupRenderer.js
CHANGED
|
@@ -13,7 +13,7 @@ export function ChooseThenAnswerGroupRenderer({ partNumber, partName, questionCo
|
|
|
13
13
|
});
|
|
14
14
|
return (_jsxs("div", { className: "w-full space-y-4", children: [_jsx(CambridgeYlePartBanner, { partNumber: partNumber, partName: partName, questionCount: questionCount }), _jsx(CambridgeYleInstructionBanner, { instruction: instruction }), _jsx("div", { className: "mt-4", children: _jsx(ChooseThenAnswerGroupClient, { questionData: {
|
|
15
15
|
title: data.title,
|
|
16
|
-
instruction:
|
|
16
|
+
instruction: '',
|
|
17
17
|
items: data.questions.map((item) => ({
|
|
18
18
|
question: item.question,
|
|
19
19
|
options: item.options,
|
package/dist/components/exams/take/components/question-renderers/MoversCrossOutWordGroupRenderer.js
CHANGED
|
@@ -8,9 +8,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
8
8
|
import { useMemo } from 'react';
|
|
9
9
|
import CambridgeYlePartBanner from '../../../../../components/themes/cambridge-yle/CambridgeYlePartBanner';
|
|
10
10
|
import CambridgeYleInstructionBanner from '../../../../../components/themes/cambridge-yle/CambridgeYleInstructionBanner';
|
|
11
|
-
|
|
12
|
-
return sentence.trim().split(/\s+/).filter(Boolean);
|
|
13
|
-
}
|
|
11
|
+
import { tokenizeCrossOutQuestion } from '../../../../questions/types/cross-out-word-group/tokenize';
|
|
14
12
|
function normalizeWord(word) {
|
|
15
13
|
return word.replace(/[^\p{L}\p{N}'-]/gu, '').toLowerCase();
|
|
16
14
|
}
|
|
@@ -27,12 +25,20 @@ function formatAnswerLabel(answer) {
|
|
|
27
25
|
return 'câu đúng (không gạch)';
|
|
28
26
|
return `gạch "${answer.crossOut}"${posInfo}`;
|
|
29
27
|
}
|
|
28
|
+
function isCorrectToken(correct, tokenIndex, plain) {
|
|
29
|
+
if (!correct)
|
|
30
|
+
return false;
|
|
31
|
+
if (typeof correct.wordIndex === 'number' && correct.wordIndex >= 0) {
|
|
32
|
+
return correct.wordIndex === tokenIndex;
|
|
33
|
+
}
|
|
34
|
+
return Boolean(correct.crossOut) && normalizeWord(correct.crossOut) === plain && plain !== '';
|
|
35
|
+
}
|
|
30
36
|
function QuestionItem({ question, answer, onAnswerChange, isReviewMode, correctAnswer, id, }) {
|
|
31
37
|
const isDisabled = isReviewMode || question.isExample;
|
|
32
38
|
const currentAnswer = question.isExample
|
|
33
39
|
? question.exampleCorrectAnswer
|
|
34
40
|
: answer;
|
|
35
|
-
const tokens = useMemo(() =>
|
|
41
|
+
const tokens = useMemo(() => tokenizeCrossOutQuestion(question.question), [question.question]);
|
|
36
42
|
const selectedWord = currentAnswer?.crossOut || '';
|
|
37
43
|
const selectedIndex = currentAnswer?.wordIndex;
|
|
38
44
|
const replaceWith = currentAnswer?.replaceWith || '';
|
|
@@ -76,11 +82,10 @@ function QuestionItem({ question, answer, onAnswerChange, isReviewMode, correctA
|
|
|
76
82
|
});
|
|
77
83
|
}
|
|
78
84
|
else {
|
|
79
|
-
const cleanWord = rawToken.replace(/[^\p{L}\p{N}'-]/gu, '') || rawToken;
|
|
80
85
|
onAnswerChange(question.id, {
|
|
81
86
|
type: mode,
|
|
82
87
|
wordIndex: tokenIndex,
|
|
83
|
-
crossOut:
|
|
88
|
+
crossOut: rawToken,
|
|
84
89
|
...(mode === 'CROSS_OUT_AND_REPLACE' ? { replaceWith } : {}),
|
|
85
90
|
});
|
|
86
91
|
}
|
|
@@ -109,9 +114,9 @@ function QuestionItem({ question, answer, onAnswerChange, isReviewMode, correctA
|
|
|
109
114
|
? 'bg-emerald-500'
|
|
110
115
|
: isWrong
|
|
111
116
|
? 'bg-rose-500'
|
|
112
|
-
: 'bg-blue-600'}`, children: question.questionNumber }), question.isExample && (_jsx("span", { className: "rounded-full border border-amber-300 bg-amber-100/90 px-2 py-0.5 text-[10.5px] font-extrabold uppercase tracking-wider text-amber-800", children: "EXAMPLE" })), _jsx("span", { className: `rounded-full px-2.5 py-0.5 text-[11px] font-bold ${mode === 'CROSS_OUT_AND_REPLACE'
|
|
117
|
+
: 'bg-blue-600'}`, children: question.questionNumber }), question.isExample && (_jsx("span", { className: "rounded-full border border-amber-300 bg-amber-100/90 px-2 py-0.5 text-[10.5px] font-extrabold uppercase tracking-wider text-amber-800", children: "EXAMPLE" })), isReviewMode ? (_jsx("span", { className: `rounded-full px-2.5 py-0.5 text-[11px] font-bold ${mode === 'CROSS_OUT_AND_REPLACE'
|
|
113
118
|
? 'bg-violet-100/80 text-violet-700 border border-violet-200/60'
|
|
114
|
-
: 'bg-sky-100/80 text-sky-700 border border-sky-200/60'}`, children: mode === 'CROSS_OUT_AND_REPLACE' ? 'Gạch + Thay thế' : 'Chỉ gạch' }), !question.isExample && selectedWord && (_jsxs("span", { className: "flex items-center gap-1 rounded-full bg-emerald-100 px-2 py-0.5 text-[10.5px] font-bold text-emerald-700", children: [_jsx("span", { className: "h-1.5 w-1.5 rounded-full bg-emerald-500" }), "\u0110\u00E3 g\u1EA1ch: \"", selectedWord, "\"", typeof selectedIndex === 'number' && (_jsxs("span", { className: "text-[10px] text-emerald-600 font-normal", children: ["(t\u1EEB #", selectedIndex + 1, ")"] }))] }))] }), mode === 'CROSS_OUT_AND_REPLACE' && (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("label", { className: "text-xs font-bold text-slate-600 whitespace-nowrap", children: "T\u1EEB \u0111\u00FAng:" }), _jsx("input", { type: "text", value: replaceWith, disabled: isDisabled, onChange: (e) => setReplaceWith(e.target.value), placeholder: "Nh\u1EADp t\u1EEB thay th\u1EBF...", className: "h-8 w-40 sm:w-52 rounded-lg border border-slate-200 bg-white px-2.5 text-sm font-semibold text-slate-800 outline-none transition-all focus:border-blue-500 focus:bg-white focus:ring-2 focus:ring-blue-100 disabled:cursor-not-allowed disabled:bg-slate-50" })] }))] }), _jsx("div", { className: "flex flex-wrap items-center gap-1.5 sm:gap-2 text-[15.5px] sm:text-[16.5px] font-medium leading-loose text-slate-800 px-1", children: tokens.map((token, tokenIndex) => {
|
|
119
|
+
: 'bg-sky-100/80 text-sky-700 border border-sky-200/60'}`, children: mode === 'CROSS_OUT_AND_REPLACE' ? 'Gạch + Thay thế' : 'Chỉ gạch' })) : null, !question.isExample && selectedWord && (_jsxs("span", { className: "flex items-center gap-1 rounded-full bg-emerald-100 px-2 py-0.5 text-[10.5px] font-bold text-emerald-700", children: [_jsx("span", { className: "h-1.5 w-1.5 rounded-full bg-emerald-500" }), "\u0110\u00E3 g\u1EA1ch: \"", selectedWord, "\"", typeof selectedIndex === 'number' && (_jsxs("span", { className: "text-[10px] text-emerald-600 font-normal", children: ["(t\u1EEB #", selectedIndex + 1, ")"] }))] }))] }), mode === 'CROSS_OUT_AND_REPLACE' && (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("label", { className: "text-xs font-bold text-slate-600 whitespace-nowrap", children: "T\u1EEB \u0111\u00FAng:" }), _jsx("input", { type: "text", value: replaceWith, disabled: isDisabled, onChange: (e) => setReplaceWith(e.target.value), placeholder: "Nh\u1EADp t\u1EEB thay th\u1EBF...", className: "h-8 w-40 sm:w-52 rounded-lg border border-slate-200 bg-white px-2.5 text-sm font-semibold text-slate-800 outline-none transition-all focus:border-blue-500 focus:bg-white focus:ring-2 focus:ring-blue-100 disabled:cursor-not-allowed disabled:bg-slate-50" })] }))] }), _jsx("div", { className: "flex flex-wrap items-center gap-1.5 sm:gap-2 text-[15.5px] sm:text-[16.5px] font-medium leading-loose text-slate-800 px-1", children: tokens.map((token, tokenIndex) => {
|
|
115
120
|
const plain = normalizeWord(token);
|
|
116
121
|
const isSelected = selectedIndex !== undefined && selectedIndex !== null
|
|
117
122
|
? selectedIndex === tokenIndex
|
|
@@ -119,12 +124,21 @@ function QuestionItem({ question, answer, onAnswerChange, isReviewMode, correctA
|
|
|
119
124
|
const isCorrectTargetInReview = isReviewMode &&
|
|
120
125
|
!question.isExample &&
|
|
121
126
|
correctAnswer?.wordIndex === tokenIndex;
|
|
127
|
+
const isSelectedCorrect = isReviewMode &&
|
|
128
|
+
!question.isExample &&
|
|
129
|
+
isSelected &&
|
|
130
|
+
isCorrectToken(correctAnswer, tokenIndex, plain);
|
|
131
|
+
const isSelectedWrong = isReviewMode &&
|
|
132
|
+
!question.isExample &&
|
|
133
|
+
isSelected &&
|
|
134
|
+
!!correctAnswer &&
|
|
135
|
+
!isSelectedCorrect;
|
|
122
136
|
return (_jsx("button", { type: "button", disabled: isDisabled || plain === '', onClick: () => setCrossOut(tokenIndex, token), className: `rounded-lg px-2 py-1 transition-all duration-150 select-none relative ${isSelected
|
|
123
137
|
? question.isExample
|
|
124
138
|
? 'bg-amber-200/90 text-amber-900 font-bold line-through decoration-amber-700 decoration-2 shadow-sm'
|
|
125
|
-
:
|
|
139
|
+
: isSelectedCorrect
|
|
126
140
|
? 'bg-emerald-100 text-emerald-800 font-bold line-through decoration-emerald-600 decoration-2 shadow-sm border border-emerald-300'
|
|
127
|
-
:
|
|
141
|
+
: isSelectedWrong
|
|
128
142
|
? 'bg-rose-100 text-rose-700 font-bold line-through decoration-rose-600 decoration-2 shadow-sm border border-rose-300 scale-[0.98]'
|
|
129
143
|
: 'bg-blue-100 text-blue-700 font-bold line-through decoration-blue-600 decoration-2 shadow-sm border border-blue-200 scale-[0.98]'
|
|
130
144
|
: isCorrectTargetInReview
|
package/dist/components/exams/take/components/question-renderers/MoversCrosswordPuzzleRenderer.js
CHANGED
|
@@ -307,5 +307,5 @@ export function MoversCrosswordPuzzleRenderer({ partNumber, partName, questionCo
|
|
|
307
307
|
inputRefs.current.delete(key);
|
|
308
308
|
}, type: "text", maxLength: 1, disabled: Boolean(isExample), value: cellValue, onChange: (e) => handleInputChange(e.target.value, r, c), onKeyDown: (e) => handleKeyDown(e, r, c), onFocus: () => handleCellClick(r, c), className: `w-full h-full text-center bg-transparent uppercase font-bold outline-none ${isExample ? 'cursor-default' : 'cursor-pointer'}` })) : (_jsxs("div", { className: "flex flex-col items-center justify-center", children: [_jsx("span", { children: cellValue || '—' }), reviewStatus && !reviewStatus.isCorrect && (_jsx("span", { className: "absolute -bottom-1 -right-1 text-[9px] bg-green-600 text-white font-bold px-1 rounded-full shadow", children: reviewStatus.expected }))] })), isReviewMode && reviewStatus && (_jsx("span", { className: `absolute -top-1 -right-1 rounded-full p-0.5 text-white ${reviewStatus.isCorrect ? 'bg-green-500' : 'bg-red-500'}`, children: reviewStatus.isCorrect ? (_jsx(Check, { className: "h-2.5 w-2.5" })) : (_jsx(X, { className: "h-2.5 w-2.5" })) }))] }, key));
|
|
309
309
|
});
|
|
310
|
-
}) }) })] })] })] }));
|
|
310
|
+
}) }) }), (markedColumn || hiddenWord) && (_jsxs("div", { className: "w-full max-w-md rounded-xl border-2 border-amber-200 bg-amber-50 p-4", children: [_jsx("label", { className: "block text-sm font-bold text-amber-900 mb-2", children: "What's the hidden word?" }), !isReviewMode ? (_jsx("input", { type: "text", value: answers['hidden-word'] || '', onChange: (event) => onAnswerChange('hidden-word', event.target.value.toUpperCase()), placeholder: "Hidden word", className: "w-full rounded-lg border border-amber-300 bg-white px-3 py-2 text-sm font-semibold uppercase tracking-wider outline-none focus:border-amber-500" })) : (_jsxs("div", { className: "text-sm", children: [answers['hidden-word'] && (_jsx("span", { className: "font-semibold text-gray-800", children: answers['hidden-word'] })), hiddenWord && (_jsxs("div", { className: "mt-1 text-xs", children: [_jsx("span", { className: "text-gray-500", children: "\u0110\u00E1p \u00E1n: " }), _jsx("span", { className: "font-bold text-green-700", children: hiddenWord })] }))] }))] }))] })] })] }));
|
|
311
311
|
}
|
package/dist/components/exams/take/components/question-renderers/MoversFillInBlankGroupRenderer.js
CHANGED
|
@@ -8,6 +8,7 @@ import { ResolvedImage } from '../../../../../components/common/ResolvedImage';
|
|
|
8
8
|
import CambridgeYlePartBanner from '../../../../../components/themes/cambridge-yle/CambridgeYlePartBanner';
|
|
9
9
|
import CambridgeYleInstructionBanner from '../../../../../components/themes/cambridge-yle/CambridgeYleInstructionBanner';
|
|
10
10
|
import CambridgeYleAudioPlayer from '../../../../../components/themes/cambridge-yle/CambridgeYleAudioPlayer';
|
|
11
|
+
import { FillInBlankAnswerKey } from '../../../../questions/types/fill-in-blank-group/FillInBlankAnswerKey';
|
|
11
12
|
import { extractPlaceholderIndices, isBlankMatch, padStudentAnswers, splitStem, toFillInBlankImageUrls, } from '../../../../questions/types/fill-in-blank-group/blank-utils';
|
|
12
13
|
function emptyAnswer(blankCount) {
|
|
13
14
|
return { answers: Array.from({ length: blankCount }, () => '') };
|
|
@@ -53,7 +54,7 @@ function ItemCard({ question, answer, onAnswerChange, isReviewMode, correctAnswe
|
|
|
53
54
|
: blankWrong
|
|
54
55
|
? 'border-rose-400 bg-rose-50 text-rose-800'
|
|
55
56
|
: 'border-blue-200 bg-blue-50/40 text-blue-900 focus:border-blue-500 focus:bg-white'}` }, partIndex));
|
|
56
|
-
}) })] }), isReviewMode && !question.isExample && expected.length > 0 && (
|
|
57
|
+
}) })] }), isReviewMode && !question.isExample && expected.length > 0 && (_jsx("div", { className: "mt-2 rounded-lg border border-emerald-200 bg-emerald-50 px-2.5 py-2", children: _jsx(FillInBlankAnswerKey, { answersByBlank: expected, blankIndices: blankIndices }) }))] }));
|
|
57
58
|
}
|
|
58
59
|
export function MoversFillInBlankGroupRenderer({ partNumber, partName, questionCount, instruction, audioUrl, imageUrl, showHints = false, hints = [], questions, answers, onAnswerChange, isReviewMode = false, correctAnswerMap, }) {
|
|
59
60
|
return (_jsxs("div", { className: "relative flex h-full flex-col gap-4 overflow-y-auto custom-scrollbar sm:gap-6", children: [_jsxs("div", { className: "flex flex-col gap-2 p-3 sm:gap-3 sm:px-6 sm:pt-4", children: [_jsx(CambridgeYlePartBanner, { partNumber: partNumber, questionCount: questionCount, partName: partName, isReviewMode: isReviewMode }), _jsx(CambridgeYleInstructionBanner, { instruction: instruction, isReviewMode: isReviewMode })] }), (audioUrl || toFillInBlankImageUrls(imageUrl).length > 0) && (_jsxs("div", { className: "sticky top-0 z-20 flex flex-col gap-3 border-b border-gray-100 bg-white/95 p-3 pb-2 shadow-sm backdrop-blur sm:px-6", children: [audioUrl && _jsx(CambridgeYleAudioPlayer, { audioSrc: audioUrl, compact: true }), toFillInBlankImageUrls(imageUrl).length > 0 && (_jsx("div", { className: "flex flex-wrap gap-2", children: toFillInBlankImageUrls(imageUrl).map((url, imageIndex) => (_jsx(ResolvedImage, { src: url, alt: `Shared illustration ${imageIndex + 1}`, className: "max-h-[180px] rounded-lg object-contain" }, `group-image-${imageIndex}`))) }))] })), showHints && hints.length > 0 && !isReviewMode && (_jsx("div", { className: "flex flex-wrap gap-2 px-3 sm:px-6", children: hints.map((hint) => (_jsx("span", { className: "rounded-full border border-blue-200 bg-blue-50 px-3 py-1 text-xs font-semibold text-blue-700", children: hint }, hint))) })), _jsx("div", { className: "grid grid-cols-1 gap-3 p-3 sm:grid-cols-2 sm:px-6 lg:grid-cols-3", children: questions.map((question) => (_jsx(ItemCard, { question: question, answer: answers[question.id], onAnswerChange: onAnswerChange, isReviewMode: isReviewMode, correctAnswer: correctAnswerMap?.[question.id] }, question.id))) })] }));
|
|
@@ -37,14 +37,24 @@ export function SpeakingSpontaneousQaGroupRenderer(props) {
|
|
|
37
37
|
useEffect(() => {
|
|
38
38
|
setSequenceStep(getInitialSequenceStep());
|
|
39
39
|
}, [getInitialSequenceStep, setup.nav.currentIndex]);
|
|
40
|
+
const hasQuestionAudio = Boolean(content?.audioUrl?.trim());
|
|
40
41
|
useEffect(() => {
|
|
41
42
|
if (sequenceStep !== SPONTANEOUS_QA_SEQUENCE_STEP.DELAYING || shouldResumeCompletedState)
|
|
42
43
|
return;
|
|
43
44
|
const timer = setTimeout(() => {
|
|
44
|
-
setSequenceStep(
|
|
45
|
+
setSequenceStep(hasQuestionAudio
|
|
46
|
+
? SPONTANEOUS_QA_SEQUENCE_STEP.PLAYING_QUESTION
|
|
47
|
+
: SPONTANEOUS_QA_SEQUENCE_STEP.ALLOW_RECORDING);
|
|
45
48
|
}, 3000);
|
|
46
49
|
return () => clearTimeout(timer);
|
|
47
|
-
}, [sequenceStep, shouldResumeCompletedState]);
|
|
50
|
+
}, [hasQuestionAudio, sequenceStep, shouldResumeCompletedState]);
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
if (sequenceStep === SPONTANEOUS_QA_SEQUENCE_STEP.PLAYING_QUESTION &&
|
|
53
|
+
!hasQuestionAudio &&
|
|
54
|
+
!shouldResumeCompletedState) {
|
|
55
|
+
setSequenceStep(SPONTANEOUS_QA_SEQUENCE_STEP.ALLOW_RECORDING);
|
|
56
|
+
}
|
|
57
|
+
}, [hasQuestionAudio, sequenceStep, shouldResumeCompletedState]);
|
|
48
58
|
const handleTeacherVideoEnded = useCallback(() => {
|
|
49
59
|
setSequenceStep(SPONTANEOUS_QA_SEQUENCE_STEP.DELAYING);
|
|
50
60
|
props.onTeacherIntroPlayed?.();
|
|
@@ -27,8 +27,9 @@ export function useSpontaneousQaGroupRendererSetup({ part, answers, onAnswerChan
|
|
|
27
27
|
flattenIndex: child.flattenIndex,
|
|
28
28
|
expectedAnswers: child.expectedAnswers,
|
|
29
29
|
points: child.points,
|
|
30
|
+
isExample: child.isExample,
|
|
30
31
|
},
|
|
31
|
-
is_example:
|
|
32
|
+
is_example: child.isExample,
|
|
32
33
|
correct_answer: { expectedAnswers: child.expectedAnswers },
|
|
33
34
|
total_points: String(child.points),
|
|
34
35
|
question_type: 'SPONTANEOUS_QA_GROUP',
|
|
@@ -263,6 +263,16 @@ export function transformAnswersForSubmission(answers, parts) {
|
|
|
263
263
|
if (speakingQuestionIds.has(questionId) && typeof answer === 'string') {
|
|
264
264
|
continue;
|
|
265
265
|
}
|
|
266
|
+
if (questionId === 'hidden-word') {
|
|
267
|
+
const parent = findGridParentQuestion(parts);
|
|
268
|
+
if (parent?.questionType === 'CROSSWORD_PUZZLE') {
|
|
269
|
+
if (!gridCellAnswers.has(parent.parentId)) {
|
|
270
|
+
gridCellAnswers.set(parent.parentId, {});
|
|
271
|
+
}
|
|
272
|
+
gridCellAnswers.get(parent.parentId)['hidden-word'] = String(answer);
|
|
273
|
+
}
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
266
276
|
// Handle grid cell keys (from FILL_MISSING_WORDS_IN_GRID)
|
|
267
277
|
if (isGridCellKey(questionId)) {
|
|
268
278
|
const parent = findGridParentQuestion(parts);
|
|
@@ -454,9 +464,14 @@ export function transformAnswersForSubmission(answers, parts) {
|
|
|
454
464
|
words[`down-${c.number}`] = word;
|
|
455
465
|
}
|
|
456
466
|
}
|
|
467
|
+
const hiddenWord = cellAnswers['hidden-word'];
|
|
468
|
+
const cells = Object.fromEntries(Object.entries(cellAnswers).filter(([key]) => key !== 'hidden-word'));
|
|
469
|
+
if (hiddenWord) {
|
|
470
|
+
words['hidden-word'] = hiddenWord;
|
|
471
|
+
}
|
|
457
472
|
result.push({
|
|
458
473
|
questionId: parentId,
|
|
459
|
-
answer: { answers: words, cells
|
|
474
|
+
answer: { answers: words, cells },
|
|
460
475
|
});
|
|
461
476
|
}
|
|
462
477
|
else {
|
|
@@ -63,21 +63,19 @@ export function normalizeCrossOutWordGroupAnswer(raw) {
|
|
|
63
63
|
const rawIndex = obj.wordIndex;
|
|
64
64
|
const wordIndex = typeof rawIndex === 'number' && Number.isInteger(rawIndex) && rawIndex >= 0
|
|
65
65
|
? rawIndex
|
|
66
|
-
:
|
|
67
|
-
? null
|
|
68
|
-
: undefined;
|
|
66
|
+
: null;
|
|
69
67
|
return type === 'CROSS_OUT_AND_REPLACE'
|
|
70
68
|
? {
|
|
71
69
|
type,
|
|
72
|
-
|
|
70
|
+
wordIndex,
|
|
73
71
|
crossOut: typeof obj.crossOut === 'string' ? obj.crossOut : '',
|
|
74
72
|
replaceWith: typeof obj.replaceWith === 'string' ? obj.replaceWith : '',
|
|
75
73
|
}
|
|
76
74
|
: {
|
|
77
75
|
type: 'CROSS_OUT',
|
|
78
|
-
|
|
76
|
+
wordIndex,
|
|
79
77
|
crossOut: typeof obj.crossOut === 'string' ? obj.crossOut : '',
|
|
80
78
|
};
|
|
81
79
|
}
|
|
82
|
-
return { type: 'CROSS_OUT', crossOut: '' };
|
|
80
|
+
return { type: 'CROSS_OUT', wordIndex: null, crossOut: '' };
|
|
83
81
|
}
|
|
@@ -472,14 +472,19 @@ export interface CrosswordClueItem {
|
|
|
472
472
|
startRow: number;
|
|
473
473
|
startCol: number;
|
|
474
474
|
length: number;
|
|
475
|
+
isExample?: boolean;
|
|
475
476
|
}
|
|
476
477
|
export interface CrosswordPuzzlePartData {
|
|
478
|
+
type?: 'STANDARD' | 'HIDDEN_WORD';
|
|
477
479
|
title?: string;
|
|
478
480
|
instruction: string;
|
|
479
481
|
gridSize: {
|
|
480
482
|
rows: number;
|
|
481
483
|
cols: number;
|
|
482
484
|
};
|
|
485
|
+
markedColumn?: number;
|
|
486
|
+
hiddenWord?: string;
|
|
487
|
+
caseSensitive?: boolean;
|
|
483
488
|
clues: {
|
|
484
489
|
across: CrosswordClueItem[];
|
|
485
490
|
down: CrosswordClueItem[];
|
|
@@ -1258,6 +1258,7 @@ export function transformCrosswordPuzzle(questions) {
|
|
|
1258
1258
|
startRow: Number(c.startRow) || 1,
|
|
1259
1259
|
startCol: Number(c.startCol) || 1,
|
|
1260
1260
|
length: Number(c.length) || 1,
|
|
1261
|
+
...(c.isExample === true ? { isExample: true } : {}),
|
|
1261
1262
|
}))
|
|
1262
1263
|
: [];
|
|
1263
1264
|
const down = Array.isArray(content?.clues?.down)
|
|
@@ -1267,15 +1268,20 @@ export function transformCrosswordPuzzle(questions) {
|
|
|
1267
1268
|
startRow: Number(c.startRow) || 1,
|
|
1268
1269
|
startCol: Number(c.startCol) || 1,
|
|
1269
1270
|
length: Number(c.length) || 1,
|
|
1271
|
+
...(c.isExample === true ? { isExample: true } : {}),
|
|
1270
1272
|
}))
|
|
1271
1273
|
: [];
|
|
1272
1274
|
const correctAnswers = correctAnswer?.answers && typeof correctAnswer.answers === 'object'
|
|
1273
1275
|
? correctAnswer.answers
|
|
1274
1276
|
: {};
|
|
1275
1277
|
return {
|
|
1278
|
+
type: content?.type === 'HIDDEN_WORD' ? 'HIDDEN_WORD' : 'STANDARD',
|
|
1276
1279
|
title: content?.title || '',
|
|
1277
1280
|
instruction: question?.instruction || content?.instruction || 'Complete the crossword.',
|
|
1278
1281
|
gridSize: { rows, cols },
|
|
1282
|
+
...(typeof content?.markedColumn === 'number' ? { markedColumn: content.markedColumn } : {}),
|
|
1283
|
+
hiddenWord: correctAnswers['hidden-word'] || '',
|
|
1284
|
+
caseSensitive: correctAnswer?.caseSensitive === true,
|
|
1279
1285
|
clues: { across, down },
|
|
1280
1286
|
correctAnswers,
|
|
1281
1287
|
explanation: question?.explanation,
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
export declare function optionLetter(index: number): string;
|
|
3
|
+
export declare function KidChoiceRow({ letter, selected, isReviewMode, isCorrect, disabled, onClick, children, className, }: {
|
|
4
|
+
letter: string;
|
|
5
|
+
selected?: boolean;
|
|
6
|
+
isReviewMode?: boolean;
|
|
7
|
+
isCorrect?: boolean;
|
|
8
|
+
disabled?: boolean;
|
|
9
|
+
onClick?: () => void;
|
|
10
|
+
children: ReactNode;
|
|
11
|
+
className?: string;
|
|
12
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Check, X } from 'lucide-react';
|
|
4
|
+
import { cn } from '../../../../../shared/lib/utils';
|
|
5
|
+
export function optionLetter(index) {
|
|
6
|
+
return String.fromCharCode(65 + index);
|
|
7
|
+
}
|
|
8
|
+
export function KidChoiceRow({ letter, selected = false, isReviewMode = false, isCorrect = false, disabled = false, onClick, children, className, }) {
|
|
9
|
+
const showStatus = isReviewMode && (isCorrect || selected);
|
|
10
|
+
const rowTone = isReviewMode
|
|
11
|
+
? isCorrect
|
|
12
|
+
? 'border-green-400 bg-green-50'
|
|
13
|
+
: selected
|
|
14
|
+
? 'border-red-400 bg-red-50'
|
|
15
|
+
: 'border-gray-200 bg-gray-50'
|
|
16
|
+
: selected
|
|
17
|
+
? 'border-blue-500 bg-blue-50 shadow-sm ring-2 ring-blue-100'
|
|
18
|
+
: 'border-gray-200 bg-white hover:border-gray-300 hover:bg-gray-50 hover:shadow-sm';
|
|
19
|
+
const badgeTone = isReviewMode
|
|
20
|
+
? isCorrect
|
|
21
|
+
? 'bg-green-500 text-white'
|
|
22
|
+
: selected
|
|
23
|
+
? 'bg-red-500 text-white'
|
|
24
|
+
: 'bg-gray-200 text-gray-600'
|
|
25
|
+
: selected
|
|
26
|
+
? 'bg-gradient-to-br from-blue-500 to-indigo-600 text-white shadow-sm'
|
|
27
|
+
: 'bg-gray-100 text-gray-600 group-hover:bg-gray-200';
|
|
28
|
+
return (_jsxs("button", { type: "button", onClick: onClick, disabled: disabled || isReviewMode || !onClick, className: cn('group flex w-full items-center gap-2 rounded-md border px-3 py-1.5 text-left transition-all', rowTone, (disabled || isReviewMode || !onClick) && 'cursor-default', className), children: [_jsx("div", { className: cn('flex h-7 w-7 flex-shrink-0 items-center justify-center rounded-full text-xs font-bold transition-all', badgeTone), children: letter }), _jsx("div", { className: cn('min-w-0 flex-1 text-sm font-medium', isReviewMode && isCorrect
|
|
29
|
+
? 'text-green-800'
|
|
30
|
+
: isReviewMode && selected && !isCorrect
|
|
31
|
+
? 'text-red-800'
|
|
32
|
+
: 'text-gray-700'), children: children }), showStatus && (_jsx("div", { className: cn('flex h-5 w-5 shrink-0 items-center justify-center rounded-full', isCorrect ? 'bg-green-500' : 'bg-red-500'), children: isCorrect ? _jsx(Check, { className: "h-3 w-3 text-white" }) : _jsx(X, { className: "h-3 w-3 text-white" }) }))] }));
|
|
33
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { cn } from '../../../../../shared/lib/utils';
|
|
4
|
+
export function KidInstruction({ children, className, }) {
|
|
5
|
+
if (!children) {
|
|
6
|
+
return null;
|
|
7
|
+
}
|
|
8
|
+
return (_jsx("div", { className: cn('rounded-xl border border-indigo-100 bg-indigo-50 px-4 py-3 text-sm font-medium text-indigo-800', className), children: children }));
|
|
9
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { cn } from '../../../../../shared/lib/utils';
|
|
4
|
+
export function KidPrompt({ children, className, }) {
|
|
5
|
+
if (!children) {
|
|
6
|
+
return null;
|
|
7
|
+
}
|
|
8
|
+
return (_jsx("div", { className: cn('rounded-md border border-blue-100 bg-gradient-to-r from-blue-50 to-indigo-50 px-3 py-2 text-sm font-semibold text-gray-800', className), children: children }));
|
|
9
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
export declare function KidQuestionCard({ icon, title, points, isExample, review, headerExtra, children, className, contentClassName, }: {
|
|
3
|
+
icon?: ReactNode;
|
|
4
|
+
title: string;
|
|
5
|
+
points?: number;
|
|
6
|
+
isExample?: boolean;
|
|
7
|
+
review?: 'correct' | 'wrong' | null;
|
|
8
|
+
headerExtra?: ReactNode;
|
|
9
|
+
children: ReactNode;
|
|
10
|
+
className?: string;
|
|
11
|
+
contentClassName?: string;
|
|
12
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Check, CircleHelp, Star, X } from 'lucide-react';
|
|
4
|
+
import { cn } from '../../../../../shared/lib/utils';
|
|
5
|
+
export function KidQuestionCard({ icon, title, points, isExample = false, review, headerExtra, children, className, contentClassName, }) {
|
|
6
|
+
return (_jsxs("div", { className: cn('overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm', className), children: [_jsxs("div", { className: "flex items-center justify-between gap-2 border-b border-gray-100 bg-gradient-to-r from-gray-50 to-white px-3 py-2", children: [_jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [_jsx("div", { className: "flex h-7 w-7 shrink-0 items-center justify-center rounded-md bg-gradient-to-br from-blue-500 to-indigo-600 text-white", children: icon ?? _jsx(CircleHelp, { className: "h-3.5 w-3.5" }) }), _jsx("span", { className: "truncate text-sm font-medium text-gray-700", children: title }), isExample && (_jsxs("span", { className: "inline-flex items-center gap-1 rounded-full bg-amber-100 px-2 py-0.5 text-[11px] font-semibold text-amber-700", children: [_jsx(Star, { className: "h-3 w-3" }), "C\u00E2u v\u00ED d\u1EE5"] }))] }), _jsxs("div", { className: "flex shrink-0 items-center gap-2", children: [headerExtra, typeof points === 'number' && (_jsxs("span", { className: "text-xs text-gray-500", children: [points, " \u0111i\u1EC3m"] })), review === 'correct' && (_jsxs("div", { className: "flex items-center gap-1 rounded-full bg-green-100 px-2 py-0.5 text-xs font-semibold text-green-700", children: [_jsx(Check, { className: "h-3 w-3" }), "\u0110\u00FAng"] })), review === 'wrong' && (_jsxs("div", { className: "flex items-center gap-1 rounded-full bg-red-100 px-2 py-0.5 text-xs font-semibold text-red-700", children: [_jsx(X, { className: "h-3 w-3" }), "Sai"] }))] })] }), _jsx("div", { className: cn('space-y-3 p-3', contentClassName), children: children })] }));
|
|
7
|
+
}
|
|
@@ -4,3 +4,7 @@ export { CompactPreviewButton } from './CompactPreviewButton';
|
|
|
4
4
|
export { CompactCreatorHeader } from './CompactCreatorHeader';
|
|
5
5
|
export { CompactFieldCard } from './CompactFieldCard';
|
|
6
6
|
export { CompactExplanationToggle } from './CompactExplanationToggle';
|
|
7
|
+
export { KidInstruction } from './KidInstruction';
|
|
8
|
+
export { KidPrompt } from './KidPrompt';
|
|
9
|
+
export { KidQuestionCard } from './KidQuestionCard';
|
|
10
|
+
export { KidChoiceRow, optionLetter } from './KidChoiceRow';
|
|
@@ -4,3 +4,7 @@ export { CompactPreviewButton } from './CompactPreviewButton';
|
|
|
4
4
|
export { CompactCreatorHeader } from './CompactCreatorHeader';
|
|
5
5
|
export { CompactFieldCard } from './CompactFieldCard';
|
|
6
6
|
export { CompactExplanationToggle } from './CompactExplanationToggle';
|
|
7
|
+
export { KidInstruction } from './KidInstruction';
|
|
8
|
+
export { KidPrompt } from './KidPrompt';
|
|
9
|
+
export { KidQuestionCard } from './KidQuestionCard';
|
|
10
|
+
export { KidChoiceRow, optionLetter } from './KidChoiceRow';
|
|
@@ -19,6 +19,8 @@ export interface AnswerTheQuestionGroupData {
|
|
|
19
19
|
items: AnswerTheQuestionGroupItemData[];
|
|
20
20
|
explanation?: string;
|
|
21
21
|
points?: number;
|
|
22
|
+
/** Preserved from content.meta; AI | MAPPING. */
|
|
23
|
+
gradingType?: 'AI' | 'MAPPING';
|
|
22
24
|
}
|
|
23
25
|
export interface AnswerTheQuestionGroupCreatorProps {
|
|
24
26
|
initialData?: AnswerTheQuestionGroupData;
|
|
@@ -36,4 +38,5 @@ export interface AnswerTheQuestionGroupClientProps {
|
|
|
36
38
|
questionData: AnswerTheQuestionGroupData;
|
|
37
39
|
isReviewMode?: boolean;
|
|
38
40
|
userAnswers?: string[];
|
|
41
|
+
onAnswerChange?: (answers: string[]) => void;
|
|
39
42
|
}
|
|
@@ -7,6 +7,7 @@ export interface ChooseThenAnswerOption {
|
|
|
7
7
|
}
|
|
8
8
|
export interface ChooseThenAnswerItemAnswer {
|
|
9
9
|
optionId: string;
|
|
10
|
+
expectedAnswers?: string[];
|
|
10
11
|
}
|
|
11
12
|
export interface ChooseThenAnswerStudentItemAnswer {
|
|
12
13
|
optionId: string;
|
|
@@ -16,6 +17,7 @@ export interface ChooseThenAnswerGroupItemData {
|
|
|
16
17
|
question: string;
|
|
17
18
|
options: ChooseThenAnswerOption[];
|
|
18
19
|
optionId: string;
|
|
20
|
+
expectedAnswers?: string[];
|
|
19
21
|
isExample?: boolean;
|
|
20
22
|
points: number;
|
|
21
23
|
questionNumber: number;
|
|
@@ -39,9 +41,24 @@ export interface ChooseThenAnswerGroupCreatorProps {
|
|
|
39
41
|
getFormData?: () => ChooseThenAnswerGroupData;
|
|
40
42
|
} | null>;
|
|
41
43
|
}
|
|
44
|
+
export interface ChooseThenAnswerItemResult {
|
|
45
|
+
mappingCorrect?: boolean;
|
|
46
|
+
aiCorrect?: boolean;
|
|
47
|
+
partialScore?: number;
|
|
48
|
+
feedback?: string;
|
|
49
|
+
}
|
|
50
|
+
export interface ChooseThenAnswerGradeDetail {
|
|
51
|
+
itemId: string;
|
|
52
|
+
isCorrect?: boolean;
|
|
53
|
+
userAnswer?: unknown;
|
|
54
|
+
correctAnswer?: unknown;
|
|
55
|
+
partialScore?: number;
|
|
56
|
+
feedback?: string;
|
|
57
|
+
}
|
|
42
58
|
export interface ChooseThenAnswerGroupClientProps {
|
|
43
59
|
questionData: ChooseThenAnswerGroupData;
|
|
44
60
|
isReviewMode?: boolean;
|
|
45
61
|
userAnswers?: ChooseThenAnswerStudentItemAnswer[];
|
|
62
|
+
itemResults?: ChooseThenAnswerItemResult[];
|
|
46
63
|
onAnswerChange?: (index: number, next: ChooseThenAnswerStudentItemAnswer) => void;
|
|
47
64
|
}
|
|
@@ -39,4 +39,5 @@ export interface CrossOutWordGroupClientProps {
|
|
|
39
39
|
questionData: CrossOutWordGroupData;
|
|
40
40
|
isReviewMode?: boolean;
|
|
41
41
|
userAnswers?: Array<CrossOutWordGroupItemAnswer | null | undefined>;
|
|
42
|
+
onAnswerChange?: (answers: Array<CrossOutWordGroupItemAnswer | null>) => void;
|
|
42
43
|
}
|
|
@@ -1,13 +1,17 @@
|
|
|
1
|
-
import type { CrosswordClue, CrosswordPuzzleContent, CrosswordPuzzleCorrectAnswer } from '../../../../shared/types/questions/crossword-puzzle';
|
|
1
|
+
import type { CrosswordClue, CrosswordPuzzleContent, CrosswordPuzzleCorrectAnswer, CrosswordSubtype } from '../../../../shared/types/questions/crossword-puzzle';
|
|
2
2
|
export interface CrosswordClueFormItem extends CrosswordClue {
|
|
3
3
|
id: string;
|
|
4
4
|
answer: string;
|
|
5
5
|
direction: 'across' | 'down';
|
|
6
6
|
}
|
|
7
7
|
export interface CrosswordPuzzleFormState {
|
|
8
|
+
type?: CrosswordSubtype;
|
|
8
9
|
title?: string;
|
|
10
|
+
instruction?: string;
|
|
9
11
|
rows: number;
|
|
10
12
|
cols: number;
|
|
13
|
+
markedColumn?: number;
|
|
14
|
+
hiddenWord?: string;
|
|
11
15
|
clues: CrosswordClueFormItem[];
|
|
12
16
|
caseSensitive: boolean;
|
|
13
17
|
points: number;
|
|
@@ -6,6 +6,7 @@ export interface SpontaneousQaGroupQuestionData {
|
|
|
6
6
|
expectedAnswers: string[];
|
|
7
7
|
points: number;
|
|
8
8
|
questionNumber: number;
|
|
9
|
+
isExample?: boolean;
|
|
9
10
|
}
|
|
10
11
|
export interface SpontaneousQaGroupPartData {
|
|
11
12
|
partNumber: number;
|
|
@@ -31,7 +32,23 @@ export interface SpontaneousQaGroupCreatorProps {
|
|
|
31
32
|
getFormData?: () => SpontaneousQaGroupData;
|
|
32
33
|
} | null>;
|
|
33
34
|
}
|
|
35
|
+
export interface SpontaneousQaGroupStudentAnswer {
|
|
36
|
+
audioKeys: Array<string | null>;
|
|
37
|
+
}
|
|
38
|
+
export interface SpontaneousQaGroupGradeDetail {
|
|
39
|
+
itemId: string;
|
|
40
|
+
isCorrect?: boolean;
|
|
41
|
+
userAnswer?: unknown;
|
|
42
|
+
correctAnswer?: unknown;
|
|
43
|
+
partialScore?: number;
|
|
44
|
+
feedback?: string;
|
|
45
|
+
}
|
|
34
46
|
export interface SpontaneousQaGroupClientProps {
|
|
35
47
|
questionData: SpontaneousQaGroupData;
|
|
36
48
|
isReviewMode?: boolean;
|
|
49
|
+
userAnswer?: SpontaneousQaGroupStudentAnswer;
|
|
50
|
+
onItemRecorded?: (flattenIndex: number, blob: Blob) => void;
|
|
51
|
+
uploadingIndex?: number | null;
|
|
52
|
+
gradeDetails?: SpontaneousQaGroupGradeDetail[];
|
|
53
|
+
disabled?: boolean;
|
|
37
54
|
}
|