@tinyweb_dev/oe-exam-sdk 1.0.9 → 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/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/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/types/answer-the-question/AnswerTheQuestionClient.js +18 -19
- package/dist/components/questions/types/answer-the-question-group/AnswerTheQuestionGroupClient.js +8 -7
- package/dist/components/questions/types/answer-the-question-group/AnswerTheQuestionGroupCreator.js +5 -4
- 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 -30
- package/dist/components/questions/types/choose-then-answer-group/ChooseThenAnswerGroupClient.js +8 -7
- package/dist/components/questions/types/cross-out-word-group/CrossOutWordGroupClient.js +50 -32
- 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 +8 -6
- 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.js +71 -28
- 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.js +21 -23
- package/dist/components/questions/types/true-false-group/TrueFalseGroupClient.js +8 -17
- 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/CrossOutWordGroupViewer.js +1 -4
- 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.js +2 -2
- package/dist/components/questions/viewer/SpontaneousQaGroupViewer.d.ts +7 -1
- package/dist/components/questions/viewer/SpontaneousQaGroupViewer.js +14 -2
- 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 +31 -5
- 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
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useState, useEffect, useCallback, useMemo } from 'react';
|
|
4
|
-
import { Check, X, ArrowUpDown
|
|
4
|
+
import { Check, X, ArrowUpDown } from 'lucide-react';
|
|
5
5
|
import { usePresignedFileUrl } from '../../../../shared/lib/hooks';
|
|
6
6
|
import { cn } from '../../../../shared/lib/utils';
|
|
7
|
+
import { KidQuestionCard } from '../../_shared/components/compact';
|
|
7
8
|
function toWordList(value) {
|
|
8
9
|
if (Array.isArray(value)) {
|
|
9
10
|
return value.filter((item) => typeof item === 'string');
|
|
@@ -86,17 +87,17 @@ export function WordOrderingClient({ questionData, onSubmit, isReviewMode = fals
|
|
|
86
87
|
const isWrong = isReviewMode && selectedWords.length > 0 && !isCorrect;
|
|
87
88
|
const isComplete = selectedWords.length === words.length;
|
|
88
89
|
const displayExplanation = explanation || questionData.explanation;
|
|
89
|
-
return (_jsx("div", { className: "space-y-4", children: _jsxs(
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
90
|
+
return (_jsx("div", { className: "space-y-4", children: _jsxs(KidQuestionCard, { icon: _jsx(ArrowUpDown, { className: "h-3.5 w-3.5" }), title: "S\u1EAFp x\u1EBFp t\u1EEB", isExample: isExample, review: isReviewMode && isComplete ? (isCorrect ? 'correct' : 'wrong') : null, children: [(imagePreviewUrl || imageUrl) && (_jsx("div", { className: "flex justify-center", children: _jsx("div", { className: "overflow-hidden rounded-lg border border-gray-200 bg-gray-50", children: isLoading ? (_jsx("div", { className: "flex h-48 w-48 items-center justify-center", children: _jsx("div", { className: "h-8 w-8 animate-spin rounded-full border-4 border-gray-200 border-t-amber-500" }) })) : (_jsx("img", { src: imagePreviewUrl || imageUrl, alt: "H\u00ECnh \u1EA3nh c\u00E2u h\u1ECFi", className: "max-h-56 w-auto object-contain p-2" })) }) })), _jsxs("div", { className: "space-y-2", children: [_jsx("span", { className: "text-xs font-medium text-gray-500 uppercase tracking-wider", children: "\u0110\u00E1p \u00E1n c\u1EE7a b\u1EA1n" }), _jsx("div", { className: "flex flex-wrap gap-2 min-h-[48px] rounded-lg border-2 border-dashed border-gray-300 bg-gray-50 p-3", children: selectedWords.length === 0 ? (_jsx("span", { className: "text-sm text-gray-400 italic", children: "Nh\u1EA5n v\u00E0o c\u00E1c t\u1EEB b\u00EAn d\u01B0\u1EDBi \u0111\u1EC3 s\u1EAFp x\u1EBFp..." })) : (selectedWords.map((word, index) => {
|
|
91
|
+
const isWordCorrect = isReviewMode && answer[index] === word;
|
|
92
|
+
const isWordWrong = isReviewMode && answer[index] !== word;
|
|
93
|
+
return (_jsxs("button", { type: "button", onClick: () => handleRemoveWord(index), disabled: isReviewMode, className: cn('inline-flex items-center gap-1 rounded-lg px-3 py-2 text-sm font-semibold transition-all shadow-sm', isReviewMode
|
|
94
|
+
? isWordCorrect
|
|
95
|
+
? 'bg-green-100 text-green-800 border border-green-300'
|
|
96
|
+
: 'bg-red-100 text-red-800 border border-red-300'
|
|
97
|
+
: 'cursor-pointer border border-blue-500 bg-blue-50 text-blue-800 ring-2 ring-blue-100 hover:bg-blue-100'), children: [word, isReviewMode && (isWordCorrect
|
|
98
|
+
? _jsx(Check, { className: "h-3.5 w-3.5 text-green-600" })
|
|
99
|
+
: _jsx(X, { className: "h-3.5 w-3.5 text-red-600" }))] }, `selected-${index}`));
|
|
100
|
+
})) })] }), availableWords.length > 0 && (_jsxs("div", { className: "space-y-2", children: [_jsx("span", { className: "text-xs font-medium text-gray-500 uppercase tracking-wider", children: "T\u1EEB c\u00F3 s\u1EB5n" }), _jsx("div", { className: "flex flex-wrap gap-2", children: availableWords.map((word, index) => (_jsx("button", { type: "button", onClick: () => handleSelectWord(word, index), disabled: isReviewMode, className: cn('rounded-lg border-2 px-3 py-2 text-sm font-semibold transition-all shadow-sm', isReviewMode
|
|
101
|
+
? 'border-gray-200 bg-gray-100 text-gray-500 cursor-default'
|
|
102
|
+
: 'border-blue-200 bg-blue-50 text-blue-700 hover:bg-blue-100 hover:border-blue-300 cursor-pointer hover:shadow-md'), children: word }, `available-${index}`))) })] })), isReviewMode && isWrong && (_jsxs("div", { className: "rounded-lg border border-green-200 bg-green-50 p-3", children: [_jsx("span", { className: "text-xs font-medium text-green-600 uppercase tracking-wider", children: "\u0110\u00E1p \u00E1n \u0111\u00FAng" }), _jsx("div", { className: "mt-2 flex flex-wrap gap-2", children: answer.map((word, index) => (_jsx("span", { className: "rounded-lg bg-green-100 px-3 py-1.5 text-sm font-semibold text-green-800 border border-green-300", children: word }, `correct-${index}`))) })] })), isReviewMode && displayExplanation && (_jsxs("div", { className: "rounded-lg border border-blue-200 bg-gradient-to-r from-blue-50 to-indigo-50 p-3", children: [_jsx("span", { className: "text-xs font-medium text-blue-600 uppercase tracking-wider", children: "Gi\u1EA3i th\u00EDch" }), _jsx("p", { className: "mt-1 text-sm text-blue-800", children: displayExplanation })] }))] }) }));
|
|
102
103
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { Lightbulb
|
|
3
|
+
import { Lightbulb } from 'lucide-react';
|
|
4
4
|
import { WordOrderingClient } from '../word-ordering/WordOrderingClient';
|
|
5
|
+
import { KidInstruction, KidQuestionCard } from '../../_shared/components/compact';
|
|
5
6
|
export function WordOrderingGroupClient({ questionData, isReviewMode = false, userAnswers = [], onAnswerChange, }) {
|
|
6
7
|
const items = questionData.items || [];
|
|
7
8
|
const handleItemSubmit = (itemIndex, ordered) => {
|
|
@@ -16,11 +17,11 @@ export function WordOrderingGroupClient({ questionData, isReviewMode = false, us
|
|
|
16
17
|
});
|
|
17
18
|
onAnswerChange(next);
|
|
18
19
|
};
|
|
19
|
-
return (_jsxs("div", { className: "space-y-4", children: [
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
20
|
+
return (_jsxs("div", { className: "space-y-4", children: [_jsx(KidInstruction, { children: questionData.instruction }), items.map((item, itemIndex) => (_jsx(KidQuestionCard, { title: `Câu ${item.questionNumber || itemIndex + 1}`, points: item.points || 0, isExample: item.isExample, children: _jsx(WordOrderingClient, { questionData: {
|
|
21
|
+
words: item.words,
|
|
22
|
+
answer: item.answer,
|
|
23
|
+
isExample: item.isExample,
|
|
24
|
+
points: item.points,
|
|
25
|
+
imageUrl: item.imageUrl,
|
|
26
|
+
}, isReviewMode: isReviewMode || item.isExample, userAnswer: item.isExample ? item.answer : userAnswers[itemIndex], onSubmit: (ordered) => handleItemSubmit(itemIndex, ordered) }) }, `${item.questionNumber}-${itemIndex}`))), isReviewMode && questionData.explanation && (_jsxs("div", { className: "flex items-start gap-2 rounded-xl border border-amber-100 bg-amber-50 px-4 py-3 text-sm text-amber-800", children: [_jsx(Lightbulb, { className: "mt-0.5 h-4 w-4 flex-shrink-0" }), questionData.explanation] }))] }));
|
|
26
27
|
}
|
package/dist/components/questions/types/write-correct-verb-form/WriteCorrectVerbFormClient.js
CHANGED
|
@@ -3,7 +3,8 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import { useState, useEffect } from 'react';
|
|
4
4
|
import { Input } from '../../../../components/ui/input';
|
|
5
5
|
import { cn } from '../../../../shared/lib/utils';
|
|
6
|
-
import { Check, X,
|
|
6
|
+
import { Check, X, PenLine, AlertTriangle, Lightbulb } from 'lucide-react';
|
|
7
|
+
import { KidInstruction, KidPrompt, KidQuestionCard } from '../../_shared/components/compact';
|
|
7
8
|
export function WriteCorrectVerbFormClient({ questionData, onSubmit, isReviewMode = false, userAnswer = '', }) {
|
|
8
9
|
// ============ STATE ============
|
|
9
10
|
const [answer, setAnswer] = useState(userAnswer || '');
|
|
@@ -39,7 +40,9 @@ export function WriteCorrectVerbFormClient({ questionData, onSubmit, isReviewMod
|
|
|
39
40
|
const parts = questionData.sentence.split(/(\{0\})/g);
|
|
40
41
|
return (_jsx("div", { className: "flex flex-wrap items-center gap-1.5 text-lg leading-relaxed", children: parts.map((part, index) => {
|
|
41
42
|
if (part === '{0}') {
|
|
42
|
-
return (_jsxs("span", { className: "inline-flex items-center gap-2", children: [_jsxs("div", { className: "relative inline-flex items-center", children: [_jsx(Input, { value: questionData.correctAnswer, readOnly: true, disabled: true, className: cn('w-36 text-center font-semibold border-
|
|
43
|
+
return (_jsxs("span", { className: "inline-flex items-center gap-2", children: [_jsxs("div", { className: "relative inline-flex items-center", children: [_jsx(Input, { value: questionData.correctAnswer, readOnly: true, disabled: true, className: cn('w-36 text-sm text-center font-semibold border px-3 py-1.5 rounded-md transition-all cursor-not-allowed', isReviewMode && isWrong
|
|
44
|
+
? 'border-red-400 bg-red-50 text-red-800'
|
|
45
|
+
: 'border-green-400 bg-green-50 text-green-800') }), isReviewMode && (_jsx("div", { className: cn('absolute -right-7 top-1/2 -translate-y-1/2 flex h-5 w-5 items-center justify-center rounded-full', isCorrect ? 'bg-green-500' : isWrong ? 'bg-red-500' : 'bg-gray-300'), children: isCorrect ? (_jsx(Check, { className: "h-3 w-3 text-white" })) : isWrong ? (_jsx(X, { className: "h-3 w-3 text-white" })) : null }))] }), _jsxs("span", { className: "rounded-lg bg-amber-100 px-2 py-0.5 text-sm font-medium text-amber-700", children: ["(", questionData.verbHint, ")"] })] }, index));
|
|
43
46
|
}
|
|
44
47
|
else {
|
|
45
48
|
return _jsx("span", { className: "text-gray-800", children: part }, index);
|
|
@@ -47,15 +50,15 @@ export function WriteCorrectVerbFormClient({ questionData, onSubmit, isReviewMod
|
|
|
47
50
|
}) }));
|
|
48
51
|
};
|
|
49
52
|
// ============ RENDER ============
|
|
50
|
-
return (_jsxs("div", { className: "space-y-3", children: [
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
53
|
+
return (_jsxs("div", { className: "space-y-3", children: [_jsx(KidInstruction, { children: questionData.instruction }), _jsxs(KidQuestionCard, { icon: _jsx(PenLine, { className: "h-3.5 w-3.5" }), title: "Chia \u0111\u1ED9ng t\u1EEB \u0111\u00FAng", points: questionData.points, review: isReviewMode && hasAnswered ? (isCorrect ? 'correct' : 'wrong') : null, children: [_jsx(KidPrompt, { children: renderSentenceWithInput() }), isReviewMode && (_jsxs("div", { className: "space-y-3", children: [_jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { className: cn('rounded-xl border-2 p-4', isCorrect
|
|
54
|
+
? 'border-green-200 bg-green-50'
|
|
55
|
+
: isWrong
|
|
56
|
+
? 'border-red-200 bg-red-50'
|
|
57
|
+
: 'border-gray-200 bg-gray-50'), children: [_jsxs("div", { className: "flex items-center gap-2 mb-2", children: [_jsx("div", { className: cn('flex h-6 w-6 items-center justify-center rounded-lg', isCorrect ? 'bg-green-200' : isWrong ? 'bg-red-200' : 'bg-gray-200'), children: isCorrect ? (_jsx(Check, { className: "h-3.5 w-3.5 text-green-700" })) : isWrong ? (_jsx(X, { className: "h-3.5 w-3.5 text-red-700" })) : (_jsx("span", { className: "text-xs text-gray-500", children: "?" })) }), _jsx("span", { className: "text-xs font-medium text-gray-600", children: "C\u00E2u tr\u1EA3 l\u1EDDi c\u1EE7a b\u1EA1n" })] }), _jsx("p", { className: cn('text-lg font-bold', isCorrect ? 'text-green-700' : isWrong ? 'text-red-700' : 'text-gray-400'), children: answer || '(Chưa trả lời)' })] }), _jsxs("div", { className: "rounded-xl border-2 border-green-200 bg-gradient-to-br from-green-50 to-emerald-50 p-4", children: [_jsxs("div", { className: "flex items-center gap-2 mb-2", children: [_jsx("div", { className: "flex h-6 w-6 items-center justify-center rounded-lg bg-green-200", children: _jsx(Check, { className: "h-3.5 w-3.5 text-green-700" }) }), _jsx("span", { className: "text-xs font-medium text-gray-600", children: "\u0110\u00E1p \u00E1n \u0111\u00FAng" })] }), _jsx("p", { className: "text-lg font-bold text-green-700", children: questionData.correctAnswer })] })] }), _jsx("div", { className: cn('rounded-xl p-4', isCorrect
|
|
58
|
+
? 'bg-gradient-to-r from-green-50 to-emerald-50 border border-green-200'
|
|
59
|
+
: 'bg-gradient-to-r from-red-50 to-rose-50 border border-red-200'), children: _jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: cn('flex h-10 w-10 items-center justify-center rounded-xl text-white shadow-sm', isCorrect
|
|
60
|
+
? 'bg-gradient-to-br from-green-500 to-emerald-600'
|
|
61
|
+
: 'bg-gradient-to-br from-red-500 to-rose-600'), children: isCorrect ? _jsx(Check, { className: "h-5 w-5" }) : _jsx(X, { className: "h-5 w-5" }) }), _jsxs("div", { children: [_jsx("p", { className: cn('text-sm font-semibold', isCorrect ? 'text-green-800' : 'text-red-800'), children: isCorrect ? 'Chính xác!' : 'Chưa đúng' }), _jsx("p", { className: cn('text-xs', isCorrect ? 'text-green-600' : 'text-red-600'), children: isCorrect
|
|
62
|
+
? 'Bạn đã chia động từ đúng.'
|
|
63
|
+
: `Đáp án đúng là "${questionData.correctAnswer}".` })] })] }) }), questionData.explanation && (_jsx("div", { className: "rounded-xl bg-gradient-to-r from-blue-50 to-indigo-50 p-4 border border-blue-200", children: _jsxs("div", { className: "flex items-start gap-3", children: [_jsx("div", { className: "flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-lg bg-blue-500 text-white", children: _jsx(Lightbulb, { className: "h-3.5 w-3.5" }) }), _jsxs("div", { className: "flex-1", children: [_jsx("span", { className: "text-xs font-semibold text-blue-700 uppercase tracking-wider", children: "Gi\u1EA3i th\u00EDch" }), _jsx("p", { className: "mt-1 text-sm text-gray-700 leading-relaxed", children: questionData.explanation })] })] }) }))] })), !isReviewMode && !hasAnswered && (_jsxs("div", { className: "flex items-center gap-2 rounded-md border border-amber-200 bg-amber-50 px-2.5 py-1.5", children: [_jsx("div", { className: "flex h-4 w-4 flex-shrink-0 items-center justify-center rounded-full bg-amber-400 text-white", children: _jsx("span", { className: "text-[10px] font-bold", children: "!" }) }), _jsx("span", { className: "text-xs text-amber-800", children: "Nh\u1EADp d\u1EA1ng \u0111\u00FAng c\u1EE7a \u0111\u1ED9ng t\u1EEB theo g\u1EE3i \u00FD trong ngo\u1EB7c" })] }))] }), !isReviewMode && questionData.caseSensitive && (_jsx("div", { className: "overflow-hidden rounded-xl border border-yellow-200 bg-gradient-to-r from-yellow-50 to-amber-50 shadow-sm", children: _jsxs("div", { className: "flex items-center gap-2 px-3 py-2", children: [_jsx("div", { className: "flex h-7 w-7 flex-shrink-0 items-center justify-center rounded-md bg-gradient-to-br from-yellow-400 to-amber-500 text-white", children: _jsx(AlertTriangle, { className: "h-3.5 w-3.5" }) }), _jsxs("div", { children: [_jsx("h3", { className: "text-sm font-semibold text-yellow-900", children: "L\u01B0u \u00FD quan tr\u1ECDng" }), _jsx("p", { className: "text-sm text-yellow-700", children: "\u0110\u00E1p \u00E1n c\u00F3 ph\u00E2n bi\u1EC7t ch\u1EEF hoa/th\u01B0\u1EDDng" })] })] }) }))] }));
|
|
61
64
|
}
|
|
@@ -13,9 +13,6 @@ function readCrossOutAnswers(value) {
|
|
|
13
13
|
}
|
|
14
14
|
export function CrossOutWordGroupViewer({ question, isReviewMode = false, userAnswer, onSubmitAnswer, }) {
|
|
15
15
|
const questionData = mapQuestionToCrossOutWordGroupData(question);
|
|
16
|
-
const
|
|
17
|
-
const userAnswers = isReviewMode && !storedAnswers
|
|
18
|
-
? questionData.items.map((item) => item.correctAnswer)
|
|
19
|
-
: storedAnswers;
|
|
16
|
+
const userAnswers = readCrossOutAnswers(userAnswer);
|
|
20
17
|
return (_jsx(CrossOutWordGroupClient, { questionData: questionData, isReviewMode: isReviewMode, userAnswers: userAnswers, onAnswerChange: isReviewMode ? undefined : onSubmitAnswer }));
|
|
21
18
|
}
|
|
@@ -3,8 +3,12 @@ import type { FillInBlankGroupStudentItemAnswer } from '../_shared/types/fill-in
|
|
|
3
3
|
type FillInBlankGroupViewerProps = {
|
|
4
4
|
question: Question;
|
|
5
5
|
isReviewMode?: boolean;
|
|
6
|
-
userAnswer?: FillInBlankGroupStudentItemAnswer[]
|
|
7
|
-
|
|
6
|
+
userAnswer?: FillInBlankGroupStudentItemAnswer[] | {
|
|
7
|
+
answer?: FillInBlankGroupStudentItemAnswer[];
|
|
8
|
+
};
|
|
9
|
+
onSubmitAnswer?: (answer: {
|
|
10
|
+
answer: FillInBlankGroupStudentItemAnswer[];
|
|
11
|
+
} | FillInBlankGroupStudentItemAnswer[]) => void;
|
|
8
12
|
};
|
|
9
13
|
export declare function FillInBlankGroupViewer({ question, isReviewMode, userAnswer, onSubmitAnswer, }: FillInBlankGroupViewerProps): import("react").JSX.Element;
|
|
10
14
|
export {};
|
|
@@ -2,13 +2,19 @@
|
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
3
|
import { FillInBlankGroupClient } from '../types/fill-in-blank-group/FillInBlankGroupClient';
|
|
4
4
|
import { mapQuestionToFillInBlankGroupData } from '../types/fill-in-blank-group/map-fill-in-blank-group-data';
|
|
5
|
+
function readFillInBlankAnswers(value) {
|
|
6
|
+
if (Array.isArray(value)) {
|
|
7
|
+
return value;
|
|
8
|
+
}
|
|
9
|
+
if (value && typeof value === 'object' && Array.isArray(value.answer)) {
|
|
10
|
+
return value.answer;
|
|
11
|
+
}
|
|
12
|
+
return undefined;
|
|
13
|
+
}
|
|
5
14
|
export function FillInBlankGroupViewer({ question, isReviewMode = false, userAnswer, onSubmitAnswer, }) {
|
|
6
15
|
const questionData = mapQuestionToFillInBlankGroupData(question);
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
}))
|
|
12
|
-
: studentAnswers;
|
|
13
|
-
return (_jsx(FillInBlankGroupClient, { questionData: questionData, isReviewMode: isReviewMode, userAnswers: reviewAnswers, onAnswerChange: isReviewMode ? undefined : onSubmitAnswer }));
|
|
16
|
+
const userAnswers = readFillInBlankAnswers(userAnswer);
|
|
17
|
+
return (_jsx(FillInBlankGroupClient, { questionData: questionData, isReviewMode: isReviewMode, userAnswers: userAnswers, onAnswerChange: isReviewMode
|
|
18
|
+
? undefined
|
|
19
|
+
: (answers) => onSubmitAnswer?.({ answer: answers }) }));
|
|
14
20
|
}
|
|
@@ -484,7 +484,7 @@ export function QuestionViewer({ question, onSubmitAnswer, isReviewMode = false,
|
|
|
484
484
|
const answers = raw.answers && typeof raw.answers === 'object' && !Array.isArray(raw.answers)
|
|
485
485
|
? raw.answers
|
|
486
486
|
: raw;
|
|
487
|
-
return (_jsx(CrosswordPuzzleClient, { content: question.content, correctAnswer: question.correctAnswer ?? question.answer, isReviewMode: isReviewMode, userAnswers: answers, onAnswerChange: isReviewMode
|
|
487
|
+
return (_jsx(CrosswordPuzzleClient, { content: question.content, correctAnswer: question.correctAnswer ?? question.answer, isReviewMode: isReviewMode, questionNumber: question.questionNumber, userAnswers: answers, onAnswerChange: isReviewMode
|
|
488
488
|
? undefined
|
|
489
489
|
: (key, value) => {
|
|
490
490
|
onSubmitAnswer?.({ answers: { ...answers, [key]: value } });
|
|
@@ -521,7 +521,7 @@ export function QuestionViewer({ question, onSubmitAnswer, isReviewMode = false,
|
|
|
521
521
|
case 'SPONTANEOUS_QA':
|
|
522
522
|
return (_jsx(SpontaneousQAClient, { questionData: mapQuestionToSpontaneousQAData(question), isReviewMode: isReviewMode }));
|
|
523
523
|
case 'SPONTANEOUS_QA_GROUP':
|
|
524
|
-
return (_jsx(SpontaneousQaGroupViewer, { question: question, isReviewMode: isReviewMode }));
|
|
524
|
+
return (_jsx(SpontaneousQaGroupViewer, { question: question, isReviewMode: isReviewMode, userAnswer: userAnswer, gradeDetails: gradeDetails, disabled: !onSubmitAnswer }));
|
|
525
525
|
case 'WORD_ORDER_AND_MATCH_GROUP':
|
|
526
526
|
case 'WORD_ORDER_AND_MATCH':
|
|
527
527
|
return (_jsx(WordOrderAndMatchGroupViewer, { question: question, isReviewMode: isReviewMode, userAnswer: userAnswer, onSubmitAnswer: onSubmitAnswer }));
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import type { Question } from '../../../shared/types/entities/question.types';
|
|
2
|
-
|
|
2
|
+
import type { SpontaneousQaGroupGradeDetail } from '../_shared/types/spontaneous-qa-group.type';
|
|
3
|
+
export declare function SpontaneousQaGroupViewer({ question, isReviewMode, userAnswer, onItemRecorded, uploadingIndex, gradeDetails, disabled, }: {
|
|
3
4
|
question: Question;
|
|
4
5
|
isReviewMode?: boolean;
|
|
6
|
+
userAnswer?: unknown;
|
|
7
|
+
onItemRecorded?: (flattenIndex: number, blob: Blob) => void;
|
|
8
|
+
uploadingIndex?: number | null;
|
|
9
|
+
gradeDetails?: SpontaneousQaGroupGradeDetail[];
|
|
10
|
+
disabled?: boolean;
|
|
5
11
|
}): import("react").JSX.Element;
|
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
3
|
import { SpontaneousQaGroupClient } from '../types/spontaneous-qa-group/SpontaneousQaGroupClient';
|
|
4
4
|
import { mapQuestionToSpontaneousQaGroupData } from '../types/spontaneous-qa-group/map-spontaneous-qa-group-data';
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
function readStudentAnswer(value) {
|
|
6
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
7
|
+
return undefined;
|
|
8
|
+
}
|
|
9
|
+
const keys = value.audioKeys;
|
|
10
|
+
if (!Array.isArray(keys)) {
|
|
11
|
+
return undefined;
|
|
12
|
+
}
|
|
13
|
+
return {
|
|
14
|
+
audioKeys: keys.map((key) => (typeof key === 'string' && key.trim() !== '' ? key : null)),
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export function SpontaneousQaGroupViewer({ question, isReviewMode = false, userAnswer, onItemRecorded, uploadingIndex, gradeDetails, disabled, }) {
|
|
18
|
+
return (_jsx(SpontaneousQaGroupClient, { questionData: mapQuestionToSpontaneousQaGroupData(question), isReviewMode: isReviewMode, userAnswer: readStudentAnswer(userAnswer), onItemRecorded: isReviewMode ? undefined : onItemRecorded, uploadingIndex: uploadingIndex, gradeDetails: isReviewMode ? undefined : gradeDetails, disabled: disabled }));
|
|
7
19
|
}
|
|
@@ -218,7 +218,7 @@ export function renderMoversReviewBody(ctx) {
|
|
|
218
218
|
case 'CROSSWORD_PUZZLE': {
|
|
219
219
|
const data = transformCrosswordPuzzle(apiQuestions);
|
|
220
220
|
const firstQuestion = apiQuestions[0];
|
|
221
|
-
return (_jsx(MoversCrosswordPuzzleRenderer, { partNumber: partNo, partName: part.name, instruction: part.instructions || data.instruction, questionCount: questionCount, title: data.title, gridSize: data.gridSize, clues: data.clues, answers: answers, correctAnswers: data.correctAnswers, onAnswerChange: handleAnswerChange, isReviewMode: true, caseSensitive: firstQuestion?.correct_answer?.caseSensitive || false }));
|
|
221
|
+
return (_jsx(MoversCrosswordPuzzleRenderer, { partNumber: partNo, partName: part.name, instruction: part.instructions || data.instruction, questionCount: questionCount, title: data.title, gridSize: data.gridSize, markedColumn: data.markedColumn, hiddenWord: data.hiddenWord, clues: data.clues, answers: answers, correctAnswers: data.correctAnswers, onAnswerChange: handleAnswerChange, isReviewMode: true, caseSensitive: data.caseSensitive || firstQuestion?.correct_answer?.caseSensitive || false }));
|
|
222
222
|
}
|
|
223
223
|
default:
|
|
224
224
|
return null;
|
|
@@ -140,13 +140,20 @@ function addCorrectAnswerMaps(result, answer) {
|
|
|
140
140
|
texts.forEach((text, index) => { result.correctAnswerTextMap[`${answer.questionId}-blank-${index}`] = text; });
|
|
141
141
|
if (answer.questionType === 'LABEL_THE_PICTURE' && isRecord(correct.labels))
|
|
142
142
|
Object.entries(correct.labels).forEach(([id, value]) => { result.correctAnswerTextMap[id] = String(value); });
|
|
143
|
-
if (
|
|
143
|
+
if (answer.questionType === 'FILL_MISSING_WORDS_IN_GRID' && isRecord(correct.cells || correct.answers)) {
|
|
144
144
|
const rawCells = (correct.cells || correct.answers);
|
|
145
145
|
result.cellAnswersMap[answer.questionId] = {
|
|
146
146
|
cells: Object.fromEntries(Object.entries(rawCells).map(([k, v]) => [k, { correctAnswer: String(v) }])),
|
|
147
147
|
caseSensitive: correct.caseSensitive === true,
|
|
148
148
|
};
|
|
149
149
|
}
|
|
150
|
+
if (answer.questionType === 'CROSSWORD_PUZZLE' && isRecord(correct.cells)) {
|
|
151
|
+
const rawCells = correct.cells;
|
|
152
|
+
result.cellAnswersMap[answer.questionId] = {
|
|
153
|
+
cells: Object.fromEntries(Object.entries(rawCells).map(([k, v]) => [k, { correctAnswer: String(v) }])),
|
|
154
|
+
caseSensitive: correct.caseSensitive === true,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
150
157
|
}
|
|
151
158
|
function transformStudentAnswer(questionId, questionType, value) {
|
|
152
159
|
if (value == null)
|
|
@@ -537,18 +537,27 @@ const reverseCrossOutWordGroup = (apiQuestion) => {
|
|
|
537
537
|
const fallback = asRecord(fallbackAnswers[index]);
|
|
538
538
|
const answerRaw = Object.keys(itemAnswer).length > 0 ? itemAnswer : fallback;
|
|
539
539
|
const isExample = item.isExample === true;
|
|
540
|
-
const type = itemContent.type === 'CROSS_OUT_AND_REPLACE'
|
|
541
|
-
answerRaw.type === 'CROSS_OUT_AND_REPLACE'
|
|
540
|
+
const type = itemContent.type === 'CROSS_OUT_AND_REPLACE'
|
|
542
541
|
? 'CROSS_OUT_AND_REPLACE'
|
|
543
|
-
: 'CROSS_OUT'
|
|
542
|
+
: itemContent.type === 'CROSS_OUT'
|
|
543
|
+
? 'CROSS_OUT'
|
|
544
|
+
: answerRaw.type === 'CROSS_OUT_AND_REPLACE'
|
|
545
|
+
? 'CROSS_OUT_AND_REPLACE'
|
|
546
|
+
: 'CROSS_OUT';
|
|
547
|
+
const rawIndex = answerRaw.wordIndex;
|
|
548
|
+
const wordIndex = typeof rawIndex === 'number' && Number.isInteger(rawIndex) && rawIndex >= 0
|
|
549
|
+
? rawIndex
|
|
550
|
+
: null;
|
|
544
551
|
const correctAnswer = type === 'CROSS_OUT_AND_REPLACE'
|
|
545
552
|
? {
|
|
546
553
|
type: 'CROSS_OUT_AND_REPLACE',
|
|
554
|
+
wordIndex,
|
|
547
555
|
crossOut: asString(answerRaw.crossOut),
|
|
548
556
|
replaceWith: asString(answerRaw.replaceWith),
|
|
549
557
|
}
|
|
550
558
|
: {
|
|
551
559
|
type: 'CROSS_OUT',
|
|
560
|
+
wordIndex,
|
|
552
561
|
crossOut: asString(answerRaw.crossOut),
|
|
553
562
|
};
|
|
554
563
|
return {
|
|
@@ -638,12 +647,14 @@ const reverseSpontaneousQaGroup = (apiQuestion) => {
|
|
|
638
647
|
const fallback = asRecord(flattenedAnswers[questionIndex]);
|
|
639
648
|
const expected = asArray(childAnswer.expectedAnswers ?? fallback.expectedAnswers)
|
|
640
649
|
.filter((value) => typeof value === 'string');
|
|
650
|
+
const isExample = child.isExample === true;
|
|
641
651
|
return {
|
|
642
652
|
questionText: asString(childContent.questionText),
|
|
643
653
|
inputType: asString(childContent.inputType, 'TEXT') === 'AUDIO' ? 'AUDIO' : 'TEXT',
|
|
644
654
|
audioUrl: asString(childContent.audioUrl),
|
|
645
655
|
expectedAnswers: expected.length > 0 ? expected : [''],
|
|
646
|
-
|
|
656
|
+
isExample,
|
|
657
|
+
points: isExample ? 0 : asNumber(child.points, 1),
|
|
647
658
|
questionNumber: asNumber(child.questionNumber, questionIndex + 1),
|
|
648
659
|
};
|
|
649
660
|
}),
|
|
@@ -890,6 +901,9 @@ function reverseCrosswordPuzzle(apiQuestion) {
|
|
|
890
901
|
const rows = apiContent.gridSize?.rows || apiContent.rows || 12;
|
|
891
902
|
const cols = apiContent.gridSize?.cols || apiContent.cols || 12;
|
|
892
903
|
const title = apiContent.title || '';
|
|
904
|
+
const type = apiContent.type === 'HIDDEN_WORD' ? 'HIDDEN_WORD' : 'STANDARD';
|
|
905
|
+
const instruction = asString(apiQuestion.instruction, asString(apiContent.instruction));
|
|
906
|
+
const markedColumn = typeof apiContent.markedColumn === 'number' ? apiContent.markedColumn : undefined;
|
|
893
907
|
const across = Array.isArray(apiContent.clues?.across) ? apiContent.clues.across : [];
|
|
894
908
|
const down = Array.isArray(apiContent.clues?.down) ? apiContent.clues.down : [];
|
|
895
909
|
const answers = apiAnswer.answers || {};
|
|
@@ -904,6 +918,7 @@ function reverseCrosswordPuzzle(apiQuestion) {
|
|
|
904
918
|
length: a.length || 1,
|
|
905
919
|
direction: 'across',
|
|
906
920
|
answer: answers[`across-${a.number}`] || '',
|
|
921
|
+
...(a.isExample === true ? { isExample: true } : {}),
|
|
907
922
|
});
|
|
908
923
|
}
|
|
909
924
|
for (const d of down) {
|
|
@@ -916,24 +931,32 @@ function reverseCrosswordPuzzle(apiQuestion) {
|
|
|
916
931
|
length: d.length || 1,
|
|
917
932
|
direction: 'down',
|
|
918
933
|
answer: answers[`down-${d.number}`] || '',
|
|
934
|
+
...(d.isExample === true ? { isExample: true } : {}),
|
|
919
935
|
});
|
|
920
936
|
}
|
|
921
937
|
return {
|
|
922
938
|
content: {
|
|
939
|
+
type,
|
|
923
940
|
title,
|
|
941
|
+
instruction,
|
|
924
942
|
rows,
|
|
925
943
|
cols,
|
|
926
944
|
gridSize: { rows, cols },
|
|
945
|
+
...(type === 'HIDDEN_WORD' && markedColumn ? { markedColumn } : {}),
|
|
927
946
|
clues: { across, down },
|
|
928
947
|
},
|
|
929
948
|
answer: {
|
|
949
|
+
type,
|
|
930
950
|
title,
|
|
951
|
+
instruction,
|
|
931
952
|
rows,
|
|
932
953
|
cols,
|
|
954
|
+
...(type === 'HIDDEN_WORD' && markedColumn ? { markedColumn } : {}),
|
|
955
|
+
hiddenWord: answers['hidden-word'] || '',
|
|
933
956
|
clues,
|
|
934
957
|
answers,
|
|
935
958
|
caseSensitive: apiAnswer.caseSensitive || false,
|
|
936
|
-
explanation: apiQuestion.explanation
|
|
959
|
+
explanation: asString(apiQuestion.explanation),
|
|
937
960
|
},
|
|
938
961
|
};
|
|
939
962
|
}
|
|
@@ -944,6 +967,7 @@ function reverseFindWordsInMatrix(apiQuestion) {
|
|
|
944
967
|
const rows = apiContent.gridSize?.rows || (Array.isArray(apiContent.grid) ? apiContent.grid.length : 14);
|
|
945
968
|
const cols = apiContent.gridSize?.cols || (Array.isArray(apiContent.grid?.[0]) ? apiContent.grid[0].length : 14);
|
|
946
969
|
const title = apiContent.title || '';
|
|
970
|
+
const instruction = typeof apiContent.instruction === 'string' ? apiContent.instruction : '';
|
|
947
971
|
const grid = Array.isArray(apiContent.grid) ? apiContent.grid : [];
|
|
948
972
|
const categories = Array.isArray(apiContent.categories) ? apiContent.categories : [];
|
|
949
973
|
const wordBank = Array.isArray(apiContent.wordBank) ? apiContent.wordBank : [];
|
|
@@ -977,6 +1001,7 @@ function reverseFindWordsInMatrix(apiQuestion) {
|
|
|
977
1001
|
content: {
|
|
978
1002
|
type,
|
|
979
1003
|
title,
|
|
1004
|
+
instruction,
|
|
980
1005
|
rows,
|
|
981
1006
|
cols,
|
|
982
1007
|
gridSize: { rows, cols },
|
|
@@ -989,6 +1014,7 @@ function reverseFindWordsInMatrix(apiQuestion) {
|
|
|
989
1014
|
answer: {
|
|
990
1015
|
type,
|
|
991
1016
|
title,
|
|
1017
|
+
instruction,
|
|
992
1018
|
rows,
|
|
993
1019
|
cols,
|
|
994
1020
|
grid,
|