@tinyweb_dev/oe-exam-sdk 0.2.8 → 0.2.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/ExamTakingPageContainer.js +6 -0
  2. package/dist/components/exams/take/components/QuestionRenderer.js +7 -3
  3. package/dist/components/exams/take/components/question-renderers/MoversChooseBestAnswerRenderer.js +5 -4
  4. package/dist/components/exams/take/components/question-renderers/MoversWordOrderingGroupRenderer.d.ts +13 -0
  5. package/dist/components/exams/take/components/question-renderers/MoversWordOrderingGroupRenderer.js +30 -0
  6. package/dist/components/exams/take/components/question-renderers/index.d.ts +1 -0
  7. package/dist/components/exams/take/components/question-renderers/index.js +1 -0
  8. package/dist/components/exams/take/utils/answer-transformers.js +8 -1
  9. package/dist/components/exams/take/utils/question-transformers.d.ts +16 -1
  10. package/dist/components/exams/take/utils/question-transformers.js +49 -4
  11. package/dist/components/index.d.ts +1 -0
  12. package/dist/components/index.js +1 -0
  13. package/dist/components/questions/_shared/config/question-types.config.js +18 -0
  14. package/dist/components/questions/_shared/types/choose-the-correct-answer-group.type.d.ts +1 -0
  15. package/dist/components/questions/_shared/types/index.d.ts +1 -0
  16. package/dist/components/questions/_shared/types/index.js +1 -0
  17. package/dist/components/questions/_shared/types/match-columns-to-make-sentences.type.d.ts +56 -0
  18. package/dist/components/questions/_shared/types/match-columns-to-make-sentences.type.js +2 -0
  19. package/dist/components/questions/_shared/types/match-word-to-picture-group.type.d.ts +35 -0
  20. package/dist/components/questions/_shared/types/match-word-to-picture-group.type.js +2 -0
  21. package/dist/components/questions/_shared/types/word-ordering-group.type.d.ts +34 -0
  22. package/dist/components/questions/_shared/types/word-ordering-group.type.js +2 -0
  23. package/dist/components/questions/components/QuestionSearchDropdown.js +3 -0
  24. package/dist/components/questions/components/QuestionTypeSelector.js +3 -0
  25. package/dist/components/questions/creator/question-type-registry.js +6 -0
  26. package/dist/components/questions/types/choose-the-correct-answer/ChooseTheCorrectAnswerClient.js +6 -4
  27. package/dist/components/questions/types/choose-the-correct-answer/ChooseTheCorrectAnswerCreator.js +16 -4
  28. package/dist/components/questions/types/choose-the-correct-answer-group/ChooseTheCorrectAnswerGroupClient.js +2 -3
  29. package/dist/components/questions/types/choose-the-correct-answer-group/ChooseTheCorrectAnswerGroupCreator.js +6 -4
  30. package/dist/components/questions/types/choose-the-correct-answer-group/map-choose-the-correct-answer-group-data.js +16 -2
  31. package/dist/components/questions/types/choose-the-correct-answer-group/transform.js +4 -0
  32. package/dist/components/questions/types/match-columns-to-make-sentences/MatchColumnsToMakeSentencesClient.d.ts +2 -0
  33. package/dist/components/questions/types/match-columns-to-make-sentences/MatchColumnsToMakeSentencesClient.js +74 -0
  34. package/dist/components/questions/types/match-columns-to-make-sentences/MatchColumnsToMakeSentencesCreator.d.ts +2 -0
  35. package/dist/components/questions/types/match-columns-to-make-sentences/MatchColumnsToMakeSentencesCreator.js +222 -0
  36. package/dist/components/questions/types/match-columns-to-make-sentences/index.d.ts +5 -0
  37. package/dist/components/questions/types/match-columns-to-make-sentences/index.js +5 -0
  38. package/dist/components/questions/types/match-columns-to-make-sentences/map-match-columns-to-make-sentences-data.d.ts +8 -0
  39. package/dist/components/questions/types/match-columns-to-make-sentences/map-match-columns-to-make-sentences-data.js +199 -0
  40. package/dist/components/questions/types/match-columns-to-make-sentences/register.d.ts +2 -0
  41. package/dist/components/questions/types/match-columns-to-make-sentences/register.js +36 -0
  42. package/dist/components/questions/types/match-columns-to-make-sentences/transform.d.ts +2 -0
  43. package/dist/components/questions/types/match-columns-to-make-sentences/transform.js +66 -0
  44. package/dist/components/questions/types/match-word-to-picture-group/MatchWordToPictureGroupClient.d.ts +2 -0
  45. package/dist/components/questions/types/match-word-to-picture-group/MatchWordToPictureGroupClient.js +65 -0
  46. package/dist/components/questions/types/match-word-to-picture-group/MatchWordToPictureGroupCreator.d.ts +2 -0
  47. package/dist/components/questions/types/match-word-to-picture-group/MatchWordToPictureGroupCreator.js +95 -0
  48. package/dist/components/questions/types/match-word-to-picture-group/MatchWordToPictureGroupImageField.d.ts +6 -0
  49. package/dist/components/questions/types/match-word-to-picture-group/MatchWordToPictureGroupImageField.js +16 -0
  50. package/dist/components/questions/types/match-word-to-picture-group/index.d.ts +5 -0
  51. package/dist/components/questions/types/match-word-to-picture-group/index.js +5 -0
  52. package/dist/components/questions/types/match-word-to-picture-group/map-match-word-to-picture-group-data.d.ts +4 -0
  53. package/dist/components/questions/types/match-word-to-picture-group/map-match-word-to-picture-group-data.js +77 -0
  54. package/dist/components/questions/types/match-word-to-picture-group/register.d.ts +2 -0
  55. package/dist/components/questions/types/match-word-to-picture-group/register.js +36 -0
  56. package/dist/components/questions/types/match-word-to-picture-group/transform.d.ts +2 -0
  57. package/dist/components/questions/types/match-word-to-picture-group/transform.js +36 -0
  58. package/dist/components/questions/types/word-ordering-group/WordOrderingGroupClient.d.ts +2 -0
  59. package/dist/components/questions/types/word-ordering-group/WordOrderingGroupClient.js +25 -0
  60. package/dist/components/questions/types/word-ordering-group/WordOrderingGroupCreator.d.ts +2 -0
  61. package/dist/components/questions/types/word-ordering-group/WordOrderingGroupCreator.js +106 -0
  62. package/dist/components/questions/types/word-ordering-group/index.d.ts +5 -0
  63. package/dist/components/questions/types/word-ordering-group/index.js +5 -0
  64. package/dist/components/questions/types/word-ordering-group/map-word-ordering-group-data.d.ts +4 -0
  65. package/dist/components/questions/types/word-ordering-group/map-word-ordering-group-data.js +74 -0
  66. package/dist/components/questions/types/word-ordering-group/register.d.ts +2 -0
  67. package/dist/components/questions/types/word-ordering-group/register.js +36 -0
  68. package/dist/components/questions/types/word-ordering-group/transform.d.ts +2 -0
  69. package/dist/components/questions/types/word-ordering-group/transform.js +33 -0
  70. package/dist/components/questions/viewer/MatchColumnsToMakeSentencesViewer.d.ts +8 -0
  71. package/dist/components/questions/viewer/MatchColumnsToMakeSentencesViewer.js +49 -0
  72. package/dist/components/questions/viewer/MatchWordToPictureGroupViewer.js +2 -2
  73. package/dist/components/questions/viewer/QuestionViewer.js +6 -0
  74. package/dist/components/questions/viewer/WordOrderingGroupViewer.d.ts +7 -0
  75. package/dist/components/questions/viewer/WordOrderingGroupViewer.js +29 -0
  76. package/dist/components/questions/viewer/index.d.ts +2 -0
  77. package/dist/components/questions/viewer/index.js +2 -0
  78. package/dist/components/results/renderers/ReviewChooseBestAnswerRenderer.js +3 -2
  79. package/dist/components/results/renderers/review-question-body-dedicated.js +1 -1
  80. package/dist/components/results/renderers/review-question-body-movers.js +6 -2
  81. package/dist/components/results/review-data.js +13 -0
  82. package/dist/components/shared/RichTextEditor.d.ts +4 -2
  83. package/dist/components/shared/RichTextEditor.js +34 -11
  84. package/dist/components/shared/RichTextHtml.d.ts +8 -0
  85. package/dist/components/shared/RichTextHtml.js +9 -0
  86. package/dist/index.d.ts +1 -1
  87. package/dist/index.js +1 -1
  88. package/dist/shared/constants/question-skills.js +6 -0
  89. package/dist/shared/lib/rich-text.d.ts +3 -0
  90. package/dist/shared/lib/rich-text.js +24 -0
  91. package/dist/shared/lib/utils/question-reverse-transform.js +111 -2
  92. package/dist/shared/lib/utils/question-transform.js +6 -0
  93. package/dist/shared/lib/validation/choose-correct-answer.validation.js +4 -2
  94. package/dist/shared/types/common.types.d.ts +1 -1
  95. package/dist/shared/types/questions/choose-the-correct-answer-group.d.ts +1 -0
  96. package/dist/shared/types/questions/index.d.ts +3 -0
  97. package/dist/shared/types/questions/index.js +6 -0
  98. package/dist/shared/types/questions/match-columns-to-make-sentences.d.ts +50 -0
  99. package/dist/shared/types/questions/match-columns-to-make-sentences.js +1 -0
  100. package/dist/shared/types/questions/match-word-to-picture-group.d.ts +31 -0
  101. package/dist/shared/types/questions/match-word-to-picture-group.js +1 -0
  102. package/dist/shared/types/questions/word-ordering-group.d.ts +28 -0
  103. package/dist/shared/types/questions/word-ordering-group.js +1 -0
  104. package/package.json +1 -1
@@ -4,6 +4,8 @@ import { useState, useEffect, useMemo } from 'react';
4
4
  import { Check, X, CircleHelp, Lightbulb, ClipboardList, Users, BookOpen } from 'lucide-react';
