@tinyweb_dev/oe-exam-sdk 1.0.6 → 1.0.7

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 (225) hide show
  1. package/dist/components/exams/ExamCreator.js +1 -16
  2. package/dist/components/exams/take/ExamTakingPageContainer.js +0 -12
  3. package/dist/components/exams/take/components/QuestionRenderer.js +2 -15
  4. package/dist/components/exams/take/components/question-renderers/index.d.ts +0 -3
  5. package/dist/components/exams/take/components/question-renderers/index.js +0 -3
  6. package/dist/components/exams/take/components/speaking/renderers/SpeakingQuestionRenderer.js +0 -4
  7. package/dist/components/exams/take/types/speaking.types.d.ts +0 -31
  8. package/dist/components/exams/take/types.d.ts +0 -65
  9. package/dist/components/exams/take/utils/answer-transformers.js +6 -60
  10. package/dist/components/exams/take/utils/question-transformers.d.ts +2 -15
  11. package/dist/components/exams/take/utils/question-transformers.js +0 -143
  12. package/dist/components/questions/_shared/components/compact/CompactCreatorHeader.d.ts +23 -0
  13. package/dist/components/questions/_shared/components/compact/CompactCreatorHeader.js +9 -0
  14. package/dist/components/questions/_shared/components/compact/CompactExampleToggle.d.ts +6 -0
  15. package/dist/components/questions/_shared/components/compact/CompactExampleToggle.js +8 -0
  16. package/dist/components/questions/_shared/components/compact/CompactExplanationToggle.d.ts +7 -0
  17. package/dist/components/questions/_shared/components/compact/CompactExplanationToggle.js +14 -0
  18. package/dist/components/questions/_shared/components/compact/CompactFieldCard.d.ts +9 -0
  19. package/dist/components/questions/_shared/components/compact/CompactFieldCard.js +7 -0
  20. package/dist/components/questions/_shared/components/compact/CompactPreviewBanner.d.ts +7 -0
  21. package/dist/components/questions/_shared/components/compact/CompactPreviewBanner.js +7 -0
  22. package/dist/components/questions/_shared/components/compact/CompactPreviewButton.d.ts +6 -0
  23. package/dist/components/questions/_shared/components/compact/CompactPreviewButton.js +7 -0
  24. package/dist/components/questions/_shared/components/compact/index.d.ts +6 -0
  25. package/dist/components/questions/_shared/components/compact/index.js +6 -0
  26. package/dist/components/questions/_shared/config/question-types.config.js +0 -24
  27. package/dist/components/questions/_shared/hooks/useQuestionInitialization.js +0 -9
  28. package/dist/components/questions/_shared/types/index.d.ts +0 -1
  29. package/dist/components/questions/_shared/types/index.js +0 -1
  30. package/dist/components/questions/_shared/types/look-and-choose-answer.type.d.ts +0 -13
  31. package/dist/components/questions/_shared/utils/answer-builder.js +0 -11
  32. package/dist/components/questions/components/ColorRegionCreator.js +66 -63
  33. package/dist/components/questions/components/QuestionSearchDropdown.js +0 -8
  34. package/dist/components/questions/components/QuestionTypeSelector.js +0 -2
  35. package/dist/components/questions/components/index.d.ts +1 -0
  36. package/dist/components/questions/components/index.js +1 -0
  37. package/dist/components/questions/creator/question-type-registry.js +0 -8
  38. package/dist/components/questions/groups/ArticlesGroupCard.js +2 -2
  39. package/dist/components/questions/groups/BasicQuestionGroupCard.js +1 -1
  40. package/dist/components/questions/groups/DocumentGroupCard.js +1 -1
  41. package/dist/components/questions/groups/ImageLabelGroupCard.js +1 -1
  42. package/dist/components/questions/groups/MatchAnswerGroupCard.js +2 -2
  43. package/dist/components/questions/question-bank/QuestionEditorRenderer.d.ts +0 -3
  44. package/dist/components/questions/question-bank/QuestionEditorRenderer.js +1 -10
  45. package/dist/components/questions/question-bank/editor-adapters/choice-adapters.d.ts +0 -2
  46. package/dist/components/questions/question-bank/editor-adapters/choice-adapters.js +0 -41
  47. package/dist/components/questions/question-bank/editor-adapters/index.d.ts +2 -2
  48. package/dist/components/questions/question-bank/editor-adapters/index.js +2 -2
  49. package/dist/components/questions/question-bank/editor-adapters/media-adapters.d.ts +0 -1
  50. package/dist/components/questions/question-bank/editor-adapters/media-adapters.js +0 -28
  51. package/dist/components/questions/question-bank/question-bank-picker.utils.js +0 -7
  52. package/dist/components/questions/types/answer-the-question/AnswerTheQuestionClient.js +16 -16
  53. package/dist/components/questions/types/answer-the-question/AnswerTheQuestionCreator.js +36 -30
  54. package/dist/components/questions/types/answer-the-question-group/AnswerTheQuestionGroupClient.js +1 -1
  55. package/dist/components/questions/types/answer-the-question-group/AnswerTheQuestionGroupCreator.js +7 -6
  56. package/dist/components/questions/types/arrange-letters-into-words/ArrangeLettersIntoWordsClient.js +2 -2
  57. package/dist/components/questions/types/arrange-letters-into-words/ArrangeLettersIntoWordsCreator.js +11 -7
  58. package/dist/components/questions/types/choose-correct-adjective/ChooseCorrectAdjectiveClient.js +1 -1
  59. package/dist/components/questions/types/choose-correct-adjective/ChooseCorrectAdjectiveCreator.js +11 -5
  60. package/dist/components/questions/types/choose-the-correct-answer/ChooseTheCorrectAnswerClient.js +5 -5
  61. package/dist/components/questions/types/choose-the-correct-answer/ChooseTheCorrectAnswerCreator.js +34 -28
  62. package/dist/components/questions/types/choose-the-correct-answer-group/ChooseAnswerGroupImageUpload.js +2 -2
  63. package/dist/components/questions/types/choose-the-correct-answer-group/ChooseTheCorrectAnswerGroupClient.js +1 -1
  64. package/dist/components/questions/types/choose-the-correct-answer-group/ChooseTheCorrectAnswerGroupCreator.js +44 -28
  65. package/dist/components/questions/types/choose-then-answer-group/ChooseThenAnswerGroupClient.js +7 -7
  66. package/dist/components/questions/types/choose-then-answer-group/ChooseThenAnswerGroupCreator.js +8 -5
  67. package/dist/components/questions/types/cross-out-word-group/CrossOutWordGroupClient.js +2 -2
  68. package/dist/components/questions/types/cross-out-word-group/CrossOutWordGroupCreator.js +20 -16
  69. package/dist/components/questions/types/crossword-puzzle/CrosswordPuzzleClient.js +1 -1
  70. package/dist/components/questions/types/crossword-puzzle/CrosswordPuzzleCreator.js +66 -29
  71. package/dist/components/questions/types/fill-in-blank/FillInBlankClient.js +5 -5
  72. package/dist/components/questions/types/fill-in-blank/FillInBlankCreator.js +45 -37
  73. package/dist/components/questions/types/fill-in-blank-group/FillInBlankGroupCreator.js +24 -20
  74. package/dist/components/questions/types/fill-missing-words-in-grid/FillMissingWordsInGridClient.js +2 -2
  75. package/dist/components/questions/types/fill-missing-words-in-grid/FillMissingWordsInGridCreator.js +68 -65
  76. package/dist/components/questions/types/find-words-in-matrix/FindWordsInMatrixClient.js +1 -1
  77. package/dist/components/questions/types/find-words-in-matrix/FindWordsInMatrixCreator.js +45 -5
  78. package/dist/components/questions/types/label-the-picture/LabelThePictureClient.js +2 -2
  79. package/dist/components/questions/types/label-the-picture/LabelThePictureCreator.js +18 -14
  80. package/dist/components/questions/types/listen-and-choose-from-answer-group/ListenAndChooseFromAnswerGroupClient.js +2 -2
  81. package/dist/components/questions/types/listen-and-choose-from-answer-group/ListenAndChooseFromAnswerGroupCreator.js +30 -30
  82. package/dist/components/questions/types/listen-and-choose-objects-in-scene/ListenAndChooseObjectsInSceneClient.js +2 -2
  83. package/dist/components/questions/types/listen-and-choose-objects-in-scene/ListenAndChooseObjectsInSceneCreator.js +24 -31
  84. package/dist/components/questions/types/listen-and-drag-objects-into-scene/ListenAndDragObjectsIntoSceneClient.js +2 -2
  85. package/dist/components/questions/types/listen-and-drag-objects-into-scene/ListenAndDragObjectsIntoSceneCreator.js +29 -32
  86. package/dist/components/questions/types/listen-and-speak-answer/ListenAndSpeakAnswerClient.js +1 -1
  87. package/dist/components/questions/types/listen-and-speak-answer/ListenAndSpeakAnswerCreator.js +27 -29
  88. package/dist/components/questions/types/listen-and-speak-compare-images/ListenAndSpeakCompareImagesClient.js +1 -1
  89. package/dist/components/questions/types/listen-and-speak-compare-images/ListenAndSpeakCompareImagesCreator.js +11 -15
  90. package/dist/components/questions/types/listen-and-speak-image-group/ListenAndSpeakImageGroupClient.js +1 -1
  91. package/dist/components/questions/types/listen-and-speak-image-group/ListenAndSpeakImageGroupCreator.js +22 -24
  92. package/dist/components/questions/types/listen-and-speak-info-exchange/ListenAndSpeakInfoExchangeClient.js +1 -1
  93. package/dist/components/questions/types/listen-and-speak-info-exchange/ListenAndSpeakInfoExchangeCreator.js +17 -24
  94. package/dist/components/questions/types/listen-and-speak-odd-one-out/ListenAndSpeakOddOneOutClient.js +1 -1
  95. package/dist/components/questions/types/listen-and-speak-odd-one-out/ListenAndSpeakOddOneOutCreator.js +31 -31
  96. package/dist/components/questions/types/listen-and-speak-question-list/ListenAndSpeakQuestionListClient.js +1 -1
  97. package/dist/components/questions/types/listen-and-speak-question-list/ListenAndSpeakQuestionListCreator.js +11 -15
  98. package/dist/components/questions/types/listen-and-speak-with-story-images/ListenAndSpeakWithStoryImagesClient.js +1 -1
  99. package/dist/components/questions/types/listen-and-speak-with-story-images/ListenAndSpeakWithStoryImagesCreator.js +14 -15
  100. package/dist/components/questions/types/match-by-writing-answer/MatchByWritingAnswerClient.js +2 -2
  101. package/dist/components/questions/types/match-by-writing-answer/MatchByWritingAnswerCreator.js +27 -25
  102. package/dist/components/questions/types/match-columns-to-make-sentences/MatchColumnsToMakeSentencesClient.js +12 -12
  103. package/dist/components/questions/types/match-columns-to-make-sentences/MatchColumnsToMakeSentencesCreator.js +8 -5
  104. package/dist/components/questions/types/match-word-to-picture/MatchWordToPictureClient.js +8 -8
  105. package/dist/components/questions/types/match-word-to-picture/MatchWordToPictureCreator.js +25 -20
  106. package/dist/components/questions/types/match-word-to-picture-group/MatchWordToPictureGroupClient.js +1 -1
  107. package/dist/components/questions/types/match-word-to-picture-group/MatchWordToPictureGroupCreator.js +13 -10
  108. package/dist/components/questions/types/matching-with-lines-group/MatchingWithLinesGroupCreator.js +8 -5
  109. package/dist/components/questions/types/read-displayed-content/ReadDisplayedContentCreator.js +7 -11
  110. package/dist/components/questions/types/read-passage-and-answer-questions/ReadPassageAndAnswerQuestionsClient.js +1 -1
  111. package/dist/components/questions/types/read-passage-and-answer-questions/ReadPassageAndAnswerQuestionsCreator.js +15 -12
  112. package/dist/components/questions/types/sort-words-into-categories/SortWordsIntoCategoriesClient.js +1 -1
  113. package/dist/components/questions/types/sort-words-into-categories/SortWordsIntoCategoriesCreator.js +5 -5
  114. package/dist/components/questions/types/speaking-conversation/SpeakingConversationCreator.js +35 -39
  115. package/dist/components/questions/types/speaking-conversation/SpeakingConversationPreviewClient.js +2 -2
  116. package/dist/components/questions/types/speaking-cue-card/SpeakingCueCardClient.js +1 -1
  117. package/dist/components/questions/types/speaking-cue-card/SpeakingCueCardCreator.js +7 -8
  118. package/dist/components/questions/types/speaking-describe-image/SpeakingDescribeImageClient.js +1 -1
  119. package/dist/components/questions/types/speaking-describe-image/SpeakingDescribeImageCreator.js +30 -23
  120. package/dist/components/questions/types/spontaneous-qa/SpontaneousQAClient.js +1 -1
  121. package/dist/components/questions/types/spontaneous-qa/SpontaneousQACreator.js +26 -19
  122. package/dist/components/questions/types/spontaneous-qa-group/SpontaneousQaGroupClient.js +1 -1
  123. package/dist/components/questions/types/spontaneous-qa-group/SpontaneousQaGroupCreator.js +13 -11
  124. package/dist/components/questions/types/true-false/TrueFalseClient.js +2 -2
  125. package/dist/components/questions/types/true-false/TrueFalseCreator.js +35 -29
  126. package/dist/components/questions/types/true-false-correct-group/TrueFalseCorrectGroupClient.js +1 -1
  127. package/dist/components/questions/types/true-false-correct-group/TrueFalseCorrectGroupCreator.js +11 -7
  128. package/dist/components/questions/types/true-false-group/TrueFalseGroupClient.js +1 -1
  129. package/dist/components/questions/types/true-false-group/TrueFalseGroupCreator.js +11 -7
  130. package/dist/components/questions/types/word-fill-paragraph/WordFillParagraphClient.js +2 -2
  131. package/dist/components/questions/types/word-fill-paragraph/WordFillParagraphCreator.js +21 -56
  132. package/dist/components/questions/types/word-fill-structured-form/WordFillStructuredFormClient.js +1 -1
  133. package/dist/components/questions/types/word-fill-structured-form/WordFillStructuredFormCreator.js +30 -24
  134. package/dist/components/questions/types/word-order-and-match-group/WordOrderAndMatchGroupClient.js +3 -3
  135. package/dist/components/questions/types/word-order-and-match-group/WordOrderAndMatchGroupCreator.js +12 -14
  136. package/dist/components/questions/types/word-ordering/WordOrderingClient.js +1 -1
  137. package/dist/components/questions/types/word-ordering/WordOrderingCreator.js +27 -18
  138. package/dist/components/questions/types/word-ordering-group/WordOrderingGroupClient.js +7 -7
  139. package/dist/components/questions/types/word-ordering-group/WordOrderingGroupCreator.js +16 -13
  140. package/dist/components/questions/types/write-a-short-letter/WriteAShortLetterClient.js +2 -2
  141. package/dist/components/questions/types/write-a-short-letter/WriteAShortLetterCreator.js +34 -28
  142. package/dist/components/questions/types/write-correct-verb-form/WriteCorrectVerbFormClient.js +2 -2
  143. package/dist/components/questions/types/write-correct-verb-form/WriteCorrectVerbFormCreator.js +22 -22
  144. package/dist/components/questions/types/write-sentences/WriteSentencesCreator.js +21 -18
  145. package/dist/components/questions/types/write-short-paragraph/WriteShortParagraphClient.js +1 -1
  146. package/dist/components/questions/types/write-short-paragraph/WriteShortParagraphCreator.js +7 -4
  147. package/dist/components/questions/viewer/QuestionViewer.js +0 -98
  148. package/dist/components/results/renderers/review-question-body-movers.js +2 -26
  149. package/dist/components/results/review-data.js +0 -4
  150. package/dist/components/ui/points-input.d.ts +1 -0
  151. package/dist/components/ui/points-input.js +12 -2
  152. package/dist/shared/constants/question-skills.js +0 -11
  153. package/dist/shared/lib/i18n/messages/en/admin.d.ts +0 -3
  154. package/dist/shared/lib/i18n/messages/en/admin.js +0 -3
  155. package/dist/shared/lib/i18n/messages/en.d.ts +0 -3
  156. package/dist/shared/lib/i18n/messages/vi/admin.d.ts +0 -3
  157. package/dist/shared/lib/i18n/messages/vi/admin.js +0 -3
  158. package/dist/shared/lib/i18n/messages/vi.d.ts +0 -3
  159. package/dist/shared/lib/utils/question-reverse-transform.js +0 -19
  160. package/dist/shared/lib/utils/question-transform-types.d.ts +1 -23
  161. package/dist/shared/lib/utils/question-transform-validation.js +0 -40
  162. package/dist/shared/lib/utils/question-transform.js +0 -9
  163. package/dist/shared/types/common.types.d.ts +1 -1
  164. package/dist/shared/types/entities/question.types.d.ts +1 -11
  165. package/dist/shared/types/questions/index.d.ts +0 -3
  166. package/dist/shared/types/questions/index.js +0 -6
  167. package/package.json +1 -1
  168. package/dist/components/exams/take/components/question-renderers/MoversFillWordHintRenderer.d.ts +0 -2
  169. package/dist/components/exams/take/components/question-renderers/MoversFillWordHintRenderer.js +0 -131
  170. package/dist/components/exams/take/components/question-renderers/MoversPictureChooseRenderer.d.ts +0 -2
  171. package/dist/components/exams/take/components/question-renderers/MoversPictureChooseRenderer.js +0 -76
  172. package/dist/components/exams/take/components/question-renderers/MoversPictureFillBlankChooseRenderer.d.ts +0 -2
  173. package/dist/components/exams/take/components/question-renderers/MoversPictureFillBlankChooseRenderer.js +0 -68
  174. package/dist/components/exams/take/components/speaking/renderers/SpeakingGNInterviewRenderer.d.ts +0 -16
  175. package/dist/components/exams/take/components/speaking/renderers/SpeakingGNInterviewRenderer.js +0 -212
  176. package/dist/components/questions/_shared/types/gn-speaking-interview.type.d.ts +0 -32
  177. package/dist/components/questions/_shared/types/gn-speaking-interview.type.js +0 -1
  178. package/dist/components/questions/_shared/types/look-picture-fill-word-hint.type.d.ts +0 -47
  179. package/dist/components/questions/_shared/types/look-picture-fill-word-hint.type.js +0 -1
  180. package/dist/components/questions/types/gn-speaking-interview/GNSpeakingInterviewCreator.d.ts +0 -2
  181. package/dist/components/questions/types/gn-speaking-interview/GNSpeakingInterviewCreator.js +0 -262
  182. package/dist/components/questions/types/gn-speaking-interview/register.d.ts +0 -2
  183. package/dist/components/questions/types/gn-speaking-interview/register.js +0 -121
  184. package/dist/components/questions/types/gn-speaking-interview/transform.d.ts +0 -2
  185. package/dist/components/questions/types/gn-speaking-interview/transform.js +0 -48
  186. package/dist/components/questions/types/look-picture-choose-correct-answer/LookPictureChooseCorrectAnswerClient.d.ts +0 -20
  187. package/dist/components/questions/types/look-picture-choose-correct-answer/LookPictureChooseCorrectAnswerClient.js +0 -72
  188. package/dist/components/questions/types/look-picture-choose-correct-answer/LookPictureChooseCorrectAnswerCreator.d.ts +0 -15
  189. package/dist/components/questions/types/look-picture-choose-correct-answer/LookPictureChooseCorrectAnswerCreator.js +0 -230
  190. package/dist/components/questions/types/look-picture-choose-correct-answer/index.d.ts +0 -1
  191. package/dist/components/questions/types/look-picture-choose-correct-answer/index.js +0 -1
  192. package/dist/components/questions/types/look-picture-choose-correct-answer/register.d.ts +0 -2
  193. package/dist/components/questions/types/look-picture-choose-correct-answer/register.js +0 -66
  194. package/dist/components/questions/types/look-picture-choose-correct-answer/transform.d.ts +0 -2
  195. package/dist/components/questions/types/look-picture-choose-correct-answer/transform.js +0 -42
  196. package/dist/components/questions/types/look-picture-fill-blank-choose-answer/LookPictureFillBlankChooseAnswerClient.d.ts +0 -6
  197. package/dist/components/questions/types/look-picture-fill-blank-choose-answer/LookPictureFillBlankChooseAnswerClient.js +0 -96
  198. package/dist/components/questions/types/look-picture-fill-blank-choose-answer/LookPictureFillBlankChooseAnswerCreator.d.ts +0 -15
  199. package/dist/components/questions/types/look-picture-fill-blank-choose-answer/LookPictureFillBlankChooseAnswerCreator.js +0 -238
  200. package/dist/components/questions/types/look-picture-fill-blank-choose-answer/index.d.ts +0 -2
  201. package/dist/components/questions/types/look-picture-fill-blank-choose-answer/index.js +0 -2
  202. package/dist/components/questions/types/look-picture-fill-blank-choose-answer/register.d.ts +0 -2
  203. package/dist/components/questions/types/look-picture-fill-blank-choose-answer/register.js +0 -76
  204. package/dist/components/questions/types/look-picture-fill-blank-choose-answer/transform.d.ts +0 -2
  205. package/dist/components/questions/types/look-picture-fill-blank-choose-answer/transform.js +0 -19
  206. package/dist/components/questions/types/look-picture-fill-word-hint/LookPictureFillWordHintClient.d.ts +0 -7
  207. package/dist/components/questions/types/look-picture-fill-word-hint/LookPictureFillWordHintClient.js +0 -134
  208. package/dist/components/questions/types/look-picture-fill-word-hint/LookPictureFillWordHintCreator.d.ts +0 -3
  209. package/dist/components/questions/types/look-picture-fill-word-hint/LookPictureFillWordHintCreator.js +0 -617
  210. package/dist/components/questions/types/look-picture-fill-word-hint/index.d.ts +0 -2
  211. package/dist/components/questions/types/look-picture-fill-word-hint/index.js +0 -2
  212. package/dist/components/questions/types/look-picture-fill-word-hint/register.d.ts +0 -2
  213. package/dist/components/questions/types/look-picture-fill-word-hint/register.js +0 -82
  214. package/dist/components/questions/types/look-picture-fill-word-hint/transform.d.ts +0 -2
  215. package/dist/components/questions/types/look-picture-fill-word-hint/transform.js +0 -29
  216. package/dist/shared/lib/validation/look-picture-choose-correct-answer.validation.d.ts +0 -9
  217. package/dist/shared/lib/validation/look-picture-choose-correct-answer.validation.js +0 -12
  218. package/dist/shared/lib/validation/look-picture-fill-blank-choose-answer.validation.d.ts +0 -17
  219. package/dist/shared/lib/validation/look-picture-fill-blank-choose-answer.validation.js +0 -47
  220. package/dist/shared/types/questions/look-picture-choose-correct-answer.d.ts +0 -38
  221. package/dist/shared/types/questions/look-picture-choose-correct-answer.js +0 -5
  222. package/dist/shared/types/questions/look-picture-fill-blank-choose-answer.d.ts +0 -63
  223. package/dist/shared/types/questions/look-picture-fill-blank-choose-answer.js +0 -9
  224. package/dist/shared/types/questions/look-picture-fill-word-hint.d.ts +0 -57
  225. package/dist/shared/types/questions/look-picture-fill-word-hint.js +0 -7
