@tinyweb_dev/oe-exam-sdk 1.0.6 → 1.0.8
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.
- package/dist/components/exams/ExamCreator.js +1 -16
- package/dist/components/exams/take/ExamTakingPageContainer.js +0 -12
- package/dist/components/exams/take/components/QuestionRenderer.js +2 -15
- package/dist/components/exams/take/components/question-renderers/index.d.ts +0 -3
- package/dist/components/exams/take/components/question-renderers/index.js +0 -3
- package/dist/components/exams/take/components/speaking/renderers/SpeakingQuestionRenderer.js +0 -4
- package/dist/components/exams/take/types/speaking.types.d.ts +0 -31
- package/dist/components/exams/take/types.d.ts +0 -65
- package/dist/components/exams/take/utils/answer-transformers.js +6 -60
- package/dist/components/exams/take/utils/question-transformers.d.ts +2 -15
- package/dist/components/exams/take/utils/question-transformers.js +0 -143
- package/dist/components/questions/_shared/components/compact/CompactCreatorHeader.d.ts +23 -0
- package/dist/components/questions/_shared/components/compact/CompactCreatorHeader.js +9 -0
- package/dist/components/questions/_shared/components/compact/CompactExampleToggle.d.ts +6 -0
- package/dist/components/questions/_shared/components/compact/CompactExampleToggle.js +8 -0
- package/dist/components/questions/_shared/components/compact/CompactExplanationToggle.d.ts +7 -0
- package/dist/components/questions/_shared/components/compact/CompactExplanationToggle.js +14 -0
- package/dist/components/questions/_shared/components/compact/CompactFieldCard.d.ts +9 -0
- package/dist/components/questions/_shared/components/compact/CompactFieldCard.js +7 -0
- package/dist/components/questions/_shared/components/compact/CompactPreviewBanner.d.ts +7 -0
- package/dist/components/questions/_shared/components/compact/CompactPreviewBanner.js +7 -0
- package/dist/components/questions/_shared/components/compact/CompactPreviewButton.d.ts +6 -0
- package/dist/components/questions/_shared/components/compact/CompactPreviewButton.js +7 -0
- package/dist/components/questions/_shared/components/compact/index.d.ts +6 -0
- package/dist/components/questions/_shared/components/compact/index.js +6 -0
- package/dist/components/questions/_shared/config/question-types.config.js +0 -24
- package/dist/components/questions/_shared/hooks/useQuestionInitialization.js +0 -9
- package/dist/components/questions/_shared/types/choose-the-correct-answer-group.type.d.ts +2 -2
- package/dist/components/questions/_shared/types/choose-the-correct-answer-group.type.js +5 -1
- package/dist/components/questions/_shared/types/index.d.ts +0 -1
- package/dist/components/questions/_shared/types/index.js +0 -1
- package/dist/components/questions/_shared/types/look-and-choose-answer.type.d.ts +0 -13
- package/dist/components/questions/_shared/utils/answer-builder.js +0 -11
- package/dist/components/questions/components/ColorRegionCreator.js +66 -63
- package/dist/components/questions/components/QuestionSearchDropdown.js +0 -8
- package/dist/components/questions/components/QuestionTypeSelector.js +0 -2
- package/dist/components/questions/components/index.d.ts +1 -0
- package/dist/components/questions/components/index.js +1 -0
- package/dist/components/questions/creator/question-type-registry.js +0 -8
- package/dist/components/questions/groups/ArticlesGroupCard.js +2 -2
- package/dist/components/questions/groups/BasicQuestionGroupCard.js +1 -1
- package/dist/components/questions/groups/DocumentGroupCard.js +1 -1
- package/dist/components/questions/groups/ImageLabelGroupCard.js +1 -1
- package/dist/components/questions/groups/MatchAnswerGroupCard.js +2 -2
- package/dist/components/questions/question-bank/QuestionEditorRenderer.d.ts +0 -3
- package/dist/components/questions/question-bank/QuestionEditorRenderer.js +1 -10
- package/dist/components/questions/question-bank/editor-adapters/choice-adapters.d.ts +0 -2
- package/dist/components/questions/question-bank/editor-adapters/choice-adapters.js +0 -41
- package/dist/components/questions/question-bank/editor-adapters/index.d.ts +2 -2
- package/dist/components/questions/question-bank/editor-adapters/index.js +2 -2
- package/dist/components/questions/question-bank/editor-adapters/media-adapters.d.ts +0 -1
- package/dist/components/questions/question-bank/editor-adapters/media-adapters.js +0 -28
- package/dist/components/questions/question-bank/question-bank-picker.utils.js +0 -7
- package/dist/components/questions/types/answer-the-question/AnswerTheQuestionClient.js +16 -16
- package/dist/components/questions/types/answer-the-question/AnswerTheQuestionCreator.js +36 -30
- package/dist/components/questions/types/answer-the-question-group/AnswerTheQuestionGroupClient.js +1 -1
- package/dist/components/questions/types/answer-the-question-group/AnswerTheQuestionGroupCreator.js +7 -6
- package/dist/components/questions/types/arrange-letters-into-words/ArrangeLettersIntoWordsClient.js +2 -2
- package/dist/components/questions/types/arrange-letters-into-words/ArrangeLettersIntoWordsCreator.js +11 -7
- package/dist/components/questions/types/choose-correct-adjective/ChooseCorrectAdjectiveClient.js +1 -1
- package/dist/components/questions/types/choose-correct-adjective/ChooseCorrectAdjectiveCreator.js +11 -5
- package/dist/components/questions/types/choose-the-correct-answer/ChooseTheCorrectAnswerClient.js +5 -5
- package/dist/components/questions/types/choose-the-correct-answer/ChooseTheCorrectAnswerCreator.js +34 -28
- package/dist/components/questions/types/choose-the-correct-answer-group/ChooseAnswerGroupImageUpload.d.ts +7 -0
- package/dist/components/questions/types/choose-the-correct-answer-group/ChooseAnswerGroupImageUpload.js +5 -2
- package/dist/components/questions/types/choose-the-correct-answer-group/ChooseTheCorrectAnswerGroupClient.js +9 -5
- package/dist/components/questions/types/choose-the-correct-answer-group/ChooseTheCorrectAnswerGroupCreator.js +65 -62
- package/dist/components/questions/types/choose-the-correct-answer-group/ChooseTheCorrectAnswerGroupItemEditor.d.ts +6 -0
- package/dist/components/questions/types/choose-the-correct-answer-group/ChooseTheCorrectAnswerGroupItemEditor.js +145 -0
- package/dist/components/questions/types/choose-the-correct-answer-group/map-choose-the-correct-answer-group-data.js +6 -15
- package/dist/components/questions/types/choose-the-correct-answer-group/transform.js +5 -4
- package/dist/components/questions/types/choose-then-answer-group/ChooseThenAnswerGroupClient.js +7 -7
- package/dist/components/questions/types/choose-then-answer-group/ChooseThenAnswerGroupCreator.js +8 -5
- package/dist/components/questions/types/cross-out-word-group/CrossOutWordGroupClient.js +2 -2
- package/dist/components/questions/types/cross-out-word-group/CrossOutWordGroupCreator.js +20 -16
- package/dist/components/questions/types/crossword-puzzle/CrosswordPuzzleClient.js +1 -1
- package/dist/components/questions/types/crossword-puzzle/CrosswordPuzzleCreator.js +66 -29
- package/dist/components/questions/types/fill-in-blank/FillInBlankClient.js +5 -5
- package/dist/components/questions/types/fill-in-blank/FillInBlankCreator.js +45 -37
- package/dist/components/questions/types/fill-in-blank-group/FillInBlankGroupCreator.js +24 -20
- package/dist/components/questions/types/fill-missing-words-in-grid/FillMissingWordsInGridClient.js +2 -2
- package/dist/components/questions/types/fill-missing-words-in-grid/FillMissingWordsInGridCreator.js +68 -65
- package/dist/components/questions/types/find-words-in-matrix/FindWordsInMatrixClient.js +1 -1
- package/dist/components/questions/types/find-words-in-matrix/FindWordsInMatrixCreator.js +45 -5
- package/dist/components/questions/types/label-the-picture/LabelThePictureClient.js +2 -2
- package/dist/components/questions/types/label-the-picture/LabelThePictureCreator.js +18 -14
- package/dist/components/questions/types/listen-and-choose-from-answer-group/ListenAndChooseFromAnswerGroupClient.js +2 -2
- package/dist/components/questions/types/listen-and-choose-from-answer-group/ListenAndChooseFromAnswerGroupCreator.js +30 -30
- package/dist/components/questions/types/listen-and-choose-objects-in-scene/ListenAndChooseObjectsInSceneClient.js +2 -2
- package/dist/components/questions/types/listen-and-choose-objects-in-scene/ListenAndChooseObjectsInSceneCreator.js +24 -31
- package/dist/components/questions/types/listen-and-drag-objects-into-scene/ListenAndDragObjectsIntoSceneClient.js +2 -2
- package/dist/components/questions/types/listen-and-drag-objects-into-scene/ListenAndDragObjectsIntoSceneCreator.js +29 -32
- package/dist/components/questions/types/listen-and-speak-answer/ListenAndSpeakAnswerClient.js +1 -1
- package/dist/components/questions/types/listen-and-speak-answer/ListenAndSpeakAnswerCreator.js +27 -29
- package/dist/components/questions/types/listen-and-speak-compare-images/ListenAndSpeakCompareImagesClient.js +1 -1
- package/dist/components/questions/types/listen-and-speak-compare-images/ListenAndSpeakCompareImagesCreator.js +11 -15
- package/dist/components/questions/types/listen-and-speak-image-group/ListenAndSpeakImageGroupClient.js +1 -1
- package/dist/components/questions/types/listen-and-speak-image-group/ListenAndSpeakImageGroupCreator.js +22 -24
- package/dist/components/questions/types/listen-and-speak-info-exchange/ListenAndSpeakInfoExchangeClient.js +1 -1
- package/dist/components/questions/types/listen-and-speak-info-exchange/ListenAndSpeakInfoExchangeCreator.js +17 -24
- package/dist/components/questions/types/listen-and-speak-odd-one-out/ListenAndSpeakOddOneOutClient.js +1 -1
- package/dist/components/questions/types/listen-and-speak-odd-one-out/ListenAndSpeakOddOneOutCreator.js +31 -31
- package/dist/components/questions/types/listen-and-speak-question-list/ListenAndSpeakQuestionListClient.js +1 -1
- package/dist/components/questions/types/listen-and-speak-question-list/ListenAndSpeakQuestionListCreator.js +11 -15
- package/dist/components/questions/types/listen-and-speak-with-story-images/ListenAndSpeakWithStoryImagesClient.js +1 -1
- package/dist/components/questions/types/listen-and-speak-with-story-images/ListenAndSpeakWithStoryImagesCreator.js +14 -15
- package/dist/components/questions/types/match-by-writing-answer/MatchByWritingAnswerClient.js +2 -2
- package/dist/components/questions/types/match-by-writing-answer/MatchByWritingAnswerCreator.js +27 -25
- package/dist/components/questions/types/match-columns-to-make-sentences/MatchColumnsToMakeSentencesClient.js +12 -12
- package/dist/components/questions/types/match-columns-to-make-sentences/MatchColumnsToMakeSentencesCreator.js +8 -5
- package/dist/components/questions/types/match-word-to-picture/MatchWordToPictureClient.js +8 -8
- package/dist/components/questions/types/match-word-to-picture/MatchWordToPictureCreator.js +25 -20
- package/dist/components/questions/types/match-word-to-picture-group/MatchWordToPictureGroupClient.js +1 -1
- package/dist/components/questions/types/match-word-to-picture-group/MatchWordToPictureGroupCreator.js +13 -10
- package/dist/components/questions/types/matching-with-lines-group/MatchingWithLinesGroupCreator.js +8 -5
- package/dist/components/questions/types/read-displayed-content/ReadDisplayedContentCreator.js +7 -11
- package/dist/components/questions/types/read-passage-and-answer-questions/ReadPassageAndAnswerQuestionsClient.js +1 -1
- package/dist/components/questions/types/read-passage-and-answer-questions/ReadPassageAndAnswerQuestionsCreator.js +15 -12
- package/dist/components/questions/types/sort-words-into-categories/SortWordsIntoCategoriesClient.js +1 -1
- package/dist/components/questions/types/sort-words-into-categories/SortWordsIntoCategoriesCreator.js +5 -5
- package/dist/components/questions/types/speaking-conversation/SpeakingConversationCreator.js +35 -39
- package/dist/components/questions/types/speaking-conversation/SpeakingConversationPreviewClient.js +2 -2
- package/dist/components/questions/types/speaking-cue-card/SpeakingCueCardClient.js +1 -1
- package/dist/components/questions/types/speaking-cue-card/SpeakingCueCardCreator.js +7 -8
- package/dist/components/questions/types/speaking-describe-image/SpeakingDescribeImageClient.js +1 -1
- package/dist/components/questions/types/speaking-describe-image/SpeakingDescribeImageCreator.js +30 -23
- package/dist/components/questions/types/spontaneous-qa/SpontaneousQAClient.js +1 -1
- package/dist/components/questions/types/spontaneous-qa/SpontaneousQACreator.js +26 -19
- package/dist/components/questions/types/spontaneous-qa-group/SpontaneousQaGroupClient.d.ts +1 -1
- package/dist/components/questions/types/spontaneous-qa-group/SpontaneousQaGroupClient.js +79 -3
- package/dist/components/questions/types/spontaneous-qa-group/SpontaneousQaGroupCreator.js +13 -11
- package/dist/components/questions/types/spontaneous-qa-group/index.d.ts +1 -0
- package/dist/components/questions/types/spontaneous-qa-group/index.js +1 -0
- package/dist/components/questions/types/spontaneous-qa-group/map-spontaneous-qa-group-data.d.ts +2 -0
- package/dist/components/questions/types/spontaneous-qa-group/map-spontaneous-qa-group-data.js +74 -0
- package/dist/components/questions/types/true-false/TrueFalseClient.js +2 -2
- package/dist/components/questions/types/true-false/TrueFalseCreator.js +35 -29
- package/dist/components/questions/types/true-false-correct-group/TrueFalseCorrectGroupClient.js +1 -1
- package/dist/components/questions/types/true-false-correct-group/TrueFalseCorrectGroupCreator.js +11 -7
- package/dist/components/questions/types/true-false-group/TrueFalseGroupClient.js +1 -1
- package/dist/components/questions/types/true-false-group/TrueFalseGroupCreator.js +11 -7
- package/dist/components/questions/types/word-fill-paragraph/WordFillParagraphClient.js +2 -2
- package/dist/components/questions/types/word-fill-paragraph/WordFillParagraphCreator.js +21 -56
- package/dist/components/questions/types/word-fill-structured-form/WordFillStructuredFormClient.js +1 -1
- package/dist/components/questions/types/word-fill-structured-form/WordFillStructuredFormCreator.js +30 -24
- package/dist/components/questions/types/word-order-and-match-group/WordOrderAndMatchGroupClient.js +3 -3
- package/dist/components/questions/types/word-order-and-match-group/WordOrderAndMatchGroupCreator.js +12 -14
- package/dist/components/questions/types/word-ordering/WordOrderingClient.js +1 -1
- package/dist/components/questions/types/word-ordering/WordOrderingCreator.js +27 -18
- package/dist/components/questions/types/word-ordering-group/WordOrderingGroupClient.js +7 -7
- package/dist/components/questions/types/word-ordering-group/WordOrderingGroupCreator.js +16 -13
- package/dist/components/questions/types/write-a-short-letter/WriteAShortLetterClient.js +2 -2
- package/dist/components/questions/types/write-a-short-letter/WriteAShortLetterCreator.js +34 -28
- package/dist/components/questions/types/write-correct-verb-form/WriteCorrectVerbFormClient.js +2 -2
- package/dist/components/questions/types/write-correct-verb-form/WriteCorrectVerbFormCreator.js +22 -22
- package/dist/components/questions/types/write-sentences/WriteSentencesCreator.js +21 -18
- package/dist/components/questions/types/write-short-paragraph/WriteShortParagraphClient.js +1 -1
- package/dist/components/questions/types/write-short-paragraph/WriteShortParagraphCreator.js +7 -4
- package/dist/components/questions/viewer/QuestionViewer.js +0 -98
- package/dist/components/questions/viewer/SpontaneousQaGroupViewer.js +4 -79
- package/dist/components/results/renderers/review-question-body-movers.js +2 -26
- package/dist/components/results/review-data.js +0 -4
- package/dist/components/ui/points-input.d.ts +1 -0
- package/dist/components/ui/points-input.js +12 -2
- package/dist/shared/constants/question-skills.js +0 -11
- package/dist/shared/lib/i18n/messages/en/admin.d.ts +0 -3
- package/dist/shared/lib/i18n/messages/en/admin.js +0 -3
- package/dist/shared/lib/i18n/messages/en.d.ts +0 -3
- package/dist/shared/lib/i18n/messages/vi/admin.d.ts +0 -3
- package/dist/shared/lib/i18n/messages/vi/admin.js +0 -3
- package/dist/shared/lib/i18n/messages/vi.d.ts +0 -3
- package/dist/shared/lib/utils/question-reverse-transform.js +4 -23
- package/dist/shared/lib/utils/question-transform-types.d.ts +1 -23
- package/dist/shared/lib/utils/question-transform-validation.js +0 -40
- package/dist/shared/lib/utils/question-transform.js +0 -9
- package/dist/shared/types/common.types.d.ts +1 -1
- package/dist/shared/types/entities/question.types.d.ts +1 -11
- package/dist/shared/types/questions/choose-the-correct-answer-group.d.ts +1 -1
- package/dist/shared/types/questions/index.d.ts +0 -3
- package/dist/shared/types/questions/index.js +0 -6
- package/package.json +1 -1
- package/dist/components/exams/take/components/question-renderers/MoversFillWordHintRenderer.d.ts +0 -2
- package/dist/components/exams/take/components/question-renderers/MoversFillWordHintRenderer.js +0 -131
- package/dist/components/exams/take/components/question-renderers/MoversPictureChooseRenderer.d.ts +0 -2
- package/dist/components/exams/take/components/question-renderers/MoversPictureChooseRenderer.js +0 -76
- package/dist/components/exams/take/components/question-renderers/MoversPictureFillBlankChooseRenderer.d.ts +0 -2
- package/dist/components/exams/take/components/question-renderers/MoversPictureFillBlankChooseRenderer.js +0 -68
- package/dist/components/exams/take/components/speaking/renderers/SpeakingGNInterviewRenderer.d.ts +0 -16
- package/dist/components/exams/take/components/speaking/renderers/SpeakingGNInterviewRenderer.js +0 -212
- package/dist/components/questions/_shared/types/gn-speaking-interview.type.d.ts +0 -32
- package/dist/components/questions/_shared/types/gn-speaking-interview.type.js +0 -1
- package/dist/components/questions/_shared/types/look-picture-fill-word-hint.type.d.ts +0 -47
- package/dist/components/questions/_shared/types/look-picture-fill-word-hint.type.js +0 -1
- package/dist/components/questions/types/gn-speaking-interview/GNSpeakingInterviewCreator.d.ts +0 -2
- package/dist/components/questions/types/gn-speaking-interview/GNSpeakingInterviewCreator.js +0 -262
- package/dist/components/questions/types/gn-speaking-interview/register.d.ts +0 -2
- package/dist/components/questions/types/gn-speaking-interview/register.js +0 -121
- package/dist/components/questions/types/gn-speaking-interview/transform.d.ts +0 -2
- package/dist/components/questions/types/gn-speaking-interview/transform.js +0 -48
- package/dist/components/questions/types/look-picture-choose-correct-answer/LookPictureChooseCorrectAnswerClient.d.ts +0 -20
- package/dist/components/questions/types/look-picture-choose-correct-answer/LookPictureChooseCorrectAnswerClient.js +0 -72
- package/dist/components/questions/types/look-picture-choose-correct-answer/LookPictureChooseCorrectAnswerCreator.d.ts +0 -15
- package/dist/components/questions/types/look-picture-choose-correct-answer/LookPictureChooseCorrectAnswerCreator.js +0 -230
- package/dist/components/questions/types/look-picture-choose-correct-answer/index.d.ts +0 -1
- package/dist/components/questions/types/look-picture-choose-correct-answer/index.js +0 -1
- package/dist/components/questions/types/look-picture-choose-correct-answer/register.d.ts +0 -2
- package/dist/components/questions/types/look-picture-choose-correct-answer/register.js +0 -66
- package/dist/components/questions/types/look-picture-choose-correct-answer/transform.d.ts +0 -2
- package/dist/components/questions/types/look-picture-choose-correct-answer/transform.js +0 -42
- package/dist/components/questions/types/look-picture-fill-blank-choose-answer/LookPictureFillBlankChooseAnswerClient.d.ts +0 -6
- package/dist/components/questions/types/look-picture-fill-blank-choose-answer/LookPictureFillBlankChooseAnswerClient.js +0 -96
- package/dist/components/questions/types/look-picture-fill-blank-choose-answer/LookPictureFillBlankChooseAnswerCreator.d.ts +0 -15
- package/dist/components/questions/types/look-picture-fill-blank-choose-answer/LookPictureFillBlankChooseAnswerCreator.js +0 -238
- package/dist/components/questions/types/look-picture-fill-blank-choose-answer/index.d.ts +0 -2
- package/dist/components/questions/types/look-picture-fill-blank-choose-answer/index.js +0 -2
- package/dist/components/questions/types/look-picture-fill-blank-choose-answer/register.d.ts +0 -2
- package/dist/components/questions/types/look-picture-fill-blank-choose-answer/register.js +0 -76
- package/dist/components/questions/types/look-picture-fill-blank-choose-answer/transform.d.ts +0 -2
- package/dist/components/questions/types/look-picture-fill-blank-choose-answer/transform.js +0 -19
- package/dist/components/questions/types/look-picture-fill-word-hint/LookPictureFillWordHintClient.d.ts +0 -7
- package/dist/components/questions/types/look-picture-fill-word-hint/LookPictureFillWordHintClient.js +0 -134
- package/dist/components/questions/types/look-picture-fill-word-hint/LookPictureFillWordHintCreator.d.ts +0 -3
- package/dist/components/questions/types/look-picture-fill-word-hint/LookPictureFillWordHintCreator.js +0 -617
- package/dist/components/questions/types/look-picture-fill-word-hint/index.d.ts +0 -2
- package/dist/components/questions/types/look-picture-fill-word-hint/index.js +0 -2
- package/dist/components/questions/types/look-picture-fill-word-hint/register.d.ts +0 -2
- package/dist/components/questions/types/look-picture-fill-word-hint/register.js +0 -82
- package/dist/components/questions/types/look-picture-fill-word-hint/transform.d.ts +0 -2
- package/dist/components/questions/types/look-picture-fill-word-hint/transform.js +0 -29
- package/dist/shared/lib/validation/look-picture-choose-correct-answer.validation.d.ts +0 -9
- package/dist/shared/lib/validation/look-picture-choose-correct-answer.validation.js +0 -12
- package/dist/shared/lib/validation/look-picture-fill-blank-choose-answer.validation.d.ts +0 -17
- package/dist/shared/lib/validation/look-picture-fill-blank-choose-answer.validation.js +0 -47
- package/dist/shared/types/questions/look-picture-choose-correct-answer.d.ts +0 -38
- package/dist/shared/types/questions/look-picture-choose-correct-answer.js +0 -5
- package/dist/shared/types/questions/look-picture-fill-blank-choose-answer.d.ts +0 -63
- package/dist/shared/types/questions/look-picture-fill-blank-choose-answer.js +0 -9
- package/dist/shared/types/questions/look-picture-fill-word-hint.d.ts +0 -57
- package/dist/shared/types/questions/look-picture-fill-word-hint.js +0 -7
|
@@ -4,10 +4,9 @@ import { useState, useRef, useEffect } from 'react';
|
|
|
4
4
|
import { Button } from '../../../../components/ui/button';
|
|
5
5
|
import { Label } from '../../../../components/ui/label';
|
|
6
6
|
import { Input } from '../../../../components/ui/input';
|
|
7
|
-
import { Textarea } from '../../../../components/ui/textarea';
|
|
8
7
|
import { Card, CardContent, CardHeader, CardTitle } from '../../../../components/ui/card';
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
8
|
+
import { Plus, X, Image, Tags } from 'lucide-react';
|
|
9
|
+
import { CompactCreatorHeader, CompactExplanationToggle, CompactFieldCard, CompactPreviewBanner, } from '../../_shared/components/compact';
|
|
11
10
|
import { FileUpload } from '../../../../components/ui/file-upload';
|
|
12
11
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '../../../../components/ui/select';
|
|
13
12
|
import { LabelThePictureClient } from './LabelThePictureClient';
|
|
@@ -313,20 +312,25 @@ function LabelThePictureCreatorContent({ initialData, onSave, onCancel, onChange
|
|
|
313
312
|
acc[label.id] = label.correctAnswer;
|
|
314
313
|
return acc;
|
|
315
314
|
}, {});
|
|
316
|
-
return (_jsxs("div", { className: "space-y-
|
|
315
|
+
return (_jsxs("div", { className: "space-y-4", children: [_jsx(CompactPreviewBanner, { onBack: () => setIsClientMode(false) }), _jsx(LabelThePictureClient, { questionData: questionData, userAnswer: correctAnswers, isReviewMode: true, explanation: explanation })] }));
|
|
317
316
|
}
|
|
318
|
-
return (_jsxs("div", { className: "space-y-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
317
|
+
return (_jsxs("div", { className: "space-y-3", children: [_jsx(CompactFieldCard, { header: _jsx(CompactCreatorHeader, { icon: _jsx(Image, { "aria-label": "G\u1EAFn nh\u00E3n h\u00ECnh \u1EA3nh", className: "h-3.5 w-3.5" }), title: "G\u1EAFn nh\u00E3n h\u00ECnh \u1EA3nh", points: {
|
|
318
|
+
value: points,
|
|
319
|
+
onValueChange: setPoints,
|
|
320
|
+
}, preview: {
|
|
321
|
+
onClick: handlePreview,
|
|
322
|
+
} }), children: _jsxs("div", { className: "space-y-1.5", children: [_jsxs(Label, { className: "flex items-center gap-1.5 text-sm font-semibold text-gray-800", children: [_jsx(Image, { "aria-label": "H\u00ECnh \u1EA3nh", className: "h-3.5 w-3.5 text-indigo-500" }), "H\u00ECnh \u1EA3nh ", _jsx("span", { className: "text-red-500", children: "*" })] }), _jsx(FileUpload, { id: "label-picture-image", label: "H\u00ECnh \u1EA3nh", accept: "image/*", value: imageUrl, onChange: (url) => {
|
|
323
|
+
setImageUrl(url);
|
|
324
|
+
form.clearErrors();
|
|
325
|
+
}, onPresignedUrlChange: (presignedUrl) => {
|
|
326
|
+
setImagePreviewUrl(presignedUrl);
|
|
327
|
+
}, maxSize: 5, placeholder: "Upload \u1EA3nh ho\u1EB7c paste URL", required: true, autoUpload: true, prefix: "questions" }), errors.imageUrl && (_jsx("p", { className: "mt-1 text-sm text-red-600", children: errors.imageUrl })), imagePreviewUrl && (_jsx("div", { className: "relative mt-2 h-64 w-full max-w-2xl overflow-hidden rounded border", children: _jsx("img", { src: imagePreviewUrl, alt: "Preview for question", className: "h-full w-full object-contain", onError: () => {
|
|
328
|
+
console.error('Failed to load image:', imageUrl);
|
|
329
|
+
} }) }))] }) }), _jsxs(Card, { className: "overflow-hidden border-0 bg-white shadow-sm", children: [_jsx(CardHeader, { className: "px-3 py-2", 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(Tags, { className: "h-3.5 w-3.5 text-indigo-600" }) }), _jsxs(CardTitle, { className: "text-lg font-semibold text-gray-800", children: ["Word Bank ", _jsxs("span", { className: "text-sm font-normal text-gray-500", children: ["(", wordBank.filter(w => w.trim()).length, " t\u1EEB)"] })] })] }), _jsxs(Button, { type: "button", size: "sm", onClick: addWordToBank, className: "gap-2 border-green-200 text-green-600 hover:bg-green-50 hover:text-green-700", variant: "outline", children: [_jsx(Plus, { className: "h-4 w-4" }), "Th\u00EAm t\u1EEB"] })] }) }), _jsxs(CardContent, { className: "space-y-4", children: [errors.wordBank && (_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("span", { className: "text-lg", children: "\u274C" }), _jsx("p", { className: "text-sm text-red-600", children: errors.wordBank })] })), _jsx("div", { className: "grid gap-3 sm:grid-cols-2", children: wordBank.map((word, index) => (_jsxs("div", { className: `group relative rounded-xl border-2 p-3 transition-all duration-200 ${word.trim()
|
|
326
330
|
? 'border-green-200 bg-gradient-to-r from-green-50 to-emerald-50'
|
|
327
331
|
: 'border-gray-200 bg-white hover:border-gray-300'}`, children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: `flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-lg font-bold transition-all ${word.trim()
|
|
328
332
|
? 'bg-gradient-to-br from-green-500 to-emerald-600 text-white shadow-md'
|
|
329
|
-
: 'bg-gray-100 text-gray-600'}`, children: index + 1 }), _jsx(Input, { value: word, onChange: (e) => updateWordInBank(index, e.target.value), placeholder: `Từ ${index + 1}`, className: `flex-1 border-gray-200 bg-white transition-all focus:border-indigo-300 focus:ring-indigo-200 ${errors.wordBankItems?.[index] ? 'border-red-400' : ''}` }), _jsx(Button, { type: "button", variant: "ghost", size: "icon", onClick: () => removeWordFromBank(index), className: "text-gray-400 hover:bg-red-50 hover:text-red-500", children: _jsx(X, { className: "h-4 w-4" }) })] }), errors.wordBankItems?.[index] && (_jsx("p", { className: "mt-2 ml-11 text-sm text-red-600", children: errors.wordBankItems[index] }))] }, index))) }), wordBank.length === 0 && (_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("span", { className: "text-lg", children: "\u26A0\uFE0F" }), _jsx("p", { className: "text-sm text-amber-800", children: "Ch\u01B0a c\u00F3 t\u1EEB n\u00E0o. Nh\u1EA5n \"Th\u00EAm t\u1EEB\" \u0111\u1EC3 b\u1EAFt \u0111\u1EA7u." })] }))] })] }), _jsxs(Card, { className: "overflow-hidden border-0 bg-white shadow-
|
|
333
|
+
: 'bg-gray-100 text-gray-600'}`, children: index + 1 }), _jsx(Input, { value: word, onChange: (e) => updateWordInBank(index, e.target.value), placeholder: `Từ ${index + 1}`, className: `flex-1 border-gray-200 bg-white transition-all focus:border-indigo-300 focus:ring-indigo-200 ${errors.wordBankItems?.[index] ? 'border-red-400' : ''}` }), _jsx(Button, { type: "button", variant: "ghost", size: "icon", onClick: () => removeWordFromBank(index), className: "text-gray-400 hover:bg-red-50 hover:text-red-500", children: _jsx(X, { className: "h-4 w-4" }) })] }), errors.wordBankItems?.[index] && (_jsx("p", { className: "mt-2 ml-11 text-sm text-red-600", children: errors.wordBankItems[index] }))] }, index))) }), wordBank.length === 0 && (_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("span", { className: "text-lg", children: "\u26A0\uFE0F" }), _jsx("p", { className: "text-sm text-amber-800", children: "Ch\u01B0a c\u00F3 t\u1EEB n\u00E0o. Nh\u1EA5n \"Th\u00EAm t\u1EEB\" \u0111\u1EC3 b\u1EAFt \u0111\u1EA7u." })] }))] })] }), _jsxs(Card, { className: "overflow-hidden border-0 bg-white shadow-sm", children: [_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-purple-100", children: _jsx(Tags, { className: "h-3.5 w-3.5 text-purple-600" }) }), _jsxs(CardTitle, { className: "text-lg font-semibold text-gray-800", children: ["V\u1ECB tr\u00ED c\u1EA7n \u0111i\u1EC1n ", _jsxs("span", { className: "text-sm font-normal text-gray-500", children: ["(", labels.length, " v\u1ECB tr\u00ED)"] })] })] }), _jsxs(Button, { type: "button", size: "sm", onClick: addLabel, className: "gap-2 border-purple-200 text-purple-600 hover:bg-purple-50 hover:text-purple-700", variant: "outline", children: [_jsx(Plus, { className: "h-4 w-4" }), "Th\u00EAm v\u1ECB tr\u00ED"] })] }) }), _jsxs(CardContent, { className: "space-y-4", children: [errors.labels && (_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("span", { className: "text-lg", children: "\u274C" }), _jsx("p", { className: "text-sm text-red-600", children: errors.labels })] })), _jsx("div", { className: "space-y-4", children: labels
|
|
330
334
|
.sort((a, b) => a.number - b.number)
|
|
331
335
|
.map((label) => {
|
|
332
336
|
const hasAnswer = label.correctAnswer && label.correctAnswer.trim();
|
|
@@ -337,7 +341,7 @@ function LabelThePictureCreatorContent({ initialData, onSave, onCancel, onChange
|
|
|
337
341
|
: 'bg-purple-100 text-purple-700'}`, children: label.number }), _jsxs("div", { className: "flex-1 space-y-3", children: [_jsxs("div", { children: [_jsxs(Label, { className: "text-sm font-semibold text-gray-700", children: ["\u0110\u00E1p \u00E1n \u0111\u00FAng ", _jsx("span", { className: "text-red-500", children: "*" })] }), _jsx("div", { className: "mt-1.5", children: _jsxs(Select, { value: label.correctAnswer || '', onValueChange: (value) => updateLabel(label.id, 'correctAnswer', value), children: [_jsx(SelectTrigger, { className: `w-full border-gray-200 transition-all focus:border-indigo-300 focus:ring-indigo-200 ${errors.labelItems?.[label.id] ? 'border-red-400' : ''}`, children: _jsx(SelectValue, { placeholder: "Ch\u1ECDn t\u1EEB trong word bank" }) }), _jsx(SelectContent, { children: wordBank
|
|
338
342
|
.filter((word) => word.trim() !== '')
|
|
339
343
|
.map((word, index) => (_jsx(SelectItem, { value: word, children: word }, index))) })] }) }), errors.labelItems?.[label.id] && (_jsx("p", { className: "mt-1 text-sm text-red-600", children: errors.labelItems[label.id] }))] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("input", { type: "checkbox", id: `prefilled-${label.id}`, checked: label.isPreFilled || false, onChange: (e) => updateLabel(label.id, 'isPreFilled', e.target.checked), className: "h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-500" }), _jsx(Label, { htmlFor: `prefilled-${label.id}`, className: "text-sm font-normal text-gray-600 cursor-pointer", children: "\u0110\u00E1nh d\u1EA5u l\u00E0 t\u1EEB g\u1EE3i \u00FD (\u0111\u00E3 \u0111i\u1EC1n s\u1EB5n)" })] })] }), _jsx(Button, { type: "button", variant: "ghost", size: "icon", onClick: () => removeLabel(label.id), className: "text-gray-400 hover:bg-red-50 hover:text-red-500", children: _jsx(X, { className: "h-4 w-4" }) })] }) }, label.id));
|
|
340
|
-
}) }), labels.length === 0 && (_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("span", { className: "text-lg", children: "\u26A0\uFE0F" }), _jsx("p", { className: "text-sm text-amber-800", children: "Ch\u01B0a c\u00F3 v\u1ECB tr\u00ED n\u00E0o. Nh\u1EA5n \"Th\u00EAm v\u1ECB tr\u00ED\" \u0111\u1EC3 b\u1EAFt \u0111\u1EA7u." })] }))] })] }),
|
|
344
|
+
}) }), labels.length === 0 && (_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("span", { className: "text-lg", children: "\u26A0\uFE0F" }), _jsx("p", { className: "text-sm text-amber-800", children: "Ch\u01B0a c\u00F3 v\u1ECB tr\u00ED n\u00E0o. Nh\u1EA5n \"Th\u00EAm v\u1ECB tr\u00ED\" \u0111\u1EC3 b\u1EAFt \u0111\u1EA7u." })] }))] })] }), _jsx(CompactExplanationToggle, { value: explanation, onValueChange: setExplanation })] }));
|
|
341
345
|
}
|
|
342
346
|
export function LabelThePictureCreator(props) {
|
|
343
347
|
return _jsx(LabelThePictureCreatorContent, { ...props });
|
|
@@ -47,9 +47,9 @@ export function ListenAndChooseFromAnswerGroupClient({ questionData, onSubmit, i
|
|
|
47
47
|
const hasAnswered = !!selectedAnswer;
|
|
48
48
|
const selectedOption = questionData.options.find((o) => o.id === selectedAnswer);
|
|
49
49
|
const isAnswerCorrect = isReviewMode && selectedAnswer === resolvedCorrectAnswer;
|
|
50
|
-
return (_jsxs("div", { className: "space-y-4", children: [questionData.isBackup && (_jsxs("div", { className: "inline-flex self-center items-center gap-1.5 px-3 py-1.5 rounded-md bg-amber-50 text-amber-700 border border-amber-200 text-xs font-bold shadow-sm", children: [_jsx("span", { children: "\uD83D\uDD04" }), " C\u00E2u h\u1ECFi d\u1EF1 ph\u00F2ng"] })), _jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-gray-100 bg-
|
|
50
|
+
return (_jsxs("div", { className: "space-y-4", children: [questionData.isBackup && (_jsxs("div", { className: "inline-flex self-center items-center gap-1.5 px-3 py-1.5 rounded-md bg-amber-50 text-amber-700 border border-amber-200 text-xs font-bold shadow-sm", children: [_jsx("span", { children: "\uD83D\uDD04" }), " C\u00E2u h\u1ECFi d\u1EF1 ph\u00F2ng"] })), _jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-gray-100 bg-gray-50 px-3 py-2", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "flex h-7 w-7 items-center justify-center rounded-md bg-gray-100 text-gray-600", children: _jsx(Volume2, { className: "h-4 w-4" }) }), _jsx("span", { className: "text-sm font-medium text-gray-700", children: "Nghe v\u00E0 ch\u1ECDn trong nh\u00F3m \u0111\u00E1p \u00E1n" })] }), isReviewMode && hasAnswered && (_jsx("div", { className: cn('flex items-center gap-1.5 rounded-full px-3 py-1 text-xs font-semibold', isAnswerCorrect
|
|
51
51
|
? 'bg-green-100 text-green-700'
|
|
52
|
-
: 'bg-red-100 text-red-700'), children: isAnswerCorrect ? (_jsxs(_Fragment, { children: [_jsx(Check, { className: "h-3.5 w-3.5" }), " \u0110\u00FAng"] })) : (_jsxs(_Fragment, { children: [_jsx(X, { className: "h-3.5 w-3.5" }), " Sai"] })) }))] }), _jsxs("div", { className: "p-
|
|
52
|
+
: 'bg-red-100 text-red-700'), children: isAnswerCorrect ? (_jsxs(_Fragment, { children: [_jsx(Check, { className: "h-3.5 w-3.5" }), " \u0110\u00FAng"] })) : (_jsxs(_Fragment, { children: [_jsx(X, { className: "h-3.5 w-3.5" }), " Sai"] })) }))] }), _jsxs("div", { className: "p-3 space-y-3", children: [questionData.audioUrl && (_jsx(QuestionAudioPlayer, { audioUrl: questionData.audioUrl })), _jsxs("div", { className: "space-y-2", children: [_jsx("span", { className: "text-xs font-medium text-gray-500 uppercase tracking-wider", children: "Ch\u1ECDn \u0111\u00E1p \u00E1n \u0111\u00FAng" }), _jsx("div", { className: "grid grid-cols-2 gap-3 sm:grid-cols-3", children: questionData.options.map((option, optIndex) => {
|
|
53
53
|
const { isSelected, isCorrect, isWrong } = getOptionStatus(option.id);
|
|
54
54
|
return (_jsxs("button", { type: "button", onClick: () => handleOptionSelect(option.id), disabled: isReviewMode, className: cn('group relative flex flex-col items-center gap-2 rounded-xl border-2 p-3 transition-all', isReviewMode
|
|
55
55
|
? isCorrect
|
|
@@ -3,19 +3,18 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import { useState, useRef, useEffect } from 'react';
|
|
4
4
|
import { Button } from '../../../../components/ui/button';
|
|
5
5
|
import { Input } from '../../../../components/ui/input';
|
|
6
|
-
import { Textarea } from '../../../../components/ui/textarea';
|
|
7
6
|
import { Label } from '../../../../components/ui/label';
|
|
8
|
-
import { Card, CardContent, CardHeader
|
|
9
|
-
import {
|
|
7
|
+
import { Card, CardContent, CardHeader } from '../../../../components/ui/card';
|
|
8
|
+
import { Play, Pause, Plus, Trash2, Music, CheckCircle2, ListChecks, ImageIcon } from 'lucide-react';
|
|
10
9
|
import { toast } from 'sonner';
|
|
11
10
|
import { FileUpload } from '../../../../components/ui/file-upload';
|
|
11
|
+
import { CompactCreatorHeader, CompactExplanationToggle, CompactFieldCard, CompactPreviewBanner, } from '../../_shared/components/compact';
|
|
12
12
|
import { ListenAndChooseFromAnswerGroupClient } from './ListenAndChooseFromAnswerGroupClient';
|
|
13
13
|
import { usePresignedFileUrl, useAudioPreview, useDebouncedCallback } from '../../../../shared/lib/hooks';
|
|
14
14
|
import { useReactHookForm } from '../../../../shared/lib/hooks/useReactHookForm';
|
|
15
15
|
import { listenAndChooseFromAnswerGroupFormSchema } from '../../../../shared/lib/validation/listen-and-choose-from-answer-group.validation';
|
|
16
16
|
import { useAnswerGroupQuestionGroup } from '../../_shared/hooks/useAnswerGroupQuestionGroup';
|
|
17
17
|
import { generateObjectId } from '../../_shared/utils/id-generator';
|
|
18
|
-
import { CreatorGuide } from '../../components/CreatorGuide';
|
|
19
18
|
// ─── Sub-component: Audio preview ────────────────────────────────────────────
|
|
20
19
|
function AudioPreview({ audioUrl }) {
|
|
21
20
|
const { previewUrl } = usePresignedFileUrl(audioUrl);
|
|
@@ -190,34 +189,35 @@ function ListenAndChooseFromAnswerGroupCreatorContent({ initialData, onSave, onC
|
|
|
190
189
|
explanation,
|
|
191
190
|
isBackup,
|
|
192
191
|
};
|
|
193
|
-
return (_jsxs("div", { className: "space-y-
|
|
192
|
+
return (_jsxs("div", { className: "space-y-4", children: [_jsx(CompactPreviewBanner, { onBack: () => setIsClientMode(false) }), _jsx(ListenAndChooseFromAnswerGroupClient, { questionData: questionData, onSubmit: () => { }, isReviewMode: true, userAnswer: correctAnswer || undefined, correctAnswer: correctAnswer || undefined, explanation: explanation })] }));
|
|
194
193
|
}
|
|
195
194
|
// ─── Creator UI ────────────────────────────────────────────────────────────
|
|
196
|
-
return (_jsxs("div", { className: "space-y-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
195
|
+
return (_jsxs("div", { className: "space-y-3", children: [_jsx(CompactFieldCard, { header: _jsx(CompactCreatorHeader, { icon: _jsx(ListChecks, { className: "h-3.5 w-3.5" }), title: "Nh\u00F3m \u0111\u00E1p \u00E1n", extraActions: _jsxs("span", { className: "inline-flex items-center rounded-full bg-gray-100 px-2 py-0.5 text-xs font-medium text-gray-600", children: ["Nh\u00F3m ", currentGroupNumber] }), points: {
|
|
196
|
+
value: points ?? 1,
|
|
197
|
+
onValueChange: (value) => {
|
|
198
|
+
setPoints(value);
|
|
199
|
+
form.clearErrors('points');
|
|
200
|
+
},
|
|
201
|
+
error: form.formState.errors.points?.message,
|
|
202
|
+
}, preview: {
|
|
203
|
+
onClick: () => setIsClientMode(true),
|
|
204
|
+
disabled: options.filter((o) => o.imageUrl).length < 2 || !audioUrl,
|
|
205
|
+
} }), contentClassName: "hidden", children: null }), _jsxs(CompactFieldCard, { header: _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-7 w-7 items-center justify-center rounded-md bg-gray-100", children: _jsx(ListChecks, { className: "h-3.5 w-3.5 text-gray-600" }) }), _jsxs("p", { className: "text-sm font-semibold text-gray-800", children: ["Nh\u00F3m \u0111\u00E1p \u00E1n (", options.length, " \u0111\u00E1p \u00E1n)"] })] }), _jsxs(Button, { variant: "outline", size: "sm", onClick: addOption, 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"] })] }), contentClassName: "grid grid-cols-1 md:grid-cols-2 gap-4", children: [form.formState.errors.options?.root && (_jsx("p", { className: "col-span-full text-sm text-red-500", children: form.formState.errors.options.root.message })), options.map((option, index) => (_jsxs(Card, { className: "border border-gray-200", children: [_jsx(CardHeader, { className: "pb-2 pt-3 px-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-7 w-7 items-center justify-center rounded-lg bg-gradient-to-br from-amber-500 to-orange-600 text-white text-sm font-bold shadow-sm", children: String.fromCharCode(65 + index) }), _jsxs("span", { className: "text-sm font-semibold text-gray-700", children: ["\u0110\u00E1p \u00E1n ", String.fromCharCode(65 + index)] })] }), options.length > 2 && (_jsx(Button, { variant: "ghost", size: "sm", onClick: () => removeOption(option.id), className: "h-7 w-7 p-0 text-red-500 hover:text-red-700 hover:bg-red-50", children: _jsx(Trash2, { className: "h-3.5 w-3.5" }) }))] }) }), _jsxs(CardContent, { className: "space-y-3 px-4 pb-4", children: [_jsxs("div", { children: [_jsx(FileUpload, { id: `option-image-${option.id}`, label: "\u1EA2nh \u0111\u00E1p \u00E1n", accept: "image/*", value: option.imageUrl, onChange: (url) => updateOptionImageUrl(option.id, url), maxSize: 5, placeholder: "Upload \u1EA3nh cho \u0111\u00E1p \u00E1n", required: true, autoUpload: true, prefix: "questions" }), form.formState.errors.options?.[index]?.imageUrl && (_jsx("p", { className: "mt-1 text-sm text-red-500", children: form.formState.errors.options[index].imageUrl.message })), option.imageUrl && _jsx(OptionImagePreview, { imageUrl: option.imageUrl })] }), _jsxs("div", { children: [_jsx(Label, { className: "font-medium", children: "Nh\u00E3n" }), _jsx(Input, { value: option.label || '', onChange: (e) => updateOptionLabel(option.id, e.target.value), placeholder: "Nh\u1EADp label / m\u00F4 t\u1EA3 ng\u1EAFn", className: "mt-1" })] })] })] }, option.id))), options.length === 0 && (_jsxs("div", { className: "col-span-1 md:col-span-2 flex flex-col items-center justify-center rounded-lg border-2 border-dashed border-amber-300 p-8 bg-amber-50/50", children: [_jsx(ImageIcon, { className: "h-10 w-10 text-amber-400 mb-2" }), _jsx("p", { className: "text-sm text-gray-500 mb-3", children: "Ch\u01B0a c\u00F3 \u0111\u00E1p \u00E1n n\u00E0o" }), _jsxs(Button, { variant: "outline", size: "sm", onClick: addOption, className: "gap-2", children: [_jsx(Plus, { className: "h-4 w-4" }), "Th\u00EAm \u0111\u00E1p \u00E1n \u0111\u1EA7u ti\u00EAn"] })] }))] }), _jsxs(CompactFieldCard, { header: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-7 w-7 items-center justify-center rounded-md bg-gray-100", children: _jsx(Music, { className: "h-3.5 w-3.5 text-gray-600" }) }), _jsx("p", { className: "text-sm font-semibold text-gray-800", children: "C\u00E2u h\u1ECFi" })] }), children: [_jsxs("div", { children: [_jsx(FileUpload, { id: "question-audio", label: "File \u00E2m thanh", accept: "audio/*", value: audioUrl, onChange: (url) => {
|
|
206
|
+
setAudioUrl(url);
|
|
207
|
+
form.clearErrors('audioUrl');
|
|
208
|
+
}, maxSize: 10, placeholder: "Upload file audio", required: true, autoUpload: true, prefix: "questions" }), form.formState.errors.audioUrl && (_jsx("p", { className: "mt-1 text-sm text-red-500", children: form.formState.errors.audioUrl.message })), audioUrl && _jsx(AudioPreview, { audioUrl: audioUrl })] }), _jsxs("div", { children: [_jsx(Label, { className: "text-sm font-semibold text-gray-700 mb-2 block", children: "Ch\u1ECDn \u0111\u00E1p \u00E1n \u0111\u00FAng *" }), options.filter((o) => o.imageUrl).length > 0 ? (_jsx("div", { className: "grid grid-cols-2 gap-3 sm:grid-cols-3", children: options.map((option, optIndex) => {
|
|
209
|
+
if (!option.imageUrl)
|
|
210
|
+
return null;
|
|
211
|
+
const isSelected = correctAnswer === option.id;
|
|
212
|
+
return (_jsxs("button", { type: "button", onClick: () => {
|
|
213
|
+
setCorrectAnswer(option.id);
|
|
214
|
+
form.clearErrors('correctAnswer');
|
|
215
|
+
}, className: `relative text-left rounded-xl border-2 p-2 transition-all ${isSelected
|
|
216
|
+
? 'border-green-500 bg-green-50 ring-2 ring-green-200 shadow-md'
|
|
217
|
+
: 'border-gray-200 hover:border-gray-400 hover:shadow-sm'}`, children: [_jsxs("div", { className: "flex flex-col items-center gap-2", children: [_jsx("div", { className: `flex h-7 w-7 flex-shrink-0 items-center justify-center rounded-lg text-sm font-bold shadow-sm ${isSelected
|
|
218
|
+
? 'bg-green-500 text-white'
|
|
219
|
+
: 'bg-gray-100 text-gray-600'}`, children: String.fromCharCode(65 + optIndex) }), _jsx(OptionImagePreviewInline, { imageUrl: option.imageUrl }), option.label && (_jsx("p", { className: `text-xs text-center ${isSelected ? 'text-green-700 font-medium' : 'text-gray-600'}`, children: option.label }))] }), isSelected && (_jsx("div", { className: "absolute top-1 right-1", children: _jsx(CheckCircle2, { className: "h-5 w-5 text-green-500" }) }))] }, option.id));
|
|
220
|
+
}) })) : (_jsx("p", { className: "text-sm text-amber-600 italic", children: "Vui l\u00F2ng upload \u1EA3nh cho \u0111\u00E1p \u00E1n \u1EDF ph\u1EA7n tr\u00EAn tr\u01B0\u1EDBc" })), form.formState.errors.correctAnswer && (_jsx("div", { className: "mt-3 rounded-lg border border-red-200 bg-red-50 p-3", children: _jsxs("p", { className: "text-sm text-red-700", children: ["\u26A0\uFE0F ", form.formState.errors.correctAnswer.message] }) })), !form.formState.errors.correctAnswer && !correctAnswer && options.filter((o) => o.imageUrl).length > 0 && (_jsx("p", { className: "mt-1 text-xs text-amber-600", children: "Ch\u01B0a ch\u1ECDn \u0111\u00E1p \u00E1n \u0111\u00FAng" }))] })] }), _jsx(CompactExplanationToggle, { value: explanation, onValueChange: setExplanation, defaultVisible: Boolean(explanation) })] }));
|
|
221
221
|
}
|
|
222
222
|
// ─── Inline image preview (for correct answer selection) ─────────────────────
|
|
223
223
|
function OptionImagePreviewInline({ imageUrl }) {
|
|
@@ -55,9 +55,9 @@ export function ListenAndChooseObjectsInSceneClient({ questionData, onSubmit, is
|
|
|
55
55
|
const hasAnswered = selectedObjectId !== '';
|
|
56
56
|
const selectedNode = questionData.objectNodes.find((n) => n.id === selectedObjectId);
|
|
57
57
|
const isAnswerCorrect = isReviewMode && correctAnswer && selectedObjectId === correctAnswer.objectId;
|
|
58
|
-
return (_jsxs("div", { className: "space-y-4", children: [questionData.isBackup && (_jsxs("div", { className: "inline-flex self-center items-center gap-1.5 px-3 py-1.5 rounded-md bg-amber-50 text-amber-700 border border-amber-200 text-xs font-bold shadow-sm", children: [_jsx("span", { children: "\uD83D\uDD04" }), " C\u00E2u h\u1ECFi d\u1EF1 ph\u00F2ng"] })), _jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-gray-100 bg-
|
|
58
|
+
return (_jsxs("div", { className: "space-y-4", children: [questionData.isBackup && (_jsxs("div", { className: "inline-flex self-center items-center gap-1.5 px-3 py-1.5 rounded-md bg-amber-50 text-amber-700 border border-amber-200 text-xs font-bold shadow-sm", children: [_jsx("span", { children: "\uD83D\uDD04" }), " C\u00E2u h\u1ECFi d\u1EF1 ph\u00F2ng"] })), _jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-gray-100 bg-gray-50 px-3 py-2", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "flex h-7 w-7 items-center justify-center rounded-md bg-gray-100 text-gray-600", children: _jsx(CircleHelp, { className: "h-4 w-4" }) }), _jsx("span", { className: "text-sm font-medium text-gray-700", children: "Nghe v\u00E0 ch\u1ECDn \u0111\u1ED1i t\u01B0\u1EE3ng trong c\u1EA3nh" })] }), isReviewMode && hasAnswered && (_jsxs("div", { className: cn('flex items-center gap-1.5 rounded-full px-3 py-1 text-xs font-semibold', isAnswerCorrect
|
|
59
59
|
? 'bg-green-100 text-green-700'
|
|
60
|
-
: 'bg-red-100 text-red-700'), children: [isAnswerCorrect ? (_jsx(Check, { className: "h-3.5 w-3.5" })) : (_jsx(X, { className: "h-3.5 w-3.5" })), isAnswerCorrect ? 'Đúng' : 'Sai'] }))] }), _jsxs("div", { className: "space-y-
|
|
60
|
+
: 'bg-red-100 text-red-700'), children: [isAnswerCorrect ? (_jsx(Check, { className: "h-3.5 w-3.5" })) : (_jsx(X, { className: "h-3.5 w-3.5" })), isAnswerCorrect ? 'Đúng' : 'Sai'] }))] }), _jsxs("div", { className: "space-y-3 p-3", children: [audioUrl && (_jsxs("div", { className: "flex items-center gap-3 rounded-lg border border-blue-100 bg-gradient-to-r from-blue-50 to-indigo-50 p-3", children: [_jsx("div", { className: "flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-full bg-blue-500 text-white", children: _jsx(Volume2, { className: "h-4 w-4" }) }), _jsx("audio", { controls: true, className: "h-8 w-full", src: audioUrl, children: "Tr\u00ECnh duy\u1EC7t kh\u00F4ng h\u1ED7 tr\u1EE3 audio." })] })), _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: "Scene" })] }) }), _jsx("div", { className: "relative p-2", children: bgLoading ? (_jsx("div", { className: "flex h-64 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" }) })) : backgroundUrl ? (_jsxs("div", { className: "relative w-full", children: [_jsx("img", { src: backgroundUrl, alt: "Scene background", className: "w-full rounded-md object-contain", draggable: false }), questionData.objectNodes.map((node) => {
|
|
61
61
|
const isSelected = selectedObjectId === node.id;
|
|
62
62
|
const isCorrectNode = isReviewMode && correctAnswer?.objectId === node.id;
|
|
63
63
|
const isWrongNode = isReviewMode && isSelected && correctAnswer?.objectId !== node.id;
|
|
@@ -4,12 +4,10 @@ import { useState, useRef, useEffect, useCallback } from 'react';
|
|
|
4
4
|
import { Button } from '../../../../components/ui/button';
|
|
5
5
|
import { Label } from '../../../../components/ui/label';
|
|
6
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
7
|
import { RadioGroup, RadioGroupItem } from '../../../../components/ui/radio-group';
|
|
11
|
-
import {
|
|
8
|
+
import { Plus, Trash2, Image as ImageIcon, Volume2, Lightbulb, MousePointerClick, GripVertical, ListChecks, } from 'lucide-react';
|
|
12
9
|
import { FileUpload } from '../../../../components/ui/file-upload';
|
|
10
|
+
import { CompactCreatorHeader, CompactExplanationToggle, CompactFieldCard, CompactPreviewBanner, } from '../../_shared/components/compact';
|
|
13
11
|
import { usePresignedFileUrl, useDebouncedCallback } from '../../../../shared/lib/hooks';
|
|
14
12
|
import { useReactHookForm } from '../../../../shared/lib/hooks/useReactHookForm';
|
|
15
13
|
import { listenAndChooseObjectsInSceneFormSchema, } from '../../../../shared/lib/validation/listen-and-choose-objects-in-scene.validation';
|
|
@@ -17,7 +15,6 @@ import { ListenAndChooseObjectsInSceneClient } from './ListenAndChooseObjectsInS
|
|
|
17
15
|
import { useSceneQuestionGroup } from '../../_shared/hooks/useSceneQuestionGroup';
|
|
18
16
|
import { generateObjectId } from '../../_shared/utils/id-generator';
|
|
19
17
|
import { useExamQuestionStore } from '../../../../shared/lib/stores/examQuestionStore';
|
|
20
|
-
import { CreatorGuide } from '../../components/CreatorGuide';
|
|
21
18
|
/**
|
|
22
19
|
* Scene Object Editor - An individual object node editor row
|
|
23
20
|
*/
|
|
@@ -347,7 +344,7 @@ function ListenAndChooseObjectsInSceneCreatorContent({ initialData, onSave, onSa
|
|
|
347
344
|
// ============ RENDER ============
|
|
348
345
|
// Preview mode
|
|
349
346
|
if (isClientMode) {
|
|
350
|
-
return (_jsxs("div", { className: "space-y-
|
|
347
|
+
return (_jsxs("div", { className: "space-y-4", children: [_jsx(CompactPreviewBanner, { onBack: () => setIsClientMode(false) }), _jsx(ListenAndChooseObjectsInSceneClient, { questionData: {
|
|
351
348
|
audioUrl: audioPreviewUrl || audioUrl,
|
|
352
349
|
backgroundImageUrl: backgroundPreviewUrl || backgroundImageUrl,
|
|
353
350
|
objectNodes,
|
|
@@ -355,31 +352,27 @@ function ListenAndChooseObjectsInSceneCreatorContent({ initialData, onSave, onSa
|
|
|
355
352
|
}
|
|
356
353
|
const formErrors = form.formState.errors;
|
|
357
354
|
// Creator mode
|
|
358
|
-
return (_jsxs("div", { className: "space-y-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
{ text:
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
form.clearErrors(); // Xóa lỗi validation khi người dùng đã chọn đáp án
|
|
380
|
-
}, className: "grid gap-3 sm:grid-cols-2 md:grid-cols-3", children: objectNodes.map((node, index) => (_jsxs("label", { className: `flex cursor-pointer items-start gap-3 rounded-lg border-2 p-3 transition-all ${correctObjectId === node.id
|
|
381
|
-
? 'border-green-500 bg-green-50 ring-1 ring-green-500'
|
|
382
|
-
: 'border-gray-200 hover:border-green-300 hover:bg-green-50/50'}`, children: [_jsx(RadioGroupItem, { value: node.id, id: `correct-${node.id}`, className: "mt-1 flex-shrink-0" }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsxs("p", { className: "font-medium text-gray-900 line-clamp-1 break-all", children: [index + 1, ". ", node.label || '(Chưa đặt tên)'] }), node.imageUrl ? (_jsx("div", { className: "mt-2 h-12 w-full max-w-16 rounded overflow-hidden bg-white border border-gray-200", children: _jsx(PresignedImage, { url: node.imageUrl, alt: node.label }) })) : (_jsx("div", { className: "mt-2 text-xs text-gray-400 italic", children: "Ch\u01B0a c\u00F3 h\u00ECnh" }))] })] }, node.id))) }))] }), formErrors.correctObjectId && (_jsx("div", { className: "rounded-lg border border-red-200 bg-red-50 p-3 mt-3", children: _jsxs("p", { className: "text-sm text-red-700", children: ["\u26A0\uFE0F ", formErrors.correctObjectId.message] }) }))] })] }), _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, { value: explanation, onChange: (e) => setExplanation(e.target.value), 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" }) })] })] }));
|
|
355
|
+
return (_jsxs("div", { className: "space-y-3", children: [_jsx(CompactFieldCard, { header: _jsx(CompactCreatorHeader, { icon: _jsx(ListChecks, { className: "h-3.5 w-3.5" }), title: "Nh\u00F3m \u0111\u00E1p \u00E1n", extraActions: _jsxs("span", { className: "inline-flex items-center rounded-full bg-gray-100 px-2 py-0.5 text-xs font-medium text-gray-600", children: ["Nh\u00F3m ", currentGroupNumber] }), points: {
|
|
356
|
+
value: points,
|
|
357
|
+
onValueChange: setPoints,
|
|
358
|
+
}, preview: {
|
|
359
|
+
onClick: () => setIsClientMode(true),
|
|
360
|
+
disabled: !backgroundImageUrl || objectNodes.length < 2,
|
|
361
|
+
} }), contentClassName: "hidden", children: null }), _jsxs(CompactFieldCard, { header: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-7 w-7 items-center justify-center rounded-md bg-gray-100", children: _jsx(ImageIcon, { className: "h-3.5 w-3.5 text-gray-600" }) }), _jsx("p", { className: "text-sm font-semibold text-gray-800", children: "\u1EA2nh n\u1EC1n & \u0110\u1ED1i t\u01B0\u1EE3ng trong Scene" })] }), children: [_jsx("div", { className: `rounded-xl border-2 p-5 bg-gradient-to-r from-blue-50 to-indigo-50 border-blue-200`, children: _jsxs("div", { className: "space-y-2", children: [_jsxs(Label, { className: "flex items-center gap-2 text-base font-semibold text-blue-800", children: [_jsx(ImageIcon, { className: "h-4 w-4 text-blue-600" }), "\u1EA2nh n\u1EC1n (Scene) ", _jsx("span", { className: "text-red-500", children: "*" })] }), _jsx(FileUpload, { id: "scene-background", label: "", accept: "image/*", value: backgroundImageUrl, onChange: (url) => {
|
|
362
|
+
setBackgroundImageUrl(url);
|
|
363
|
+
form.clearErrors();
|
|
364
|
+
}, onPresignedUrlChange: setBackgroundPreviewUrl, maxSize: 10, placeholder: "Upload h\u00ECnh \u1EA3nh n\u1EC1n c\u1EA3nh", autoUpload: true, prefix: "questions/scenes" }), formErrors.backgroundImageUrl && (_jsx("p", { className: "mt-1 text-sm text-red-500", children: formErrors.backgroundImageUrl.message })), backgroundPreviewUrl && (_jsxs("div", { className: "mt-2 overflow-hidden rounded-lg border border-gray-200 bg-gray-50 isolate", children: [_jsx("div", { className: "border-b border-gray-100 px-3 py-1.5", children: _jsx("span", { className: "text-xs font-medium text-gray-500", children: "\u1EA2nh n\u1EC1n - K\u00E9o th\u1EA3 \u0111\u1ED1i t\u01B0\u1EE3ng \u0111\u1EC3 \u0111\u1ECBnh v\u1ECB" }) }), _jsx("div", { className: "p-2", children: _jsx(ScenePreviewWithDrag, { backgroundUrl: backgroundPreviewUrl, objectNodes: objectNodes, correctObjectId: correctObjectId, onNodeDrag: handleNodeDrag, onNodeResize: handleNodeResize }) })] }))] }) }), _jsxs("div", { className: "pt-4 border-t border-gray-100", children: [_jsxs("div", { className: "flex items-center justify-between mb-4", 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(MousePointerClick, { className: "h-3.5 w-3.5 text-indigo-600" }) }), _jsxs("h3", { className: "text-lg font-semibold text-gray-800", children: ["\u0110\u1ED1i t\u01B0\u1EE3ng (", objectNodes.length, " items)"] })] }), _jsxs(Button, { variant: "outline", size: "sm", onClick: addObjectNode, 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\u1ED1i t\u01B0\u1EE3ng"] })] }), _jsx("div", { className: "space-y-3", children: objectNodes.length === 0 ? (_jsxs("div", { className: "flex flex-col items-center gap-3 rounded-xl border-2 border-dashed border-gray-200 bg-gray-50/50 py-10", children: [_jsx("div", { className: "flex h-12 w-12 items-center justify-center rounded-full bg-gray-100", children: _jsx(ImageIcon, { className: "h-6 w-6 text-gray-400" }) }), _jsx("p", { className: "text-sm text-gray-500", children: "Ch\u01B0a c\u00F3 \u0111\u1ED1i t\u01B0\u1EE3ng n\u00E0o. Nh\u1EA5n \"Th\u00EAm \u0111\u1ED1i t\u01B0\u1EE3ng\" \u0111\u1EC3 t\u1EA1o c\u00E1c \u0111\u1ED1i t\u01B0\u1EE3ng chung cho nh\u00F3m." })] })) : (objectNodes.map((node, index) => (_jsx(SceneObjectEditor, { node: node, index: index, isCorrect: false, onUpdate: updateObjectNode, onRemove: removeObjectNode, onSelectCorrect: () => { }, errors: {
|
|
365
|
+
label: formErrors.objectNodes?.[index]?.label?.message,
|
|
366
|
+
imageUrl: formErrors.objectNodes?.[index]?.imageUrl?.message,
|
|
367
|
+
} }, node.id)))) })] })] }), _jsxs(CompactFieldCard, { header: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-7 w-7 items-center justify-center rounded-md bg-gray-100", children: _jsx(Volume2, { className: "h-3.5 w-3.5 text-gray-600" }) }), _jsx("p", { className: "text-sm font-semibold text-gray-800", children: "Th\u00F4ng tin c\u00E2u h\u1ECFi" })] }), children: [_jsxs("div", { className: "space-y-2", children: [_jsxs(Label, { className: "flex items-center gap-2 text-base font-semibold text-gray-800", children: [_jsx(Volume2, { className: "h-4 w-4 text-indigo-500" }), "Audio c\u00E2u h\u1ECFi ", _jsx("span", { className: "text-red-500", children: "*" })] }), _jsx(FileUpload, { id: "scene-audio", label: "", accept: "audio/*", value: audioUrl, onChange: (url) => {
|
|
368
|
+
setAudioUrl(url);
|
|
369
|
+
form.clearErrors();
|
|
370
|
+
}, onPresignedUrlChange: setAudioPreviewUrl, maxSize: 10, placeholder: "Upload file audio c\u00E2u h\u1ECFi", autoUpload: true, prefix: "questions/audio", examId: examId || undefined, partId: partId || undefined }), formErrors.audioUrl && (_jsx("p", { className: "mt-1 text-sm text-red-500", children: formErrors.audioUrl.message })), audioPreviewUrl && (_jsx("audio", { controls: true, className: "mt-2 w-full", src: audioPreviewUrl, children: "Tr\u00ECnh duy\u1EC7t kh\u00F4ng h\u1ED7 tr\u1EE3 audio." }))] }), _jsxs("div", { className: "space-y-3", 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-green-100", children: _jsx(Lightbulb, { className: "h-3.5 w-3.5 text-green-600" }) }), _jsxs(Label, { className: "text-base font-semibold text-gray-800", children: ["\u0110\u00E1p \u00E1n \u0111\u00FAng c\u1EE7a c\u00E2u h\u1ECFi n\u00E0y ", _jsx("span", { className: "text-red-500", children: "*" })] })] }), objectNodes.length === 0 ? (_jsx("p", { className: "text-sm text-amber-600 italic", children: "Vui l\u00F2ng th\u00EAm \u0111\u1ED1i t\u01B0\u1EE3ng \u1EDF b\u00EAn tr\u00EAn tr\u01B0\u1EDBc khi ch\u1ECDn \u0111\u00E1p \u00E1n." })) : (_jsx(RadioGroup, { value: correctObjectId, onValueChange: (val) => {
|
|
371
|
+
setCorrectObjectId(val);
|
|
372
|
+
form.clearErrors(); // Xóa lỗi validation khi người dùng đã chọn đáp án
|
|
373
|
+
}, className: "grid gap-3 sm:grid-cols-2 md:grid-cols-3", children: objectNodes.map((node, index) => (_jsxs("label", { className: `flex cursor-pointer items-start gap-3 rounded-lg border-2 p-3 transition-all ${correctObjectId === node.id
|
|
374
|
+
? 'border-green-500 bg-green-50 ring-1 ring-green-500'
|
|
375
|
+
: 'border-gray-200 hover:border-green-300 hover:bg-green-50/50'}`, children: [_jsx(RadioGroupItem, { value: node.id, id: `correct-${node.id}`, className: "mt-1 flex-shrink-0" }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsxs("p", { className: "font-medium text-gray-900 line-clamp-1 break-all", children: [index + 1, ". ", node.label || '(Chưa đặt tên)'] }), node.imageUrl ? (_jsx("div", { className: "mt-2 h-12 w-full max-w-16 rounded overflow-hidden bg-white border border-gray-200", children: _jsx(PresignedImage, { url: node.imageUrl, alt: node.label }) })) : (_jsx("div", { className: "mt-2 text-xs text-gray-400 italic", children: "Ch\u01B0a c\u00F3 h\u00ECnh" }))] })] }, node.id))) }))] }), formErrors.correctObjectId && (_jsx("div", { className: "rounded-lg border border-red-200 bg-red-50 p-3 mt-3", children: _jsxs("p", { className: "text-sm text-red-700", children: ["\u26A0\uFE0F ", formErrors.correctObjectId.message] }) }))] }), _jsx(CompactExplanationToggle, { value: explanation, onValueChange: setExplanation, defaultVisible: Boolean(explanation) })] }));
|
|
383
376
|
}
|
|
384
377
|
export function ListenAndChooseObjectsInSceneCreator(props) {
|
|
385
378
|
return _jsx(ListenAndChooseObjectsInSceneCreatorContent, { ...props });
|
|
@@ -76,9 +76,9 @@ export function ListenAndDragObjectsIntoSceneClient({ questionData, onSubmit, is
|
|
|
76
76
|
selectedDropZoneId === correctAnswer.dropZoneId;
|
|
77
77
|
const selectedObject = questionData.objectNodes.find((n) => n.id === selectedObjectId);
|
|
78
78
|
const selectedDropZone = questionData.dropZones.find((z) => z.id === selectedDropZoneId);
|
|
79
|
-
return (_jsxs("div", { className: "space-y-4", children: [questionData.isBackup && (_jsxs("div", { className: "inline-flex self-center items-center gap-1.5 px-3 py-1.5 rounded-md bg-amber-50 text-amber-700 border border-amber-200 text-xs font-bold shadow-sm", children: [_jsx("span", { children: "\uD83D\uDD04" }), " C\u00E2u h\u1ECFi d\u1EF1 ph\u00F2ng"] })), _jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-gray-100 bg-
|
|
79
|
+
return (_jsxs("div", { className: "space-y-4", children: [questionData.isBackup && (_jsxs("div", { className: "inline-flex self-center items-center gap-1.5 px-3 py-1.5 rounded-md bg-amber-50 text-amber-700 border border-amber-200 text-xs font-bold shadow-sm", children: [_jsx("span", { children: "\uD83D\uDD04" }), " C\u00E2u h\u1ECFi d\u1EF1 ph\u00F2ng"] })), _jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-gray-100 bg-gray-50 px-3 py-2", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "flex h-7 w-7 items-center justify-center rounded-md bg-gray-100 text-gray-600", children: _jsx(CircleHelp, { className: "h-4 w-4" }) }), _jsx("span", { className: "text-sm font-medium text-gray-700", children: "Nghe v\u00E0 k\u00E9o th\u1EA3 \u0111\u1ED1i t\u01B0\u1EE3ng v\u00E0o c\u1EA3nh" })] }), isReviewMode && hasAnswered && (_jsxs("div", { className: cn('flex items-center gap-1.5 rounded-full px-3 py-1 text-xs font-semibold', isAnswerCorrect
|
|
80
80
|
? 'bg-green-100 text-green-700'
|
|
81
|
-
: 'bg-red-100 text-red-700'), children: [isAnswerCorrect ? (_jsx(Check, { className: "h-3.5 w-3.5" })) : (_jsx(X, { className: "h-3.5 w-3.5" })), isAnswerCorrect ? 'Đúng' : 'Sai'] }))] }), _jsxs("div", { className: "space-y-
|
|
81
|
+
: 'bg-red-100 text-red-700'), children: [isAnswerCorrect ? (_jsx(Check, { className: "h-3.5 w-3.5" })) : (_jsx(X, { className: "h-3.5 w-3.5" })), isAnswerCorrect ? 'Đúng' : 'Sai'] }))] }), _jsxs("div", { className: "space-y-3 p-3", children: [audioUrl && (_jsxs("div", { className: "flex items-center gap-3 rounded-lg border border-orange-100 bg-gradient-to-r from-orange-50 to-amber-50 p-3", children: [_jsx("div", { className: "flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-full bg-orange-500 text-white", children: _jsx(Volume2, { className: "h-4 w-4" }) }), _jsx("audio", { controls: true, className: "h-8 w-full", src: audioUrl, children: "Tr\u00ECnh duy\u1EC7t kh\u00F4ng h\u1ED7 tr\u1EE3 audio." })] })), _jsxs("div", { className: "flex gap-4", children: [_jsx("div", { className: "flex w-24 flex-shrink-0 flex-col gap-2", children: questionData.objectNodes.slice(0, Math.ceil(questionData.objectNodes.length / 2)).map((node) => {
|
|
82
82
|
const isSelected = selectedObjectId === node.id;
|
|
83
83
|
const isDragging = draggedObjectId === node.id;
|
|
84
84
|
const isCorrectObj = isReviewMode && correctAnswer?.objectId === node.id;
|