@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,10 +1,25 @@
1
1
  'use client';
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { useState, useEffect, useMemo } from 'react';
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { useState, useEffect, useMemo, useRef } from 'react';
4
4
  import { Plus, Trash2, Edit2, Check, AlertCircle, Grid3X3 } from 'lucide-react';
5
5
  import { CompactCreatorHeader, CompactFieldCard, CompactPreviewBanner, } from '../../_shared/components/compact';
6
6
  import { CrosswordPuzzleClient } from './CrosswordPuzzleClient';
7
7
  export function CrosswordPuzzleCreator({ initialData, onChange, onUnsavedChangesChange, }) {
8
+ const [instruction, setInstruction] = useState(() => {
9
+ return initialData?.content?.instruction || initialData?.instruction || initialData?.answer?.instruction || '';
10
+ });
11
+ const [puzzleType, setPuzzleType] = useState(() => {
12
+ return initialData?.content?.type === 'HIDDEN_WORD' || initialData?.answer?.type === 'HIDDEN_WORD'
13
+ ? 'HIDDEN_WORD'
14
+ : 'STANDARD';
15
+ });
16
+ const [markedColumn, setMarkedColumn] = useState(() => {
17
+ return Number(initialData?.content?.markedColumn || initialData?.answer?.markedColumn || 1);
18
+ });
19
+ const [hiddenWord, setHiddenWord] = useState(() => {
20
+ const answers = initialData?.answer?.answers || initialData?.correctAnswer?.answers || {};
21
+ return answers['hidden-word'] || initialData?.answer?.hiddenWord || '';
22
+ });
8
23
  const [title, setTitle] = useState(() => {
9
24
  return initialData?.content?.title || 'Nationalities Crossword';
10
25
  });
@@ -39,6 +54,7 @@ export function CrosswordPuzzleCreator({ initialData, onChange, onUnsavedChanges
39
54
  length: a.length,
40
55
  direction: 'across',
41
56
  answer: answers[`across-${a.number}`] || '',
57
+ isExample: a.isExample === true,
42
58
  });
43
59
  }
44
60
  for (const d of down) {
@@ -51,6 +67,7 @@ export function CrosswordPuzzleCreator({ initialData, onChange, onUnsavedChanges
51
67
  length: d.length,
52
68
  direction: 'down',
53
69
  answer: answers[`down-${d.number}`] || '',
70
+ isExample: d.isExample === true,
54
71
  });
55
72
  }
56
73
  return items;
@@ -62,6 +79,7 @@ export function CrosswordPuzzleCreator({ initialData, onChange, onUnsavedChanges
62
79
  const [formAnswer, setFormAnswer] = useState('');
63
80
  const [formStartRow, setFormStartRow] = useState(1);
64
81
  const [formStartCol, setFormStartCol] = useState(1);
82
+ const [formIsExample, setFormIsExample] = useState(false);
65
83
  const { cellMap, clueNumberMap, conflicts } = useMemo(() => {
66
84
  const cMap = new Map();
67
85
  const numMap = new Map();
@@ -89,9 +107,17 @@ export function CrosswordPuzzleCreator({ initialData, onChange, onUnsavedChanges
89
107
  }
90
108
  return { cellMap: cMap, clueNumberMap: numMap, conflicts: conflictList };
91
109
  }, [clues]);
110
+ const onChangeRef = useRef(onChange);
111
+ const onUnsavedChangesChangeRef = useRef(onUnsavedChangesChange);
112
+ const previousPayloadRef = useRef('');
113
+ const skipUnsavedRef = useRef(true);
114
+ useEffect(() => {
115
+ onChangeRef.current = onChange;
116
+ }, [onChange]);
117
+ useEffect(() => {
118
+ onUnsavedChangesChangeRef.current = onUnsavedChangesChange;
119
+ }, [onUnsavedChangesChange]);
92
120
  useEffect(() => {
93
- if (!onChange)
94
- return;
95
121
  const across = clues
96
122
  .filter((c) => c.direction === 'across')
97
123
  .map((c) => ({
@@ -100,6 +126,7 @@ export function CrosswordPuzzleCreator({ initialData, onChange, onUnsavedChanges
100
126
  startRow: c.startRow,
101
127
  startCol: c.startCol,
102
128
  length: c.length || c.answer.trim().length,
129
+ ...(c.isExample ? { isExample: true } : {}),
103
130
  }));
104
131
  const down = clues
105
132
  .filter((c) => c.direction === 'down')
@@ -109,6 +136,7 @@ export function CrosswordPuzzleCreator({ initialData, onChange, onUnsavedChanges
109
136
  startRow: c.startRow,
110
137
  startCol: c.startCol,
111
138
  length: c.length || c.answer.trim().length,
139
+ ...(c.isExample ? { isExample: true } : {}),
112
140
  }));
113
141
  const answers = {};
114
142
  for (const c of clues) {
@@ -116,20 +144,37 @@ export function CrosswordPuzzleCreator({ initialData, onChange, onUnsavedChanges
116
144
  answers[`${c.direction}-${c.number}`] = c.answer.trim().toUpperCase();
117
145
  }
118
146
  }
119
- onChange({
147
+ if (puzzleType === 'HIDDEN_WORD' && hiddenWord.trim()) {
148
+ answers['hidden-word'] = hiddenWord.trim().toUpperCase();
149
+ }
150
+ const payload = {
120
151
  content: {
152
+ type: puzzleType,
121
153
  title,
154
+ instruction,
122
155
  gridSize: { rows, cols },
156
+ ...(puzzleType === 'HIDDEN_WORD' ? { markedColumn } : {}),
123
157
  clues: { across, down },
124
158
  },
125
159
  answer: {
126
160
  answers,
127
161
  caseSensitive,
162
+ hiddenWord: puzzleType === 'HIDDEN_WORD' ? hiddenWord.trim().toUpperCase() : '',
128
163
  },
129
164
  points,
130
- });
131
- onUnsavedChangesChange?.(true);
132
- }, [title, rows, cols, clues, caseSensitive, points, onChange, onUnsavedChangesChange]);
165
+ };
166
+ const serialized = JSON.stringify(payload);
167
+ if (serialized === previousPayloadRef.current) {
168
+ return;
169
+ }
170
+ previousPayloadRef.current = serialized;
171
+ onChangeRef.current?.(payload);
172
+ if (skipUnsavedRef.current) {
173
+ skipUnsavedRef.current = false;
174
+ return;
175
+ }
176
+ onUnsavedChangesChangeRef.current?.(true);
177
+ }, [instruction, puzzleType, markedColumn, hiddenWord, title, rows, cols, clues, caseSensitive, points]);
133
178
  const handleSaveClue = (e) => {
134
179
  e.preventDefault();
135
180
  if (!formClue.trim() || !formAnswer.trim())
@@ -147,6 +192,7 @@ export function CrosswordPuzzleCreator({ initialData, onChange, onUnsavedChanges
147
192
  startRow: formStartRow,
148
193
  startCol: formStartCol,
149
194
  length,
195
+ isExample: formIsExample,
150
196
  }
151
197
  : c));
152
198
  setEditingClueId(null);
@@ -161,11 +207,13 @@ export function CrosswordPuzzleCreator({ initialData, onChange, onUnsavedChanges
161
207
  startRow: formStartRow,
162
208
  startCol: formStartCol,
163
209
  length,
210
+ isExample: formIsExample,
164
211
  };
165
212
  setClues((prev) => [...prev, newClue]);
166
213
  }
167
214
  setFormClue('');
168
215
  setFormAnswer('');
216
+ setFormIsExample(false);
169
217
  setFormNumber((prev) => prev + 1);
170
218
  };
171
219
  const handleEditClue = (c) => {
@@ -176,6 +224,7 @@ export function CrosswordPuzzleCreator({ initialData, onChange, onUnsavedChanges
176
224
  setFormAnswer(c.answer);
177
225
  setFormStartRow(c.startRow);
178
226
  setFormStartCol(c.startCol);
227
+ setFormIsExample(c.isExample === true);
179
228
  };
180
229
  const handleDeleteClue = (id) => {
181
230
  setClues((prev) => prev.filter((c) => c.id !== id));
@@ -194,6 +243,7 @@ export function CrosswordPuzzleCreator({ initialData, onChange, onUnsavedChanges
194
243
  startRow: c.startRow,
195
244
  startCol: c.startCol,
196
245
  length: c.length || c.answer.trim().length,
246
+ ...(c.isExample ? { isExample: true } : {}),
197
247
  }));
198
248
  const down = clues
199
249
  .filter((c) => c.direction === 'down')
@@ -203,6 +253,7 @@ export function CrosswordPuzzleCreator({ initialData, onChange, onUnsavedChanges
203
253
  startRow: c.startRow,
204
254
  startCol: c.startCol,
205
255
  length: c.length || c.answer.trim().length,
256
+ ...(c.isExample ? { isExample: true } : {}),
206
257
  }));
207
258
  const answers = {};
208
259
  for (const c of clues) {
@@ -210,20 +261,26 @@ export function CrosswordPuzzleCreator({ initialData, onChange, onUnsavedChanges
210
261
  answers[`${c.direction}-${c.number}`] = c.answer.trim().toUpperCase();
211
262
  }
212
263
  }
264
+ if (puzzleType === 'HIDDEN_WORD' && hiddenWord.trim()) {
265
+ answers['hidden-word'] = hiddenWord.trim().toUpperCase();
266
+ }
213
267
  return (_jsxs("div", { className: "space-y-4", children: [_jsx(CompactPreviewBanner, { onBack: () => setIsClientMode(false) }), _jsx(CrosswordPuzzleClient, { content: {
268
+ type: puzzleType,
214
269
  title,
270
+ instruction,
215
271
  gridSize: { rows, cols },
272
+ ...(puzzleType === 'HIDDEN_WORD' ? { markedColumn } : {}),
216
273
  clues: { across, down },
217
- }, correctAnswer: { answers, caseSensitive }, isReviewMode: true, userAnswers: answers })] }));
274
+ }, correctAnswer: { answers, caseSensitive }, isReviewMode: true, questionNumber: initialData?.questionNumber ?? 1 })] }));
218
275
  }
