@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,9 +1,10 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useState, useEffect, useCallback, useMemo } from 'react';
4
- import { Check, X, ArrowUpDown, Lightbulb } from 'lucide-react';
4
+ import { Check, X, ArrowUpDown } from 'lucide-react';
5
5
  import { usePresignedFileUrl } from '../../../../shared/lib/hooks';
6
6
  import { cn } from '../../../../shared/lib/utils';
7
+ import { KidQuestionCard } from '../../_shared/components/compact';
7
8
  function toWordList(value) {
8
9
  if (Array.isArray(value)) {
9
10
  return value.filter((item) => typeof item === 'string');
@@ -86,17 +87,17 @@ export function WordOrderingClient({ questionData, onSubmit, isReviewMode = fals
86
87
  const isWrong = isReviewMode && selectedWords.length > 0 && !isCorrect;
87
88
  const isComplete = selectedWords.length === words.length;
88
89
  const displayExplanation = explanation || questionData.explanation;
89
- return (_jsx("div", { className: "space-y-4", children: _jsxs("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-md bg-gradient-to-br from-amber-500 to-orange-600 text-white", children: _jsx(ArrowUpDown, { className: "h-3.5 w-3.5" }) }), _jsx("span", { className: "text-sm font-medium text-gray-700", children: "S\u1EAFp x\u1EBFp t\u1EEB" }), isExample && (_jsxs("span", { className: "inline-flex items-center gap-1 rounded-full bg-amber-100 px-2.5 py-0.5 text-xs font-semibold text-amber-700", children: [_jsx(Lightbulb, { className: "h-3 w-3" }), "V\u00ED d\u1EE5"] }))] }), isReviewMode && isComplete && (_jsxs("div", { className: cn('flex items-center gap-1.5 rounded-full px-3 py-1 text-xs font-semibold', isCorrect ? 'bg-green-100 text-green-700' : 'bg-red-100 text-red-700'), children: [isCorrect ? _jsx(Check, { className: "h-3.5 w-3.5" }) : _jsx(X, { className: "h-3.5 w-3.5" }), isCorrect ? 'Đúng' : 'Sai'] }))] }), _jsxs("div", { className: "space-y-3 p-3", children: [(imagePreviewUrl || imageUrl) && (_jsx("div", { className: "flex justify-center", children: _jsx("div", { className: "overflow-hidden rounded-lg border border-gray-200 bg-gray-50", children: isLoading ? (_jsx("div", { className: "flex h-48 w-48 items-center justify-center", children: _jsx("div", { className: "h-8 w-8 animate-spin rounded-full border-4 border-gray-200 border-t-amber-500" }) })) : (_jsx("img", { src: imagePreviewUrl || imageUrl, alt: "H\u00ECnh \u1EA3nh c\u00E2u h\u1ECFi", className: "max-h-56 w-auto object-contain p-2" })) }) })), _jsxs("div", { className: "space-y-2", children: [_jsx("span", { className: "text-xs font-medium text-gray-500 uppercase tracking-wider", children: "\u0110\u00E1p \u00E1n c\u1EE7a b\u1EA1n" }), _jsx("div", { className: "flex flex-wrap gap-2 min-h-[48px] rounded-lg border-2 border-dashed border-gray-300 bg-gray-50 p-3", children: selectedWords.length === 0 ? (_jsx("span", { className: "text-sm text-gray-400 italic", children: "Nh\u1EA5n v\u00E0o c\u00E1c t\u1EEB b\u00EAn d\u01B0\u1EDBi \u0111\u1EC3 s\u1EAFp x\u1EBFp..." })) : (selectedWords.map((word, index) => {
90
- const isWordCorrect = isReviewMode && answer[index] === word;
91
- const isWordWrong = isReviewMode && answer[index] !== word;
92
- return (_jsxs("button", { type: "button", onClick: () => handleRemoveWord(index), disabled: isReviewMode, className: cn('inline-flex items-center gap-1 rounded-lg px-3 py-2 text-sm font-semibold transition-all shadow-sm', isReviewMode
93
- ? isWordCorrect
94
- ? 'bg-green-100 text-green-800 border border-green-300'
95
- : 'bg-red-100 text-red-800 border border-red-300'
96
- : 'bg-amber-100 text-amber-800 border border-amber-300 hover:bg-amber-200 cursor-pointer'), children: [word, isReviewMode && (isWordCorrect
97
- ? _jsx(Check, { className: "h-3.5 w-3.5 text-green-600" })
98
- : _jsx(X, { className: "h-3.5 w-3.5 text-red-600" }))] }, `selected-${index}`));
99
- })) })] }), availableWords.length > 0 && (_jsxs("div", { className: "space-y-2", children: [_jsx("span", { className: "text-xs font-medium text-gray-500 uppercase tracking-wider", children: "T\u1EEB c\u00F3 s\u1EB5n" }), _jsx("div", { className: "flex flex-wrap gap-2", children: availableWords.map((word, index) => (_jsx("button", { type: "button", onClick: () => handleSelectWord(word, index), disabled: isReviewMode, className: cn('rounded-lg border-2 px-3 py-2 text-sm font-semibold transition-all shadow-sm', isReviewMode
100
- ? 'border-gray-200 bg-gray-100 text-gray-500 cursor-default'
101
- : 'border-blue-200 bg-blue-50 text-blue-700 hover:bg-blue-100 hover:border-blue-300 cursor-pointer hover:shadow-md'), children: word }, `available-${index}`))) })] })), isReviewMode && isWrong && (_jsxs("div", { className: "rounded-lg border border-green-200 bg-green-50 p-3", children: [_jsx("span", { className: "text-xs font-medium text-green-600 uppercase tracking-wider", children: "\u0110\u00E1p \u00E1n \u0111\u00FAng" }), _jsx("div", { className: "mt-2 flex flex-wrap gap-2", children: answer.map((word, index) => (_jsx("span", { className: "rounded-lg bg-green-100 px-3 py-1.5 text-sm font-semibold text-green-800 border border-green-300", children: word }, `correct-${index}`))) })] })), isReviewMode && displayExplanation && (_jsxs("div", { className: "rounded-lg border border-blue-200 bg-gradient-to-r from-blue-50 to-indigo-50 p-3", children: [_jsx("span", { className: "text-xs font-medium text-blue-600 uppercase tracking-wider", children: "Gi\u1EA3i th\u00EDch" }), _jsx("p", { className: "mt-1 text-sm text-blue-800", children: displayExplanation })] }))] })] }) }));
90
+ return (_jsx("div", { className: "space-y-4", children: _jsxs(KidQuestionCard, { icon: _jsx(ArrowUpDown, { className: "h-3.5 w-3.5" }), title: "S\u1EAFp x\u1EBFp t\u1EEB", isExample: isExample, review: isReviewMode && isComplete ? (isCorrect ? 'correct' : 'wrong') : null, children: [(imagePreviewUrl || imageUrl) && (_jsx("div", { className: "flex justify-center", children: _jsx("div", { className: "overflow-hidden rounded-lg border border-gray-200 bg-gray-50", children: isLoading ? (_jsx("div", { className: "flex h-48 w-48 items-center justify-center", children: _jsx("div", { className: "h-8 w-8 animate-spin rounded-full border-4 border-gray-200 border-t-amber-500" }) })) : (_jsx("img", { src: imagePreviewUrl || imageUrl, alt: "H\u00ECnh \u1EA3nh c\u00E2u h\u1ECFi", className: "max-h-56 w-auto object-contain p-2" })) }) })), _jsxs("div", { className: "space-y-2", children: [_jsx("span", { className: "text-xs font-medium text-gray-500 uppercase tracking-wider", children: "\u0110\u00E1p \u00E1n c\u1EE7a b\u1EA1n" }), _jsx("div", { className: "flex flex-wrap gap-2 min-h-[48px] rounded-lg border-2 border-dashed border-gray-300 bg-gray-50 p-3", children: selectedWords.length === 0 ? (_jsx("span", { className: "text-sm text-gray-400 italic", children: "Nh\u1EA5n v\u00E0o c\u00E1c t\u1EEB b\u00EAn d\u01B0\u1EDBi \u0111\u1EC3 s\u1EAFp x\u1EBFp..." })) : (selectedWords.map((word, index) => {
91
+ const isWordCorrect = isReviewMode && answer[index] === word;
92
+ const isWordWrong = isReviewMode && answer[index] !== word;
93
+ return (_jsxs("button", { type: "button", onClick: () => handleRemoveWord(index), disabled: isReviewMode, className: cn('inline-flex items-center gap-1 rounded-lg px-3 py-2 text-sm font-semibold transition-all shadow-sm', isReviewMode
94
+ ? isWordCorrect
95
+ ? 'bg-green-100 text-green-800 border border-green-300'
96
+ : 'bg-red-100 text-red-800 border border-red-300'
97
+ : 'cursor-pointer border border-blue-500 bg-blue-50 text-blue-800 ring-2 ring-blue-100 hover:bg-blue-100'), children: [word, isReviewMode && (isWordCorrect
98
+ ? _jsx(Check, { className: "h-3.5 w-3.5 text-green-600" })
99
+ : _jsx(X, { className: "h-3.5 w-3.5 text-red-600" }))] }, `selected-${index}`));
100
+ })) })] }), availableWords.length > 0 && (_jsxs("div", { className: "space-y-2", children: [_jsx("span", { className: "text-xs font-medium text-gray-500 uppercase tracking-wider", children: "T\u1EEB c\u00F3 s\u1EB5n" }), _jsx("div", { className: "flex flex-wrap gap-2", children: availableWords.map((word, index) => (_jsx("button", { type: "button", onClick: () => handleSelectWord(word, index), disabled: isReviewMode, className: cn('rounded-lg border-2 px-3 py-2 text-sm font-semibold transition-all shadow-sm', isReviewMode
101
+ ? 'border-gray-200 bg-gray-100 text-gray-500 cursor-default'
102
+ : 'border-blue-200 bg-blue-50 text-blue-700 hover:bg-blue-100 hover:border-blue-300 cursor-pointer hover:shadow-md'), children: word }, `available-${index}`))) })] })), isReviewMode && isWrong && (_jsxs("div", { className: "rounded-lg border border-green-200 bg-green-50 p-3", children: [_jsx("span", { className: "text-xs font-medium text-green-600 uppercase tracking-wider", children: "\u0110\u00E1p \u00E1n \u0111\u00FAng" }), _jsx("div", { className: "mt-2 flex flex-wrap gap-2", children: answer.map((word, index) => (_jsx("span", { className: "rounded-lg bg-green-100 px-3 py-1.5 text-sm font-semibold text-green-800 border border-green-300", children: word }, `correct-${index}`))) })] })), isReviewMode && displayExplanation && (_jsxs("div", { className: "rounded-lg border border-blue-200 bg-gradient-to-r from-blue-50 to-indigo-50 p-3", children: [_jsx("span", { className: "text-xs font-medium text-blue-600 uppercase tracking-wider", children: "Gi\u1EA3i th\u00EDch" }), _jsx("p", { className: "mt-1 text-sm text-blue-800", children: displayExplanation })] }))] }) }));
102
103
  }