@@ -1,42 +0,0 @@
1
- // LOOK_PICTURE_CHOOSE_CORRECT_ANSWER - flattened structure (always 1 question, no question text)
2
- // NEW FORMAT: content { imageUrl, options: string[] }, correct_answer { answer: number }
3
- export const transformLookPictureChooseCorrectAnswer = (question) => {
4
- const answerData = question.answer;
5
- // NEW format: { imageUrl, options: string[], correctAnswer: number, points }
6
- if (answerData?.imageUrl !== undefined || answerData?.options) {
7
- const options = (answerData.options || []).map((opt) => typeof opt === 'string' ? opt : (opt.text || ''));
8
- let correctAnswer = -1;
9
- if (typeof answerData.correctAnswer === 'number') {
10
- correctAnswer = answerData.correctAnswer;
11
- }
12
- else if (Array.isArray(answerData.options)) {
13
- correctAnswer = answerData.options.findIndex((opt) => opt.isCorrect === true);
14
- }
15
- return {
16
- apiContent: {
17
- imageUrl: answerData.imageUrl || '',
18
- options,
19
- },
20
- apiCorrectAnswer: {
21
- answer: correctAnswer,
22
- },
23
- };
24
- }
25
- // LEGACY format with subQuestions - convert to new format
26
- const legacyData = answerData;
27
- if (!legacyData?.subQuestions || legacyData.subQuestions.length === 0) {
28
- return { apiContent: {}, apiCorrectAnswer: {} };
29
- }
30
- const sq = legacyData.subQuestions[0];
31
- const options = (sq.options || []).map((opt) => opt.text || '');
32
- const correctAnswerIndex = sq.options?.findIndex((opt) => opt.isCorrect) ?? -1;
33
- return {
34
- apiContent: {
35
- imageUrl: sq.imageUrl || '',
36
- options,
37
- },
38
- apiCorrectAnswer: {
39
- answer: correctAnswerIndex,
40
- },
41
- };
42
- };
@@ -1,6 +0,0 @@
1
- import { LookAndChooseClientProps } from '../../_shared/types/look-and-choose-answer.type';
2
- interface ExtendedProps extends LookAndChooseClientProps {
3
- explanation?: string;
4
- }
5
- export declare function LookPictureFillBlankChooseAnswerClient({ questionData, onSubmit, isReviewMode, userAnswer, correctAnswer, explanation, }: ExtendedProps): import("react").JSX.Element;
6
- export {};
@@ -1,96 +0,0 @@
1
- 'use client';
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { useState, useEffect } from 'react';
4
- import { Check, X, ImageIcon, Lightbulb } from 'lucide-react';
5
- import { usePresignedFileUrl } from '../../../../shared/lib/hooks';
6
- import { cn } from '../../../../shared/lib/utils';
7
- export function LookPictureFillBlankChooseAnswerClient({ questionData, onSubmit, isReviewMode = false, userAnswer = '', correctAnswer = '', explanation, }) {
8
- // Handle both flat structure and subQuestions structure
9
- const flatData = questionData.subQuestions?.[0]
10
- ? {
11
- imageUrl: questionData.subQuestions[0].imageUrl,
12
- question: questionData.subQuestions[0].question || '',
13
- options: questionData.subQuestions[0].options.map(opt => ({ id: opt.id, text: opt.text })),
14
- }
15
- : {
16
- imageUrl: questionData.imageUrl || '',
17
- question: questionData.question || '',
18
- options: questionData.options || [],
19
- };
20
- // State for selected answer
21
- const initialAnswer = typeof userAnswer === 'string' ? userAnswer : userAnswer?.[questionData.subQuestions?.[0]?.id || ''] || '';
22
- const [selectedAnswer, setSelectedAnswer] = useState(initialAnswer);
23
- // Get presigned URL for image
24
- const { previewUrl: imagePreviewUrl, isLoading } = usePresignedFileUrl(flatData.imageUrl || '');
25
- // Check if answer is correct
26
- const isAnswerCorrect = selectedAnswer === correctAnswer;
27
- const hasAnswered = selectedAnswer !== '';
28
- // Sync userAnswer when it changes
29
- useEffect(() => {
30
- const answer = typeof userAnswer === 'string' ? userAnswer : userAnswer?.[questionData.subQuestions?.[0]?.id || ''] || '';
31
- setSelectedAnswer(answer);
32
- }, [userAnswer, questionData.subQuestions]);
33
- // Handle answer change
34
- const handleAnswerChange = (optionId) => {
35
- if (isReviewMode)
36
- return;
37
- setSelectedAnswer(optionId);
38
- onSubmit?.(optionId);
39
- };
40
- // Replace {0} with styled blank for display
41
- const renderQuestionWithBlank = () => {
42
- const parts = flatData.question.split('{0}');
43
- return (_jsx("span", { className: "text-gray-800", children: parts.map((part, idx) => (_jsxs("span", { children: [part, idx < parts.length - 1 && (_jsx("span", { className: "inline-flex items-center mx-1", children: _jsx("span", { className: "inline-block min-w-[80px] border-b-2 border-dashed border-blue-400 bg-blue-50/50 px-3 py-0.5 text-center text-blue-600 font-medium rounded", children: "___________" }) }))] }, idx))) }));
44
- };
45
- // Replace {0} with selected answer for preview
46
- const renderQuestionWithAnswer = (answerText) => {
47
- const parts = flatData.question.split('{0}');
48
- return (_jsx("span", { children: parts.map((part, idx) => (_jsxs("span", { children: [part, idx < parts.length - 1 && (_jsx("span", { className: "inline-flex items-center mx-1", children: _jsx("span", { className: "inline-block rounded-md bg-gradient-to-r from-blue-500 to-indigo-500 px-3 py-0.5 text-white font-semibold shadow-sm", children: answerText }) }))] }, idx))) }));
49
- };
50
- // Get selected option text
51
- const getSelectedOptionText = () => {
52
- const option = flatData.options.find(opt => opt.id === selectedAnswer);
53
- return option?.text || '';
54
- };
55
- // Get correct option text
56
- const getCorrectOptionText = () => {
57
- const option = flatData.options.find(opt => opt.id === correctAnswer);
58
- return option?.text || '';
59
- };
60
- return (_jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm transition-all hover:shadow-md", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-gray-100 bg-gradient-to-r from-gray-50 to-white px-5 py-3", 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-purple-500 to-indigo-600 text-white shadow-sm", children: _jsx(ImageIcon, { className: "h-4 w-4" }) }), _jsx("span", { className: "text-sm font-medium text-gray-700", children: "Nh\u00ECn tranh, \u0111i\u1EC1n t\u1EEB v\u00E0o ch\u1ED7 tr\u1ED1ng" })] }), isReviewMode && hasAnswered && (_jsxs("div", { className: cn('flex items-center gap-1.5 rounded-full px-3 py-1 text-xs font-semibold', isAnswerCorrect
61
- ? 'bg-green-100 text-green-700'
62
- : '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: "p-5 space-y-5", children: [(imagePreviewUrl || flatData.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-purple-500" }) })) : (
63
- /* eslint-disable-next-line @next/next/no-img-element */
64
- _jsx("img", { src: imagePreviewUrl || flatData.imageUrl, alt: "Question illustration", className: "max-h-56 w-auto object-contain rounded-md", onError: (e) => {
65
- const target = e.target;
66
- target.onerror = null;
67
- target.src = '/images/placeholder-image.svg';
68
- } })) })] })), _jsx("div", { className: "rounded-lg bg-gradient-to-r from-gray-50 to-slate-50 p-4 border border-gray-100", children: _jsxs("div", { className: "flex items-start gap-3", children: [_jsx("div", { className: "flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-full bg-purple-100 text-purple-600", children: _jsx("span", { className: "text-xs font-bold", children: "?" }) }), _jsxs("div", { className: "flex-1", children: [_jsx("span", { className: "text-xs font-medium text-gray-500 uppercase tracking-wider", children: "C\u00E2u h\u1ECFi" }), _jsx("p", { className: "mt-1 text-base font-medium leading-relaxed", children: renderQuestionWithBlank() })] })] }) }), _jsxs("div", { className: "space-y-2.5", children: [_jsx("span", { className: "text-xs font-medium text-gray-500 uppercase tracking-wider", children: "Ch\u1ECDn \u0111\u00E1p \u00E1n" }), flatData.options.map((option, optIndex) => {
69
- const isSelected = selectedAnswer === option.id;
70
- const isCorrectOption = option.id === correctAnswer;
71
- const letter = String.fromCharCode(65 + optIndex);
72
- return _jsxs("button", { type: "button", onClick: () => handleAnswerChange(option.id), disabled: isReviewMode, className: cn('group flex w-full items-center gap-3 rounded-lg border-2 p-3.5 text-left transition-all', isReviewMode
73
- ? isCorrectOption
74
- ? 'border-green-400 bg-green-50'
75
- : isSelected
76
- ? 'border-red-400 bg-red-50'
77
- : 'border-gray-200 bg-gray-50'
78
- : isSelected
79
- ? 'border-purple-500 bg-purple-50 shadow-sm ring-2 ring-purple-100'
80
- : 'border-gray-200 bg-white hover:border-gray-300 hover:bg-gray-50 hover:shadow-sm'), children: [_jsx("div", { className: cn('flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-lg text-sm font-bold transition-all', isReviewMode
81
- ? isCorrectOption
82
- ? 'bg-green-500 text-white'
83
- : isSelected
84
- ? 'bg-red-500 text-white'
85
- : 'bg-gray-200 text-gray-600'
86
- : isSelected
87
- ? 'bg-gradient-to-br from-purple-500 to-indigo-600 text-white shadow-sm'
88
- : 'bg-gray-100 text-gray-600 group-hover:bg-gray-200'), children: letter }), _jsx("div", { className: "flex-1", children: _jsx("span", { className: cn('text-sm font-medium', isReviewMode && isCorrectOption
89
- ? 'text-green-800'
90
- : isReviewMode && isSelected && !isCorrectOption
91
- ? 'text-red-800'
92
- : 'text-gray-700'), children: option.text }) }), isReviewMode && (isCorrectOption || isSelected) && (_jsx("div", { className: cn('flex h-6 w-6 items-center justify-center rounded-full', isCorrectOption ? 'bg-green-500' : 'bg-red-500'), children: isCorrectOption
93
- ? _jsx(Check, { className: "h-4 w-4 text-white" })
94
- : _jsx(X, { className: "h-4 w-4 text-white" }) }))] }, option.id);
95
- })] }), hasAnswered && !isReviewMode && (_jsxs("div", { className: "rounded-lg bg-gradient-to-r from-purple-50 to-indigo-50 p-4 border border-purple-100", children: [_jsxs("div", { className: "flex items-center gap-2 mb-2", children: [_jsx("div", { className: "flex h-5 w-5 items-center justify-center rounded-full bg-purple-500 text-white", children: _jsx(Check, { className: "h-3 w-3" }) }), _jsx("span", { className: "text-xs font-semibold text-purple-700 uppercase tracking-wider", children: "Preview c\u00E2u tr\u1EA3 l\u1EDDi" })] }), _jsx("p", { className: "text-sm text-gray-800 leading-relaxed", children: renderQuestionWithAnswer(getSelectedOptionText()) })] })), isReviewMode && hasAnswered && !isAnswerCorrect && (_jsxs("div", { className: "rounded-lg bg-gradient-to-r from-green-50 to-emerald-50 p-4 border border-green-200", children: [_jsxs("div", { className: "flex items-center gap-2 mb-2", children: [_jsx("div", { className: "flex h-5 w-5 items-center justify-center rounded-full bg-green-500 text-white", children: _jsx(Check, { className: "h-3 w-3" }) }), _jsx("span", { className: "text-xs font-semibold text-green-700 uppercase tracking-wider", children: "\u0110\u00E1p \u00E1n \u0111\u00FAng" })] }), _jsx("p", { className: "text-sm text-gray-800 leading-relaxed", children: renderQuestionWithAnswer(getCorrectOptionText()) })] })), !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: "Vui l\u00F2ng ch\u1ECDn m\u1ED9t \u0111\u00E1p \u00E1n" })] })), isReviewMode && explanation && (_jsx("div", { className: "rounded-lg bg-gradient-to-r from-blue-50 to-indigo-50 p-4 border border-blue-200", children: _jsxs("div", { className: "flex items-start gap-3", children: [_jsx("div", { className: "flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-full bg-blue-500 text-white", children: _jsx(Lightbulb, { className: "h-3.5 w-3.5" }) }), _jsxs("div", { className: "flex-1", children: [_jsx("span", { className: "text-xs font-semibold text-blue-700 uppercase tracking-wider", children: "Gi\u1EA3i th\u00EDch" }), _jsx("p", { className: "mt-1 text-sm text-gray-700 leading-relaxed", children: explanation })] })] }) }))] })] }));
96
- }
@@ -1,15 +0,0 @@
1
- interface LookPictureFillBlankChooseAnswerCreatorProps {
2
- initialData?: any;
3
- onSave?: (data: any) => void;
4
- onCancel?: () => void;
5
- onChange?: (data: any) => void;
6
- onUnsavedChangesChange?: (hasUnsavedChanges: boolean) => void;
7
- optionCount?: number;
8
- validationRef?: React.MutableRefObject<{
9
- triggerValidation: () => Promise<boolean>;
10
- getFormData?: () => any;
11
- } | null>;
12
- }
13
- export declare function LookPictureFillBlankChooseAnswerCreator(props: LookPictureFillBlankChooseAnswerCreatorProps): import("react").JSX.Element;
14
- export declare const LookPictureFillBlankChooseAnswer: typeof LookPictureFillBlankChooseAnswerCreator;
15
- export {};
@@ -1,238 +0,0 @@
1
- 'use client';
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { useState, useRef, useEffect } from 'react';
4
- import { Button } from '../../../../components/ui/button';
5
- import { Label } from '../../../../components/ui/label';
6
- import { Input } from '../../../../components/ui/input';
7
- import { Textarea } from '../../../../components/ui/textarea';
8
- import { Card, CardContent, CardHeader, CardTitle } from '../../../../components/ui/card';
9
- import { PointsInput } from '../../../../components/ui/points-input';
10
- import { RadioGroup } from '../../../../components/ui/radio-group';
11
- import { Pencil, Eye, Plus, Trash2, ImageIcon, CheckCircle2, Lightbulb, MessageSquare, BookOpen } from 'lucide-react';
12
- import { FileUpload } from '../../../../components/ui/file-upload';
13
- import { ImagePreview } from '../../../../components/ui/image-preview';
14
- import { usePresignedFileUrl, useDebouncedCallback } from '../../../../shared/lib/hooks';
15
- import { LookPictureFillBlankChooseAnswerClient } from './LookPictureFillBlankChooseAnswerClient';
16
- import { useReactHookForm } from '../../../../shared/lib/hooks/useReactHookForm';
17
- import { lookPictureFillBlankChooseAnswerFormSchema, } from '../../../../shared/lib/validation/look-picture-fill-blank-choose-answer.validation';
18
- function LookPictureFillBlankChooseAnswerContent({ initialData, onSave, onCancel, onChange, onUnsavedChangesChange, optionCount = 4, validationRef, }) {
19
- // Helper to normalize options - ensure they are always string[]
20
- const normalizeOptions = (opts, defaultCount) => {
21
- if (!opts || opts.length === 0) {
22
- return Array.from({ length: defaultCount }, () => '');
23
- }
24
- // Options should always be string[] - convert each to string for safety
25
- return opts.map((opt) => String(opt || ''));
26
- };
27
- // Use react-hook-form with Zod validation
28
- // initialData comes from the router in flat format:
29
- // { imageUrl, question, options: ["A", "B", "C"], correctAnswer: 1, explanation, points }
30
- const apiData = initialData;
31
- const rawOptions = apiData?.options || [];
32
- const defaultValues = {
33
- imageUrl: apiData?.imageUrl || '',
34
- question: apiData?.question || '',
35
- options: normalizeOptions(rawOptions, optionCount),
36
- correctAnswer: apiData?.correctAnswer ?? -1,
37
- explanation: apiData?.explanation || '',
38
- points: apiData?.points ?? 1,
39
- };
40
- const form = useReactHookForm({
41
- defaultValues,
42
- zodSchema: lookPictureFillBlankChooseAnswerFormSchema,
43
- mode: 'onBlur',
44
- });
45
- // UI State
46
- const [isClientMode, setIsClientMode] = useState(false);
47
- const [imagePreviewUrl, setImagePreviewUrl] = useState('');
48
- // Track previous initialData to detect changes
49
- const previousInitialDataRef = useRef(initialData);
50
- const isInitializingRef = useRef(false);
51
- const onChangeRef = useRef(onChange);
52
- const previousPayloadRef = useRef('');
53
- // Update onChange ref when it changes
54
- useEffect(() => {
55
- onChangeRef.current = onChange;
56
- }, [onChange]);
57
- // Debounced onChange callback to prevent excessive re-renders
58
- const debouncedOnChange = useDebouncedCallback((data) => {
59
- onChangeRef.current?.(data);
60
- }, 300);
61
- // Watch form values - MUST be declared before useEffects that use them
62
- const watchedImageUrl = form.watch('imageUrl');
63
- const watchedQuestion = form.watch('question');
64
- const watchedOptions = form.watch('options');
65
- const watchedCorrectAnswer = form.watch('correctAnswer');
66
- const watchedExplanation = form.watch('explanation');
67
- const watchedPoints = form.watch('points');
68
- // Custom hooks for presigned URL
69
- const { previewUrl: generatedPreviewUrl } = usePresignedFileUrl(watchedImageUrl);
70
- const displayPreviewUrl = imagePreviewUrl || generatedPreviewUrl || '';
71
- // NOTE: Status tracking is now handled entirely by ExamCreator via localStorage
72
- // No need to track originalSavedData in Creator component
73
- // NOTE: Unsaved changes tracking has been REMOVED from Creator component
74
- // Status is now managed entirely by ExamCreator via localStorage (exam_question_status_{id})
75
- // The onChange callback triggers handleQuestionDraftChange in ExamCreator which updates localStorage
76
- // Expose validation function and getFormData via ref
77
- useEffect(() => {
78
- if (validationRef) {
79
- validationRef.current = {
80
- triggerValidation: async () => {
81
- const result = await form.trigger();
82
- // Also check correctAnswer
83
- if (watchedCorrectAnswer === -1) {
84
- form.setError('correctAnswer', { message: 'Vui lòng chọn đáp án đúng!' });
85
- return false;
86
- }
87
- return result;
88
- },
89
- // Return current form data immediately (not debounced)
90
- // This allows parent to get fresh data at save time
91
- getFormData: () => ({
92
- imageUrl: watchedImageUrl,
93
- question: watchedQuestion,
94
- options: watchedOptions,
95
- correctAnswer: watchedCorrectAnswer,
96
- explanation: watchedExplanation,
97
- points: watchedPoints,
98
- }),
99
- };
100
- }
101
- }, [validationRef, form, watchedCorrectAnswer, watchedImageUrl, watchedQuestion, watchedOptions, watchedExplanation, watchedPoints]);
102
- // Sync state from initialData when it changes from EXTERNAL sources only
103
- // initialData comes from the router already in flat format
104
- useEffect(() => {
105
- const apiData = initialData;
106
- const normalizedInitialData = {
107
- imageUrl: apiData?.imageUrl || '',
108
- question: apiData?.question || '',
109
- options: normalizeOptions(apiData?.options || [], optionCount),
110
- correctAnswer: apiData?.correctAnswer ?? -1,
111
- explanation: apiData?.explanation || '',
112
- points: apiData?.points ?? 1,
113
- };
114
- const initialDataChanged = JSON.stringify(previousInitialDataRef.current) !== JSON.stringify(initialData);
115
- if (!initialDataChanged) {
116
- return;
117
- }
118
- previousInitialDataRef.current = initialData;
119
- const currentFormData = {
120
- imageUrl: watchedImageUrl,
121
- question: watchedQuestion,
122
- options: watchedOptions,
123
- correctAnswer: watchedCorrectAnswer,
124
- explanation: watchedExplanation,
125
- points: watchedPoints,
126
- };
127
- const isEchoFromOnChange = currentFormData.imageUrl === normalizedInitialData.imageUrl &&
128
- currentFormData.question === normalizedInitialData.question &&
129
- currentFormData.correctAnswer === normalizedInitialData.correctAnswer &&
130
- currentFormData.points === normalizedInitialData.points &&
131
- currentFormData.explanation === normalizedInitialData.explanation &&
132
- JSON.stringify(currentFormData.options) === JSON.stringify(normalizedInitialData.options);
133
- const wasJustSaved = initialData?.isCompleted === true;
134
- if (isEchoFromOnChange && wasJustSaved) {
135
- return;
136
- }
137
- const shouldReset = !isEchoFromOnChange &&
138
- normalizedInitialData.correctAnswer !== currentFormData.correctAnswer;
139
- if (shouldReset && normalizedInitialData.correctAnswer !== -1) {
140
- isInitializingRef.current = true;
141
- form.reset(normalizedInitialData);
142
- setTimeout(() => {
143
- isInitializingRef.current = false;
144
- }, 150);
145
- }
146
- }, [initialData, optionCount, form, watchedImageUrl, watchedQuestion, watchedOptions, watchedCorrectAnswer, watchedExplanation, watchedPoints]);
147
- // Convert to payload format and call onChange
148
- // NOTE: We need to stringify options for proper change detection since array reference doesn't change
149
- const watchedOptionsString = JSON.stringify(watchedOptions);
150
- useEffect(() => {
151
- if (isInitializingRef.current) {
152
- const currentPayload = JSON.stringify({
153
- imageUrl: watchedImageUrl,
154
- question: watchedQuestion,
155
- options: watchedOptions,
156
- correctAnswer: watchedCorrectAnswer,
157
- explanation: watchedExplanation,
158
- points: watchedPoints,
159
- });
160
- previousPayloadRef.current = currentPayload;
161
- return;
162
- }
163
- const payload = {
164
- imageUrl: watchedImageUrl,
165
- question: watchedQuestion,
166
- options: watchedOptions,
167
- correctAnswer: watchedCorrectAnswer,
168
- explanation: watchedExplanation,
169
- points: watchedPoints,
170
- };
171
- const payloadString = JSON.stringify(payload);
172
- if (previousPayloadRef.current !== payloadString) {
173
- previousPayloadRef.current = payloadString;
174
- debouncedOnChange(payload);
175
- }
176
- }, [
177
- watchedImageUrl,
178
- watchedQuestion,
179
- watchedOptionsString, // Use stringified version for proper change detection
180
- watchedCorrectAnswer,
181
- watchedExplanation,
182
- watchedPoints,
183
- ]);
184
- // Handle save with validation
185
- const handleSave = form.handleSubmit((data) => {
186
- // Additional validation for correctAnswer
187
- if (data.correctAnswer === -1 || data.correctAnswer >= data.options.length) {
188
- form.setError('correctAnswer', { message: 'Vui lòng chọn đáp án đúng!' });
189
- return;
190
- }
191
- // NOTE: Save status is now managed by ExamCreator via localStorage
192
- onSave?.(data);
193
- });
194
- // Client mode preview
195
- if (isClientMode) {
196
- const questionData = {
197
- imageUrl: displayPreviewUrl || watchedImageUrl,
198
- question: watchedQuestion,
199
- options: watchedOptions.map((text, index) => ({ id: `opt-${index}`, text })),
200
- };
201
- return (_jsx("div", { className: "space-y-6", children: _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { children: [_jsx(CardTitle, { children: "Nh\u00ECn tranh, \u0111i\u1EC1n ch\u1ED7 tr\u1ED1ng (Preview)" }), _jsx("p", { className: "mt-1 text-sm text-gray-500", children: "Ch\u1EBF \u0111\u1ED9 xem tr\u01B0\u1EDBc - Hi\u1EC3n th\u1ECB \u0111\u00E1p \u00E1n \u0111\u00FAng" })] }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => setIsClientMode(false), children: [_jsx(Pencil, { className: "mr-2 h-4 w-4" }), "Quay l\u1EA1i Edit Mode"] })] }) }), _jsx(CardContent, { children: _jsx(LookPictureFillBlankChooseAnswerClient, { questionData: questionData, isReviewMode: true, userAnswer: watchedCorrectAnswer !== -1 ? `opt-${watchedCorrectAnswer}` : undefined, correctAnswer: watchedCorrectAnswer !== -1 ? `opt-${watchedCorrectAnswer}` : undefined, explanation: watchedExplanation }) })] }) }));
202
- }
203
- // Creator UI
204
- return (_jsxs("div", { className: "space-y-6", children: [_jsxs(Card, { className: "overflow-hidden border-0 bg-white shadow-lg shadow-blue-100/50", children: [_jsx("div", { className: "h-1.5 bg-gradient-to-r from-blue-500 via-indigo-500 to-purple-500" }), _jsx(CardHeader, { className: "pb-4", children: _jsxs("div", { className: "flex items-center justify-between", children: [_jsx("div", { className: "space-y-1", children: _jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "flex h-10 w-10 items-center justify-center rounded-xl bg-gradient-to-br from-blue-500 to-indigo-600 shadow-md", children: _jsx(ImageIcon, { className: "h-5 w-5 text-white" }) }), _jsxs("div", { children: [_jsx(CardTitle, { className: "text-xl font-bold text-gray-900", children: "Nh\u00ECn tranh, \u0111i\u1EC1n ch\u1ED7 tr\u1ED1ng" }), _jsx("p", { className: "text-sm text-gray-500", children: "T\u1EA1o c\u00E2u h\u1ECFi v\u1EDBi h\u00ECnh \u1EA3nh v\u00E0 c\u00E1c l\u1EF1a ch\u1ECDn \u0111\u00E1p \u00E1n" })] })] }) }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => setIsClientMode(true), disabled: !watchedQuestion || 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-6", children: [_jsx("div", { className: "rounded-xl border border-gray-200 bg-gradient-to-r from-gray-50 to-slate-50 p-4", children: _jsx(PointsInput, { ...form.register('points', { valueAsNumber: true }), error: form.hasError('points') ? form.getFieldError('points') : undefined }) }), _jsx("div", { className: "rounded-xl border border-blue-200 bg-gradient-to-r from-blue-50 to-indigo-50 p-5 shadow-sm", children: _jsxs("div", { className: "flex items-start gap-3", children: [_jsx("div", { className: "flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-lg bg-blue-100", children: _jsx(Lightbulb, { className: "h-4 w-4 text-blue-600" }) }), _jsxs("div", { className: "text-sm text-blue-900", children: [_jsx("p", { className: "font-semibold text-blue-800", children: "H\u01B0\u1EDBng d\u1EABn t\u1EA1o c\u00E2u h\u1ECFi" }), _jsxs("ul", { className: "mt-2 space-y-1.5 text-blue-700", children: [_jsxs("li", { className: "flex items-center gap-2", children: [_jsx("span", { className: "h-1.5 w-1.5 rounded-full bg-blue-400" }), "S\u1EED d\u1EE5ng ", _jsx("code", { className: "mx-1 rounded bg-blue-100 px-1.5 py-0.5 font-mono text-blue-800", children: '{0}' }), " \u0111\u1EC3 \u0111\u00E1nh d\u1EA5u ch\u1ED7 tr\u1ED1ng"] }), _jsxs("li", { className: "flex items-center gap-2", children: [_jsx("span", { className: "h-1.5 w-1.5 rounded-full bg-blue-400" }), "V\u00ED d\u1EE5: \u201CThe cat is ", '{0}', " the chair.\u201D v\u1EDBi \u0111\u00E1p \u00E1n \u201Cunder\u201D"] })] })] })] }) }), _jsxs("div", { children: [_jsx(FileUpload, { id: "question-image", label: "H\u00ECnh \u1EA3nh", accept: "image/*", value: watchedImageUrl, onChange: (url) => {
205
- form.setValue('imageUrl', url, { shouldValidate: true });
206
- }, onPresignedUrlChange: setImagePreviewUrl, maxSize: 5, placeholder: "Upload \u1EA3nh ho\u1EB7c paste URL", required: true, 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" }))] }), _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" }), "C\u00E2u h\u1ECFi ", _jsx("span", { className: "text-red-500", children: "*" })] }), _jsx(Textarea, { id: "question", ...form.register('question'), placeholder: "V\u00ED d\u1EE5: The cat is {0} the chair.", rows: 2, className: `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' : ''}` }), form.hasError('question') && (_jsx("p", { className: "mt-1 text-sm text-red-600", children: form.getFieldError('question') })), _jsxs("p", { className: "mt-1 text-xs text-gray-500", children: ["S\u1EED d\u1EE5ng ", '{0}', " \u0111\u1EC3 \u0111\u00E1nh d\u1EA5u ch\u1ED7 tr\u1ED1ng"] })] })] })] }), _jsxs(Card, { className: "overflow-hidden border-0 bg-white shadow-lg shadow-indigo-100/50", children: [_jsx("div", { className: "h-1 bg-gradient-to-r from-indigo-400 to-purple-400" }), _jsx(CardHeader, { className: "pb-4", 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-lg font-semibold text-gray-800", children: ["C\u00E1c l\u1EF1a ch\u1ECDn ", _jsxs("span", { className: "text-sm font-normal text-gray-500", children: ["(", watchedOptions.length, " \u0111\u00E1p \u00E1n)"] })] })] }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => {
207
- const currentOptions = form.getValues('options');
208
- form.setValue('options', [...currentOptions, '']);
209
- }, className: "gap-2 border-green-200 text-green-600 hover:bg-green-50 hover:text-green-700", children: [_jsx(Plus, { className: "h-4 w-4" }), "Th\u00EAm \u0111\u00E1p \u00E1n"] })] }) }), _jsxs(CardContent, { className: "space-y-4", children: [_jsx(RadioGroup, { value: watchedCorrectAnswer.toString(), onValueChange: (value) => {
210
- form.setValue('correctAnswer', Number(value), { shouldValidate: true });
211
- }, children: watchedOptions.map((option, index) => {
212
- const optionError = form.formState.errors.options?.[index];
213
- const isCorrect = watchedCorrectAnswer === index;
214
- const optionLetter = String.fromCharCode(65 + index);
215
- return (_jsx("div", { className: `group relative rounded-xl border-2 p-4 transition-all duration-200 ${isCorrect
216
- ? 'border-green-300 bg-gradient-to-r from-green-50 to-emerald-50 shadow-md shadow-green-100'
217
- : 'border-gray-200 bg-white hover:border-gray-300 hover:shadow-sm'}`, children: _jsxs("div", { className: "flex items-start gap-4", children: [_jsx("div", { className: `flex h-10 w-10 flex-shrink-0 cursor-pointer items-center justify-center rounded-xl font-bold transition-all ${isCorrect
218
- ? 'bg-gradient-to-br from-green-500 to-emerald-600 text-white shadow-md'
219
- : 'bg-gray-100 text-gray-600 hover:bg-indigo-100 hover:text-indigo-600'}`, onClick: () => form.setValue('correctAnswer', index, { shouldValidate: true }), children: optionLetter }), _jsxs("div", { className: "flex-1 space-y-2", children: [_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"] })), isCorrect && option && watchedQuestion.includes('{0}') && (_jsxs("div", { className: "mt-2 rounded-lg border border-blue-200 bg-blue-50/50 px-4 py-3", children: [_jsxs("div", { className: "mb-1.5 flex items-center gap-1.5 text-xs font-semibold text-blue-600", children: [_jsx(Eye, { className: "h-3.5 w-3.5" }), "Xem tr\u01B0\u1EDBc c\u00E2u ho\u00E0n ch\u1EC9nh"] }), _jsx("div", { className: "text-sm text-gray-700", children: watchedQuestion.split('{0}').map((part, idx, array) => (_jsxs("span", { children: [part, idx < array.length - 1 && (_jsx("span", { className: "mx-0.5 rounded bg-green-100 px-1.5 py-0.5 font-semibold text-green-700", children: option }))] }, idx))) })] }))] }), _jsxs("div", { className: "flex items-center gap-2", children: [!isCorrect && (_jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => form.setValue('correctAnswer', index, { shouldValidate: true }), className: "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: () => {
220
- const currentOptions = form.getValues('options');
221
- const newOptions = currentOptions.filter((_, i) => i !== index);
222
- form.setValue('options', newOptions);
223
- // Adjust correctAnswer if needed
224
- const currentCorrect = form.getValues('correctAnswer');
225
- if (currentCorrect === index) {
226
- form.setValue('correctAnswer', -1);
227
- }
228
- else if (currentCorrect > index) {
229
- form.setValue('correctAnswer', currentCorrect - 1);
230
- }
231
- }, className: "text-red-500 hover:bg-red-50 hover:text-red-600", children: _jsx(Trash2, { className: "h-4 w-4" }) }))] })] }) }, index));
232
- }) }), watchedCorrectAnswer === -1 && (_jsxs("div", { className: "flex items-start gap-3 rounded-xl border border-amber-200 bg-gradient-to-r from-amber-50 to-yellow-50 p-4", children: [_jsx("div", { className: "flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-lg bg-amber-100", children: _jsx("span", { className: "text-lg", children: "\u26A0\uFE0F" }) }), _jsxs("div", { className: "text-sm text-amber-800", children: [_jsx("p", { className: "font-semibold", children: "Ch\u01B0a ch\u1ECDn \u0111\u00E1p \u00E1n \u0111\u00FAng" }), _jsx("p", { className: "mt-0.5 text-amber-700", children: "Click v\u00E0o \u00F4 ch\u1EEF c\u00E1i ho\u1EB7c icon \u2713 \u0111\u1EC3 ch\u1ECDn \u0111\u00E1p \u00E1n \u0111\u00FAng" })] })] })), form.hasError('correctAnswer') && (_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.getFieldError('correctAnswer') }) }))] })] }), _jsxs(Card, { className: "overflow-hidden border-0 bg-white shadow-lg shadow-amber-100/50", children: [_jsx("div", { className: "h-1 bg-gradient-to-r from-amber-400 to-orange-400" }), _jsx(CardHeader, { className: "pb-4", children: _jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "flex h-9 w-9 items-center justify-center rounded-lg bg-gradient-to-br from-amber-500 to-orange-500 shadow-sm", children: _jsx(BookOpen, { className: "h-4 w-4 text-white" }) }), _jsxs("div", { children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(CardTitle, { className: "text-lg font-semibold text-gray-800", children: "Gi\u1EA3i th\u00EDch" }), _jsx("span", { className: "inline-flex items-center rounded-full border border-amber-200 bg-amber-50 px-2.5 py-0.5 text-xs font-medium text-amber-600", children: "Kh\u00F4ng b\u1EAFt bu\u1ED9c" })] }), _jsx("p", { className: "text-sm text-gray-500 mt-0.5", children: "Gi\u1EA3i th\u00EDch cho \u0111\u00E1p \u00E1n \u0111\u00FAng \u2014 hi\u1EC3n th\u1ECB sau khi h\u1ECDc sinh ho\u00E0n th\u00E0nh c\u00E2u h\u1ECFi" })] })] }) }), _jsx(CardContent, { children: _jsx(Textarea, { ...form.register('explanation'), placeholder: "VD: C\u00E2u tr\u1EA3 l\u1EDDi \u0111\u00FAng l\u00E0... v\u00EC...", rows: 3, className: "min-h-[80px] border-gray-200 bg-white transition-all focus:border-amber-300 focus:ring-amber-200 resize-none" }) })] })] }));
233
- }
234
- export function LookPictureFillBlankChooseAnswerCreator(props) {
235
- return _jsx(LookPictureFillBlankChooseAnswerContent, { ...props });
236
- }
237
- // Legacy export for backward compatibility
238
- export const LookPictureFillBlankChooseAnswer = LookPictureFillBlankChooseAnswerCreator;
@@ -1,2 +0,0 @@
1
- export * from './LookPictureFillBlankChooseAnswerCreator';
2
- export * from './LookPictureFillBlankChooseAnswerClient';
@@ -1,2 +0,0 @@
1
- export * from './LookPictureFillBlankChooseAnswerCreator';
2
- export * from './LookPictureFillBlankChooseAnswerClient';
@@ -1,2 +0,0 @@
1
- import type { QuestionTypeRegistration } from '../../creator/question-type-registry';
2
- export declare const lookPictureFillBlankChooseAnswerRegistration: QuestionTypeRegistration;
@@ -1,76 +0,0 @@
1
- import { LookPictureFillBlankChooseAnswer } from './LookPictureFillBlankChooseAnswerCreator';
2
- export const lookPictureFillBlankChooseAnswerRegistration = {
3
- component: LookPictureFillBlankChooseAnswer,
4
- transformInitialData: (initialData) => {
5
- let creatorData = undefined;
6
- const content = initialData?.content;
7
- const answer = initialData?.answer;
8
- const apiPoints = initialData?.total_points
9
- ? parseFloat(initialData.total_points)
10
- : (initialData?.points || answer?.points || 1);
11
- const explanation = initialData?.explanation || answer?.explanation || '';
12
- const normalizeOptions = (opts) => {
13
- if (!Array.isArray(opts) || opts.length === 0)
14
- return [];
15
- return opts.map((opt) => String(opt || ''));
16
- };
17
- const extractCorrectAnswer = () => {
18
- if (typeof answer?.correctAnswer === 'number')
19
- return answer.correctAnswer;
20
- if (typeof initialData?.correctAnswer === 'object' && initialData?.correctAnswer?.answer !== undefined)
21
- return initialData.correctAnswer.answer;
22
- if (typeof initialData?.correct_answer?.answer === 'number')
23
- return initialData.correct_answer.answer;
24
- if (typeof answer?.answer === 'number')
25
- return answer.answer;
26
- return -1;
27
- };
28
- // Priority 1: Data from store
29
- const hasStoreData = answer?.imageUrl !== undefined || answer?.question !== undefined || answer?.options;
30
- if (hasStoreData) {
31
- creatorData = {
32
- imageUrl: answer.imageUrl || '',
33
- question: answer.question || '',
34
- options: normalizeOptions(answer.options || []),
35
- correctAnswer: extractCorrectAnswer(),
36
- explanation,
37
- points: answer.points || apiPoints,
38
- };
39
- }
40
- // Priority 2: Raw API content format
41
- else if (content && (content.imageUrl !== undefined || content.question !== undefined || content.options)) {
42
- creatorData = {
43
- imageUrl: content.imageUrl || '',
44
- question: content.question || '',
45
- options: normalizeOptions(content.options || []),
46
- correctAnswer: extractCorrectAnswer(),
47
- explanation,
48
- points: apiPoints,
49
- };
50
- }
51
- return creatorData;
52
- },
53
- getExtraProps: (ctx) => ({
54
- optionCount: ctx.optionCount ?? undefined,
55
- onUnsavedChangesChange: ctx.onUnsavedChangesChange,
56
- validationRef: ctx.validationRef,
57
- }),
58
- wrapOnSave: (data, ctx) => ({
59
- type: ctx.questionType,
60
- content: ctx.state.content,
61
- points: data?.points ?? ctx.state.points ?? 1,
62
- level: ctx.state.level,
63
- difficulty: ctx.state.difficulty,
64
- skill: ctx.state.skill,
65
- answer: data,
66
- }),
67
- wrapOnChange: (data, ctx) => ({
68
- type: ctx.questionType,
69
- content: ctx.state.content,
70
- points: data?.points ?? ctx.state.points ?? 1,
71
- level: ctx.state.level,
72
- difficulty: ctx.state.difficulty,
73
- skill: ctx.state.skill,
74
- answer: data,
75
- }),
76
- };
@@ -1,2 +0,0 @@
1
- import type { TransformHandler } from '../../../../shared/lib/utils/question-transform-types';
2
- export declare const transformLookPictureFillBlankChooseAnswer: TransformHandler;
@@ -1,19 +0,0 @@
1
- // LOOK_PICTURE_FILL_BLANK_CHOOSE_ANSWER - single question format (same as ChooseTheCorrectAnswer but with image)
2
- // API format: content { imageUrl, question, options: string[] }, correct_answer { answer: number }
3
- export const transformLookPictureFillBlankChooseAnswer = (question) => {
4
- const answerData = question.answer;
5
- if (answerData?.imageUrl !== undefined || answerData?.question !== undefined || answerData?.options) {
6
- const apiContent = {
7
- imageUrl: answerData.imageUrl || '',
8
- question: answerData.question || '',
9
- options: answerData.options || [],
10
- };
11
- return {
12
- apiContent,
13
- apiCorrectAnswer: {
14
- answer: answerData.correctAnswer ?? -1,
15
- },
16
- };
17
- }
18
- return { apiContent: {}, apiCorrectAnswer: {} };
19
- };
@@ -1,7 +0,0 @@
1
- import { LookPictureFillWordHintClientProps } from '../../_shared/types/look-picture-fill-word-hint.type';
2
- interface ExtendedProps extends LookPictureFillWordHintClientProps {
3
- autoFillCorrectAnswers?: boolean;
4
- explanation?: string;
5
- }
6
- export declare function LookPictureFillWordHintClient({ questionData, userAnswer, isReviewMode, autoFillCorrectAnswers, explanation, onSubmit, }: ExtendedProps): import("react").JSX.Element;
7
- export {};