219
276
  return (_jsx("div", { className: "space-y-3", children: _jsx(CompactFieldCard, { header: _jsx(CompactCreatorHeader, { icon: _jsx(Grid3X3, { className: "h-3.5 w-3.5" }), title: "\u00D4 ch\u1EEF (Crossword)", points: {
220
277
  value: points,
221
278
  onValueChange: setPoints,
222
- }, preview: { onClick: () => setIsClientMode(true) } }), children: _jsxs("div", { className: "flex flex-col gap-4", children: [_jsxs("div", { className: "grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4 p-3 bg-gray-50 rounded-lg border border-gray-200", children: [_jsxs("div", { children: [_jsx("label", { className: "block text-xs font-bold text-gray-700 mb-1", children: "Ti\u00EAu \u0111\u1EC1 \u00F4 ch\u1EEF" }), _jsx("input", { type: "text", value: title, onChange: (e) => setTitle(e.target.value), placeholder: "vd: Nationalities Crossword", className: "w-full px-3 py-1.5 text-sm border border-gray-300 rounded focus:ring-1 focus:ring-blue-500 outline-none" })] }), _jsxs("div", { children: [_jsx("label", { className: "block text-xs font-bold text-gray-700 mb-1", children: "K\u00EDch th\u01B0\u1EDBc l\u01B0\u1EDBi (H\u00E0ng x C\u1ED9t)" }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("input", { type: "number", min: 3, max: 30, value: rows, onChange: (e) => setRows(Math.max(3, parseInt(e.target.value) || 12)), className: "w-20 px-2 py-1.5 text-sm border border-gray-300 rounded outline-none" }), _jsx("span", { className: "text-gray-500 font-bold", children: "x" }), _jsx("input", { type: "number", min: 3, max: 30, value: cols, onChange: (e) => setCols(Math.max(3, parseInt(e.target.value) || 12)), className: "w-20 px-2 py-1.5 text-sm border border-gray-300 rounded outline-none" })] })] }), _jsx("div", { className: "flex items-center pt-5", children: _jsxs("label", { className: "flex items-center gap-2 text-sm text-gray-700 cursor-pointer", children: [_jsx("input", { type: "checkbox", checked: caseSensitive, onChange: (e) => setCaseSensitive(e.target.checked), className: "rounded text-blue-600 focus:ring-blue-500 h-4 w-4" }), _jsx("span", { children: "Ph\u00E2n bi\u1EC7t hoa/th\u01B0\u1EDDng" })] }) })] }), _jsxs("div", { className: "grid grid-cols-1 lg:grid-cols-12 gap-6", children: [_jsxs("div", { className: "lg:col-span-5 flex flex-col gap-4", children: [_jsxs("form", { onSubmit: handleSaveClue, className: "p-4 rounded-xl border border-blue-200 bg-blue-50/40 flex flex-col gap-3", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx("span", { className: "text-sm font-bold text-blue-900", children: editingClueId ? 'Chỉnh sửa gợi ý' : 'Thêm gợi ý mới' }), editingClueId && (_jsx("button", { type: "button", onClick: () => {
279
+ }, preview: { onClick: () => setIsClientMode(true) } }), children: _jsxs("div", { className: "flex flex-col gap-4", children: [_jsxs("div", { className: "grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4 p-3 bg-gray-50 rounded-lg border border-gray-200", children: [_jsxs("div", { className: "sm:col-span-2 md:col-span-3", children: [_jsx("label", { className: "block text-xs font-bold text-gray-700 mb-1", children: "H\u01B0\u1EDBng d\u1EABn b\u00E0i l\u00E0m" }), _jsx("input", { type: "text", value: instruction, onChange: (e) => setInstruction(e.target.value), placeholder: "vd: Read the information and do the crossword. What's the hidden word?", className: "w-full px-3 py-1.5 text-sm border border-gray-300 rounded focus:ring-1 focus:ring-blue-500 outline-none" })] }), _jsxs("div", { children: [_jsx("label", { className: "block text-xs font-bold text-gray-700 mb-1", children: "D\u1EA1ng \u00F4 ch\u1EEF" }), _jsxs("select", { value: puzzleType, onChange: (e) => setPuzzleType(e.target.value), className: "w-full px-3 py-1.5 text-sm border border-gray-300 rounded outline-none bg-white", children: [_jsx("option", { value: "STANDARD", children: "STANDARD" }), _jsx("option", { value: "HIDDEN_WORD", children: "HIDDEN_WORD" })] })] }), puzzleType === 'HIDDEN_WORD' && (_jsxs(_Fragment, { children: [_jsxs("div", { children: [_jsx("label", { className: "block text-xs font-bold text-gray-700 mb-1", children: "C\u1ED9t t\u1EEB kh\u00F3a (markedColumn)" }), _jsx("input", { type: "number", min: 1, max: cols, value: markedColumn, onChange: (e) => setMarkedColumn(Math.max(1, parseInt(e.target.value) || 1)), className: "w-full px-3 py-1.5 text-sm border border-gray-300 rounded outline-none" })] }), _jsxs("div", { children: [_jsx("label", { className: "block text-xs font-bold text-gray-700 mb-1", children: "T\u1EEB kh\u00F3a b\u00ED \u1EA9n (hidden-word)" }), _jsx("input", { type: "text", value: hiddenWord, onChange: (e) => setHiddenWord(e.target.value.toUpperCase()), placeholder: "vd: BRITAIN", className: "w-full px-3 py-1.5 text-sm border border-gray-300 rounded outline-none uppercase font-semibold" })] })] })), _jsxs("div", { children: [_jsx("label", { className: "block text-xs font-bold text-gray-700 mb-1", children: "Ti\u00EAu \u0111\u1EC1 \u00F4 ch\u1EEF" }), _jsx("input", { type: "text", value: title, onChange: (e) => setTitle(e.target.value), placeholder: "vd: Nationalities Crossword", className: "w-full px-3 py-1.5 text-sm border border-gray-300 rounded focus:ring-1 focus:ring-blue-500 outline-none" })] }), _jsxs("div", { children: [_jsx("label", { className: "block text-xs font-bold text-gray-700 mb-1", children: "K\u00EDch th\u01B0\u1EDBc l\u01B0\u1EDBi (H\u00E0ng x C\u1ED9t)" }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("input", { type: "number", min: 3, max: 30, value: rows, onChange: (e) => setRows(Math.max(3, parseInt(e.target.value) || 12)), className: "w-20 px-2 py-1.5 text-sm border border-gray-300 rounded outline-none" }), _jsx("span", { className: "text-gray-500 font-bold", children: "x" }), _jsx("input", { type: "number", min: 3, max: 30, value: cols, onChange: (e) => setCols(Math.max(3, parseInt(e.target.value) || 12)), className: "w-20 px-2 py-1.5 text-sm border border-gray-300 rounded outline-none" })] })] }), _jsx("div", { className: "flex items-center pt-5", children: _jsxs("label", { className: "flex items-center gap-2 text-sm text-gray-700 cursor-pointer", children: [_jsx("input", { type: "checkbox", checked: caseSensitive, onChange: (e) => setCaseSensitive(e.target.checked), className: "rounded text-blue-600 focus:ring-blue-500 h-4 w-4" }), _jsx("span", { children: "Ph\u00E2n bi\u1EC7t hoa/th\u01B0\u1EDDng" })] }) })] }), _jsxs("div", { className: "grid grid-cols-1 lg:grid-cols-12 gap-6", children: [_jsxs("div", { className: "lg:col-span-5 flex flex-col gap-4", children: [_jsxs("form", { onSubmit: handleSaveClue, className: "p-4 rounded-xl border border-blue-200 bg-blue-50/40 flex flex-col gap-3", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx("span", { className: "text-sm font-bold text-blue-900", children: editingClueId ? 'Chỉnh sửa gợi ý' : 'Thêm gợi ý mới' }), editingClueId && (_jsx("button", { type: "button", onClick: () => {
223
280
  setEditingClueId(null);
224
281
  setFormClue('');
225
282
  setFormAnswer('');
226
- }, className: "text-xs text-gray-500 hover:text-gray-700 underline", children: "H\u1EE7y ch\u1EC9nh s\u1EEDa" }))] }), _jsxs("div", { className: "grid grid-cols-3 gap-2", children: [_jsxs("div", { children: [_jsx("label", { className: "block text-[11px] font-semibold text-gray-600 mb-0.5", children: "S\u1ED1 Clue" }), _jsx("input", { type: "number", min: 1, value: formNumber, onChange: (e) => setFormNumber(parseInt(e.target.value) || 1), className: "w-full px-2 py-1 text-sm border border-gray-300 rounded outline-none" })] }), _jsxs("div", { children: [_jsx("label", { className: "block text-[11px] font-semibold text-gray-600 mb-0.5", children: "H\u01B0\u1EDBng" }), _jsxs("select", { value: formDirection, onChange: (e) => setFormDirection(e.target.value), className: "w-full px-2 py-1 text-sm border border-gray-300 rounded outline-none bg-white", children: [_jsx("option", { value: "across", children: "Across (Ngang)" }), _jsx("option", { value: "down", children: "Down (D\u1ECDc)" })] })] }), _jsxs("div", { children: [_jsx("label", { className: "block text-[11px] font-semibold text-gray-600 mb-0.5", children: "To\u1EA1 \u0111\u1ED9 (R, C)" }), _jsxs("div", { className: "flex gap-1", children: [_jsx("input", { type: "number", min: 1, max: rows, value: formStartRow, onChange: (e) => setFormStartRow(parseInt(e.target.value) || 1), placeholder: "R", className: "w-1/2 px-1 py-1 text-xs border border-gray-300 rounded text-center" }), _jsx("input", { type: "number", min: 1, max: cols, value: formStartCol, onChange: (e) => setFormStartCol(parseInt(e.target.value) || 1), placeholder: "C", className: "w-1/2 px-1 py-1 text-xs border border-gray-300 rounded text-center" })] })] })] }), _jsxs("div", { children: [_jsx("label", { className: "block text-[11px] font-semibold text-gray-600 mb-0.5", children: "C\u00E2u g\u1EE3i \u00FD (Clue)" }), _jsx("input", { type: "text", value: formClue, onChange: (e) => setFormClue(e.target.value), placeholder: "vd: Spain, Capital of France...", className: "w-full px-3 py-1.5 text-sm border border-gray-300 rounded outline-none", required: true })] }), _jsxs("div", { children: [_jsx("label", { className: "block text-[11px] font-semibold text-gray-600 mb-0.5", children: "T\u1EEB \u0111\u00E1p \u00E1n (Answer Word)" }), _jsx("input", { type: "text", value: formAnswer, onChange: (e) => setFormAnswer(e.target.value), placeholder: "vd: SPANISH", className: "w-full px-3 py-1.5 text-sm border border-gray-300 rounded outline-none uppercase font-semibold", required: true })] }), _jsxs("button", { type: "submit", className: "mt-1 flex items-center justify-center gap-1.5 px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded text-sm font-semibold transition", children: [editingClueId ? _jsx(Check, { className: "h-4 w-4" }) : _jsx(Plus, { className: "h-4 w-4" }), _jsx("span", { children: editingClueId ? 'Cập nhật gợi ý' : 'Thêm vào danh sách' })] })] }), _jsxs("div", { className: "flex flex-col gap-2 max-h-[420px] overflow-y-auto pr-1", children: [_jsxs("span", { className: "text-xs font-bold text-gray-600", children: ["Danh s\u00E1ch (", clues.length, " t\u1EEB):"] }), clues.length === 0 && (_jsx("p", { className: "text-xs text-gray-400 italic", children: "Ch\u01B0a c\u00F3 g\u1EE3i \u00FD n\u00E0o. H\u00E3y th\u00EAm g\u1EE3i \u00FD \u1EDF tr\u00EAn." })), clues.map((c) => (_jsxs("div", { className: "flex items-center justify-between p-2.5 bg-white rounded-lg border border-gray-200 hover:border-blue-300 transition text-sm shadow-sm", children: [_jsxs("div", { className: "flex items-center gap-2 flex-1 min-w-0", children: [_jsx("span", { className: `flex items-center justify-center h-6 w-6 rounded text-xs font-bold text-white ${c.direction === 'across' ? 'bg-blue-600' : 'bg-purple-600'}`, children: c.number }), _jsxs("div", { className: "flex flex-col min-w-0 flex-1", children: [_jsx("span", { className: "font-semibold text-gray-800 truncate", children: c.clue }), _jsxs("span", { className: "text-xs text-gray-500 flex items-center gap-2", children: [_jsx("span", { className: "font-bold text-blue-700", children: c.answer }), _jsxs("span", { children: ["(", c.length, " ch\u1EEF)"] }), _jsxs("span", { children: ["[", c.direction === 'across' ? '→ Ngang' : '↓ Dọc', " (", c.startRow, ",", c.startCol, ")]"] })] })] })] }), _jsxs("div", { className: "flex items-center gap-1", children: [_jsx("button", { type: "button", onClick: () => handleEditClue(c), className: "p-1 text-gray-500 hover:text-blue-600 rounded", title: "S\u1EEDa", children: _jsx(Edit2, { className: "h-4 w-4" }) }), _jsx("button", { type: "button", onClick: () => handleDeleteClue(c.id), className: "p-1 text-gray-500 hover:text-red-600 rounded", title: "X\u00F3a", children: _jsx(Trash2, { className: "h-4 w-4" }) })] })] }, c.id)))] })] }), _jsxs("div", { className: "lg:col-span-7 flex flex-col gap-2", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx("span", { className: "text-xs font-bold text-gray-700", children: "Ma tr\u1EADn l\u01B0\u1EDBi \u00F4 ch\u1EEF tr\u1EF1c quan:" }), conflicts.length > 0 && (_jsxs("span", { className: "flex items-center gap-1 text-xs font-bold text-red-600 bg-red-50 px-2 py-0.5 rounded border border-red-200", children: [_jsx(AlertCircle, { className: "h-3.5 w-3.5" }), _jsxs("span", { children: ["C\u00F3 ", conflicts.length, " \u00F4 b\u1ECB xung \u0111\u1ED9t ch\u1EEF c\u00E1i!"] })] }))] }), _jsx("div", { className: "flex justify-center overflow-x-auto p-4 bg-gray-100 rounded-xl border border-gray-300 min-h-[380px]", children: _jsx("div", { className: "inline-grid gap-1", style: {
283
+ }, className: "text-xs text-gray-500 hover:text-gray-700 underline", children: "H\u1EE7y ch\u1EC9nh s\u1EEDa" }))] }), _jsxs("div", { className: "grid grid-cols-3 gap-2", children: [_jsxs("div", { children: [_jsx("label", { className: "block text-[11px] font-semibold text-gray-600 mb-0.5", children: "S\u1ED1 Clue" }), _jsx("input", { type: "number", min: 1, value: formNumber, onChange: (e) => setFormNumber(parseInt(e.target.value) || 1), className: "w-full px-2 py-1 text-sm border border-gray-300 rounded outline-none" })] }), _jsxs("div", { children: [_jsx("label", { className: "block text-[11px] font-semibold text-gray-600 mb-0.5", children: "H\u01B0\u1EDBng" }), _jsxs("select", { value: formDirection, onChange: (e) => setFormDirection(e.target.value), className: "w-full px-2 py-1 text-sm border border-gray-300 rounded outline-none bg-white", children: [_jsx("option", { value: "across", children: "Across (Ngang)" }), _jsx("option", { value: "down", children: "Down (D\u1ECDc)" })] })] }), _jsxs("div", { children: [_jsx("label", { className: "block text-[11px] font-semibold text-gray-600 mb-0.5", children: "To\u1EA1 \u0111\u1ED9 (R, C)" }), _jsxs("div", { className: "flex gap-1", children: [_jsx("input", { type: "number", min: 1, max: rows, value: formStartRow, onChange: (e) => setFormStartRow(parseInt(e.target.value) || 1), placeholder: "R", className: "w-1/2 px-1 py-1 text-xs border border-gray-300 rounded text-center" }), _jsx("input", { type: "number", min: 1, max: cols, value: formStartCol, onChange: (e) => setFormStartCol(parseInt(e.target.value) || 1), placeholder: "C", className: "w-1/2 px-1 py-1 text-xs border border-gray-300 rounded text-center" })] })] })] }), _jsxs("div", { children: [_jsx("label", { className: "block text-[11px] font-semibold text-gray-600 mb-0.5", children: "C\u00E2u g\u1EE3i \u00FD (Clue)" }), _jsx("input", { type: "text", value: formClue, onChange: (e) => setFormClue(e.target.value), placeholder: "vd: Spain, Capital of France...", className: "w-full px-3 py-1.5 text-sm border border-gray-300 rounded outline-none", required: true })] }), _jsxs("div", { children: [_jsx("label", { className: "block text-[11px] font-semibold text-gray-600 mb-0.5", children: "T\u1EEB \u0111\u00E1p \u00E1n (Answer Word)" }), _jsx("input", { type: "text", value: formAnswer, onChange: (e) => setFormAnswer(e.target.value), placeholder: "vd: SPANISH", className: "w-full px-3 py-1.5 text-sm border border-gray-300 rounded outline-none uppercase font-semibold", required: true })] }), _jsxs("label", { className: "flex items-center gap-2 text-xs text-gray-700 cursor-pointer", children: [_jsx("input", { type: "checkbox", checked: formIsExample, onChange: (e) => setFormIsExample(e.target.checked), className: "rounded text-amber-600 focus:ring-amber-500 h-4 w-4" }), _jsx("span", { children: "Example (kh\u00F4ng t\u00EDnh \u0111i\u1EC3m)" })] }), _jsxs("button", { type: "submit", className: "mt-1 flex items-center justify-center gap-1.5 px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded text-sm font-semibold transition", children: [editingClueId ? _jsx(Check, { className: "h-4 w-4" }) : _jsx(Plus, { className: "h-4 w-4" }), _jsx("span", { children: editingClueId ? 'Cập nhật gợi ý' : 'Thêm vào danh sách' })] })] }), _jsxs("div", { className: "flex flex-col gap-2 max-h-[420px] overflow-y-auto pr-1", children: [_jsxs("span", { className: "text-xs font-bold text-gray-600", children: ["Danh s\u00E1ch (", clues.length, " t\u1EEB):"] }), clues.length === 0 && (_jsx("p", { className: "text-xs text-gray-400 italic", children: "Ch\u01B0a c\u00F3 g\u1EE3i \u00FD n\u00E0o. H\u00E3y th\u00EAm g\u1EE3i \u00FD \u1EDF tr\u00EAn." })), clues.map((c) => (_jsxs("div", { className: "flex items-center justify-between p-2.5 bg-white rounded-lg border border-gray-200 hover:border-blue-300 transition text-sm shadow-sm", children: [_jsxs("div", { className: "flex items-center gap-2 flex-1 min-w-0", children: [_jsx("span", { className: `flex items-center justify-center h-6 w-6 rounded text-xs font-bold text-white ${c.direction === 'across' ? 'bg-blue-600' : 'bg-purple-600'}`, children: c.number }), _jsxs("div", { className: "flex flex-col min-w-0 flex-1", children: [_jsxs("span", { className: "font-semibold text-gray-800 truncate", children: [c.clue, c.isExample && (_jsx("span", { className: "ml-1 rounded bg-amber-100 px-1.5 py-0.5 text-[10px] font-bold text-amber-700", children: "Example" }))] }), _jsxs("span", { className: "text-xs text-gray-500 flex items-center gap-2", children: [_jsx("span", { className: "font-bold text-blue-700", children: c.answer }), _jsxs("span", { children: ["(", c.length, " ch\u1EEF)"] }), _jsxs("span", { children: ["[", c.direction === 'across' ? '→ Ngang' : '↓ Dọc', " (", c.startRow, ",", c.startCol, ")]"] })] })] })] }), _jsxs("div", { className: "flex items-center gap-1", children: [_jsx("button", { type: "button", onClick: () => handleEditClue(c), className: "p-1 text-gray-500 hover:text-blue-600 rounded", title: "S\u1EEDa", children: _jsx(Edit2, { className: "h-4 w-4" }) }), _jsx("button", { type: "button", onClick: () => handleDeleteClue(c.id), className: "p-1 text-gray-500 hover:text-red-600 rounded", title: "X\u00F3a", children: _jsx(Trash2, { className: "h-4 w-4" }) })] })] }, c.id)))] })] }), _jsxs("div", { className: "lg:col-span-7 flex flex-col gap-2", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx("span", { className: "text-xs font-bold text-gray-700", children: "Ma tr\u1EADn l\u01B0\u1EDBi \u00F4 ch\u1EEF tr\u1EF1c quan:" }), conflicts.length > 0 && (_jsxs("span", { className: "flex items-center gap-1 text-xs font-bold text-red-600 bg-red-50 px-2 py-0.5 rounded border border-red-200", children: [_jsx(AlertCircle, { className: "h-3.5 w-3.5" }), _jsxs("span", { children: ["C\u00F3 ", conflicts.length, " \u00F4 b\u1ECB xung \u0111\u1ED9t ch\u1EEF c\u00E1i!"] })] }))] }), _jsx("div", { className: "flex justify-center overflow-x-auto p-4 bg-gray-100 rounded-xl border border-gray-300 min-h-[380px]", children: _jsx("div", { className: "inline-grid gap-1", style: {
227
284
  gridTemplateColumns: `repeat(${cols}, minmax(0, 1fr))`,
228
285
  }, children: Array.from({ length: rows }).map((_, rIdx) => {
229
286
  const r = rIdx + 1;
@@ -1,67 +1,96 @@
1
+ function asSubtype(value) {
2
+ return value === 'HIDDEN_WORD' ? 'HIDDEN_WORD' : 'STANDARD';
3
+ }
4
+ function mapClue(raw) {
5
+ return {
6
+ number: Number(raw.number),
7
+ clue: String(raw.clue || '').trim(),
8
+ startRow: Number(raw.startRow) || 1,
9
+ startCol: Number(raw.startCol) || 1,
10
+ length: Number(raw.length) || (raw.answer ? String(raw.answer).trim().length : 1),
11
+ ...(raw.isExample === true ? { isExample: true } : {}),
12
+ };
13
+ }
14
+ function collectAnswers(formClues, hiddenWord, existing) {
15
+ const answers = { ...(existing || {}) };
16
+ for (const c of formClues) {
17
+ const key = `${c.direction}-${c.number}`;
18
+ if (c.answer) {
19
+ answers[key] = String(c.answer).trim().toUpperCase();
20
+ }
21
+ }
22
+ const hidden = (hiddenWord || answers['hidden-word'] || '').trim();
23
+ if (hidden) {
24
+ answers['hidden-word'] = hidden.toUpperCase();
25
+ }
26
+ return answers;
27
+ }
1
28
  export const transformCrosswordPuzzle = (question) => {
2
29
  const contentData = question.content;
3
30
  const answerData = question.answer;
4
31
  let content;
5
32
  let correctAnswer;
6
33
  if (contentData?.gridSize && contentData?.clues) {
34
+ const type = asSubtype(contentData.type);
35
+ const markedColumn = Number(contentData.markedColumn);
7
36
  content = {
37
+ type,
8
38
  title: contentData.title || '',
39
+ ...(contentData.instruction ? { instruction: String(contentData.instruction) } : {}),
9
40
  gridSize: {
10
41
  rows: Number(contentData.gridSize.rows) || 12,
11
42
  cols: Number(contentData.gridSize.cols) || 12,
12
43
  },
44
+ ...(type === 'HIDDEN_WORD' && Number.isFinite(markedColumn) && markedColumn > 0
45
+ ? { markedColumn }
46
+ : {}),
13
47
  clues: {
14
- across: Array.isArray(contentData.clues.across) ? contentData.clues.across : [],
15
- down: Array.isArray(contentData.clues.down) ? contentData.clues.down : [],
48
+ across: Array.isArray(contentData.clues.across)
49
+ ? contentData.clues.across.map(mapClue)
50
+ : [],
51
+ down: Array.isArray(contentData.clues.down)
52
+ ? contentData.clues.down.map(mapClue)
53
+ : [],
16
54
  },
17
55
  };
56
+ const answers = { ...(answerData?.answers || {}) };
57
+ const hidden = String(answerData?.hiddenWord || answers['hidden-word'] || '').trim();
58
+ if (type === 'HIDDEN_WORD' && hidden) {
59
+ answers['hidden-word'] = hidden.toUpperCase();
60
+ }
18
61
  correctAnswer = {
19
- answers: answerData?.answers || {},
62
+ answers,
20
63
  caseSensitive: answerData?.caseSensitive || false,
21
64
  };
22
65
  }
23
66
  else if (Array.isArray(contentData?.clues)) {
24
67
  const formClues = contentData.clues;
25
- const across = formClues
26
- .filter((c) => c.direction === 'across')
27
- .map((c) => ({
28
- number: Number(c.number),
29
- clue: String(c.clue || '').trim(),
30
- startRow: Number(c.startRow) || 1,
31
- startCol: Number(c.startCol) || 1,
32
- length: Number(c.length) || (c.answer ? String(c.answer).trim().length : 1),
33
- }));
34
- const down = formClues
35
- .filter((c) => c.direction === 'down')
36
- .map((c) => ({
37
- number: Number(c.number),
38
- clue: String(c.clue || '').trim(),
39
- startRow: Number(c.startRow) || 1,
40
- startCol: Number(c.startCol) || 1,
41
- length: Number(c.length) || (c.answer ? String(c.answer).trim().length : 1),
42
- }));
68
+ const type = asSubtype(contentData.type);
69
+ const markedColumn = Number(contentData.markedColumn);
43
70
  content = {
71
+ type,
44
72
  title: contentData.title || '',
73
+ ...(contentData.instruction ? { instruction: String(contentData.instruction) } : {}),
45
74
  gridSize: {
46
75
  rows: Number(contentData.rows) || 12,
47
76
  cols: Number(contentData.cols) || 12,
48
77
  },
49
- clues: { across, down },
78
+ ...(type === 'HIDDEN_WORD' && Number.isFinite(markedColumn) && markedColumn > 0
79
+ ? { markedColumn }
80
+ : {}),
81
+ clues: {
82
+ across: formClues.filter((c) => c.direction === 'across').map(mapClue),
83
+ down: formClues.filter((c) => c.direction === 'down').map(mapClue),
84
+ },
50
85
  };
51
- const answers = {};
52
- for (const c of formClues) {
53
- const key = `${c.direction}-${c.number}`;
54
- if (c.answer) {
55
- answers[key] = String(c.answer).trim().toUpperCase();
56
- }
57
- }
58
86
  correctAnswer = {
59
- answers,
87
+ answers: collectAnswers(formClues, contentData.hiddenWord, answerData?.answers),
60
88
  caseSensitive: contentData.caseSensitive || false,
61
89
  };
62
90
  }
63
91
  else {
64
92
  content = {
93
+ type: 'STANDARD',
65
94
  gridSize: { rows: 12, cols: 12 },
66
95
  clues: { across: [], down: [] },
67
96
  };
@@ -2,13 +2,14 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
3
  import { useState, useEffect, useMemo } from 'react';
4
4
  import { Input } from '../../../../components/ui/input';
5
- import { Check, X, PenLine, AlertTriangle, ClipboardList, Lightbulb, BookOpen, Hash, Volume2, } from 'lucide-react';
5
+ import { Check, X, PenLine, ClipboardList, Lightbulb, BookOpen, Hash, Volume2, } from 'lucide-react';
6
6
  import { ImageLabelWordBank } from '../../groups/ImageLabelWordBank';
7
7
  import { hasImageLabels } from '../../_shared/types/question-group.utils';
8
8
  import { extractPlaceholderIndices, formatBlankAnswersDisplay, getPrimaryBlankAnswer, isBlankInputCorrect, normalizeBlankAnswers, normalizeFillInBlankAnswers, } from './answer-utils';
9
9
  import { parseFillBlankSegments } from '../../../../shared/lib/utils/fill-in-blank';
10
10
  import { cn } from '../../../../shared/lib/utils';
11
11
  import { usePresignedFileUrl } from '../../../../shared/lib/hooks';
12
+ import { KidPrompt, KidQuestionCard } from '../../_shared/components/compact';
12
13
  // Sub-component: Resolve presigned URL for group image
13
14
  function GroupImage({ src, alt, className }) {
14
15
  const { previewUrl, isLoading } = usePresignedFileUrl(src);
@@ -143,11 +144,11 @@ export function FillInBlankClient({ questionData, questionConfig, onSubmit, isRe
143
144
  const userInput = answers[placeholderIndex] || '';
144
145
  const isCorrect = isReviewMode && isAnswerCorrect(placeholderIndex, userInput);
145
146
  const isWrong = isReviewMode && userInput && !isCorrect;
146
- return (_jsxs("span", { className: "relative inline-flex items-center mx-0.5", children: [_jsx("span", { className: "flex h-5 w-5 items-center justify-center rounded-md bg-orange-100 text-[10px] font-bold text-orange-600 mr-1 ring-1 ring-orange-200/60", children: placeholderIndex + 1 }), _jsx(Input, { value: userInput, onChange: (e) => handleAnswerChange(placeholderIndex, e.target.value), disabled: isReviewMode, className: cn('h-9 w-28 text-center text-sm font-medium rounded-lg border-2 transition-all duration-200', 'focus:ring-2 focus:ring-offset-1 focus:outline-none', isReviewMode
147
+ return (_jsxs("span", { className: "relative inline-flex items-center mx-0.5", children: [_jsx("span", { className: "flex h-5 w-5 items-center justify-center rounded-md bg-orange-100 text-[10px] font-bold text-orange-600 mr-1 ring-1 ring-orange-200/60", children: placeholderIndex + 1 }), _jsx(Input, { value: userInput, onChange: (e) => handleAnswerChange(placeholderIndex, e.target.value), disabled: isReviewMode, className: cn('h-8 w-28 text-center text-sm font-medium rounded-md border px-3 py-1.5 transition-all duration-200', 'focus:ring-2 focus:ring-offset-1 focus:outline-none', isReviewMode
147
148
  ? isCorrect
148
- ? 'border-emerald-400 bg-emerald-50 text-emerald-700'
149
+ ? 'border-green-400 bg-green-50 text-green-800'
149
150
  : isWrong
150
- ? 'border-red-400 bg-red-50 text-red-700'
151
+ ? 'border-red-400 bg-red-50 text-red-800'
151
152
  : 'border-gray-200 bg-gray-50 text-gray-500'
152
153
  : 'border-orange-200 bg-white hover:border-orange-300 focus:border-orange-400 focus:ring-orange-200/50'), placeholder: "..." }), isReviewMode && (isCorrect || isWrong) && (_jsx("span", { className: cn('absolute -right-1.5 -top-1.5 flex h-4 w-4 items-center justify-center rounded-full shadow-sm ring-2 ring-white', isCorrect ? 'bg-emerald-500' : 'bg-red-500'), children: isCorrect ? (_jsx(Check, { className: "h-2.5 w-2.5 text-white" })) : (_jsx(X, { className: "h-2.5 w-2.5 text-white" })) }))] }, partIndex));
153
154
  }
@@ -162,11 +163,11 @@ export function FillInBlankClient({ questionData, questionConfig, onSubmit, isRe
162
163
  const userInput = answers[blankIndex] || '';
163
164
  const isCorrect = isReviewMode && isAnswerCorrect(blankIndex, userInput);
164
165
  const isWrong = isReviewMode && userInput && !isCorrect;
165
- return (_jsxs("span", { className: "relative inline-flex items-center mx-0.5", children: [_jsx(Input, { value: userInput, onChange: (e) => handleAnswerChange(blankIndex, e.target.value), disabled: isReviewMode, className: cn('h-9 w-32 text-center text-sm font-medium rounded-lg border-2 transition-all duration-200', 'focus:ring-2 focus:ring-offset-1 focus:outline-none', isReviewMode
166
+ return (_jsxs("span", { className: "relative inline-flex items-center mx-0.5", children: [_jsx(Input, { value: userInput, onChange: (e) => handleAnswerChange(blankIndex, e.target.value), disabled: isReviewMode, className: cn('h-8 w-32 text-center text-sm font-medium rounded-md border px-3 py-1.5 transition-all duration-200', 'focus:ring-2 focus:ring-offset-1 focus:outline-none', isReviewMode
166
167
  ? isCorrect
167
- ? 'border-emerald-400 bg-emerald-50 text-emerald-700'
168
+ ? 'border-green-400 bg-green-50 text-green-800'
168
169
  : isWrong
169
- ? 'border-red-400 bg-red-50 text-red-700'
170
+ ? 'border-red-400 bg-red-50 text-red-800'
170
171
  : 'border-gray-200 bg-gray-50 text-gray-500'
171
172
  : 'border-orange-200 bg-white hover:border-orange-300 focus:border-orange-400 focus:ring-orange-200/50'), placeholder: "..." }), isReviewMode && (isCorrect || isWrong) && (_jsx("span", { className: cn('absolute -right-1.5 -top-1.5 flex h-4 w-4 items-center justify-center rounded-full shadow-sm ring-2 ring-white', isCorrect ? 'bg-emerald-500' : 'bg-red-500'), children: isCorrect ? (_jsx(Check, { className: "h-2.5 w-2.5 text-white" })) : (_jsx(X, { className: "h-2.5 w-2.5 text-white" })) }))] }, partIdx));
172
173
  }
@@ -208,21 +209,17 @@ export function FillInBlankClient({ questionData, questionConfig, onSubmit, isRe
208
209
  // =====================================================
209
210
  if (!isLegacyFormat) {
210
211
  const hasAnyAnswer = placeholderIndices.some(idx => (answers[idx] || '').trim());
211
- return (_jsxs("div", { className: "space-y-4", children: [renderGroupBlock(), _jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm transition-all hover:shadow-md", 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-orange-500 to-amber-600 text-white", children: _jsx(PenLine, { className: "h-3.5 w-3.5" }) }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "text-sm font-semibold text-gray-700", children: "\u0110i\u1EC1n v\u00E0o ch\u1ED7 tr\u1ED1ng" }), _jsxs("span", { className: "inline-flex items-center gap-1 rounded-full bg-orange-100 px-2 py-0.5 text-xs font-medium text-orange-700 ring-1 ring-inset ring-orange-200/60", children: [_jsx(Hash, { className: "h-3 w-3" }), placeholderIndices.length] })] })] }), isReviewMode && score && (_jsxs("div", { className: cn('flex items-center gap-1.5 rounded-full px-3 py-1 text-xs font-bold', score.correct === score.total
212
- ? 'bg-emerald-100 text-emerald-700 ring-1 ring-emerald-200/60'
213
- : score.correct >= score.total / 2
214
- ? 'bg-yellow-100 text-yellow-700 ring-1 ring-yellow-200/60'
215
- : 'bg-red-100 text-red-700 ring-1 ring-red-200/60'), children: [score.correct === score.total && _jsx(Check, { className: "h-3 w-3" }), score.correct, "/", score.total, " \u0111\u00FAng"] }))] }), _jsxs("div", { className: "space-y-3 p-3", children: [showAudio && cleanAudioUrl && (_jsxs("div", { className: "flex items-center gap-3 rounded-lg border border-indigo-100 bg-gradient-to-r from-indigo-50 to-blue-50 p-3", children: [_jsx("div", { className: "flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-lg bg-indigo-100", children: _jsx(Volume2, { className: "h-4 w-4 text-indigo-600" }) }), _jsx("audio", { controls: true, className: "h-9 w-full", src: audioPreviewUrl || cleanAudioUrl, preload: "metadata", children: "Tr\u00ECnh duy\u1EC7t kh\u00F4ng h\u1ED7 tr\u1EE3 audio." })] })), _jsx("div", { className: "rounded-xl bg-gradient-to-r from-orange-50/80 to-amber-50/50 p-5 border border-orange-100/80", children: renderQuestionWithBlanks() }), renderQuestionImage(), renderHints(), isReviewMode && (_jsxs("div", { className: "rounded-xl border border-gray-200 bg-white overflow-hidden shadow-sm", children: [_jsxs("div", { className: "flex items-center gap-2 border-b border-gray-100 bg-gradient-to-r from-slate-50 to-gray-50 px-4 py-2.5", children: [_jsx("div", { className: "flex h-5 w-5 items-center justify-center rounded-full bg-slate-500 text-white", children: _jsx(ClipboardList, { className: "h-3 w-3" }) }), _jsx("h4", { className: "text-xs font-semibold text-gray-600 uppercase tracking-wider", children: "Chi ti\u1EBFt \u0111\u00E1p \u00E1n" })] }), _jsx("div", { className: "p-4 space-y-2.5", children: placeholderIndices.map((idx) => {
216
- const userInput = answers[idx] || '';
217
- const correctAnswer = formatBlankAnswersDisplay(correctAnswersMatrix[idx]) || '—';
218
- const isCorrect = isAnswerCorrect(idx, userInput);
219
- const hasInput = userInput.trim().length > 0;
220
- return (_jsxs("div", { className: cn('rounded-lg border p-3 transition-colors', isCorrect
221
- ? 'border-emerald-200 bg-emerald-50/50'
222
- : hasInput
223
- ? 'border-red-200 bg-red-50/50'
224
- : 'border-gray-100 bg-gray-50/50'), children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "flex h-6 w-6 items-center justify-center rounded-md bg-orange-100 text-xs font-bold text-orange-600 ring-1 ring-orange-200/60", children: idx + 1 }), _jsxs("span", { className: "text-sm text-gray-500", children: ["Ch\u1ED7 tr\u1ED1ng ", idx + 1] })] }), _jsx("div", { className: cn('flex items-center gap-1 text-xs font-semibold', isCorrect ? 'text-emerald-600' : hasInput ? 'text-red-600' : 'text-gray-400'), children: isCorrect ? (_jsxs(_Fragment, { children: [_jsx(Check, { className: "h-3.5 w-3.5" }), " \u0110\u00FAng"] })) : hasInput ? (_jsxs(_Fragment, { children: [_jsx(X, { className: "h-3.5 w-3.5" }), " Sai"] })) : ('Bỏ trống') })] }), _jsxs("div", { className: "mt-2 grid grid-cols-2 gap-2 text-sm", children: [_jsxs("div", { className: "flex items-center justify-between rounded-md bg-white px-3 py-2 border border-gray-100", children: [_jsx("span", { className: "text-gray-500 text-xs", children: "\u0110\u00E3 nh\u1EADp" }), _jsx("span", { className: cn('font-semibold', isCorrect ? 'text-emerald-600' : hasInput ? 'text-red-600' : 'text-gray-400 italic'), children: userInput || '—' })] }), _jsxs("div", { className: "flex items-center justify-between rounded-md bg-emerald-50 px-3 py-2 border border-emerald-100", children: [_jsx("span", { className: "text-gray-500 text-xs", children: "\u0110\u00E1p \u00E1n" }), _jsx("span", { className: "font-semibold text-emerald-600", children: correctAnswer })] })] })] }, idx));
225
- }) })] })), !isReviewMode && !hasAnyAnswer && (_jsxs("div", { className: "rounded-lg border border-amber-200 bg-gradient-to-r from-amber-50 to-yellow-50 p-3 flex items-center gap-2.5", children: [_jsx("div", { className: "flex h-5 w-5 flex-shrink-0 items-center justify-center rounded-full bg-amber-400 text-white", children: _jsx(AlertTriangle, { className: "h-3 w-3" }) }), _jsx("span", { className: "text-sm text-amber-800", children: "Vui l\u00F2ng \u0111i\u1EC1n c\u00E2u tr\u1EA3 l\u1EDDi v\u00E0o c\u00E1c ch\u1ED7 tr\u1ED1ng" })] }))] })] }), renderExplanation()] }));
212
+ return (_jsxs("div", { className: "space-y-4", children: [renderGroupBlock(), _jsxs(KidQuestionCard, { icon: _jsx(PenLine, { className: "h-3.5 w-3.5" }), title: "\u0110i\u1EC1n v\u00E0o ch\u1ED7 tr\u1ED1ng", points: questionData.points, isExample: questionData.isExample, review: isReviewMode && hasAnyAnswer ? (score && score.correct === score.total ? 'correct' : 'wrong') : null, headerExtra: _jsxs("span", { className: "inline-flex items-center gap-1 rounded-full bg-orange-100 px-2 py-0.5 text-xs font-medium text-orange-700", children: [_jsx(Hash, { className: "h-3 w-3" }), placeholderIndices.length] }), children: [showAudio && cleanAudioUrl && (_jsxs("div", { className: "flex items-center gap-3 rounded-lg border border-indigo-100 bg-gradient-to-r from-indigo-50 to-blue-50 p-3", children: [_jsx("div", { className: "flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-lg bg-indigo-100", children: _jsx(Volume2, { className: "h-4 w-4 text-indigo-600" }) }), _jsx("audio", { controls: true, className: "h-9 w-full", src: audioPreviewUrl || cleanAudioUrl, preload: "metadata", children: "Tr\u00ECnh duy\u1EC7t kh\u00F4ng h\u1ED7 tr\u1EE3 audio." })] })), _jsx(KidPrompt, { children: renderQuestionWithBlanks() }), renderQuestionImage(), renderHints(), isReviewMode && (_jsxs("div", { className: "rounded-xl border border-gray-200 bg-white overflow-hidden shadow-sm", children: [_jsxs("div", { className: "flex items-center gap-2 border-b border-gray-100 bg-gradient-to-r from-slate-50 to-gray-50 px-4 py-2.5", children: [_jsx("div", { className: "flex h-5 w-5 items-center justify-center rounded-full bg-slate-500 text-white", children: _jsx(ClipboardList, { className: "h-3 w-3" }) }), _jsx("h4", { className: "text-xs font-semibold text-gray-600 uppercase tracking-wider", children: "Chi ti\u1EBFt \u0111\u00E1p \u00E1n" })] }), _jsx("div", { className: "p-4 space-y-2.5", children: placeholderIndices.map((idx) => {
213
+ const userInput = answers[idx] || '';
214
+ const correctAnswer = formatBlankAnswersDisplay(correctAnswersMatrix[idx]) || '—';
215
+ const isCorrect = isAnswerCorrect(idx, userInput);
216
+ const hasInput = userInput.trim().length > 0;
217
+ return (_jsxs("div", { className: cn('rounded-lg border p-3 transition-colors', isCorrect
218
+ ? 'border-emerald-200 bg-emerald-50/50'
219
+ : hasInput
220
+ ? 'border-red-200 bg-red-50/50'
221
+ : 'border-gray-100 bg-gray-50/50'), children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "flex h-6 w-6 items-center justify-center rounded-md bg-orange-100 text-xs font-bold text-orange-600 ring-1 ring-orange-200/60", children: idx + 1 }), _jsxs("span", { className: "text-sm text-gray-500", children: ["Ch\u1ED7 tr\u1ED1ng ", idx + 1] })] }), _jsx("div", { className: cn('flex items-center gap-1 text-xs font-semibold', isCorrect ? 'text-emerald-600' : hasInput ? 'text-red-600' : 'text-gray-400'), children: isCorrect ? (_jsxs(_Fragment, { children: [_jsx(Check, { className: "h-3.5 w-3.5" }), " \u0110\u00FAng"] })) : hasInput ? (_jsxs(_Fragment, { children: [_jsx(X, { className: "h-3.5 w-3.5" }), " Sai"] })) : ('Bỏ trống') })] }), _jsxs("div", { className: "mt-2 grid grid-cols-2 gap-2 text-sm", children: [_jsxs("div", { className: "flex items-center justify-between rounded-md bg-white px-3 py-2 border border-gray-100", children: [_jsx("span", { className: "text-gray-500 text-xs", children: "\u0110\u00E3 nh\u1EADp" }), _jsx("span", { className: cn('font-semibold', isCorrect ? 'text-emerald-600' : hasInput ? 'text-red-600' : 'text-gray-400 italic'), children: userInput || '—' })] }), _jsxs("div", { className: "flex items-center justify-between rounded-md bg-emerald-50 px-3 py-2 border border-emerald-100", children: [_jsx("span", { className: "text-gray-500 text-xs", children: "\u0110\u00E1p \u00E1n" }), _jsx("span", { className: "font-semibold text-emerald-600", children: correctAnswer })] })] })] }, idx));
222
+ }) })] })), !isReviewMode && !hasAnyAnswer && (_jsxs("div", { className: "flex items-center gap-2 rounded-md border border-amber-200 bg-amber-50 px-2.5 py-1.5", children: [_jsx("div", { className: "flex h-4 w-4 flex-shrink-0 items-center justify-center rounded-full bg-amber-400 text-white", children: _jsx("span", { className: "text-[10px] font-bold", children: "!" }) }), _jsx("span", { className: "text-xs text-amber-800", children: "Vui l\u00F2ng \u0111i\u1EC1n c\u00E2u tr\u1EA3 l\u1EDDi v\u00E0o c\u00E1c ch\u1ED7 tr\u1ED1ng" })] }))] }), renderExplanation()] }));
226
223
  }