@@ -1,7 +1,8 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { Lightbulb, Star } from 'lucide-react';
3
+ import { Lightbulb } from 'lucide-react';
4
4
  import { WordOrderingClient } from '../word-ordering/WordOrderingClient';
5
+ import { KidInstruction, KidQuestionCard } from '../../_shared/components/compact';
5
6
  export function WordOrderingGroupClient({ questionData, isReviewMode = false, userAnswers = [], onAnswerChange, }) {
6
7
  const items = questionData.items || [];
7
8
  const handleItemSubmit = (itemIndex, ordered) => {
@@ -16,11 +17,11 @@ export function WordOrderingGroupClient({ questionData, isReviewMode = false, us
16
17
  });
17
18
  onAnswerChange(next);
18
19
  };
19
- return (_jsxs("div", { className: "space-y-4", children: [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) => (_jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-gray-100 px-3 py-2", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_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"] })] }), _jsx("div", { className: "space-y-3 p-3", children: _jsx(WordOrderingClient, { questionData: {
20
- words: item.words,
21
- answer: item.answer,
22
- isExample: item.isExample,
23
- points: item.points,
24
- imageUrl: item.imageUrl,
25
- }, isReviewMode: isReviewMode || item.isExample, userAnswer: item.isExample ? item.answer : userAnswers[itemIndex], onSubmit: (ordered) => handleItemSubmit(itemIndex, ordered) }) })] }, `${item.questionNumber}-${itemIndex}`))), isReviewMode && questionData.explanation && (_jsxs("div", { className: "flex items-start gap-2 rounded-xl border border-amber-100 bg-amber-50 px-4 py-3 text-sm text-amber-800", children: [_jsx(Lightbulb, { className: "mt-0.5 h-4 w-4 flex-shrink-0" }), questionData.explanation] }))] }));
20
+ return (_jsxs("div", { className: "space-y-4", children: [_jsx(KidInstruction, { children: questionData.instruction }), items.map((item, itemIndex) => (_jsx(KidQuestionCard, { title: `Câu ${item.questionNumber || itemIndex + 1}`, points: item.points || 0, isExample: item.isExample, children: _jsx(WordOrderingClient, { questionData: {
21
+ words: item.words,
22
+ answer: item.answer,
23
+ isExample: item.isExample,
24
+ points: item.points,
25
+ imageUrl: item.imageUrl,
26
+ }, isReviewMode: isReviewMode || item.isExample, userAnswer: item.isExample ? item.answer : userAnswers[itemIndex], onSubmit: (ordered) => handleItemSubmit(itemIndex, ordered) }) }, `${item.questionNumber}-${itemIndex}`))), isReviewMode && questionData.explanation && (_jsxs("div", { className: "flex items-start gap-2 rounded-xl border border-amber-100 bg-amber-50 px-4 py-3 text-sm text-amber-800", children: [_jsx(Lightbulb, { className: "mt-0.5 h-4 w-4 flex-shrink-0" }), questionData.explanation] }))] }));
26
27
  }
