@tinyweb_dev/oe-exam-sdk 0.2.5 → 0.2.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/exams/take/ExamTakingPageContainer.js +6 -0
- package/dist/components/exams/take/components/QuestionRenderer.js +10 -2
- package/dist/components/exams/take/components/question-renderers/MoversFillInBlankGroupRenderer.d.ts +22 -0
- package/dist/components/exams/take/components/question-renderers/MoversFillInBlankGroupRenderer.js +60 -0
- package/dist/components/exams/take/components/question-renderers/MoversListenAndWriteRenderer.d.ts +2 -2
- package/dist/components/exams/take/components/question-renderers/MoversListenAndWriteRenderer.js +58 -14
- package/dist/components/exams/take/components/question-renderers/MoversMatchingWithLinesGroupRenderer.d.ts +13 -0
- package/dist/components/exams/take/components/question-renderers/MoversMatchingWithLinesGroupRenderer.js +31 -0
- package/dist/components/exams/take/components/question-renderers/index.d.ts +2 -0
- package/dist/components/exams/take/components/question-renderers/index.js +2 -0
- package/dist/components/exams/take/types.d.ts +12 -5
- package/dist/components/exams/take/utils/answer-transformers.js +17 -1
- package/dist/components/exams/take/utils/question-transformers.d.ts +45 -1
- package/dist/components/exams/take/utils/question-transformers.js +109 -53
- package/dist/components/questions/_shared/config/question-types.config.js +12 -0
- package/dist/components/questions/_shared/types/fill-in-blank-group.type.d.ts +50 -0
- package/dist/components/questions/_shared/types/fill-in-blank-group.type.js +2 -0
- package/dist/components/questions/_shared/types/fill-in-blank.type.d.ts +16 -1
- 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/matching-with-lines-group.type.d.ts +40 -0
- package/dist/components/questions/_shared/types/matching-with-lines-group.type.js +1 -0
- package/dist/components/questions/components/QuestionSearchDropdown.js +4 -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/fill-in-blank/FillInBlankClient.d.ts +1 -1
- package/dist/components/questions/types/fill-in-blank/FillInBlankClient.js +37 -60
- package/dist/components/questions/types/fill-in-blank/FillInBlankCreator.js +122 -149
- package/dist/components/questions/types/fill-in-blank/answer-utils.d.ts +34 -0
- package/dist/components/questions/types/fill-in-blank/answer-utils.js +58 -0
- package/dist/components/questions/types/fill-in-blank/register.js +19 -6
- package/dist/components/questions/types/fill-in-blank/transform.js +23 -26
- package/dist/components/questions/types/fill-in-blank-group/FillInBlankGroupClient.d.ts +2 -0
- package/dist/components/questions/types/fill-in-blank-group/FillInBlankGroupClient.js +64 -0
- package/dist/components/questions/types/fill-in-blank-group/FillInBlankGroupCreator.d.ts +2 -0
- package/dist/components/questions/types/fill-in-blank-group/FillInBlankGroupCreator.js +158 -0
- package/dist/components/questions/types/fill-in-blank-group/FillInBlankGroupMediaField.d.ts +7 -0
- package/dist/components/questions/types/fill-in-blank-group/FillInBlankGroupMediaField.js +30 -0
- package/dist/components/questions/types/fill-in-blank-group/blank-utils.d.ts +20 -0
- package/dist/components/questions/types/fill-in-blank-group/blank-utils.js +99 -0
- package/dist/components/questions/types/fill-in-blank-group/index.d.ts +5 -0
- package/dist/components/questions/types/fill-in-blank-group/index.js +5 -0
- package/dist/components/questions/types/fill-in-blank-group/map-fill-in-blank-group-data.d.ts +4 -0
- package/dist/components/questions/types/fill-in-blank-group/map-fill-in-blank-group-data.js +102 -0
- package/dist/components/questions/types/fill-in-blank-group/register.d.ts +3 -0
- package/dist/components/questions/types/fill-in-blank-group/register.js +37 -0
- package/dist/components/questions/types/fill-in-blank-group/transform.d.ts +2 -0
- package/dist/components/questions/types/fill-in-blank-group/transform.js +68 -0
- package/dist/components/questions/types/matching-with-lines-group/MatchingWithLinesBoard.d.ts +20 -0
- package/dist/components/questions/types/matching-with-lines-group/MatchingWithLinesBoard.js +109 -0
- package/dist/components/questions/types/matching-with-lines-group/MatchingWithLinesGroupClient.d.ts +2 -0
- package/dist/components/questions/types/matching-with-lines-group/MatchingWithLinesGroupClient.js +30 -0
- package/dist/components/questions/types/matching-with-lines-group/MatchingWithLinesGroupCreator.d.ts +2 -0
- package/dist/components/questions/types/matching-with-lines-group/MatchingWithLinesGroupCreator.js +180 -0
- package/dist/components/questions/types/matching-with-lines-group/MatchingWithLinesImageField.d.ts +6 -0
- package/dist/components/questions/types/matching-with-lines-group/MatchingWithLinesImageField.js +26 -0
- package/dist/components/questions/types/matching-with-lines-group/index.d.ts +4 -0
- package/dist/components/questions/types/matching-with-lines-group/index.js +4 -0
- package/dist/components/questions/types/matching-with-lines-group/map-matching-with-lines-group-data.d.ts +8 -0
- package/dist/components/questions/types/matching-with-lines-group/map-matching-with-lines-group-data.js +104 -0
- package/dist/components/questions/types/matching-with-lines-group/register.d.ts +3 -0
- package/dist/components/questions/types/matching-with-lines-group/register.js +37 -0
- package/dist/components/questions/types/matching-with-lines-group/transform.d.ts +2 -0
- package/dist/components/questions/types/matching-with-lines-group/transform.js +44 -0
- package/dist/components/questions/viewer/FillInBlankGroupViewer.d.ts +10 -0
- package/dist/components/questions/viewer/FillInBlankGroupViewer.js +14 -0
- package/dist/components/questions/viewer/MatchingWithLinesGroupViewer.d.ts +7 -0
- package/dist/components/questions/viewer/MatchingWithLinesGroupViewer.js +48 -0
- package/dist/components/questions/viewer/QuestionViewer.js +6 -0
- package/dist/components/questions/viewer/index.d.ts +2 -0
- package/dist/components/questions/viewer/index.js +2 -0
- package/dist/components/results/renderers/ReviewListenAndWriteRenderer.d.ts +2 -3
- package/dist/components/results/renderers/ReviewListenAndWriteRenderer.js +17 -8
- package/dist/components/results/renderers/review-question-body-dedicated.js +2 -2
- package/dist/components/results/renderers/review-question-body-movers.js +37 -2
- package/dist/components/results/review-data.js +37 -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/fill-in-blank.d.ts +46 -0
- package/dist/shared/lib/utils/fill-in-blank.js +96 -0
- package/dist/shared/lib/utils/question-reverse-transform.js +150 -3
- 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/fill-in-blank-group.d.ts +45 -0
- package/dist/shared/types/questions/fill-in-blank-group.js +1 -0
- package/dist/shared/types/questions/index.d.ts +2 -0
- package/dist/shared/types/questions/index.js +4 -0
- package/dist/shared/types/questions/matching-with-lines-group.d.ts +39 -0
- package/dist/shared/types/questions/matching-with-lines-group.js +1 -0
- package/package.json +1 -1
|
@@ -2,22 +2,11 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import { useState, useEffect, useMemo } from 'react';
|
|
4
4
|
import { Input } from '../../../../components/ui/input';
|
|
5
|
-
import { Check, X, PenLine, AlertTriangle, ClipboardList, Lightbulb, BookOpen,
|
|
5
|
+
import { Check, X, PenLine, AlertTriangle, ClipboardList, Lightbulb, BookOpen, Hash, Volume2, } from 'lucide-react';
|
|
6
|
+
import { extractPlaceholderIndices, formatBlankAnswersDisplay, getPrimaryBlankAnswer, isBlankInputCorrect, normalizeBlankAnswers, normalizeFillInBlankAnswers, } from './answer-utils';
|
|
7
|
+
import { parseFillBlankSegments } from '../../../../shared/lib/utils/fill-in-blank';
|
|
6
8
|
import { cn } from '../../../../shared/lib/utils';
|
|
7
9
|
import { usePresignedFileUrl } from '../../../../shared/lib/hooks';
|
|
8
|
-
// Helper: Extract placeholder indices from question text
|
|
9
|
-
function extractPlaceholderIndices(question) {
|
|
10
|
-
const regex = /\{(\d+)\}/g;
|
|
11
|
-
const indices = [];
|
|
12
|
-
let match;
|
|
13
|
-
while ((match = regex.exec(question)) !== null) {
|
|
14
|
-
const index = parseInt(match[1], 10);
|
|
15
|
-
if (!indices.includes(index)) {
|
|
16
|
-
indices.push(index);
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
return indices.sort((a, b) => a - b);
|
|
20
|
-
}
|
|
21
10
|
// Sub-component: Resolve presigned URL for group image
|
|
22
11
|
function GroupImage({ src, alt, className }) {
|
|
23
12
|
const { previewUrl, isLoading } = usePresignedFileUrl(src);
|
|
@@ -32,7 +21,7 @@ function GroupImage({ src, alt, className }) {
|
|
|
32
21
|
target.src = '/images/placeholder-image.svg';
|
|
33
22
|
} }));
|
|
34
23
|
}
|
|
35
|
-
export function FillInBlankClient({ questionData, onSubmit, isReviewMode = false, userAnswer = [], autoFillCorrectAnswers = false, explanation: explanationProp, }) {
|
|
24
|
+
export function FillInBlankClient({ questionData, questionConfig, onSubmit, isReviewMode = false, userAnswer = [], autoFillCorrectAnswers = false, explanation: explanationProp, }) {
|
|
36
25
|
// Detect if this is new format (question + answers) or legacy format (blanks array)
|
|
37
26
|
const isLegacyFormat = !questionData.question && questionData.blanks && questionData.blanks.length > 0;
|
|
38
27
|
const explanation = explanationProp || questionData.explanation;
|
|
@@ -47,37 +36,48 @@ export function FillInBlankClient({ questionData, onSubmit, isReviewMode = false
|
|
|
47
36
|
const { previewUrl: imagePreviewUrl, isLoading: isImageLoading } = usePresignedFileUrl(
|
|
48
37
|
// Only use question-level imageUrl when NOT grouped (group image is handled separately)
|
|
49
38
|
(!isGroup && questionData.imageUrl) ? questionData.imageUrl : '');
|
|
39
|
+
// Per-question audio — show when configured or when audioUrl already saved
|
|
40
|
+
const cleanAudioUrl = questionData.audioUrl?.trim() ? questionData.audioUrl : '';
|
|
41
|
+
const showAudio = Boolean(questionConfig?.includes('audio') || questionConfig?.includes('audioUrl') || cleanAudioUrl);
|
|
42
|
+
const { previewUrl: audioPreviewUrl } = usePresignedFileUrl(cleanAudioUrl || undefined);
|
|
50
43
|
// For new format: extract placeholders from question
|
|
51
44
|
const placeholderIndices = useMemo(() => {
|
|
52
45
|
if (isLegacyFormat)
|
|
53
46
|
return [];
|
|
54
47
|
return extractPlaceholderIndices(questionData.question || '');
|
|
55
48
|
}, [questionData.question, isLegacyFormat]);
|
|
56
|
-
//
|
|
49
|
+
// Canonical correct answers matrix (string[][]), supports legacy string[]
|
|
50
|
+
const correctAnswersMatrix = useMemo(() => {
|
|
51
|
+
if (isLegacyFormat && questionData.blanks) {
|
|
52
|
+
return normalizeFillInBlankAnswers(questionData.blanks.map((b) => b.correctAnswers ?? b.correctAnswer ?? ''));
|
|
53
|
+
}
|
|
54
|
+
return normalizeFillInBlankAnswers(questionData.answers || []);
|
|
55
|
+
}, [isLegacyFormat, questionData.answers, questionData.blanks]);
|
|
56
|
+
// Student answers are always flat string[] (one input per blank)
|
|
57
57
|
const getInitialAnswers = () => {
|
|
58
58
|
if (autoFillCorrectAnswers) {
|
|
59
59
|
if (isLegacyFormat && questionData.blanks) {
|
|
60
|
-
return questionData.blanks.map(b => b.correctAnswer);
|
|
60
|
+
return questionData.blanks.map((b) => getPrimaryBlankAnswer(b.correctAnswers ?? normalizeBlankAnswers(b.correctAnswer)));
|
|
61
61
|
}
|
|
62
|
-
return
|
|
62
|
+
return correctAnswersMatrix.map((alts) => getPrimaryBlankAnswer(alts));
|
|
63
63
|
}
|
|
64
64
|
return userAnswer || [];
|
|
65
65
|
};
|
|
66
|
-
const [answers, setAnswers] = useState(getInitialAnswers
|
|
66
|
+
const [answers, setAnswers] = useState(getInitialAnswers);
|
|
67
67
|
// Sync userAnswer when it changes
|
|
68
68
|
useEffect(() => {
|
|
69
69
|
if (autoFillCorrectAnswers) {
|
|
70
70
|
if (isLegacyFormat && questionData.blanks) {
|
|
71
|
-
setAnswers(questionData.blanks.map(b => b.correctAnswer));
|
|
71
|
+
setAnswers(questionData.blanks.map((b) => getPrimaryBlankAnswer(b.correctAnswers ?? normalizeBlankAnswers(b.correctAnswer))));
|
|
72
72
|
}
|
|
73
73
|
else {
|
|
74
|
-
setAnswers(
|
|
74
|
+
setAnswers(correctAnswersMatrix.map((alts) => getPrimaryBlankAnswer(alts)));
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
77
|
else if (userAnswer) {
|
|
78
78
|
setAnswers(userAnswer);
|
|
79
79
|
}
|
|
80
|
-
}, [userAnswer, autoFillCorrectAnswers, questionData, isLegacyFormat]);
|
|
80
|
+
}, [userAnswer, autoFillCorrectAnswers, questionData, isLegacyFormat, correctAnswersMatrix]);
|
|
81
81
|
// Handle answer change for specific placeholder index
|
|
82
82
|
const handleAnswerChange = (index, value) => {
|
|
83
83
|
if (isReviewMode)
|
|
@@ -90,26 +90,13 @@ export function FillInBlankClient({ questionData, onSubmit, isReviewMode = false
|
|
|
90
90
|
setAnswers(newAnswers);
|
|
91
91
|
onSubmit?.(newAnswers);
|
|
92
92
|
};
|
|
93
|
-
// Check if answer at index is correct
|
|
93
|
+
// Check if answer at index is correct against any equivalent
|
|
94
94
|
const isAnswerCorrect = (index, userInput) => {
|
|
95
|
-
let correctAnswer;
|
|
96
95
|
if (isLegacyFormat && questionData.blanks?.[index]) {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
else if (questionData.answers?.[index]) {
|
|
100
|
-
correctAnswer = questionData.answers[index];
|
|
101
|
-
}
|
|
102
|
-
else {
|
|
103
|
-
return false;
|
|
104
|
-
}
|
|
105
|
-
const answer = userInput.trim();
|
|
106
|
-
const correct = correctAnswer.trim();
|
|
107
|
-
if (questionData.caseSensitive) {
|
|
108
|
-
return answer === correct;
|
|
109
|
-
}
|
|
110
|
-
else {
|
|
111
|
-
return answer.toLowerCase() === correct.toLowerCase();
|
|
96
|
+
const blank = questionData.blanks[index];
|
|
97
|
+
return isBlankInputCorrect(userInput, blank.correctAnswers ?? normalizeBlankAnswers(blank.correctAnswer));
|
|
112
98
|
}
|
|
99
|
+
return isBlankInputCorrect(userInput, correctAnswersMatrix[index]);
|
|
113
100
|
};
|
|
114
101
|
// Calculate score
|
|
115
102
|
const calculateScore = () => {
|
|
@@ -145,12 +132,10 @@ export function FillInBlankClient({ questionData, onSubmit, isReviewMode = false
|
|
|
145
132
|
};
|
|
146
133
|
/** Render inline blanks within question text (new format) */
|
|
147
134
|
const renderQuestionWithBlanks = () => {
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
if (match) {
|
|
153
|
-
const placeholderIndex = parseInt(match[1], 10);
|
|
135
|
+
const segments = parseFillBlankSegments(questionData.question || '');
|
|
136
|
+
return (_jsx("div", { className: "flex flex-wrap items-baseline gap-x-1.5 gap-y-2 leading-loose text-[15px]", children: segments.map((segment, partIndex) => {
|
|
137
|
+
if (segment.type === 'blank') {
|
|
138
|
+
const placeholderIndex = segment.index;
|
|
154
139
|
const userInput = answers[placeholderIndex] || '';
|
|
155
140
|
const isCorrect = isReviewMode && isAnswerCorrect(placeholderIndex, userInput);
|
|
156
141
|
const isWrong = isReviewMode && userInput && !isCorrect;
|
|
@@ -162,9 +147,7 @@ export function FillInBlankClient({ questionData, onSubmit, isReviewMode = false
|
|
|
162
147
|
: 'border-gray-200 bg-gray-50 text-gray-500'
|
|
163
148
|
: 'border-orange-200 bg-white hover:border-orange-300 focus:border-orange-400 focus:ring-orange-200/50'), placeholder: "..." }), isReviewMode && (isCorrect || isWrong) && (_jsx("span", { className: cn('absolute -right-1.5 -top-1.5 flex h-4 w-4 items-center justify-center rounded-full shadow-sm ring-2 ring-white', isCorrect ? 'bg-emerald-500' : 'bg-red-500'), children: isCorrect ? (_jsx(Check, { className: "h-2.5 w-2.5 text-white" })) : (_jsx(X, { className: "h-2.5 w-2.5 text-white" })) }))] }, partIndex));
|
|
164
149
|
}
|
|
165
|
-
|
|
166
|
-
return (_jsx("span", { className: "text-slate-800 font-medium", children: part }, partIndex));
|
|
167
|
-
}
|
|
150
|
+
return (_jsx("span", { className: "text-slate-800 font-medium", children: segment.value }, partIndex));
|
|
168
151
|
}) }));
|
|
169
152
|
};
|
|
170
153
|
/** Legacy format: render blanks array */
|
|
@@ -195,12 +178,6 @@ export function FillInBlankClient({ questionData, onSubmit, isReviewMode = false
|
|
|
195
178
|
}
|
|
196
179
|
return (_jsxs("div", { className: "rounded-xl border border-violet-200 bg-gradient-to-r from-violet-50/80 to-purple-50/50 p-4", children: [_jsxs("div", { className: "flex items-center gap-2 mb-3", children: [_jsx("div", { className: "flex h-6 w-6 items-center justify-center rounded-lg bg-violet-100 ring-1 ring-violet-200/60", children: _jsx(Lightbulb, { className: "h-3.5 w-3.5 text-violet-600" }) }), _jsx("span", { className: "text-sm font-semibold text-violet-800", children: "G\u1EE3i \u00FD \u0111\u00E1p \u00E1n" })] }), _jsx("div", { className: "flex flex-wrap gap-2", children: questionData.hints.map((hint, index) => (_jsx("span", { className: "inline-flex items-center rounded-full border border-violet-200 bg-white px-3 py-1 text-sm font-medium text-violet-700 shadow-sm transition-all hover:border-violet-300 hover:bg-violet-50 hover:shadow", children: hint }, index))) })] }));
|
|
197
180
|
};
|
|
198
|
-
/** Case-sensitivity warning */
|
|
199
|
-
const renderCaseSensitiveNote = () => {
|
|
200
|
-
if (isReviewMode || !questionData.caseSensitive)
|
|
201
|
-
return null;
|
|
202
|
-
return (_jsxs("div", { className: "flex items-center gap-3 rounded-xl border border-amber-200 bg-gradient-to-r from-amber-50 to-yellow-50/50 px-4 py-3", children: [_jsx("div", { className: "flex h-7 w-7 flex-shrink-0 items-center justify-center rounded-lg bg-gradient-to-br from-amber-400 to-orange-500 text-white shadow-sm", children: _jsx(Type, { className: "h-3.5 w-3.5" }) }), _jsxs("div", { children: [_jsx("span", { className: "text-sm font-semibold text-amber-800", children: "Ph\u00E2n bi\u1EC7t hoa/th\u01B0\u1EDDng" }), _jsx("span", { className: "text-sm text-amber-700 ml-1", children: "\u2013 Vui l\u00F2ng nh\u1EADp \u0111\u00FAng ch\u1EEF hoa/th\u01B0\u1EDDng" })] })] }));
|
|
203
|
-
};
|
|
204
181
|
/** Explanation card (review mode only) */
|
|
205
182
|
const renderExplanation = () => {
|
|
206
183
|
const explText = explanation || questionData.explanation;
|
|
@@ -231,9 +208,9 @@ export function FillInBlankClient({ questionData, onSubmit, isReviewMode = false
|
|
|
231
208
|
? 'bg-emerald-100 text-emerald-700 ring-1 ring-emerald-200/60'
|
|
232
209
|
: score.correct >= score.total / 2
|
|
233
210
|
? 'bg-yellow-100 text-yellow-700 ring-1 ring-yellow-200/60'
|
|
234
|
-
: 'bg-red-100 text-red-700 ring-1 ring-red-200/60'), children: [score.correct === score.total && _jsx(Check, { className: "h-3 w-3" }), score.correct, "/", score.total, " \u0111\u00FAng"] }))] }), _jsxs("div", { className: "p-5 space-y-4", children: [_jsx("div", { className: "rounded-xl bg-gradient-to-r from-orange-50/80 to-amber-50/50 p-5 border border-orange-100/80", children: renderQuestionWithBlanks() }), renderQuestionImage(), renderHints(), isReviewMode && (_jsxs("div", { className: "rounded-xl border border-gray-200 bg-white overflow-hidden shadow-sm", children: [_jsxs("div", { className: "flex items-center gap-2 border-b border-gray-100 bg-gradient-to-r from-slate-50 to-gray-50 px-4 py-2.5", children: [_jsx("div", { className: "flex h-5 w-5 items-center justify-center rounded-full bg-slate-500 text-white", children: _jsx(ClipboardList, { className: "h-3 w-3" }) }), _jsx("h4", { className: "text-xs font-semibold text-gray-600 uppercase tracking-wider", children: "Chi ti\u1EBFt \u0111\u00E1p \u00E1n" })] }), _jsx("div", { className: "p-4 space-y-2.5", children: placeholderIndices.map((idx) => {
|
|
211
|
+
: 'bg-red-100 text-red-700 ring-1 ring-red-200/60'), children: [score.correct === score.total && _jsx(Check, { className: "h-3 w-3" }), score.correct, "/", score.total, " \u0111\u00FAng"] }))] }), _jsxs("div", { className: "p-5 space-y-4", children: [showAudio && cleanAudioUrl && (_jsxs("div", { className: "flex items-center gap-3 rounded-lg border border-indigo-100 bg-gradient-to-r from-indigo-50 to-blue-50 p-3", children: [_jsx("div", { className: "flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-lg bg-indigo-100", children: _jsx(Volume2, { className: "h-4 w-4 text-indigo-600" }) }), _jsx("audio", { controls: true, className: "h-9 w-full", src: audioPreviewUrl || cleanAudioUrl, preload: "metadata", children: "Tr\u00ECnh duy\u1EC7t kh\u00F4ng h\u1ED7 tr\u1EE3 audio." })] })), _jsx("div", { className: "rounded-xl bg-gradient-to-r from-orange-50/80 to-amber-50/50 p-5 border border-orange-100/80", children: renderQuestionWithBlanks() }), renderQuestionImage(), renderHints(), isReviewMode && (_jsxs("div", { className: "rounded-xl border border-gray-200 bg-white overflow-hidden shadow-sm", children: [_jsxs("div", { className: "flex items-center gap-2 border-b border-gray-100 bg-gradient-to-r from-slate-50 to-gray-50 px-4 py-2.5", children: [_jsx("div", { className: "flex h-5 w-5 items-center justify-center rounded-full bg-slate-500 text-white", children: _jsx(ClipboardList, { className: "h-3 w-3" }) }), _jsx("h4", { className: "text-xs font-semibold text-gray-600 uppercase tracking-wider", children: "Chi ti\u1EBFt \u0111\u00E1p \u00E1n" })] }), _jsx("div", { className: "p-4 space-y-2.5", children: placeholderIndices.map((idx) => {
|
|
235
212
|
const userInput = answers[idx] || '';
|
|
236
|
-
const correctAnswer =
|
|
213
|
+
const correctAnswer = formatBlankAnswersDisplay(correctAnswersMatrix[idx]) || '—';
|
|
237
214
|
const isCorrect = isAnswerCorrect(idx, userInput);
|
|
238
215
|
const hasInput = userInput.trim().length > 0;
|
|
239
216
|
return (_jsxs("div", { className: cn('rounded-lg border p-3 transition-colors', isCorrect
|
|
@@ -241,7 +218,7 @@ export function FillInBlankClient({ questionData, onSubmit, isReviewMode = false
|
|
|
241
218
|
: hasInput
|
|
242
219
|
? 'border-red-200 bg-red-50/50'
|
|
243
220
|
: 'border-gray-100 bg-gray-50/50'), children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "flex h-6 w-6 items-center justify-center rounded-md bg-orange-100 text-xs font-bold text-orange-600 ring-1 ring-orange-200/60", children: idx + 1 }), _jsxs("span", { className: "text-sm text-gray-500", children: ["Ch\u1ED7 tr\u1ED1ng ", idx + 1] })] }), _jsx("div", { className: cn('flex items-center gap-1 text-xs font-semibold', isCorrect ? 'text-emerald-600' : hasInput ? 'text-red-600' : 'text-gray-400'), children: isCorrect ? (_jsxs(_Fragment, { children: [_jsx(Check, { className: "h-3.5 w-3.5" }), " \u0110\u00FAng"] })) : hasInput ? (_jsxs(_Fragment, { children: [_jsx(X, { className: "h-3.5 w-3.5" }), " Sai"] })) : ('Bỏ trống') })] }), _jsxs("div", { className: "mt-2 grid grid-cols-2 gap-2 text-sm", children: [_jsxs("div", { className: "flex items-center justify-between rounded-md bg-white px-3 py-2 border border-gray-100", children: [_jsx("span", { className: "text-gray-500 text-xs", children: "\u0110\u00E3 nh\u1EADp" }), _jsx("span", { className: cn('font-semibold', isCorrect ? 'text-emerald-600' : hasInput ? 'text-red-600' : 'text-gray-400 italic'), children: userInput || '—' })] }), _jsxs("div", { className: "flex items-center justify-between rounded-md bg-emerald-50 px-3 py-2 border border-emerald-100", children: [_jsx("span", { className: "text-gray-500 text-xs", children: "\u0110\u00E1p \u00E1n" }), _jsx("span", { className: "font-semibold text-emerald-600", children: correctAnswer })] })] })] }, idx));
|
|
244
|
-
}) })] })), !isReviewMode && !hasAnyAnswer && (_jsxs("div", { className: "rounded-lg border border-amber-200 bg-gradient-to-r from-amber-50 to-yellow-50 p-3 flex items-center gap-2.5", children: [_jsx("div", { className: "flex h-5 w-5 flex-shrink-0 items-center justify-center rounded-full bg-amber-400 text-white", children: _jsx(AlertTriangle, { className: "h-3 w-3" }) }), _jsx("span", { className: "text-sm text-amber-800", children: "Vui l\u00F2ng \u0111i\u1EC1n c\u00E2u tr\u1EA3 l\u1EDDi v\u00E0o c\u00E1c ch\u1ED7 tr\u1ED1ng" })] }))] })] }),
|
|
221
|
+
}) })] })), !isReviewMode && !hasAnyAnswer && (_jsxs("div", { className: "rounded-lg border border-amber-200 bg-gradient-to-r from-amber-50 to-yellow-50 p-3 flex items-center gap-2.5", children: [_jsx("div", { className: "flex h-5 w-5 flex-shrink-0 items-center justify-center rounded-full bg-amber-400 text-white", children: _jsx(AlertTriangle, { className: "h-3 w-3" }) }), _jsx("span", { className: "text-sm text-amber-800", children: "Vui l\u00F2ng \u0111i\u1EC1n c\u00E2u tr\u1EA3 l\u1EDDi v\u00E0o c\u00E1c ch\u1ED7 tr\u1ED1ng" })] }))] })] }), renderExplanation()] }));
|
|
245
222
|
}
|
|
246
223
|
// =====================================================
|
|
247
224
|
// LEGACY FORMAT RENDER
|
|
@@ -252,14 +229,14 @@ export function FillInBlankClient({ questionData, onSubmit, isReviewMode = false
|
|
|
252
229
|
const hasAnswered = userInput.trim() !== '';
|
|
253
230
|
return (_jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm transition-all hover:shadow-md", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-gray-100 bg-gradient-to-r from-gray-50 to-white px-5 py-3", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "flex h-8 w-8 items-center justify-center rounded-lg bg-gradient-to-br from-orange-500 to-amber-600 text-white shadow-sm", children: _jsx(PenLine, { className: "h-4 w-4" }) }), _jsxs("span", { className: "text-sm font-semibold text-gray-700", children: ["C\u00E2u ", index + 1, ": \u0110i\u1EC1n v\u00E0o ch\u1ED7 tr\u1ED1ng"] })] }), isReviewMode && hasAnswered && (_jsxs("div", { className: cn('flex items-center gap-1.5 rounded-full px-3 py-1 text-xs font-bold', isCorrect
|
|
254
231
|
? 'bg-emerald-100 text-emerald-700 ring-1 ring-emerald-200/60'
|
|
255
|
-
: 'bg-red-100 text-red-700 ring-1 ring-red-200/60'), children: [isCorrect ? _jsx(Check, { className: "h-3.5 w-3.5" }) : _jsx(X, { className: "h-3.5 w-3.5" }), isCorrect ? 'Đúng' : 'Sai'] }))] }), _jsxs("div", { className: "p-5 space-y-4", children: [_jsx("div", { className: "rounded-xl bg-gradient-to-r from-orange-50/80 to-amber-50/50 p-5 border border-orange-100/80", children: renderLegacyBlank(blank.text, blank.id, index) }), index === 0 && renderQuestionImage(), index === 0 && renderHints(), isReviewMode && (_jsx("div", { className: cn('rounded-xl border p-3 transition-colors', isCorrect
|
|
232
|
+
: 'bg-red-100 text-red-700 ring-1 ring-red-200/60'), children: [isCorrect ? _jsx(Check, { className: "h-3.5 w-3.5" }) : _jsx(X, { className: "h-3.5 w-3.5" }), isCorrect ? 'Đúng' : 'Sai'] }))] }), _jsxs("div", { className: "p-5 space-y-4", children: [index === 0 && showAudio && cleanAudioUrl && (_jsxs("div", { className: "flex items-center gap-3 rounded-lg border border-indigo-100 bg-gradient-to-r from-indigo-50 to-blue-50 p-3", children: [_jsx("div", { className: "flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-lg bg-indigo-100", children: _jsx(Volume2, { className: "h-4 w-4 text-indigo-600" }) }), _jsx("audio", { controls: true, className: "h-9 w-full", src: audioPreviewUrl || cleanAudioUrl, preload: "metadata", children: "Tr\u00ECnh duy\u1EC7t kh\u00F4ng h\u1ED7 tr\u1EE3 audio." })] })), _jsx("div", { className: "rounded-xl bg-gradient-to-r from-orange-50/80 to-amber-50/50 p-5 border border-orange-100/80", children: renderLegacyBlank(blank.text, blank.id, index) }), index === 0 && renderQuestionImage(), index === 0 && renderHints(), isReviewMode && (_jsx("div", { className: cn('rounded-xl border p-3 transition-colors', isCorrect
|
|
256
233
|
? 'border-emerald-200 bg-emerald-50/50'
|
|
257
234
|
: hasAnswered
|
|
258
235
|
? 'border-red-200 bg-red-50/50'
|
|
259
|
-
: 'border-gray-100 bg-gray-50/50'), children: _jsxs("div", { className: "space-y-2.5", children: [_jsxs("div", { className: "flex items-center justify-between rounded-md bg-white p-3 border border-gray-100", children: [_jsx("span", { className: "text-sm text-gray-500", children: "\u0110\u00E3 nh\u1EADp:" }), _jsx("span", { className: cn('text-sm font-semibold', isCorrect ? 'text-emerald-600' : hasAnswered ? 'text-red-600' : 'text-gray-400 italic'), children: userInput || '—' })] }), _jsxs("div", { className: "flex items-center justify-between rounded-md bg-emerald-50 p-3 border border-emerald-100", children: [_jsx("span", { className: "text-sm text-gray-500", children: "\u0110\u00E1p \u00E1n \u0111\u00FAng:" }), _jsx("span", { className: "text-sm font-semibold text-emerald-600", children: blank.correctAnswer })] }), _jsxs("div", { className: cn('flex items-center justify-between rounded-md p-3 border', isCorrect
|
|
236
|
+
: 'border-gray-100 bg-gray-50/50'), children: _jsxs("div", { className: "space-y-2.5", children: [_jsxs("div", { className: "flex items-center justify-between rounded-md bg-white p-3 border border-gray-100", children: [_jsx("span", { className: "text-sm text-gray-500", children: "\u0110\u00E3 nh\u1EADp:" }), _jsx("span", { className: cn('text-sm font-semibold', isCorrect ? 'text-emerald-600' : hasAnswered ? 'text-red-600' : 'text-gray-400 italic'), children: userInput || '—' })] }), _jsxs("div", { className: "flex items-center justify-between rounded-md bg-emerald-50 p-3 border border-emerald-100", children: [_jsx("span", { className: "text-sm text-gray-500", children: "\u0110\u00E1p \u00E1n \u0111\u00FAng:" }), _jsx("span", { className: "text-sm font-semibold text-emerald-600", children: formatBlankAnswersDisplay(blank.correctAnswers ?? normalizeBlankAnswers(blank.correctAnswer)) || '—' })] }), _jsxs("div", { className: cn('flex items-center justify-between rounded-md p-3 border', isCorrect
|
|
260
237
|
? 'bg-emerald-50 border-emerald-200'
|
|
261
238
|
: 'bg-red-50 border-red-200'), children: [_jsx("span", { className: "text-sm text-gray-500", children: "K\u1EBFt qu\u1EA3:" }), _jsx("div", { className: cn('flex items-center gap-1.5 text-sm font-bold', isCorrect ? 'text-emerald-600' : 'text-red-600'), children: isCorrect ? (_jsxs(_Fragment, { children: [_jsx(Check, { className: "h-4 w-4" }), " Ch\u00EDnh x\u00E1c"] })) : (_jsxs(_Fragment, { children: [_jsx(X, { className: "h-4 w-4" }), " Sai"] })) })] })] }) })), !isReviewMode && !hasAnswered && (_jsxs("div", { className: "rounded-lg border border-amber-200 bg-gradient-to-r from-amber-50 to-yellow-50 p-3 flex items-center gap-2.5", children: [_jsx("div", { className: "flex h-5 w-5 flex-shrink-0 items-center justify-center rounded-full bg-amber-400 text-white", children: _jsx(AlertTriangle, { className: "h-3 w-3" }) }), _jsx("span", { className: "text-sm text-amber-800", children: "Vui l\u00F2ng \u0111i\u1EC1n c\u00E2u tr\u1EA3 l\u1EDDi" })] }))] })] }, blank.id));
|
|
262
|
-
}),
|
|
239
|
+
}), isReviewMode && score && (questionData.blanks?.length || 0) > 1 && (_jsx("div", { className: cn('overflow-hidden rounded-xl border shadow-sm', score.correct === score.total
|
|
263
240
|
? 'border-emerald-200 bg-gradient-to-r from-emerald-50 to-green-50'
|
|
264
241
|
: score.correct >= score.total / 2
|
|
265
242
|
? 'border-yellow-200 bg-gradient-to-r from-yellow-50 to-amber-50'
|