227
224
  // =====================================================
228
225
  // LEGACY FORMAT RENDER
@@ -231,15 +228,13 @@ export function FillInBlankClient({ questionData, questionConfig, onSubmit, isRe
231
228
  const userInput = answers[index] || '';
232
229
  const isCorrect = isReviewMode && isAnswerCorrect(index, userInput);
233
230
  const hasAnswered = userInput.trim() !== '';
234
- return (_jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm transition-all hover:shadow-md", 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-orange-500 to-amber-600 text-white", children: _jsx(PenLine, { className: "h-3.5 w-3.5" }) }), _jsxs("span", { className: "text-sm font-semibold text-gray-700", children: ["C\u00E2u ", index + 1, ": \u0110i\u1EC1n v\u00E0o ch\u1ED7 tr\u1ED1ng"] })] }), isReviewMode && hasAnswered && (_jsxs("div", { className: cn('flex items-center gap-1.5 rounded-full px-3 py-1 text-xs font-bold', isCorrect
235
- ? 'bg-emerald-100 text-emerald-700 ring-1 ring-emerald-200/60'
236
- : 'bg-red-100 text-red-700 ring-1 ring-red-200/60'), 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: [index === 0 && showAudio && cleanAudioUrl && (_jsxs("div", { className: "flex items-center gap-3 rounded-lg border border-indigo-100 bg-gradient-to-r from-indigo-50 to-blue-50 p-3", children: [_jsx("div", { className: "flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-lg bg-indigo-100", children: _jsx(Volume2, { className: "h-4 w-4 text-indigo-600" }) }), _jsx("audio", { controls: true, className: "h-9 w-full", src: audioPreviewUrl || cleanAudioUrl, preload: "metadata", children: "Tr\u00ECnh duy\u1EC7t kh\u00F4ng h\u1ED7 tr\u1EE3 audio." })] })), _jsx("div", { className: "rounded-xl bg-gradient-to-r from-orange-50/80 to-amber-50/50 p-5 border border-orange-100/80", children: renderLegacyBlank(blank.text, blank.id, index) }), index === 0 && renderQuestionImage(), index === 0 && renderHints(), isReviewMode && (_jsx("div", { className: cn('rounded-xl border p-3 transition-colors', isCorrect
237
- ? 'border-emerald-200 bg-emerald-50/50'
238
- : hasAnswered
239
- ? 'border-red-200 bg-red-50/50'
240
- : 'border-gray-100 bg-gray-50/50'), children: _jsxs("div", { className: "space-y-2.5", children: [_jsxs("div", { className: "flex items-center justify-between rounded-md bg-white p-3 border border-gray-100", children: [_jsx("span", { className: "text-sm text-gray-500", children: "\u0110\u00E3 nh\u1EADp:" }), _jsx("span", { className: cn('text-sm font-semibold', isCorrect ? 'text-emerald-600' : hasAnswered ? 'text-red-600' : 'text-gray-400 italic'), children: userInput || '—' })] }), _jsxs("div", { className: "flex items-center justify-between rounded-md bg-emerald-50 p-3 border border-emerald-100", children: [_jsx("span", { className: "text-sm text-gray-500", children: "\u0110\u00E1p \u00E1n \u0111\u00FAng:" }), _jsx("span", { className: "text-sm font-semibold text-emerald-600", children: formatBlankAnswersDisplay(blank.correctAnswers ?? normalizeBlankAnswers(blank.correctAnswer)) || '—' })] }), _jsxs("div", { className: cn('flex items-center justify-between rounded-md p-3 border', isCorrect
241
- ? 'bg-emerald-50 border-emerald-200'
242
- : 'bg-red-50 border-red-200'), children: [_jsx("span", { className: "text-sm text-gray-500", children: "K\u1EBFt qu\u1EA3:" }), _jsx("div", { className: cn('flex items-center gap-1.5 text-sm font-bold', isCorrect ? 'text-emerald-600' : 'text-red-600'), children: isCorrect ? (_jsxs(_Fragment, { children: [_jsx(Check, { className: "h-4 w-4" }), " Ch\u00EDnh x\u00E1c"] })) : (_jsxs(_Fragment, { children: [_jsx(X, { className: "h-4 w-4" }), " Sai"] })) })] })] }) })), !isReviewMode && !hasAnswered && (_jsxs("div", { className: "rounded-lg border border-amber-200 bg-gradient-to-r from-amber-50 to-yellow-50 p-3 flex items-center gap-2.5", children: [_jsx("div", { className: "flex h-5 w-5 flex-shrink-0 items-center justify-center rounded-full bg-amber-400 text-white", children: _jsx(AlertTriangle, { className: "h-3 w-3" }) }), _jsx("span", { className: "text-sm text-amber-800", children: "Vui l\u00F2ng \u0111i\u1EC1n c\u00E2u tr\u1EA3 l\u1EDDi" })] }))] })] }, blank.id));
231
+ return (_jsxs(KidQuestionCard, { icon: _jsx(PenLine, { className: "h-3.5 w-3.5" }), title: `Câu ${index + 1}: Điền vào chỗ trống`, review: isReviewMode && hasAnswered ? (isCorrect ? 'correct' : 'wrong') : null, children: [index === 0 && showAudio && cleanAudioUrl && (_jsxs("div", { className: "flex items-center gap-3 rounded-lg border border-indigo-100 bg-gradient-to-r from-indigo-50 to-blue-50 p-3", children: [_jsx("div", { className: "flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-lg bg-indigo-100", children: _jsx(Volume2, { className: "h-4 w-4 text-indigo-600" }) }), _jsx("audio", { controls: true, className: "h-9 w-full", src: audioPreviewUrl || cleanAudioUrl, preload: "metadata", children: "Tr\u00ECnh duy\u1EC7t kh\u00F4ng h\u1ED7 tr\u1EE3 audio." })] })), _jsx(KidPrompt, { children: renderLegacyBlank(blank.text, blank.id, index) }), index === 0 && renderQuestionImage(), index === 0 && renderHints(), isReviewMode && (_jsx("div", { className: cn('rounded-xl border p-3 transition-colors', isCorrect
232
+ ? 'border-emerald-200 bg-emerald-50/50'
233
+ : hasAnswered
234
+ ? 'border-red-200 bg-red-50/50'
235
+ : 'border-gray-100 bg-gray-50/50'), children: _jsxs("div", { className: "space-y-2.5", children: [_jsxs("div", { className: "flex items-center justify-between rounded-md bg-white p-3 border border-gray-100", children: [_jsx("span", { className: "text-sm text-gray-500", children: "\u0110\u00E3 nh\u1EADp:" }), _jsx("span", { className: cn('text-sm font-semibold', isCorrect ? 'text-emerald-600' : hasAnswered ? 'text-red-600' : 'text-gray-400 italic'), children: userInput || '—' })] }), _jsxs("div", { className: "flex items-center justify-between rounded-md bg-emerald-50 p-3 border border-emerald-100", children: [_jsx("span", { className: "text-sm text-gray-500", children: "\u0110\u00E1p \u00E1n \u0111\u00FAng:" }), _jsx("span", { className: "text-sm font-semibold text-emerald-600", children: formatBlankAnswersDisplay(blank.correctAnswers ?? normalizeBlankAnswers(blank.correctAnswer)) || '—' })] }), _jsxs("div", { className: cn('flex items-center justify-between rounded-md p-3 border', isCorrect
236
+ ? 'bg-emerald-50 border-emerald-200'
237
+ : 'bg-red-50 border-red-200'), children: [_jsx("span", { className: "text-sm text-gray-500", children: "K\u1EBFt qu\u1EA3:" }), _jsx("div", { className: cn('flex items-center gap-1.5 text-sm font-bold', isCorrect ? 'text-emerald-600' : 'text-red-600'), children: isCorrect ? (_jsxs(_Fragment, { children: [_jsx(Check, { className: "h-4 w-4" }), " Ch\u00EDnh x\u00E1c"] })) : (_jsxs(_Fragment, { children: [_jsx(X, { className: "h-4 w-4" }), " Sai"] })) })] })] }) })), !isReviewMode && !hasAnswered && (_jsxs("div", { className: "flex items-center gap-2 rounded-md border border-amber-200 bg-amber-50 px-2.5 py-1.5", children: [_jsx("div", { className: "flex h-4 w-4 flex-shrink-0 items-center justify-center rounded-full bg-amber-400 text-white", children: _jsx("span", { className: "text-[10px] font-bold", children: "!" }) }), _jsx("span", { className: "text-xs text-amber-800", children: "Vui l\u00F2ng \u0111i\u1EC1n c\u00E2u tr\u1EA3 l\u1EDDi" })] }))] }, blank.id));
243
238
  }), isReviewMode && score && (questionData.blanks?.length || 0) > 1 && (_jsx("div", { className: cn('overflow-hidden rounded-xl border shadow-sm', score.correct === score.total
244
239
  ? 'border-emerald-200 bg-gradient-to-r from-emerald-50 to-green-50'
245
240
  : score.correct >= score.total / 2
@@ -0,0 +1,6 @@
1
+ type FillInBlankAnswerKeyProps = {
2
+ answersByBlank: string[][];
3
+ blankIndices: number[];
4
+ };
5
+ export declare function FillInBlankAnswerKey({ answersByBlank, blankIndices, }: FillInBlankAnswerKeyProps): import("react").JSX.Element;
6
+ export {};
@@ -0,0 +1,17 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { Fragment } from 'react';
3
+ import { acceptedAnswerList } from './blank-utils';
4
+ function AnswerOrList({ answers }) {
5
+ if (answers.length === 0) {
6
+ return _jsx("span", { className: "font-semibold text-green-900", children: "(tr\u1ED1ng)" });
7
+ }
8
+ return (_jsx(_Fragment, { children: answers.map((answer, index) => (_jsxs(Fragment, { children: [index > 0 && (_jsx("span", { className: "mx-1 text-xs font-normal italic text-green-600", children: "ho\u1EB7c" })), _jsx("span", { className: "font-semibold text-green-900", children: answer })] }, `${answer}-${index}`))) }));
9
+ }
10
+ export function FillInBlankAnswerKey({ answersByBlank, blankIndices, }) {
11
+ const rows = blankIndices.map((blankIndex, order) => ({
12
+ order: order + 1,
13
+ answers: acceptedAnswerList(answersByBlank[blankIndex]),
14
+ }));
15
+ const manyBlanks = rows.length > 1;
16
+ return (_jsxs("div", { className: "space-y-1.5", children: [_jsx("p", { className: "text-xs font-semibold text-green-800", children: "\u0110\u00E1p \u00E1n \u0111\u00FAng" }), _jsx("div", { className: "grid grid-cols-2 gap-1.5", children: rows.map((row) => (_jsxs("div", { className: "min-w-0 rounded-md border border-green-200 bg-white px-2 py-1.5 text-sm", children: [manyBlanks && (_jsxs("div", { className: "text-[10px] font-semibold uppercase tracking-wide text-green-600", children: ["Ch\u1ED7 tr\u1ED1ng ", row.order] })), _jsx("div", { className: "flex flex-wrap items-baseline", children: _jsx(AnswerOrList, { answers: row.answers }) })] }, row.order))) })] }));
17
+ }