@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
package/dist/components/questions/types/word-fill-structured-form/WordFillStructuredFormClient.js
CHANGED
|
@@ -4,6 +4,7 @@ import React, { useState, useEffect, useMemo, useCallback } from 'react';
|
|
|
4
4
|
import { Check, X, FileText, Library } from 'lucide-react';
|
|
5
5
|
import { usePresignedFileUrl } from '../../../../shared/lib/hooks';
|
|
6
6
|
import { cn } from '../../../../shared/lib/utils';
|
|
7
|
+
import { KidInstruction, KidPrompt, KidQuestionCard } from '../../_shared/components/compact';
|
|
7
8
|
import { detectWfsfBankMode, isOptionsWordEntry, normalizeSharedWordBank, } from './wfsf-word-bank.utils';
|
|
8
9
|
import { expandWfsfCorrectAlts, formatWfsfCorrectLabel, isWfsfBlankCorrect, primaryWfsfAnswers, } from '../../../../shared/lib/utils/wfsf-blank-match';
|
|
9
10
|
function normalizeWfsfBlankStyle(raw) {
|
|
@@ -160,11 +161,11 @@ export function WordFillStructuredFormClient({ questionData, isReviewMode = fals
|
|
|
160
161
|
const blankOptions = optionsByKey.get(key);
|
|
161
162
|
if (bankMode === 'WITH_OPTIONS' && blankOptions && blankOptions.length >= 2) {
|
|
162
163
|
const isExpanded = expandedBlank === key;
|
|
163
|
-
return (_jsxs("span", { className: "relative mx-1 inline-flex flex-col items-center align-baseline", children: [_jsxs("button", { type: "button", onClick: () => !isReviewMode && setExpandedBlank(isExpanded ? null : key), disabled: isReviewMode, className: cn('inline-flex min-w-[90px] items-center justify-center rounded border
|
|
164
|
+
return (_jsxs("span", { className: "relative mx-1 inline-flex flex-col items-center align-baseline", children: [_jsxs("button", { type: "button", onClick: () => !isReviewMode && setExpandedBlank(isExpanded ? null : key), disabled: isReviewMode, className: cn('inline-flex min-w-[90px] items-center justify-center rounded-md border border-dashed px-3 py-1.5 text-center text-sm font-semibold transition-all', isReviewMode
|
|
164
165
|
? isCorrectFill
|
|
165
|
-
? 'border-
|
|
166
|
+
? 'border-green-400 bg-green-50 text-green-800'
|
|
166
167
|
: isWrongFill
|
|
167
|
-
? 'border-
|
|
168
|
+
? 'border-red-400 bg-red-50 text-red-800'
|
|
168
169
|
: 'border-amber-500 bg-amber-50 text-amber-800'
|
|
169
170
|
: 'border-teal-400 bg-teal-50/50 text-teal-800 hover:bg-teal-50'), children: [filled || `{${key}}`, isReviewMode && filled && (isCorrectFill
|
|
170
171
|
? _jsx(Check, { className: "ml-0.5 h-3.5 w-3.5 text-green-600" })
|
|
@@ -175,14 +176,14 @@ export function WordFillStructuredFormClient({ questionData, isReviewMode = fals
|
|
|
175
176
|
return (_jsxs("span", { className: "relative mx-1 inline-flex items-baseline align-baseline", children: [_jsx("span", { className: "absolute -top-4 left-1 text-[10px] font-semibold text-gray-400", children: key }), _jsx("input", { type: "text", value: filled, onChange: (e) => handleInputChange(key, e.target.value), disabled: isReviewMode, placeholder: "", maxLength: isLetterBlank
|
|
176
177
|
? Math.max(1, expandWfsfCorrectAlts(answers[key]).reduce((max, alt) => Math.max(max, alt.trim().length), 0) || 1)
|
|
177
178
|
: undefined, size: isLetterBlank ? undefined : isPhraseBlank ? 28 : 10, className: cn('inline-block bg-slate-50/40 text-center text-sm font-semibold outline-none transition-all align-baseline hover:bg-slate-100/60 focus:bg-teal-50/60 focus:ring-0', isLetterBlank
|
|
178
|
-
? 'h-7 w-7 rounded border
|
|
179
|
+
? 'h-7 w-7 rounded-md border px-0 uppercase'
|
|
179
180
|
: isPhraseBlank
|
|
180
|
-
? 'min-w-[16rem] max-w-[36rem] w-[28ch] rounded-
|
|
181
|
-
: 'min-w-[4.5rem] max-w-[8rem] w-[10ch] rounded-
|
|
181
|
+
? 'min-w-[16rem] max-w-[36rem] w-[28ch] rounded-md border px-3 py-1.5'
|
|
182
|
+
: 'min-w-[4.5rem] max-w-[8rem] w-[10ch] rounded-md border px-3 py-1.5', isReviewMode
|
|
182
183
|
? isCorrectFill
|
|
183
|
-
? 'border-
|
|
184
|
+
? 'border-green-400 bg-green-50 text-green-800'
|
|
184
185
|
: isWrongFill
|
|
185
|
-
? 'border-
|
|
186
|
+
? 'border-red-400 bg-red-50 text-red-800'
|
|
186
187
|
: 'border-amber-500 bg-amber-50 text-amber-800'
|
|
187
188
|
: 'border-slate-400 text-slate-800 focus:border-teal-600'), style: isLetterBlank
|
|
188
189
|
? {
|
|
@@ -277,12 +278,12 @@ export function WordFillStructuredFormClient({ questionData, isReviewMode = fals
|
|
|
277
278
|
.map((node, index) => renderNode(node, `root-${index}`))
|
|
278
279
|
.filter((node) => node !== null);
|
|
279
280
|
}, [content, renderBlank]);
|
|
280
|
-
return (
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
281
|
+
return (_jsxs("div", { className: "space-y-4", children: [_jsx(KidInstruction, { children: headerInstruction }), _jsxs(KidQuestionCard, { icon: _jsx(FileText, { className: "h-3.5 w-3.5" }), title: headerInstruction ? DEFAULT_WFSF_HEADER : headerLabel, headerExtra: typeof questionNumber === 'number' && questionNumber > 0 ? (_jsxs("span", { className: "text-[11px] font-semibold uppercase tracking-wider text-teal-700", children: ["B\u00E0i ", questionNumber] })) : undefined, review: isReviewMode && allFilled ? (isAllCorrect ? 'correct' : 'wrong') : null, children: [title && (_jsx(KidPrompt, { children: _jsx("h2", { className: "text-sm font-semibold text-gray-800", children: title }) })), allAudios.length > 0 && (_jsx("div", { className: "flex flex-col items-center gap-2", children: allAudios.map((audio, idx) => (_jsx(ClientAudioItem, { url: audio }, idx))) })), allImages.length > 0 && (_jsx("div", { className: "flex flex-wrap justify-center gap-3", children: allImages.map((img, idx) => (_jsx(ClientImageItem, { url: img, alt: title ? `${title} (${idx + 1})` : undefined }, idx))) })), bankMode === 'WITH_WORD_BANK' && sharedWords.length > 0 && (_jsxs("div", { className: "rounded-lg border border-purple-200 bg-gradient-to-r from-purple-50 to-pink-50 p-3", children: [_jsxs("div", { className: "mb-2 flex items-center gap-2", children: [_jsx(Library, { className: "h-4 w-4 text-purple-600" }), _jsx("span", { className: "text-xs font-semibold uppercase tracking-wider text-purple-700", children: "Word Bank" })] }), _jsx("div", { className: "flex flex-wrap gap-2", children: sharedWords.map((item, index) => {
|
|
282
|
+
const used = usedSharedWords.has(item.word.trim().toLowerCase());
|
|
283
|
+
return (_jsxs("span", { className: cn('rounded-full border px-3 py-1 text-sm font-medium', item.isExample
|
|
284
|
+
? 'border-amber-300 bg-amber-100 text-amber-800'
|
|
285
|
+
: used
|
|
286
|
+
? 'border-purple-200 bg-white/60 text-purple-400 line-through'
|
|
287
|
+
: 'border-purple-300 bg-white text-purple-800'), children: [item.word, item.isExample ? ' (ex)' : ''] }, `${item.word}-${index}`));
|
|
288
|
+
}) })] })), _jsx("div", { className: "structured-form-content space-y-2 rounded-lg border border-gray-200 bg-gradient-to-r from-gray-50 to-slate-50 p-5 text-base leading-loose text-gray-800", children: renderedContent }), isReviewMode && !isAllCorrect && (_jsxs("div", { className: "rounded-lg border border-green-200 bg-green-50 p-3", children: [_jsx("span", { className: "text-xs font-medium uppercase tracking-wider text-green-600", children: "\u0110\u00E1p \u00E1n \u0111\u00FAng" }), _jsx("div", { className: "mt-2 flex flex-wrap gap-2", children: expectedBlankKeys.map((key) => (_jsxs("span", { className: "rounded-lg border border-green-300 bg-green-100 px-3 py-1.5 text-sm font-semibold text-green-800", children: [`{${key}}`, ": ", formatWfsfCorrectLabel(answers[key])] }, key))) })] })), 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 uppercase tracking-wider text-blue-600", children: "Gi\u1EA3i th\u00EDch" }), _jsx("p", { className: "mt-1 text-sm text-blue-800", children: displayExplanation })] }))] })] }));
|
|
288
289
|
}
|
package/dist/components/questions/types/word-order-and-match-group/WordOrderAndMatchGroupClient.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useState, useEffect, useCallback } from 'react';
|
|
4
|
-
import {
|
|
4
|
+
import { X, RotateCcw } from 'lucide-react';
|
|
5
5
|
import { cn } from '../../../../shared/lib/utils';
|
|
6
|
+
import { KidInstruction, KidQuestionCard } from '../../_shared/components/compact';
|
|
6
7
|
export function WordOrderAndMatchGroupClient({ questionData, isReviewMode = false, userAnswers, onAnswerChange, }) {
|
|
7
8
|
const { instruction, matchOptions = ['A', 'B', 'C', 'D', 'E', 'F'], items = [] } = questionData;
|
|
8
9
|
// Initialize interactive state for each item
|
|
@@ -140,7 +141,7 @@ export function WordOrderAndMatchGroupClient({ questionData, isReviewMode = fals
|
|
|
140
141
|
return next;
|
|
141
142
|
});
|
|
142
143
|
}, [isReviewMode, items, onAnswerChange]);
|
|
143
|
-
return (_jsxs("div", { className: "w-full space-y-6", children: [_jsx(
|
|
144
|
+
return (_jsxs("div", { className: "w-full space-y-6", children: [_jsx(KidInstruction, { children: instruction || 'Put the useful phrases in order, then match them to parts A–F.' }), matchOptions.length > 0 && (_jsxs("div", { className: "flex flex-wrap items-center gap-1.5", children: [_jsx("span", { className: "text-xs font-semibold text-gray-500 mr-1", children: "Match Options:" }), matchOptions.map((opt) => (_jsx("span", { className: "inline-flex h-7 min-w-[28px] items-center justify-center rounded-lg border border-blue-200 bg-white px-2.5 text-xs font-bold text-blue-700 shadow-xs", children: opt }, opt)))] })), _jsx("div", { className: "space-y-4", children: items.map((item, itemIdx) => {
|
|
144
145
|
const state = itemStates[itemIdx] || { placedIndices: [], selectedLabel: '' };
|
|
145
146
|
const userAns = userAnswers?.[itemIdx];
|
|
146
147
|
const isExample = item.isExample === true;
|
|
@@ -151,40 +152,34 @@ export function WordOrderAndMatchGroupClient({ questionData, isReviewMode = fals
|
|
|
151
152
|
const isPhraseCorrect = normalizeStr(userPhrase) === normalizeStr(item.orderedPhrase || '');
|
|
152
153
|
const isLabelCorrect = (userLabel || '').trim().toUpperCase() === (item.matchLabel || '').trim().toUpperCase();
|
|
153
154
|
const isItemAllCorrect = isPhraseCorrect && isLabelCorrect;
|
|
154
|
-
return (_jsxs("div", { className: cn('
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
}) })), isReviewMode && !isExample && !isPhraseCorrect && (_jsxs("div", { className: "mt-2 rounded-lg bg-emerald-50 border border-emerald-200 p-2.5 text-xs text-emerald-800", children: [_jsx("span", { className: "font-bold", children: "C\u00E2u \u0111\u00FAng:" }), " ", item.orderedPhrase] }))] }), _jsxs("div", { className: "space-y-3 lg:col-span-4 xl:col-span-3 border-t lg:border-t-0 lg:border-l border-gray-100 pt-3 lg:pt-0 lg:pl-4", children: [_jsx("div", { className: "text-xs font-semibold text-gray-500 uppercase tracking-wider", children: "2. Ch\u1ECDn nh\u00E3n n\u1ED1i:" }), _jsx("div", { className: "flex flex-wrap gap-1.5", children: matchOptions.map((opt) => {
|
|
177
|
-
const isSelected = state.selectedLabel === opt;
|
|
178
|
-
const isCorrectOpt = (item.matchLabel || '').trim().toUpperCase() === opt;
|
|
179
|
-
return (_jsx("button", { type: "button", disabled: isReviewMode || isExample, onClick: () => handleLabelSelect(itemIdx, opt), className: cn('h-9 w-9 sm:h-10 sm:w-10 rounded-xl font-bold text-sm transition-all flex items-center justify-center', isReviewMode
|
|
180
|
-
? isCorrectOpt
|
|
181
|
-
? 'bg-emerald-600 text-white ring-2 ring-emerald-300'
|
|
182
|
-
: isSelected
|
|
183
|
-
? 'bg-rose-500 text-white line-through'
|
|
184
|
-
: 'bg-gray-100 text-gray-400 opacity-60'
|
|
155
|
+
return (_jsx(KidQuestionCard, { title: isExample ? 'Ví dụ (Example)' : `Câu ${item.questionNumber || itemIdx + 1}`, points: !isExample && typeof item.points === 'number' ? item.points : undefined, isExample: isExample, review: isReviewMode && !isExample ? (isItemAllCorrect ? 'correct' : 'wrong') : null, children: _jsxs("div", { className: "grid grid-cols-1 gap-4 lg:grid-cols-12", children: [_jsxs("div", { className: "space-y-3 lg:col-span-8 xl:col-span-9", children: [_jsx("div", { className: "text-xs font-semibold text-gray-500 uppercase tracking-wider", children: "1. S\u1EAFp x\u1EBFp c\u00E1c t\u1EEB th\u00E0nh c\u00E2u ho\u00E0n ch\u1EC9nh:" }), _jsxs("div", { className: cn('min-h-[52px] rounded-xl border-2 border-dashed p-2.5 transition-colors flex flex-wrap items-center gap-1.5', state.placedIndices.length > 0
|
|
156
|
+
? 'border-blue-300 bg-blue-50/40'
|
|
157
|
+
: 'border-gray-300 bg-gray-50/60'), children: [state.placedIndices.length === 0 ? (_jsx("span", { className: "text-xs italic text-gray-400 select-none", children: "Nh\u1EA5n v\u00E0o c\u00E1c t\u1EEB b\u00EAn d\u01B0\u1EDBi \u0111\u1EC3 gh\u00E9p c\u00E2u..." })) : (state.placedIndices.map((wordIdx, orderIdx) => {
|
|
158
|
+
const word = item.scrambledWords[wordIdx];
|
|
159
|
+
return (_jsxs("button", { type: "button", disabled: isReviewMode || isExample, onClick: () => handleWordClick(itemIdx, wordIdx), className: cn('group inline-flex items-center gap-1 rounded-lg px-2.5 py-1 text-xs sm:text-sm font-semibold transition-all shadow-xs', isExample
|
|
160
|
+
? 'bg-amber-100 text-amber-900 border border-amber-300 cursor-default'
|
|
161
|
+
: isReviewMode
|
|
162
|
+
? isPhraseCorrect
|
|
163
|
+
? 'border border-green-300 bg-green-50 text-green-800'
|
|
164
|
+
: 'border border-red-300 bg-red-50 text-red-800'
|
|
165
|
+
: 'cursor-pointer border border-blue-500 bg-blue-50 text-blue-800 ring-2 ring-blue-100 hover:bg-blue-100 active:scale-95'), children: [_jsx("span", { children: word }), !isReviewMode && !isExample && (_jsx(X, { className: "h-3 w-3 opacity-60 group-hover:opacity-100" }))] }, `placed-${wordIdx}-${orderIdx}`));
|
|
166
|
+
})), state.placedIndices.length > 0 && !isReviewMode && !isExample && (_jsx("button", { type: "button", onClick: () => handleResetItem(itemIdx), className: "ml-auto inline-flex items-center gap-1 rounded-md p-1 text-xs text-gray-400 hover:bg-white hover:text-gray-700", title: "\u0110\u1EB7t l\u1EA1i", children: _jsx(RotateCcw, { className: "h-3.5 w-3.5" }) }))] }), (!isExample || !isReviewMode) && (_jsx("div", { className: "flex flex-wrap items-center gap-1.5 pt-1", children: item.scrambledWords.map((word, wordIdx) => {
|
|
167
|
+
const isPlaced = state.placedIndices.includes(wordIdx);
|
|
168
|
+
return (_jsx("button", { type: "button", disabled: isReviewMode || isExample || isPlaced, onClick: () => handleWordClick(itemIdx, wordIdx), className: cn('rounded-lg border px-3 py-1 text-xs sm:text-sm font-medium transition-all', isPlaced
|
|
169
|
+
? 'border-dashed border-gray-200 bg-gray-100 text-gray-300 opacity-50 cursor-not-allowed'
|
|
170
|
+
: 'cursor-pointer border-gray-300 bg-white text-gray-800 shadow-xs hover:border-blue-400 hover:bg-blue-50/50 active:scale-95'), children: word }, `word-${wordIdx}`));
|
|
171
|
+
}) })), isReviewMode && !isExample && !isPhraseCorrect && (_jsxs("div", { className: "mt-2 rounded-lg bg-green-50 border border-green-200 p-2.5 text-xs text-green-800", children: [_jsx("span", { className: "font-bold", children: "C\u00E2u \u0111\u00FAng:" }), " ", item.orderedPhrase] }))] }), _jsxs("div", { className: "space-y-3 lg:col-span-4 xl:col-span-3 border-t lg:border-t-0 lg:border-l border-gray-100 pt-3 lg:pt-0 lg:pl-4", children: [_jsx("div", { className: "text-xs font-semibold text-gray-500 uppercase tracking-wider", children: "2. Ch\u1ECDn nh\u00E3n n\u1ED1i:" }), _jsx("div", { className: "flex flex-wrap gap-1.5", children: matchOptions.map((opt) => {
|
|
172
|
+
const isSelected = state.selectedLabel === opt;
|
|
173
|
+
const isCorrectOpt = (item.matchLabel || '').trim().toUpperCase() === opt;
|
|
174
|
+
return (_jsx("button", { type: "button", disabled: isReviewMode || isExample, onClick: () => handleLabelSelect(itemIdx, opt), className: cn('h-9 w-9 sm:h-10 sm:w-10 rounded-xl font-bold text-sm transition-all flex items-center justify-center', isReviewMode
|
|
175
|
+
? isCorrectOpt
|
|
176
|
+
? 'bg-green-600 text-white ring-2 ring-green-300'
|
|
185
177
|
: isSelected
|
|
186
|
-
? 'bg-
|
|
187
|
-
: '
|
|
188
|
-
|
|
178
|
+
? 'bg-red-500 text-white line-through'
|
|
179
|
+
: 'bg-gray-100 text-gray-400 opacity-60'
|
|
180
|
+
: isSelected
|
|
181
|
+
? 'scale-105 border border-blue-500 bg-blue-50 text-blue-800 shadow-md ring-2 ring-blue-100'
|
|
182
|
+
: 'cursor-pointer border border-gray-200 bg-white text-gray-700 hover:border-blue-300 hover:bg-blue-50/50'), children: opt }, `opt-${opt}`));
|
|
183
|
+
}) }), isReviewMode && !isExample && !isLabelCorrect && (_jsxs("div", { className: "text-xs text-green-800", children: [_jsx("span", { className: "font-bold", children: "Nh\u00E3n \u0111\u00FAng:" }), " [", item.matchLabel, "]"] }))] })] }) }, itemIdx));
|
|
189
184
|
}) })] }));
|
|
190
185
|
}
|
|
@@ -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
|
}
|
|
@@ -2,7 +2,8 @@ import type { Question } from '../../../shared/types/entities/question.types';
|
|
|
2
2
|
type AnswerTheQuestionGroupViewerProps = {
|
|
3
3
|
question: Question;
|
|
4
4
|
isReviewMode?: boolean;
|
|
5
|
-
userAnswer?:
|
|
5
|
+
userAnswer?: unknown;
|
|
6
|
+
onSubmitAnswer?: (answer: string[]) => void;
|
|
6
7
|
};
|
|
7
|
-
export declare function AnswerTheQuestionGroupViewer({ question, isReviewMode, userAnswer, }: AnswerTheQuestionGroupViewerProps): import("react").JSX.Element;
|
|
8
|
+
export declare function AnswerTheQuestionGroupViewer({ question, isReviewMode, userAnswer, onSubmitAnswer, }: AnswerTheQuestionGroupViewerProps): import("react").JSX.Element;
|
|
8
9
|
export {};
|
|
@@ -2,10 +2,17 @@
|
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
3
|
import { AnswerTheQuestionGroupClient } from '../types/answer-the-question-group/AnswerTheQuestionGroupClient';
|
|
4
4
|
import { mapQuestionToAnswerTheQuestionGroupData } from '../types/answer-the-question-group/map-answer-the-question-group-data';
|
|
5
|
-
|
|
5
|
+
function readStringAnswers(value) {
|
|
6
|
+
if (Array.isArray(value)) {
|
|
7
|
+
return value.map((item) => String(item ?? ''));
|
|
8
|
+
}
|
|
9
|
+
if (value && typeof value === 'object' && Array.isArray(value.answer)) {
|
|
10
|
+
return value.answer.map((item) => String(item ?? ''));
|
|
11
|
+
}
|
|
12
|
+
return undefined;
|
|
13
|
+
}
|
|
14
|
+
export function AnswerTheQuestionGroupViewer({ question, isReviewMode = false, userAnswer, onSubmitAnswer, }) {
|
|
6
15
|
const questionData = mapQuestionToAnswerTheQuestionGroupData(question);
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
: userAnswer;
|
|
10
|
-
return (_jsx(AnswerTheQuestionGroupClient, { questionData: questionData, isReviewMode: isReviewMode, userAnswers: reviewAnswers }));
|
|
16
|
+
const storedAnswers = readStringAnswers(userAnswer);
|
|
17
|
+
return (_jsx(AnswerTheQuestionGroupClient, { questionData: questionData, isReviewMode: isReviewMode, userAnswers: storedAnswers, onAnswerChange: isReviewMode ? undefined : onSubmitAnswer }));
|
|
11
18
|
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import type { Question } from '../../../shared/types/entities/question.types';
|
|
2
|
-
import type { ChooseThenAnswerStudentItemAnswer } from '../_shared/types/choose-then-answer-group.type';
|
|
3
|
-
export declare function ChooseThenAnswerGroupViewer({ question, isReviewMode, userAnswer, onSubmitAnswer, }: {
|
|
2
|
+
import type { ChooseThenAnswerGradeDetail, ChooseThenAnswerStudentItemAnswer } from '../_shared/types/choose-then-answer-group.type';
|
|
3
|
+
export declare function ChooseThenAnswerGroupViewer({ question, isReviewMode, userAnswer, gradeDetails, onSubmitAnswer, }: {
|
|
4
4
|
question: Question;
|
|
5
5
|
isReviewMode?: boolean;
|
|
6
6
|
userAnswer?: unknown;
|
|
7
|
+
gradeDetails?: ChooseThenAnswerGradeDetail[];
|
|
7
8
|
onSubmitAnswer?: (answer: {
|
|
8
9
|
answer: ChooseThenAnswerStudentItemAnswer[];
|
|
9
10
|
}) => void;
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
3
|
import { useState } from 'react';
|
|
4
4
|
import { ChooseThenAnswerGroupClient } from '../types/choose-then-answer-group/ChooseThenAnswerGroupClient';
|
|
5
|
-
import { mapQuestionToChooseThenAnswerGroupData, parseStudentAnswer, } from '../types/choose-then-answer-group/map-choose-then-answer-group-data';
|
|
5
|
+
import { mapQuestionToChooseThenAnswerGroupData, parseStudentAnswer, toCtaItemResults, } from '../types/choose-then-answer-group/map-choose-then-answer-group-data';
|
|
6
6
|
function readStudentAnswers(value, itemCount) {
|
|
7
7
|
const record = value && typeof value === 'object' && !Array.isArray(value)
|
|
8
8
|
? value
|
|
@@ -16,7 +16,7 @@ function readStudentAnswers(value, itemCount) {
|
|
|
16
16
|
return undefined;
|
|
17
17
|
return Array.from({ length: itemCount }, (_, index) => parseStudentAnswer(raw[index]));
|
|
18
18
|
}
|
|
19
|
-
export function ChooseThenAnswerGroupViewer({ question, isReviewMode = false, userAnswer, onSubmitAnswer, }) {
|
|
19
|
+
export function ChooseThenAnswerGroupViewer({ question, isReviewMode = false, userAnswer, gradeDetails, onSubmitAnswer, }) {
|
|
20
20
|
const questionData = mapQuestionToChooseThenAnswerGroupData(question);
|
|
21
21
|
const seed = questionData.items.map((item) => ({
|
|
22
22
|
optionId: item.isExample ? item.optionId : '',
|
|
@@ -35,5 +35,6 @@ export function ChooseThenAnswerGroupViewer({ question, isReviewMode = false, us
|
|
|
35
35
|
setLocalAnswers(updated);
|
|
36
36
|
onSubmitAnswer?.({ answer: updated });
|
|
37
37
|
};
|
|
38
|
-
|
|
38
|
+
const displayedAnswers = isReviewMode && !parsedUser ? [] : (parsedUser ?? localAnswers);
|
|
39
|
+
return (_jsx(ChooseThenAnswerGroupClient, { questionData: questionData, isReviewMode: isReviewMode, userAnswers: isReviewMode && !parsedUser ? undefined : displayedAnswers, itemResults: toCtaItemResults(gradeDetails, questionData.items, displayedAnswers), onAnswerChange: isReviewMode ? undefined : handleChange }));
|
|
39
40
|
}
|
|
@@ -2,6 +2,8 @@ import type { Question } from '../../../shared/types/entities/question.types';
|
|
|
2
2
|
type CrossOutWordGroupViewerProps = {
|
|
3
3
|
question: Question;
|
|
4
4
|
isReviewMode?: boolean;
|
|
5
|
+
userAnswer?: unknown;
|
|
6
|
+
onSubmitAnswer?: (answer: unknown[]) => void;
|
|
5
7
|
};
|
|
6
|
-
export declare function CrossOutWordGroupViewer({ question, isReviewMode, }: CrossOutWordGroupViewerProps): import("react").JSX.Element;
|
|
8
|
+
export declare function CrossOutWordGroupViewer({ question, isReviewMode, userAnswer, onSubmitAnswer, }: CrossOutWordGroupViewerProps): import("react").JSX.Element;
|
|
7
9
|
export {};
|
|
@@ -2,7 +2,17 @@
|
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
3
|
import { CrossOutWordGroupClient } from '../types/cross-out-word-group/CrossOutWordGroupClient';
|
|
4
4
|
import { mapQuestionToCrossOutWordGroupData } from '../types/cross-out-word-group/map-cross-out-word-group-data';
|
|
5
|
-
|
|
5
|
+
function readCrossOutAnswers(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
|
+
}
|
|
14
|
+
export function CrossOutWordGroupViewer({ question, isReviewMode = false, userAnswer, onSubmitAnswer, }) {
|
|
6
15
|
const questionData = mapQuestionToCrossOutWordGroupData(question);
|
|
7
|
-
|
|
16
|
+
const userAnswers = readCrossOutAnswers(userAnswer);
|
|
17
|
+
return (_jsx(CrossOutWordGroupClient, { questionData: questionData, isReviewMode: isReviewMode, userAnswers: userAnswers, onAnswerChange: isReviewMode ? undefined : onSubmitAnswer }));
|
|
8
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
|
}
|
|
@@ -4,6 +4,14 @@ interface QuestionViewerProps {
|
|
|
4
4
|
onSubmitAnswer?: (answer: any) => void;
|
|
5
5
|
isReviewMode?: boolean;
|
|
6
6
|
userAnswer?: any;
|
|
7
|
+
gradeDetails?: Array<{
|
|
8
|
+
itemId: string;
|
|
9
|
+
isCorrect?: boolean;
|
|
10
|
+
userAnswer?: unknown;
|
|
11
|
+
correctAnswer?: unknown;
|
|
12
|
+
partialScore?: number;
|
|
13
|
+
feedback?: string;
|
|
14
|
+
}>;
|
|
7
15
|
}
|
|
8
|
-
export declare function QuestionViewer({ question, onSubmitAnswer, isReviewMode, userAnswer, }: QuestionViewerProps): import("react").JSX.Element;
|
|
16
|
+
export declare function QuestionViewer({ question, onSubmitAnswer, isReviewMode, userAnswer, gradeDetails, }: QuestionViewerProps): import("react").JSX.Element;
|
|
9
17
|
export {};
|