@@ -3,7 +3,8 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useState, useEffect } from 'react';
4
4
  import { Input } from '../../../../components/ui/input';
5
5
  import { cn } from '../../../../shared/lib/utils';
6
- import { Check, X, FileText, PenLine, AlertTriangle, Lightbulb } from 'lucide-react';
6
+ import { Check, X, PenLine, AlertTriangle, Lightbulb } from 'lucide-react';
7
+ import { KidInstruction, KidPrompt, KidQuestionCard } from '../../_shared/components/compact';
7
8
  export function WriteCorrectVerbFormClient({ questionData, onSubmit, isReviewMode = false, userAnswer = '', }) {
8
9
  // ============ STATE ============
9
10
  const [answer, setAnswer] = useState(userAnswer || '');
@@ -39,7 +40,9 @@ export function WriteCorrectVerbFormClient({ questionData, onSubmit, isReviewMod
39
40
  const parts = questionData.sentence.split(/(\{0\})/g);
40
41
  return (_jsx("div", { className: "flex flex-wrap items-center gap-1.5 text-lg leading-relaxed", children: parts.map((part, index) => {
41
42
  if (part === '{0}') {
42
- return (_jsxs("span", { className: "inline-flex items-center gap-2", children: [_jsxs("div", { className: "relative inline-flex items-center", children: [_jsx(Input, { value: questionData.correctAnswer, readOnly: true, disabled: true, className: cn('w-36 text-center font-semibold border-2 rounded-lg transition-all', 'border-green-400 bg-green-50 text-green-800 cursor-not-allowed') }), isReviewMode && (_jsx("div", { className: cn('absolute -right-7 top-1/2 -translate-y-1/2 flex h-5 w-5 items-center justify-center rounded-full', isCorrect ? 'bg-green-500' : isWrong ? 'bg-red-500' : 'bg-gray-300'), children: isCorrect ? (_jsx(Check, { className: "h-3 w-3 text-white" })) : isWrong ? (_jsx(X, { className: "h-3 w-3 text-white" })) : null }))] }), _jsxs("span", { className: "rounded-lg bg-amber-100 px-2 py-0.5 text-sm font-medium text-amber-700", children: ["(", questionData.verbHint, ")"] })] }, index));
43
+ return (_jsxs("span", { className: "inline-flex items-center gap-2", children: [_jsxs("div", { className: "relative inline-flex items-center", children: [_jsx(Input, { value: questionData.correctAnswer, readOnly: true, disabled: true, className: cn('w-36 text-sm text-center font-semibold border px-3 py-1.5 rounded-md transition-all cursor-not-allowed', isReviewMode && isWrong
44
+ ? 'border-red-400 bg-red-50 text-red-800'
45
+ : 'border-green-400 bg-green-50 text-green-800') }), isReviewMode && (_jsx("div", { className: cn('absolute -right-7 top-1/2 -translate-y-1/2 flex h-5 w-5 items-center justify-center rounded-full', isCorrect ? 'bg-green-500' : isWrong ? 'bg-red-500' : 'bg-gray-300'), children: isCorrect ? (_jsx(Check, { className: "h-3 w-3 text-white" })) : isWrong ? (_jsx(X, { className: "h-3 w-3 text-white" })) : null }))] }), _jsxs("span", { className: "rounded-lg bg-amber-100 px-2 py-0.5 text-sm font-medium text-amber-700", children: ["(", questionData.verbHint, ")"] })] }, index));
43
46
  }
44
47
  else {
45
48
  return _jsx("span", { className: "text-gray-800", children: part }, index);
@@ -47,15 +50,15 @@ export function WriteCorrectVerbFormClient({ questionData, onSubmit, isReviewMod
47
50
  }) }));
48
51
  };
49
52
  // ============ RENDER ============
50
- return (_jsxs("div", { className: "space-y-3", children: [questionData.instruction && (_jsx("div", { className: "overflow-hidden rounded-xl border border-blue-200 bg-gradient-to-r from-blue-50 to-indigo-50 shadow-sm", children: _jsxs("div", { className: "flex items-center gap-2 px-3 py-2", children: [_jsx("div", { className: "flex h-7 w-7 flex-shrink-0 items-center justify-center rounded-md bg-gradient-to-br from-blue-500 to-indigo-600 text-white", children: _jsx(FileText, { className: "h-3.5 w-3.5" }) }), _jsxs("div", { children: [_jsx("h3", { className: "text-sm font-semibold text-blue-900", children: "H\u01B0\u1EDBng d\u1EABn" }), _jsx("p", { className: "text-sm text-blue-700", children: questionData.instruction })] })] }) })), _jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm", children: [_jsxs("div", { className: cn('flex items-center justify-between border-b px-3 py-2', isReviewMode && isCorrect && 'border-green-200 bg-gradient-to-r from-green-50 to-emerald-50', isReviewMode && isWrong && 'border-red-200 bg-gradient-to-r from-red-50 to-rose-50', (!isReviewMode || (!isCorrect && !isWrong)) && 'border-gray-100 bg-gradient-to-r from-gray-50 to-white'), children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: cn('flex h-7 w-7 items-center justify-center rounded-md text-white', isReviewMode && isCorrect && 'bg-gradient-to-br from-green-500 to-emerald-600', isReviewMode && isWrong && 'bg-gradient-to-br from-red-500 to-rose-600', (!isReviewMode || (!isCorrect && !isWrong)) && 'bg-gradient-to-br from-violet-500 to-purple-600'), children: _jsx(PenLine, { className: "h-3.5 w-3.5" }) }), _jsx("span", { className: "text-sm font-medium text-gray-700", children: "Chia \u0111\u1ED9ng t\u1EEB \u0111\u00FAng" })] }), isReviewMode && hasAnswered && (_jsxs("div", { className: cn('flex items-center gap-1.5 rounded-full px-3 py-1 text-xs font-semibold', isCorrect ? 'bg-green-100 text-green-700' : 'bg-red-100 text-red-700'), children: [isCorrect ? _jsx(Check, { className: "h-3.5 w-3.5" }) : _jsx(X, { className: "h-3.5 w-3.5" }), isCorrect ? 'Đúng' : 'Sai'] }))] }), _jsxs("div", { className: "space-y-3 p-3", children: [_jsx("div", { className: "rounded-xl bg-gradient-to-br from-slate-50 to-gray-50 border border-gray-100 p-5", children: renderSentenceWithInput() }), isReviewMode && (_jsxs("div", { className: "space-y-3", children: [_jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { className: cn('rounded-xl border-2 p-4', isCorrect
51
- ? 'border-green-200 bg-green-50'
52
- : isWrong
53
- ? 'border-red-200 bg-red-50'
54
- : 'border-gray-200 bg-gray-50'), children: [_jsxs("div", { className: "flex items-center gap-2 mb-2", children: [_jsx("div", { className: cn('flex h-6 w-6 items-center justify-center rounded-lg', isCorrect ? 'bg-green-200' : isWrong ? 'bg-red-200' : 'bg-gray-200'), children: isCorrect ? (_jsx(Check, { className: "h-3.5 w-3.5 text-green-700" })) : isWrong ? (_jsx(X, { className: "h-3.5 w-3.5 text-red-700" })) : (_jsx("span", { className: "text-xs text-gray-500", children: "?" })) }), _jsx("span", { className: "text-xs font-medium text-gray-600", children: "C\u00E2u tr\u1EA3 l\u1EDDi c\u1EE7a b\u1EA1n" })] }), _jsx("p", { className: cn('text-lg font-bold', isCorrect ? 'text-green-700' : isWrong ? 'text-red-700' : 'text-gray-400'), children: answer || '(Chưa trả lời)' })] }), _jsxs("div", { className: "rounded-xl border-2 border-green-200 bg-gradient-to-br from-green-50 to-emerald-50 p-4", children: [_jsxs("div", { className: "flex items-center gap-2 mb-2", children: [_jsx("div", { className: "flex h-6 w-6 items-center justify-center rounded-lg bg-green-200", children: _jsx(Check, { className: "h-3.5 w-3.5 text-green-700" }) }), _jsx("span", { className: "text-xs font-medium text-gray-600", children: "\u0110\u00E1p \u00E1n \u0111\u00FAng" })] }), _jsx("p", { className: "text-lg font-bold text-green-700", children: questionData.correctAnswer })] })] }), _jsx("div", { className: cn('rounded-xl p-4', isCorrect
55
- ? 'bg-gradient-to-r from-green-50 to-emerald-50 border border-green-200'
56
- : 'bg-gradient-to-r from-red-50 to-rose-50 border border-red-200'), children: _jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: cn('flex h-10 w-10 items-center justify-center rounded-xl text-white shadow-sm', isCorrect
57
- ? 'bg-gradient-to-br from-green-500 to-emerald-600'
58
- : 'bg-gradient-to-br from-red-500 to-rose-600'), children: isCorrect ? _jsx(Check, { className: "h-5 w-5" }) : _jsx(X, { className: "h-5 w-5" }) }), _jsxs("div", { children: [_jsx("p", { className: cn('text-sm font-semibold', isCorrect ? 'text-green-800' : 'text-red-800'), children: isCorrect ? 'Chính xác!' : 'Chưa đúng' }), _jsx("p", { className: cn('text-xs', isCorrect ? 'text-green-600' : 'text-red-600'), children: isCorrect
59
- ? 'Bạn đã chia động từ đúng.'
60
- : `Đáp án đúng là "${questionData.correctAnswer}".` })] })] }) }), questionData.explanation && (_jsx("div", { className: "rounded-xl bg-gradient-to-r from-blue-50 to-indigo-50 p-4 border border-blue-200", children: _jsxs("div", { className: "flex items-start gap-3", children: [_jsx("div", { className: "flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-lg bg-blue-500 text-white", children: _jsx(Lightbulb, { className: "h-3.5 w-3.5" }) }), _jsxs("div", { className: "flex-1", children: [_jsx("span", { className: "text-xs font-semibold text-blue-700 uppercase tracking-wider", children: "Gi\u1EA3i th\u00EDch" }), _jsx("p", { className: "mt-1 text-sm text-gray-700 leading-relaxed", children: questionData.explanation })] })] }) }))] })), !isReviewMode && !hasAnswered && (_jsx("div", { className: "rounded-xl border border-amber-200 bg-gradient-to-r from-amber-50 to-yellow-50 p-4", children: _jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-lg bg-gradient-to-br from-amber-400 to-orange-500 text-white shadow-sm", children: _jsx("span", { className: "text-sm font-bold", children: "!" }) }), _jsxs("div", { children: [_jsx("p", { className: "text-sm font-medium text-amber-800", children: "Nh\u1EADp d\u1EA1ng \u0111\u00FAng c\u1EE7a \u0111\u1ED9ng t\u1EEB" }), _jsx("p", { className: "text-xs text-amber-600", children: "S\u1EED d\u1EE5ng g\u1EE3i \u00FD trong ngo\u1EB7c \u0111\u1EC3 chia \u0111\u1ED9ng t\u1EEB ph\u00F9 h\u1EE3p." })] })] }) }))] })] }), !isReviewMode && questionData.caseSensitive && (_jsx("div", { className: "overflow-hidden rounded-xl border border-yellow-200 bg-gradient-to-r from-yellow-50 to-amber-50 shadow-sm", children: _jsxs("div", { className: "flex items-center gap-2 px-3 py-2", children: [_jsx("div", { className: "flex h-7 w-7 flex-shrink-0 items-center justify-center rounded-md bg-gradient-to-br from-yellow-400 to-amber-500 text-white", children: _jsx(AlertTriangle, { className: "h-3.5 w-3.5" }) }), _jsxs("div", { children: [_jsx("h3", { className: "text-sm font-semibold text-yellow-900", children: "L\u01B0u \u00FD quan tr\u1ECDng" }), _jsx("p", { className: "text-sm text-yellow-700", children: "\u0110\u00E1p \u00E1n c\u00F3 ph\u00E2n bi\u1EC7t ch\u1EEF hoa/th\u01B0\u1EDDng" })] })] }) }))] }));
53
+ return (_jsxs("div", { className: "space-y-3", children: [_jsx(KidInstruction, { children: questionData.instruction }), _jsxs(KidQuestionCard, { icon: _jsx(PenLine, { className: "h-3.5 w-3.5" }), title: "Chia \u0111\u1ED9ng t\u1EEB \u0111\u00FAng", points: questionData.points, review: isReviewMode && hasAnswered ? (isCorrect ? 'correct' : 'wrong') : null, children: [_jsx(KidPrompt, { children: renderSentenceWithInput() }), isReviewMode && (_jsxs("div", { className: "space-y-3", children: [_jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { className: cn('rounded-xl border-2 p-4', isCorrect
54
+ ? 'border-green-200 bg-green-50'
55
+ : isWrong
56
+ ? 'border-red-200 bg-red-50'
57
+ : 'border-gray-200 bg-gray-50'), children: [_jsxs("div", { className: "flex items-center gap-2 mb-2", children: [_jsx("div", { className: cn('flex h-6 w-6 items-center justify-center rounded-lg', isCorrect ? 'bg-green-200' : isWrong ? 'bg-red-200' : 'bg-gray-200'), children: isCorrect ? (_jsx(Check, { className: "h-3.5 w-3.5 text-green-700" })) : isWrong ? (_jsx(X, { className: "h-3.5 w-3.5 text-red-700" })) : (_jsx("span", { className: "text-xs text-gray-500", children: "?" })) }), _jsx("span", { className: "text-xs font-medium text-gray-600", children: "C\u00E2u tr\u1EA3 l\u1EDDi c\u1EE7a b\u1EA1n" })] }), _jsx("p", { className: cn('text-lg font-bold', isCorrect ? 'text-green-700' : isWrong ? 'text-red-700' : 'text-gray-400'), children: answer || '(Chưa trả lời)' })] }), _jsxs("div", { className: "rounded-xl border-2 border-green-200 bg-gradient-to-br from-green-50 to-emerald-50 p-4", children: [_jsxs("div", { className: "flex items-center gap-2 mb-2", children: [_jsx("div", { className: "flex h-6 w-6 items-center justify-center rounded-lg bg-green-200", children: _jsx(Check, { className: "h-3.5 w-3.5 text-green-700" }) }), _jsx("span", { className: "text-xs font-medium text-gray-600", children: "\u0110\u00E1p \u00E1n \u0111\u00FAng" })] }), _jsx("p", { className: "text-lg font-bold text-green-700", children: questionData.correctAnswer })] })] }), _jsx("div", { className: cn('rounded-xl p-4', isCorrect
58
+ ? 'bg-gradient-to-r from-green-50 to-emerald-50 border border-green-200'
59
+ : 'bg-gradient-to-r from-red-50 to-rose-50 border border-red-200'), children: _jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: cn('flex h-10 w-10 items-center justify-center rounded-xl text-white shadow-sm', isCorrect
60
+ ? 'bg-gradient-to-br from-green-500 to-emerald-600'
61
+ : 'bg-gradient-to-br from-red-500 to-rose-600'), children: isCorrect ? _jsx(Check, { className: "h-5 w-5" }) : _jsx(X, { className: "h-5 w-5" }) }), _jsxs("div", { children: [_jsx("p", { className: cn('text-sm font-semibold', isCorrect ? 'text-green-800' : 'text-red-800'), children: isCorrect ? 'Chính xác!' : 'Chưa đúng' }), _jsx("p", { className: cn('text-xs', isCorrect ? 'text-green-600' : 'text-red-600'), children: isCorrect
62
+ ? 'Bạn đã chia động từ đúng.'
63
+ : `Đáp án đúng là "${questionData.correctAnswer}".` })] })] }) }), questionData.explanation && (_jsx("div", { className: "rounded-xl bg-gradient-to-r from-blue-50 to-indigo-50 p-4 border border-blue-200", children: _jsxs("div", { className: "flex items-start gap-3", children: [_jsx("div", { className: "flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-lg bg-blue-500 text-white", children: _jsx(Lightbulb, { className: "h-3.5 w-3.5" }) }), _jsxs("div", { className: "flex-1", children: [_jsx("span", { className: "text-xs font-semibold text-blue-700 uppercase tracking-wider", children: "Gi\u1EA3i th\u00EDch" }), _jsx("p", { className: "mt-1 text-sm text-gray-700 leading-relaxed", children: questionData.explanation })] })] }) }))] })), !isReviewMode && !hasAnswered && (_jsxs("div", { className: "flex items-center gap-2 rounded-md border border-amber-200 bg-amber-50 px-2.5 py-1.5", children: [_jsx("div", { className: "flex h-4 w-4 flex-shrink-0 items-center justify-center rounded-full bg-amber-400 text-white", children: _jsx("span", { className: "text-[10px] font-bold", children: "!" }) }), _jsx("span", { className: "text-xs text-amber-800", children: "Nh\u1EADp d\u1EA1ng \u0111\u00FAng c\u1EE7a \u0111\u1ED9ng t\u1EEB theo g\u1EE3i \u00FD trong ngo\u1EB7c" })] }))] }), !isReviewMode && questionData.caseSensitive && (_jsx("div", { className: "overflow-hidden rounded-xl border border-yellow-200 bg-gradient-to-r from-yellow-50 to-amber-50 shadow-sm", children: _jsxs("div", { className: "flex items-center gap-2 px-3 py-2", children: [_jsx("div", { className: "flex h-7 w-7 flex-shrink-0 items-center justify-center rounded-md bg-gradient-to-br from-yellow-400 to-amber-500 text-white", children: _jsx(AlertTriangle, { className: "h-3.5 w-3.5" }) }), _jsxs("div", { children: [_jsx("h3", { className: "text-sm font-semibold text-yellow-900", children: "L\u01B0u \u00FD quan tr\u1ECDng" }), _jsx("p", { className: "text-sm text-yellow-700", children: "\u0110\u00E1p \u00E1n c\u00F3 ph\u00E2n bi\u1EC7t ch\u1EEF hoa/th\u01B0\u1EDDng" })] })] }) }))] }));
61
64
  }
