@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
|
@@ -3,6 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import { Mic, Volume2, CheckCircle2, Play, Pause, ListChecks, Info } from 'lucide-react';
|
|
4
4
|
import { Button } from '../../../../components/ui/button';
|
|
5
5
|
import { usePresignedFileUrl, useAudioPreview } from '../../../../shared/lib/hooks';
|
|
6
|
+
import { KidQuestionCard } from '../../_shared/components/compact';
|
|
6
7
|
import { cn } from '../../../../shared/lib/utils';
|
|
7
8
|
// ─── Sub-component: Custom Audio Player ──────────────────────────────────────
|
|
8
9
|
function QuestionAudioPlayer({ audioUrl }) {
|
|
@@ -13,14 +14,14 @@ function QuestionAudioPlayer({ audioUrl }) {
|
|
|
13
14
|
// ─── Main Client Component ───────────────────────────────────────────────────
|
|
14
15
|
export function ListenAndSpeakQuestionListClient({ questionData, isReviewMode = false, }) {
|
|
15
16
|
const { audioUrl, expectedAnswers, activeQuestionId, questions = [], explanation } = questionData;
|
|
16
|
-
return (_jsxs("div", { className: "space-y-4", children: [questionData.isBackup && (_jsxs("div", { className: "inline-flex self-center items-center gap-1.5 px-3 py-1.5 rounded-md bg-amber-50 text-amber-700 border border-amber-200 text-xs font-bold shadow-sm", children: [_jsx("span", { children: "\uD83D\uDD04" }), " C\u00E2u h\u1ECFi d\u1EF1 ph\u00F2ng"] })), _jsxs(
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
17
|
+
return (_jsxs("div", { className: "space-y-4", children: [questionData.isBackup && (_jsxs("div", { className: "inline-flex self-center items-center gap-1.5 px-3 py-1.5 rounded-md bg-amber-50 text-amber-700 border border-amber-200 text-xs font-bold shadow-sm", children: [_jsx("span", { children: "\uD83D\uDD04" }), " C\u00E2u h\u1ECFi d\u1EF1 ph\u00F2ng"] })), _jsxs(KidQuestionCard, { title: "Nghe v\u00E0 n\u00F3i (Danh s\u00E1ch c\u00E2u h\u1ECFi)", icon: _jsx(ListChecks, { className: "h-3.5 w-3.5" }), children: [audioUrl ? (_jsx(QuestionAudioPlayer, { audioUrl: audioUrl })) : (_jsxs("div", { className: "flex items-center gap-2 rounded-lg border border-gray-200 bg-gray-50 px-4 py-3", children: [_jsx(Volume2, { className: "h-4 w-4 text-gray-400" }), _jsx("span", { className: "text-xs text-gray-400", children: "Ch\u01B0a c\u00F3 audio c\u00E2u h\u1ECFi" })] })), questions.length > 0 ? (_jsx("div", { className: "rounded-xl border border-gray-100 bg-gradient-to-b from-gray-50/80 to-white p-5 space-y-2.5", children: questions.map((q, idx) => {
|
|
18
|
+
const isActive = q.id === activeQuestionId;
|
|
19
|
+
return (_jsxs("div", { className: cn('flex items-center gap-3 rounded-lg px-4 py-3 transition-all duration-200', isActive
|
|
20
|
+
? 'bg-blue-50 border-[2px] border-blue-300 shadow-sm shadow-blue-100/50'
|
|
21
|
+
: 'bg-white border border-gray-200 hover:border-gray-300 hover:shadow-sm'), children: [_jsx("div", { className: cn('flex h-7 w-7 flex-shrink-0 items-center justify-center rounded-lg text-xs font-bold shadow-sm', isActive
|
|
22
|
+
? 'bg-blue-500 text-white'
|
|
23
|
+
: 'bg-gray-100 text-gray-500 border border-gray-200'), children: idx + 1 }), _jsx("span", { className: cn('text-sm font-medium flex-1', isActive ? 'text-blue-800' : 'text-gray-700'), children: q.text || _jsx("span", { className: "italic text-gray-400", children: "(Ch\u01B0a nh\u1EADp n\u1ED9i dung)" }) }), isActive && (_jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx("span", { className: "text-[10px] font-medium text-blue-500 animate-pulse", children: "\u0110ang h\u1ECFi" }), _jsx(CheckCircle2, { className: "h-4 w-4 text-blue-500" })] }))] }, q.id));
|
|
24
|
+
}) })) : (
|
|
25
|
+
/* Empty state */
|
|
26
|
+
_jsxs("div", { className: "flex flex-col items-center justify-center py-10 px-4 rounded-xl border-2 border-dashed border-gray-200 bg-gray-50/50", children: [_jsx(ListChecks, { className: "h-12 w-12 text-gray-300 mb-3" }), _jsx("p", { className: "text-sm font-medium text-gray-500", children: "Ch\u01B0a c\u00F3 c\u00E2u h\u1ECFi n\u00E0o" }), _jsx("p", { className: "text-xs text-gray-400 mt-1", children: "Danh s\u00E1ch c\u00E2u h\u1ECFi s\u1EBD hi\u1EC3n th\u1ECB t\u1EA1i \u0111\u00E2y" })] })), _jsxs("div", { className: "flex items-center gap-3 rounded-lg border border-dashed border-gray-300 bg-gray-50/80 px-4 py-3", children: [_jsx("div", { className: "flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-full bg-indigo-50 ring-2 ring-indigo-100/50", children: _jsx(Mic, { className: "w-4 h-4 text-indigo-400" }) }), _jsx("p", { className: "text-xs text-gray-400", children: "H\u1ECDc sinh s\u1EBD nh\u1EA5n n\u00FAt ghi \u00E2m v\u00E0 n\u00F3i c\u00E2u tr\u1EA3 l\u1EDDi t\u1EA1i \u0111\u00E2y" })] }), isReviewMode && expectedAnswers && expectedAnswers.length > 0 && (_jsxs("div", { className: "rounded-xl border border-green-200 bg-green-50/80 p-4", children: [_jsxs("div", { className: "flex items-center gap-2 mb-3", children: [_jsx(CheckCircle2, { className: "h-4 w-4 text-green-600" }), _jsx("h4", { className: "text-sm font-semibold text-green-800", children: "C\u00E1c \u0111\u00E1p \u00E1n mong mu\u1ED1n" })] }), _jsx("div", { className: "space-y-2", children: expectedAnswers.map((answer, index) => (_jsxs("div", { className: "flex items-center gap-2.5 rounded-lg bg-white/70 border border-green-100 px-3 py-2", children: [_jsx("span", { className: "flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-full bg-green-100 text-green-700 text-xs font-bold", children: index + 1 }), _jsx("span", { className: "text-sm text-green-800", children: answer })] }, index))) })] })), isReviewMode && explanation && (_jsxs("div", { className: "rounded-xl border border-indigo-200 bg-indigo-50 p-4", children: [_jsxs("div", { className: "flex items-center gap-2 mb-2", children: [_jsx(Info, { className: "h-4 w-4 text-indigo-600" }), _jsx("p", { className: "text-sm font-semibold text-indigo-800", children: "Gi\u1EA3i th\u00EDch" })] }), _jsx("p", { className: "text-sm text-indigo-700 leading-relaxed", children: explanation })] }))] })] }));
|
|
26
27
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { Mic, Volume2, CheckCircle2, Play, Pause, ImageIcon, Info, BookImage } from 'lucide-react';
|
|
4
4
|
import { Button } from '../../../../components/ui/button';
|
|
5
|
+
import { KidQuestionCard } from '../../_shared/components/compact';
|
|
5
6
|
import { usePresignedFileUrl, useAudioPreview } from '../../../../shared/lib/hooks';
|
|
6
7
|
// ─── Sub-component: Custom Audio Player ──────────────────────────────────────
|
|
7
8
|
function QuestionAudioPlayer({ audioUrl }) {
|
|
@@ -20,7 +21,7 @@ function StoryImagePreview({ imageUrl, index }) {
|
|
|
20
21
|
// ─── Main Client Component ───────────────────────────────────────────────────
|
|
21
22
|
export function ListenAndSpeakWithStoryImagesClient({ questionData, isReviewMode = false, }) {
|
|
22
23
|
const { audioUrl, images, expectedAnswers = [], explanation } = questionData;
|
|
23
|
-
return (_jsxs("div", { className: "space-y-4", children: [questionData.isBackup && (_jsxs("div", { className: "inline-flex self-center items-center gap-1.5 px-3 py-1.5 rounded-md bg-amber-50 text-amber-700 border border-amber-200 text-xs font-bold shadow-sm", children: [_jsx("span", { children: "\uD83D\uDD04" }), " C\u00E2u h\u1ECFi d\u1EF1 ph\u00F2ng"] })), _jsxs(
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
return (_jsxs("div", { className: "space-y-4", children: [questionData.isBackup && (_jsxs("div", { className: "inline-flex self-center items-center gap-1.5 px-3 py-1.5 rounded-md bg-amber-50 text-amber-700 border border-amber-200 text-xs font-bold shadow-sm", children: [_jsx("span", { children: "\uD83D\uDD04" }), " C\u00E2u h\u1ECFi d\u1EF1 ph\u00F2ng"] })), _jsxs(KidQuestionCard, { title: "Nghe v\u00E0 n\u00F3i (K\u1EC3 chuy\u1EC7n theo h\u00ECnh)", icon: _jsx(BookImage, { className: "h-3.5 w-3.5" }), children: [audioUrl ? (_jsx(QuestionAudioPlayer, { audioUrl: audioUrl })) : (_jsxs("div", { className: "flex items-center gap-2 rounded-lg border border-gray-200 bg-gray-50 px-4 py-3", children: [_jsx(Volume2, { className: "h-4 w-4 text-gray-400" }), _jsx("span", { className: "text-xs text-gray-400", children: "Ch\u01B0a c\u00F3 audio c\u00E2u h\u1ECFi" })] })), images && images.length > 0 && (_jsx("div", { className: "bg-gradient-to-br from-purple-50/80 to-fuchsia-50/60 rounded-2xl p-6 border border-purple-100/80 shadow-sm", children: _jsx("div", { className: "grid gap-4", style: {
|
|
25
|
+
gridTemplateColumns: `repeat(${Math.min(images.length, 4)}, 1fr)`,
|
|
26
|
+
}, children: images.map((imgUrl, index) => (_jsxs("div", { className: "space-y-2", children: [_jsx("div", { className: "flex items-center gap-1.5", children: _jsx("span", { className: "flex h-6 w-6 items-center justify-center rounded-md bg-purple-200 text-purple-800 text-xs font-bold", children: index + 1 }) }), _jsx("div", { className: "relative aspect-square rounded-xl overflow-hidden border-[3px] border-purple-300 bg-white shadow-md ring-2 ring-purple-100", children: imgUrl ? (_jsx(StoryImagePreview, { imageUrl: imgUrl, index: index })) : (_jsx("div", { className: "w-full h-full flex items-center justify-center text-gray-400", children: _jsx(ImageIcon, { className: "h-6 w-6" }) })) })] }, index))) }) })), _jsxs("div", { className: "flex items-center gap-3 rounded-lg border border-dashed border-gray-300 bg-gray-50/80 px-4 py-3", children: [_jsx("div", { className: "flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-full bg-indigo-50 ring-2 ring-indigo-100/50", children: _jsx(Mic, { className: "w-4 h-4 text-indigo-400" }) }), _jsx("p", { className: "text-xs text-gray-400", children: "H\u1ECDc sinh s\u1EBD nh\u1EA5n n\u00FAt ghi \u00E2m v\u00E0 n\u00F3i c\u00E2u tr\u1EA3 l\u1EDDi t\u1EA1i \u0111\u00E2y" })] }), isReviewMode && expectedAnswers.length > 0 && (_jsxs("div", { className: "rounded-xl border border-green-200 bg-green-50/80 p-4", children: [_jsxs("div", { className: "flex items-center gap-2 mb-3", children: [_jsx(CheckCircle2, { className: "h-4 w-4 text-green-600" }), _jsx("h4", { className: "text-sm font-semibold text-green-800", children: "C\u00E1c \u0111\u00E1p \u00E1n mong mu\u1ED1n" })] }), _jsx("div", { className: "space-y-2", children: expectedAnswers.map((answer, index) => (_jsxs("div", { className: "flex items-center gap-2.5 rounded-lg bg-white/70 border border-green-100 px-3 py-2", children: [_jsx("span", { className: "flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-full bg-green-100 text-green-700 text-xs font-bold", children: index + 1 }), _jsx("span", { className: "text-sm text-green-800", children: answer })] }, index))) })] })), isReviewMode && explanation && (_jsxs("div", { className: "rounded-xl border border-indigo-200 bg-indigo-50 p-4", children: [_jsxs("div", { className: "flex items-center gap-2 mb-2", children: [_jsx(Info, { className: "h-4 w-4 text-indigo-600" }), _jsx("p", { className: "text-sm font-semibold text-indigo-800", children: "Gi\u1EA3i th\u00EDch" })] }), _jsx("p", { className: "text-sm text-indigo-700 leading-relaxed", children: explanation })] }))] })] }));
|
|
26
27
|
}
|
package/dist/components/questions/types/match-by-writing-answer/MatchByWritingAnswerClient.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { Card, CardContent } from '../../../../components/ui/card';
|
|
4
3
|
import { Input } from '../../../../components/ui/input';
|
|
5
4
|
import { Label } from '../../../../components/ui/label';
|
|
6
|
-
import { CheckCircle2, XCircle,
|
|
5
|
+
import { CheckCircle2, XCircle, Grid3X3 } from 'lucide-react';
|
|
7
6
|
import { usePresignedFileUrl } from '../../../../shared/lib/hooks';
|
|
8
7
|
import { useState } from 'react';
|
|
8
|
+
import { KidQuestionCard } from '../../_shared/components/compact';
|
|
9
9
|
function OptionCard({ option }) {
|
|
10
10
|
const { previewUrl } = usePresignedFileUrl(option.imageUrl || '');
|
|
11
11
|
const displayUrl = previewUrl || option.imageUrl;
|
|
@@ -18,7 +18,7 @@ export function MatchByWritingAnswerClient({ questionData, onSubmit, isReviewMod
|
|
|
18
18
|
const [answer, setAnswer] = useState(userAnswer || '');
|
|
19
19
|
const { previewUrl: questionImageUrl } = usePresignedFileUrl(questionData.imageUrl || '');
|
|
20
20
|
if (!questionData.question) {
|
|
21
|
-
return (_jsx(
|
|
21
|
+
return (_jsx(KidQuestionCard, { title: "Gh\u00E9p \u0111\u00E1p \u00E1n b\u1EB1ng c\u00E1ch vi\u1EBFt", children: _jsx("p", { className: "text-gray-500", children: "Kh\u00F4ng c\u00F3 d\u1EEF li\u1EC7u c\u00E2u h\u1ECFi" }) }));
|
|
22
22
|
}
|
|
23
23
|
const isExample = questionData.isExample || false;
|
|
24
24
|
const displayedAnswer = autoFillCorrectAnswers || isExample
|
|
@@ -27,12 +27,14 @@ export function MatchByWritingAnswerClient({ questionData, onSubmit, isReviewMod
|
|
|
27
27
|
const isCorrect = isReviewMode && userAnswer !== undefined
|
|
28
28
|
? userAnswer.trim().toLowerCase() === questionData.correctAnswer.trim().toLowerCase()
|
|
29
29
|
: undefined;
|
|
30
|
-
return (_jsxs(
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
30
|
+
return (_jsxs(KidQuestionCard, { icon: _jsx(Grid3X3, { className: "h-3.5 w-3.5" }), title: "Gh\u00E9p \u0111\u00E1p \u00E1n b\u1EB1ng c\u00E1ch vi\u1EBFt", isExample: isExample, review: isReviewMode && isCorrect !== undefined ? (isCorrect ? 'correct' : 'wrong') : null, children: [questionData.options && questionData.options.length > 0 && (_jsxs("div", { className: "rounded-xl border border-purple-200 bg-gradient-to-r from-purple-50 to-violet-50 p-4", children: [_jsx("p", { className: "mb-3 text-sm font-semibold text-purple-700", children: "C\u00E1c l\u1EF1a ch\u1ECDn:" }), _jsx("div", { className: `grid gap-3 ${questionData.options.some((o) => o.imageUrl) ? 'grid-cols-2 sm:grid-cols-3 md:grid-cols-4' : 'grid-cols-1 sm:grid-cols-2'}`, children: questionData.options.map((option, index) => (_jsx(OptionCard, { option: option }, index))) })] })), _jsxs("div", { className: "rounded-xl border border-gray-200 bg-gradient-to-r from-gray-50 to-slate-50 p-5", children: [_jsxs("div", { className: "flex items-center gap-4", children: [(questionImageUrl || questionData.imageUrl) ? (_jsx("div", { className: "relative h-20 w-20 flex-shrink-0 overflow-hidden rounded-lg border border-gray-200 bg-gray-50 shadow-sm", children: _jsx("img", { src: questionImageUrl || questionData.imageUrl, alt: questionData.question, className: "h-full w-full object-contain" }) })) : null, _jsxs("div", { className: "flex-1 space-y-2", children: [_jsx(Label, { className: "text-base font-semibold text-gray-800", children: questionData.question }), _jsxs("div", { className: "flex items-center gap-3", children: [_jsx(Input, { value: displayedAnswer, onChange: (e) => {
|
|
31
|
+
setAnswer(e.target.value);
|
|
32
|
+
onSubmit?.(e.target.value);
|
|
33
|
+
}, placeholder: "Vi\u1EBFt \u0111\u00E1p \u00E1n...", disabled: isReviewMode || isExample, className: `max-w-xs border-2 text-lg font-medium ${isReviewMode
|
|
34
|
+
? isCorrect
|
|
35
|
+
? 'border-green-400 bg-green-50 text-green-700'
|
|
36
|
+
: 'border-red-400 bg-red-50 text-red-700'
|
|
37
|
+
: displayedAnswer
|
|
38
|
+
? 'border-blue-500 bg-blue-50 ring-2 ring-blue-100 focus:border-blue-500 focus:ring-blue-100'
|
|
39
|
+
: 'border-gray-200 bg-white focus:border-blue-500 focus:ring-blue-100'}` }), isReviewMode && isCorrect !== undefined && (_jsx("span", { className: "flex items-center gap-1", children: isCorrect ? (_jsx(CheckCircle2, { className: "h-5 w-5 text-green-500" })) : (_jsx(XCircle, { className: "h-5 w-5 text-red-500" })) }))] })] })] }), isReviewMode && isCorrect === false && (_jsxs("div", { className: "mt-3 flex items-center gap-2 rounded-lg bg-green-50 border border-green-200 px-4 py-2", children: [_jsx(CheckCircle2, { className: "h-4 w-4 text-green-600" }), _jsxs("span", { className: "text-sm text-green-700", children: ["\u0110\u00E1p \u00E1n \u0111\u00FAng: ", _jsx("strong", { children: questionData.correctAnswer })] })] })), autoFillCorrectAnswers && (_jsxs("div", { className: "mt-3 flex items-center gap-2 rounded-lg bg-blue-50 border border-blue-200 px-4 py-2", children: [_jsx(CheckCircle2, { className: "h-4 w-4 text-blue-600" }), _jsxs("span", { className: "text-sm text-blue-700", children: ["\u0110\u00E1p \u00E1n: ", _jsx("strong", { children: questionData.correctAnswer })] })] }))] }), explanation && isReviewMode && (_jsxs("div", { className: "rounded-xl border border-blue-200 bg-blue-50 p-4", children: [_jsx("p", { className: "text-sm font-semibold text-blue-800 mb-1", children: "Gi\u1EA3i th\u00EDch:" }), _jsx("p", { className: "text-sm text-blue-700", children: explanation })] }))] }));
|
|
38
40
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
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 { cn } from '../../../../shared/lib/utils';
|
|
5
5
|
import { assembleMatchColumnsSentence, getMatchColumnsChoiceColumns, getMatchColumnsStemColumn, } from './map-match-columns-to-make-sentences-data';
|
|
6
|
+
import { KidChoiceRow, KidInstruction, KidPrompt, KidQuestionCard, optionLetter, } from '../../_shared/components/compact';
|
|
6
7
|
function emptyAnswer(stemFragmentId) {
|
|
7
8
|
return { fragmentIds: stemFragmentId ? [stemFragmentId] : [], sentence: '' };
|
|
8
9
|
}
|
|
@@ -50,7 +51,7 @@ export function MatchColumnsToMakeSentencesClient({ questionData, isReviewMode =
|
|
|
50
51
|
});
|
|
51
52
|
onAnswerChange?.(next);
|
|
52
53
|
};
|
|
53
|
-
return (_jsxs("div", { className: "space-y-4", children: [
|
|
54
|
+
return (_jsxs("div", { className: "space-y-4", children: [questionData.title ? _jsx(KidPrompt, { children: questionData.title }) : null, _jsx(KidInstruction, { children: questionData.instruction }), _jsx("div", { className: "overflow-x-auto rounded-xl border border-gray-200 bg-white", children: _jsxs("table", { className: "min-w-full text-sm", children: [_jsx("thead", { children: _jsx("tr", { className: "bg-slate-50 text-left text-xs uppercase tracking-wide text-slate-500", children: columns.map((column) => (_jsxs("th", { className: "px-3 py-2 font-semibold", children: [column.label || column.id, column.role === 'stem' ? ' (stem)' : ''] }, column.id))) }) }), _jsx("tbody", { children: _jsx("tr", { className: "align-top", children: columns.map((column) => (_jsx("td", { className: "px-3 py-2", children: _jsx("ul", { className: "space-y-1", children: column.fragments.map((fragment) => (_jsxs("li", { className: "text-slate-700", children: [_jsxs("span", { className: "mr-1 font-semibold text-slate-500", children: [fragment.id, "."] }), fragment.text || '—'] }, fragment.id))) }) }, `${column.id}-bank`))) }) })] }) }), _jsx("div", { className: "space-y-3", children: items.map((item, itemIndex) => {
|
|
54
55
|
const current = resolveAnswer(itemIndex);
|
|
55
56
|
const canSelect = canEdit && !item.isExample;
|
|
56
57
|
const stemText = stemColumn?.fragments.find((fragment) => fragment.id === item.stemFragmentId)?.text
|
|
@@ -58,17 +59,19 @@ export function MatchColumnsToMakeSentencesClient({ questionData, isReviewMode =
|
|
|
58
59
|
|| '—';
|
|
59
60
|
const isCorrect = isReviewMode &&
|
|
60
61
|
current.sentence.trim().toLowerCase() === item.sentence.trim().toLowerCase();
|
|
61
|
-
return (_jsxs(
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
62
|
+
return (_jsxs(KidQuestionCard, { title: `Câu ${item.questionNumber || itemIndex + 1}`, points: item.points || 0, isExample: item.isExample, review: isReviewMode && !item.isExample ? (isCorrect ? 'correct' : 'wrong') : null, children: [_jsx(KidPrompt, { children: stemText }), choiceColumns.map((column, choiceIndex) => {
|
|
63
|
+
const selectedId = current.fragmentIds[choiceIndex + 1] || '';
|
|
64
|
+
const correctId = item.fragmentIds[choiceIndex + 1] || '';
|
|
65
|
+
return (_jsxs("div", { className: "space-y-1.5", children: [_jsx("p", { className: "text-xs font-medium uppercase tracking-wider text-gray-500", children: column.label || column.id }), column.fragments.map((fragment, fragmentIndex) => {
|
|
66
|
+
const isSelected = selectedId === fragment.id;
|
|
67
|
+
return (_jsxs(KidChoiceRow, { letter: optionLetter(fragmentIndex), selected: isSelected, isReviewMode: isReviewMode, isCorrect: isReviewMode && fragment.id === correctId, disabled: !canSelect, onClick: canSelect
|
|
68
|
+
? () => handleChoiceChange(itemIndex, choiceIndex, isSelected ? '' : fragment.id)
|
|
69
|
+
: undefined, children: [fragment.id, ". ", fragment.text] }, fragment.id));
|
|
70
|
+
})] }, `${itemIndex}-${column.id}`));
|
|
71
|
+
}), _jsx("p", { className: cn('text-sm', isReviewMode
|
|
72
|
+
? isCorrect
|
|
73
|
+
? 'text-green-700'
|
|
74
|
+
: 'text-red-700'
|
|
75
|
+
: 'text-slate-600'), children: current.sentence || 'Chưa ghép câu' }), isReviewMode && !item.isExample && (_jsxs("p", { className: "text-xs text-slate-500", children: ["\u0110\u00E1p \u00E1n: ", item.sentence] }))] }, `${item.questionNumber}-${itemIndex}`));
|
|
73
76
|
}) }), 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] }))] }));
|
|
74
77
|
}
|
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import {
|
|
4
|
-
import { Label } from '../../../../components/ui/label';
|
|
5
|
-
import { BookOpen, CheckCircle2, XCircle } from 'lucide-react';
|
|
3
|
+
import { CheckCircle2 } from 'lucide-react';
|
|
6
4
|
import { usePresignedFileUrl } from '../../../../shared/lib/hooks';
|
|
7
5
|
import { MATCH_WORD_TO_PICTURE_DEFAULT_INSTRUCTION, } from '../../_shared/types/match-word-to-picture.type';
|
|
8
6
|
import { useState } from 'react';
|
|
7
|
+
import { KidChoiceRow, KidInstruction, KidQuestionCard, optionLetter, } from '../../_shared/components/compact';
|
|
9
8
|
export function MatchWordToPictureClient({ questionData, onSubmit, isReviewMode = false, userAnswer, autoFillCorrectAnswers = false, explanation, }) {
|
|
10
9
|
const [answer, setAnswer] = useState(userAnswer || '');
|
|
11
10
|
const { previewUrl: questionImageUrl } = usePresignedFileUrl(questionData.imageUrl || '');
|
|
12
11
|
const wordBank = questionData.wordBank || [];
|
|
13
12
|
if (!questionData.imageUrl) {
|
|
14
|
-
return (_jsx(
|
|
13
|
+
return (_jsx(KidQuestionCard, { title: "Gh\u00E9p t\u1EEB v\u1EDBi tranh", children: _jsx("p", { className: "text-gray-500", children: "Kh\u00F4ng c\u00F3 d\u1EEF li\u1EC7u c\u00E2u h\u1ECFi" }) }));
|
|
15
14
|
}
|
|
16
15
|
const isExample = questionData.isExample || false;
|
|
17
16
|
const displayedAnswer = autoFillCorrectAnswers || isExample
|
|
@@ -20,12 +19,17 @@ export function MatchWordToPictureClient({ questionData, onSubmit, isReviewMode
|
|
|
20
19
|
const isCorrect = isReviewMode && userAnswer !== undefined
|
|
21
20
|
? userAnswer.trim().toLowerCase() === questionData.correctAnswer.trim().toLowerCase()
|
|
22
21
|
: undefined;
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
22
|
+
const canSelect = !isReviewMode && !isExample;
|
|
23
|
+
const handleSelect = (word) => {
|
|
24
|
+
if (!canSelect)
|
|
25
|
+
return;
|
|
26
|
+
const next = displayedAnswer === word ? '' : word;
|
|
27
|
+
setAnswer(next);
|
|
28
|
+
onSubmit?.(next);
|
|
29
|
+
};
|
|
30
|
+
return (_jsxs(KidQuestionCard, { title: "Gh\u00E9p t\u1EEB v\u1EDBi tranh", isExample: isExample, review: isReviewMode && isCorrect !== undefined ? (isCorrect ? 'correct' : 'wrong') : null, children: [_jsx(KidInstruction, { children: questionData.instruction || MATCH_WORD_TO_PICTURE_DEFAULT_INSTRUCTION }), wordBank.length > 0 && (_jsxs("div", { className: "rounded-xl border border-blue-100 bg-blue-50/60 p-3", children: [_jsx("p", { className: "mb-2 text-xs font-semibold uppercase tracking-wider text-blue-700", children: "Word bank" }), _jsx("div", { className: "flex flex-wrap gap-2", children: wordBank.map((word, index) => (_jsx("span", { className: "rounded-full border border-gray-200 bg-white px-3 py-1 text-sm font-semibold text-slate-700", children: word }, `${word}-${index}`))) })] })), _jsxs("div", { className: "flex items-center gap-4", children: [_jsx("div", { className: "relative h-24 w-24 flex-shrink-0 overflow-hidden rounded-lg border border-gray-200 bg-gray-50 shadow-sm", children: _jsx("img", { src: questionImageUrl || questionData.imageUrl, alt: questionData.correctAnswer || 'Picture', className: "h-full w-full object-contain" }) }), _jsx("p", { className: "text-sm font-semibold text-gray-800", children: "Ch\u1ECDn t\u1EEB ph\u00F9 h\u1EE3p" })] }), _jsx("div", { className: "space-y-1.5", children: wordBank.map((word, index) => {
|
|
31
|
+
const isSelected = displayedAnswer === word;
|
|
32
|
+
const isWordCorrect = word.trim().toLowerCase() === questionData.correctAnswer.trim().toLowerCase();
|
|
33
|
+
return (_jsx(KidChoiceRow, { letter: optionLetter(index), selected: isSelected, isReviewMode: isReviewMode, isCorrect: isReviewMode && isWordCorrect, disabled: !canSelect, onClick: canSelect ? () => handleSelect(word) : undefined, children: word }, `${word}-${index}`));
|
|
34
|
+
}) }), isReviewMode && isCorrect === false && (_jsxs("div", { className: "flex items-center gap-2 rounded-lg border border-green-200 bg-green-50 px-4 py-2", children: [_jsx(CheckCircle2, { className: "h-4 w-4 text-green-600" }), _jsxs("span", { className: "text-sm text-green-700", children: ["\u0110\u00E1p \u00E1n \u0111\u00FAng: ", _jsx("strong", { children: questionData.correctAnswer })] })] })), explanation && isReviewMode && (_jsxs("div", { className: "rounded-xl border border-blue-200 bg-blue-50 p-4", children: [_jsx("p", { className: "mb-1 text-sm font-semibold text-blue-800", children: "Gi\u1EA3i th\u00EDch:" }), _jsx("p", { className: "text-sm text-blue-700", children: explanation })] }))] }));
|
|
31
35
|
}
|
package/dist/components/questions/types/match-word-to-picture/MatchWordToPictureGroupClient.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import {
|
|
3
|
+
import { Lightbulb } from 'lucide-react';
|
|
4
4
|
import { cn } from '../../../../shared/lib/utils';
|
|
5
5
|
import { usePresignedFileUrl } from '../../../../shared/lib/hooks';
|
|
6
|
+
import { KidChoiceRow, KidInstruction, KidQuestionCard, optionLetter, } from '../../_shared/components/compact';
|
|
6
7
|
function normalizeWord(value) {
|
|
7
8
|
return value.trim().toLowerCase();
|
|
8
9
|
}
|
|
@@ -19,7 +20,7 @@ function ClientImageItem({ url, alt }) {
|
|
|
19
20
|
if (!src && !isLoading) {
|
|
20
21
|
return (_jsx("div", { className: "flex h-28 w-28 items-center justify-center rounded-lg border border-gray-200 bg-gray-50 text-xs text-gray-400", children: "Kh\u00F4ng t\u1EA3i \u0111\u01B0\u1EE3c \u1EA3nh" }));
|
|
21
22
|
}
|
|
22
|
-
return (_jsx("div", { className: "relative h-28 w-28 flex-shrink-0 overflow-hidden rounded-lg border border-gray-200 bg-gray-50", children: isLoading ? (_jsx("div", { className: "flex h-full w-full items-center justify-center", children: _jsx("div", { className: "h-6 w-6 animate-spin rounded-full border-2 border-gray-200 border-t-
|
|
23
|
+
return (_jsx("div", { className: "relative h-28 w-28 flex-shrink-0 overflow-hidden rounded-lg border border-gray-200 bg-gray-50", children: isLoading ? (_jsx("div", { className: "flex h-full w-full items-center justify-center", children: _jsx("div", { className: "h-6 w-6 animate-spin rounded-full border-2 border-gray-200 border-t-blue-500" }) })) : (_jsx("img", { src: src, alt: alt || 'Picture', className: "h-full w-full object-contain" })) }));
|
|
23
24
|
}
|
|
24
25
|
export function MatchWordToPictureGroupClient({ questionData, isReviewMode = false, userAnswers = [], onAnswerChange, }) {
|
|
25
26
|
const items = questionData.items || [];
|
|
@@ -40,11 +41,11 @@ export function MatchWordToPictureGroupClient({ questionData, isReviewMode = fal
|
|
|
40
41
|
usedWords.add(normalizeWord(value));
|
|
41
42
|
}
|
|
42
43
|
});
|
|
43
|
-
return (_jsxs("div", { className: "space-y-4", children: [
|
|
44
|
+
return (_jsxs("div", { className: "space-y-4", children: [_jsx(KidInstruction, { children: questionData.instruction }), wordBank.length > 0 && (_jsxs("div", { className: "rounded-xl border border-blue-100 bg-blue-50/60 p-3", children: [_jsx("p", { className: "mb-2 text-xs font-semibold uppercase tracking-wider text-blue-700", children: "Word bank" }), _jsx("div", { className: "flex flex-wrap gap-2", children: wordBank.map((word, index) => {
|
|
44
45
|
const isUsed = usedWords.has(normalizeWord(word));
|
|
45
46
|
return (_jsx("span", { className: cn('rounded-full border px-3 py-1 text-sm font-semibold', isUsed
|
|
46
|
-
? 'border-
|
|
47
|
-
: 'border-
|
|
47
|
+
? 'border-blue-500 bg-blue-50 text-blue-800 ring-2 ring-blue-100'
|
|
48
|
+
: 'border-gray-200 bg-white text-slate-700'), children: word }, `${word}-${index}`));
|
|
48
49
|
}) })] })), items.map((item, itemIndex) => {
|
|
49
50
|
const selectedWord = item.isExample
|
|
50
51
|
? item.correctAnswer
|
|
@@ -52,14 +53,13 @@ export function MatchWordToPictureGroupClient({ questionData, isReviewMode = fal
|
|
|
52
53
|
const canSelect = !isReviewMode && !item.isExample && Boolean(onAnswerChange);
|
|
53
54
|
const isCorrect = normalizeWord(selectedWord) === normalizeWord(item.correctAnswer) &&
|
|
54
55
|
Boolean(item.correctAnswer);
|
|
55
|
-
return (
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
})] }), isReviewMode && !item.isExample && (_jsx("span", { className: "flex items-center gap-1", children: isCorrect ? (_jsx(Check, { className: "h-5 w-5 text-green-500" })) : (_jsx(X, { className: "h-5 w-5 text-red-500" })) }))] }), isReviewMode && !item.isExample && !isCorrect && (_jsxs("p", { className: "text-sm text-green-700", children: ["\u0110\u00E1p \u00E1n \u0111\u00FAng: ", _jsx("strong", { children: item.correctAnswer || '—' })] }))] })] })] }, `${item.questionNumber}-${itemIndex}`));
|
|
56
|
+
return (_jsx(KidQuestionCard, { title: `Câu ${item.questionNumber || itemIndex + 1}`, points: item.points || 0, isExample: item.isExample, review: isReviewMode && !item.isExample ? (isCorrect ? 'correct' : 'wrong') : null, children: _jsxs("div", { className: "flex flex-col gap-3 sm:flex-row sm:items-start", children: [_jsx(ClientImageItem, { url: item.imageUrl, alt: `Hình ảnh câu ${item.questionNumber || itemIndex + 1}` }), _jsxs("div", { className: "min-w-0 flex-1 space-y-1.5", children: [_jsx("p", { className: "text-sm font-semibold text-gray-800", children: "Ch\u1ECDn t\u1EEB ph\u00F9 h\u1EE3p" }), wordBank.map((word, wordIndex) => {
|
|
57
|
+
const isSelected = normalizeWord(word) === normalizeWord(selectedWord);
|
|
58
|
+
const isUsedByOther = usedWords.has(normalizeWord(word)) && !isSelected;
|
|
59
|
+
const isWordCorrect = normalizeWord(word) === normalizeWord(item.correctAnswer);
|
|
60
|
+
return (_jsx(KidChoiceRow, { letter: optionLetter(wordIndex), selected: isSelected, isReviewMode: isReviewMode, isCorrect: isReviewMode && isWordCorrect, disabled: !canSelect || isUsedByOther, onClick: canSelect && !isUsedByOther
|
|
61
|
+
? () => handleSelect(itemIndex, isSelected ? '' : word)
|
|
62
|
+
: undefined, children: word }, `${word}-${wordIndex}`));
|
|
63
|
+
}), isReviewMode && !item.isExample && !isCorrect && (_jsxs("p", { className: "text-sm text-green-700", children: ["\u0110\u00E1p \u00E1n \u0111\u00FAng: ", _jsx("strong", { children: item.correctAnswer || '—' })] }))] })] }) }, `${item.questionNumber}-${itemIndex}`));
|
|
64
64
|
}), 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] }))] }));
|
|
65
65
|
}
|
package/dist/components/questions/types/match-word-to-picture-group/MatchWordToPictureGroupClient.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { BookOpen,
|
|
3
|
+
import { BookOpen, Lightbulb, Volume2 } from 'lucide-react';
|
|
4
4
|
import { cn } from '../../../../shared/lib/utils';
|
|
5
5
|
import { usePresignedFileUrl } from '../../../../shared/lib/hooks';
|
|
6
6
|
import { toMatchWordToPictureImageUrls } from '../../_shared/types/match-word-to-picture-group.type';
|
|
7
|
+
import { KidChoiceRow, KidInstruction, KidPrompt, KidQuestionCard, optionLetter, } from '../../_shared/components/compact';
|
|
7
8
|
function normalizeWord(value) {
|
|
8
9
|
return value.trim().toLowerCase();
|
|
9
10
|
}
|
|
@@ -69,11 +70,11 @@ export function MatchWordToPictureGroupClient({ questionData, isReviewMode = fal
|
|
|
69
70
|
usedWords.add(normalizeWord(value));
|
|
70
71
|
}
|
|
71
72
|
});
|
|
72
|
-
return (_jsxs("div", { className: "space-y-4", children: [
|
|
73
|
+
return (_jsxs("div", { className: "space-y-4", children: [questionData.title ? _jsx(KidPrompt, { children: questionData.title }) : null, _jsx(KidInstruction, { children: questionData.instruction }), questionData.audioUrl ? _jsx(ClientAudio, { url: questionData.audioUrl }) : null, toMatchWordToPictureImageUrls(questionData.imageUrl).length > 0 ? (_jsx("div", { className: "flex flex-wrap justify-center gap-3 rounded-lg border border-slate-100 bg-slate-50 p-3", children: toMatchWordToPictureImageUrls(questionData.imageUrl).map((url, imageIndex) => (_jsx(ClientImageItem, { url: url, alt: `Hình ảnh bài (${imageIndex + 1})`, large: true }, `mwtp-cover-${imageIndex}`))) })) : null, questionData.passage ? (_jsxs("div", { className: "rounded-xl border border-gray-200 bg-white p-5 shadow-xs", children: [_jsxs("div", { className: "mb-2.5 flex items-center gap-2 text-xs font-bold uppercase tracking-wider text-indigo-700", children: [_jsx(BookOpen, { className: "h-4 w-4" }), _jsx("span", { children: "B\u00E0i \u0111\u1ECDc (Reading Passage)" })] }), _jsx("div", { className: "prose prose-sm max-w-none leading-relaxed text-gray-800", dangerouslySetInnerHTML: { __html: questionData.passage } })] })) : null, wordBank.length > 0 && (_jsxs("div", { className: "rounded-xl border border-blue-100 bg-blue-50/60 p-3", children: [_jsx("p", { className: "mb-2 text-xs font-semibold uppercase tracking-wider text-blue-700", children: "Word bank" }), _jsx("div", { className: "flex flex-wrap gap-2", children: wordBank.map((word, index) => {
|
|
73
74
|
const isUsed = usedWords.has(normalizeWord(word));
|
|
74
75
|
return (_jsx("span", { className: cn('rounded-full border px-3 py-1 text-sm font-semibold', isUsed
|
|
75
|
-
? 'border-
|
|
76
|
-
: 'border-
|
|
76
|
+
? 'border-blue-500 bg-blue-50 text-blue-800 ring-2 ring-blue-100'
|
|
77
|
+
: 'border-gray-200 bg-white text-slate-700'), children: word }, `${word}-${index}`));
|
|
77
78
|
}) })] })), items.map((item, itemIndex) => {
|
|
78
79
|
const selectedWord = item.isExample
|
|
79
80
|
? item.correctAnswer
|
|
@@ -81,14 +82,13 @@ export function MatchWordToPictureGroupClient({ questionData, isReviewMode = fal
|
|
|
81
82
|
const canSelect = !isReviewMode && !item.isExample && Boolean(onAnswerChange);
|
|
82
83
|
const isCorrect = normalizeWord(selectedWord) === normalizeWord(item.correctAnswer) &&
|
|
83
84
|
Boolean(item.correctAnswer);
|
|
84
|
-
return (
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
})] })), isReviewMode && !item.isExample && (_jsx("span", { className: "flex items-center gap-1", children: isCorrect ? (_jsx(Check, { className: "h-5 w-5 text-green-500" })) : (_jsx(X, { className: "h-5 w-5 text-red-500" })) }))] }), isReviewMode && !item.isExample && !isCorrect && (_jsxs("p", { className: "text-sm text-green-700", children: ["\u0110\u00E1p \u00E1n \u0111\u00FAng: ", _jsx("strong", { children: item.correctAnswer || '—' })] }))] })] })] }, `${item.questionNumber}-${itemIndex}`));
|
|
85
|
+
return (_jsx(KidQuestionCard, { title: `Câu ${item.questionNumber || itemIndex + 1}`, points: item.points || 0, isExample: item.isExample, review: isReviewMode && !item.isExample ? (isCorrect ? 'correct' : 'wrong') : null, children: _jsxs("div", { className: "flex flex-col gap-3 sm:flex-row sm:items-start", children: [toMatchWordToPictureImageUrls(item.imageUrl).length > 0 ? (_jsx("div", { className: "flex flex-wrap gap-2", children: toMatchWordToPictureImageUrls(item.imageUrl).map((url, imageIndex) => (_jsx(ClientImageItem, { url: url, alt: `Hình ảnh câu ${item.questionNumber || itemIndex + 1}${imageIndex > 0 ? ` (${imageIndex + 1})` : ''}` }, `${item.questionNumber}-${imageIndex}`))) })) : null, _jsxs("div", { className: "min-w-0 flex-1 space-y-1.5", children: [_jsx("p", { className: "text-sm font-semibold text-gray-800", children: "Ch\u1ECDn t\u1EEB ph\u00F9 h\u1EE3p" }), item.isExample ? (_jsx("span", { className: "text-sm italic text-gray-800 underline decoration-gray-400", children: item.correctAnswer })) : (wordBank.map((word, wordIndex) => {
|
|
86
|
+
const isSelected = normalizeWord(word) === normalizeWord(selectedWord);
|
|
87
|
+
const isUsedByOther = usedWords.has(normalizeWord(word)) && !isSelected;
|
|
88
|
+
const isWordCorrect = normalizeWord(word) === normalizeWord(item.correctAnswer);
|
|
89
|
+
return (_jsx(KidChoiceRow, { letter: optionLetter(wordIndex), selected: isSelected, isReviewMode: isReviewMode, isCorrect: isReviewMode && isWordCorrect, disabled: !canSelect || isUsedByOther, onClick: canSelect && !isUsedByOther
|
|
90
|
+
? () => handleSelect(itemIndex, isSelected ? '' : word)
|
|
91
|
+
: undefined, children: word }, `${word}-${wordIndex}`));
|
|
92
|
+
})), isReviewMode && !item.isExample && !isCorrect && (_jsxs("p", { className: "text-sm text-green-700", children: ["\u0110\u00E1p \u00E1n \u0111\u00FAng: ", _jsx("strong", { children: item.correctAnswer || '—' })] }))] })] }) }, `${item.questionNumber}-${itemIndex}`));
|
|
93
93
|
}), 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] }))] }));
|
|
94
94
|
}
|
package/dist/components/questions/types/matching-with-lines-group/MatchingWithLinesGroupClient.js
CHANGED
|
@@ -4,6 +4,7 @@ import { BookOpen, Lightbulb } from 'lucide-react';
|
|
|
4
4
|
import { usePresignedFileUrl } from '../../../../shared/lib/hooks';
|
|
5
5
|
import { ResolvedImage } from '../../../../components/common/ResolvedImage';
|
|
6
6
|
import { MatchingWithLinesBoard } from './MatchingWithLinesBoard';
|
|
7
|
+
import { KidInstruction, KidPrompt, KidQuestionCard } from '../../_shared/components/compact';
|
|
7
8
|
function ClientAudio({ url }) {
|
|
8
9
|
const { previewUrl, isLoading } = usePresignedFileUrl(url);
|
|
9
10
|
const src = previewUrl ||
|
|
@@ -32,10 +33,10 @@ export function MatchingWithLinesGroupClient({ questionData, isReviewMode = fals
|
|
|
32
33
|
}
|
|
33
34
|
});
|
|
34
35
|
};
|
|
35
|
-
return (_jsxs("div", { className: "space-y-4", children: [
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
36
|
+
return (_jsxs("div", { className: "space-y-4", children: [questionData.title ? _jsx(KidPrompt, { children: questionData.title }) : null, _jsx(KidInstruction, { children: questionData.instruction }), questionData.passage ? (_jsxs("div", { className: "rounded-xl border border-gray-200 bg-white p-5 shadow-xs", children: [_jsxs("div", { className: "mb-2.5 flex items-center gap-2 text-xs font-bold uppercase tracking-wider text-indigo-700", children: [_jsx(BookOpen, { className: "h-4 w-4" }), _jsx("span", { children: "B\u00E0i \u0111\u1ECDc (Reading Passage)" })] }), _jsx("div", { className: "prose prose-sm max-w-none leading-relaxed text-gray-800", dangerouslySetInnerHTML: { __html: questionData.passage } })] })) : null, questionData.audioUrl ? _jsx(ClientAudio, { url: questionData.audioUrl }) : null, questionData.imageUrl ? (_jsx("div", { className: "flex justify-center rounded-lg border border-slate-100 bg-slate-50 p-3", children: _jsx(ResolvedImage, { src: questionData.imageUrl, alt: "Match passage image", className: "max-h-44 w-auto rounded-md object-contain" }) })) : null, _jsx(KidQuestionCard, { title: "N\u1ED1i b\u1EB1ng \u0111\u01B0\u1EDDng k\u1EBB", children: _jsx(MatchingWithLinesBoard, { leftItems: items.map((item) => ({
|
|
37
|
+
imageUrl: item.imageUrl,
|
|
38
|
+
text: item.text,
|
|
39
|
+
isExample: item.isExample,
|
|
40
|
+
questionNumber: item.questionNumber,
|
|
41
|
+
})), rightItems: rightItems, values: values, correctValues: isReviewMode ? correctValues : undefined, onChange: handleChange, isReviewMode: isReviewMode, disabled: isReviewMode || !canEdit }) }), 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] }))] }));
|
|
41
42
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useState, useEffect, useRef } from 'react';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
4
|
+
import { Check, BookOpen, Lightbulb } from 'lucide-react';
|
|
5
|
+
import { KidChoiceRow, KidInstruction, KidPrompt, KidQuestionCard, optionLetter } from '../../_shared/components/compact';
|
|
6
6
|
export function ReadPassageAndAnswerQuestionsClient({ questionData, userAnswer, isReviewMode = false, onSubmit, autoFillCorrectAnswers = false, explanation: explanationProp, }) {
|
|
7
7
|
const { passage, passageTitle, instructions, singleQuestion, explanation: dataExplanation } = questionData;
|
|
8
8
|
const explanation = explanationProp || dataExplanation;
|
|
@@ -38,28 +38,10 @@ export function ReadPassageAndAnswerQuestionsClient({ questionData, userAnswer,
|
|
|
38
38
|
const isCorrect = isReviewMode && selectedAnswer === singleQuestion.correctAnswer;
|
|
39
39
|
const isWrong = isReviewMode && selectedAnswer >= 0 && selectedAnswer !== singleQuestion.correctAnswer;
|
|
40
40
|
const hasAnswered = selectedAnswer >= 0;
|
|
41
|
-
return (_jsxs("div", { className: "space-y-3", children: [
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
? isCorrectOption
|
|
48
|
-
? 'border-green-400 bg-green-50'
|
|
49
|
-
: isSelected
|
|
50
|
-
? 'border-red-400 bg-red-50'
|
|
51
|
-
: 'border-gray-200 bg-gray-50'
|
|
52
|
-
: isSelected
|
|
53
|
-
? 'border-indigo-500 bg-indigo-50 shadow-sm ring-2 ring-indigo-100'
|
|
54
|
-
: 'border-gray-200 bg-white hover:border-gray-300 hover:bg-gray-50 hover:shadow-sm'), children: [_jsx("div", { className: cn('flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-lg text-sm font-bold transition-all', isReviewMode
|
|
55
|
-
? isCorrectOption
|
|
56
|
-
? 'bg-green-500 text-white'
|
|
57
|
-
: isSelected
|
|
58
|
-
? 'bg-red-500 text-white'
|
|
59
|
-
: 'bg-gray-200 text-gray-600'
|
|
60
|
-
: isSelected
|
|
61
|
-
? 'bg-gradient-to-br from-indigo-500 to-purple-600 text-white shadow-sm'
|
|
62
|
-
: 'bg-gray-100 text-gray-600 group-hover:bg-gray-200'), children: letter }), _jsx("span", { className: cn('flex-1 text-sm font-medium', isReviewMode && isCorrectOption ? 'text-green-800' :
|
|
63
|
-
isReviewMode && isSelected && !isCorrectOption ? 'text-red-800' : 'text-gray-700'), children: optText }), isReviewMode && (isCorrectOption || isSelected) && (_jsx("div", { className: cn('flex h-6 w-6 items-center justify-center rounded-full', isCorrectOption ? 'bg-green-500' : 'bg-red-500'), children: isCorrectOption ? _jsx(Check, { className: "h-4 w-4 text-white" }) : _jsx(X, { className: "h-4 w-4 text-white" }) }))] }, optIndex));
|
|
64
|
-
}), !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 mt-3", 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("span", { className: "text-xs font-bold", children: "!" }) }), _jsx("span", { className: "text-sm text-amber-800", children: "Vui l\u00F2ng ch\u1ECDn m\u1ED9t \u0111\u00E1p \u00E1n" })] })), isReviewMode && isWrong && (_jsx("div", { className: "rounded-lg bg-gradient-to-r from-green-50 to-emerald-50 p-4 border border-green-200 mt-3", children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Check, { className: "h-4 w-4 text-green-600" }), _jsxs("span", { className: "text-sm text-green-700", children: ["\u0110\u00E1p \u00E1n \u0111\u00FAng: ", _jsxs("strong", { children: [String.fromCharCode(65 + singleQuestion.correctAnswer), ". ", typeof singleQuestion.options[singleQuestion.correctAnswer] === 'string' ? singleQuestion.options[singleQuestion.correctAnswer] : singleQuestion.options[singleQuestion.correctAnswer]?.text ?? ''] })] })] }) }))] })] }), isReviewMode && explanation && (_jsxs("div", { className: "overflow-hidden rounded-xl border border-blue-200 bg-gradient-to-br from-blue-50 to-indigo-50 shadow-sm", children: [_jsxs("div", { className: "flex items-center gap-2 border-b border-blue-100 bg-white/50 px-4 py-3", children: [_jsx("div", { className: "flex h-7 w-7 items-center justify-center rounded-lg bg-gradient-to-br from-amber-400 to-orange-500 text-white shadow-sm", children: _jsx(Lightbulb, { className: "h-4 w-4" }) }), _jsx("h4", { className: "text-sm font-semibold text-gray-800", children: "Gi\u1EA3i th\u00EDch" })] }), _jsx("div", { className: "p-4", children: _jsx("p", { className: "text-sm text-gray-700 leading-relaxed", children: explanation }) })] }))] }));
|
|
41
|
+
return (_jsxs("div", { className: "space-y-3", children: [_jsx(KidInstruction, { children: instructions }), _jsxs(KidQuestionCard, { icon: _jsx(BookOpen, { className: "h-3.5 w-3.5" }), title: "\u0110o\u1EA1n v\u0103n \u0111\u1ECDc hi\u1EC3u", children: [passageTitle && (_jsx("h3", { className: "text-center text-sm font-bold text-gray-900", children: passageTitle })), _jsx("p", { className: "whitespace-pre-wrap text-sm leading-relaxed text-gray-700", children: passage })] }), _jsxs(KidQuestionCard, { title: "Ch\u1ECDn \u0111\u00E1p \u00E1n \u0111\u00FAng", review: isReviewMode && hasAnswered ? (isCorrect ? 'correct' : 'wrong') : null, children: [_jsx(KidPrompt, { children: singleQuestion.question }), _jsx("div", { className: "space-y-1.5", children: singleQuestion.options.map((option, optIndex) => {
|
|
42
|
+
const isSelected = selectedAnswer === optIndex;
|
|
43
|
+
const isCorrectOption = optIndex === singleQuestion.correctAnswer;
|
|
44
|
+
const optText = typeof option === 'string' ? option : option?.text ?? option?.imageUrl ?? option?.content ?? '';
|
|
45
|
+
return (_jsx(KidChoiceRow, { letter: optionLetter(optIndex), selected: isSelected, isReviewMode: isReviewMode, isCorrect: isReviewMode && isCorrectOption, onClick: () => handleAnswerChange(optIndex), children: optText }, optIndex));
|
|
46
|
+
}) }), !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 mt-3", 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("span", { className: "text-xs font-bold", children: "!" }) }), _jsx("span", { className: "text-sm text-amber-800", children: "Vui l\u00F2ng ch\u1ECDn m\u1ED9t \u0111\u00E1p \u00E1n" })] })), isReviewMode && isWrong && (_jsx("div", { className: "rounded-lg bg-gradient-to-r from-green-50 to-emerald-50 p-4 border border-green-200 mt-3", children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Check, { className: "h-4 w-4 text-green-600" }), _jsxs("span", { className: "text-sm text-green-700", children: ["\u0110\u00E1p \u00E1n \u0111\u00FAng: ", _jsxs("strong", { children: [String.fromCharCode(65 + singleQuestion.correctAnswer), ". ", typeof singleQuestion.options[singleQuestion.correctAnswer] === 'string' ? singleQuestion.options[singleQuestion.correctAnswer] : singleQuestion.options[singleQuestion.correctAnswer]?.text ?? ''] })] })] }) }))] }), isReviewMode && explanation && (_jsxs("div", { className: "overflow-hidden rounded-xl border border-blue-200 bg-gradient-to-br from-blue-50 to-indigo-50 shadow-sm", children: [_jsxs("div", { className: "flex items-center gap-2 border-b border-blue-100 bg-white/50 px-4 py-3", children: [_jsx("div", { className: "flex h-7 w-7 items-center justify-center rounded-lg bg-gradient-to-br from-amber-400 to-orange-500 text-white shadow-sm", children: _jsx(Lightbulb, { className: "h-4 w-4" }) }), _jsx("h4", { className: "text-sm font-semibold text-gray-800", children: "Gi\u1EA3i th\u00EDch" })] }), _jsx("div", { className: "p-4", children: _jsx("p", { className: "text-sm text-gray-700 leading-relaxed", children: explanation }) })] }))] }));
|
|
65
47
|
}
|
package/dist/components/questions/types/sort-words-into-categories/SortWordsIntoCategoriesClient.js
CHANGED
|
@@ -3,6 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import { Lightbulb } from 'lucide-react';
|
|
4
4
|
import { SortWordsIntoCategoriesBoard } from './SortWordsIntoCategoriesBoard';
|
|
5
5
|
import { buildPlacements, padPlacements } from './map-sort-words-into-categories-data';
|
|
6
|
+
import { KidInstruction, KidPrompt, KidQuestionCard } from '../../_shared/components/compact';
|
|
6
7
|
export function SortWordsIntoCategoriesClient({ questionData, isReviewMode = false, userPlacements, onPlacementsChange, }) {
|
|
7
8
|
const correctPlacements = buildPlacements(questionData.words);
|
|
8
9
|
const exampleLocked = questionData.words.map((word) => ({
|
|
@@ -20,5 +21,5 @@ export function SortWordsIntoCategoriesClient({ questionData, isReviewMode = fal
|
|
|
20
21
|
return item;
|
|
21
22
|
});
|
|
22
23
|
const canEdit = Boolean(onPlacementsChange) && !isReviewMode;
|
|
23
|
-
return (_jsxs("div", { className: "space-y-4", children: [
|
|
24
|
+
return (_jsxs("div", { className: "space-y-4", children: [questionData.title ? _jsx(KidPrompt, { children: questionData.title }) : null, _jsx(KidInstruction, { children: questionData.instruction }), _jsx(KidQuestionCard, { title: "S\u1EAFp x\u1EBFp t\u1EEB v\u00E0o nh\u00F3m", children: _jsx(SortWordsIntoCategoriesBoard, { categories: questionData.categories, words: questionData.words, placements: placements, correctPlacements: isReviewMode ? correctPlacements : undefined, onChange: canEdit ? onPlacementsChange : undefined, disabled: !canEdit, isReviewMode: isReviewMode }) }), 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] }))] }));
|
|
24
25
|
}
|