@tinyweb_dev/oe-exam-sdk 1.0.12 → 1.0.14

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 (53) hide show
  1. package/dist/components/exams/take/components/question-renderers/MoversTrueFalseRenderer.js +3 -1
  2. package/dist/components/exams/take/components/question-renderers/MoversWriteSentencesRenderer.js +3 -1
  3. package/dist/components/questions/_shared/components/compact/KidCollapsibleBlock.d.ts +8 -0
  4. package/dist/components/questions/_shared/components/compact/KidCollapsibleBlock.js +16 -0
  5. package/dist/components/questions/_shared/components/compact/KidPassage.d.ts +5 -0
  6. package/dist/components/questions/_shared/components/compact/KidPassage.js +21 -0
  7. package/dist/components/questions/_shared/components/compact/KidQuestionCard.js +3 -1
  8. package/dist/components/questions/_shared/components/compact/SpeakingReplyMic.d.ts +7 -0
  9. package/dist/components/questions/_shared/components/compact/SpeakingReplyMic.js +86 -0
  10. package/dist/components/questions/_shared/components/compact/index.d.ts +4 -0
  11. package/dist/components/questions/_shared/components/compact/index.js +4 -0
  12. package/dist/components/questions/_shared/components/compact/reviewItemCard.d.ts +3 -0
  13. package/dist/components/questions/_shared/components/compact/reviewItemCard.js +11 -0
  14. package/dist/components/questions/types/answer-the-question-group/AnswerTheQuestionGroupClient.js +14 -5
  15. package/dist/components/questions/types/answer-the-question-group/map-answer-the-question-group-data.js +2 -2
  16. package/dist/components/questions/types/arrange-letters-into-words/ArrangeLettersIntoWordsClient.js +2 -2
  17. package/dist/components/questions/types/choose-the-correct-answer/ChooseTheCorrectAnswerClient.js +19 -8
  18. package/dist/components/questions/types/choose-the-correct-answer-group/ChooseTheCorrectAnswerGroupClient.js +2 -2
  19. package/dist/components/questions/types/choose-then-answer-group/ChooseThenAnswerGroupClient.js +36 -56
  20. package/dist/components/questions/types/cross-out-word-group/CrossOutWordGroupClient.js +2 -2
  21. package/dist/components/questions/types/crossword-puzzle/CrosswordPuzzleClient.js +53 -50
  22. package/dist/components/questions/types/fill-in-blank/FillInBlankClient.js +2 -2
  23. package/dist/components/questions/types/fill-in-blank-group/FillInBlankAnswerKey.js +8 -2
  24. package/dist/components/questions/types/fill-in-blank-group/FillInBlankGroupClient.js +5 -3
  25. package/dist/components/questions/types/find-words-in-matrix/FindWordsInMatrixClient.js +18 -15
  26. package/dist/components/questions/types/match-columns-to-make-sentences/MatchColumnsToMakeSentencesClient.js +2 -2
  27. package/dist/components/questions/types/match-word-to-picture/MatchWordToPictureGroupClient.js +2 -2
  28. package/dist/components/questions/types/match-word-to-picture-group/MatchWordToPictureGroupClient.js +2 -2
  29. package/dist/components/questions/types/speaking-cue-card/SpeakingCueCardClient.d.ts +4 -1
  30. package/dist/components/questions/types/speaking-cue-card/SpeakingCueCardClient.js +4 -4
  31. package/dist/components/questions/types/spontaneous-qa-group/SpontaneousQaGroupClient.js +17 -82
  32. package/dist/components/questions/types/true-false-correct-group/TrueFalseCorrectGroupClient.js +2 -2
  33. package/dist/components/questions/types/true-false-group/TrueFalseGroupClient.js +2 -2
  34. package/dist/components/questions/types/word-fill-structured-form/WordFillStructuredFormClient.js +18 -23
  35. package/dist/components/questions/types/word-fill-structured-form/WordFillStructuredFormCreator.js +2 -8
  36. package/dist/components/questions/types/word-fill-structured-form/map-wfsf-question-data.js +4 -11
  37. package/dist/components/questions/types/word-fill-structured-form/transform.js +4 -9
  38. package/dist/components/questions/types/word-order-and-match-group/WordOrderAndMatchGroupClient.js +4 -2
  39. package/dist/components/questions/types/word-ordering-group/WordOrderingGroupClient.js +2 -2
  40. package/dist/components/questions/viewer/QuestionViewer.d.ts +3 -1
  41. package/dist/components/questions/viewer/QuestionViewer.js +37 -19
  42. package/dist/components/questions/viewer/SpontaneousQaGroupViewer.js +1 -1
  43. package/dist/shared/index.d.ts +2 -0
  44. package/dist/shared/index.js +1 -0
  45. package/dist/shared/lib/i18n/messages/en/common.d.ts +38 -0
  46. package/dist/shared/lib/i18n/messages/en/common.js +39 -0
  47. package/dist/shared/lib/i18n/messages/en.d.ts +38 -0
  48. package/dist/shared/lib/i18n/messages/vi/common.d.ts +38 -0
  49. package/dist/shared/lib/i18n/messages/vi/common.js +39 -0
  50. package/dist/shared/lib/i18n/messages/vi.d.ts +38 -0
  51. package/dist/shared/lib/utils/media-urls.d.ts +2 -0
  52. package/dist/shared/lib/utils/media-urls.js +23 -0
  53. package/package.json +1 -1
@@ -1,8 +1,9 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { Check, Lightbulb, X } from 'lucide-react';
4
+ import { useT } from '../../../../shared/lib/i18n';
4
5
  import { cn } from '../../../../shared/lib/utils';
5
- import { KidInstruction, KidQuestionCard } from '../../_shared/components/compact';
6
+ import { KidInstruction, KidQuestionCard, reviewItemPoints, reviewItemTitle } from '../../_shared/components/compact';
6
7
  function emptyAnswer() {
7
8
  return { optionId: '', text: '' };
8
9
  }
@@ -10,33 +11,13 @@ function formatHalf(points) {
10
11
  const half = points * 0.5;
11
12
  return Number.isInteger(half) ? String(half) : half.toFixed(1);
12
13
  }
