@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.
Files changed (104) hide show
  1. package/dist/components/exams/take/components/QuestionRenderer.js +1 -5
  2. package/dist/components/exams/take/components/question-renderers/MoversCrossOutWordGroupRenderer.js +24 -10
  3. package/dist/components/exams/take/components/question-renderers/MoversCrosswordPuzzleRenderer.js +1 -1
  4. package/dist/components/exams/take/components/question-renderers/MoversFillInBlankGroupRenderer.js +2 -1
  5. package/dist/components/exams/take/components/speaking/renderers/SpeakingSpontaneousQaGroupRenderer.js +12 -2
  6. package/dist/components/exams/take/hooks/useSpontaneousQaGroupRendererSetup.js +2 -1
  7. package/dist/components/exams/take/utils/answer-transformers.js +16 -1
  8. package/dist/components/exams/take/utils/cross-out-word-group.transformer.js +4 -6
  9. package/dist/components/exams/take/utils/question-transformers.d.ts +5 -0
  10. package/dist/components/exams/take/utils/question-transformers.js +6 -0
  11. package/dist/components/questions/_shared/components/compact/KidChoiceRow.d.ts +12 -0
  12. package/dist/components/questions/_shared/components/compact/KidChoiceRow.js +33 -0
  13. package/dist/components/questions/_shared/components/compact/KidInstruction.d.ts +5 -0
  14. package/dist/components/questions/_shared/components/compact/KidInstruction.js +9 -0
  15. package/dist/components/questions/_shared/components/compact/KidPrompt.d.ts +5 -0
  16. package/dist/components/questions/_shared/components/compact/KidPrompt.js +9 -0
  17. package/dist/components/questions/_shared/components/compact/KidQuestionCard.d.ts +12 -0
  18. package/dist/components/questions/_shared/components/compact/KidQuestionCard.js +7 -0
  19. package/dist/components/questions/_shared/components/compact/index.d.ts +4 -0
  20. package/dist/components/questions/_shared/components/compact/index.js +4 -0
  21. package/dist/components/questions/_shared/types/crossword-puzzle.type.d.ts +5 -1
  22. package/dist/components/questions/_shared/types/spontaneous-qa-group.type.d.ts +17 -0
  23. package/dist/components/questions/types/answer-the-question/AnswerTheQuestionClient.js +18 -19
  24. package/dist/components/questions/types/answer-the-question-group/AnswerTheQuestionGroupClient.js +8 -7
  25. package/dist/components/questions/types/answer-the-question-group/AnswerTheQuestionGroupCreator.js +5 -4
  26. package/dist/components/questions/types/arrange-letters-into-words/ArrangeLettersIntoWordsClient.js +17 -11
  27. package/dist/components/questions/types/choose-correct-adjective/ChooseCorrectAdjectiveClient.js +15 -42
  28. package/dist/components/questions/types/choose-the-correct-answer/ChooseTheCorrectAnswerClient.js +15 -36
  29. package/dist/components/questions/types/choose-the-correct-answer-group/ChooseTheCorrectAnswerGroupClient.js +9 -30
  30. package/dist/components/questions/types/choose-then-answer-group/ChooseThenAnswerGroupClient.js +8 -7
  31. package/dist/components/questions/types/cross-out-word-group/CrossOutWordGroupClient.js +50 -32
  32. package/dist/components/questions/types/cross-out-word-group/CrossOutWordGroupCreator.js +24 -44
  33. package/dist/components/questions/types/cross-out-word-group/index.d.ts +1 -0
  34. package/dist/components/questions/types/cross-out-word-group/index.js +1 -0
  35. package/dist/components/questions/types/cross-out-word-group/map-cross-out-word-group-data.js +9 -9
  36. package/dist/components/questions/types/cross-out-word-group/tokenize.d.ts +2 -0
  37. package/dist/components/questions/types/cross-out-word-group/tokenize.js +11 -0
  38. package/dist/components/questions/types/cross-out-word-group/transform.js +2 -4
  39. package/dist/components/questions/types/crossword-puzzle/CrosswordPuzzleClient.d.ts +2 -1
  40. package/dist/components/questions/types/crossword-puzzle/CrosswordPuzzleClient.js +8 -6
  41. package/dist/components/questions/types/crossword-puzzle/CrosswordPuzzleCreator.js +68 -11
  42. package/dist/components/questions/types/crossword-puzzle/transform.js +59 -30
  43. package/dist/components/questions/types/fill-in-blank/FillInBlankClient.js +26 -31
  44. package/dist/components/questions/types/fill-in-blank-group/FillInBlankAnswerKey.d.ts +6 -0
  45. package/dist/components/questions/types/fill-in-blank-group/FillInBlankAnswerKey.js +17 -0
  46. package/dist/components/questions/types/fill-in-blank-group/FillInBlankGroupClient.js +32 -28
  47. package/dist/components/questions/types/fill-in-blank-group/FillInBlankGroupCreator.js +109 -26
  48. package/dist/components/questions/types/fill-in-blank-group/FillInBlankGroupMediaField.js +21 -11
  49. package/dist/components/questions/types/fill-in-blank-group/blank-utils.d.ts +1 -0
  50. package/dist/components/questions/types/fill-in-blank-group/blank-utils.js +4 -1
  51. package/dist/components/questions/types/fill-missing-words-in-grid/FillMissingWordsInGridClient.js +32 -31
  52. package/dist/components/questions/types/find-words-in-matrix/FindWordsInMatrixClient.js +71 -28
  53. package/dist/components/questions/types/find-words-in-matrix/FindWordsInMatrixCreator.js +6 -2
  54. package/dist/components/questions/types/find-words-in-matrix/transform.js +5 -0
  55. package/dist/components/questions/types/label-the-picture/LabelThePictureClient.js +4 -3
  56. package/dist/components/questions/types/listen-and-choose-from-answer-group/ListenAndChooseFromAnswerGroupClient.js +11 -30
  57. package/dist/components/questions/types/listen-and-speak-answer/ListenAndSpeakAnswerClient.js +7 -6
  58. package/dist/components/questions/types/listen-and-speak-compare-images/ListenAndSpeakCompareImagesClient.js +2 -1
  59. package/dist/components/questions/types/listen-and-speak-image-group/ListenAndSpeakImageGroupClient.js +14 -13
  60. package/dist/components/questions/types/listen-and-speak-info-exchange/ListenAndSpeakInfoExchangeClient.js +4 -3
  61. package/dist/components/questions/types/listen-and-speak-odd-one-out/ListenAndSpeakOddOneOutClient.js +4 -3
  62. package/dist/components/questions/types/listen-and-speak-question-list/ListenAndSpeakQuestionListClient.js +11 -10
  63. package/dist/components/questions/types/listen-and-speak-with-story-images/ListenAndSpeakWithStoryImagesClient.js +4 -3
  64. package/dist/components/questions/types/match-by-writing-answer/MatchByWritingAnswerClient.js +13 -11
  65. package/dist/components/questions/types/match-columns-to-make-sentences/MatchColumnsToMakeSentencesClient.js +17 -14
  66. package/dist/components/questions/types/match-word-to-picture/MatchWordToPictureClient.js +16 -12
  67. package/dist/components/questions/types/match-word-to-picture/MatchWordToPictureGroupClient.js +14 -14
  68. package/dist/components/questions/types/match-word-to-picture-group/MatchWordToPictureGroupClient.js +13 -13
  69. package/dist/components/questions/types/matching-with-lines-group/MatchingWithLinesGroupClient.js +7 -6
  70. package/dist/components/questions/types/read-passage-and-answer-questions/ReadPassageAndAnswerQuestionsClient.js +8 -26
  71. package/dist/components/questions/types/sort-words-into-categories/SortWordsIntoCategoriesClient.js +2 -1
  72. package/dist/components/questions/types/speaking-conversation/SpeakingConversationPreviewClient.js +6 -5
  73. package/dist/components/questions/types/speaking-cue-card/SpeakingCueCardClient.js +2 -1
  74. package/dist/components/questions/types/speaking-describe-image/SpeakingDescribeImageClient.js +5 -4
  75. package/dist/components/questions/types/spontaneous-qa-group/SpontaneousQaGroupClient.d.ts +1 -1
  76. package/dist/components/questions/types/spontaneous-qa-group/SpontaneousQaGroupClient.js +54 -11
  77. package/dist/components/questions/types/spontaneous-qa-group/SpontaneousQaGroupCreator.js +11 -7
  78. package/dist/components/questions/types/spontaneous-qa-group/creator-helpers.js +8 -2
  79. package/dist/components/questions/types/spontaneous-qa-group/flatten.d.ts +1 -0
  80. package/dist/components/questions/types/spontaneous-qa-group/flatten.js +4 -2
  81. package/dist/components/questions/types/spontaneous-qa-group/map-spontaneous-qa-group-data.js +5 -3
  82. package/dist/components/questions/types/spontaneous-qa-group/register.js +5 -2
  83. package/dist/components/questions/types/spontaneous-qa-group/transform.js +6 -2
  84. package/dist/components/questions/types/true-false/TrueFalseClient.js +5 -54
  85. package/dist/components/questions/types/true-false-correct-group/TrueFalseCorrectGroupClient.js +21 -23
  86. package/dist/components/questions/types/true-false-group/TrueFalseGroupClient.js +8 -17
  87. package/dist/components/questions/types/word-fill-paragraph/WordFillParagraphClient.js +76 -81
  88. package/dist/components/questions/types/word-fill-structured-form/WordFillStructuredFormClient.js +17 -16
  89. package/dist/components/questions/types/word-order-and-match-group/WordOrderAndMatchGroupClient.js +31 -36
  90. package/dist/components/questions/types/word-ordering/WordOrderingClient.js +15 -14
  91. package/dist/components/questions/types/word-ordering-group/WordOrderingGroupClient.js +9 -8
  92. package/dist/components/questions/types/write-correct-verb-form/WriteCorrectVerbFormClient.js +16 -13
  93. package/dist/components/questions/viewer/CrossOutWordGroupViewer.js +1 -4
  94. package/dist/components/questions/viewer/FillInBlankGroupViewer.d.ts +6 -2
  95. package/dist/components/questions/viewer/FillInBlankGroupViewer.js +13 -7
  96. package/dist/components/questions/viewer/QuestionViewer.js +2 -2
  97. package/dist/components/questions/viewer/SpontaneousQaGroupViewer.d.ts +7 -1
  98. package/dist/components/questions/viewer/SpontaneousQaGroupViewer.js +14 -2
  99. package/dist/components/results/renderers/review-question-body-movers.js +1 -1
  100. package/dist/components/results/review-data.js +8 -1
  101. package/dist/shared/lib/utils/question-reverse-transform.js +31 -5
  102. package/dist/shared/types/questions/crossword-puzzle.d.ts +1 -0
  103. package/dist/shared/types/questions/find-words-in-matrix.d.ts +1 -0
  104. package/package.json +1 -1
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { Check, CircleHelp, Lightbulb, Star, X } from 'lucide-react';
4
- import { cn } from '../../../../shared/lib/utils';
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: [questionData.instruction && (_jsx("div", { className: "rounded-xl border border-indigo-100 bg-indigo-50 px-4 py-3 text-sm font-medium text-indigo-800", children: questionData.instruction })), items.map((item, itemIndex) => {
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
- return (_jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-gray-100 bg-gradient-to-r from-gray-50 to-white px-3 py-2", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-7 w-7 items-center justify-center rounded-lg bg-gradient-to-br from-blue-500 to-indigo-600 text-white", children: _jsx(CircleHelp, { className: "h-3.5 w-3.5" }) }), _jsxs("span", { className: "text-sm font-medium text-gray-700", children: ["C\u00E2u ", item.questionNumber || itemIndex + 1] }), item.isExample && (_jsxs("span", { className: "inline-flex items-center gap-1 rounded-full bg-amber-100 px-2 py-0.5 text-xs font-semibold text-amber-700", children: [_jsx(Star, { className: "h-3 w-3" }), "Example"] }))] }), _jsxs("span", { className: "text-xs text-gray-500", children: [item.points || 0, " \u0111i\u1EC3m"] })] }), _jsxs("div", { className: "space-y-3 p-3", children: [_jsx("p", { className: "text-sm font-semibold leading-relaxed text-gray-800", children: item.question || 'Câu hỏi chưa được nhập' }), _jsx("div", { className: "flex items-center gap-3", children: [
27
- { value: true, label: 'True' },
28
- { value: false, label: 'False' },
29
- ].map((opt) => {
30
- const isCorrect = opt.value === correctIsTrue;
31
- const isSelected = userValue?.isTrue === opt.value;
32
- return (_jsxs("button", { type: "button", disabled: !canSelect, onClick: () => emit(itemIndex, {
33
- isTrue: opt.value,
34
- correction: opt.value ? '' : (userValue?.correction || ''),
35
- }), 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
36
- ? 'border-green-500 bg-green-50 text-green-700'
37
- : isReviewMode && isSelected && !isCorrect
38
- ? 'border-red-500 bg-red-50 text-red-700'
39
- : isSelected
40
- ? 'border-blue-500 bg-blue-50 text-blue-700'
41
- : '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)));
42
- }) }), 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: "Correct the false sentence" }), _jsx("textarea", { value: userValue?.correction || '', onChange: (event) => emit(itemIndex, {
43
- isTrue: false,
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 { Check, CircleHelp, Lightbulb, Star, X } from 'lucide-react';
4
- import { cn } from '../../../../shared/lib/utils';
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: [questionData.instruction && (_jsx("div", { className: "rounded-xl border border-indigo-100 bg-indigo-50 px-4 py-3 text-sm font-medium text-indigo-800", children: questionData.instruction })), items.map((item, itemIndex) => {
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
- return (_jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-gray-100 bg-gradient-to-r from-gray-50 to-white px-3 py-2", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-7 w-7 items-center justify-center rounded-lg bg-gradient-to-br from-blue-500 to-indigo-600 text-white", children: _jsx(CircleHelp, { className: "h-3.5 w-3.5" }) }), _jsxs("span", { className: "text-sm font-medium text-gray-700", children: ["C\u00E2u ", item.questionNumber || itemIndex + 1] }), item.isExample && (_jsxs("span", { className: "inline-flex items-center gap-1 rounded-full bg-amber-100 px-2 py-0.5 text-xs font-semibold text-amber-700", children: [_jsx(Star, { className: "h-3 w-3" }), "Example"] }))] }), _jsxs("span", { className: "text-xs text-gray-500", children: [item.points || 0, " \u0111i\u1EC3m"] })] }), _jsxs("div", { className: "space-y-3 p-3", children: [_jsx("p", { className: "text-sm font-semibold leading-relaxed text-gray-800", children: item.question || 'Câu hỏi chưa được nhập' }), _jsx("div", { className: "flex items-center gap-3", children: [
21
- { value: true, label: 'True' },
22
- { value: false, label: 'False' },
23
- ].map((opt) => {
24
- const isCorrect = opt.value === correctAnswer;
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
- // ── Render ────────────────────────────────────────────────────────────────
148
- const headerGradient = withOptions
149
- ? 'bg-gradient-to-br from-emerald-500 to-teal-600'
150
- : isDefaultMode
151
- ? 'bg-gradient-to-br from-indigo-500 to-blue-600'
152
- : 'bg-gradient-to-br from-teal-500 to-cyan-600';
153
- return (_jsx("div", { className: "space-y-4", children: _jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-gray-100 bg-gradient-to-r from-gray-50 to-white px-3 py-2", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: `flex h-8 w-8 items-center justify-center rounded-lg ${headerGradient} text-white shadow-sm`, children: _jsx(FileText, { className: "h-4 w-4" }) }), _jsx("span", { className: "text-sm font-medium text-gray-700", children: "\u0110i\u1EC1n t\u1EEB v\u00E0o \u0111o\u1EA1n v\u0103n" })] }), isReviewMode && allFilled && (_jsxs("div", { className: cn('flex items-center gap-1.5 rounded-full px-3 py-1 text-xs font-semibold', isAllCorrect ? 'bg-green-100 text-green-700' : 'bg-red-100 text-red-700'), children: [isAllCorrect ? _jsx(Check, { className: "h-3.5 w-3.5" }) : _jsx(X, { className: "h-3.5 w-3.5" }), isAllCorrect ? 'Đúng' : 'Sai'] }))] }), _jsxs("div", { className: "space-y-3 p-3", children: [title && (_jsx("h2", { className: "text-center text-xl font-bold text-gray-900", 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)
154
- ? 'overflow-hidden rounded-xl border border-gray-300 bg-white shadow-sm'
155
- : '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) => {
156
- if (segment.type === 'text') {
157
- return _jsx("span", { children: segment.value }, idx);
158
- }
159
- const blankIdx = segment.index;
160
- const filled = filledAnswers[blankIdx];
161
- const isCorrectFill = isReviewMode && filled === answers[blankIdx];
162
- const isWrongFill = isReviewMode && !!filled && filled !== answers[blankIdx];
163
- const example = isExampleBlank(blankIdx);
164
- // ── Example blank: always shown pre-filled ──────────────────
165
- if (example) {
166
- return _jsx(ExampleBadge, { text: answers[blankIdx] || '___' }, idx);
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-dashed border-gray-300 bg-gray-50 text-gray-400'
201
- : filled
202
- ? 'border-indigo-300 bg-indigo-50 text-indigo-800'
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 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
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" }) }))] }, idx));
219
- }) })] }), !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) => {
220
- const isUsed = usedWords.has(item.word);
221
- const isExampleWord = item.isExample;
222
- const hasImage = item.imageUrl && item.imageUrl.trim() !== '';
223
- return (_jsxs("div", { className: cn('flex flex-col items-center gap-2 rounded-xl border-2 p-3', isExampleWord
224
- ? 'border-amber-300 bg-amber-50'
225
- : isUsed
226
- ? 'border-emerald-200 bg-emerald-50 opacity-60'
227
- : '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));
228
- }) })] })), 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) => {
229
- if (isExampleBlank(index))
230
- return null;
231
- 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));
232
- }) })] })), 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 })] }))] })] }) }));
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
  }