5
5
  import { usePresignedFileUrl } from '../../../../shared/lib/hooks';
6
6
  import { cn } from '../../../../shared/lib/utils';
7
+ import { RichTextHtml } from '../../../../components/shared/RichTextHtml';
8
+ import { isRichTextEmpty, richTextToPlainText } from '../../../../shared/lib/rich-text';
7
9
  import { normalizeChooseAnswerCorrectAnswer, normalizeChooseAnswerOptions, optionToString } from './normalize';
8
10
  // Helper component to resolve presigned URL for each image option
9
11
  function OptionImage({ src, alt, className }) {
@@ -82,7 +84,7 @@ export function ChooseTheCorrectAnswerClient({ questionData, onSubmit, isReviewM
82
84
  const optText = optionToString(opt);
83
85
  return questionData.optionType === 'image'
84
86
  ? `Đáp án ${answerLetter}`
85
- : `${answerLetter}. ${optText}`;
87
+ : `${answerLetter}. ${richTextToPlainText(optText)}`;
86
88
  }).join(', ');
87
89
  };
88
90
  // Visibility toggles based on questionConfig (from template)
@@ -109,7 +111,7 @@ export function ChooseTheCorrectAnswerClient({ questionData, onSubmit, isReviewM
109
111
  .split('\n')
110
112
  .map((line) => `<p class="last:mb-0">${line}</p>`)
111
113
  .join(''),
112
- } })), questionData.groupImageUrl && (_jsxs("div", { className: "mt-5 relative overflow-hidden rounded-xl border border-slate-200/60 bg-slate-50 shadow-sm transition-all hover:shadow-md", children: [_jsx("div", { className: "flex items-center justify-between border-b border-slate-200/60 bg-slate-100/50 px-4 py-2", children: _jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx("div", { className: "h-2.5 w-2.5 rounded-full bg-slate-300" }), _jsx("div", { className: "h-2.5 w-2.5 rounded-full bg-slate-300" }), _jsx("div", { className: "h-2.5 w-2.5 rounded-full bg-slate-300" }), _jsx("span", { className: "ml-2 text-[11px] font-semibold uppercase tracking-wider text-slate-500", children: "T\u00E0i li\u1EC7u \u0111\u00EDnh k\u00E8m" })] }) }), _jsx("div", { className: "flex items-center justify-center p-4 min-h-[160px]", children: _jsx(OptionImage, { src: questionData.groupImageUrl, alt: "Group illustration", className: "max-h-64 w-auto rounded-lg object-contain" }) })] }))] }))] })] })), _jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white shadow-xs", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-gray-100 bg-gradient-to-r from-gray-50 to-white px-5 py-3.5", 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-blue-500 to-indigo-600 text-white shadow-xs", children: _jsx(CircleHelp, { className: "h-4 w-4" }) }), _jsx("span", { className: "text-sm font-medium text-gray-700", children: isMultipleAnswers ? 'Câu hỏi nhiều đáp án' : 'Chọn đáp án đúng' })] }), questionData.points !== undefined && (_jsx("div", { className: "flex items-center gap-1.5 rounded-full bg-blue-50 px-3 py-1 text-xs font-semibold text-blue-700", children: _jsxs("span", { children: [questionData.points, " \u0111i\u1EC3m"] }) }))] }), _jsxs("div", { className: "p-5 space-y-4", children: [(questionData.askerName || questionData.answererName) && (_jsxs("div", { className: "flex items-center gap-2 rounded-lg bg-gray-50 p-2.5 text-xs text-gray-600", children: [_jsx(Users, { className: "h-4 w-4 text-gray-400" }), questionData.askerName && (_jsxs("span", { children: [_jsx("strong", { children: "Ng\u01B0\u1EDDi h\u1ECFi:" }), " ", questionData.askerName] })), questionData.askerName && questionData.answererName && _jsx("span", { children: "\u2022" }), questionData.answererName && (_jsxs("span", { children: [_jsx("strong", { children: "Ng\u01B0\u1EDDi tr\u1EA3 l\u1EDDi:" }), " ", questionData.answererName] }))] })), showQuestion && (_jsx("div", { className: "rounded-lg bg-gradient-to-r from-blue-50 to-indigo-50 p-4 border border-blue-100", children: _jsx("h3", { className: "text-base font-semibold text-gray-800 leading-relaxed", children: questionData.question || 'Câu hỏi chưa được nhập' }) })), showImage && (imagePreviewUrl || cleanImageUrl) && (_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" }) })) : (
114
+ } })), questionData.groupImageUrl && (_jsxs("div", { className: "mt-5 relative overflow-hidden rounded-xl border border-slate-200/60 bg-slate-50 shadow-sm transition-all hover:shadow-md", children: [_jsx("div", { className: "flex items-center justify-between border-b border-slate-200/60 bg-slate-100/50 px-4 py-2", children: _jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx("div", { className: "h-2.5 w-2.5 rounded-full bg-slate-300" }), _jsx("div", { className: "h-2.5 w-2.5 rounded-full bg-slate-300" }), _jsx("div", { className: "h-2.5 w-2.5 rounded-full bg-slate-300" }), _jsx("span", { className: "ml-2 text-[11px] font-semibold uppercase tracking-wider text-slate-500", children: "T\u00E0i li\u1EC7u \u0111\u00EDnh k\u00E8m" })] }) }), _jsx("div", { className: "flex items-center justify-center p-4 min-h-[160px]", children: _jsx(OptionImage, { src: questionData.groupImageUrl, alt: "Group illustration", className: "max-h-64 w-auto rounded-lg object-contain" }) })] }))] }))] })] })), _jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white shadow-xs", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-gray-100 bg-gradient-to-r from-gray-50 to-white px-5 py-3.5", 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-blue-500 to-indigo-600 text-white shadow-xs", children: _jsx(CircleHelp, { className: "h-4 w-4" }) }), _jsx("span", { className: "text-sm font-medium text-gray-700", children: isMultipleAnswers ? 'Câu hỏi nhiều đáp án' : 'Chọn đáp án đúng' })] }), questionData.points !== undefined && (_jsx("div", { className: "flex items-center gap-1.5 rounded-full bg-blue-50 px-3 py-1 text-xs font-semibold text-blue-700", children: _jsxs("span", { children: [questionData.points, " \u0111i\u1EC3m"] }) }))] }), _jsxs("div", { className: "p-5 space-y-4", children: [(questionData.askerName || questionData.answererName) && (_jsxs("div", { className: "flex items-center gap-2 rounded-lg bg-gray-50 p-2.5 text-xs text-gray-600", children: [_jsx(Users, { className: "h-4 w-4 text-gray-400" }), questionData.askerName && (_jsxs("span", { children: [_jsx("strong", { children: "Ng\u01B0\u1EDDi h\u1ECFi:" }), " ", questionData.askerName] })), questionData.askerName && questionData.answererName && _jsx("span", { children: "\u2022" }), questionData.answererName && (_jsxs("span", { children: [_jsx("strong", { children: "Ng\u01B0\u1EDDi tr\u1EA3 l\u1EDDi:" }), " ", questionData.answererName] }))] })), showQuestion && (_jsx("div", { className: "rounded-lg bg-gradient-to-r from-blue-50 to-indigo-50 p-4 border border-blue-100", children: isRichTextEmpty(questionData.question) ? (_jsx("h3", { className: "text-base font-semibold text-gray-800 leading-relaxed", children: "C\u00E2u h\u1ECFi ch\u01B0a \u0111\u01B0\u1EE3c nh\u1EADp" })) : (_jsx(RichTextHtml, { html: questionData.question, className: "text-base font-semibold text-gray-800 leading-relaxed" })) })), showImage && (imagePreviewUrl || cleanImageUrl) && (_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" }) })) : (
113
115
  /* eslint-disable-next-line @next/next/no-img-element */
114
116
  _jsx("img", { src: imagePreviewUrl || cleanImageUrl, alt: "Question illustration", className: "max-h-56 w-auto object-contain rounded-md", onError: (e) => {
115
117
  const target = e.target;
@@ -136,11 +138,11 @@ export function ChooseTheCorrectAnswerClient({ questionData, onSubmit, isReviewM
136
138
  : 'bg-gray-200 text-gray-600'
137
139
  : isSelected
138
140
  ? 'bg-gradient-to-br from-blue-500 to-indigo-600 text-white shadow-sm'
139
- : 'bg-gray-100 text-gray-600 group-hover:bg-gray-200'), children: letter }), questionData.optionType === 'image' ? (_jsx("div", { className: "flex-1 flex items-center justify-center py-1", children: _jsx(OptionImage, { src: optionText, alt: `Đáp án ${letter}` }) })) : (_jsx("span", { className: cn('flex-1 text-sm font-medium', isReviewMode && isCorrectAnswer
141
+ : 'bg-gray-100 text-gray-600 group-hover:bg-gray-200'), children: letter }), questionData.optionType === 'image' ? (_jsx("div", { className: "flex-1 flex items-center justify-center py-1", children: _jsx(OptionImage, { src: optionText, alt: `Đáp án ${letter}` }) })) : (_jsx(RichTextHtml, { html: optionText, className: cn('flex-1 text-sm font-medium', isReviewMode && isCorrectAnswer
140
142
  ? 'text-green-800'
141
143
  : isReviewMode && isSelected && !isCorrectAnswer
142
144
  ? 'text-red-800'
143
- : 'text-gray-700'), children: optionText })), isReviewMode && (isCorrectAnswer || isSelected) && (_jsx("div", { className: cn('flex h-6 w-6 items-center justify-center rounded-full', isCorrectAnswer ? 'bg-green-500' : 'bg-red-500'), children: isCorrectAnswer
145
+ : 'text-gray-700') })), isReviewMode && (isCorrectAnswer || isSelected) && (_jsx("div", { className: cn('flex h-6 w-6 items-center justify-center rounded-full', isCorrectAnswer ? 'bg-green-500' : 'bg-red-500'), children: isCorrectAnswer
144
146
  ? _jsx(Check, { className: "h-4 w-4 text-white" })
145
147
  : _jsx(X, { className: "h-4 w-4 text-white" }) }))] }, index));