@@ -13,9 +13,6 @@ function readCrossOutAnswers(value) {
13
13
  }
14
14
  export function CrossOutWordGroupViewer({ question, isReviewMode = false, userAnswer, onSubmitAnswer, }) {
15
15
  const questionData = mapQuestionToCrossOutWordGroupData(question);
16
- const storedAnswers = readCrossOutAnswers(userAnswer);
17
- const userAnswers = isReviewMode && !storedAnswers
18
- ? questionData.items.map((item) => item.correctAnswer)
19
- : storedAnswers;
16
+ const userAnswers = readCrossOutAnswers(userAnswer);
20
17
  return (_jsx(CrossOutWordGroupClient, { questionData: questionData, isReviewMode: isReviewMode, userAnswers: userAnswers, onAnswerChange: isReviewMode ? undefined : onSubmitAnswer }));
21
18
  }
@@ -3,8 +3,12 @@ import type { FillInBlankGroupStudentItemAnswer } from '../_shared/types/fill-in
3
3
  type FillInBlankGroupViewerProps = {
4
4
  question: Question;
5
5
  isReviewMode?: boolean;
6
- userAnswer?: FillInBlankGroupStudentItemAnswer[];
7
- onSubmitAnswer?: (answer: FillInBlankGroupStudentItemAnswer[]) => void;
6
+ userAnswer?: FillInBlankGroupStudentItemAnswer[] | {
7
+ answer?: FillInBlankGroupStudentItemAnswer[];
8
+ };
9
+ onSubmitAnswer?: (answer: {
10
+ answer: FillInBlankGroupStudentItemAnswer[];
11
+ } | FillInBlankGroupStudentItemAnswer[]) => void;
8
12
  };