@@ -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-2 border-dashed px-2 py-0.5 text-center text-sm font-semibold transition-all', isReviewMode
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-emerald-500 bg-emerald-50 text-emerald-800'
166
+ ? 'border-green-400 bg-green-50 text-green-800'
166
167
  : isWrongFill
167
- ? 'border-rose-500 bg-rose-50 text-rose-800'
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-2 px-0 uppercase'
179
+ ? 'h-7 w-7 rounded-md border px-0 uppercase'
179
180
  : isPhraseBlank
180
- ? 'min-w-[16rem] max-w-[36rem] w-[28ch] rounded-t border-b-2 px-2 py-0.5'
181
- : 'min-w-[4.5rem] max-w-[8rem] w-[10ch] rounded-t border-b-2 px-1 py-0.5', isReviewMode
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-emerald-500 bg-emerald-50 text-emerald-800 font-bold'
184
+ ? 'border-green-400 bg-green-50 text-green-800'
184
185
  : isWrongFill
185
- ? 'border-rose-500 bg-rose-50 text-rose-800 font-bold'
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 (_jsx("div", { className: "space-y-4", children: _jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm", children: [_jsxs("div", { className: "flex items-center justify-between gap-3 border-b border-gray-100 bg-gradient-to-r from-gray-50 to-white px-3 py-2", children: [_jsxs("div", { className: "flex min-w-0 items-start gap-3", children: [_jsx("div", { className: "mt-0.5 flex h-8 w-8 shrink-0 items-center justify-center rounded-lg bg-gradient-to-br from-teal-500 to-cyan-600 text-white shadow-sm", children: _jsx(FileText, { className: "h-4 w-4" }) }), _jsxs("div", { className: "min-w-0", children: [typeof questionNumber === 'number' && questionNumber > 0 ? (_jsxs("p", { className: "text-[11px] font-semibold uppercase tracking-wider text-teal-700", children: ["B\u00E0i ", questionNumber] })) : null, _jsx("span", { className: "block text-sm font-medium leading-snug text-gray-800", children: headerLabel })] })] }), isReviewMode && allFilled && (_jsxs("div", { className: cn('flex items-center gap-1.5 rounded-full px-3 py-1 text-xs font-semibold', isAllCorrect ? 'bg-green-100 text-green-700' : 'bg-red-100 text-red-700'), children: [isAllCorrect ? _jsx(Check, { className: "h-3.5 w-3.5" }) : _jsx(X, { className: "h-3.5 w-3.5" }), isAllCorrect ? 'Đúng' : 'Sai'] }))] }), _jsxs("div", { className: "space-y-3 p-3", children: [title && (_jsx("h2", { className: "text-center text-xl font-bold text-gray-900", 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) => {
281
- const used = usedSharedWords.has(item.word.trim().toLowerCase());
282
- return (_jsxs("span", { className: cn('rounded-full border px-3 py-1 text-sm font-medium', item.isExample
283
- ? 'border-amber-300 bg-amber-100 text-amber-800'
284
- : used
285
- ? 'border-purple-200 bg-white/60 text-purple-400 line-through'
286
- : 'border-purple-300 bg-white text-purple-800'), children: [item.word, item.isExample ? ' (ex)' : ''] }, `${item.word}-${index}`));
287
- }) })] })), _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 })] }))] })] }) }));
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
  }
