@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
@@ -3,6 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { Mic, Volume2, CheckCircle2, Play, Pause, MessageCircle, Users } from 'lucide-react';
4
4
  import { Button } from '../../../../components/ui/button';
5
5
  import { usePresignedFileUrl, useAudioPreview } from '../../../../shared/lib/hooks';
6
+ import { KidPrompt, KidQuestionCard } from '../../_shared/components/compact';
6
7
  // ─── Sub-component: Audio Player ─────────────────────────────────────────────
7
8
  function ConversationAudioPlayer({ audioUrl, label }) {
8
9
  const { previewUrl } = usePresignedFileUrl(audioUrl);
@@ -33,9 +34,9 @@ export function SpeakingConversationPreviewClient({ questionData, isReviewMode =
33
34
  : questionData.images.length === 4
34
35
  ? 'grid-cols-4'
35
36
  : 'grid-cols-3';
36
- return (_jsxs("div", { className: "space-y-4", children: [questionData.isBackup && (_jsxs("div", { className: "inline-flex 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("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-gray-100 bg-gray-50 px-3 py-2", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "flex h-7 w-7 items-center justify-center rounded-md bg-gray-100 text-gray-600", children: _jsx(MessageCircle, { className: "h-4 w-4" }) }), _jsx("span", { className: "text-sm font-medium text-gray-700", children: "H\u1ED9i tho\u1EA1i Speaking (KFS)" })] }), _jsxs("div", { className: `inline-flex items-center gap-1.5 rounded-full px-3 py-1 text-xs font-semibold ${isPartnerQuestion
37
- ? 'bg-orange-100 text-orange-700 border border-orange-200'
38
- : 'bg-green-100 text-green-700 border border-green-200'}`, children: [_jsx(Mic, { className: "h-3 w-3" }), isPartnerQuestion ? 'Bạn thi (ví dụ)' : 'Học sinh trả lời'] })] }), _jsxs("div", { className: "p-3 space-y-3", children: [(questionData.partnerName || questionData.partnerAvatarUrl) && (_jsxs("div", { className: "flex items-center gap-2 rounded-lg bg-orange-50 border border-orange-100 px-4 py-2.5", children: [_jsx("span", { className: "text-xs font-medium text-orange-600", children: "Nh\u00E2n v\u1EADt b\u1EA1n thi:" }), _jsx(PartnerAvatar, { avatarUrl: questionData.partnerAvatarUrl, name: questionData.partnerName })] })), questionData.questionText && (_jsxs("div", { className: "rounded-lg bg-blue-50 border border-blue-200 px-4 py-3", children: [_jsx("p", { className: "text-xs font-medium text-blue-600 mb-1", children: "N\u1ED9i dung c\u00E2u h\u1ECFi:" }), _jsx("p", { className: "text-sm font-semibold text-gray-800", children: questionData.questionText })] })), _jsxs("div", { className: "space-y-3", children: [questionData.interlocutorAudioUrl && (_jsx(ConversationAudioPlayer, { audioUrl: questionData.interlocutorAudioUrl, label: "Audio ng\u01B0\u1EDDi h\u1ECFi (Interlocutor)" })), questionData.partnerAudioUrl && (_jsx(ConversationAudioPlayer, { audioUrl: questionData.partnerAudioUrl, label: "Audio b\u1EA1n thi (Partner)" }))] }), hasImages && (_jsxs("div", { children: [_jsx("p", { className: "text-xs font-medium text-gray-500 mb-2", children: "H\u00ECnh \u1EA3nh th\u1EA3o lu\u1EADn:" }), _jsx("div", { className: `grid ${gridCols} gap-3`, style: { maxWidth: '500px' }, children: questionData.images.map((img) => (_jsx(PreviewImageItem, { url: img.url, label: img.label }, img.id))) })] })), _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-indigo-50 ring-2 ring-indigo-100/50", children: _jsx(Mic, { className: "w-4 h-4 text-indigo-400" }) }), _jsx("p", { className: "text-xs text-gray-400", children: isPartnerQuestion
39
- ? 'Câu hỏi ví dụ — Bạn thi sẽ trả lời, học sinh lắng nghe'
40
- : 'Học sinh sẽ nhấn nút ghi âm và nói câu trả lời tại đây' })] }), isReviewMode && questionData.expectedAnswers && questionData.expectedAnswers.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: "C\u00E1c \u0111\u00E1p \u00E1n mong mu\u1ED1n" })] }), _jsx("div", { className: "space-y-2", children: questionData.expectedAnswers.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 })] }, index))) })] }))] })] })] }));
37
+ return (_jsxs("div", { className: "space-y-4", children: [questionData.isBackup && (_jsxs("div", { className: "inline-flex 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: "H\u1ED9i tho\u1EA1i Speaking (KFS)", icon: _jsx(MessageCircle, { className: "h-3.5 w-3.5" }), headerExtra: _jsxs("div", { className: `inline-flex items-center gap-1.5 rounded-full px-3 py-1 text-xs font-semibold ${isPartnerQuestion
38
+ ? 'bg-orange-100 text-orange-700 border border-orange-200'
39
+ : 'bg-green-100 text-green-700 border border-green-200'}`, children: [_jsx(Mic, { className: "h-3 w-3" }), isPartnerQuestion ? 'Bạn thi (ví dụ)' : 'Học sinh trả lời'] }), children: [(questionData.partnerName || questionData.partnerAvatarUrl) && (_jsxs("div", { className: "flex items-center gap-2 rounded-lg bg-orange-50 border border-orange-100 px-4 py-2.5", children: [_jsx("span", { className: "text-xs font-medium text-orange-600", children: "Nh\u00E2n v\u1EADt b\u1EA1n thi:" }), _jsx(PartnerAvatar, { avatarUrl: questionData.partnerAvatarUrl, name: questionData.partnerName })] })), questionData.questionText && (_jsx(KidPrompt, { children: questionData.questionText })), _jsxs("div", { className: "space-y-3", children: [questionData.interlocutorAudioUrl && (_jsx(ConversationAudioPlayer, { audioUrl: questionData.interlocutorAudioUrl, label: "Audio ng\u01B0\u1EDDi h\u1ECFi (Interlocutor)" })), questionData.partnerAudioUrl && (_jsx(ConversationAudioPlayer, { audioUrl: questionData.partnerAudioUrl, label: "Audio b\u1EA1n thi (Partner)" }))] }), hasImages && (_jsxs("div", { children: [_jsx("p", { className: "text-xs font-medium text-gray-500 mb-2", children: "H\u00ECnh \u1EA3nh th\u1EA3o lu\u1EADn:" }), _jsx("div", { className: `grid ${gridCols} gap-3`, style: { maxWidth: '500px' }, children: questionData.images.map((img) => (_jsx(PreviewImageItem, { url: img.url, label: img.label }, img.id))) })] })), _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-indigo-50 ring-2 ring-indigo-100/50", children: _jsx(Mic, { className: "w-4 h-4 text-indigo-400" }) }), _jsx("p", { className: "text-xs text-gray-400", children: isPartnerQuestion
40
+ ? 'Câu hỏi ví dụ — Bạn thi sẽ trả lời, học sinh lắng nghe'
41
+ : 'Học sinh sẽ nhấn nút ghi âm và nói câu trả lời tại đây' })] }), isReviewMode && questionData.expectedAnswers && questionData.expectedAnswers.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: "C\u00E1c \u0111\u00E1p \u00E1n mong mu\u1ED1n" })] }), _jsx("div", { className: "space-y-2", children: questionData.expectedAnswers.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 })] }, index))) })] }))] })] }));
41
42
  }
@@ -2,6 +2,7 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { Mic, CheckCircle2, Info, CreditCard } from 'lucide-react';
4
4
  import { RichTextHtml } from '../../../../components/shared/RichTextHtml';
5
+ import { KidQuestionCard } from '../../_shared/components/compact';
5
6
  function asRecord(value) {
6
7
  return value && typeof value === 'object' && !Array.isArray(value)
7
8
  ? value
@@ -23,5 +24,5 @@ export function mapQuestionToSpeakingCueCardData(question) {
23
24
  }
24
25
  export function SpeakingCueCardClient({ questionData, isReviewMode = false, }) {
25
26
  const answers = questionData.expectedAnswers || [];
26
- 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("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm", children: [_jsx("div", { className: "flex items-center justify-between border-b border-gray-100 bg-gray-50 px-3 py-2", children: _jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "flex h-7 w-7 items-center justify-center rounded-md bg-gray-100 text-gray-600", children: _jsx(CreditCard, { className: "h-4 w-4" }) }), _jsx("span", { className: "text-sm font-medium text-gray-700", children: "Th\u1EBB g\u1EE3i \u00FD (Cue Card)" })] }) }), _jsxs("div", { className: "p-3 space-y-3", 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" }) }) }), _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
28
  }
@@ -1,10 +1,11 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { Mic, CheckCircle2, Info, ImageIcon, FileQuestion } from 'lucide-react';
3
+ import { Mic, CheckCircle2, Info, ImageIcon } from 'lucide-react';
4
4
  import { usePresignedFileUrl } from '../../../../shared/lib/hooks';
5
+ import { KidPrompt, KidQuestionCard } from '../../_shared/components/compact';
5
6
  export function SpeakingDescribeImageClient({ questionData, isReviewMode = false, }) {
6
7
  const { previewUrl: imageUrl } = usePresignedFileUrl(questionData.imageUrl || '');
7
- 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("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm", children: [_jsx("div", { className: "flex items-center justify-between border-b border-gray-100 bg-gray-50 px-3 py-2", children: _jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "flex h-7 w-7 items-center justify-center rounded-md bg-gray-100 text-gray-600", children: _jsx(Mic, { className: "h-4 w-4" }) }), _jsx("span", { className: "text-sm font-medium text-gray-700", children: "M\u00F4 t\u1EA3 h\u00ECnh \u1EA3nh (Speaking)" })] }) }), _jsxs("div", { className: "p-3 space-y-3", children: [questionData.question && (_jsxs("div", { className: "flex gap-3", children: [_jsx(FileQuestion, { className: "h-5 w-5 text-indigo-500 shrink-0 mt-0.5" }), _jsx("div", { className: "text-base font-semibold text-gray-800", children: questionData.question })] })), imageUrl ? (_jsx("div", { className: "w-full max-w-3xl mx-auto rounded-xl overflow-hidden shadow-sm border border-gray-200 bg-gray-50 flex justify-center", children: _jsx("img", { src: imageUrl, alt: "Scene", className: "w-[80%] max-h-[400px] object-contain flex-shrink-0" }) })) : (_jsxs("div", { className: "flex flex-col items-center justify-center py-10 rounded-xl border border-dashed border-gray-300 bg-gray-50", children: [_jsx(ImageIcon, { className: "h-8 w-8 text-gray-400 mb-2" }), _jsx("span", { className: "text-sm text-gray-500", children: "Ch\u01B0a t\u1EA3i h\u00ECnh \u1EA3nh m\u00F4 t\u1EA3" })] })), _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-indigo-50 ring-2 ring-indigo-100/50", children: _jsx(Mic, { className: "w-4 h-4 text-indigo-400" }) }), _jsx("p", { className: "text-xs text-gray-400", children: isReviewMode && questionData.audioUrl
8
- ? "Audio ghi âm của học sinh (đã lưu)"
9
- : "Học sinh sẽ nhìn hình ảnh và nhấn nút micro để mô tả" })] }), isReviewMode && questionData.expectedAnswers && questionData.expectedAnswers.length > 0 && (_jsxs("div", { className: "rounded-xl border border-green-200 bg-green-50/80 p-4 mt-6", 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: "C\u00E1c \u0111\u00E1p \u00E1n t\u1EF1 \u0111\u1ED9ng (Tham chi\u1EBFu AI)" })] }), _jsx("div", { className: "space-y-2", children: questionData.expectedAnswers.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 })] }, index))) })] })), isReviewMode && questionData.explanation && (_jsxs("div", { className: "rounded-xl border border-amber-200 bg-amber-50 p-4 mt-2", 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 gi\u00E1o vi\u00EAn" })] }), _jsx("p", { className: "text-sm text-amber-700 leading-relaxed whitespace-pre-wrap", children: questionData.explanation })] }))] })] })] }));
8
+ 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: "M\u00F4 t\u1EA3 h\u00ECnh \u1EA3nh (Speaking)", icon: _jsx(Mic, { className: "h-3.5 w-3.5" }), children: [questionData.question && (_jsx(KidPrompt, { children: questionData.question })), imageUrl ? (_jsx("div", { className: "w-full max-w-3xl mx-auto rounded-xl overflow-hidden shadow-sm border border-gray-200 bg-gray-50 flex justify-center", children: _jsx("img", { src: imageUrl, alt: "Scene", className: "w-[80%] max-h-[400px] object-contain flex-shrink-0" }) })) : (_jsxs("div", { className: "flex flex-col items-center justify-center py-10 rounded-xl border border-dashed border-gray-300 bg-gray-50", children: [_jsx(ImageIcon, { className: "h-8 w-8 text-gray-400 mb-2" }), _jsx("span", { className: "text-sm text-gray-500", children: "Ch\u01B0a t\u1EA3i h\u00ECnh \u1EA3nh m\u00F4 t\u1EA3" })] })), _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-indigo-50 ring-2 ring-indigo-100/50", children: _jsx(Mic, { className: "w-4 h-4 text-indigo-400" }) }), _jsx("p", { className: "text-xs text-gray-400", children: isReviewMode && questionData.audioUrl
9
+ ? "Audio ghi âm của học sinh (đã lưu)"
10
+ : "Học sinh sẽ nhìn hình ảnh và nhấn nút micro để mô tả" })] }), isReviewMode && questionData.expectedAnswers && questionData.expectedAnswers.length > 0 && (_jsxs("div", { className: "rounded-xl border border-green-200 bg-green-50/80 p-4 mt-6", 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: "C\u00E1c \u0111\u00E1p \u00E1n t\u1EF1 \u0111\u1ED9ng (Tham chi\u1EBFu AI)" })] }), _jsx("div", { className: "space-y-2", children: questionData.expectedAnswers.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 })] }, index))) })] })), isReviewMode && questionData.explanation && (_jsxs("div", { className: "rounded-xl border border-amber-200 bg-amber-50 p-4 mt-2", 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 gi\u00E1o vi\u00EAn" })] }), _jsx("p", { className: "text-sm text-amber-700 leading-relaxed whitespace-pre-wrap", children: questionData.explanation })] }))] })] }));
10
11
  }
@@ -1,2 +1,2 @@
1
1
  import type { SpontaneousQaGroupClientProps } from '../../_shared/types/spontaneous-qa-group.type';
2
- export declare function SpontaneousQaGroupClient({ questionData, isReviewMode, }: SpontaneousQaGroupClientProps): import("react").JSX.Element;
2
+ export declare function SpontaneousQaGroupClient({ questionData, isReviewMode, userAnswer, onItemRecorded, uploadingIndex, gradeDetails, disabled, }: SpontaneousQaGroupClientProps): import("react").JSX.Element;
@@ -1,8 +1,9 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useEffect, useRef, useState } from 'react';
4
- import { CircleHelp, Lightbulb, Mic, Square, Volume2 } from 'lucide-react';
4
+ import { Lightbulb, Mic, Square, Volume2 } from 'lucide-react';
5
5
  import { usePresignedFileUrl } from '../../../../shared/lib/hooks';
6
+ import { KidInstruction, KidPrompt, KidQuestionCard } from '../../_shared/components/compact';
6
7
  function QuestionAudio({ url }) {
7
8
  const { previewUrl, isLoading } = usePresignedFileUrl(url);
8
9
  const src = previewUrl ||
@@ -12,7 +13,28 @@ function QuestionAudio({ url }) {
12
13
  }
13
14
  return (_jsxs("div", { className: "flex items-center gap-3 rounded-lg border border-blue-100 bg-blue-50/60 px-3 py-2", children: [_jsx(Volume2, { className: "h-4 w-4 shrink-0 text-blue-500" }), isLoading ? (_jsx("div", { className: "h-8 flex-1 animate-pulse rounded-md bg-blue-100" })) : (_jsx("audio", { controls: true, src: src, className: "h-8 w-full", preload: "metadata", children: "Tr\u00ECnh duy\u1EC7t kh\u00F4ng h\u1ED7 tr\u1EE3 audio." }))] }));
14
15
  }
15
- function SpeakingReplyMic() {
16
+ function readGradeUser(detail) {
17
+ if (!detail?.userAnswer || typeof detail.userAnswer !== 'object' || Array.isArray(detail.userAnswer)) {
18
+ return { feedback: detail?.feedback };
19
+ }
20
+ const raw = detail.userAnswer;
21
+ return {
22
+ transcript: typeof raw.transcript === 'string' ? raw.transcript : undefined,
23
+ contentScore: typeof raw.contentScore === 'number' ? raw.contentScore : undefined,
24
+ pronunciationScore: typeof raw.pronunciationScore === 'number' ? raw.pronunciationScore : undefined,
25
+ fluencyScore: typeof raw.fluencyScore === 'number' ? raw.fluencyScore : undefined,
26
+ feedback: typeof raw.feedback === 'string' ? raw.feedback : detail.feedback,
27
+ };
28
+ }
29
+ function findGradeDetail(details, flattenIndex) {
30
+ if (!details?.length) {
31
+ return undefined;
32
+ }
33
+ return details.find((item) => (item.itemId === String(flattenIndex) ||
34
+ item.itemId === `item-${flattenIndex}` ||
35
+ item.itemId === `item-${flattenIndex + 1}`));
36
+ }
37
+ function SpeakingReplyMic({ flattenIndex, audioKey, disabled, uploading, onRecorded, }) {
16
38
  const [status, setStatus] = useState('idle');
17
39
  const [error, setError] = useState('');
18
40
  const [blobUrl, setBlobUrl] = useState('');
@@ -33,6 +55,9 @@ function SpeakingReplyMic() {
33
55
  streamRef.current = null;
34
56
  };
35
57
  const startRecording = async () => {
58
+ if (disabled || uploading || !onRecorded) {
59
+ return;
60
+ }
36
61
  setError('');
37
62
  try {
38
63
  const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
@@ -55,6 +80,7 @@ function SpeakingReplyMic() {
55
80
  });
56
81
  setStatus('recorded');
57
82
  stopStream();
83
+ onRecorded(flattenIndex, blob);
58
84
  };
59
85
  recorder.start();
60
86
  setStatus('recording');
@@ -69,17 +95,34 @@ function SpeakingReplyMic() {
69
95
  recorderRef.current.stop();
70
96
  }
71
97
  };
72
- return (_jsxs("div", { className: "flex flex-col items-center gap-2 rounded-lg border border-dashed border-indigo-200 bg-indigo-50/60 px-4 py-3", children: [_jsx("button", { type: "button", onClick: status === 'recording' ? stopRecording : startRecording, title: status === 'recording' ? 'Dừng ghi âm' : 'Nhấn micro để trả lời', className: `inline-flex h-12 w-12 items-center justify-center rounded-full text-white shadow-sm transition-colors ${status === 'recording'
98
+ const canRecord = Boolean(onRecorded) && !disabled && !uploading;
99
+ return (_jsxs("div", { className: "flex flex-col items-center gap-2 rounded-lg border border-dashed border-indigo-200 bg-indigo-50/60 px-4 py-3", children: [canRecord ? (_jsx("button", { type: "button", onClick: status === 'recording' ? stopRecording : startRecording, title: status === 'recording' ? 'Dừng ghi âm' : 'Nhấn micro để trả lời', className: `inline-flex h-12 w-12 items-center justify-center rounded-full text-white shadow-sm transition-colors ${status === 'recording'
73
100
  ? 'animate-pulse bg-red-500 hover:bg-red-600'
74
- : 'bg-indigo-600 hover:bg-indigo-700'}`, children: status === 'recording' ? _jsx(Square, { className: "h-4 w-4 fill-current" }) : _jsx(Mic, { className: "h-5 w-5" }) }), _jsx("p", { className: "text-center text-xs text-indigo-700", children: status === 'recording'
75
- ? 'Đang ghi âm... Nhấn để dừng'
76
- : status === 'recorded'
77
- ? 'Đã ghi. Nhấn micro để ghi lại'
78
- : 'Nhấn micro để trả lời bằng giọng nói' }), error && _jsx("p", { className: "text-center text-xs text-red-600", children: error }), status === 'recorded' && blobUrl && (_jsx("audio", { controls: true, src: blobUrl, className: "h-8 w-full max-w-sm", preload: "metadata", children: "Tr\u00ECnh duy\u1EC7t kh\u00F4ng h\u1ED7 tr\u1EE3 audio." }))] }));
101
+ : 'bg-indigo-600 hover:bg-indigo-700'}`, children: status === 'recording' ? _jsx(Square, { className: "h-4 w-4 fill-current" }) : _jsx(Mic, { className: "h-5 w-5" }) })) : (_jsx("div", { className: "inline-flex h-12 w-12 items-center justify-center rounded-full bg-slate-300 text-white", children: _jsx(Mic, { className: "h-5 w-5" }) })), _jsx("p", { className: "text-center text-xs text-indigo-700", children: uploading
102
+ ? 'Đang tải lên bản ghi...'
103
+ : status === 'recording'
104
+ ? 'Đang ghi âm... Nhấn để dừng'
105
+ : audioKey || status === 'recorded'
106
+ ? 'Đã ghi. Nhấn micro để ghi lại'
107
+ : 'Nhấn micro để trả lời bằng giọng nói' }), error && _jsx("p", { className: "text-center text-xs text-red-600", children: error }), audioKey ? (_jsx("div", { className: "w-full max-w-sm", children: _jsx(QuestionAudio, { url: audioKey }) })) : status === 'recorded' && blobUrl ? (_jsx("audio", { controls: true, src: blobUrl, className: "h-8 w-full max-w-sm", preload: "metadata", children: "Tr\u00ECnh duy\u1EC7t kh\u00F4ng h\u1ED7 tr\u1EE3 audio." })) : null] }));
79
108
  }
80
- export function SpontaneousQaGroupClient({ questionData, isReviewMode = false, }) {
109
+ export function SpontaneousQaGroupClient({ questionData, isReviewMode = false, userAnswer, onItemRecorded, uploadingIndex = null, gradeDetails, disabled = false, }) {
81
110
  const parts = questionData.parts || [];
82
- return (_jsxs("div", { className: "space-y-4", children: [questionData.instruction && (_jsx("div", { className: "rounded-xl border border-indigo-100 bg-indigo-50 px-4 py-3 text-sm font-medium text-indigo-800", children: questionData.instruction })), parts.map((part, partIndex) => (_jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm", children: [_jsxs("div", { className: "border-b border-gray-100 bg-gradient-to-r from-slate-50 to-white px-3 py-2", children: [_jsx("p", { className: "text-sm font-semibold text-slate-800", children: part.partTitle || `PART ${part.partNumber || partIndex + 1}` }), part.topic && (_jsx("p", { className: "mt-1 text-sm text-slate-600", children: part.topic }))] }), _jsx("div", { className: "space-y-3 p-3", children: part.questions.map((question, questionIndex) => (_jsxs("div", { className: "rounded-lg border border-gray-200 bg-white p-3", children: [_jsxs("div", { className: "mb-2 flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-7 w-7 items-center justify-center rounded-lg bg-blue-100 text-blue-700", children: _jsx(CircleHelp, { className: "h-3.5 w-3.5" }) }), _jsxs("span", { className: "text-sm font-medium text-gray-700", children: ["C\u00E2u ", question.questionNumber || questionIndex + 1] })] }), _jsxs("span", { className: "text-xs text-gray-500", children: [question.points || 0, " \u0111i\u1EC3m"] })] }), _jsx("p", { className: "text-sm font-semibold text-gray-800", children: question.questionText || 'Câu hỏi chưa được nhập' }), question.audioUrl ? _jsx("div", { className: "mt-2", children: _jsx(QuestionAudio, { url: question.audioUrl }) }) : null, _jsx("div", { className: "mt-3", children: _jsx(SpeakingReplyMic, {}) }), isReviewMode && question.expectedAnswers.filter((item) => item.trim() !== '').length > 0 && (_jsxs("div", { className: "mt-2 rounded-lg border border-amber-100 bg-amber-50 px-3 py-2", children: [_jsxs("div", { className: "mb-1 flex items-center gap-1 text-xs font-semibold text-amber-700", children: [_jsx(Lightbulb, { className: "h-3 w-3" }), "\u0110\u00E1p \u00E1n m\u1EABu"] }), _jsx("ul", { className: "list-disc space-y-1 pl-4 text-xs text-amber-800", children: question.expectedAnswers
111
+ const audioKeys = Array.isArray(userAnswer?.audioKeys) ? userAnswer.audioKeys : [];
112
+ let flattenIndex = 0;
113
+ return (_jsxs("div", { className: "space-y-4", children: [_jsx(KidInstruction, { children: questionData.instruction }), parts.map((part, partIndex) => (_jsxs(KidQuestionCard, { icon: _jsx(Mic, { className: "h-3.5 w-3.5" }), title: part.partTitle || `PART ${part.partNumber || partIndex + 1}`, children: [part.topic ? _jsx(KidPrompt, { children: part.topic }) : null, part.questions.map((question, questionIndex) => {
114
+ const currentIndex = flattenIndex;
115
+ flattenIndex += 1;
116
+ const audioKey = audioKeys[currentIndex];
117
+ const detail = findGradeDetail(gradeDetails, currentIndex);
118
+ const grade = readGradeUser(detail);
119
+ const showMic = !isReviewMode && !question.isExample;
120
+ return (_jsxs(KidQuestionCard, { title: `Câu ${question.questionNumber || questionIndex + 1}`, points: detail?.partialScore != null ? undefined : (question.points || 0), isExample: question.isExample, headerExtra: detail?.partialScore != null ? (_jsxs("span", { className: "text-xs text-gray-500", children: [detail.partialScore, " / ", question.points || 0, " \u0111i\u1EC3m"] })) : undefined, children: [_jsx(KidPrompt, { children: question.questionText || 'Câu hỏi chưa được nhập' }), question.audioUrl ? _jsx(QuestionAudio, { url: question.audioUrl }) : null, showMic && (_jsx(SpeakingReplyMic, { flattenIndex: currentIndex, audioKey: typeof audioKey === 'string' ? audioKey : null, disabled: disabled, uploading: uploadingIndex === currentIndex, onRecorded: onItemRecorded })), detail && !question.isExample && (_jsxs("div", { className: "space-y-1 rounded-lg border border-slate-200 bg-slate-50 px-3 py-2 text-xs text-slate-700", children: [grade.transcript && (_jsxs("p", { children: [_jsx("span", { className: "font-semibold", children: "Transcript:" }), " ", grade.transcript] })), (grade.contentScore != null || grade.pronunciationScore != null || grade.fluencyScore != null) && (_jsx("p", { children: [
121
+ grade.contentScore != null ? `Content ${Math.round(grade.contentScore)}` : null,
122
+ grade.pronunciationScore != null ? `Pronunciation ${Math.round(grade.pronunciationScore)}` : null,
123
+ grade.fluencyScore != null ? `Fluency ${Math.round(grade.fluencyScore)}` : null,
124
+ ].filter(Boolean).join(' · ') })), grade.feedback && _jsx("p", { children: grade.feedback })] })), isReviewMode && question.expectedAnswers.filter((item) => item.trim() !== '').length > 0 && (_jsxs("div", { className: "rounded-lg border border-amber-100 bg-amber-50 px-3 py-2", children: [_jsxs("div", { className: "mb-1 flex items-center gap-1 text-xs font-semibold text-amber-700", children: [_jsx(Lightbulb, { className: "h-3 w-3" }), "\u0110\u00E1p \u00E1n m\u1EABu"] }), _jsx("ul", { className: "list-disc space-y-1 pl-4 text-xs text-amber-800", children: question.expectedAnswers
83
125
  .filter((item) => item.trim() !== '')
84
- .map((item) => (_jsx("li", { children: item }, item))) })] }))] }, `${part.partNumber}-${question.questionNumber}-${questionIndex}`))) })] }, `part-${part.partNumber}-${partIndex}`)))] }));
126
+ .map((item) => (_jsx("li", { children: item }, item))) })] }))] }, `${part.partNumber}-${question.questionNumber}-${questionIndex}`));
127
+ })] }, `part-${part.partNumber}-${partIndex}`)))] }));
85
128
  }
@@ -6,11 +6,12 @@ import { Label } from '../../../../components/ui/label';
6
6
  import { Input } from '../../../../components/ui/input';
7
7
  import { Textarea } from '../../../../components/ui/textarea';
8
8
  import { PointsInput } from '../../../../components/ui/points-input';
9
+ import { Switch } from '../../../../components/ui/switch';
9
10
  import { CompactCreatorHeader, CompactExplanationToggle, CompactFieldCard, CompactPreviewBanner, } from '../../_shared/components/compact';
10
11
  import { FileUpload } from '../../../../components/ui/file-upload';
11
12
  import { useDebouncedCallback } from '../../../../shared/lib/hooks';
12
13
  import { toast } from 'sonner';
13
- import { Layers, Mic, Plus, Trash2, Type, Volume2 } from 'lucide-react';
14
+ import { Layers, Mic, Plus, Star, Trash2, Type, Volume2 } from 'lucide-react';
14
15
  import { SpontaneousQaGroupClient } from './SpontaneousQaGroupClient';
15
16
  import { createEmptyPart, createEmptyQuestion, normalizeParts, sumPartPoints, } from './creator-helpers';
16
17
  export function SpontaneousQaGroupCreator({ initialData, onChange, externalErrors, onUnsavedChangesChange, validationRef, }) {
@@ -172,10 +173,13 @@ export function SpontaneousQaGroupCreator({ initialData, onChange, externalError
172
173
  onClick: () => setIsClientMode(true),
173
174
  } }), children: [_jsxs("div", { className: "space-y-1.5", children: [_jsx(Label, { htmlFor: "group-instruction", className: "text-sm font-semibold text-gray-800", children: "H\u01B0\u1EDBng d\u1EABn" }), _jsx(Input, { id: "group-instruction", value: instruction, onChange: (event) => setInstruction(event.target.value), placeholder: "In pairs, ask and answer the questions.", className: "h-10 border-gray-200 bg-white" })] }), _jsx("p", { className: "text-xs text-gray-500", children: "T\u1ED5ng \u0111i\u1EC3m \u0111\u01B0\u1EE3c c\u1ED9ng t\u1EEB \u0111i\u1EC3m t\u1EEBng c\u00E2u trong t\u1EA5t c\u1EA3 part." })] }), parts.map((part, partIndex) => (_jsxs(CompactFieldCard, { header: _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("p", { className: "text-sm font-semibold text-gray-800", children: ["Part ", partIndex + 1] }), _jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => removePart(partIndex), disabled: parts.length <= 1, children: _jsx(Trash2, { className: "h-4 w-4" }) })] }), children: [_jsxs("div", { className: "grid gap-2.5 md:grid-cols-2", children: [_jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Ti\u00EAu \u0111\u1EC1 part" }), _jsx(Input, { value: part.partTitle, onChange: (event) => updatePart(partIndex, { partTitle: event.target.value }), placeholder: "PART 1" })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Ch\u1EE7 \u0111\u1EC1" }), _jsx(Input, { value: part.topic, onChange: (event) => updatePart(partIndex, { topic: event.target.value }), placeholder: "Talk about school." })] })] }), part.questions.map((question, questionIndex) => (_jsxs("div", { className: "space-y-2.5 rounded-md border border-slate-200 px-3 py-2.5", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("p", { className: "text-sm font-semibold text-slate-700", children: ["C\u00E2u ", questionIndex + 1] }), _jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => removeQuestion(partIndex, questionIndex), disabled: part.questions.length <= 1, children: _jsx(Trash2, { className: "h-4 w-4" }) })] }), _jsxs("div", { className: "flex w-fit rounded-lg border border-slate-200 bg-slate-100 p-1", children: [_jsxs("button", { type: "button", onClick: () => updateQuestion(partIndex, questionIndex, { inputType: 'AUDIO' }), className: `flex items-center gap-2 rounded-md px-3 py-1.5 text-xs font-medium ${question.inputType === 'AUDIO' ? 'bg-white text-blue-700 shadow-sm' : 'text-slate-500'}`, children: [_jsx(Mic, { className: "h-3.5 w-3.5" }), " Audio"] }), _jsxs("button", { type: "button", onClick: () => updateQuestion(partIndex, questionIndex, { inputType: 'TEXT' }), className: `flex items-center gap-2 rounded-md px-3 py-1.5 text-xs font-medium ${question.inputType === 'TEXT' ? 'bg-white text-emerald-700 shadow-sm' : 'text-slate-500'}`, children: [_jsx(Type, { className: "h-3.5 w-3.5" }), " Text (TTS)"] })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "C\u00E2u h\u1ECFi" }), _jsx(Textarea, { value: question.questionText, onChange: (event) => updateQuestion(partIndex, questionIndex, {
174
175
  questionText: event.target.value,
175
- }), rows: 2, placeholder: "Do you like your timetable at school? Why?/Why not?" })] }), question.inputType === 'AUDIO' ? (_jsx(FileUpload, { id: `audio-${partIndex}-${questionIndex}`, label: "File audio c\u00E2u h\u1ECFi", accept: "audio/*", value: question.audioUrl, onChange: (url) => updateQuestion(partIndex, questionIndex, { audioUrl: url }), maxSize: 20, placeholder: "T\u1EA3i l\u00EAn file audio (MP3, WAV...)", autoUpload: true, prefix: "speaking/spontaneous-qa-group" })) : (_jsxs(Button, { type: "button", variant: "outline", size: "sm", disabled: !question.questionText.trim(), onClick: handleGenerateAudio, children: [_jsx(Volume2, { className: "mr-2 h-3.5 w-3.5" }), "T\u1EA1o Audio"] })), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "\u0110\u00E1p \u00E1n m\u1EABu" }), question.expectedAnswers.map((answer, answerIndex) => (_jsx(Input, { value: answer, onChange: (event) => updateExpectedAnswer(partIndex, questionIndex, answerIndex, event.target.value), placeholder: "\u0110\u00E1p \u00E1n m\u1EABu cho AI ch\u1EA5m" }, `ans-${partIndex}-${questionIndex}-${answerIndex}`))), _jsxs(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => addExpectedAnswer(partIndex, questionIndex), children: [_jsx(Plus, { className: "mr-2 h-4 w-4" }), "Th\u00EAm \u0111\u00E1p \u00E1n m\u1EABu"] })] }), _jsx(PointsInput, { value: question.points, allowZero: true, onChange: (event) => {
176
- const parsed = Number.parseInt(event.target.value.replace(/[^0-9]/g, ''), 10);
177
- updateQuestion(partIndex, questionIndex, {
178
- points: Number.isNaN(parsed) ? 0 : Math.max(0, parsed),
179
- });
180
- } })] }, `q-${partIndex}-${questionIndex}`))), _jsxs(Button, { type: "button", variant: "outline", onClick: () => addQuestion(partIndex), children: [_jsx(Plus, { className: "mr-2 h-4 w-4" }), "Th\u00EAm c\u00E2u h\u1ECFi"] })] }, `part-${partIndex}`))), _jsxs(Button, { type: "button", variant: "outline", onClick: addPart, children: [_jsx(Plus, { className: "mr-2 h-4 w-4" }), "Th\u00EAm part"] }), _jsx(CompactExplanationToggle, { value: explanation, onValueChange: setExplanation, defaultVisible: Boolean(initialData?.explanation) }), allErrors.length > 0 && (_jsx("div", { className: "rounded-md border border-red-200 bg-red-50 p-2 text-xs text-red-700", children: allErrors.map((error) => (_jsx("p", { children: error }, error))) }))] }));
176
+ }), rows: 2, placeholder: "Do you like your timetable at school? Why?/Why not?" })] }), question.inputType === 'AUDIO' ? (_jsx(FileUpload, { id: `audio-${partIndex}-${questionIndex}`, label: "File audio c\u00E2u h\u1ECFi", accept: "audio/*", value: question.audioUrl, onChange: (url) => updateQuestion(partIndex, questionIndex, { audioUrl: url }), maxSize: 20, placeholder: "T\u1EA3i l\u00EAn file audio (MP3, WAV...)", autoUpload: true, prefix: "speaking/spontaneous-qa-group" })) : (_jsxs(Button, { type: "button", variant: "outline", size: "sm", disabled: !question.questionText.trim(), onClick: handleGenerateAudio, children: [_jsx(Volume2, { className: "mr-2 h-3.5 w-3.5" }), "T\u1EA1o Audio"] })), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "\u0110\u00E1p \u00E1n m\u1EABu" }), question.expectedAnswers.map((answer, answerIndex) => (_jsx(Input, { value: answer, onChange: (event) => updateExpectedAnswer(partIndex, questionIndex, answerIndex, event.target.value), placeholder: "\u0110\u00E1p \u00E1n m\u1EABu cho AI ch\u1EA5m" }, `ans-${partIndex}-${questionIndex}-${answerIndex}`))), _jsxs(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => addExpectedAnswer(partIndex, questionIndex), children: [_jsx(Plus, { className: "mr-2 h-4 w-4" }), "Th\u00EAm \u0111\u00E1p \u00E1n m\u1EABu"] })] }), _jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Switch, { checked: question.isExample === true, onCheckedChange: (checked) => updateQuestion(partIndex, questionIndex, {
177
+ isExample: checked,
178
+ points: checked ? 0 : (question.points || 1),
179
+ }) }), _jsxs(Label, { className: "flex cursor-pointer items-center gap-1 text-xs", children: [_jsx(Star, { className: "h-3 w-3 text-amber-500" }), "C\u00E2u v\u00ED d\u1EE5"] })] }), !question.isExample && (_jsx(PointsInput, { value: question.points, allowZero: true, onChange: (event) => {
180
+ const parsed = Number.parseInt(event.target.value.replace(/[^0-9]/g, ''), 10);
181
+ updateQuestion(partIndex, questionIndex, {
182
+ points: Number.isNaN(parsed) ? 0 : Math.max(0, parsed),
183
+ });
184
+ } }))] })] }, `q-${partIndex}-${questionIndex}`))), _jsxs(Button, { type: "button", variant: "outline", onClick: () => addQuestion(partIndex), children: [_jsx(Plus, { className: "mr-2 h-4 w-4" }), "Th\u00EAm c\u00E2u h\u1ECFi"] })] }, `part-${partIndex}`))), _jsxs(Button, { type: "button", variant: "outline", onClick: addPart, children: [_jsx(Plus, { className: "mr-2 h-4 w-4" }), "Th\u00EAm part"] }), _jsx(CompactExplanationToggle, { value: explanation, onValueChange: setExplanation, defaultVisible: Boolean(initialData?.explanation) }), allErrors.length > 0 && (_jsx("div", { className: "rounded-md border border-red-200 bg-red-50 p-2 text-xs text-red-700", children: allErrors.map((error) => (_jsx("p", { children: error }, error))) }))] }));
181
185
  }
@@ -6,6 +6,7 @@ export function createEmptyQuestion(questionNumber) {
6
6
  expectedAnswers: [''],
7
7
  points: 1,
8
8
  questionNumber,
9
+ isExample: false,
9
10
  };
10
11
  }
11
12
  export function createEmptyPart(partNumber) {
@@ -32,12 +33,17 @@ export function normalizeParts(rawParts) {
32
33
  expectedAnswers: Array.isArray(question.expectedAnswers) && question.expectedAnswers.length > 0
33
34
  ? question.expectedAnswers
34
35
  : [''],
35
- points: typeof question.points === 'number' ? question.points : 1,
36
+ isExample: question.isExample === true,
37
+ points: question.isExample === true
38
+ ? 0
39
+ : typeof question.points === 'number' ? question.points : 1,
36
40
  questionNumber: question.questionNumber || questionIndex + 1,
37
41
  }))
38
42
  : [createEmptyQuestion(1)],
39
43
  }));
40
44
  }
41
45
  export function sumPartPoints(parts) {
42
- return parts.reduce((total, part) => (total + part.questions.reduce((partTotal, question) => (partTotal + (typeof question.points === 'number' ? question.points : 1)), 0)), 0);
46
+ return parts.reduce((total, part) => (total + part.questions.reduce((partTotal, question) => (partTotal + (question.isExample
47
+ ? 0
48
+ : typeof question.points === 'number' ? question.points : 1)), 0)), 0);
43
49
  }
@@ -10,6 +10,7 @@ export interface SpontaneousQaGroupFlatChild {
10
10
  inputType: 'TEXT' | 'AUDIO';
11
11
  expectedAnswers: string[];
12
12
  points: number;
13
+ isExample: boolean;
13
14
  }
14
15
  export declare function flattenSpontaneousQaGroupContent(content: unknown): SpontaneousQaGroupFlatChild[];
15
16
  /** Speaking Bank / translate lists — no teacher video or one-at-a-time mic. */
@@ -42,7 +42,8 @@ export function flattenSpontaneousQaGroupContent(content) {
42
42
  audioUrl: asString(childContent.audioUrl),
43
43
  inputType: asString(childContent.inputType) === 'AUDIO' ? 'AUDIO' : 'TEXT',
44
44
  expectedAnswers: normalizeExpectedAnswers(asRecord(child.correctAnswer).expectedAnswers),
45
- points: asNumber(child.points, 1),
45
+ isExample: child.isExample === true,
46
+ points: child.isExample === true ? 0 : asNumber(child.points, 1),
46
47
  });
47
48
  });
48
49
  });
@@ -62,7 +63,8 @@ export function flattenSpontaneousQaGroupContent(content) {
62
63
  audioUrl: asString(childContent.audioUrl),
63
64
  inputType: asString(childContent.inputType) === 'AUDIO' ? 'AUDIO' : 'TEXT',
64
65
  expectedAnswers: normalizeExpectedAnswers(asRecord(child.correctAnswer).expectedAnswers),
65
- points: asNumber(child.points, 1),
66
+ isExample: child.isExample === true,
67
+ points: child.isExample === true ? 0 : asNumber(child.points, 1),
66
68
  });
67
69
  });
68
70
  return flattened;
@@ -23,6 +23,7 @@ function mapQuestion(item, index) {
23
23
  const record = asRecord(item);
24
24
  const content = asRecord(record.content);
25
25
  const correctAnswer = asRecord(record.correctAnswer);
26
+ const isExample = record.isExample === true;
26
27
  return {
27
28
  questionText: asString(content.questionText) ||
28
29
  asString(content.question) ||
@@ -30,7 +31,8 @@ function mapQuestion(item, index) {
30
31
  inputType: asString(content.inputType) === 'AUDIO' ? 'AUDIO' : 'TEXT',
31
32
  audioUrl: asString(content.audioUrl),
32
33
  expectedAnswers: normalizeExpectedAnswers(correctAnswer.expectedAnswers),
33
- points: asNumber(record.points, 1),
34
+ isExample,
35
+ points: isExample ? 0 : asNumber(record.points, 1),
34
36
  questionNumber: asNumber(record.questionNumber, index + 1),
35
37
  };
36
38
  }
@@ -66,9 +68,9 @@ export function mapQuestionToSpontaneousQaGroupData(question) {
66
68
  return {
67
69
  instruction: asString(asRecord(list.meta).instruction) ||
68
70
  asString(asRecord(content.meta).instruction) ||
69
- 'Talk about the topic.',
71
+ 'In pairs, ask and answer the questions.',
70
72
  parts,
71
73
  explanation: asString(question.explanation),
72
- points: parts.reduce((total, part) => (total + part.questions.reduce((partTotal, item) => partTotal + (item.points || 0), 0)), 0),
74
+ points: parts.reduce((total, part) => (total + part.questions.reduce((partTotal, item) => (partTotal + (item.isExample ? 0 : (item.points || 0))), 0)), 0),
73
75
  };
74
76
  }
@@ -28,6 +28,7 @@ function createEmptyQuestion(questionNumber) {
28
28
  expectedAnswers: [''],
29
29
  points: 1,
30
30
  questionNumber,
31
+ isExample: false,
31
32
  };
32
33
  }
33
34
  function createEmptyPart(partNumber) {
@@ -43,12 +44,14 @@ function mapQuestion(item, index, fallbackAnswers) {
43
44
  const content = asRecord(record.content);
44
45
  const correctAnswer = asRecord(record.correctAnswer);
45
46
  const expected = normalizeExpectedAnswers(correctAnswer.expectedAnswers ?? fallbackAnswers);
47
+ const isExample = record.isExample === true;
46
48
  return {
47
49
  questionText: asString(content.questionText) || asString(record.questionText),
48
50
  inputType: asString(content.inputType) === 'AUDIO' ? 'AUDIO' : 'TEXT',
49
51
  audioUrl: asString(content.audioUrl),
50
52
  expectedAnswers: expected,
51
- points: asNumber(record.points, 1),
53
+ isExample,
54
+ points: isExample ? 0 : asNumber(record.points, 1),
52
55
  questionNumber: asNumber(record.questionNumber, index + 1),
53
56
  };
54
57
  }
@@ -65,7 +68,7 @@ function mapPart(item, index) {
65
68
  };
66
69
  }
67
70
  function sumPoints(parts) {
68
- return parts.reduce((total, part) => (total + part.questions.reduce((partTotal, question) => partTotal + (question.points || 0), 0)), 0);
71
+ return parts.reduce((total, part) => (total + part.questions.reduce((partTotal, question) => (partTotal + (question.isExample ? 0 : (question.points || 0))), 0)), 0);
69
72
  }
70
73
  export const spontaneousQaGroupRegistration = {
71
74
  component: SpontaneousQaGroupCreator,
@@ -9,14 +9,18 @@ function normalizeExpectedAnswers(value) {
9
9
  .filter((item) => item !== '');
10
10
  }
11
11
  function sumPoints(parts) {
12
- return parts.reduce((total, part) => (total + part.questions.reduce((partTotal, question) => (partTotal + (typeof question.points === 'number' ? question.points : 1)), 0)), 0);
12
+ return parts.reduce((total, part) => (total + part.questions.reduce((partTotal, question) => (partTotal + (question.isExample
13
+ ? 0
14
+ : typeof question.points === 'number' ? question.points : 1)), 0)), 0);
13
15
  }
14
16
  function toApiQuestion(question, index) {
15
17
  const inputType = question.inputType === 'AUDIO' ? 'AUDIO' : 'TEXT';
18
+ const isExample = question.isExample === true;
16
19
  return {
17
20
  questionType: CHILD_QUESTION_TYPE,
18
21
  questionNumber: question.questionNumber || index + 1,
19
- points: typeof question.points === 'number' ? question.points : 1,
22
+ points: isExample ? 0 : typeof question.points === 'number' ? question.points : 1,
23
+ ...(isExample ? { isExample: true } : {}),
20
24
  content: {
21
25
  inputType,
22
26
  questionText: question.questionText || '',
@@ -1,20 +1,17 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useState, useEffect } from 'react';
4
- import { Check, X, CircleHelp } from 'lucide-react';
4
+ import { Lightbulb } from 'lucide-react';
5
5
  import { usePresignedFileUrl } from '../../../../shared/lib/hooks';
6
- import { cn } from '../../../../shared/lib/utils';
6
+ import { KidChoiceRow, KidPrompt, KidQuestionCard } from '../../_shared/components/compact';
7
7
  export function TrueFalseClient({ questionData, onSubmit, isReviewMode = false, userAnswer, }) {
8
8
  const [selectedAnswer, setSelectedAnswer] = useState(userAnswer !== undefined ? userAnswer : undefined);
9
- // Presigned URL for image
10
9
  const { previewUrl: imagePreviewUrl, isLoading } = usePresignedFileUrl(questionData.imageUrl || '');
11
- // Sync userAnswer
12
10
  useEffect(() => {
13
11
  if (userAnswer !== undefined) {
14
12
  setSelectedAnswer(userAnswer);
15
13
  }
16
14
  }, [userAnswer]);
17
- // Handle answer change
18
15
  const handleAnswerChange = (value) => {
19
16
  if (isReviewMode)
20
17
  return;
@@ -23,53 +20,7 @@ export function TrueFalseClient({ questionData, onSubmit, isReviewMode = false,
23
20
  };
24
21
  const hasAnswered = selectedAnswer !== undefined;
25
22
  const isAnswerCorrect = isReviewMode && selectedAnswer === questionData.answer;
26
- return (_jsx("div", { className: "space-y-4", children: _jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-gray-100 bg-gradient-to-r from-gray-50 to-white px-3 py-2", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "flex h-8 w-8 items-center justify-center rounded-lg bg-gradient-to-br from-emerald-500 to-teal-600 text-white shadow-sm", children: _jsx(CircleHelp, { className: "h-4 w-4" }) }), _jsx("span", { className: "text-sm font-medium text-gray-700", children: "\u0110\u00FAng / Sai" })] }), isReviewMode && hasAnswered && (_jsxs("div", { className: cn('flex items-center gap-1.5 rounded-full px-3 py-1 text-xs font-semibold', isAnswerCorrect
27
- ? 'bg-green-100 text-green-700'
28
- : 'bg-red-100 text-red-700'), children: [isAnswerCorrect ? (_jsx(Check, { className: "h-3.5 w-3.5" })) : (_jsx(X, { className: "h-3.5 w-3.5" })), isAnswerCorrect ? 'Đúng' : 'Sai'] }))] }), _jsxs("div", { className: "space-y-3 p-3", children: [_jsx("div", { className: "rounded-lg border border-blue-100 bg-gradient-to-r from-blue-50 to-indigo-50 p-4", children: _jsx("h3", { className: "text-base font-semibold leading-relaxed text-gray-800", children: questionData.question || 'Chưa nhập câu hỏi' }) }), (imagePreviewUrl || questionData.imageUrl) && (_jsxs("div", { className: "overflow-hidden rounded-lg border border-gray-200 bg-gradient-to-br from-slate-50 to-gray-100", children: [_jsx("div", { className: "border-b border-gray-100 px-3 py-1.5", children: _jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx("div", { className: "h-2 w-2 rounded-full bg-red-400" }), _jsx("div", { className: "h-2 w-2 rounded-full bg-yellow-400" }), _jsx("div", { className: "h-2 w-2 rounded-full bg-green-400" }), _jsx("span", { className: "ml-2 text-xs font-medium text-gray-500", children: "H\u00ECnh \u1EA3nh" })] }) }), _jsx("div", { className: "flex items-center justify-center p-4", children: isLoading ? (_jsx("div", { className: "flex h-48 items-center justify-center", children: _jsx("div", { className: "h-8 w-8 animate-spin rounded-full border-4 border-gray-200 border-t-blue-500" }) })) : (
29
- /* eslint-disable-next-line @next/next/no-img-element */
30
- _jsx("img", { src: imagePreviewUrl || questionData.imageUrl, alt: "Question illustration", className: "max-h-56 w-auto rounded-md object-contain" })) })] })), _jsxs("div", { className: "space-y-2.5", children: [_jsx("span", { className: "text-xs font-medium uppercase tracking-wider text-gray-500", children: "Ch\u1ECDn \u0111\u00E1p \u00E1n" }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("button", { type: "button", onClick: () => handleAnswerChange(true), disabled: isReviewMode, className: cn('group flex items-center justify-center gap-3 rounded-xl border-2 p-4 transition-all', isReviewMode
31
- ? questionData.answer === true
32
- ? 'border-green-400 bg-green-50'
33
- : selectedAnswer === true
34
- ? 'border-red-400 bg-red-50'
35
- : 'border-gray-200 bg-gray-50'
36
- : selectedAnswer === true
37
- ? 'border-emerald-500 bg-emerald-50 shadow-sm ring-2 ring-emerald-100'
38
- : 'border-gray-200 bg-white hover:border-emerald-300 hover:bg-emerald-50/50 hover:shadow-sm'), children: [_jsx("div", { className: cn('flex h-9 w-9 items-center justify-center rounded-full text-sm font-bold transition-all', isReviewMode
39
- ? questionData.answer === true
40
- ? 'bg-green-500 text-white'
41
- : selectedAnswer === true
42
- ? 'bg-red-500 text-white'
43
- : 'bg-gray-200 text-gray-600'
44
- : selectedAnswer === true
45
- ? 'bg-emerald-500 text-white shadow-sm'
46
- : 'bg-gray-100 text-gray-500 group-hover:bg-emerald-100 group-hover:text-emerald-600'), children: _jsx(Check, { className: "h-4 w-4" }) }), _jsx("span", { className: cn('text-base font-semibold', isReviewMode && questionData.answer === true
47
- ? 'text-green-800'
48
- : isReviewMode && selectedAnswer === true
49
- ? 'text-red-800'
50
- : selectedAnswer === true
51
- ? 'text-emerald-700'
52
- : 'text-gray-600'), children: "\u0110\u00FAng (True)" }), isReviewMode && (questionData.answer === true || selectedAnswer === true) && (_jsx("div", { className: cn('flex h-6 w-6 items-center justify-center rounded-full', questionData.answer === true ? 'bg-green-500' : 'bg-red-500'), children: questionData.answer === true ? (_jsx(Check, { className: "h-4 w-4 text-white" })) : (_jsx(X, { className: "h-4 w-4 text-white" })) }))] }), _jsxs("button", { type: "button", onClick: () => handleAnswerChange(false), disabled: isReviewMode, className: cn('group flex items-center justify-center gap-3 rounded-xl border-2 p-4 transition-all', isReviewMode
53
- ? questionData.answer === false
54
- ? 'border-green-400 bg-green-50'
55
- : selectedAnswer === false
56
- ? 'border-red-400 bg-red-50'
57
- : 'border-gray-200 bg-gray-50'
58
- : selectedAnswer === false
59
- ? 'border-red-500 bg-red-50 shadow-sm ring-2 ring-red-100'
60
- : 'border-gray-200 bg-white hover:border-red-300 hover:bg-red-50/50 hover:shadow-sm'), children: [_jsx("div", { className: cn('flex h-9 w-9 items-center justify-center rounded-full text-sm font-bold transition-all', isReviewMode
61
- ? questionData.answer === false
62
- ? 'bg-green-500 text-white'
63
- : selectedAnswer === false
64
- ? 'bg-red-500 text-white'
65
- : 'bg-gray-200 text-gray-600'
66
- : selectedAnswer === false
67
- ? 'bg-red-500 text-white shadow-sm'
68
- : 'bg-gray-100 text-gray-500 group-hover:bg-red-100 group-hover:text-red-600'), children: _jsx(X, { className: "h-4 w-4" }) }), _jsx("span", { className: cn('text-base font-semibold', isReviewMode && questionData.answer === false
69
- ? 'text-green-800'
70
- : isReviewMode && selectedAnswer === false
71
- ? 'text-red-800'
72
- : selectedAnswer === false
73
- ? 'text-red-700'
74
- : 'text-gray-600'), children: "Sai (False)" }), isReviewMode && (questionData.answer === false || selectedAnswer === false) && (_jsx("div", { className: cn('flex h-6 w-6 items-center justify-center rounded-full', questionData.answer === false ? 'bg-green-500' : 'bg-red-500'), children: questionData.answer === false ? (_jsx(Check, { className: "h-4 w-4 text-white" })) : (_jsx(X, { className: "h-4 w-4 text-white" })) }))] })] })] }), !isReviewMode && !hasAnswered && (_jsxs("div", { className: "flex items-center gap-2 rounded-lg border border-amber-200 bg-gradient-to-r from-amber-50 to-yellow-50 p-3", children: [_jsx("div", { className: "flex h-5 w-5 flex-shrink-0 items-center justify-center rounded-full bg-amber-400 text-white", children: _jsx("span", { className: "text-xs font-bold", children: "!" }) }), _jsx("span", { className: "text-sm text-amber-800", children: "Vui l\u00F2ng ch\u1ECDn \u0111\u00E1p \u00E1n" })] })), isReviewMode && questionData.explanation && (_jsxs("div", { className: "rounded-lg border border-blue-200 bg-blue-50 p-4", children: [_jsx("p", { className: "text-sm font-semibold text-blue-800", children: "Gi\u1EA3i th\u00EDch:" }), _jsx("p", { className: "mt-1 text-sm text-blue-700", children: questionData.explanation })] }))] })] }) }));
23
+ return (_jsxs("div", { className: "space-y-4", children: [_jsxs(KidQuestionCard, { title: "\u0110\u00FAng / Sai", isExample: questionData.isExample, review: isReviewMode && hasAnswered ? (isAnswerCorrect ? 'correct' : 'wrong') : null, children: [_jsx(KidPrompt, { children: questionData.question || 'Chưa nhập câu hỏi' }), (imagePreviewUrl || questionData.imageUrl) && (_jsxs("div", { className: "overflow-hidden rounded-lg border border-gray-200 bg-gradient-to-br from-slate-50 to-gray-100", children: [_jsx("div", { className: "border-b border-gray-100 px-3 py-1.5", children: _jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx("div", { className: "h-2 w-2 rounded-full bg-red-400" }), _jsx("div", { className: "h-2 w-2 rounded-full bg-yellow-400" }), _jsx("div", { className: "h-2 w-2 rounded-full bg-green-400" }), _jsx("span", { className: "ml-2 text-xs font-medium text-gray-500", children: "H\u00ECnh \u1EA3nh" })] }) }), _jsx("div", { className: "flex items-center justify-center p-4", children: isLoading ? (_jsx("div", { className: "flex h-48 items-center justify-center", children: _jsx("div", { className: "h-8 w-8 animate-spin rounded-full border-4 border-gray-200 border-t-blue-500" }) })) : (
24
+ /* eslint-disable-next-line @next/next/no-img-element */
25
+ _jsx("img", { src: imagePreviewUrl || questionData.imageUrl, alt: "H\u00ECnh \u1EA3nh c\u00E2u h\u1ECFi", className: "max-h-56 w-auto rounded-md object-contain" })) })] })), _jsxs("div", { className: "space-y-1.5", children: [_jsx(KidChoiceRow, { letter: "T", selected: selectedAnswer === true, isReviewMode: isReviewMode, isCorrect: isReviewMode && questionData.answer === true, onClick: () => handleAnswerChange(true), children: "\u0110\u00FAng" }), _jsx(KidChoiceRow, { letter: "F", selected: selectedAnswer === false, isReviewMode: isReviewMode, isCorrect: isReviewMode && questionData.answer === false, onClick: () => handleAnswerChange(false), children: "Sai" })] }), !isReviewMode && !hasAnswered && (_jsxs("div", { className: "flex items-center gap-2 rounded-md border border-amber-200 bg-amber-50 px-2.5 py-1.5", children: [_jsx("div", { className: "flex h-4 w-4 flex-shrink-0 items-center justify-center rounded-full bg-amber-400 text-white", children: _jsx("span", { className: "text-[10px] font-bold", children: "!" }) }), _jsx("span", { className: "text-xs text-amber-800", children: "Vui l\u00F2ng ch\u1ECDn \u0111\u00E1p \u00E1n" })] }))] }), isReviewMode && questionData.explanation && (_jsxs("div", { className: "flex items-start gap-2 rounded-md border border-amber-200 bg-amber-50 px-3 py-2", children: [_jsx(Lightbulb, { className: "mt-0.5 h-3.5 w-3.5 shrink-0 text-amber-600" }), _jsxs("p", { className: "text-xs text-gray-700", children: [_jsx("span", { className: "font-semibold text-gray-800", children: "Gi\u1EA3i th\u00EDch: " }), questionData.explanation] })] }))] }));
75
26
  }