146
148
  })] })), !isReviewMode && !hasAnswered && (_jsxs("div", { className: "rounded-lg border border-amber-200 bg-gradient-to-r from-amber-50 to-yellow-50 p-3 flex items-center gap-2", 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: isMultipleAnswers ? 'Vui lòng chọn ít nhất một đáp án' : 'Vui lòng chọn một đáp án' })] }))] })] }), isReviewMode && questionData.explanation && (_jsxs("div", { className: "overflow-hidden rounded-xl border border-blue-200 bg-gradient-to-br from-blue-50 to-indigo-50 shadow-sm", children: [_jsxs("div", { className: "flex items-center gap-2 border-b border-blue-100 bg-white/50 px-4 py-3", children: [_jsx("div", { className: "flex h-7 w-7 items-center justify-center rounded-lg bg-gradient-to-br from-amber-400 to-orange-500 text-white shadow-sm", children: _jsx(Lightbulb, { className: "h-4 w-4" }) }), _jsx("h4", { className: "text-sm font-semibold text-gray-800", children: "Gi\u1EA3i th\u00EDch" })] }), _jsx("div", { className: "p-4", children: _jsx("p", { className: "text-sm text-gray-700 leading-relaxed", children: questionData.explanation }) })] })), isReviewMode && (_jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm", children: [_jsxs("div", { className: "flex items-center gap-2 border-b border-gray-100 bg-gradient-to-r from-gray-50 to-white px-4 py-3", children: [_jsx("div", { className: "flex h-7 w-7 items-center justify-center rounded-lg bg-gradient-to-br from-slate-500 to-gray-600 text-white shadow-sm", children: _jsx(ClipboardList, { className: "h-4 w-4" }) }), _jsx("h4", { className: "text-sm font-semibold text-gray-800", children: "T\u1ED5ng k\u1EBFt" })] }), _jsxs("div", { className: "p-4 space-y-3", children: [_jsxs("div", { className: "flex items-center justify-between rounded-lg bg-gray-50 p-3", children: [_jsx("span", { className: "text-sm text-gray-600", children: "\u0110\u00E1p \u00E1n c\u1EE7a b\u1EA1n:" }), _jsx("span", { className: cn('text-sm font-semibold', isAnswerCorrect ? 'text-green-600' : hasAnswered ? 'text-red-600' : 'text-gray-500'), children: formatAnswerLabel(selectedAnswer) })] }), _jsxs("div", { className: "flex items-center justify-between rounded-lg bg-green-50 p-3 border border-green-100", children: [_jsx("span", { className: "text-sm text-gray-600", children: "\u0110\u00E1p \u00E1n \u0111\u00FAng:" }), _jsx("span", { className: "text-sm font-semibold text-green-600", children: formatAnswerLabel(normalizedCorrectAnswers) })] }), _jsxs("div", { className: cn('flex items-center justify-between rounded-lg p-3 border', isAnswerCorrect
@@ -14,6 +14,8 @@ import { usePresignedFileUrl, useDebouncedCallback } from '../../../../shared/li
14
14
  import { useReactHookForm } from '../../../../shared/lib/hooks/useReactHookForm';
15
15
  import { Pencil, Eye, Plus, Trash2, HelpCircle, CheckCircle2, Lightbulb, MessageSquare, ImageIcon, Type, Users, BookOpen, Star, ListChecks } from 'lucide-react';
16
16
  import { FileUpload } from '../../../../components/ui/file-upload';
17
+ import { RichTextEditor } from '../../../../components/shared/RichTextEditor';
18
+ import { isRichTextEmpty } from '../../../../shared/lib/rich-text';
17
19
  import { ChooseTheCorrectAnswerClient } from './ChooseTheCorrectAnswerClient';
18
20
  import { normalizeChooseAnswerCorrectAnswer, normalizeChooseAnswerOptions, pickRawChooseAnswer, } from './normalize';
19
21
  import { chooseCorrectAnswerFormSchema, } from '../../../../shared/lib/validation/choose-correct-answer.validation';
@@ -410,7 +412,9 @@ function ChooseTheCorrectAnswerCreatorContent({ initialData, onSave, onCancel, o
410
412
  : undefined })] }));
411
413
  }
412
414
  // Creator UI
