@tinyweb_dev/oe-exam-sdk 1.0.9 β 1.0.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/exams/take/ExamTakingPageContainer.js +6 -0
- package/dist/components/exams/take/components/QuestionRenderer.js +7 -7
- 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/question-renderers/MoversMatchWordToPictureGroupRenderer.d.ts +14 -0
- package/dist/components/exams/take/components/question-renderers/MoversMatchWordToPictureGroupRenderer.js +36 -0
- package/dist/components/exams/take/components/question-renderers/MoversMatchingWithLinesGroupRenderer.js +4 -0
- package/dist/components/exams/take/components/question-renderers/index.d.ts +1 -0
- package/dist/components/exams/take/components/question-renderers/index.js +1 -0
- 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 +17 -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 +32 -3
- package/dist/components/exams/take/utils/question-transformers.js +51 -2
- 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/match-word-to-picture-group.type.d.ts +1 -0
- package/dist/components/questions/_shared/types/match-word-to-picture-group.type.js +14 -0
- package/dist/components/questions/_shared/types/matching-with-lines-group.type.d.ts +2 -2
- package/dist/components/questions/_shared/types/spontaneous-qa-group.type.d.ts +17 -0
- package/dist/components/questions/components/QuestionSearchDropdown.js +1 -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 +24 -35
- package/dist/components/questions/types/match-word-to-picture-group/MatchWordToPictureGroupCreator.js +16 -3
- package/dist/components/questions/types/match-word-to-picture-group/MatchWordToPictureGroupImageField.d.ts +2 -1
- package/dist/components/questions/types/match-word-to-picture-group/MatchWordToPictureGroupImageField.js +25 -6
- package/dist/components/questions/types/matching-with-lines-group/MatchingWithLinesBoard.js +1 -1
- package/dist/components/questions/types/matching-with-lines-group/MatchingWithLinesGroupClient.js +22 -10
- package/dist/components/questions/types/matching-with-lines-group/MatchingWithLinesGroupCreator.js +60 -18
- package/dist/components/questions/types/matching-with-lines-group/MatchingWithLinesImageField.d.ts +2 -1
- package/dist/components/questions/types/matching-with-lines-group/MatchingWithLinesImageField.js +24 -5
- package/dist/components/questions/types/matching-with-lines-group/map-matching-with-lines-group-data.js +8 -4
- package/dist/components/questions/types/matching-with-lines-group/transform.js +5 -4
- 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 +7 -3
- package/dist/components/results/review-data.js +20 -1
- package/dist/shared/lib/utils/question-reverse-transform.js +48 -7
- 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/dist/shared/types/questions/matching-with-lines-group.d.ts +2 -2
- package/package.json +1 -1
|
@@ -1,20 +1,17 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useState, useEffect } from 'react';
|
|
4
|
-
import {
|
|
4
|
+
import { Lightbulb } from 'lucide-react';
|
|
5
5
|
import { usePresignedFileUrl } from '../../../../shared/lib/hooks';
|
|
6
|
-
import {
|
|
6
|
+
import { KidChoiceRow, KidPrompt, KidQuestionCard } from '../../_shared/components/compact';
|
|
7
7
|
export function TrueFalseClient({ questionData, onSubmit, isReviewMode = false, userAnswer, }) {
|
|
8
8
|
const [selectedAnswer, setSelectedAnswer] = useState(userAnswer !== undefined ? userAnswer : undefined);
|
|
9
|
-
// Presigned URL for image
|
|
10
9
|
const { previewUrl: imagePreviewUrl, isLoading } = usePresignedFileUrl(questionData.imageUrl || '');
|
|
11
|
-
// Sync userAnswer
|
|
12
10
|
useEffect(() => {
|
|
13
11
|
if (userAnswer !== undefined) {
|
|
14
12
|
setSelectedAnswer(userAnswer);
|
|
15
13
|
}
|
|
16
14
|
}, [userAnswer]);
|
|
17
|
-
// Handle answer change
|
|
18
15
|
const handleAnswerChange = (value) => {
|
|
19
16
|
if (isReviewMode)
|
|
20
17
|
return;
|
|
@@ -23,53 +20,7 @@ export function TrueFalseClient({ questionData, onSubmit, isReviewMode = false,
|
|
|
23
20
|
};
|
|
24
21
|
const hasAnswered = selectedAnswer !== undefined;
|
|
25
22
|
const isAnswerCorrect = isReviewMode && selectedAnswer === questionData.answer;
|
|
26
|
-
return (
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
/* eslint-disable-next-line @next/next/no-img-element */
|
|
30
|
-
_jsx("img", { src: imagePreviewUrl || questionData.imageUrl, alt: "Question illustration", className: "max-h-56 w-auto rounded-md object-contain" })) })] })), _jsxs("div", { className: "space-y-2.5", children: [_jsx("span", { className: "text-xs font-medium uppercase tracking-wider text-gray-500", children: "Ch\u1ECDn \u0111\u00E1p \u00E1n" }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("button", { type: "button", onClick: () => handleAnswerChange(true), disabled: isReviewMode, className: cn('group flex items-center justify-center gap-3 rounded-xl border-2 p-4 transition-all', isReviewMode
|
|
31
|
-
? questionData.answer === true
|
|
32
|
-
? 'border-green-400 bg-green-50'
|
|
33
|
-
: selectedAnswer === true
|
|
34
|
-
? 'border-red-400 bg-red-50'
|
|
35
|
-
: 'border-gray-200 bg-gray-50'
|
|
36
|
-
: selectedAnswer === true
|
|
37
|
-
? 'border-emerald-500 bg-emerald-50 shadow-sm ring-2 ring-emerald-100'
|
|
38
|
-
: 'border-gray-200 bg-white hover:border-emerald-300 hover:bg-emerald-50/50 hover:shadow-sm'), children: [_jsx("div", { className: cn('flex h-9 w-9 items-center justify-center rounded-full text-sm font-bold transition-all', isReviewMode
|
|
39
|
-
? questionData.answer === true
|
|
40
|
-
? 'bg-green-500 text-white'
|
|
41
|
-
: selectedAnswer === true
|
|
42
|
-
? 'bg-red-500 text-white'
|
|
43
|
-
: 'bg-gray-200 text-gray-600'
|
|
44
|
-
: selectedAnswer === true
|
|
45
|
-
? 'bg-emerald-500 text-white shadow-sm'
|
|
46
|
-
: 'bg-gray-100 text-gray-500 group-hover:bg-emerald-100 group-hover:text-emerald-600'), children: _jsx(Check, { className: "h-4 w-4" }) }), _jsx("span", { className: cn('text-base font-semibold', isReviewMode && questionData.answer === true
|
|
47
|
-
? 'text-green-800'
|
|
48
|
-
: isReviewMode && selectedAnswer === true
|
|
49
|
-
? 'text-red-800'
|
|
50
|
-
: selectedAnswer === true
|
|
51
|
-
? 'text-emerald-700'
|
|
52
|
-
: 'text-gray-600'), children: "\u0110\u00FAng (True)" }), isReviewMode && (questionData.answer === true || selectedAnswer === true) && (_jsx("div", { className: cn('flex h-6 w-6 items-center justify-center rounded-full', questionData.answer === true ? 'bg-green-500' : 'bg-red-500'), children: questionData.answer === true ? (_jsx(Check, { className: "h-4 w-4 text-white" })) : (_jsx(X, { className: "h-4 w-4 text-white" })) }))] }), _jsxs("button", { type: "button", onClick: () => handleAnswerChange(false), disabled: isReviewMode, className: cn('group flex items-center justify-center gap-3 rounded-xl border-2 p-4 transition-all', isReviewMode
|
|
53
|
-
? questionData.answer === false
|
|
54
|
-
? 'border-green-400 bg-green-50'
|
|
55
|
-
: selectedAnswer === false
|
|
56
|
-
? 'border-red-400 bg-red-50'
|
|
57
|
-
: 'border-gray-200 bg-gray-50'
|
|
58
|
-
: selectedAnswer === false
|
|
59
|
-
? 'border-red-500 bg-red-50 shadow-sm ring-2 ring-red-100'
|
|
60
|
-
: 'border-gray-200 bg-white hover:border-red-300 hover:bg-red-50/50 hover:shadow-sm'), children: [_jsx("div", { className: cn('flex h-9 w-9 items-center justify-center rounded-full text-sm font-bold transition-all', isReviewMode
|
|
61
|
-
? questionData.answer === false
|
|
62
|
-
? 'bg-green-500 text-white'
|
|
63
|
-
: selectedAnswer === false
|
|
64
|
-
? 'bg-red-500 text-white'
|
|
65
|
-
: 'bg-gray-200 text-gray-600'
|
|
66
|
-
: selectedAnswer === false
|
|
67
|
-
? 'bg-red-500 text-white shadow-sm'
|
|
68
|
-
: 'bg-gray-100 text-gray-500 group-hover:bg-red-100 group-hover:text-red-600'), children: _jsx(X, { className: "h-4 w-4" }) }), _jsx("span", { className: cn('text-base font-semibold', isReviewMode && questionData.answer === false
|
|
69
|
-
? 'text-green-800'
|
|
70
|
-
: isReviewMode && selectedAnswer === false
|
|
71
|
-
? 'text-red-800'
|
|
72
|
-
: selectedAnswer === false
|
|
73
|
-
? 'text-red-700'
|
|
74
|
-
: 'text-gray-600'), children: "Sai (False)" }), isReviewMode && (questionData.answer === false || selectedAnswer === false) && (_jsx("div", { className: cn('flex h-6 w-6 items-center justify-center rounded-full', questionData.answer === false ? 'bg-green-500' : 'bg-red-500'), children: questionData.answer === false ? (_jsx(Check, { className: "h-4 w-4 text-white" })) : (_jsx(X, { className: "h-4 w-4 text-white" })) }))] })] })] }), !isReviewMode && !hasAnswered && (_jsxs("div", { className: "flex items-center gap-2 rounded-lg border border-amber-200 bg-gradient-to-r from-amber-50 to-yellow-50 p-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 \u0111\u00E1p \u00E1n" })] })), isReviewMode && questionData.explanation && (_jsxs("div", { className: "rounded-lg border border-blue-200 bg-blue-50 p-4", children: [_jsx("p", { className: "text-sm font-semibold text-blue-800", children: "Gi\u1EA3i th\u00EDch:" }), _jsx("p", { className: "mt-1 text-sm text-blue-700", children: questionData.explanation })] }))] })] }) }));
|
|
23
|
+
return (_jsxs("div", { className: "space-y-4", children: [_jsxs(KidQuestionCard, { title: "\u0110\u00FAng / Sai", isExample: questionData.isExample, review: isReviewMode && hasAnswered ? (isAnswerCorrect ? 'correct' : 'wrong') : null, children: [_jsx(KidPrompt, { children: questionData.question || 'ChΖ°a nhαΊp cΓ’u hα»i' }), (imagePreviewUrl || questionData.imageUrl) && (_jsxs("div", { className: "overflow-hidden rounded-lg border border-gray-200 bg-gradient-to-br from-slate-50 to-gray-100", children: [_jsx("div", { className: "border-b border-gray-100 px-3 py-1.5", children: _jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx("div", { className: "h-2 w-2 rounded-full bg-red-400" }), _jsx("div", { className: "h-2 w-2 rounded-full bg-yellow-400" }), _jsx("div", { className: "h-2 w-2 rounded-full bg-green-400" }), _jsx("span", { className: "ml-2 text-xs font-medium text-gray-500", children: "H\u00ECnh \u1EA3nh" })] }) }), _jsx("div", { className: "flex items-center justify-center p-4", children: isLoading ? (_jsx("div", { className: "flex h-48 items-center justify-center", children: _jsx("div", { className: "h-8 w-8 animate-spin rounded-full border-4 border-gray-200 border-t-blue-500" }) })) : (
|
|
24
|
+
/* eslint-disable-next-line @next/next/no-img-element */
|
|
25
|
+
_jsx("img", { src: imagePreviewUrl || questionData.imageUrl, alt: "H\u00ECnh \u1EA3nh c\u00E2u h\u1ECFi", className: "max-h-56 w-auto rounded-md object-contain" })) })] })), _jsxs("div", { className: "space-y-1.5", children: [_jsx(KidChoiceRow, { letter: "T", selected: selectedAnswer === true, isReviewMode: isReviewMode, isCorrect: isReviewMode && questionData.answer === true, onClick: () => handleAnswerChange(true), children: "\u0110\u00FAng" }), _jsx(KidChoiceRow, { letter: "F", selected: selectedAnswer === false, isReviewMode: isReviewMode, isCorrect: isReviewMode && questionData.answer === false, onClick: () => handleAnswerChange(false), children: "Sai" })] }), !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: "Vui l\u00F2ng ch\u1ECDn \u0111\u00E1p \u00E1n" })] }))] }), isReviewMode && questionData.explanation && (_jsxs("div", { className: "flex items-start gap-2 rounded-md border border-amber-200 bg-amber-50 px-3 py-2", children: [_jsx(Lightbulb, { className: "mt-0.5 h-3.5 w-3.5 shrink-0 text-amber-600" }), _jsxs("p", { className: "text-xs text-gray-700", children: [_jsx("span", { className: "font-semibold text-gray-800", children: "Gi\u1EA3i th\u00EDch: " }), questionData.explanation] })] }))] }));
|
|
75
26
|
}
|
package/dist/components/questions/types/true-false-correct-group/TrueFalseCorrectGroupClient.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { Lightbulb } from 'lucide-react';
|
|
4
|
+
import { KidChoiceRow, KidInstruction, KidPrompt, KidQuestionCard } from '../../_shared/components/compact';
|
|
5
5
|
function emptyAnswer() {
|
|
6
6
|
return { isTrue: null, correction: '' };
|
|
7
7
|
}
|
|
@@ -16,32 +16,30 @@ export function TrueFalseCorrectGroupClient({ questionData, isReviewMode = false
|
|
|
16
16
|
next[itemIndex] = nextItem;
|
|
17
17
|
onAnswerChange(next);
|
|
18
18
|
};
|
|
19
|
-
return (_jsxs("div", { className: "space-y-4", children: [
|
|
19
|
+
return (_jsxs("div", { className: "space-y-4", children: [_jsx(KidInstruction, { children: questionData.instruction }), items.map((item, itemIndex) => {
|
|
20
20
|
const correctIsTrue = item.isTrue === true;
|
|
21
21
|
const userValue = item.isExample
|
|
22
22
|
? { isTrue: item.isTrue, correction: item.correction }
|
|
23
23
|
: userAnswers[itemIndex];
|
|
24
24
|
const canSelect = !isReviewMode && !item.isExample && Boolean(onAnswerChange);
|
|
25
25
|
const showStudentCorrection = userValue?.isTrue === false || (isReviewMode && !correctIsTrue);
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
correction: event.target.value,
|
|
45
|
-
}), placeholder: "Write the correct sentence...", rows: 2, className: "w-full rounded-lg border border-slate-200 bg-white px-3 py-2 text-sm text-slate-800 outline-none ring-blue-200 focus:border-blue-400 focus:ring-2" })] })) : null, isReviewMode && !correctIsTrue ? (_jsxs("div", { className: "rounded-lg border border-amber-100 bg-amber-50/70 px-3 py-2", children: [_jsx("p", { className: "text-xs font-semibold uppercase tracking-wide text-amber-800", children: "C\u00E2u \u0111\u00FAng" }), _jsx("p", { className: "mt-1 text-sm text-amber-900", children: item.correction || 'ChΖ°a nhαΊp cΓ’u sα»a' }), userValue && userValue.isTrue === false ? (_jsxs("p", { className: "mt-2 text-xs text-slate-600", children: ["H\u1ECDc sinh: ", userValue.correction || '(trα»ng)'] })) : null] })) : null] })] }, `${item.questionNumber}-${itemIndex}`));
|
|
26
|
+
const hasAnswered = userValue?.isTrue === true || userValue?.isTrue === false;
|
|
27
|
+
const isAnswerCorrect = userValue?.isTrue === correctIsTrue;
|
|
28
|
+
return (_jsxs(KidQuestionCard, { title: `CΓ’u ${item.questionNumber || itemIndex + 1}`, points: item.points || 0, isExample: item.isExample, review: isReviewMode && !item.isExample && hasAnswered
|
|
29
|
+
? (isAnswerCorrect ? 'correct' : 'wrong')
|
|
30
|
+
: null, children: [_jsx(KidPrompt, { children: item.question || 'CΓ’u hα»i chΖ°a Δược nhαΊp' }), _jsxs("div", { className: "space-y-1.5", children: [_jsx(KidChoiceRow, { letter: "T", selected: userValue?.isTrue === true, isReviewMode: isReviewMode, isCorrect: isReviewMode && correctIsTrue, disabled: !canSelect, onClick: canSelect
|
|
31
|
+
? () => emit(itemIndex, {
|
|
32
|
+
isTrue: true,
|
|
33
|
+
correction: '',
|
|
34
|
+
})
|
|
35
|
+
: undefined, children: "\u0110\u00FAng" }), _jsx(KidChoiceRow, { letter: "F", selected: userValue?.isTrue === false, isReviewMode: isReviewMode, isCorrect: isReviewMode && !correctIsTrue, disabled: !canSelect, onClick: canSelect
|
|
36
|
+
? () => emit(itemIndex, {
|
|
37
|
+
isTrue: false,
|
|
38
|
+
correction: userValue?.correction || '',
|
|
39
|
+
})
|
|
40
|
+
: undefined, children: "Sai" })] }), showStudentCorrection && canSelect ? (_jsxs("div", { className: "rounded-lg border border-slate-200 bg-white px-3 py-2", children: [_jsx("label", { className: "mb-1.5 block text-xs font-semibold uppercase tracking-wide text-slate-500", children: "S\u1EEDa c\u00E2u sai" }), _jsx("textarea", { value: userValue?.correction || '', onChange: (event) => emit(itemIndex, {
|
|
41
|
+
isTrue: false,
|
|
42
|
+
correction: event.target.value,
|
|
43
|
+
}), placeholder: "Vi\u1EBFt c\u00E2u \u0111\u00FAng...", rows: 2, className: "w-full rounded-lg border border-slate-200 bg-white px-3 py-2 text-sm text-slate-800 outline-none ring-blue-200 focus:border-blue-400 focus:ring-2" })] })) : null, isReviewMode && !correctIsTrue ? (_jsxs("div", { className: "rounded-lg border border-amber-100 bg-amber-50/70 px-3 py-2", children: [_jsx("p", { className: "text-xs font-semibold uppercase tracking-wide text-amber-800", children: "C\u00E2u \u0111\u00FAng" }), _jsx("p", { className: "mt-1 text-sm text-amber-900", children: item.correction || 'ChΖ°a nhαΊp cΓ’u sα»a' }), userValue && userValue.isTrue === false ? (_jsxs("p", { className: "mt-2 text-xs text-slate-600", children: ["H\u1ECDc sinh: ", userValue.correction || '(trα»ng)'] })) : null] })) : null] }, `${item.questionNumber}-${itemIndex}`));
|
|
46
44
|
}), 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] }))] }));
|
|
47
45
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { Lightbulb } from 'lucide-react';
|
|
4
|
+
import { KidChoiceRow, KidInstruction, KidPrompt, KidQuestionCard } from '../../_shared/components/compact';
|
|
5
5
|
export function TrueFalseGroupClient({ questionData, isReviewMode = false, userAnswers = [], onAnswerChange, }) {
|
|
6
6
|
const items = questionData.items || [];
|
|
7
7
|
const handleSelect = (itemIndex, value) => {
|
|
@@ -13,23 +13,14 @@ export function TrueFalseGroupClient({ questionData, isReviewMode = false, userA
|
|
|
13
13
|
next[itemIndex] = value;
|
|
14
14
|
onAnswerChange(next);
|
|
15
15
|
};
|
|
16
|
-
return (_jsxs("div", { className: "space-y-4", children: [
|
|
16
|
+
return (_jsxs("div", { className: "space-y-4", children: [_jsx(KidInstruction, { children: questionData.instruction }), items.map((item, itemIndex) => {
|
|
17
17
|
const userValue = item.isExample ? item.correctAnswer : userAnswers[itemIndex];
|
|
18
18
|
const correctAnswer = item.correctAnswer;
|
|
19
19
|
const canSelect = !isReviewMode && !item.isExample && Boolean(onAnswerChange);
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const isSelected = opt.value === userValue;
|
|
26
|
-
return (_jsxs("button", { type: "button", disabled: !canSelect, onClick: () => handleSelect(itemIndex, opt.value), className: cn('flex flex-1 items-center justify-center gap-2 rounded-lg border-2 px-4 py-2 text-sm font-semibold transition-colors', canSelect ? 'cursor-pointer hover:border-blue-300' : 'cursor-default', isReviewMode && isCorrect
|
|
27
|
-
? 'border-green-500 bg-green-50 text-green-700'
|
|
28
|
-
: isReviewMode && isSelected && !isCorrect
|
|
29
|
-
? 'border-red-500 bg-red-50 text-red-700'
|
|
30
|
-
: isSelected
|
|
31
|
-
? 'border-blue-500 bg-blue-50 text-blue-700'
|
|
32
|
-
: 'border-gray-200 bg-white text-gray-700'), children: [opt.value ? _jsx(Check, { className: "h-4 w-4" }) : _jsx(X, { className: "h-4 w-4" }), _jsx("span", { children: opt.label })] }, String(opt.value)));
|
|
33
|
-
}) })] })] }, `${item.questionNumber}-${itemIndex}`));
|
|
20
|
+
const hasAnswered = userValue !== undefined;
|
|
21
|
+
const isAnswerCorrect = userValue === correctAnswer;
|
|
22
|
+
return (_jsxs(KidQuestionCard, { title: `CΓ’u ${item.questionNumber || itemIndex + 1}`, points: item.points || 0, isExample: item.isExample, review: isReviewMode && !item.isExample && hasAnswered
|
|
23
|
+
? (isAnswerCorrect ? 'correct' : 'wrong')
|
|
24
|
+
: null, children: [_jsx(KidPrompt, { children: item.question || 'CΓ’u hα»i chΖ°a Δược nhαΊp' }), _jsxs("div", { className: "space-y-1.5", children: [_jsx(KidChoiceRow, { letter: "T", selected: userValue === true, isReviewMode: isReviewMode, isCorrect: isReviewMode && correctAnswer === true, disabled: !canSelect, onClick: canSelect ? () => handleSelect(itemIndex, true) : undefined, children: "\u0110\u00FAng" }), _jsx(KidChoiceRow, { letter: "F", selected: userValue === false, isReviewMode: isReviewMode, isCorrect: isReviewMode && correctAnswer === false, disabled: !canSelect, onClick: canSelect ? () => handleSelect(itemIndex, false) : undefined, children: "Sai" })] })] }, `${item.questionNumber}-${itemIndex}`));
|
|
34
25
|
}), 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] }))] }));
|
|
35
26
|
}
|
|
@@ -4,6 +4,7 @@ import { useState, useEffect, useCallback, useMemo } from 'react';
|
|
|
4
4
|
import { Check, X, FileText, Star, Mail } from 'lucide-react';
|
|
5
5
|
import { usePresignedFileUrl } from '../../../../shared/lib/hooks';
|
|
6
6
|
import { cn } from '../../../../shared/lib/utils';
|
|
7
|
+
import { KidPrompt, KidQuestionCard } from '../../_shared/components/compact';
|
|
7
8
|
function parseParagraph(paragraph) {
|
|
8
9
|
const segments = [];
|
|
9
10
|
const regex = /\{(\d+)\}/g;
|
|
@@ -144,90 +145,84 @@ export function WordFillParagraphClient({ questionData, onSubmit, isReviewMode =
|
|
|
144
145
|
.map(idx => ({ blankIdx: idx, answer: answers[idx] || '' }))
|
|
145
146
|
.filter(e => e.answer);
|
|
146
147
|
}, [allBlanks, isExampleBlank, answers]);
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
}
|
|
168
|
-
// ββ WITH_OPTIONS blank ββββββββββββββββββββββββββββββββββββββ
|
|
169
|
-
if (withOptions) {
|
|
170
|
-
const isExpanded = expandedBlank === blankIdx;
|
|
171
|
-
const blankOptions = wordBank[blankIdx]?.options || [];
|
|
172
|
-
return (_jsxs("span", { className: "relative mx-1 inline-flex flex-col items-center align-middle", children: [_jsxs("button", { type: "button", onClick: () => !isReviewMode && setExpandedBlank(isExpanded ? null : blankIdx), disabled: isReviewMode, className: cn('inline-flex min-w-[80px] items-center rounded border-2 border-dashed px-3 py-0.5 text-center text-sm font-semibold transition-all', isReviewMode
|
|
173
|
-
? isCorrectFill
|
|
174
|
-
? 'border-green-400 bg-green-50 text-green-800'
|
|
175
|
-
: isWrongFill
|
|
176
|
-
? 'border-red-400 bg-red-50 text-red-800'
|
|
177
|
-
: 'border-gray-300 bg-gray-50 text-gray-400'
|
|
178
|
-
: isExpanded
|
|
179
|
-
? 'border-teal-500 bg-teal-50 text-teal-700 ring-2 ring-teal-200'
|
|
180
|
-
: filled
|
|
181
|
-
? 'cursor-pointer border-teal-300 bg-teal-50 text-teal-700 hover:bg-teal-100'
|
|
182
|
-
: 'cursor-pointer border-gray-300 bg-white text-gray-400 hover:border-teal-300 hover:bg-teal-50'), children: [filled || `(${blankIdx + 1})`, isReviewMode && filled && (isCorrectFill
|
|
183
|
-
? _jsx(Check, { className: "ml-1 h-3.5 w-3.5 text-green-600" })
|
|
184
|
-
: _jsx(X, { className: "ml-1 h-3.5 w-3.5 text-red-600" }))] }), isExpanded && !isReviewMode && blankOptions.length > 0 && (_jsx("div", { className: "absolute left-1/2 top-full z-10 mt-1 flex -translate-x-1/2 gap-1 rounded-lg border border-teal-200 bg-white p-1.5 shadow-lg", children: blankOptions.map((opt, optIdx) => (_jsx("button", { type: "button", onClick: (e) => {
|
|
185
|
-
e.stopPropagation();
|
|
186
|
-
handleSelectOption(blankIdx, opt);
|
|
187
|
-
setExpandedBlank(null);
|
|
188
|
-
}, className: cn('whitespace-nowrap rounded-md px-2.5 py-1 text-sm font-medium transition-all', filled === opt
|
|
189
|
-
? 'bg-teal-500 text-white'
|
|
190
|
-
: 'bg-gray-50 text-gray-700 hover:bg-teal-100 hover:text-teal-700'), children: opt }, optIdx))) }))] }, idx));
|
|
191
|
-
}
|
|
192
|
-
// ββ WITH_WORD_BANK or DEFAULT blank βββββββββββββββββββββββββ
|
|
193
|
-
if (isDefaultMode) {
|
|
194
|
-
// DEFAULT: read-only inputs in review, otherwise show filled/empty state
|
|
195
|
-
return (_jsxs("span", { className: cn('mx-1 inline-flex min-w-[80px] items-center justify-center rounded border-2 px-3 py-0.5 text-sm font-semibold align-middle', isReviewMode
|
|
148
|
+
return (_jsx("div", { className: "space-y-4", children: _jsxs(KidQuestionCard, { icon: _jsx(FileText, { className: "h-3.5 w-3.5" }), title: "\u0110i\u1EC1n t\u1EEB v\u00E0o \u0111o\u1EA1n v\u0103n", points: questionData.points, isExample: questionData.isExample, review: isReviewMode && allFilled ? (isAllCorrect ? 'correct' : 'wrong') : null, children: [title && (_jsx(KidPrompt, { children: _jsx("h2", { className: "text-sm font-semibold text-gray-800", children: title }) })), exampleEntries.length > 0 && (_jsxs("div", { className: "flex flex-wrap items-center gap-2 rounded-lg border border-amber-200 bg-amber-50 px-4 py-2.5", children: [_jsxs("div", { className: "flex items-center gap-1.5 text-xs font-semibold text-amber-700 shrink-0", children: [_jsx(Star, { className: "h-3.5 w-3.5 fill-amber-400 text-amber-400" }), "\u0110\u00E3 \u0111i\u1EC1n s\u1EB5n (Example):"] }), exampleEntries.map(({ blankIdx, answer }) => (_jsxs("span", { className: "inline-flex items-center gap-1 rounded-md border border-amber-300 bg-white px-2 py-0.5 text-xs font-semibold text-amber-800", children: [_jsx("span", { className: "text-amber-500", children: '{' + blankIdx + '}' }), answer] }, blankIdx)))] })), (imagePreviewUrl || imageUrl) && (_jsx("div", { className: "flex justify-center", children: _jsx("div", { className: "overflow-hidden rounded-lg border border-gray-200 bg-gray-50", children: imageLoading ? (_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-teal-500" }) })) : (_jsx("img", { src: imagePreviewUrl || imageUrl, alt: title, className: "max-h-56 w-auto object-contain p-2" })) }) })), _jsxs("div", { className: (emailFrom || emailTo)
|
|
149
|
+
? 'overflow-hidden rounded-xl border border-gray-300 bg-white shadow-sm'
|
|
150
|
+
: 'rounded-lg border border-gray-200 bg-gradient-to-r from-gray-50 to-slate-50', children: [(emailFrom || emailTo) && (_jsxs(_Fragment, { children: [_jsxs("div", { className: "flex items-center gap-1.5 border-b border-gray-200 bg-gray-100 px-4 py-2.5", children: [_jsx("span", { className: "h-3 w-3 rounded-full bg-red-400" }), _jsx("span", { className: "h-3 w-3 rounded-full bg-yellow-400" }), _jsx("span", { className: "h-3 w-3 rounded-full bg-green-400" }), _jsxs("div", { className: "ml-3 flex items-center gap-1.5 text-xs text-gray-400", children: [_jsx(Mail, { className: "h-3.5 w-3.5" }), _jsx("span", { children: "Email" })] })] }), _jsxs("div", { className: "border-b border-gray-200 bg-gray-50 px-3 py-2 space-y-1.5 text-sm", children: [emailFrom && (_jsxs("div", { className: "flex items-baseline gap-2", children: [_jsx("span", { className: "w-10 shrink-0 text-xs font-semibold uppercase tracking-wide text-gray-400", children: "From" }), _jsx("span", { className: "font-medium text-gray-800", children: emailFrom })] })), emailTo && (_jsxs("div", { className: "flex items-baseline gap-2", children: [_jsx("span", { className: "w-10 shrink-0 text-xs font-semibold uppercase tracking-wide text-gray-400", children: "To" }), _jsx("span", { className: "font-medium text-gray-800", children: emailTo })] }))] })] })), _jsx("div", { className: `text-base leading-loose text-gray-800 whitespace-pre-wrap ${(emailFrom || emailTo) ? 'p-5 bg-white' : 'p-5 bg-gradient-to-r from-gray-50 to-slate-50'}`, children: segments.map((segment, idx) => {
|
|
151
|
+
if (segment.type === 'text') {
|
|
152
|
+
return _jsx("span", { children: segment.value }, idx);
|
|
153
|
+
}
|
|
154
|
+
const blankIdx = segment.index;
|
|
155
|
+
const filled = filledAnswers[blankIdx];
|
|
156
|
+
const isCorrectFill = isReviewMode && filled === answers[blankIdx];
|
|
157
|
+
const isWrongFill = isReviewMode && !!filled && filled !== answers[blankIdx];
|
|
158
|
+
const example = isExampleBlank(blankIdx);
|
|
159
|
+
// ββ Example blank: always shown pre-filled ββββββββββββββββββ
|
|
160
|
+
if (example) {
|
|
161
|
+
return _jsx(ExampleBadge, { text: answers[blankIdx] || '___' }, idx);
|
|
162
|
+
}
|
|
163
|
+
// ββ WITH_OPTIONS blank ββββββββββββββββββββββββββββββββββββββ
|
|
164
|
+
if (withOptions) {
|
|
165
|
+
const isExpanded = expandedBlank === blankIdx;
|
|
166
|
+
const blankOptions = wordBank[blankIdx]?.options || [];
|
|
167
|
+
return (_jsxs("span", { className: "relative mx-1 inline-flex flex-col items-center align-middle", children: [_jsxs("button", { type: "button", onClick: () => !isReviewMode && setExpandedBlank(isExpanded ? null : blankIdx), disabled: isReviewMode, className: cn('inline-flex min-w-[80px] items-center rounded-md border border-dashed px-3 py-1.5 text-center text-sm font-semibold transition-all', isReviewMode
|
|
196
168
|
? isCorrectFill
|
|
197
169
|
? 'border-green-400 bg-green-50 text-green-800'
|
|
198
170
|
: isWrongFill
|
|
199
171
|
? 'border-red-400 bg-red-50 text-red-800'
|
|
200
|
-
: 'border-
|
|
201
|
-
:
|
|
202
|
-
? 'border-
|
|
203
|
-
: 'border-dashed border-gray-300 bg-white text-gray-400'), children: [filled || `(${blankIdx + 1})`, isReviewMode && filled && (isCorrectFill
|
|
204
|
-
? _jsx(Check, { className: "ml-1 h-3.5 w-3.5 text-green-600" })
|
|
205
|
-
: _jsx(X, { className: "ml-1 h-3.5 w-3.5 text-red-600" }))] }, idx));
|
|
206
|
-
}
|
|
207
|
-
// WITH_WORD_BANK input field
|
|
208
|
-
return (_jsxs("span", { className: "mx-1 inline-flex items-center align-middle", children: [_jsx("input", { type: "text", value: filled || '', onChange: (e) => handleInputChange(blankIdx, e.target.value), disabled: isReviewMode, placeholder: `(${blankIdx + 1})`, className: cn('inline-flex min-w-[80px] max-w-[120px] rounded border-2 border-dashed px-3 py-0.5 text-center text-sm font-semibold transition-all outline-none', isReviewMode
|
|
209
|
-
? isCorrectFill
|
|
210
|
-
? 'border-green-400 bg-green-50 text-green-800'
|
|
211
|
-
: isWrongFill
|
|
212
|
-
? 'border-red-400 bg-red-50 text-red-800'
|
|
213
|
-
: 'border-gray-300 bg-gray-50 text-gray-400'
|
|
172
|
+
: 'border-gray-300 bg-gray-50 text-gray-400'
|
|
173
|
+
: isExpanded
|
|
174
|
+
? 'border-teal-500 bg-teal-50 text-teal-700 ring-2 ring-teal-200'
|
|
214
175
|
: filled
|
|
215
|
-
? 'border-teal-300 bg-teal-50 text-teal-700'
|
|
216
|
-
: 'border-gray-300 bg-white text-gray-400
|
|
217
|
-
? _jsx(Check, { className: "h-3.5 w-3.5 text-green-600" })
|
|
218
|
-
: _jsx(X, { className: "h-3.5 w-3.5 text-red-600" }) }))
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
176
|
+
? 'cursor-pointer border-teal-300 bg-teal-50 text-teal-700 hover:bg-teal-100'
|
|
177
|
+
: 'cursor-pointer border-gray-300 bg-white text-gray-400 hover:border-teal-300 hover:bg-teal-50'), children: [filled || `(${blankIdx + 1})`, isReviewMode && filled && (isCorrectFill
|
|
178
|
+
? _jsx(Check, { className: "ml-1 h-3.5 w-3.5 text-green-600" })
|
|
179
|
+
: _jsx(X, { className: "ml-1 h-3.5 w-3.5 text-red-600" }))] }), isExpanded && !isReviewMode && blankOptions.length > 0 && (_jsx("div", { className: "absolute left-1/2 top-full z-10 mt-1 flex -translate-x-1/2 gap-1 rounded-lg border border-teal-200 bg-white p-1.5 shadow-lg", children: blankOptions.map((opt, optIdx) => (_jsx("button", { type: "button", onClick: (e) => {
|
|
180
|
+
e.stopPropagation();
|
|
181
|
+
handleSelectOption(blankIdx, opt);
|
|
182
|
+
setExpandedBlank(null);
|
|
183
|
+
}, className: cn('whitespace-nowrap rounded-md px-2.5 py-1 text-sm font-medium transition-all', filled === opt
|
|
184
|
+
? 'bg-teal-500 text-white'
|
|
185
|
+
: 'bg-gray-50 text-gray-700 hover:bg-teal-100 hover:text-teal-700'), children: opt }, optIdx))) }))] }, idx));
|
|
186
|
+
}
|
|
187
|
+
// ββ WITH_WORD_BANK or DEFAULT blank βββββββββββββββββββββββββ
|
|
188
|
+
if (isDefaultMode) {
|
|
189
|
+
// DEFAULT: read-only inputs in review, otherwise show filled/empty state
|
|
190
|
+
return (_jsxs("span", { className: cn('mx-1 inline-flex min-w-[80px] items-center justify-center rounded-md border px-3 py-1.5 text-sm font-semibold align-middle', isReviewMode
|
|
191
|
+
? isCorrectFill
|
|
192
|
+
? 'border-green-400 bg-green-50 text-green-800'
|
|
193
|
+
: isWrongFill
|
|
194
|
+
? 'border-red-400 bg-red-50 text-red-800'
|
|
195
|
+
: 'border-dashed border-gray-300 bg-gray-50 text-gray-400'
|
|
196
|
+
: filled
|
|
197
|
+
? 'border-indigo-300 bg-indigo-50 text-indigo-800'
|
|
198
|
+
: 'border-dashed border-gray-300 bg-white text-gray-400'), children: [filled || `(${blankIdx + 1})`, isReviewMode && filled && (isCorrectFill
|
|
199
|
+
? _jsx(Check, { className: "ml-1 h-3.5 w-3.5 text-green-600" })
|
|
200
|
+
: _jsx(X, { className: "ml-1 h-3.5 w-3.5 text-red-600" }))] }, idx));
|
|
201
|
+
}
|
|
202
|
+
// WITH_WORD_BANK input field
|
|
203
|
+
return (_jsxs("span", { className: "mx-1 inline-flex items-center align-middle", children: [_jsx("input", { type: "text", value: filled || '', onChange: (e) => handleInputChange(blankIdx, e.target.value), disabled: isReviewMode, placeholder: `(${blankIdx + 1})`, className: cn('inline-flex min-w-[80px] max-w-[120px] rounded-md border border-dashed px-3 py-1.5 text-center text-sm font-semibold transition-all outline-none', isReviewMode
|
|
204
|
+
? isCorrectFill
|
|
205
|
+
? 'border-green-400 bg-green-50 text-green-800'
|
|
206
|
+
: isWrongFill
|
|
207
|
+
? 'border-red-400 bg-red-50 text-red-800'
|
|
208
|
+
: 'border-gray-300 bg-gray-50 text-gray-400'
|
|
209
|
+
: filled
|
|
210
|
+
? 'border-teal-300 bg-teal-50 text-teal-700'
|
|
211
|
+
: 'border-gray-300 bg-white text-gray-400 placeholder:text-gray-300', 'focus:border-teal-500 focus:bg-teal-50 focus:text-teal-700 focus:ring-2 focus:ring-teal-200', isReviewMode ? 'cursor-default' : 'cursor-text') }), isReviewMode && filled && (_jsx("span", { className: "ml-1", children: isCorrectFill
|
|
212
|
+
? _jsx(Check, { className: "h-3.5 w-3.5 text-green-600" })
|
|
213
|
+
: _jsx(X, { className: "h-3.5 w-3.5 text-red-600" }) }))] }, idx));
|
|
214
|
+
}) })] }), !withOptions && !isDefaultMode && (_jsxs("div", { className: "space-y-2", children: [_jsx("span", { className: "text-xs font-medium uppercase tracking-wider text-gray-500", children: "Word Bank (Reference)" }), _jsx("div", { className: "grid grid-cols-2 gap-3 sm:grid-cols-3 md:grid-cols-4", children: wordBank.map((item, index) => {
|
|
215
|
+
const isUsed = usedWords.has(item.word);
|
|
216
|
+
const isExampleWord = item.isExample;
|
|
217
|
+
const hasImage = item.imageUrl && item.imageUrl.trim() !== '';
|
|
218
|
+
return (_jsxs("div", { className: cn('flex flex-col items-center gap-2 rounded-xl border-2 p-3', isExampleWord
|
|
219
|
+
? 'border-amber-300 bg-amber-50'
|
|
220
|
+
: isUsed
|
|
221
|
+
? 'border-emerald-200 bg-emerald-50 opacity-60'
|
|
222
|
+
: 'border-gray-200 bg-gray-50'), children: [hasImage && _jsx(WordBankImage, { imageUrl: item.imageUrl }), _jsxs("div", { className: "flex flex-col items-center gap-1", children: [_jsx("span", { className: cn('text-sm font-semibold', isExampleWord ? 'text-amber-700' : isUsed ? 'text-emerald-700' : 'text-gray-700'), children: item.word }), isExampleWord && (_jsxs("span", { className: "flex items-center gap-0.5 rounded-full bg-amber-100 px-1.5 py-0.5 text-[10px] font-semibold text-amber-600", children: [_jsx(Star, { className: "h-2.5 w-2.5 fill-amber-400 text-amber-400" }), "example"] })), isUsed && !isExampleWord && (_jsx("span", { className: "flex items-center gap-0.5 rounded-full bg-emerald-100 px-1.5 py-0.5 text-[10px] font-semibold text-emerald-600", children: "used" }))] })] }, index));
|
|
223
|
+
}) })] })), 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: answers.map((answer, index) => {
|
|
224
|
+
if (isExampleBlank(index))
|
|
225
|
+
return null;
|
|
226
|
+
return (_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: ['{' + index + '}', ": ", answer] }, index));
|
|
227
|
+
}) })] })), 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 })] }))] }) }));
|
|
233
228
|
}
|
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
|
}
|