13
- function badgeClass(params) {
14
- if (params.isExample) {
15
- return 'bg-amber-500';
16
- }
17
- if (!params.isReviewMode || !params.hasAttempt) {
18
- return 'bg-indigo-600';
19
- }
20
- if (params.partialScore != null) {
21
- if (params.partialScore >= params.points - 1e-9) {
22
- return 'bg-emerald-500';
23
- }
24
- if (params.partialScore > 0) {
25
- return 'bg-amber-500';
26
- }
27
- return 'bg-red-500';
28
- }
29
- if (params.mappingCorrect || params.hasText) {
30
- return 'bg-amber-500';
31
- }
32
- return 'bg-red-500';
33
- }
34
14
  export function ChooseThenAnswerGroupClient({ questionData, isReviewMode = false, userAnswers = [], itemResults, onAnswerChange, }) {
15
+ const t = useT();
35
16
  const canEdit = Boolean(onAnswerChange) && !isReviewMode;
36
17
  const items = questionData.items || [];
37
18
  const hasAttempt = Boolean(itemResults?.length) ||
38
19
  userAnswers.some((answer) => Boolean(answer?.optionId || answer?.text?.trim()));
39
- return (_jsxs("div", { className: "space-y-2", children: [(questionData.title || questionData.instruction) && (_jsxs(KidInstruction, { children: [questionData.title ? (_jsx("p", { className: "mb-1 text-[11px] font-semibold text-indigo-500", children: questionData.title })) : null, questionData.instruction] })), items.map((item, itemIndex) => {
20
+ return (_jsxs("div", { className: "space-y-3", children: [(questionData.title || questionData.instruction) && (_jsxs(KidInstruction, { children: [questionData.title ? (_jsx("p", { className: "mb-1 text-[11px] font-semibold text-indigo-500", children: questionData.title })) : null, questionData.instruction] })), items.map((item, itemIndex) => {
40
21
  const stored = userAnswers[itemIndex] || emptyAnswer();
41
22
  const current = item.isExample
42
23
  ? { optionId: item.optionId, text: stored.text }
@@ -62,38 +43,37 @@ export function ChooseThenAnswerGroupClient({ questionData, isReviewMode = false
62
43
  text: next.text ?? current.text,
63
44
  });
64
45
  };
65
- return (_jsx(KidQuestionCard, { title: `Câu ${item.questionNumber || itemIndex + 1}`, points: points, isExample: item.isExample, children: _jsxs("div", { className: "flex items-start gap-2", children: [_jsx("span", { className: cn('mt-0.5 flex h-7 w-7 shrink-0 items-center justify-center rounded-full text-xs font-bold text-white', badgeClass({
66
- isExample: item.isExample,
67
- isReviewMode,
68
- hasAttempt,
69
- points,
70
- partialScore: result?.partialScore,
71
- mappingCorrect,
72
- hasText: hasStudentText,
73
- })), children: item.questionNumber || itemIndex + 1 }), _jsxs("div", { className: "min-w-0 flex-1 space-y-2", children: [_jsxs("p", { className: "flex flex-wrap items-center gap-x-1.5 gap-y-1.5 text-sm font-semibold leading-7 text-slate-800", children: [_jsx("span", { children: parts[0] || '' }), _jsx("span", { className: "inline-flex items-center gap-1", children: item.options.map((option, optionIndex) => {
74
- const selected = current.optionId === option.id;
75
- const isKey = option.id === expectedId;
76
- const pickedCorrect = showGradedReview && selected && isKey;
77
- const pickedWrong = showGradedReview && selected && !isKey;
78
- const keyOnly = showGradedReview && !selected && isKey;
79
- const officialCorrect = showAnswerKey && isKey;
80
- return (_jsxs("span", { className: "inline-flex items-center gap-1", children: [optionIndex > 0 && (_jsx("span", { className: "text-sm font-medium text-slate-300", children: "|" })), _jsxs("button", { type: "button", disabled: !canEdit || item.isExample, onClick: () => setAnswer({ optionId: option.id }), className: cn('inline-flex min-w-[3.5rem] items-center justify-center gap-1 rounded-full border px-2.5 py-1 text-sm font-semibold transition-colors', !showReview &&
81
- selected &&
82
- 'border-blue-500 bg-blue-500 text-white shadow-sm ring-2 ring-blue-100', !showReview &&
83
- !selected &&
84
- 'border-gray-200 bg-white text-slate-800 hover:border-blue-300 hover:bg-blue-50', showAnswerKey &&
85
- !isKey &&
86
- 'border-slate-200 bg-white text-slate-800', officialCorrect &&
87
- 'border-2 border-emerald-500 bg-white text-slate-800', pickedCorrect && 'border-emerald-600 bg-emerald-600 text-white', pickedWrong && 'border-red-500 bg-red-500 text-white', keyOnly && 'border-emerald-500 bg-white text-emerald-800', showGradedReview &&
88
- !selected &&
89
- !isKey &&
90
- 'border-slate-200 bg-slate-50 text-slate-400', canEdit &&
91
- !item.isExample &&
92
- 'cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-200', (!canEdit || item.isExample) && 'cursor-default'), children: [option.text || option.id, pickedCorrect ? _jsx(Check, { className: "h-3 w-3" }) : null, pickedWrong ? _jsx(X, { className: "h-3 w-3" }) : null] })] }, option.id));
93
- }) }), _jsx("span", { children: parts.slice(1).join('{0}') })] }), showReview && hasAttempt ? (_jsxs("div", { className: "flex flex-wrap gap-1.5", children: [_jsxs("span", { className: cn('rounded-full px-2 py-0.5 text-[11px] font-semibold', mappingCorrect
94
- ? 'bg-emerald-50 text-emerald-700'
95
- : 'bg-red-50 text-red-700'), children: ["Ch\u1ECDn ", mappingCorrect ? halfLabel : '0'] }), result?.aiCorrect !== undefined ? (_jsxs("span", { className: cn('rounded-full px-2 py-0.5 text-[11px] font-semibold', result.aiCorrect
96
- ? 'bg-emerald-50 text-emerald-700'
97
- : 'bg-red-50 text-red-700'), children: ["Vi\u1EBFt ", result.aiCorrect ? halfLabel : '0'] })) : null] })) : null, canEdit || hasStudentText || (isReviewMode && hasAttempt) ? (_jsxs("label", { className: "block space-y-1", children: [_jsx("span", { className: "text-xs font-medium text-slate-500", children: "C\u00E2u tr\u1EA3 l\u1EDDi c\u1EE7a b\u1EA1n" }), isReviewMode && !hasStudentText ? (_jsx("p", { className: "rounded-lg border border-dashed border-slate-200 bg-slate-50 px-3 py-2 text-sm italic text-slate-400", children: "Ch\u01B0a vi\u1EBFt" })) : (_jsx("textarea", { value: current.text, disabled: !canEdit || item.isExample, onChange: (event) => setAnswer({ text: event.target.value }), placeholder: "Vi\u1EBFt c\u00E2u tr\u1EA3 l\u1EDDi t\u1EA1i \u0111\u00E2y...", rows: 2, className: "min-h-0 w-full resize-none rounded-lg border border-slate-200 bg-white px-3 py-2 text-sm leading-relaxed text-slate-800 outline-none focus:border-indigo-400 focus:ring-2 focus:ring-indigo-100 disabled:bg-slate-50" }))] })) : null, isReviewMode && sampleAnswers.length > 0 ? (_jsxs("p", { className: "text-xs text-slate-500", children: [_jsx("span", { className: "font-semibold", children: "M\u1EABu:" }), " ", sampleAnswers.join(' · ')] })) : null] })] }) }, `${item.questionNumber}-${itemIndex}`));
46
+ return (_jsx(KidQuestionCard, { title: reviewItemTitle(item.questionNumber || itemIndex + 1, isReviewMode, t('question.viewer.questionN', { n: item.questionNumber || itemIndex + 1 })), points: reviewItemPoints(isReviewMode, points), isExample: item.isExample, children: _jsx("div", { className: "space-y-3", children: _jsxs("div", { className: "min-w-0 space-y-2.5", children: [(parts[0] || parts.slice(1).join('{0}')) ? (_jsxs("p", { className: "text-sm font-semibold leading-7 text-slate-800", children: [_jsx("span", { children: parts[0] || '' }), _jsx("span", { className: "mx-1 inline-block min-w-[3rem] border-b-2 border-dashed border-slate-300 align-baseline" }), _jsx("span", { children: parts.slice(1).join('{0}') })] })) : null, _jsx("div", { className: "flex flex-wrap items-center gap-2.5", role: "group", "aria-label": t('question.viewer.questionN', {
47
+ n: item.questionNumber || itemIndex + 1,
48
+ }), children: item.options.map((option) => {
49
+ const selected = current.optionId === option.id;
50
+ const isKey = option.id === expectedId;
51
+ const pickedCorrect = showGradedReview && selected && isKey;
52
+ const pickedWrong = showGradedReview && selected && !isKey;
53
+ const keyOnly = showGradedReview && !selected && isKey;
54
+ const officialCorrect = showAnswerKey && isKey;
55
+ const optionId = `cta-${itemIndex}-opt-${option.id}`;
56
+ return (_jsxs("button", { id: optionId, name: `cta-${itemIndex}-option`, type: "button", disabled: !canEdit || item.isExample, onClick: () => setAnswer({ optionId: option.id }), className: cn('inline-flex min-w-[3.5rem] items-center justify-center gap-1 rounded-full border px-3 py-1.5 text-sm font-semibold transition-colors', !showReview &&
57
+ selected &&
58
+ 'border-blue-500 bg-blue-500 text-white shadow-sm ring-2 ring-blue-100', !showReview &&
59
+ !selected &&
60
+ 'border-gray-200 bg-white text-slate-800 hover:border-blue-300 hover:bg-blue-50', showAnswerKey &&
61
+ !isKey &&
62
+ 'border-slate-200 bg-white text-slate-800', officialCorrect &&
63
+ 'border-2 border-emerald-500 bg-white text-slate-800', pickedCorrect && 'border-emerald-600 bg-emerald-600 text-white', pickedWrong && 'border-red-500 bg-red-500 text-white', keyOnly && 'border-emerald-500 bg-white text-emerald-800', showGradedReview &&
64
+ !selected &&
65
+ !isKey &&
66
+ 'border-slate-200 bg-slate-50 text-slate-400', canEdit &&
67
+ !item.isExample &&
68
+ 'cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-200', (!canEdit || item.isExample) && 'cursor-default'), children: [option.text || option.id, pickedCorrect ? _jsx(Check, { className: "h-3 w-3" }) : null, pickedWrong ? _jsx(X, { className: "h-3 w-3" }) : null] }, option.id));
69
+ }) }), showReview && hasAttempt ? (_jsxs("div", { className: "flex flex-wrap gap-1.5", children: [_jsx("span", { className: cn('rounded-full px-2 py-0.5 text-[11px] font-semibold', mappingCorrect
70
+ ? 'bg-emerald-50 text-emerald-700'
71
+ : 'bg-red-50 text-red-700'), children: t('question.viewer.chooseScore', {
72
+ score: mappingCorrect ? halfLabel : '0',
73
+ }) }), result?.aiCorrect !== undefined ? (_jsx("span", { className: cn('rounded-full px-2 py-0.5 text-[11px] font-semibold', result.aiCorrect
74
+ ? 'bg-emerald-50 text-emerald-700'
75
+ : 'bg-red-50 text-red-700'), children: t('question.viewer.writeScore', {
76
+ score: result.aiCorrect ? halfLabel : '0',
77
+ }) })) : null] })) : null, canEdit || hasStudentText || (isReviewMode && hasAttempt) ? (_jsxs("label", { className: "block space-y-1", htmlFor: `cta-${itemIndex}-text`, children: [_jsx("span", { className: "text-xs font-medium text-slate-500", children: t('question.viewer.yourAnswer') }), isReviewMode && !hasStudentText ? (_jsx("p", { className: "rounded-lg border border-dashed border-slate-200 bg-slate-50 px-3 py-2 text-sm italic text-slate-400", children: t('question.viewer.notWritten') })) : (_jsx("textarea", { id: `cta-${itemIndex}-text`, name: `cta-${itemIndex}-text`, value: current.text, disabled: !canEdit || item.isExample, onChange: (event) => setAnswer({ text: event.target.value }), placeholder: t('question.viewer.writePlaceholder'), rows: 2, className: "min-h-0 w-full resize-none rounded-lg border border-slate-200 bg-white px-3 py-2 text-sm leading-relaxed text-slate-800 outline-none focus:border-indigo-400 focus:ring-2 focus:ring-indigo-100 disabled:bg-slate-50" }))] })) : null, isReviewMode && sampleAnswers.length > 0 ? (_jsxs("p", { className: "text-xs text-slate-500", children: [_jsxs("span", { className: "font-semibold", children: [t('question.viewer.sample'), ":"] }), ' ', sampleAnswers.join(' · ')] })) : null] }) }) }, `${item.questionNumber}-${itemIndex}`));
98
78
  }), isReviewMode && questionData.explanation && (_jsxs("div", { className: "flex items-start gap-2 rounded-lg border border-amber-100 bg-amber-50 px-3 py-2 text-sm text-amber-800", children: [_jsx(Lightbulb, { className: "mt-0.5 h-4 w-4 flex-shrink-0" }), questionData.explanation] }))] }));
99
79
  }
@@ -3,7 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { Lightbulb } from 'lucide-react';
4
4
  import { cn } from '../../../../shared/lib/utils';
5
5
  import { tokenizeCrossOutQuestion } from './tokenize';
6
- import { KidInstruction, KidQuestionCard } from '../../_shared/components/compact';
6
+ import { KidInstruction, KidQuestionCard, reviewItemPoints, reviewItemTitle } from '../../_shared/components/compact';
7
7
  function normalizeWord(word) {
8
8
  return word.replace(/[^\p{L}\p{N}'-]/gu, '').toLowerCase();
9
9
  }
@@ -48,7 +48,7 @@ export function CrossOutWordGroupClient({ questionData, isReviewMode = false, us
48
48
  const selectedWord = userValue?.crossOut || '';
49
49
  const replaceWith = userValue?.replaceWith || '';
50
50
  const correct = item.correctAnswer;
51
- return (_jsxs(KidQuestionCard, { title: `Câu ${item.questionNumber || itemIndex + 1}`, points: item.points || 0, isExample: item.isExample, headerExtra: isReviewMode ? (_jsx("span", { className: cn('rounded-full px-2 py-0.5 text-xs font-semibold', isReplace
51
+ return (_jsxs(KidQuestionCard, { title: reviewItemTitle(item.questionNumber || itemIndex + 1, isReviewMode, `Câu ${item.questionNumber || itemIndex + 1}`), points: reviewItemPoints(isReviewMode, item.points || 0), isExample: item.isExample, headerExtra: isReviewMode ? (_jsx("span", { className: cn('rounded-full px-2 py-0.5 text-xs font-semibold', isReplace
52
52
  ? 'bg-violet-100 text-violet-700'
53
53
  : 'bg-sky-100 text-sky-700'), children: isReplace ? 'Gạch + thay' : 'Chỉ gạch' })) : null, children: [isReplace ? (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("label", { className: "text-xs font-semibold text-slate-600", children: "T\u1EEB \u0111\u00FAng:" }), _jsx("input", { type: "text", value: replaceWith, disabled: !canSelect, onChange: (event) => emit(itemIndex, {
54
54
  type: item.type,
@@ -32,7 +32,9 @@ export function CrosswordPuzzleClient({ content, correctAnswer, isReviewMode = f
32
32
  }
33
33
  return { cellMap: cMap, clueNumberMap: numMap };
34
34
  }, [acrossClues, downClues]);
35
- return (_jsxs("div", { className: "flex flex-col items-stretch gap-3 text-left", children: [_jsxs("p", { className: "w-full text-left text-sm font-semibold text-gray-800", children: [_jsxs("span", { className: "font-bold", children: [displayNumber, "."] }), instructionText ? ` ${instructionText}` : ''] }), _jsxs("div", { className: "grid grid-cols-1 lg:grid-cols-12 gap-6 items-start", children: [_jsxs("div", { className: "lg:col-span-5 flex flex-col gap-4", children: [_jsxs("div", { className: "rounded-lg border border-blue-200 bg-blue-50/50 p-4", children: [_jsxs("h4", { className: "font-bold text-blue-900 mb-2.5 flex items-center gap-2", children: [_jsx("span", { className: "px-2 py-0.5 rounded bg-blue-600 text-white text-xs font-semibold", children: "Across" }), _jsx("span", { children: "H\u00E0ng ngang" })] }), _jsx("div", { className: "flex flex-col gap-2 max-h-60 overflow-y-auto pr-1", children: acrossClues.map((c) => {
35
+ return (_jsxs("div", { className: "flex w-full min-w-0 flex-col items-stretch gap-3 text-left", children: [_jsxs("p", { className: "w-full text-left text-sm font-semibold text-gray-800", children: [!isReviewMode ? (_jsxs("span", { className: "font-bold", children: [displayNumber, "."] })) : null, instructionText
36
+ ? `${!isReviewMode ? ' ' : ''}${instructionText}`
37
+ : ''] }), _jsxs("div", { className: "flex w-full min-w-0 flex-col gap-4", children: [_jsxs("div", { className: "grid min-w-0 grid-cols-1 gap-4 md:grid-cols-2", children: [_jsxs("div", { className: "rounded-lg border border-blue-200 bg-blue-50/50 p-4", children: [_jsxs("h4", { className: "font-bold text-blue-900 mb-2.5 flex items-center gap-2", children: [_jsx("span", { className: "px-2 py-0.5 rounded bg-blue-600 text-white text-xs font-semibold", children: "Across" }), _jsx("span", { children: "H\u00E0ng ngang" })] }), _jsx("div", { className: "flex flex-col gap-2 max-h-60 overflow-y-auto pr-1", children: acrossClues.map((c) => {
36
38
  const answer = correctAnswer?.answers?.[`across-${c.number}`];
37
39
  const userAns = userAnswers[`across-${c.number}`];
38
40
  return (_jsxs("div", { className: "flex items-start gap-2 text-sm bg-white p-2 rounded border border-blue-100", children: [_jsxs("span", { className: "font-bold text-blue-700 min-w-[20px]", children: [c.number, "."] }), _jsxs("div", { className: "flex-1", children: [_jsxs("span", { className: "text-gray-800", 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" }))] }), !isReviewMode && onAnswerChange && !c.isExample ? (_jsx("input", { type: "text", value: userAns || '', maxLength: c.length, onChange: (event) => onAnswerChange(`across-${c.number}`, event.target.value.toUpperCase()), placeholder: `${c.length} chữ`, className: "mt-1.5 w-full rounded border border-blue-200 px-2 py-1 text-sm font-semibold uppercase tracking-wider outline-none focus:border-blue-500" })) : null, isReviewMode && answer && (_jsxs("div", { className: "text-xs mt-1", children: [_jsx("span", { className: "text-gray-500", children: "\u0110\u00E1p \u00E1n: " }), _jsx("span", { className: "font-bold text-green-700", children: answer }), userAns && userAns !== answer && (_jsx("span", { className: "ml-2 line-through text-red-500 font-semibold", children: userAns }))] }))] }), _jsxs("span", { className: "text-xs text-gray-400", children: ["(", c.length, " ch\u1EEF)"] })] }, `across-${c.number}`));
@@ -40,57 +42,58 @@ export function CrosswordPuzzleClient({ content, correctAnswer, isReviewMode = f
40
42
  const answer = correctAnswer?.answers?.[`down-${c.number}`];
41
43
  const userAns = userAnswers[`down-${c.number}`];
42
44
  return (_jsxs("div", { className: "flex items-start gap-2 text-sm bg-white p-2 rounded border border-purple-100", children: [_jsxs("span", { className: "font-bold text-purple-700 min-w-[20px]", children: [c.number, "."] }), _jsxs("div", { className: "flex-1", children: [_jsxs("span", { className: "text-gray-800", 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" }))] }), !isReviewMode && onAnswerChange && !c.isExample ? (_jsx("input", { type: "text", value: userAns || '', maxLength: c.length, onChange: (event) => onAnswerChange(`down-${c.number}`, event.target.value.toUpperCase()), placeholder: `${c.length} chữ`, className: "mt-1.5 w-full rounded border border-purple-200 px-2 py-1 text-sm font-semibold uppercase tracking-wider outline-none focus:border-purple-500" })) : null, isReviewMode && answer && (_jsxs("div", { className: "text-xs mt-1", children: [_jsx("span", { className: "text-gray-500", children: "\u0110\u00E1p \u00E1n: " }), _jsx("span", { className: "font-bold text-green-700", children: answer }), userAns && userAns !== answer && (_jsx("span", { className: "ml-2 line-through text-red-500 font-semibold", children: userAns }))] }))] }), _jsxs("span", { className: "text-xs text-gray-400", children: ["(", c.length, " ch\u1EEF)"] })] }, `down-${c.number}`));
43
- }) })] })] }), _jsx("div", { className: "lg:col-span-7 flex justify-center overflow-x-auto p-4 bg-gray-50 rounded-xl border border-gray-200", children: _jsx("div", { className: "inline-grid gap-1", style: {
44
- gridTemplateColumns: `repeat(${cols}, minmax(0, 1fr))`,
45
- }, children: Array.from({ length: rows }).map((_, rIdx) => {
46
- const r = rIdx + 1;
47
- return Array.from({ length: cols }).map((_, cIdx) => {
48
- const c = cIdx + 1;
49
- const key = `${r}-${c}`;
50
- const cellInfo = cellMap.get(key);
51
- const clueNum = clueNumberMap.get(key);
52
- if (!cellInfo) {
53
- return (_jsx("div", { className: "h-8 w-8 sm:h-9 sm:w-9 md:h-10 md:w-10 bg-transparent" }, key));
54
- }
55
- const isMarked = content?.markedColumn && c === content.markedColumn;
56
- const isExample = Boolean(cellInfo.across?.isExample || cellInfo.down?.isExample);
57
- // If in review mode or cell is an example, display correct answer letter
58
- let previewLetter = '';
59
- if (isReviewMode || isExample) {
60
- if (cellInfo.across && correctAnswer?.answers) {
61
- const word = correctAnswer.answers[`across-${cellInfo.across.number}`] || '';
62
- const offset = c - cellInfo.across.startCol;
63
- previewLetter = word[offset] || '';
45
+ }) })] })] }), _jsx("div", { className: "w-full min-w-0 overflow-x-auto rounded-xl border border-gray-200 bg-gray-50", children: _jsx("div", { className: "flex w-max min-w-full justify-center p-4", children: _jsx("div", { className: "inline-grid shrink-0 gap-1 [--cw-cell:2rem] sm:[--cw-cell:2.25rem] md:[--cw-cell:2.5rem]", style: {
46
+ gridTemplateColumns: `repeat(${cols}, var(--cw-cell))`,
47
+ gridAutoRows: 'var(--cw-cell)',
48
+ }, children: Array.from({ length: rows }).map((_, rIdx) => {
49
+ const r = rIdx + 1;
50
+ return Array.from({ length: cols }).map((_, cIdx) => {
51
+ const c = cIdx + 1;
52
+ const key = `${r}-${c}`;
53
+ const cellInfo = cellMap.get(key);
54
+ const clueNum = clueNumberMap.get(key);
55
+ if (!cellInfo) {
56
+ return (_jsx("div", { className: "size-[var(--cw-cell)] bg-transparent" }, key));
64
57
  }
65
- else if (cellInfo.down && correctAnswer?.answers) {
66
- const word = correctAnswer.answers[`down-${cellInfo.down.number}`] || '';
67
- const offset = r - cellInfo.down.startRow;
68
- previewLetter = word[offset] || '';
58
+ const isMarked = content?.markedColumn && c === content.markedColumn;
59
+ const isExample = Boolean(cellInfo.across?.isExample || cellInfo.down?.isExample);
60
+ // If in review mode or cell is an example, display correct answer letter
61
+ let previewLetter = '';
62
+ if (isReviewMode || isExample) {
63
+ if (cellInfo.across && correctAnswer?.answers) {
64
+ const word = correctAnswer.answers[`across-${cellInfo.across.number}`] || '';
65
+ const offset = c - cellInfo.across.startCol;
66
+ previewLetter = word[offset] || '';
67
+ }
68
+ else if (cellInfo.down && correctAnswer?.answers) {
69
+ const word = correctAnswer.answers[`down-${cellInfo.down.number}`] || '';
70
+ const offset = r - cellInfo.down.startRow;
71
+ previewLetter = word[offset] || '';
72
+ }
69
73
  }
70
- }
71
- else {
72
- if (cellInfo.across) {
73
- const word = userAnswers[`across-${cellInfo.across.number}`] || '';
74
- previewLetter = word[c - cellInfo.across.startCol] || '';
74
+ else {
75
+ if (cellInfo.across) {
76
+ const word = userAnswers[`across-${cellInfo.across.number}`] || '';
77
+ previewLetter = word[c - cellInfo.across.startCol] || '';
78
+ }
79
+ if (!previewLetter && cellInfo.down) {
80
+ const word = userAnswers[`down-${cellInfo.down.number}`] || '';
81
+ previewLetter = word[r - cellInfo.down.startRow] || '';
82
+ }
83
+ if (!previewLetter) {
84
+ previewLetter = userAnswers[key] || '';
85
+ }
75
86
  }
76
- if (!previewLetter && cellInfo.down) {
77
- const word = userAnswers[`down-${cellInfo.down.number}`] || '';
78
- previewLetter = word[r - cellInfo.down.startRow] || '';
87
+ const cellBg = isMarked ? 'bg-gray-400 border-gray-400 shadow-sm ring-1 ring-gray-400/50' : 'bg-gray-100 border-gray-300';
88
+ let textColor = isExample
89
+ ? 'text-orange-600 font-bold italic'
90
+ : isMarked
91
+ ? 'text-gray-950 font-bold'
92
+ : 'text-gray-800 font-bold';
93
+ if (isReviewMode && previewLetter && !isExample) {
94
+ textColor = 'text-green-700 font-extrabold';
79
95
  }
80
- if (!previewLetter) {
81
- previewLetter = userAnswers[key] || '';
82
- }
83
- }
84
- const cellBg = isMarked ? 'bg-gray-400 border-gray-400 shadow-sm ring-1 ring-gray-400/50' : 'bg-gray-100 border-gray-300';
85
- let textColor = isExample
86
- ? 'text-orange-600 font-bold italic'
87
- : isMarked
88
- ? 'text-gray-950 font-bold'
89
- : 'text-gray-800 font-bold';
90
- if (isReviewMode && previewLetter && !isExample) {
91
- textColor = 'text-green-700 font-extrabold';
92
- }
93
- return (_jsxs("div", { className: `relative flex items-center justify-center rounded border-2 font-bold shadow-sm h-8 w-8 sm:h-9 sm:w-9 md:h-10 md:w-10 text-sm sm:text-base md:text-lg ${cellBg} ${textColor}`, children: [clueNum && (_jsx("span", { className: "absolute top-0.5 left-1 text-[9px] font-bold text-gray-500 leading-none", children: clueNum })), _jsx("span", { children: previewLetter })] }, key));
94
- });
95
- }) }) })] }), (content?.type === 'HIDDEN_WORD' || content?.markedColumn) && (_jsxs("div", { className: "rounded-lg border border-amber-200 bg-amber-50 p-3", children: [_jsx("label", { className: "block text-sm font-semibold text-amber-900 mb-1", children: "What's the hidden word?" }), !isReviewMode && onAnswerChange ? (_jsx("input", { type: "text", value: userAnswers['hidden-word'] || '', onChange: (event) => onAnswerChange('hidden-word', event.target.value.toUpperCase()), placeholder: "Hidden word", className: "w-full rounded border border-amber-300 px-3 py-1.5 text-sm font-semibold uppercase tracking-wider outline-none focus:border-amber-500" })) : (_jsxs("div", { className: "text-sm", children: [userAnswers['hidden-word'] && (_jsx("span", { className: "font-semibold text-gray-800", children: userAnswers['hidden-word'] })), isReviewMode && correctAnswer?.answers?.['hidden-word'] && (_jsxs("div", { className: "text-xs mt-1", children: [_jsx("span", { className: "text-gray-500", children: "\u0110\u00E1p \u00E1n: " }), _jsx("span", { className: "font-bold text-green-700", children: correctAnswer.answers['hidden-word'] })] }))] }))] }))] }));
96
+ return (_jsxs("div", { className: `relative flex size-[var(--cw-cell)] items-center justify-center rounded border-2 font-bold shadow-sm text-sm sm:text-base md:text-lg ${cellBg} ${textColor}`, children: [clueNum && (_jsx("span", { className: "absolute top-0.5 left-1 text-[9px] font-bold text-gray-500 leading-none", children: clueNum })), _jsx("span", { children: previewLetter })] }, key));
97
+ });
98
+ }) }) }) })] }), (content?.type === 'HIDDEN_WORD' || content?.markedColumn) && (_jsxs("div", { className: "rounded-lg border border-amber-200 bg-amber-50 p-3", children: [_jsx("label", { className: "block text-sm font-semibold text-amber-900 mb-1", children: "What's the hidden word?" }), !isReviewMode && onAnswerChange ? (_jsx("input", { type: "text", value: userAnswers['hidden-word'] || '', onChange: (event) => onAnswerChange('hidden-word', event.target.value.toUpperCase()), placeholder: "Hidden word", className: "w-full rounded border border-amber-300 px-3 py-1.5 text-sm font-semibold uppercase tracking-wider outline-none focus:border-amber-500" })) : (_jsxs("div", { className: "text-sm", children: [userAnswers['hidden-word'] && (_jsx("span", { className: "font-semibold text-gray-800", children: userAnswers['hidden-word'] })), isReviewMode && correctAnswer?.answers?.['hidden-word'] && (_jsxs("div", { className: "text-xs mt-1", children: [_jsx("span", { className: "text-gray-500", children: "\u0110\u00E1p \u00E1n: " }), _jsx("span", { className: "font-bold text-green-700", children: correctAnswer.answers['hidden-word'] })] }))] }))] }))] }));
96
99
  }
@@ -9,7 +9,7 @@ import { extractPlaceholderIndices, formatBlankAnswersDisplay, getPrimaryBlankAn
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
+ import { KidPrompt, KidQuestionCard, reviewItemTitle } from '../../_shared/components/compact';
13
13
  // Sub-component: Resolve presigned URL for group image
14
14
  function GroupImage({ src, alt, className }) {
15
15
  const { previewUrl, isLoading } = usePresignedFileUrl(src);
@@ -228,7 +228,7 @@ export function FillInBlankClient({ questionData, questionConfig, onSubmit, isRe
228
228
  const userInput = answers[index] || '';
229
229
  const isCorrect = isReviewMode && isAnswerCorrect(index, userInput);
230
230
  const hasAnswered = userInput.trim() !== '';
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
231
+ return (_jsxs(KidQuestionCard, { icon: _jsx(PenLine, { className: "h-3.5 w-3.5" }), title: reviewItemTitle(index + 1, isReviewMode, `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
232
  ? 'border-emerald-200 bg-emerald-50/50'
233
233
  : hasAnswered
234
234
  ? 'border-red-200 bg-red-50/50'
@@ -1,11 +1,17 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { Fragment } from 'react';
2
+ import { Fragment, useState } from 'react';
3
+ import { useT } from '../../../../shared/lib/i18n';
3
4
  import { acceptedAnswerList } from './blank-utils';
4
5
  function AnswerOrList({ answers }) {
6
+ const t = useT();
7
+ const [expanded, setExpanded] = useState(false);
5
8
  if (answers.length === 0) {
6
9
  return _jsx("span", { className: "font-semibold text-green-900", children: "(tr\u1ED1ng)" });
7
10
  }
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}`))) }));
11
+ const visible = expanded ? answers : answers.slice(0, 1);
12
+ return (_jsxs(_Fragment, { children: [visible.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}`))), answers.length > 1 ? (_jsx("button", { type: "button", onClick: () => setExpanded((value) => !value), className: "ml-1 text-xs font-semibold text-indigo-600 hover:text-indigo-800", children: expanded
13
+ ? t('question.viewer.collapsePassage')
14
+ : t('question.viewer.expandPassage') })) : null] }));
9
15
  }
10
16
  export function FillInBlankAnswerKey({ answersByBlank, blankIndices, }) {
11
17
  const rows = blankIndices.map((blankIndex, order) => ({
@@ -3,7 +3,7 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
3
3
  import { BookOpen, Lightbulb, PenLine, Volume2 } from 'lucide-react';
4
4
  import { cn } from '../../../../shared/lib/utils';
5
5
  import { usePresignedFileUrl } from '../../../../shared/lib/hooks';
6
- import { KidInstruction, KidPrompt, KidQuestionCard } from '../../_shared/components/compact';
6
+ import { COLLAPSE_CHAR_THRESHOLD, KidCollapsibleBlock, KidInstruction, KidPrompt, KidQuestionCard, reviewItemPoints, reviewItemTitle, } from '../../_shared/components/compact';
7
7
  import { FillInBlankAnswerKey } from './FillInBlankAnswerKey';
8
8
  import { blankSlotCount, extractPlaceholderIndices, isBlankMatch, padStudentAnswers, resolveBlankOptions, splitStem, splitStemTextDecorations, toFillInBlankImageUrls, } from './blank-utils';
9
9
  const STEM_MARK_STYLE = {
@@ -66,7 +66,9 @@ export function FillInBlankGroupClient({ questionData, isReviewMode = false, use
66
66
  next[itemIndex] = { answers: current };
67
67
  onAnswerChange(next);
68
68
  };
69
- return (_jsxs("div", { className: "space-y-4", children: [_jsx(KidInstruction, { children: questionData.instruction }), questionData.passage && (_jsxs("div", { className: "rounded-xl border border-gray-200 bg-white p-5 shadow-xs", children: [_jsxs("div", { className: "mb-2.5 flex items-center gap-2 text-xs font-bold uppercase tracking-wider text-indigo-700", children: [_jsx(BookOpen, { className: "h-4 w-4" }), _jsx("span", { children: "B\u00E0i \u0111\u1ECDc" })] }), _jsx("div", { className: "prose prose-sm max-w-none leading-relaxed text-gray-800", dangerouslySetInnerHTML: { __html: questionData.passage } })] })), questionData.audioUrl && (_jsxs("div", { className: "flex items-center gap-2 rounded-lg border border-slate-200 bg-white px-3 py-2", children: [_jsx(Volume2, { className: "h-4 w-4 text-slate-500" }), _jsx(MediaPreview, { src: questionData.audioUrl, kind: "audio", alt: "Group audio" })] })), toFillInBlankImageUrls(questionData.imageUrl).length > 0 && (_jsx("div", { className: "flex flex-wrap gap-2", children: toFillInBlankImageUrls(questionData.imageUrl).map((url, imageIndex) => (_jsx(MediaPreview, { src: url, kind: "image", alt: `Group image ${imageIndex + 1}` }, `group-image-${imageIndex}`))) })), groupHints.length > 0 && (_jsx("div", { className: "flex flex-wrap gap-2", children: groupHints.map((hint) => (_jsx("span", { className: "rounded-full border border-blue-200 bg-blue-50 px-3 py-1 text-xs font-semibold text-blue-700", children: hint }, hint))) })), _jsx("div", { className: "grid grid-cols-1 gap-3", children: items.map((item, itemIndex) => {
69
+ return (_jsxs("div", { className: "space-y-4", children: [_jsx(KidInstruction, { children: questionData.instruction }), questionData.passage && (_jsx(KidCollapsibleBlock, { collapse: isReviewMode &&
70
+ questionData.passage.replace(/<[^>]+>/g, ' ').replace(/\s+/g, ' ').trim().length >
71
+ COLLAPSE_CHAR_THRESHOLD, children: _jsxs("div", { className: "rounded-xl border border-gray-200 bg-white p-5 shadow-xs", children: [_jsxs("div", { className: "mb-2.5 flex items-center gap-2 text-xs font-bold uppercase tracking-wider text-indigo-700", children: [_jsx(BookOpen, { className: "h-4 w-4" }), _jsx("span", { children: "B\u00E0i \u0111\u1ECDc" })] }), _jsx("div", { className: "prose prose-sm max-w-none leading-relaxed text-gray-800", dangerouslySetInnerHTML: { __html: questionData.passage } })] }) })), questionData.audioUrl && (_jsxs("div", { className: "flex items-center gap-2 rounded-lg border border-slate-200 bg-white px-3 py-2", children: [_jsx(Volume2, { className: "h-4 w-4 text-slate-500" }), _jsx(MediaPreview, { src: questionData.audioUrl, kind: "audio", alt: "Group audio" })] })), toFillInBlankImageUrls(questionData.imageUrl).length > 0 && (_jsx("div", { className: "flex flex-wrap gap-2", children: toFillInBlankImageUrls(questionData.imageUrl).map((url, imageIndex) => (_jsx(MediaPreview, { src: url, kind: "image", alt: `Group image ${imageIndex + 1}` }, `group-image-${imageIndex}`))) })), groupHints.length > 0 && (_jsx("div", { className: "flex flex-wrap gap-2", children: groupHints.map((hint) => (_jsx("span", { className: "rounded-full border border-blue-200 bg-blue-50 px-3 py-1 text-xs font-semibold text-blue-700", children: hint }, hint))) })), _jsx("div", { className: "grid grid-cols-1 gap-3", children: items.map((item, itemIndex) => {
70
72
  const blankCount = blankSlotCount(item.question || '{0}');
71
73
  const placeholderIndices = extractPlaceholderIndices(item.question || '{0}');
72
74
  const userValue = padStudentAnswers(userAnswers[itemIndex], blankCount);
@@ -74,7 +76,7 @@ export function FillInBlankGroupClient({ questionData, isReviewMode = false, use
74
76
  const showItemAudio = Boolean(item.audioUrl) && !questionData.audioUrl;
75
77
  const hasAnyAnswer = userValue.some((value) => value.trim().length > 0);
76
78
  const allBlanksCorrect = placeholderIndices.every((index) => isBlankMatch(userValue[index] || '', item.answers[index] || []));
77
- return (_jsxs(KidQuestionCard, { icon: _jsx(PenLine, { className: "h-3.5 w-3.5" }), title: `Câu ${item.questionNumber || itemIndex + 1}`, points: item.points || 0, isExample: item.isExample, review: isReviewMode && !item.isExample && hasAnyAnswer ? (allBlanksCorrect ? 'correct' : 'wrong') : null, children: [toFillInBlankImageUrls(item.imageUrl).length > 0 && (_jsx("div", { className: "flex flex-wrap items-center gap-2", children: toFillInBlankImageUrls(item.imageUrl).map((url, imageIndex) => (_jsx(MediaPreview, { src: url, kind: "image", alt: `Item ${item.questionNumber} image ${imageIndex + 1}` }, `${item.questionNumber}-${imageIndex}`))) })), showItemAudio && item.audioUrl && (_jsx(MediaPreview, { src: item.audioUrl, kind: "audio", alt: `Item ${item.questionNumber} audio` })), _jsx(KidPrompt, { children: _jsx("div", { className: "min-w-0 text-sm leading-relaxed text-slate-800", children: stem.map((part, partIndex) => {
79
+ return (_jsxs(KidQuestionCard, { icon: _jsx(PenLine, { className: "h-3.5 w-3.5" }), title: reviewItemTitle(item.questionNumber || itemIndex + 1, isReviewMode, `Câu ${item.questionNumber || itemIndex + 1}`), points: reviewItemPoints(isReviewMode, item.points || 0), isExample: item.isExample, review: isReviewMode && !item.isExample && hasAnyAnswer ? (allBlanksCorrect ? 'correct' : 'wrong') : null, children: [toFillInBlankImageUrls(item.imageUrl).length > 0 && (_jsx("div", { className: "flex flex-wrap items-center gap-2", children: toFillInBlankImageUrls(item.imageUrl).map((url, imageIndex) => (_jsx(MediaPreview, { src: url, kind: "image", alt: `Item ${item.questionNumber} image ${imageIndex + 1}` }, `${item.questionNumber}-${imageIndex}`))) })), showItemAudio && item.audioUrl && (_jsx(MediaPreview, { src: item.audioUrl, kind: "audio", alt: `Item ${item.questionNumber} audio` })), _jsx(KidPrompt, { children: _jsx("div", { className: "min-w-0 text-sm leading-relaxed text-slate-800", children: stem.map((part, partIndex) => {
78
80
  if (part.type === 'text') {
79
81
  return (_jsx("span", { children: _jsx(StemText, { value: part.value }) }, partIndex));
80
82
  }
@@ -193,19 +193,22 @@ export function FindWordsInMatrixClient({ content, correctAnswer, showSolution =
193
193
  : 'bấm ô cuối để hoàn thành từ.'] })) : (_jsx(_Fragment, { children: "Ch\u1ECDn m\u1ED9t t\u1EEB trong danh s\u00E1ch, r\u1ED3i b\u1EA5m t\u1EEBng ch\u1EEF tr\u00EAn l\u01B0\u1EDBi (ho\u1EB7c \u00F4 \u0111\u1EA7u r\u1ED3i \u00F4 cu\u1ED1i)." })) })) : null, isCategorize ? (_jsx("div", { className: "grid grid-cols-1 gap-4 sm:grid-cols-2 md:grid-cols-3", children: categories.map((cat) => {
194
194
  const wordsInCat = wordBank.filter((word) => word.categoryId === cat.id);
195
195
  return (_jsxs(Card, { className: "border bg-gray-50/70 p-3.5", children: [_jsxs("div", { className: "mb-2.5 flex items-center justify-between border-b pb-1.5 text-sm font-bold", children: [_jsx("span", { children: cat.name }), _jsx(Badge, { variant: "outline", className: "text-xs", children: wordsInCat.length })] }), _jsx("div", { className: "flex flex-wrap gap-1.5", children: wordsInCat.map((word, index) => renderWordBadge(word, index)) })] }, cat.id));
196
- }) })) : (_jsxs(Card, { className: "border-2 border-dashed bg-gray-50 p-4", children: [_jsxs("div", { className: "mb-3 text-xs font-semibold uppercase tracking-wider text-gray-500", children: ["Word Bank (", wordBank.length, " words)"] }), _jsx("div", { className: "flex flex-wrap gap-2", children: wordBank.map((word, index) => renderWordBadge(word, index)) })] })), _jsx("div", { className: "flex justify-center overflow-x-auto pb-2", children: _jsx("div", { className: "inline-grid gap-1 rounded-xl border border-gray-200 bg-white p-3 shadow-sm", style: { gridTemplateColumns: `repeat(${cols}, minmax(0, 1fr))` }, children: Array.from({ length: rows }).map((_, rIdx) => {
197
- const r = rIdx + 1;
198
- return Array.from({ length: cols }).map((_, cIdx) => {
199
- const c = cIdx + 1;
200
- const char = grid[rIdx]?.[cIdx] || '';
201
- const highlight = cellColorMap[`${r}-${c}`];
202
- const colorConfig = highlight ? WORD_COLORS[highlight.colorIdx] : null;
203
- const isTapped = tappedPath.some((cell) => cell.row === r && cell.col === c);
204
- return (_jsx("button", { type: "button", disabled: !canSelect, onClick: () => handleCellClick(r, c), className: `flex h-9 w-9 items-center justify-center rounded-md border text-sm font-bold sm:h-11 sm:w-11 sm:text-base ${colorConfig
205
- ? `${colorConfig.bg} shadow-sm`
206
- : isTapped
207
- ? 'border-blue-500 bg-blue-50 text-blue-800'
208
- : 'border-gray-100 bg-gray-50/50 text-gray-700'}`, title: highlight ? `${highlight.word} (${r},${c})` : `(${r},${c})`, children: char || '·' }, `${r}-${c}`));
209
- });
210
- }) }) })] }));
196
+ }) })) : (_jsxs(Card, { className: "border-2 border-dashed bg-gray-50 p-4", children: [_jsxs("div", { className: "mb-3 text-xs font-semibold uppercase tracking-wider text-gray-500", children: ["Word Bank (", wordBank.length, " words)"] }), _jsx("div", { className: "flex flex-wrap gap-2", children: wordBank.map((word, index) => renderWordBadge(word, index)) })] })), _jsx("div", { className: "w-full min-w-0 overflow-x-auto pb-2", children: _jsx("div", { className: "flex w-max min-w-full justify-center", children: _jsx("div", { className: "inline-grid shrink-0 gap-1 rounded-xl border border-gray-200 bg-white p-3 shadow-sm [--fw-cell:2.25rem] sm:[--fw-cell:2.75rem]", style: {
197
+ gridTemplateColumns: `repeat(${cols}, var(--fw-cell))`,
198
+ gridAutoRows: 'var(--fw-cell)',
199
+ }, children: Array.from({ length: rows }).map((_, rIdx) => {
200
+ const r = rIdx + 1;
201
+ return Array.from({ length: cols }).map((_, cIdx) => {
202
+ const c = cIdx + 1;
203
+ const char = grid[rIdx]?.[cIdx] || '';
204
+ const highlight = cellColorMap[`${r}-${c}`];
205
+ const colorConfig = highlight ? WORD_COLORS[highlight.colorIdx] : null;
206
+ const isTapped = tappedPath.some((cell) => cell.row === r && cell.col === c);
207
+ return (_jsx("button", { type: "button", disabled: !canSelect, onClick: () => handleCellClick(r, c), className: `flex size-[var(--fw-cell)] items-center justify-center rounded-md border text-sm font-bold sm:text-base ${colorConfig
208
+ ? `${colorConfig.bg} shadow-sm`
209
+ : isTapped
210
+ ? 'border-blue-500 bg-blue-50 text-blue-800'
211
+ : 'border-gray-100 bg-gray-50/50 text-gray-700'}`, title: highlight ? `${highlight.word} (${r},${c})` : `(${r},${c})`, children: char || '·' }, `${r}-${c}`));
212
+ });
213
+ }) }) }) })] }));
211
214
  }
@@ -3,7 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { Lightbulb } from 'lucide-react';
4
4
  import { cn } from '../../../../shared/lib/utils';
5
5
  import { assembleMatchColumnsSentence, getMatchColumnsChoiceColumns, getMatchColumnsStemColumn, } from './map-match-columns-to-make-sentences-data';
6
- import { KidChoiceRow, KidInstruction, KidPrompt, KidQuestionCard, optionLetter, } from '../../_shared/components/compact';
6
+ import { KidChoiceRow, KidInstruction, KidPrompt, KidQuestionCard, optionLetter, reviewItemPoints, reviewItemTitle, } from '../../_shared/components/compact';
7
7
  function emptyAnswer(stemFragmentId) {
8
8
  return { fragmentIds: stemFragmentId ? [stemFragmentId] : [], sentence: '' };
9
9
  }
@@ -59,7 +59,7 @@ export function MatchColumnsToMakeSentencesClient({ questionData, isReviewMode =
59
59
  || '—';
60
60
  const isCorrect = isReviewMode &&
61
61
  current.sentence.trim().toLowerCase() === item.sentence.trim().toLowerCase();
62
- return (_jsxs(KidQuestionCard, { title: `Câu ${item.questionNumber || itemIndex + 1}`, points: item.points || 0, isExample: item.isExample, review: isReviewMode && !item.isExample ? (isCorrect ? 'correct' : 'wrong') : null, children: [_jsx(KidPrompt, { children: stemText }), choiceColumns.map((column, choiceIndex) => {
62
+ return (_jsxs(KidQuestionCard, { title: reviewItemTitle(item.questionNumber || itemIndex + 1, isReviewMode, `Câu ${item.questionNumber || itemIndex + 1}`), points: reviewItemPoints(isReviewMode, item.points || 0), isExample: item.isExample, review: isReviewMode && !item.isExample ? (isCorrect ? 'correct' : 'wrong') : null, children: [_jsx(KidPrompt, { children: stemText }), choiceColumns.map((column, choiceIndex) => {
63
63
  const selectedId = current.fragmentIds[choiceIndex + 1] || '';
64
64
  const correctId = item.fragmentIds[choiceIndex + 1] || '';
65
65
  return (_jsxs("div", { className: "space-y-1.5", children: [_jsx("p", { className: "text-xs font-medium uppercase tracking-wider text-gray-500", children: column.label || column.id }), column.fragments.map((fragment, fragmentIndex) => {
@@ -3,7 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { Lightbulb } from 'lucide-react';
4
4
  import { cn } from '../../../../shared/lib/utils';
5
5
  import { usePresignedFileUrl } from '../../../../shared/lib/hooks';
6
- import { KidChoiceRow, KidInstruction, KidQuestionCard, optionLetter, } from '../../_shared/components/compact';
6
+ import { KidChoiceRow, KidInstruction, KidQuestionCard, optionLetter, reviewItemPoints, reviewItemTitle, } from '../../_shared/components/compact';
7
7
  function normalizeWord(value) {
8
8
  return value.trim().toLowerCase();
9
9
  }
@@ -53,7 +53,7 @@ export function MatchWordToPictureGroupClient({ questionData, isReviewMode = fal
53
53
  const canSelect = !isReviewMode && !item.isExample && Boolean(onAnswerChange);
54
54
  const isCorrect = normalizeWord(selectedWord) === normalizeWord(item.correctAnswer) &&
55
55
  Boolean(item.correctAnswer);
56
- return (_jsx(KidQuestionCard, { title: `Câu ${item.questionNumber || itemIndex + 1}`, points: item.points || 0, isExample: item.isExample, review: isReviewMode && !item.isExample ? (isCorrect ? 'correct' : 'wrong') : null, children: _jsxs("div", { className: "flex flex-col gap-3 sm:flex-row sm:items-start", children: [_jsx(ClientImageItem, { url: item.imageUrl, alt: `Hình ảnh câu ${item.questionNumber || itemIndex + 1}` }), _jsxs("div", { className: "min-w-0 flex-1 space-y-1.5", children: [_jsx("p", { className: "text-sm font-semibold text-gray-800", children: "Ch\u1ECDn t\u1EEB ph\u00F9 h\u1EE3p" }), wordBank.map((word, wordIndex) => {
56
+ return (_jsx(KidQuestionCard, { title: reviewItemTitle(item.questionNumber || itemIndex + 1, isReviewMode, `Câu ${item.questionNumber || itemIndex + 1}`), points: reviewItemPoints(isReviewMode, item.points || 0), isExample: item.isExample, review: isReviewMode && !item.isExample ? (isCorrect ? 'correct' : 'wrong') : null, children: _jsxs("div", { className: "flex flex-col gap-3 sm:flex-row sm:items-start", children: [_jsx(ClientImageItem, { url: item.imageUrl, alt: `Hình ảnh câu ${item.questionNumber || itemIndex + 1}` }), _jsxs("div", { className: "min-w-0 flex-1 space-y-1.5", children: [_jsx("p", { className: "text-sm font-semibold text-gray-800", children: "Ch\u1ECDn t\u1EEB ph\u00F9 h\u1EE3p" }), wordBank.map((word, wordIndex) => {
57
57
  const isSelected = normalizeWord(word) === normalizeWord(selectedWord);
58
58
  const isUsedByOther = usedWords.has(normalizeWord(word)) && !isSelected;
59
59
  const isWordCorrect = normalizeWord(word) === normalizeWord(item.correctAnswer);
@@ -4,7 +4,7 @@ import { BookOpen, Check, ChevronDown, Lightbulb, Volume2, X } from 'lucide-reac
4
4
  import { cn } from '../../../../shared/lib/utils';
5
5
  import { usePresignedFileUrl } from '../../../../shared/lib/hooks';
6
6
  import { toMatchWordToPictureImageUrls, toMatchWordToPictureStorageKey, } from '../../_shared/types/match-word-to-picture-group.type';
7
- import { KidInstruction, KidPrompt, KidQuestionCard, } from '../../_shared/components/compact';
7
+ import { KidInstruction, KidPrompt, KidQuestionCard, reviewItemPoints, reviewItemTitle, } from '../../_shared/components/compact';
8
8
  function normalizeWord(value) {
9
9
  return value.trim().toLowerCase();
10
10
  }
@@ -76,7 +76,7 @@ export function MatchWordToPictureGroupClient({ questionData, isReviewMode = fal
76
76
  const isCorrect = normalizeWord(selectedWord) === normalizeWord(item.correctAnswer) &&
77
77
  Boolean(item.correctAnswer);
78
78
  const itemImages = toMatchWordToPictureImageUrls(item.imageUrl);
79
- return (_jsx(KidQuestionCard, { title: `Câu ${item.questionNumber || itemIndex + 1}`, points: item.points || 0, isExample: item.isExample, review: isReviewMode && !item.isExample ? (isCorrect ? 'correct' : 'wrong') : null, children: _jsxs("div", { className: "flex flex-col gap-3 sm:flex-row sm:items-start", children: [itemImages.length > 0 ? (_jsx("div", { className: "flex flex-wrap gap-2", children: itemImages.map((url, imageIndex) => (_jsx(ClientImageItem, { url: url, alt: `Hình ảnh câu ${item.questionNumber || itemIndex + 1}${imageIndex > 0 ? ` (${imageIndex + 1})` : ''}` }, `${item.questionNumber}-${imageIndex}`))) })) : null, _jsxs("div", { className: "min-w-0 flex-1 space-y-1.5", children: [_jsx("p", { className: "text-sm font-semibold text-gray-800", children: "Ch\u1ECDn t\u1EEB ph\u00F9 h\u1EE3p" }), item.isExample ? (_jsx("span", { className: "text-sm italic text-gray-800 underline decoration-gray-400", children: item.correctAnswer })) : (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(WordDropdown, { value: selectedWord, wordBank: wordBank, usedWords: usedWords, disabled: !canSelect, onChange: (value) => handleSelect(itemIndex, value) }), isReviewMode && (isCorrect
79
+ return (_jsx(KidQuestionCard, { title: reviewItemTitle(item.questionNumber || itemIndex + 1, isReviewMode, `Câu ${item.questionNumber || itemIndex + 1}`), points: reviewItemPoints(isReviewMode, item.points || 0), isExample: item.isExample, review: isReviewMode && !item.isExample ? (isCorrect ? 'correct' : 'wrong') : null, children: _jsxs("div", { className: "flex flex-col gap-3 sm:flex-row sm:items-start", children: [itemImages.length > 0 ? (_jsx("div", { className: "flex flex-wrap gap-2", children: itemImages.map((url, imageIndex) => (_jsx(ClientImageItem, { url: url, alt: `Hình ảnh câu ${item.questionNumber || itemIndex + 1}${imageIndex > 0 ? ` (${imageIndex + 1})` : ''}` }, `${item.questionNumber}-${imageIndex}`))) })) : null, _jsxs("div", { className: "min-w-0 flex-1 space-y-1.5", children: [_jsx("p", { className: "text-sm font-semibold text-gray-800", children: "Ch\u1ECDn t\u1EEB ph\u00F9 h\u1EE3p" }), item.isExample ? (_jsx("span", { className: "text-sm italic text-gray-800 underline decoration-gray-400", children: item.correctAnswer })) : (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(WordDropdown, { value: selectedWord, wordBank: wordBank, usedWords: usedWords, disabled: !canSelect, onChange: (value) => handleSelect(itemIndex, value) }), isReviewMode && (isCorrect
80
80
  ? _jsx(Check, { className: "h-5 w-5 text-emerald-600" })
81
81
  : _jsx(X, { className: "h-5 w-5 text-red-500" }))] })), isReviewMode && !item.isExample && !isCorrect && (_jsxs("p", { className: "text-sm text-green-700", children: ["\u0110\u00E1p \u00E1n \u0111\u00FAng: ", _jsx("strong", { children: item.correctAnswer || '—' })] }))] })] }) }, `${item.questionNumber}-${itemIndex}`));
82
82
  }), 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] }))] }));
@@ -6,6 +6,9 @@ export interface SpeakingCueCardClientProps {
6
6
  explanation?: string;
7
7
  };
8
8
  isReviewMode?: boolean;
9
+ audioKey?: string | null;
10
+ uploading?: boolean;
11
+ onRecorded?: (blob: Blob) => void;
9
12
  }
10
13
  export declare function mapQuestionToSpeakingCueCardData(question: Record<string, unknown> | null | undefined): SpeakingCueCardClientProps['questionData'];
11
- export declare function SpeakingCueCardClient({ questionData, isReviewMode, }: SpeakingCueCardClientProps): import("react").JSX.Element;
14
+ export declare function SpeakingCueCardClient({ questionData, isReviewMode, audioKey, uploading, onRecorded, }: SpeakingCueCardClientProps): import("react").JSX.Element;
@@ -1,8 +1,8 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { Mic, CheckCircle2, Info, CreditCard } from 'lucide-react';
3
+ import { CheckCircle2, Info, CreditCard } from 'lucide-react';
4
4
  import { RichTextHtml } from '../../../../components/shared/RichTextHtml';
5
- import { KidQuestionCard } from '../../_shared/components/compact';
5
+ import { KidQuestionCard, SpeakingReplyMic } from '../../_shared/components/compact';
6
6
  function asRecord(value) {
7
7
  return value && typeof value === 'object' && !Array.isArray(value)
8
8
  ? value
@@ -22,7 +22,7 @@ export function mapQuestionToSpeakingCueCardData(question) {
22
22
  explanation: typeof source.explanation === 'string' ? source.explanation : '',
23
23
  };
24
24
  }
25
- export function SpeakingCueCardClient({ questionData, isReviewMode = false, }) {
25
+ export function SpeakingCueCardClient({ questionData, isReviewMode = false, audioKey = null, uploading = false, onRecorded, }) {
26
26
  const answers = questionData.expectedAnswers || [];
27
- return (_jsxs("div", { className: "space-y-4", children: [questionData.isBackup && (_jsxs("div", { className: "inline-flex self-center items-center gap-1.5 px-3 py-1.5 rounded-md bg-amber-50 text-amber-700 border border-amber-200 text-xs font-bold shadow-sm", children: [_jsx("span", { children: "\uD83D\uDD04" }), " C\u00E2u h\u1ECFi d\u1EF1 ph\u00F2ng"] })), _jsxs(KidQuestionCard, { title: "Th\u1EBB g\u1EE3i \u00FD (Cue Card)", icon: _jsx(CreditCard, { className: "h-3.5 w-3.5" }), children: [_jsx("div", { className: "rounded-xl border border-purple-100 bg-purple-50/40 px-5 py-4", children: _jsx(RichTextHtml, { html: questionData.cueCardHtml, className: "text-sm text-slate-800", fallback: _jsx("p", { className: "text-sm text-gray-400", children: "Ch\u01B0a c\u00F3 n\u1ED9i dung cue card" }) }) }), _jsxs("div", { className: "flex items-center gap-3 rounded-lg border border-dashed border-gray-300 bg-gray-50/80 px-4 py-3", children: [_jsx("div", { className: "flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-full bg-purple-50 ring-2 ring-purple-100/50", children: _jsx(Mic, { className: "w-4 h-4 text-purple-400" }) }), _jsx("p", { className: "text-xs text-gray-400", children: "H\u1ECDc sinh \u0111\u1ECDc th\u1EBB g\u1EE3i \u00FD, chu\u1EA9n b\u1ECB, r\u1ED3i tr\u1EA3 l\u1EDDi b\u1EB1ng gi\u1ECDng n\u00F3i" })] }), isReviewMode && answers.length > 0 && (_jsxs("div", { className: "rounded-xl border border-green-200 bg-green-50/80 p-4", children: [_jsxs("div", { className: "flex items-center gap-2 mb-3", children: [_jsx(CheckCircle2, { className: "h-4 w-4 text-green-600" }), _jsx("h4", { className: "text-sm font-semibold text-green-800", children: "\u0110\u00E1p \u00E1n m\u1EABu" })] }), _jsx("div", { className: "space-y-2", children: answers.map((answer, index) => (_jsxs("div", { className: "flex items-center gap-2.5 rounded-lg bg-white/70 border border-green-100 px-3 py-2", children: [_jsx("span", { className: "flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-full bg-green-100 text-green-700 text-xs font-bold", children: index + 1 }), _jsx("span", { className: "text-sm text-green-800", children: answer })] }, `${answer}-${index}`))) })] })), isReviewMode && questionData.explanation && (_jsxs("div", { className: "rounded-xl border border-amber-200 bg-amber-50 p-4", children: [_jsxs("div", { className: "flex items-center gap-2 mb-2", children: [_jsx(Info, { className: "h-4 w-4 text-amber-600" }), _jsx("p", { className: "text-sm font-semibold text-amber-800", children: "Gi\u1EA3i th\u00EDch" })] }), _jsx("p", { className: "text-sm text-amber-700 leading-relaxed whitespace-pre-wrap", children: questionData.explanation })] }))] })] }));
27
+ return (_jsxs("div", { className: "space-y-4", children: [questionData.isBackup && (_jsxs("div", { className: "inline-flex self-center items-center gap-1.5 px-3 py-1.5 rounded-md bg-amber-50 text-amber-700 border border-amber-200 text-xs font-bold shadow-sm", children: [_jsx("span", { children: "\uD83D\uDD04" }), " C\u00E2u h\u1ECFi d\u1EF1 ph\u00F2ng"] })), _jsxs(KidQuestionCard, { title: "Th\u1EBB g\u1EE3i \u00FD (Cue Card)", icon: _jsx(CreditCard, { className: "h-3.5 w-3.5" }), children: [_jsx("div", { className: "rounded-xl border border-purple-100 bg-purple-50/40 px-5 py-4", children: _jsx(RichTextHtml, { html: questionData.cueCardHtml, className: "text-sm text-slate-800", fallback: _jsx("p", { className: "text-sm text-gray-400", children: "Ch\u01B0a c\u00F3 n\u1ED9i dung cue card" }) }) }), isReviewMode && !onRecorded ? (audioKey ? (_jsx(SpeakingReplyMic, { flattenIndex: 0, audioKey: audioKey, disabled: true })) : (_jsx("p", { className: "text-xs text-gray-400", children: "Ch\u01B0a c\u00F3 b\u1EA3n ghi." }))) : (_jsx(SpeakingReplyMic, { flattenIndex: 0, audioKey: audioKey, uploading: uploading, onRecorded: onRecorded ? (_index, blob) => onRecorded(blob) : undefined })), isReviewMode && answers.length > 0 && (_jsxs("div", { className: "rounded-xl border border-green-200 bg-green-50/80 p-4", children: [_jsxs("div", { className: "flex items-center gap-2 mb-3", children: [_jsx(CheckCircle2, { className: "h-4 w-4 text-green-600" }), _jsx("h4", { className: "text-sm font-semibold text-green-800", children: "\u0110\u00E1p \u00E1n m\u1EABu" })] }), _jsx("div", { className: "space-y-2", children: answers.map((answer, index) => (_jsxs("div", { className: "flex items-center gap-2.5 rounded-lg bg-white/70 border border-green-100 px-3 py-2", children: [_jsx("span", { className: "flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-full bg-green-100 text-green-700 text-xs font-bold", children: index + 1 }), _jsx("span", { className: "text-sm text-green-800", children: answer })] }, `${answer}-${index}`))) })] })), isReviewMode && questionData.explanation && (_jsxs("div", { className: "rounded-xl border border-amber-200 bg-amber-50 p-4", children: [_jsxs("div", { className: "flex items-center gap-2 mb-2", children: [_jsx(Info, { className: "h-4 w-4 text-amber-600" }), _jsx("p", { className: "text-sm font-semibold text-amber-800", children: "Gi\u1EA3i th\u00EDch" })] }), _jsx("p", { className: "text-sm text-amber-700 leading-relaxed whitespace-pre-wrap", children: questionData.explanation })] }))] })] }));
28
28
  }