413
- return (_jsxs("div", { className: "space-y-4", children: [isGrouped && effectiveGroupType === 'ARTICLES' && (_jsx(ArticlesGroupCard, { groupNumber: currentGroupNumber, articleCount: articleCount, articles: articles, onArticlesChange: setArticles, title: groupTitle, onTitleChange: setGroupTitle, isReadOnly: !isFirstInGroup, uploadIdPrefix: `choose-correct-answer-${partId ?? 'articles'}` })), isGrouped && effectiveGroupType === 'BASIC' && (_jsx(BasicQuestionGroupCard, { groupNumber: currentGroupNumber, title: groupTitle, subtitle: groupSubtitle, content: groupContent, imageUrl: groupImageUrl, onTitleChange: setGroupTitle, onSubtitleChange: setGroupSubtitle, onContentChange: setGroupContent, onImageChange: setGroupImageUrl, groupConfig: groupType ? groupConfig : undefined, isReadOnly: !isFirstInGroup, uploadIdPrefix: `choose-correct-answer-${partId ?? 'group'}` })), isGrouped && effectiveGroupType === 'DOCUMENT' && (_jsx(DocumentGroupCard, { groupNumber: currentGroupNumber, title: groupTitle, subtitle: groupSubtitle, content: groupContent, imageUrl: groupImageUrl, onTitleChange: setGroupTitle, onSubtitleChange: setGroupSubtitle, onContentChange: setGroupContent, onImageChange: setGroupImageUrl, groupConfig: groupType ? groupConfig : undefined, isReadOnly: !isFirstInGroup, uploadIdPrefix: `choose-correct-answer-${partId ?? 'document'}` })), _jsxs(Card, { className: "overflow-hidden border-0 bg-white shadow-lg shadow-blue-100/50", children: [_jsx(CardHeader, { className: "px-4 py-3", children: _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-8 w-8 items-center justify-center rounded-lg bg-gray-100", children: _jsx(HelpCircle, { className: "h-4 w-4 text-gray-600" }) }), _jsxs("div", { children: [_jsx(CardTitle, { className: "text-base font-bold text-gray-900", children: "Ch\u1ECDn \u0111\u00E1p \u00E1n \u0111\u00FAng" }), _jsx("p", { className: "text-xs text-gray-500", children: "T\u1EA1o c\u00E2u h\u1ECFi tr\u1EAFc nghi\u1EC7m v\u1EDBi nhi\u1EC1u l\u1EF1a ch\u1ECDn" })] })] }), _jsxs("div", { className: "flex items-center gap-2", children: [headerExtra, _jsxs(Button, { variant: "outline", size: "sm", onClick: () => setIsClientMode(true), disabled: watchedOptions.filter((o) => typeof o === 'string' && o.trim()).length < 2, className: "gap-2 border-indigo-200 text-indigo-600 hover:bg-indigo-50 hover:text-indigo-700", children: [_jsx(Eye, { className: "h-4 w-4" }), "Xem tr\u01B0\u1EDBc"] })] })] }) }), _jsxs(CardContent, { className: "space-y-3 px-4 pb-4", children: [_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-y-2", children: [_jsx(PointsInput, { ...form.register('points', { valueAsNumber: true }), error: form.hasError('points') ? form.getFieldError('points') : undefined }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsxs("button", { type: "button", style: { paddingBlock: 'calc(var(--spacing) * 1)' }, onClick: () => form.setValue('isExample', !watchedIsExample), className: `inline-flex items-center gap-2 rounded-md border px-3.5 text-sm font-medium transition-colors select-none ${watchedIsExample
415
+ return (_jsxs("div", { className: "space-y-4", children: [isGrouped && effectiveGroupType === 'ARTICLES' && (_jsx(ArticlesGroupCard, { groupNumber: currentGroupNumber, articleCount: articleCount, articles: articles, onArticlesChange: setArticles, title: groupTitle, onTitleChange: setGroupTitle, isReadOnly: !isFirstInGroup, uploadIdPrefix: `choose-correct-answer-${partId ?? 'articles'}` })), isGrouped && effectiveGroupType === 'BASIC' && (_jsx(BasicQuestionGroupCard, { groupNumber: currentGroupNumber, title: groupTitle, subtitle: groupSubtitle, content: groupContent, imageUrl: groupImageUrl, onTitleChange: setGroupTitle, onSubtitleChange: setGroupSubtitle, onContentChange: setGroupContent, onImageChange: setGroupImageUrl, groupConfig: groupType ? groupConfig : undefined, isReadOnly: !isFirstInGroup, uploadIdPrefix: `choose-correct-answer-${partId ?? 'group'}` })), isGrouped && effectiveGroupType === 'DOCUMENT' && (_jsx(DocumentGroupCard, { groupNumber: currentGroupNumber, title: groupTitle, subtitle: groupSubtitle, content: groupContent, imageUrl: groupImageUrl, onTitleChange: setGroupTitle, onSubtitleChange: setGroupSubtitle, onContentChange: setGroupContent, onImageChange: setGroupImageUrl, groupConfig: groupType ? groupConfig : undefined, isReadOnly: !isFirstInGroup, uploadIdPrefix: `choose-correct-answer-${partId ?? 'document'}` })), _jsxs(Card, { className: "overflow-hidden border-0 bg-white shadow-lg shadow-blue-100/50", children: [_jsx(CardHeader, { className: "px-4 py-3", children: _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-8 w-8 items-center justify-center rounded-lg bg-gray-100", children: _jsx(HelpCircle, { className: "h-4 w-4 text-gray-600" }) }), _jsxs("div", { children: [_jsx(CardTitle, { className: "text-base font-bold text-gray-900", children: "Ch\u1ECDn \u0111\u00E1p \u00E1n \u0111\u00FAng" }), _jsx("p", { className: "text-xs text-gray-500", children: "T\u1EA1o c\u00E2u h\u1ECFi tr\u1EAFc nghi\u1EC7m v\u1EDBi nhi\u1EC1u l\u1EF1a ch\u1ECDn" })] })] }), _jsxs("div", { className: "flex items-center gap-2", children: [headerExtra, _jsxs(Button, { variant: "outline", size: "sm", onClick: () => setIsClientMode(true), disabled: watchedOptions.filter((o) => watchedOptionType === 'image'
416
+ ? typeof o === 'string' && o.trim()
417
+ : !isRichTextEmpty(o)).length < 2, className: "gap-2 border-indigo-200 text-indigo-600 hover:bg-indigo-50 hover:text-indigo-700", children: [_jsx(Eye, { className: "h-4 w-4" }), "Xem tr\u01B0\u1EDBc"] })] })] }) }), _jsxs(CardContent, { className: "space-y-3 px-4 pb-4", children: [_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-y-2", children: [_jsx(PointsInput, { ...form.register('points', { valueAsNumber: true }), error: form.hasError('points') ? form.getFieldError('points') : undefined }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsxs("button", { type: "button", style: { paddingBlock: 'calc(var(--spacing) * 1)' }, onClick: () => form.setValue('isExample', !watchedIsExample), className: `inline-flex items-center gap-2 rounded-md border px-3.5 text-sm font-medium transition-colors select-none ${watchedIsExample
414
418
  ? 'border-amber-300 bg-amber-50 text-amber-700'
415
419
  : 'border-gray-200 bg-white text-gray-400 hover:text-gray-600'}`, children: [_jsx(Star, { className: `h-4 w-4 ${watchedIsExample ? 'fill-amber-400 text-amber-400' : ''}` }), "Example"] }), _jsxs("button", { type: "button", style: { paddingBlock: 'calc(var(--spacing) * 1)' }, disabled: Boolean(multipleAnswers), onClick: () => {
416
420
  const enabled = !watchedMultipleAnswers;
@@ -422,7 +426,7 @@ function ChooseTheCorrectAnswerCreatorContent({ initialData, onSave, onCancel, o
422
426
  form.setValue('correctAnswer', enabled ? normalized : (normalized[0] ?? -1), { shouldValidate: true });
423
427
  }, className: `inline-flex items-center gap-2 rounded-md border px-3.5 text-sm font-medium transition-colors select-none ${watchedMultipleAnswers
424
428
  ? 'border-violet-300 bg-violet-50 text-violet-700'
425
- : 'border-gray-200 bg-white text-gray-400 hover:text-gray-600'} ${Boolean(multipleAnswers) ? 'cursor-not-allowed opacity-50' : ''}`, children: [_jsx(ListChecks, { className: "h-4 w-4" }), "Nhi\u1EC1u \u0111\u00E1p \u00E1n"] })] })] }), viewMode === 'CONVERSATION' && (_jsxs("div", { className: "space-y-3", children: [_jsxs(Label, { className: "flex items-center gap-2 text-base font-semibold text-gray-800", children: [_jsx(Users, { className: "h-4 w-4 text-indigo-500" }), "H\u1ED9i tho\u1EA1i ", _jsx("span", { className: "text-sm font-normal text-gray-500", children: "(tu\u1EF3 ch\u1ECDn - d\u00F9ng cho c\u00E2u h\u1ECFi d\u1EA1ng h\u1ED9i tho\u1EA1i)" })] }), _jsxs("div", { className: "grid grid-cols-2 gap-4 rounded-xl border border-gray-200 bg-gradient-to-r from-gray-50 to-slate-50 p-4", children: [_jsxs("div", { className: "space-y-1.5", children: [_jsxs(Label, { htmlFor: "askerName", className: "flex items-center gap-1 text-sm font-medium text-gray-600", children: ["T\u00EAn ng\u01B0\u1EDDi h\u1ECFi ", _jsx("span", { className: "text-red-500", children: "*" })] }), _jsx(Input, { id: "askerName", ...form.register('askerName'), placeholder: "VD: John, Mary, Teacher...", className: `border-gray-200 bg-white transition-all focus:border-indigo-300 focus:ring-indigo-200 ${form.hasError('askerName') ? 'border-red-400 focus:border-red-500 focus:ring-red-200' : ''}` }), form.hasError('askerName') && (_jsx("p", { className: "text-sm text-red-600", children: form.getFieldError('askerName') }))] }), _jsxs("div", { className: "space-y-1.5", children: [_jsxs(Label, { htmlFor: "answererName", className: "flex items-center gap-1 text-sm font-medium text-gray-600", children: ["T\u00EAn ng\u01B0\u1EDDi tr\u1EA3 l\u1EDDi ", _jsx("span", { className: "text-red-500", children: "*" })] }), _jsx(Input, { id: "answererName", ...form.register('answererName'), placeholder: "VD: Tom, Anna, Student...", className: `border-gray-200 bg-white transition-all focus:border-indigo-300 focus:ring-indigo-200 ${form.hasError('answererName') ? 'border-red-400 focus:border-red-500 focus:ring-red-200' : ''}` }), form.hasError('answererName') && (_jsx("p", { className: "text-sm text-red-600", children: form.getFieldError('answererName') }))] })] })] })), (!questionConfig || questionConfig.length === 0 || questionConfig.includes('question')) && (_jsxs("div", { className: "space-y-2", children: [_jsxs(Label, { htmlFor: "question", className: "flex items-center gap-2 text-base font-semibold text-gray-800", children: [_jsx(MessageSquare, { className: "h-4 w-4 text-indigo-500" }), _jsx("span", { className: "flex items-center gap-1", children: "C\u00E2u h\u1ECFi" }), _jsx("span", { className: "text-sm font-normal text-gray-500", children: "(tu\u1EF3 ch\u1ECDn)" })] }), _jsx(Textarea, { id: "question", ...form.register('question'), placeholder: "Nh\u1EADp c\u00E2u h\u1ECFi...", className: `min-h-[100px] border-gray-200 bg-white transition-all focus:border-indigo-300 focus:ring-indigo-200 ${form.hasError('question') ? 'border-red-400 focus:border-red-500 focus:ring-red-200' : ''}`, rows: 3 }), form.hasError('question') && (_jsx("p", { className: "text-sm text-red-600", children: form.getFieldError('question') }))] })), (!questionConfig || questionConfig.length === 0 || questionConfig.includes('imageUrl') || questionConfig.includes('imageUrl ')) && (_jsxs("div", { className: "space-y-2", children: [_jsxs(Label, { className: "flex items-center gap-2 text-base font-semibold text-gray-800", children: [_jsx(ImageIcon, { className: "h-4 w-4 text-indigo-500" }), "H\u00ECnh \u1EA3nh ", _jsx("span", { className: "text-sm font-normal text-gray-500" })] }), _jsx(FileUpload, { id: "question-image", label: "", accept: "image/*", value: watchedImageUrl || '', onChange: (url) => {
429
+ : 'border-gray-200 bg-white text-gray-400 hover:text-gray-600'} ${Boolean(multipleAnswers) ? 'cursor-not-allowed opacity-50' : ''}`, children: [_jsx(ListChecks, { className: "h-4 w-4" }), "Nhi\u1EC1u \u0111\u00E1p \u00E1n"] })] })] }), viewMode === 'CONVERSATION' && (_jsxs("div", { className: "space-y-3", children: [_jsxs(Label, { className: "flex items-center gap-2 text-base font-semibold text-gray-800", children: [_jsx(Users, { className: "h-4 w-4 text-indigo-500" }), "H\u1ED9i tho\u1EA1i ", _jsx("span", { className: "text-sm font-normal text-gray-500", children: "(tu\u1EF3 ch\u1ECDn - d\u00F9ng cho c\u00E2u h\u1ECFi d\u1EA1ng h\u1ED9i tho\u1EA1i)" })] }), _jsxs("div", { className: "grid grid-cols-2 gap-4 rounded-xl border border-gray-200 bg-gradient-to-r from-gray-50 to-slate-50 p-4", children: [_jsxs("div", { className: "space-y-1.5", children: [_jsxs(Label, { htmlFor: "askerName", className: "flex items-center gap-1 text-sm font-medium text-gray-600", children: ["T\u00EAn ng\u01B0\u1EDDi h\u1ECFi ", _jsx("span", { className: "text-red-500", children: "*" })] }), _jsx(Input, { id: "askerName", ...form.register('askerName'), placeholder: "VD: John, Mary, Teacher...", className: `border-gray-200 bg-white transition-all focus:border-indigo-300 focus:ring-indigo-200 ${form.hasError('askerName') ? 'border-red-400 focus:border-red-500 focus:ring-red-200' : ''}` }), form.hasError('askerName') && (_jsx("p", { className: "text-sm text-red-600", children: form.getFieldError('askerName') }))] }), _jsxs("div", { className: "space-y-1.5", children: [_jsxs(Label, { htmlFor: "answererName", className: "flex items-center gap-1 text-sm font-medium text-gray-600", children: ["T\u00EAn ng\u01B0\u1EDDi tr\u1EA3 l\u1EDDi ", _jsx("span", { className: "text-red-500", children: "*" })] }), _jsx(Input, { id: "answererName", ...form.register('answererName'), placeholder: "VD: Tom, Anna, Student...", className: `border-gray-200 bg-white transition-all focus:border-indigo-300 focus:ring-indigo-200 ${form.hasError('answererName') ? 'border-red-400 focus:border-red-500 focus:ring-red-200' : ''}` }), form.hasError('answererName') && (_jsx("p", { className: "text-sm text-red-600", children: form.getFieldError('answererName') }))] })] })] })), (!questionConfig || questionConfig.length === 0 || questionConfig.includes('question')) && (_jsxs("div", { className: "space-y-2", children: [_jsxs(Label, { htmlFor: "question", className: "flex items-center gap-2 text-base font-semibold text-gray-800", children: [_jsx(MessageSquare, { className: "h-4 w-4 text-indigo-500" }), _jsx("span", { className: "flex items-center gap-1", children: "C\u00E2u h\u1ECFi" }), _jsx("span", { className: "text-sm font-normal text-gray-500", children: "(tu\u1EF3 ch\u1ECDn)" })] }), _jsx(RichTextEditor, { value: watchedQuestion, onChange: (html) => form.setValue('question', html, { shouldValidate: true }), variant: "compact", minHeightClassName: "min-h-[100px]" }), form.hasError('question') && (_jsx("p", { className: "text-sm text-red-600", children: form.getFieldError('question') }))] })), (!questionConfig || questionConfig.length === 0 || questionConfig.includes('imageUrl') || questionConfig.includes('imageUrl ')) && (_jsxs("div", { className: "space-y-2", children: [_jsxs(Label, { className: "flex items-center gap-2 text-base font-semibold text-gray-800", children: [_jsx(ImageIcon, { className: "h-4 w-4 text-indigo-500" }), "H\u00ECnh \u1EA3nh ", _jsx("span", { className: "text-sm font-normal text-gray-500" })] }), _jsx(FileUpload, { id: "question-image", label: "", accept: "image/*", value: watchedImageUrl || '', onChange: (url) => {
426
430
  form.setValue('imageUrl', url, { shouldValidate: true });
427
431
  }, onPresignedUrlChange: setImagePreviewUrl, maxSize: 5, placeholder: "Upload \u1EA3nh ho\u1EB7c paste URL", autoUpload: true, prefix: "questions" }), form.hasError('imageUrl') && (_jsx("p", { className: "mt-1 text-sm text-red-600", children: form.getFieldError('imageUrl') })), displayPreviewUrl && (_jsx(ImagePreview, { src: displayPreviewUrl, alt: "Preview", className: "mt-2 h-48 w-full max-w-md rounded-lg border border-gray-200" }))] })), externalErrors && externalErrors.length > 0 && (_jsxs("div", { className: "flex items-start gap-3 rounded-xl border border-red-200 bg-gradient-to-r from-red-50 to-rose-50 p-4", children: [_jsx("div", { className: "flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-lg bg-red-100", children: _jsx("span", { className: "text-lg", children: "\u274C" }) }), _jsxs("div", { className: "text-sm", children: [_jsx("p", { className: "font-semibold text-red-800", children: "L\u1ED7i:" }), _jsx("ul", { className: "mt-1 list-inside list-disc text-red-700", children: externalErrors.map((error, index) => (_jsx("li", { children: error }, index))) })] })] })), form.formState.errors.options?.message && (_jsx("div", { className: "rounded-xl border border-red-200 bg-red-50 p-4", children: _jsx("p", { className: "text-sm text-red-600", children: form.formState.errors.options.message }) }))] })] }), (!questionConfig || questionConfig.length === 0 || questionConfig.includes('options')) && (_jsxs(Card, { className: "overflow-hidden border-0 bg-white shadow-lg shadow-indigo-100/50", children: [_jsx(CardHeader, { className: "px-4 py-3", children: _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-6 w-6 items-center justify-center rounded-md bg-indigo-100", children: _jsx(Lightbulb, { className: "h-3.5 w-3.5 text-indigo-600" }) }), _jsxs(CardTitle, { className: "text-base font-semibold text-gray-800", children: ["C\u00E1c l\u1EF1a ch\u1ECDn ", _jsxs("span", { className: "text-xs font-normal text-gray-500", children: ["(", watchedOptions.length, " \u0111\u00E1p \u00E1n)"] })] })] }), _jsxs("div", { className: "flex items-center gap-3", children: [_jsxs("div", { className: "flex items-center gap-2 rounded-lg border border-gray-200 bg-gray-50 px-3 py-1.5", children: [_jsx(Type, { className: `h-3.5 w-3.5 ${watchedOptionType === 'text' ? 'text-indigo-600' : 'text-gray-400'}` }), _jsx("span", { className: `text-xs font-medium ${watchedOptionType === 'text' ? 'text-indigo-600' : 'text-gray-400'}`, children: "Text" }), _jsx(Switch, { checked: watchedOptionType === 'image', disabled: true, className: watchedOptionType === 'image' ? '!bg-indigo-600 !opacity-100' : '!bg-indigo-600 !opacity-100', onCheckedChange: (checked) => {
428
432
  const newType = checked ? 'image' : 'text';
@@ -488,7 +492,11 @@ function ChooseTheCorrectAnswerCreatorContent({ initialData, onSave, onCancel, o
488
492
  setOptionPreviewUrls((prev) => ({ ...prev, [index]: presignedUrl }));
489
493
  }, maxSize: 5, placeholder: `Upload ảnh đáp án ${optionLetter}...`, autoUpload: true, prefix: "questions/options" }), (optionPreviewUrls[index] || option) && (_jsx(OptionImagePreview, { src: optionPreviewUrls[index] || option, alt: `Đáp án ${optionLetter}` }))] })) : (
490
494
  /* Text Input Mode */
491
- _jsx(Input, { id: `option-input-${index}`, ...form.register(`options.${index}`), placeholder: `Nhập đáp án ${optionLetter}...`, className: `border-gray-200 bg-white transition-all focus:border-indigo-300 focus:ring-indigo-200 ${optionError ? 'border-red-400 focus:border-red-500 focus:ring-red-200' : ''}` })), optionError && (_jsx("p", { className: "text-sm text-red-600", children: optionError.message })), isCorrect && (_jsxs("span", { className: "inline-flex items-center gap-1.5 rounded-full bg-green-100 px-3 py-1 text-xs font-semibold text-green-700", children: [_jsx(CheckCircle2, { className: "h-3.5 w-3.5" }), watchedMultipleAnswers ? 'Đáp án đúng đã chọn' : 'Đáp án đúng'] }))] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => {
495
+ _jsx(RichTextEditor, { value: option, onChange: (html) => {
496
+ const newOptions = [...watchedOptions];
497
+ newOptions[index] = html;
498
+ form.setValue('options', newOptions, { shouldValidate: true });
499
+ }, variant: "compact", minHeightClassName: "min-h-[72px]" })), optionError && (_jsx("p", { className: "text-sm text-red-600", children: optionError.message })), isCorrect && (_jsxs("span", { className: "inline-flex items-center gap-1.5 rounded-full bg-green-100 px-3 py-1 text-xs font-semibold text-green-700", children: [_jsx(CheckCircle2, { className: "h-3.5 w-3.5" }), watchedMultipleAnswers ? 'Đáp án đúng đã chọn' : 'Đáp án đúng'] }))] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => {
492
500
  if (watchedMultipleAnswers) {
493
501
  const currentAnswers = Array.isArray(form.getValues('correctAnswer'))
494
502
  ? form.getValues('correctAnswer')
@@ -543,7 +551,11 @@ function ChooseTheCorrectAnswerCreatorContent({ initialData, onSave, onCancel, o
543
551
  setOptionPreviewUrls((prev) => ({ ...prev, [index]: presignedUrl }));
544
552
  }, maxSize: 5, placeholder: `Upload ảnh đáp án ${optionLetter}...`, autoUpload: true, prefix: "questions/options" }), (optionPreviewUrls[index] || option) && (_jsx(OptionImagePreview, { src: optionPreviewUrls[index] || option, alt: `Đáp án ${optionLetter}` }))] })) : (
545
553
  /* Text Input Mode */
546
- _jsx(Input, { id: `option-input-${index}`, ...form.register(`options.${index}`), placeholder: `Nhập đáp án ${optionLetter}...`, className: `border-gray-200 bg-white transition-all focus:border-indigo-300 focus:ring-indigo-200 ${optionError ? 'border-red-400 focus:border-red-500 focus:ring-red-200' : ''}` })), optionError && (_jsx("p", { className: "text-sm text-red-600", children: optionError.message })), isCorrect && (_jsxs("span", { className: "inline-flex items-center gap-1.5 rounded-full bg-green-100 px-3 py-1 text-xs font-semibold text-green-700", children: [_jsx(CheckCircle2, { className: "h-3.5 w-3.5" }), "\u0110\u00E1p \u00E1n \u0111\u00FAng"] }))] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => {
554
+ _jsx(RichTextEditor, { value: option, onChange: (html) => {
555
+ const newOptions = [...watchedOptions];
556
+ newOptions[index] = html;
557
+ form.setValue('options', newOptions, { shouldValidate: true });
558
+ }, variant: "compact", minHeightClassName: "min-h-[72px]" })), optionError && (_jsx("p", { className: "text-sm text-red-600", children: optionError.message })), isCorrect && (_jsxs("span", { className: "inline-flex items-center gap-1.5 rounded-full bg-green-100 px-3 py-1 text-xs font-semibold text-green-700", children: [_jsx(CheckCircle2, { className: "h-3.5 w-3.5" }), "\u0110\u00E1p \u00E1n \u0111\u00FAng"] }))] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => {
547
559
  form.setValue('correctAnswer', index, { shouldValidate: true });
548
560
  }, className: isCorrect ? 'text-green-600 hover:text-green-700' : 'text-gray-400 hover:text-green-600', title: "Ch\u1ECDn l\u00E0m \u0111\u00E1p \u00E1n \u0111\u00FAng", children: _jsx(CheckCircle2, { className: "h-4 w-4" }) }), watchedOptions.length > 2 && (_jsx(Button, { variant: "ghost", size: "sm", onClick: () => {
549
561
  const currentOptions = form.getValues('options');
@@ -34,11 +34,10 @@ export function ChooseTheCorrectAnswerGroupClient({ questionData, isReviewMode =
34
34
  next[itemIndex] = optionId;
35
35
  onAnswerChange(next);
36
36
  };
37
- 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 })), questionData.audioUrl && _jsx(ClientAudio, { url: questionData.audioUrl }), 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 (Reading Passage)" })] }), _jsx("div", { className: "prose prose-sm max-w-none text-gray-800 leading-relaxed", dangerouslySetInnerHTML: { __html: questionData.passage } })] })), items.map((item, itemIndex) => {
37
+ 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 })), questionData.audioUrl && _jsx(ClientAudio, { url: questionData.audioUrl }), toChooseAnswerGroupImageUrls(questionData.imageUrl).length > 0 && (_jsx("div", { className: "flex flex-wrap justify-center gap-3", children: toChooseAnswerGroupImageUrls(questionData.imageUrl).map((url, imageIndex) => (_jsx(ClientImageItem, { url: url, alt: `Hình ảnh nhóm (${imageIndex + 1})` }, `group-image-${imageIndex}`))) })), 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 (Reading Passage)" })] }), _jsx("div", { className: "prose prose-sm max-w-none text-gray-800 leading-relaxed", dangerouslySetInnerHTML: { __html: questionData.passage } })] })), items.map((item, itemIndex) => {
38
38
  const selectedId = userAnswers[itemIndex];