@@ -1,8 +1,9 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useState, useEffect, useCallback } from 'react';
4
- import { Check, X, RotateCcw, Sparkles } from 'lucide-react';
4
+ import { X, RotateCcw } from 'lucide-react';
5
5
  import { cn } from '../../../../shared/lib/utils';
6
+ import { KidInstruction, KidQuestionCard } from '../../_shared/components/compact';
6
7
  export function WordOrderAndMatchGroupClient({ questionData, isReviewMode = false, userAnswers, onAnswerChange, }) {
7
8
  const { instruction, matchOptions = ['A', 'B', 'C', 'D', 'E', 'F'], items = [] } = questionData;
8
9
  // Initialize interactive state for each item
@@ -140,7 +141,7 @@ export function WordOrderAndMatchGroupClient({ questionData, isReviewMode = fals
140
141
  return next;
141
142
  });
142
143
  }, [isReviewMode, items, onAnswerChange]);
143
- return (_jsxs("div", { className: "w-full space-y-6", children: [_jsx("div", { className: "rounded-2xl border border-indigo-100 bg-gradient-to-r from-indigo-50/80 via-blue-50/50 to-white p-4 sm:p-5 shadow-sm", children: _jsxs("div", { className: "flex items-start gap-3", children: [_jsx("div", { className: "mt-0.5 rounded-lg bg-indigo-600 p-1.5 text-white shadow-sm", children: _jsx(Sparkles, { className: "h-4 w-4" }) }), _jsxs("div", { className: "flex-1 space-y-3", children: [_jsx("p", { className: "text-sm sm:text-base font-semibold text-gray-900 leading-relaxed", children: instruction || 'Put the useful phrases in order, then match them to parts A–F.' }), matchOptions.length > 0 && (_jsxs("div", { className: "flex flex-wrap items-center gap-1.5 pt-1", children: [_jsx("span", { className: "text-xs font-semibold text-gray-500 mr-1", children: "Match Options:" }), matchOptions.map((opt) => (_jsx("span", { className: "inline-flex h-7 min-w-[28px] items-center justify-center rounded-lg border border-indigo-200 bg-white px-2.5 text-xs font-bold text-indigo-700 shadow-xs", children: opt }, opt)))] }))] })] }) }), _jsx("div", { className: "space-y-4", children: items.map((item, itemIdx) => {
144
+ return (_jsxs("div", { className: "w-full space-y-6", children: [_jsx(KidInstruction, { children: instruction || 'Put the useful phrases in order, then match them to parts A–F.' }), matchOptions.length > 0 && (_jsxs("div", { className: "flex flex-wrap items-center gap-1.5", children: [_jsx("span", { className: "text-xs font-semibold text-gray-500 mr-1", children: "Match Options:" }), matchOptions.map((opt) => (_jsx("span", { className: "inline-flex h-7 min-w-[28px] items-center justify-center rounded-lg border border-blue-200 bg-white px-2.5 text-xs font-bold text-blue-700 shadow-xs", children: opt }, opt)))] })), _jsx("div", { className: "space-y-4", children: items.map((item, itemIdx) => {
144
145
  const state = itemStates[itemIdx] || { placedIndices: [], selectedLabel: '' };
145
146
  const userAns = userAnswers?.[itemIdx];
146
147
  const isExample = item.isExample === true;
@@ -151,40 +152,34 @@ export function WordOrderAndMatchGroupClient({ questionData, isReviewMode = fals
151
152
  const isPhraseCorrect = normalizeStr(userPhrase) === normalizeStr(item.orderedPhrase || '');
152
153
  const isLabelCorrect = (userLabel || '').trim().toUpperCase() === (item.matchLabel || '').trim().toUpperCase();
153
154
  const isItemAllCorrect = isPhraseCorrect && isLabelCorrect;
154
- return (_jsxs("div", { className: cn('overflow-hidden rounded-xl border bg-white shadow-xs transition-all', isExample
155
- ? 'border-amber-200 bg-amber-50/30'
156
- : isReviewMode
157
- ? isItemAllCorrect
158
- ? 'border-emerald-200 bg-emerald-50/20'
159
- : 'border-rose-200 bg-rose-50/20'
160
- : 'border-gray-200 hover:border-indigo-200'), children: [_jsxs("div", { className: "flex items-center justify-between gap-2 border-b border-gray-100 px-3 py-2", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: cn('inline-flex h-6 items-center rounded-md px-2 text-xs font-bold', isExample
161
- ? 'bg-amber-100 text-amber-800'
162
- : 'bg-indigo-100 text-indigo-800'), children: isExample ? ' dụ (Example)' : `Câu ${item.questionNumber || itemIdx + 1}` }), !isExample && typeof item.points === 'number' && (_jsxs("span", { className: "text-xs font-medium text-gray-500", children: ["(", item.points, " ", item.points > 1 ? 'điểm' : 'điểm', ")"] }))] }), isReviewMode && !isExample && (_jsx("div", { className: "flex items-center gap-1.5", children: isItemAllCorrect ? (_jsxs("span", { className: "inline-flex items-center gap-1 rounded-full bg-emerald-100 px-2.5 py-0.5 text-xs font-bold text-emerald-700", children: [_jsx(Check, { className: "h-3.5 w-3.5" }), " \u0110\u00FAng"] })) : (_jsxs("span", { className: "inline-flex items-center gap-1 rounded-full bg-rose-100 px-2.5 py-0.5 text-xs font-bold text-rose-700", children: [_jsx(X, { className: "h-3.5 w-3.5" }), " Sai"] })) }))] }), _jsxs("div", { className: "grid grid-cols-1 gap-4 p-3 lg:grid-cols-12", children: [_jsxs("div", { className: "space-y-3 lg:col-span-8 xl:col-span-9", children: [_jsx("div", { className: "text-xs font-semibold text-gray-500 uppercase tracking-wider", children: "1. S\u1EAFp x\u1EBFp c\u00E1c t\u1EEB th\u00E0nh c\u00E2u ho\u00E0n ch\u1EC9nh:" }), _jsxs("div", { className: cn('min-h-[52px] rounded-xl border-2 border-dashed p-2.5 transition-colors flex flex-wrap items-center gap-1.5', state.placedIndices.length > 0
163
- ? 'border-indigo-300 bg-indigo-50/40'
164
- : 'border-gray-300 bg-gray-50/60'), children: [state.placedIndices.length === 0 ? (_jsx("span", { className: "text-xs italic text-gray-400 select-none", children: "Nh\u1EA5n v\u00E0o c\u00E1c t\u1EEB b\u00EAn d\u01B0\u1EDBi \u0111\u1EC3 gh\u00E9p c\u00E2u..." })) : (state.placedIndices.map((wordIdx, orderIdx) => {
165
- const word = item.scrambledWords[wordIdx];
166
- return (_jsxs("button", { type: "button", disabled: isReviewMode || isExample, onClick: () => handleWordClick(itemIdx, wordIdx), className: cn('group inline-flex items-center gap-1 rounded-lg px-2.5 py-1 text-xs sm:text-sm font-semibold transition-all shadow-xs', isExample
167
- ? 'bg-amber-100 text-amber-900 border border-amber-300 cursor-default'
168
- : isReviewMode
169
- ? 'bg-indigo-100 text-indigo-900 border border-indigo-200 cursor-default'
170
- : 'bg-indigo-600 text-white hover:bg-rose-500 active:scale-95 cursor-pointer'), children: [_jsx("span", { children: word }), !isReviewMode && !isExample && (_jsx(X, { className: "h-3 w-3 opacity-60 group-hover:opacity-100" }))] }, `placed-${wordIdx}-${orderIdx}`));
171
- })), state.placedIndices.length > 0 && !isReviewMode && !isExample && (_jsx("button", { type: "button", onClick: () => handleResetItem(itemIdx), className: "ml-auto inline-flex items-center gap-1 rounded-md p-1 text-xs text-gray-400 hover:bg-white hover:text-gray-700", title: "\u0110\u1EB7t l\u1EA1i", children: _jsx(RotateCcw, { className: "h-3.5 w-3.5" }) }))] }), (!isExample || !isReviewMode) && (_jsx("div", { className: "flex flex-wrap items-center gap-1.5 pt-1", children: item.scrambledWords.map((word, wordIdx) => {
172
- const isPlaced = state.placedIndices.includes(wordIdx);
173
- return (_jsx("button", { type: "button", disabled: isReviewMode || isExample || isPlaced, onClick: () => handleWordClick(itemIdx, wordIdx), className: cn('rounded-lg border px-3 py-1 text-xs sm:text-sm font-medium transition-all', isPlaced
174
- ? 'border-dashed border-gray-200 bg-gray-100 text-gray-300 opacity-50 cursor-not-allowed'
175
- : 'border-gray-300 bg-white text-gray-800 shadow-xs hover:border-indigo-400 hover:bg-indigo-50/50 active:scale-95 cursor-pointer'), children: word }, `word-${wordIdx}`));
176
- }) })), isReviewMode && !isExample && !isPhraseCorrect && (_jsxs("div", { className: "mt-2 rounded-lg bg-emerald-50 border border-emerald-200 p-2.5 text-xs text-emerald-800", children: [_jsx("span", { className: "font-bold", children: "C\u00E2u \u0111\u00FAng:" }), " ", item.orderedPhrase] }))] }), _jsxs("div", { className: "space-y-3 lg:col-span-4 xl:col-span-3 border-t lg:border-t-0 lg:border-l border-gray-100 pt-3 lg:pt-0 lg:pl-4", children: [_jsx("div", { className: "text-xs font-semibold text-gray-500 uppercase tracking-wider", children: "2. Ch\u1ECDn nh\u00E3n n\u1ED1i:" }), _jsx("div", { className: "flex flex-wrap gap-1.5", children: matchOptions.map((opt) => {
177
- const isSelected = state.selectedLabel === opt;
178
- const isCorrectOpt = (item.matchLabel || '').trim().toUpperCase() === opt;
179
- return (_jsx("button", { type: "button", disabled: isReviewMode || isExample, onClick: () => handleLabelSelect(itemIdx, opt), className: cn('h-9 w-9 sm:h-10 sm:w-10 rounded-xl font-bold text-sm transition-all flex items-center justify-center', isReviewMode
180
- ? isCorrectOpt
181
- ? 'bg-emerald-600 text-white ring-2 ring-emerald-300'
182
- : isSelected
183
- ? 'bg-rose-500 text-white line-through'
184
- : 'bg-gray-100 text-gray-400 opacity-60'
155
+ return (_jsx(KidQuestionCard, { title: isExample ? 'Ví dụ (Example)' : `Câu ${item.questionNumber || itemIdx + 1}`, points: !isExample && typeof item.points === 'number' ? item.points : undefined, isExample: isExample, review: isReviewMode && !isExample ? (isItemAllCorrect ? 'correct' : 'wrong') : null, children: _jsxs("div", { className: "grid grid-cols-1 gap-4 lg:grid-cols-12", children: [_jsxs("div", { className: "space-y-3 lg:col-span-8 xl:col-span-9", children: [_jsx("div", { className: "text-xs font-semibold text-gray-500 uppercase tracking-wider", children: "1. S\u1EAFp x\u1EBFp c\u00E1c t\u1EEB th\u00E0nh c\u00E2u ho\u00E0n ch\u1EC9nh:" }), _jsxs("div", { className: cn('min-h-[52px] rounded-xl border-2 border-dashed p-2.5 transition-colors flex flex-wrap items-center gap-1.5', state.placedIndices.length > 0
156
+ ? 'border-blue-300 bg-blue-50/40'
157
+ : 'border-gray-300 bg-gray-50/60'), children: [state.placedIndices.length === 0 ? (_jsx("span", { className: "text-xs italic text-gray-400 select-none", children: "Nh\u1EA5n v\u00E0o c\u00E1c t\u1EEB b\u00EAn d\u01B0\u1EDBi \u0111\u1EC3 gh\u00E9p c\u00E2u..." })) : (state.placedIndices.map((wordIdx, orderIdx) => {
158
+ const word = item.scrambledWords[wordIdx];
159
+ return (_jsxs("button", { type: "button", disabled: isReviewMode || isExample, onClick: () => handleWordClick(itemIdx, wordIdx), className: cn('group inline-flex items-center gap-1 rounded-lg px-2.5 py-1 text-xs sm:text-sm font-semibold transition-all shadow-xs', isExample
160
+ ? 'bg-amber-100 text-amber-900 border border-amber-300 cursor-default'
161
+ : isReviewMode
162
+ ? isPhraseCorrect
163
+ ? 'border border-green-300 bg-green-50 text-green-800'
164
+ : 'border border-red-300 bg-red-50 text-red-800'
165
+ : 'cursor-pointer border border-blue-500 bg-blue-50 text-blue-800 ring-2 ring-blue-100 hover:bg-blue-100 active:scale-95'), children: [_jsx("span", { children: word }), !isReviewMode && !isExample && (_jsx(X, { className: "h-3 w-3 opacity-60 group-hover:opacity-100" }))] }, `placed-${wordIdx}-${orderIdx}`));
166
+ })), state.placedIndices.length > 0 && !isReviewMode && !isExample && (_jsx("button", { type: "button", onClick: () => handleResetItem(itemIdx), className: "ml-auto inline-flex items-center gap-1 rounded-md p-1 text-xs text-gray-400 hover:bg-white hover:text-gray-700", title: "\u0110\u1EB7t l\u1EA1i", children: _jsx(RotateCcw, { className: "h-3.5 w-3.5" }) }))] }), (!isExample || !isReviewMode) && (_jsx("div", { className: "flex flex-wrap items-center gap-1.5 pt-1", children: item.scrambledWords.map((word, wordIdx) => {
167
+ const isPlaced = state.placedIndices.includes(wordIdx);
168
+ return (_jsx("button", { type: "button", disabled: isReviewMode || isExample || isPlaced, onClick: () => handleWordClick(itemIdx, wordIdx), className: cn('rounded-lg border px-3 py-1 text-xs sm:text-sm font-medium transition-all', isPlaced
169
+ ? 'border-dashed border-gray-200 bg-gray-100 text-gray-300 opacity-50 cursor-not-allowed'
170
+ : 'cursor-pointer border-gray-300 bg-white text-gray-800 shadow-xs hover:border-blue-400 hover:bg-blue-50/50 active:scale-95'), children: word }, `word-${wordIdx}`));
171
+ }) })), isReviewMode && !isExample && !isPhraseCorrect && (_jsxs("div", { className: "mt-2 rounded-lg bg-green-50 border border-green-200 p-2.5 text-xs text-green-800", children: [_jsx("span", { className: "font-bold", children: "C\u00E2u \u0111\u00FAng:" }), " ", item.orderedPhrase] }))] }), _jsxs("div", { className: "space-y-3 lg:col-span-4 xl:col-span-3 border-t lg:border-t-0 lg:border-l border-gray-100 pt-3 lg:pt-0 lg:pl-4", children: [_jsx("div", { className: "text-xs font-semibold text-gray-500 uppercase tracking-wider", children: "2. Ch\u1ECDn nh\u00E3n n\u1ED1i:" }), _jsx("div", { className: "flex flex-wrap gap-1.5", children: matchOptions.map((opt) => {
172
+ const isSelected = state.selectedLabel === opt;
173
+ const isCorrectOpt = (item.matchLabel || '').trim().toUpperCase() === opt;
174
+ return (_jsx("button", { type: "button", disabled: isReviewMode || isExample, onClick: () => handleLabelSelect(itemIdx, opt), className: cn('h-9 w-9 sm:h-10 sm:w-10 rounded-xl font-bold text-sm transition-all flex items-center justify-center', isReviewMode
175
+ ? isCorrectOpt
176
+ ? 'bg-green-600 text-white ring-2 ring-green-300'
185
177
  : isSelected
186
- ? 'bg-indigo-600 text-white shadow-md scale-105'
187
- : 'border border-gray-200 bg-white text-gray-700 hover:border-indigo-300 hover:bg-indigo-50/50 cursor-pointer'), children: opt }, `opt-${opt}`));
188
- }) }), isReviewMode && !isExample && !isLabelCorrect && (_jsxs("div", { className: "text-xs text-emerald-800", children: [_jsx("span", { className: "font-bold", children: "Nh\u00E3n \u0111\u00FAng:" }), " [", item.matchLabel, "]"] }))] })] })] }, itemIdx));
178
+ ? 'bg-red-500 text-white line-through'
179
+ : 'bg-gray-100 text-gray-400 opacity-60'
180
+ : isSelected
181
+ ? 'scale-105 border border-blue-500 bg-blue-50 text-blue-800 shadow-md ring-2 ring-blue-100'
182
+ : 'cursor-pointer border border-gray-200 bg-white text-gray-700 hover:border-blue-300 hover:bg-blue-50/50'), children: opt }, `opt-${opt}`));
183
+ }) }), isReviewMode && !isExample && !isLabelCorrect && (_jsxs("div", { className: "text-xs text-green-800", children: [_jsx("span", { className: "font-bold", children: "Nh\u00E3n \u0111\u00FAng:" }), " [", item.matchLabel, "]"] }))] })] }) }, itemIdx));
189
184
  }) })] }));
190
185
  }