9
13
  export declare function FillInBlankGroupViewer({ question, isReviewMode, userAnswer, onSubmitAnswer, }: FillInBlankGroupViewerProps): import("react").JSX.Element;
10
14
  export {};
@@ -2,13 +2,19 @@
2
2
  import { jsx as _jsx } from "react/jsx-runtime";
3
3
  import { FillInBlankGroupClient } from '../types/fill-in-blank-group/FillInBlankGroupClient';
4
4
  import { mapQuestionToFillInBlankGroupData } from '../types/fill-in-blank-group/map-fill-in-blank-group-data';
5
+ function readFillInBlankAnswers(value) {
6
+ if (Array.isArray(value)) {
7
+ return value;
8
+ }
9
+ if (value && typeof value === 'object' && Array.isArray(value.answer)) {
10
+ return value.answer;
11
+ }
12
+ return undefined;
13
+ }
5
14
  export function FillInBlankGroupViewer({ question, isReviewMode = false, userAnswer, onSubmitAnswer, }) {
6
15
  const questionData = mapQuestionToFillInBlankGroupData(question);
7
- const studentAnswers = Array.isArray(userAnswer) ? userAnswer : undefined;
8
- const reviewAnswers = isReviewMode && !studentAnswers
9
- ? questionData.items.map((item) => ({
10
- answers: item.answers.map((alts) => alts[0] || ''),
11
- }))
12
- : studentAnswers;
13
- return (_jsx(FillInBlankGroupClient, { questionData: questionData, isReviewMode: isReviewMode, userAnswers: reviewAnswers, onAnswerChange: isReviewMode ? undefined : onSubmitAnswer }));
16
+ const userAnswers = readFillInBlankAnswers(userAnswer);
17
+ return (_jsx(FillInBlankGroupClient, { questionData: questionData, isReviewMode: isReviewMode, userAnswers: userAnswers, onAnswerChange: isReviewMode
18
+ ? undefined
19
+ : (answers) => onSubmitAnswer?.({ answer: answers }) }));
14
20
  }