39
39
  const canSelect = !isReviewMode && !item.isExample && Boolean(onAnswerChange);
40
- const showItemAudio = Boolean(item.audioUrl) && !questionData.audioUrl;
41
- return (_jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-gray-100 bg-gradient-to-r from-gray-50 to-white px-4 py-2.5", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-7 w-7 items-center justify-center rounded-lg bg-gradient-to-br from-blue-500 to-indigo-600 text-white", children: _jsx(CircleHelp, { className: "h-3.5 w-3.5" }) }), _jsxs("span", { className: "text-sm font-medium text-gray-700", children: ["C\u00E2u ", item.questionNumber || itemIndex + 1] }), item.isExample && (_jsxs("span", { className: "inline-flex items-center gap-1 rounded-full bg-amber-100 px-2 py-0.5 text-xs font-semibold text-amber-700", children: [_jsx(Star, { className: "h-3 w-3" }), "Example"] }))] }), _jsxs("span", { className: "text-xs text-gray-500", children: [item.points || 0, " \u0111i\u1EC3m"] })] }), _jsxs("div", { className: "space-y-3 p-4", children: [_jsx("p", { className: "text-sm font-semibold leading-relaxed text-gray-800", children: item.question || 'Câu hỏi chưa được nhập' }), showItemAudio && item.audioUrl && _jsx(ClientAudio, { url: item.audioUrl }), toChooseAnswerGroupImageUrls(item.imageUrl).length > 0 && (_jsx("div", { className: "flex flex-wrap justify-center gap-3", children: toChooseAnswerGroupImageUrls(item.imageUrl).map((url, imageIndex) => (_jsx(ClientImageItem, { url: url, alt: `Hình ảnh câu ${item.questionNumber || itemIndex + 1} (${imageIndex + 1})` }, `${item.questionNumber}-${imageIndex}`))) })), item.optionType === 'image' ? (_jsx("div", { className: "grid gap-3 sm:grid-cols-2", children: item.options.map((option, optionIndex) => {
40
+ return (_jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-gray-100 bg-gradient-to-r from-gray-50 to-white px-4 py-2.5", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-7 w-7 items-center justify-center rounded-lg bg-gradient-to-br from-blue-500 to-indigo-600 text-white", children: _jsx(CircleHelp, { className: "h-3.5 w-3.5" }) }), _jsxs("span", { className: "text-sm font-medium text-gray-700", children: ["C\u00E2u ", item.questionNumber || itemIndex + 1] }), item.isExample && (_jsxs("span", { className: "inline-flex items-center gap-1 rounded-full bg-amber-100 px-2 py-0.5 text-xs font-semibold text-amber-700", children: [_jsx(Star, { className: "h-3 w-3" }), "Example"] }))] }), _jsxs("span", { className: "text-xs text-gray-500", children: [item.points || 0, " \u0111i\u1EC3m"] })] }), _jsxs("div", { className: "space-y-3 p-4", children: [_jsx("p", { className: "text-sm font-semibold leading-relaxed text-gray-800", children: item.question || 'Câu hỏi chưa được nhập' }), item.audioUrl && _jsx(ClientAudio, { url: item.audioUrl }), toChooseAnswerGroupImageUrls(item.imageUrl).length > 0 && (_jsx("div", { className: "flex flex-wrap justify-center gap-3", children: toChooseAnswerGroupImageUrls(item.imageUrl).map((url, imageIndex) => (_jsx(ClientImageItem, { url: url, alt: `Hình ảnh câu ${item.questionNumber || itemIndex + 1} (${imageIndex + 1})` }, `${item.questionNumber}-${imageIndex}`))) })), item.optionType === 'image' ? (_jsx("div", { className: "grid gap-3 sm:grid-cols-2", children: item.options.map((option, optionIndex) => {
42
41
  const isCorrect = option.id === item.correctAnswer;
43
42
  const isSelected = option.id === selectedId;
44
43
  const optionImageSrc = getChooseAnswerGroupOptionImageSrc(option);
@@ -70,6 +70,7 @@ export function ChooseTheCorrectAnswerGroupCreator({ initialData, onChange, exte
70
70
  const [instruction, setInstruction] = useState(initialData?.instruction || 'Choose the correct answer.');
71
71
  const [passage, setPassage] = useState(initialData?.passage || '');
72
72
  const [audioUrl, setAudioUrl] = useState(initialData?.audioUrl || '');
73
+ const [imageUrl, setImageUrl] = useState(toChooseAnswerGroupImageUrls(initialData?.imageUrl)[0] || '');
73
74
  const [items, setItems] = useState(() => normalizeItems(initialData?.items));
74
75
  const [explanation, setExplanation] = useState(initialData?.explanation || '');
75
76
  const [isClientMode, setIsClientMode] = useState(false);
@@ -80,10 +81,11 @@ export function ChooseTheCorrectAnswerGroupCreator({ initialData, onChange, exte
80
81
  onChangeRef.current = onChange;
81
82
  }, [onChange]);
82
83
  const isContentEmpty = (html) => !html || html === '<p></p>' || html === '<p><br></p>' || html.trim() === '';
83
- const buildPayload = (nextInstruction = instruction, nextPassage = passage, nextItems = items, nextExplanation = explanation, nextAudioUrl = audioUrl) => ({
84
+ const buildPayload = (nextInstruction = instruction, nextPassage = passage, nextItems = items, nextExplanation = explanation, nextAudioUrl = audioUrl, nextImageUrl = imageUrl) => ({
84
85
  instruction: nextInstruction,
85
86
  ...(!isContentEmpty(nextPassage) ? { passage: nextPassage.trim() } : {}),
86
87
  ...(nextAudioUrl.trim() ? { audioUrl: nextAudioUrl.trim() } : {}),
88
+ ...(nextImageUrl.trim() ? { imageUrl: nextImageUrl.trim() } : {}),
87
89
  items: nextItems,
88
90
  explanation: nextExplanation,
89
91
  points: sumPoints(nextItems),
@@ -100,7 +102,7 @@ export function ChooseTheCorrectAnswerGroupCreator({ initialData, onChange, exte
100
102
  previousPayloadRef.current = serialized;
101
103
  onUnsavedChangesChange?.(true);
102
104
  debouncedOnChange(payload);
103
- }, [instruction, passage, audioUrl, items, explanation, debouncedOnChange, onUnsavedChangesChange]);
105
+ }, [instruction, passage, audioUrl, imageUrl, items, explanation, debouncedOnChange, onUnsavedChangesChange]);
104
106
  const validate = () => {
105
107
  const nextErrors = [];
106
108
  if (!instruction.trim()) {
@@ -165,10 +167,10 @@ export function ChooseTheCorrectAnswerGroupCreator({ initialData, onChange, exte
165
167
  return (_jsxs("div", { className: "space-y-4", children: [_jsx("div", { className: "flex justify-end", children: _jsxs(Button, { type: "button", variant: "outline", onClick: () => setIsClientMode(false), children: [_jsx(Pencil, { className: "mr-2 h-4 w-4" }), "Ch\u1EC9nh s\u1EEDa"] }) }), _jsx(ChooseTheCorrectAnswerGroupClient, { questionData: buildPayload(), isReviewMode: true })] }));
166
168
  }
167
169
  const allErrors = [...errors, ...(externalErrors || [])];
168
- return (_jsxs("div", { className: "space-y-4", children: [_jsx("div", { className: "flex justify-end", children: _jsxs(Button, { type: "button", variant: "outline", onClick: () => setIsClientMode(true), children: [_jsx(Eye, { className: "mr-2 h-4 w-4" }), "Xem tr\u01B0\u1EDBc"] }) }), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { children: "Nh\u00F3m ch\u1ECDn \u0111\u00E1p \u00E1n \u0111\u00FAng" }) }), _jsxs(CardContent, { className: "space-y-4", children: [_jsxs("div", { className: "space-y-2", children: [_jsx(Label, { htmlFor: "group-instruction", children: "H\u01B0\u1EDBng d\u1EABn" }), _jsx(Input, { id: "group-instruction", value: instruction, onChange: (event) => setInstruction(event.target.value), placeholder: "Choose the correct answer." })] }), _jsx("div", { className: "space-y-2", children: _jsx(FileUpload, { id: "choose-answer-group-audio", label: "Audio chung (tu\u1EF3 ch\u1ECDn)", accept: "audio/*", value: audioUrl, onChange: setAudioUrl, maxSize: 20, placeholder: "Upload file audio ho\u1EB7c paste URL", autoUpload: true, prefix: "questions", showPlayButton: true }) }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "N\u1ED9i dung b\u00E0i \u0111\u1ECDc (Passage - Tu\u1EF3 ch\u1ECDn)" }), _jsx(RichTextEditor, { value: passage, onChange: setPassage, minHeightClassName: "min-h-[160px]" })] }), _jsxs("div", { className: "rounded-xl border border-gray-200 bg-gray-50 p-4", children: [_jsx(PointsInput, { value: sumPoints(items), allowZero: true, readOnly: true }), _jsx("p", { className: "mt-2 text-xs text-gray-500", children: "T\u1ED5ng \u0111i\u1EC3m \u0111\u01B0\u1EE3c c\u1ED9ng t\u1EEB c\u00E1c c\u00E2u kh\u00F4ng ph\u1EA3i example." })] })] })] }), items.map((item, itemIndex) => (_jsxs(Card, { children: [_jsxs(CardHeader, { className: "flex flex-row items-center justify-between space-y-0", children: [_jsxs(CardTitle, { className: "text-base", children: ["C\u00E2u ", itemIndex + 1] }), _jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => removeItem(itemIndex), disabled: items.length <= 1, children: _jsx(Trash2, { className: "h-4 w-4" }) })] }), _jsxs(CardContent, { className: "space-y-4", children: [_jsxs("div", { className: "flex items-center justify-between rounded-lg border border-amber-100 bg-amber-50 px-3 py-2", children: [_jsxs("div", { className: "flex items-center gap-2 text-sm font-medium text-amber-800", children: [_jsx(Star, { className: "h-4 w-4" }), "C\u00E2u v\u00ED d\u1EE5"] }), _jsx(Switch, { checked: item.isExample === true, onCheckedChange: (checked) => updateItem(itemIndex, {
170
+ return (_jsxs("div", { className: "space-y-4", children: [_jsx("div", { className: "flex justify-end", children: _jsxs(Button, { type: "button", variant: "outline", onClick: () => setIsClientMode(true), children: [_jsx(Eye, { className: "mr-2 h-4 w-4" }), "Xem tr\u01B0\u1EDBc"] }) }), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { children: "Nh\u00F3m ch\u1ECDn \u0111\u00E1p \u00E1n \u0111\u00FAng" }) }), _jsxs(CardContent, { className: "space-y-4", children: [_jsxs("div", { className: "space-y-2", children: [_jsx(Label, { htmlFor: "group-instruction", children: "H\u01B0\u1EDBng d\u1EABn" }), _jsx(Input, { id: "group-instruction", value: instruction, onChange: (event) => setInstruction(event.target.value), placeholder: "Choose the correct answer." })] }), _jsx("div", { className: "space-y-2", children: _jsx(FileUpload, { id: "choose-answer-group-audio", label: "Audio chung (tu\u1EF3 ch\u1ECDn)", accept: "audio/*", value: audioUrl, onChange: setAudioUrl, maxSize: 20, placeholder: "Upload file audio ho\u1EB7c paste URL", autoUpload: true, prefix: "questions", showPlayButton: true }) }), _jsx("div", { className: "space-y-2", children: _jsx(FileUpload, { id: "choose-answer-group-image", label: "H\u00ECnh \u1EA3nh chung (tu\u1EF3 ch\u1ECDn)", accept: "image/*", value: imageUrl, onChange: setImageUrl, maxSize: 5, placeholder: "Upload \u1EA3nh nh\u00F3m ho\u1EB7c paste URL", autoUpload: true, prefix: "questions" }) }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "N\u1ED9i dung b\u00E0i \u0111\u1ECDc (Passage - Tu\u1EF3 ch\u1ECDn)" }), _jsx(RichTextEditor, { value: passage, onChange: setPassage, minHeightClassName: "min-h-[160px]" })] }), _jsxs("div", { className: "rounded-xl border border-gray-200 bg-gray-50 p-4", children: [_jsx(PointsInput, { value: sumPoints(items), allowZero: true, readOnly: true }), _jsx("p", { className: "mt-2 text-xs text-gray-500", children: "T\u1ED5ng \u0111i\u1EC3m \u0111\u01B0\u1EE3c c\u1ED9ng t\u1EEB c\u00E1c c\u00E2u kh\u00F4ng ph\u1EA3i example." })] })] })] }), items.map((item, itemIndex) => (_jsxs(Card, { children: [_jsxs(CardHeader, { className: "flex flex-row items-center justify-between space-y-0", children: [_jsxs(CardTitle, { className: "text-base", children: ["C\u00E2u ", itemIndex + 1] }), _jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => removeItem(itemIndex), disabled: items.length <= 1, children: _jsx(Trash2, { className: "h-4 w-4" }) })] }), _jsxs(CardContent, { className: "space-y-4", children: [_jsxs("div", { className: "flex items-center justify-between rounded-lg border border-amber-100 bg-amber-50 px-3 py-2", children: [_jsxs("div", { className: "flex items-center gap-2 text-sm font-medium text-amber-800", children: [_jsx(Star, { className: "h-4 w-4" }), "C\u00E2u v\u00ED d\u1EE5"] }), _jsx(Switch, { checked: item.isExample === true, onCheckedChange: (checked) => updateItem(itemIndex, {
169
171
  isExample: checked,
170
172
  points: checked ? 0 : (item.points || 1),
171
- }) })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "C\u00E2u h\u1ECFi" }), _jsx(Textarea, { value: item.question, onChange: (event) => updateItem(itemIndex, { question: event.target.value }), rows: 2, placeholder: "A place for someone to stay or live is called" })] }), _jsxs("div", { className: "space-y-2", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs(Label, { className: "flex items-center gap-2", children: [_jsx(ImageIcon, { className: "h-4 w-4 text-teal-500" }), "H\u00ECnh \u1EA3nh (tu\u1EF3 ch\u1ECDn)"] }), _jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: () => {
173
+ }) })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "C\u00E2u h\u1ECFi" }), _jsx(Textarea, { value: item.question, onChange: (event) => updateItem(itemIndex, { question: event.target.value }), rows: 2, placeholder: "A place for someone to stay or live is called" })] }), _jsx("div", { className: "space-y-2", children: _jsx(FileUpload, { id: `choose-answer-item-audio-${itemIndex}`, label: "Audio c\u00E2u h\u1ECFi (tu\u1EF3 ch\u1ECDn)", accept: "audio/*", value: item.audioUrl || '', onChange: (url) => updateItem(itemIndex, { audioUrl: url }), maxSize: 20, placeholder: "Upload file audio ho\u1EB7c paste URL", autoUpload: true, prefix: "questions", showPlayButton: true }) }), _jsxs("div", { className: "space-y-2", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs(Label, { className: "flex items-center gap-2", children: [_jsx(ImageIcon, { className: "h-4 w-4 text-teal-500" }), "H\u00ECnh \u1EA3nh (tu\u1EF3 ch\u1ECDn)"] }), _jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: () => {
172
174
  const nextUrls = [...toEditableChooseAnswerGroupImageUrls(item.imageUrl), ''];
173
175
  updateItem(itemIndex, { imageUrl: nextUrls });
174
176
  }, className: "gap-1.5 border-teal-200 text-teal-600 hover:bg-teal-50 hover:text-teal-700", children: [_jsx(Plus, { className: "h-3.5 w-3.5" }), "Th\u00EAm h\u00ECnh \u1EA3nh"] })] }), toEditableChooseAnswerGroupImageUrls(item.imageUrl).length === 0 ? (_jsx("div", { className: "rounded-lg border border-dashed border-gray-200 bg-gray-50/50 p-3 text-xs text-gray-500", children: "Ch\u01B0a c\u00F3 h\u00ECnh \u1EA3nh. Nh\u1EA5n \"Th\u00EAm h\u00ECnh \u1EA3nh\" \u0111\u1EC3 upload \u1EA3nh minh ho\u1EA1 cho c\u00E2u h\u1ECFi." })) : (_jsx("div", { className: "space-y-3", children: toEditableChooseAnswerGroupImageUrls(item.imageUrl).map((imageUrl, imageIndex) => (_jsx(ChooseAnswerGroupImageUploadItem, { index: imageIndex, itemIndex: itemIndex, imageUrl: imageUrl, onChange: (url) => {
@@ -8,6 +8,16 @@ function asRecord(value) {
8
8
  function asString(value, fallback = '') {
9
9
  return typeof value === 'string' ? value : fallback;
10
10
  }
11
+ function asOptionId(value) {
12
+ if (typeof value === 'string') {
13
+ return value.trim();
14
+ }
15
+ if (Array.isArray(value)) {
16
+ const first = value.find((item) => typeof item === 'string' && item.trim() !== '');
17
+ return typeof first === 'string' ? first.trim() : '';
18
+ }
19
+ return '';
20
+ }
11
21
  function asOptionalUrl(value) {
12
22
  if (typeof value !== 'string') {
13
23
  return undefined;
@@ -63,7 +73,7 @@ export function mapChooseAnswerGroupItem(item, index, fallbackAnswer) {
63
73
  question: asString(content.question) || asString(record.question),
64
74
  optionType: asString(content.optionType) === 'image' ? 'image' : 'text',
65
75
  options,
66
- correctAnswer: asString(correctAnswer.answer) || fallbackAnswer || options[0]?.id || 'opt-1',
76
+ correctAnswer: asOptionId(correctAnswer.answer) || fallbackAnswer || options[0]?.id || 'opt-1',
67
77
  isExample,
68
78
  points: isExample ? 0 : typeof record.points === 'number' ? record.points : 1,
69
79
  questionNumber: typeof record.questionNumber === 'number' ? record.questionNumber : index + 1,
@@ -78,15 +88,17 @@ export function mapQuestionToChooseAnswerGroupData(question) {
78
88
  const correctAnswer = asRecord(source.correctAnswer ?? source.correct_answer ?? answer.correctAnswer);
79
89
  const explanation = String(source.explanation || answer.explanation || '');
80
90
  const fallbackAnswers = Array.isArray(correctAnswer.answer)
81
- ? correctAnswer.answer.map((value) => typeof value === 'string' ? value : undefined)
91
+ ? correctAnswer.answer.map((value) => asOptionId(value))
82
92
  : [];
83
93
  if (Array.isArray(content.items)) {
84
94
  const items = content.items.map((item, index) => mapChooseAnswerGroupItem(item, index, fallbackAnswers[index]));
85
95
  const audioUrl = asOptionalUrl(asRecord(content.meta).audioUrl);
96
+ const imageUrl = fromChooseAnswerGroupImageUrls(toChooseAnswerGroupImageUrls(asRecord(content.meta).imageUrl));
86
97
  return {
87
98
  instruction: asString(asRecord(content.meta).instruction) || 'Choose the correct answer.',
88
99
  passage: asString(asRecord(content.meta).passage),
89
100
  ...(audioUrl ? { audioUrl } : {}),
101
+ ...(imageUrl !== undefined ? { imageUrl } : {}),
90
102
  items,
91
103
  explanation,
92
104
  points: items.reduce((total, item) => (item.isExample ? total : total + (item.points || 0)), 0),
@@ -94,10 +106,12 @@ export function mapQuestionToChooseAnswerGroupData(question) {
94
106
  }
95
107
  if (Array.isArray(answer.items)) {
96
108
  const audioUrl = asOptionalUrl(answer.audioUrl);
109
+ const imageUrl = fromChooseAnswerGroupImageUrls(toChooseAnswerGroupImageUrls(answer.imageUrl));
97
110
  return {
98
111
  instruction: asString(answer.instruction, 'Choose the correct answer.'),
99
112
  passage: asString(answer.passage),
100
113
  ...(audioUrl ? { audioUrl } : {}),
114
+ ...(imageUrl !== undefined ? { imageUrl } : {}),
101
115
  items: answer.items.map((item, index) => mapChooseAnswerGroupItem(item, index, fallbackAnswers[index])),
102
116
  explanation,
103
117
  points: typeof answer.points === 'number' ? answer.points : undefined,
@@ -51,6 +51,10 @@ export const transformChooseTheCorrectAnswerGroup = (question) => {
51
51
  if (answerData?.audioUrl && answerData.audioUrl.trim()) {
52
52
  meta.audioUrl = answerData.audioUrl.trim();
53
53
  }
54
+ const groupImageUrl = fromChooseAnswerGroupImageUrls(toChooseAnswerGroupImageUrls(answerData?.imageUrl));
55
+ if (groupImageUrl !== undefined) {
56
+ meta.imageUrl = groupImageUrl;
57
+ }
54
58
  return {
55
59
  apiContent: {
56
60
  meta,
@@ -0,0 +1,2 @@
1
+ import type { MatchColumnsGroupClientProps } from '../../_shared/types/match-columns-to-make-sentences.type';
2
+ export declare function MatchColumnsToMakeSentencesClient({ questionData, isReviewMode, userAnswers, onAnswerChange, }: MatchColumnsGroupClientProps): import("react").JSX.Element;
@@ -0,0 +1,74 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { Lightbulb, Star } from 'lucide-react';
4
+ import { cn } from '../../../../shared/lib/utils';
5
+ import { assembleMatchColumnsSentence, getMatchColumnsChoiceColumns, getMatchColumnsStemColumn, } from './map-match-columns-to-make-sentences-data';
6
+ function emptyAnswer(stemFragmentId) {
7
+ return { fragmentIds: stemFragmentId ? [stemFragmentId] : [], sentence: '' };
8
+ }
9
+ export function MatchColumnsToMakeSentencesClient({ questionData, isReviewMode = false, userAnswers = [], onAnswerChange, }) {
10
+ const items = questionData.items || [];
11
+ const columns = questionData.columns || [];
12
+ const stemColumn = getMatchColumnsStemColumn(columns);
13
+ const choiceColumns = getMatchColumnsChoiceColumns(columns);
14
+ const canEdit = Boolean(onAnswerChange) && !isReviewMode;
15
+ const resolveAnswer = (itemIndex) => {
16
+ const item = items[itemIndex];
17
+ if (!item)
18
+ return emptyAnswer('');
19
+ if (item.isExample) {
20
+ return { fragmentIds: item.fragmentIds, sentence: item.sentence };
21
+ }
22
+ const raw = userAnswers[itemIndex];
23
+ if (raw && Array.isArray(raw.fragmentIds)) {
24
+ return raw;
25
+ }
26
+ return emptyAnswer(item.stemFragmentId);
27
+ };
28
+ const handleChoiceChange = (itemIndex, choiceIndex, fragmentId) => {
29
+ if (!canEdit || items[itemIndex]?.isExample)
30
+ return;
31
+ const next = items.map((item, index) => {
32
+ if (item.isExample) {
33
+ return { fragmentIds: item.fragmentIds, sentence: item.sentence };
34
+ }
35
+ const current = resolveAnswer(index);
36
+ if (index !== itemIndex)
37
+ return current;
38
+ const fragmentIds = [
39
+ item.stemFragmentId,
40
+ ...choiceColumns.map((column, colIndex) => {
41
+ if (colIndex === choiceIndex)
42
+ return fragmentId;
43
+ return current.fragmentIds[colIndex + 1] || '';
44
+ }),
45
+ ];
46
+ return {
47
+ fragmentIds,
48
+ sentence: assembleMatchColumnsSentence(fragmentIds, columns),
49
+ };
50
+ });
51
+ onAnswerChange?.(next);
52
+ };
53
+ return (_jsxs("div", { className: "space-y-4", children: [(questionData.title || questionData.instruction) && (_jsxs("div", { className: "rounded-xl border border-indigo-100 bg-indigo-50 px-4 py-3", children: [questionData.title && (_jsx("p", { className: "text-xs font-semibold uppercase tracking-wide text-indigo-500", children: questionData.title })), questionData.instruction && (_jsx("p", { className: "text-sm font-medium text-indigo-800", children: questionData.instruction }))] })), _jsx("div", { className: "overflow-x-auto rounded-xl border border-gray-200 bg-white", children: _jsxs("table", { className: "min-w-full text-sm", children: [_jsx("thead", { children: _jsx("tr", { className: "bg-slate-50 text-left text-xs uppercase tracking-wide text-slate-500", children: columns.map((column) => (_jsxs("th", { className: "px-3 py-2 font-semibold", children: [column.label || column.id, column.role === 'stem' ? ' (stem)' : ''] }, column.id))) }) }), _jsx("tbody", { children: _jsx("tr", { className: "align-top", children: columns.map((column) => (_jsx("td", { className: "px-3 py-2", children: _jsx("ul", { className: "space-y-1", children: column.fragments.map((fragment) => (_jsxs("li", { className: "text-slate-700", children: [_jsxs("span", { className: "mr-1 font-semibold text-slate-500", children: [fragment.id, "."] }), fragment.text || '—'] }, fragment.id))) }) }, `${column.id}-bank`))) }) })] }) }), _jsx("div", { className: "space-y-3", children: items.map((item, itemIndex) => {
54
+ const current = resolveAnswer(itemIndex);
55
+ const canSelect = canEdit && !item.isExample;
56
+ const stemText = stemColumn?.fragments.find((fragment) => fragment.id === item.stemFragmentId)?.text
57
+ || item.stemFragmentId
58
+ || '—';
59
+ const isCorrect = isReviewMode &&
60
+ current.sentence.trim().toLowerCase() === item.sentence.trim().toLowerCase();
61
+ return (_jsxs("div", { className: "rounded-xl border border-gray-200 bg-white p-4 shadow-sm", children: [_jsxs("div", { className: "mb-3 flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsxs("span", { className: "text-sm font-medium text-gray-700", children: ["C\u00E2u ", item.questionNumber || itemIndex + 1] }), item.isExample && (_jsxs("span", { className: "inline-flex items-center gap-1 rounded-full bg-amber-100 px-2 py-0.5 text-xs font-semibold text-amber-700", children: [_jsx(Star, { className: "h-3 w-3" }), "Example"] }))] }), _jsxs("span", { className: "text-xs text-gray-500", children: [item.points || 0, " \u0111i\u1EC3m"] })] }), _jsxs("div", { className: "grid gap-3 md:grid-cols-[minmax(0,1fr)_repeat(2,minmax(0,1fr))]", children: [_jsx("div", { className: "rounded-lg border border-slate-200 bg-slate-50 px-3 py-2 text-sm font-semibold text-slate-800", children: stemText }), choiceColumns.map((column, choiceIndex) => {
62
+ const selectedId = current.fragmentIds[choiceIndex + 1] || '';
63
+ return (_jsxs("select", { className: cn('rounded-lg border-2 px-3 py-2 text-sm', isReviewMode && selectedId === item.fragmentIds[choiceIndex + 1]
64
+ ? 'border-green-400 bg-green-50'
65
+ : selectedId
66
+ ? 'border-blue-400 bg-blue-50'
67
+ : 'border-gray-200 bg-white'), disabled: !canSelect, value: selectedId, onChange: (event) => handleChoiceChange(itemIndex, choiceIndex, event.target.value), children: [_jsxs("option", { value: "", children: ["Ch\u1ECDn ", column.label || column.id] }), column.fragments.map((fragment) => (_jsxs("option", { value: fragment.id, children: [fragment.id, ". ", fragment.text] }, fragment.id)))] }, `${itemIndex}-${column.id}`));
68
+ })] }), _jsx("p", { className: cn('mt-3 text-sm', isReviewMode
69
+ ? isCorrect
70
+ ? 'text-green-700'
71
+ : 'text-red-700'
72
+ : 'text-slate-600'), children: current.sentence || 'Chưa ghép câu' }), isReviewMode && !item.isExample && (_jsxs("p", { className: "mt-1 text-xs text-slate-500", children: ["\u0110\u00E1p \u00E1n: ", item.sentence] }))] }, `${item.questionNumber}-${itemIndex}`));
73
+ }) }), 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] }))] }));
74
+ }
@@ -0,0 +1,2 @@
1
+ import type { MatchColumnsGroupCreatorProps } from '../../_shared/types/match-columns-to-make-sentences.type';
2
+ export declare function MatchColumnsToMakeSentencesCreator({ initialData, onChange, externalErrors, onUnsavedChangesChange, validationRef, }: MatchColumnsGroupCreatorProps): import("react").JSX.Element;