@@ -484,7 +484,7 @@ export function QuestionViewer({ question, onSubmitAnswer, isReviewMode = false,
484
484
  const answers = raw.answers && typeof raw.answers === 'object' && !Array.isArray(raw.answers)
485
485
  ? raw.answers
486
486
  : raw;
487
- return (_jsx(CrosswordPuzzleClient, { content: question.content, correctAnswer: question.correctAnswer ?? question.answer, isReviewMode: isReviewMode, userAnswers: answers, onAnswerChange: isReviewMode
487
+ return (_jsx(CrosswordPuzzleClient, { content: question.content, correctAnswer: question.correctAnswer ?? question.answer, isReviewMode: isReviewMode, questionNumber: question.questionNumber, userAnswers: answers, onAnswerChange: isReviewMode
488
488
  ? undefined
489
489
  : (key, value) => {
490
490
  onSubmitAnswer?.({ answers: { ...answers, [key]: value } });
@@ -521,7 +521,7 @@ export function QuestionViewer({ question, onSubmitAnswer, isReviewMode = false,
521
521
  case 'SPONTANEOUS_QA':
522
522
  return (_jsx(SpontaneousQAClient, { questionData: mapQuestionToSpontaneousQAData(question), isReviewMode: isReviewMode }));
523
523
  case 'SPONTANEOUS_QA_GROUP':
524
- return (_jsx(SpontaneousQaGroupViewer, { question: question, isReviewMode: isReviewMode }));
524
+ return (_jsx(SpontaneousQaGroupViewer, { question: question, isReviewMode: isReviewMode, userAnswer: userAnswer, gradeDetails: gradeDetails, disabled: !onSubmitAnswer }));
525
525
  case 'WORD_ORDER_AND_MATCH_GROUP':
526
526
  case 'WORD_ORDER_AND_MATCH':
527
527
  return (_jsx(WordOrderAndMatchGroupViewer, { question: question, isReviewMode: isReviewMode, userAnswer: userAnswer, onSubmitAnswer: onSubmitAnswer }));
@@ -1,5 +1,11 @@
1
1
  import type { Question } from '../../../shared/types/entities/question.types';
2
- export declare function SpontaneousQaGroupViewer({ question, isReviewMode, }: {
2
+ import type { SpontaneousQaGroupGradeDetail } from '../_shared/types/spontaneous-qa-group.type';
3
+ export declare function SpontaneousQaGroupViewer({ question, isReviewMode, userAnswer, onItemRecorded, uploadingIndex, gradeDetails, disabled, }: {
3
4
  question: Question;
4
5
  isReviewMode?: boolean;
6
+ userAnswer?: unknown;
7
+ onItemRecorded?: (flattenIndex: number, blob: Blob) => void;
8
+ uploadingIndex?: number | null;
9
+ gradeDetails?: SpontaneousQaGroupGradeDetail[];
10
+ disabled?: boolean;
5
11
  }): import("react").JSX.Element;
@@ -2,6 +2,18 @@
2
2
  import { jsx as _jsx } from "react/jsx-runtime";
3
3
  import { SpontaneousQaGroupClient } from '../types/spontaneous-qa-group/SpontaneousQaGroupClient';
4
4
  import { mapQuestionToSpontaneousQaGroupData } from '../types/spontaneous-qa-group/map-spontaneous-qa-group-data';
5
- export function SpontaneousQaGroupViewer({ question, isReviewMode = false, }) {
6
- return (_jsx(SpontaneousQaGroupClient, { questionData: mapQuestionToSpontaneousQaGroupData(question), isReviewMode: isReviewMode }));
5
+ function readStudentAnswer(value) {
6
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
7
+ return undefined;
8
+ }
9
+ const keys = value.audioKeys;
10
+ if (!Array.isArray(keys)) {
11
+ return undefined;
12
+ }
13
+ return {
14
+ audioKeys: keys.map((key) => (typeof key === 'string' && key.trim() !== '' ? key : null)),
15
+ };
16
+ }
17
+ export function SpontaneousQaGroupViewer({ question, isReviewMode = false, userAnswer, onItemRecorded, uploadingIndex, gradeDetails, disabled, }) {
18
+ return (_jsx(SpontaneousQaGroupClient, { questionData: mapQuestionToSpontaneousQaGroupData(question), isReviewMode: isReviewMode, userAnswer: readStudentAnswer(userAnswer), onItemRecorded: isReviewMode ? undefined : onItemRecorded, uploadingIndex: uploadingIndex, gradeDetails: isReviewMode ? undefined : gradeDetails, disabled: disabled }));
7
19
  }
@@ -218,7 +218,7 @@ export function renderMoversReviewBody(ctx) {
218
218
  case 'CROSSWORD_PUZZLE': {
219
219
  const data = transformCrosswordPuzzle(apiQuestions);
220
220
  const firstQuestion = apiQuestions[0];
221
- return (_jsx(MoversCrosswordPuzzleRenderer, { partNumber: partNo, partName: part.name, instruction: part.instructions || data.instruction, questionCount: questionCount, title: data.title, gridSize: data.gridSize, clues: data.clues, answers: answers, correctAnswers: data.correctAnswers, onAnswerChange: handleAnswerChange, isReviewMode: true, caseSensitive: firstQuestion?.correct_answer?.caseSensitive || false }));
221
+ return (_jsx(MoversCrosswordPuzzleRenderer, { partNumber: partNo, partName: part.name, instruction: part.instructions || data.instruction, questionCount: questionCount, title: data.title, gridSize: data.gridSize, markedColumn: data.markedColumn, hiddenWord: data.hiddenWord, clues: data.clues, answers: answers, correctAnswers: data.correctAnswers, onAnswerChange: handleAnswerChange, isReviewMode: true, caseSensitive: data.caseSensitive || firstQuestion?.correct_answer?.caseSensitive || false }));
222
222
  }
223
223
  default:
224
224
  return null;
@@ -140,13 +140,20 @@ function addCorrectAnswerMaps(result, answer) {
140
140
  texts.forEach((text, index) => { result.correctAnswerTextMap[`${answer.questionId}-blank-${index}`] = text; });
141
141
  if (answer.questionType === 'LABEL_THE_PICTURE' && isRecord(correct.labels))
142
142
  Object.entries(correct.labels).forEach(([id, value]) => { result.correctAnswerTextMap[id] = String(value); });
143
- if ((answer.questionType === 'FILL_MISSING_WORDS_IN_GRID' || answer.questionType === 'CROSSWORD_PUZZLE') && isRecord(correct.cells || correct.answers)) {
143
+ if (answer.questionType === 'FILL_MISSING_WORDS_IN_GRID' && isRecord(correct.cells || correct.answers)) {
144
144
  const rawCells = (correct.cells || correct.answers);
145
145
  result.cellAnswersMap[answer.questionId] = {
146
146
  cells: Object.fromEntries(Object.entries(rawCells).map(([k, v]) => [k, { correctAnswer: String(v) }])),
147
147
  caseSensitive: correct.caseSensitive === true,
148
148
  };
149
149
  }
150
+ if (answer.questionType === 'CROSSWORD_PUZZLE' && isRecord(correct.cells)) {
151
+ const rawCells = correct.cells;
152
+ result.cellAnswersMap[answer.questionId] = {
153
+ cells: Object.fromEntries(Object.entries(rawCells).map(([k, v]) => [k, { correctAnswer: String(v) }])),
154
+ caseSensitive: correct.caseSensitive === true,
155
+ };
156
+ }
150
157
  }
151
158
  function transformStudentAnswer(questionId, questionType, value) {
152
159
  if (value == null)
@@ -537,18 +537,27 @@ const reverseCrossOutWordGroup = (apiQuestion) => {
537
537
  const fallback = asRecord(fallbackAnswers[index]);
538
538
  const answerRaw = Object.keys(itemAnswer).length > 0 ? itemAnswer : fallback;
539
539
  const isExample = item.isExample === true;
540
- const type = itemContent.type === 'CROSS_OUT_AND_REPLACE' ||
541
- answerRaw.type === 'CROSS_OUT_AND_REPLACE'
540
+ const type = itemContent.type === 'CROSS_OUT_AND_REPLACE'
542
541
  ? 'CROSS_OUT_AND_REPLACE'
543
- : 'CROSS_OUT';
542
+ : itemContent.type === 'CROSS_OUT'
543
+ ? 'CROSS_OUT'
544
+ : answerRaw.type === 'CROSS_OUT_AND_REPLACE'
545
+ ? 'CROSS_OUT_AND_REPLACE'
546
+ : 'CROSS_OUT';
547
+ const rawIndex = answerRaw.wordIndex;
548
+ const wordIndex = typeof rawIndex === 'number' && Number.isInteger(rawIndex) && rawIndex >= 0
549
+ ? rawIndex
550
+ : null;
544
551
  const correctAnswer = type === 'CROSS_OUT_AND_REPLACE'
545
552
  ? {
546
553
  type: 'CROSS_OUT_AND_REPLACE',
554
+ wordIndex,
547
555
  crossOut: asString(answerRaw.crossOut),
548
556
  replaceWith: asString(answerRaw.replaceWith),
549
557
  }
550
558
  : {
551
559
  type: 'CROSS_OUT',
560
+ wordIndex,
552
561
  crossOut: asString(answerRaw.crossOut),
553
562
  };
554
563
  return {
@@ -638,12 +647,14 @@ const reverseSpontaneousQaGroup = (apiQuestion) => {
638
647
  const fallback = asRecord(flattenedAnswers[questionIndex]);
639
648
  const expected = asArray(childAnswer.expectedAnswers ?? fallback.expectedAnswers)
640
649
  .filter((value) => typeof value === 'string');
650
+ const isExample = child.isExample === true;
641
651
  return {
642
652
  questionText: asString(childContent.questionText),
643
653
  inputType: asString(childContent.inputType, 'TEXT') === 'AUDIO' ? 'AUDIO' : 'TEXT',
644
654
  audioUrl: asString(childContent.audioUrl),
645
655
  expectedAnswers: expected.length > 0 ? expected : [''],
646
- points: asNumber(child.points, 1),
656
+ isExample,
657
+ points: isExample ? 0 : asNumber(child.points, 1),
647
658
  questionNumber: asNumber(child.questionNumber, questionIndex + 1),
648
659
  };
649
660
  }),
@@ -890,6 +901,9 @@ function reverseCrosswordPuzzle(apiQuestion) {
890
901
  const rows = apiContent.gridSize?.rows || apiContent.rows || 12;
891
902
  const cols = apiContent.gridSize?.cols || apiContent.cols || 12;
892
903
  const title = apiContent.title || '';
904
+ const type = apiContent.type === 'HIDDEN_WORD' ? 'HIDDEN_WORD' : 'STANDARD';
905
+ const instruction = asString(apiQuestion.instruction, asString(apiContent.instruction));
906
+ const markedColumn = typeof apiContent.markedColumn === 'number' ? apiContent.markedColumn : undefined;
893
907
  const across = Array.isArray(apiContent.clues?.across) ? apiContent.clues.across : [];
894
908
  const down = Array.isArray(apiContent.clues?.down) ? apiContent.clues.down : [];
895
909
  const answers = apiAnswer.answers || {};
@@ -904,6 +918,7 @@ function reverseCrosswordPuzzle(apiQuestion) {
904
918
  length: a.length || 1,
905
919
  direction: 'across',
906
920
  answer: answers[`across-${a.number}`] || '',
921
+ ...(a.isExample === true ? { isExample: true } : {}),
907
922
  });
908
923
  }
909
924
  for (const d of down) {
@@ -916,24 +931,32 @@ function reverseCrosswordPuzzle(apiQuestion) {
916
931
  length: d.length || 1,
917
932
  direction: 'down',
918
933
  answer: answers[`down-${d.number}`] || '',
934
+ ...(d.isExample === true ? { isExample: true } : {}),
919
935
  });
920
936
  }
921
937
  return {
922
938
  content: {
939
+ type,
923
940
  title,
941
+ instruction,
924
942
  rows,
925
943
  cols,
926
944
  gridSize: { rows, cols },
945
+ ...(type === 'HIDDEN_WORD' && markedColumn ? { markedColumn } : {}),
927
946
  clues: { across, down },
928
947
  },
929
948
  answer: {
949
+ type,
930
950
  title,
951
+ instruction,
931
952
  rows,
932
953
  cols,
954
+ ...(type === 'HIDDEN_WORD' && markedColumn ? { markedColumn } : {}),
955
+ hiddenWord: answers['hidden-word'] || '',
933
956
  clues,
934
957
  answers,
935
958
  caseSensitive: apiAnswer.caseSensitive || false,
936
- explanation: apiQuestion.explanation || '',
959
+ explanation: asString(apiQuestion.explanation),
937
960
  },
938
961
  };
939
962
  }
@@ -944,6 +967,7 @@ function reverseFindWordsInMatrix(apiQuestion) {
944
967
  const rows = apiContent.gridSize?.rows || (Array.isArray(apiContent.grid) ? apiContent.grid.length : 14);
945
968
  const cols = apiContent.gridSize?.cols || (Array.isArray(apiContent.grid?.[0]) ? apiContent.grid[0].length : 14);
946
969
  const title = apiContent.title || '';
970
+ const instruction = typeof apiContent.instruction === 'string' ? apiContent.instruction : '';
947
971
  const grid = Array.isArray(apiContent.grid) ? apiContent.grid : [];
948
972
  const categories = Array.isArray(apiContent.categories) ? apiContent.categories : [];
949
973
  const wordBank = Array.isArray(apiContent.wordBank) ? apiContent.wordBank : [];
@@ -977,6 +1001,7 @@ function reverseFindWordsInMatrix(apiQuestion) {
977
1001
  content: {
978
1002
  type,
979
1003
  title,
1004
+ instruction,
980
1005
  rows,
981
1006
  cols,
982
1007
  gridSize: { rows, cols },
@@ -989,6 +1014,7 @@ function reverseFindWordsInMatrix(apiQuestion) {
989
1014
  answer: {
990
1015
  type,
991
1016
  title,
1017
+ instruction,
992
1018
  rows,
993
1019
  cols,
994
1020
  grid,
@@ -20,6 +20,7 @@ export type CrosswordSubtype = 'STANDARD' | 'HIDDEN_WORD';
20
20
  export interface CrosswordPuzzleContent {
21
21
  type?: CrosswordSubtype;
22
22
  title?: string;
23
+ instruction?: string;
23
24
  gridSize: {
24
25
  rows: number;
25
26
  cols: number;
@@ -30,6 +30,7 @@ export interface ExampleSelection {
30
30
  export interface FindWordsInMatrixContent {
31
31
  type?: FindWordsInMatrixType;
32
32
  title?: string;
33
+ instruction?: string;
33
34
  gridSize: {
34
35
  rows: number;
35
36
  cols: number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tinyweb_dev/oe-exam-sdk",
3
- "version": "1.0.9",
3
+ "version": "1.0.10",
4
4
  "description": "Reusable OceanEdu question components.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",