@tinyweb_dev/oe-exam-sdk 0.1.1 → 0.1.2
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/api/exam-create.js +1 -179
- package/dist/api/exam-create.types.js +1 -1
- package/dist/api/exam-questions.js +1 -98
- package/dist/api/exam-questions.types.js +1 -1
- package/dist/api/exam-taking.js +1 -73
- package/dist/api/exam-taking.types.js +1 -1
- package/dist/api/index.js +1 -6
- package/dist/api/result-review.js +1 -50
- package/dist/api/result-review.types.js +1 -1
- package/dist/components/CueCardEditor.js +1 -58
- package/dist/components/common/ResolvedImage.js +1 -15
- package/dist/components/exams/CreateExamInfoForm.js +1 -35
- package/dist/components/exams/CreateExamPageContainer.js +1 -103
- package/dist/components/exams/ExamCreator.js +1 -89
- package/dist/components/exams/ExamPartTabs.js +1 -40
- package/dist/components/exams/ExamQuestionGrid.js +1 -18
- package/dist/components/exams/ExamQuestionsPageContainer.js +1 -90
- package/dist/components/exams/PartAudioUploader.js +1 -9
- package/dist/components/exams/TemplateSelector.js +1 -66
- package/dist/components/exams/api.js +1 -3
- package/dist/components/exams/create-exam.validation.js +1 -18
- package/dist/components/exams/exam-template.utils.js +1 -18
- package/dist/components/exams/index.js +1 -10
- package/dist/components/exams/take/ExamTakingPageContainer.js +1 -153
- package/dist/components/exams/take/GenericQuestionRenderer.js +1 -14
- package/dist/components/exams/take/exam-taking.utils.js +1 -88
- package/dist/components/exams/take/hooks/useSpeakingRecorder.js +1 -24
- package/dist/components/exams/take/index.js +1 -2
- package/dist/components/exams/take/types.js +1 -1
- package/dist/components/index.js +1 -42
- package/dist/components/questions/_shared/config/question-types.config.js +1 -333
- package/dist/components/questions/_shared/constants/color-palette.js +1 -21
- package/dist/components/questions/_shared/hooks/useAnswerGroupQuestionGroup.js +1 -105
- package/dist/components/questions/_shared/hooks/useBasicQuestionGroup.js +1 -222
- package/dist/components/questions/_shared/hooks/useCompareImagesGroup.js +1 -140
- package/dist/components/questions/_shared/hooks/useImageQuestionGroup.js +1 -123
- package/dist/components/questions/_shared/hooks/useInfoExchangeGroup.js +1 -50
- package/dist/components/questions/_shared/hooks/useQuestionFormState.js +1 -54
- package/dist/components/questions/_shared/hooks/useQuestionInitialization.js +1 -123
- package/dist/components/questions/_shared/hooks/useQuestionListGroup.js +1 -121
- package/dist/components/questions/_shared/hooks/useQuestionOnChange.js +1 -70
- package/dist/components/questions/_shared/hooks/useSceneQuestionGroup.js +1 -194
- package/dist/components/questions/_shared/index.js +1 -7
- package/dist/components/questions/_shared/types/answer-the-question.type.js +1 -1
- package/dist/components/questions/_shared/types/arrange-letters-into-words.type.js +1 -1
- package/dist/components/questions/_shared/types/choose-correct-adjective.type.js +1 -3
- package/dist/components/questions/_shared/types/color-region.config.js +1 -1
- package/dist/components/questions/_shared/types/fill-in-blank.type.js +1 -1
- package/dist/components/questions/_shared/types/fill-missing-words-in-grid.type.js +1 -1
- package/dist/components/questions/_shared/types/gn-speaking-interview.type.js +1 -1
- package/dist/components/questions/_shared/types/index.js +1 -12
- package/dist/components/questions/_shared/types/label-the-picture.type.js +1 -1
- package/dist/components/questions/_shared/types/listen-and-choose-from-answer-group.type.js +1 -1
- package/dist/components/questions/_shared/types/listen-and-choose-objects-in-scene.type.js +1 -1
- package/dist/components/questions/_shared/types/listen-and-drag-objects-into-scene.type.js +1 -1
- package/dist/components/questions/_shared/types/listen-and-speak-answer.type.js +1 -1
- package/dist/components/questions/_shared/types/listen-and-speak-compare-images.type.js +1 -1
- package/dist/components/questions/_shared/types/listen-and-speak-image-group.type.js +1 -1
- package/dist/components/questions/_shared/types/listen-and-speak-info-exchange.type.js +1 -1
- package/dist/components/questions/_shared/types/listen-and-speak-odd-one-out.type.js +1 -3
- package/dist/components/questions/_shared/types/listen-and-speak-question-list.type.js +1 -1
- package/dist/components/questions/_shared/types/listen-and-speak-with-story-images.type.js +1 -3
- package/dist/components/questions/_shared/types/listen-and-tick-answer.type.js +1 -1
- package/dist/components/questions/_shared/types/listening-drawline.type.js +1 -1
- package/dist/components/questions/_shared/types/look-and-choose-answer.type.js +1 -1
- package/dist/components/questions/_shared/types/look-picture-fill-word-hint.type.js +1 -1
- package/dist/components/questions/_shared/types/match-by-writing-answer.type.js +1 -1
- package/dist/components/questions/_shared/types/multiple-choice.type.js +1 -1
- package/dist/components/questions/_shared/types/question-group.type.js +1 -11
- package/dist/components/questions/_shared/types/read-and-color-objects.type.js +1 -1
- package/dist/components/questions/_shared/types/read-displayed-content.type.js +1 -1
- package/dist/components/questions/_shared/types/read-passage-and-answer-questions.type.js +1 -1
- package/dist/components/questions/_shared/types/speaking-describe-image.type.js +1 -1
- package/dist/components/questions/_shared/types/spontaneous-qa.type.js +1 -1
- package/dist/components/questions/_shared/types/word-fill-paragraph-with-options.type.js +1 -1
- package/dist/components/questions/_shared/types/word-fill-paragraph.type.js +1 -1
- package/dist/components/questions/_shared/types/word-fill-structured-form.type.js +1 -1
- package/dist/components/questions/_shared/types/word-ordering.type.js +1 -1
- package/dist/components/questions/_shared/types/write-a-short-letter.type.js +1 -1
- package/dist/components/questions/_shared/types/write-correct-verb-form.type.js +1 -3
- package/dist/components/questions/_shared/types/write-sentences.type.js +1 -1
- package/dist/components/questions/_shared/types/write-short-paragraph.type.js +1 -1
- package/dist/components/questions/_shared/utils/answer-builder.js +1 -79
- package/dist/components/questions/_shared/utils/createColorRegionRegistration.js +1 -39
- package/dist/components/questions/_shared/utils/id-generator.js +1 -3
- package/dist/components/questions/_shared/utils/question-validation.js +1 -21
- package/dist/components/questions/components/ColorNode.js +1 -85
- package/dist/components/questions/components/ColorRegionClient.js +1 -545
- package/dist/components/questions/components/ColorRegionCreator.js +1 -420
- package/dist/components/questions/components/ColorRegionNode.js +1 -59
- package/dist/components/questions/components/CreatorGuide.js +1 -103
- package/dist/components/questions/components/NameNode.js +1 -26
- package/dist/components/questions/components/QuestionBankOption.js +1 -46
- package/dist/components/questions/components/QuestionBasicInfoForm.js +1 -9
- package/dist/components/questions/components/QuestionCommonFields.js +1 -33
- package/dist/components/questions/components/QuestionSearchDropdown.js +1 -258
- package/dist/components/questions/components/QuestionTypeSelector.js +1 -32
- package/dist/components/questions/components/QuestionTypeSpecificForms.js +1 -6
- package/dist/components/questions/components/RegionNode.js +1 -94
- package/dist/components/questions/components/index.js +1 -14
- package/dist/components/questions/creator/QuestionCreator.js +1 -90
- package/dist/components/questions/creator/QuestionGroupCreator.js +1 -119
- package/dist/components/questions/creator/dedicated-component-router.js +1 -79
- package/dist/components/questions/creator/index.js +1 -3
- package/dist/components/questions/creator/question-type-registry.js +1 -80
- package/dist/components/questions/groups/ArticlesGroupCard.js +1 -121
- package/dist/components/questions/groups/BasicQuestionGroupCard.js +1 -50
- package/dist/components/questions/groups/DocumentGroupCard.js +1 -114
- package/dist/components/questions/groups/MatchAnswerGroupCard.js +1 -25
- package/dist/components/questions/index.js +1 -10
- package/dist/components/questions/types/answer-the-question/AnswerTheQuestionClient.js +1 -37
- package/dist/components/questions/types/answer-the-question/AnswerTheQuestionCreator.js +1 -351
- package/dist/components/questions/types/answer-the-question/register.js +1 -67
- package/dist/components/questions/types/answer-the-question/transform.js +1 -36
- package/dist/components/questions/types/arrange-letters-into-words/ArrangeLettersIntoWordsClient.js +1 -141
- package/dist/components/questions/types/arrange-letters-into-words/ArrangeLettersIntoWordsCreator.js +1 -118
- package/dist/components/questions/types/arrange-letters-into-words/index.js +1 -2
- package/dist/components/questions/types/arrange-letters-into-words/register.js +1 -4
- package/dist/components/questions/types/arrange-letters-into-words/transform.js +1 -24
- package/dist/components/questions/types/choose-correct-adjective/ChooseCorrectAdjectiveClient.js +1 -64
- package/dist/components/questions/types/choose-correct-adjective/ChooseCorrectAdjectiveCreator.js +1 -169
- package/dist/components/questions/types/choose-correct-adjective/index.js +1 -2
- package/dist/components/questions/types/choose-correct-adjective/register.js +1 -55
- package/dist/components/questions/types/choose-correct-adjective/transform.js +1 -16
- package/dist/components/questions/types/choose-the-correct-answer/ChooseTheCorrectAnswerClient.js +1 -131
- package/dist/components/questions/types/choose-the-correct-answer/ChooseTheCorrectAnswerCreator.js +1 -581
- package/dist/components/questions/types/choose-the-correct-answer/index.js +1 -2
- package/dist/components/questions/types/choose-the-correct-answer/register.js +1 -101
- package/dist/components/questions/types/choose-the-correct-answer/transform.js +1 -43
- package/dist/components/questions/types/fill-in-blank/FillInBlankClient.js +1 -275
- package/dist/components/questions/types/fill-in-blank/FillInBlankCreator.js +1 -448
- package/dist/components/questions/types/fill-in-blank/index.js +1 -2
- package/dist/components/questions/types/fill-in-blank/register.js +1 -138
- package/dist/components/questions/types/fill-in-blank/transform.js +1 -59
- package/dist/components/questions/types/fill-missing-words-in-grid/FillMissingWordsInGridClient.js +1 -117
- package/dist/components/questions/types/fill-missing-words-in-grid/FillMissingWordsInGridCreator.js +1 -671
- package/dist/components/questions/types/fill-missing-words-in-grid/index.js +1 -2
- package/dist/components/questions/types/fill-missing-words-in-grid/register.js +1 -37
- package/dist/components/questions/types/fill-missing-words-in-grid/transform.js +1 -15
- package/dist/components/questions/types/gn-speaking-interview/GNSpeakingInterviewCreator.js +1 -262
- package/dist/components/questions/types/gn-speaking-interview/register.js +1 -121
- package/dist/components/questions/types/gn-speaking-interview/transform.js +1 -48
- package/dist/components/questions/types/image-object-matching/ImageObjectMatchingClient.js +1 -16
- package/dist/components/questions/types/image-object-matching/ImageObjectMatchingCreator.js +1 -27
- package/dist/components/questions/types/image-object-matching/index.js +1 -2
- package/dist/components/questions/types/image-object-matching/register.js +1 -3
- package/dist/components/questions/types/image-object-matching/transform.js +1 -2
- package/dist/components/questions/types/label-the-picture/LabelThePictureClient.js +1 -102
- package/dist/components/questions/types/label-the-picture/LabelThePictureCreator.js +1 -344
- package/dist/components/questions/types/label-the-picture/index.js +1 -2
- package/dist/components/questions/types/label-the-picture/register.js +1 -54
- package/dist/components/questions/types/label-the-picture/transform.js +1 -28
- package/dist/components/questions/types/listen-and-choose-from-answer-group/ListenAndChooseFromAnswerGroupClient.js +1 -78
- package/dist/components/questions/types/listen-and-choose-from-answer-group/ListenAndChooseFromAnswerGroupCreator.js +1 -232
- package/dist/components/questions/types/listen-and-choose-from-answer-group/index.js +1 -2
- package/dist/components/questions/types/listen-and-choose-from-answer-group/register.js +1 -82
- package/dist/components/questions/types/listen-and-choose-from-answer-group/transform.js +1 -27
- package/dist/components/questions/types/listen-and-choose-objects-in-scene/ListenAndChooseObjectsInSceneClient.js +1 -70
- package/dist/components/questions/types/listen-and-choose-objects-in-scene/ListenAndChooseObjectsInSceneCreator.js +1 -386
- package/dist/components/questions/types/listen-and-choose-objects-in-scene/index.js +1 -2
- package/dist/components/questions/types/listen-and-choose-objects-in-scene/register.js +1 -100
- package/dist/components/questions/types/listen-and-choose-objects-in-scene/transform.js +1 -33
- package/dist/components/questions/types/listen-and-drag-objects-into-scene/ListenAndDragObjectsIntoSceneClient.js +1 -106
- package/dist/components/questions/types/listen-and-drag-objects-into-scene/ListenAndDragObjectsIntoSceneCreator.js +1 -457
- package/dist/components/questions/types/listen-and-drag-objects-into-scene/index.js +1 -2
- package/dist/components/questions/types/listen-and-drag-objects-into-scene/register.js +1 -110
- package/dist/components/questions/types/listen-and-drag-objects-into-scene/transform.js +1 -35
- package/dist/components/questions/types/listen-and-speak-answer/ArrowIndicator.js +1 -8
- package/dist/components/questions/types/listen-and-speak-answer/ListenAndSpeakAnswerClient.js +1 -22
- package/dist/components/questions/types/listen-and-speak-answer/ListenAndSpeakAnswerCreator.js +1 -291
- package/dist/components/questions/types/listen-and-speak-answer/index.js +1 -2
- package/dist/components/questions/types/listen-and-speak-answer/register.js +1 -84
- package/dist/components/questions/types/listen-and-speak-answer/transform.js +1 -27
- package/dist/components/questions/types/listen-and-speak-compare-images/ListenAndSpeakCompareImagesClient.js +1 -32
- package/dist/components/questions/types/listen-and-speak-compare-images/ListenAndSpeakCompareImagesCreator.js +1 -295
- package/dist/components/questions/types/listen-and-speak-compare-images/register.js +1 -79
- package/dist/components/questions/types/listen-and-speak-compare-images/transform.js +1 -27
- package/dist/components/questions/types/listen-and-speak-image-group/ListenAndSpeakImageGroupClient.js +1 -34
- package/dist/components/questions/types/listen-and-speak-image-group/ListenAndSpeakImageGroupCreator.js +1 -285
- package/dist/components/questions/types/listen-and-speak-image-group/register.js +1 -81
- package/dist/components/questions/types/listen-and-speak-image-group/transform.js +1 -26
- package/dist/components/questions/types/listen-and-speak-info-exchange/ListenAndSpeakInfoExchangeClient.js +1 -38
- package/dist/components/questions/types/listen-and-speak-info-exchange/ListenAndSpeakInfoExchangeCreator.js +1 -385
- package/dist/components/questions/types/listen-and-speak-info-exchange/register.js +1 -87
- package/dist/components/questions/types/listen-and-speak-info-exchange/transform.js +1 -32
- package/dist/components/questions/types/listen-and-speak-odd-one-out/ListenAndSpeakOddOneOutClient.js +1 -26
- package/dist/components/questions/types/listen-and-speak-odd-one-out/ListenAndSpeakOddOneOutCreator.js +1 -247
- package/dist/components/questions/types/listen-and-speak-odd-one-out/register.js +1 -59
- package/dist/components/questions/types/listen-and-speak-odd-one-out/transform.js +1 -18
- package/dist/components/questions/types/listen-and-speak-question-list/ListenAndSpeakQuestionListClient.js +1 -26
- package/dist/components/questions/types/listen-and-speak-question-list/ListenAndSpeakQuestionListCreator.js +1 -256
- package/dist/components/questions/types/listen-and-speak-question-list/register.js +1 -81
- package/dist/components/questions/types/listen-and-speak-question-list/transform.js +1 -26
- package/dist/components/questions/types/listen-and-speak-with-story-images/ListenAndSpeakWithStoryImagesClient.js +1 -26
- package/dist/components/questions/types/listen-and-speak-with-story-images/ListenAndSpeakWithStoryImagesCreator.js +1 -230
- package/dist/components/questions/types/listen-and-speak-with-story-images/register.js +1 -56
- package/dist/components/questions/types/listen-and-speak-with-story-images/transform.js +1 -16
- package/dist/components/questions/types/look-picture-choose-correct-answer/LookPictureChooseCorrectAnswerClient.js +1 -72
- package/dist/components/questions/types/look-picture-choose-correct-answer/LookPictureChooseCorrectAnswerCreator.js +1 -230
- package/dist/components/questions/types/look-picture-choose-correct-answer/index.js +1 -1
- package/dist/components/questions/types/look-picture-choose-correct-answer/register.js +1 -66
- package/dist/components/questions/types/look-picture-choose-correct-answer/transform.js +1 -42
- package/dist/components/questions/types/look-picture-fill-blank-choose-answer/LookPictureFillBlankChooseAnswerClient.js +1 -96
- package/dist/components/questions/types/look-picture-fill-blank-choose-answer/LookPictureFillBlankChooseAnswerCreator.js +1 -238
- package/dist/components/questions/types/look-picture-fill-blank-choose-answer/index.js +1 -2
- package/dist/components/questions/types/look-picture-fill-blank-choose-answer/register.js +1 -76
- package/dist/components/questions/types/look-picture-fill-blank-choose-answer/transform.js +1 -19
- package/dist/components/questions/types/look-picture-fill-word-hint/LookPictureFillWordHintClient.js +1 -134
- package/dist/components/questions/types/look-picture-fill-word-hint/LookPictureFillWordHintCreator.js +1 -617
- package/dist/components/questions/types/look-picture-fill-word-hint/index.js +1 -2
- package/dist/components/questions/types/look-picture-fill-word-hint/register.js +1 -82
- package/dist/components/questions/types/look-picture-fill-word-hint/transform.js +1 -29
- package/dist/components/questions/types/match-by-writing-answer/MatchByWritingAnswerClient.js +1 -38
- package/dist/components/questions/types/match-by-writing-answer/MatchByWritingAnswerCreator.js +1 -359
- package/dist/components/questions/types/match-by-writing-answer/index.js +1 -3
- package/dist/components/questions/types/match-by-writing-answer/register.js +1 -58
- package/dist/components/questions/types/match-by-writing-answer/transform.js +1 -61
- package/dist/components/questions/types/read-and-color-objects/ReadAndColorObjectsClient.js +1 -16
- package/dist/components/questions/types/read-and-color-objects/ReadAndColorObjectsCreator.js +1 -23
- package/dist/components/questions/types/read-and-color-objects/index.js +1 -2
- package/dist/components/questions/types/read-and-color-objects/register.js +1 -3
- package/dist/components/questions/types/read-and-color-objects/transform.js +1 -36
- package/dist/components/questions/types/read-displayed-content/ReadDisplayedContentCreator.js +1 -125
- package/dist/components/questions/types/read-displayed-content/register.js +1 -64
- package/dist/components/questions/types/read-displayed-content/transform.js +1 -12
- package/dist/components/questions/types/read-passage-and-answer-questions/ReadPassageAndAnswerQuestionsClient.js +1 -64
- package/dist/components/questions/types/read-passage-and-answer-questions/ReadPassageAndAnswerQuestionsCreator.js +1 -511
- package/dist/components/questions/types/read-passage-and-answer-questions/index.js +1 -2
- package/dist/components/questions/types/read-passage-and-answer-questions/register.js +1 -58
- package/dist/components/questions/types/read-passage-and-answer-questions/transform.js +1 -28
- package/dist/components/questions/types/speaking-conversation/SpeakingConversationCreator.js +1 -373
- package/dist/components/questions/types/speaking-conversation/SpeakingConversationPreviewClient.js +1 -41
- package/dist/components/questions/types/speaking-conversation/register.js +1 -110
- package/dist/components/questions/types/speaking-conversation/transform.js +1 -34
- package/dist/components/questions/types/speaking-cue-card/SpeakingCueCardCreator.js +1 -74
- package/dist/components/questions/types/speaking-cue-card/register.js +1 -52
- package/dist/components/questions/types/speaking-cue-card/transform.js +1 -12
- package/dist/components/questions/types/speaking-describe-image/SpeakingDescribeImageClient.js +1 -10
- package/dist/components/questions/types/speaking-describe-image/SpeakingDescribeImageCreator.js +1 -161
- package/dist/components/questions/types/speaking-describe-image/index.js +1 -2
- package/dist/components/questions/types/speaking-describe-image/register.js +1 -69
- package/dist/components/questions/types/speaking-describe-image/transform.js +1 -16
- package/dist/components/questions/types/spontaneous-qa/SpontaneousQACreator.js +1 -234
- package/dist/components/questions/types/spontaneous-qa/register.js +1 -93
- package/dist/components/questions/types/spontaneous-qa/transform.js +1 -34
- package/dist/components/questions/types/true-false/TrueFalseClient.js +1 -75
- package/dist/components/questions/types/true-false/TrueFalseCreator.js +1 -244
- package/dist/components/questions/types/true-false/index.js +1 -2
- package/dist/components/questions/types/true-false/register.js +1 -77
- package/dist/components/questions/types/true-false/transform.js +1 -32
- package/dist/components/questions/types/word-fill-paragraph/WordFillParagraphClient.js +1 -233
- package/dist/components/questions/types/word-fill-paragraph/WordFillParagraphCreator.js +1 -631
- package/dist/components/questions/types/word-fill-paragraph/WordFillParagraphWrapper.js +1 -28
- package/dist/components/questions/types/word-fill-paragraph/index.js +1 -2
- package/dist/components/questions/types/word-fill-paragraph/register.js +1 -136
- package/dist/components/questions/types/word-fill-paragraph/transform.js +1 -56
- package/dist/components/questions/types/word-fill-structured-form/WordFillStructuredFormClient.js +1 -138
- package/dist/components/questions/types/word-fill-structured-form/WordFillStructuredFormCreator.js +1 -261
- package/dist/components/questions/types/word-fill-structured-form/index.js +1 -2
- package/dist/components/questions/types/word-fill-structured-form/register.js +1 -67
- package/dist/components/questions/types/word-fill-structured-form/transform.js +1 -40
- package/dist/components/questions/types/word-ordering/WordOrderingClient.js +1 -84
- package/dist/components/questions/types/word-ordering/WordOrderingCreator.js +1 -192
- package/dist/components/questions/types/word-ordering/index.js +1 -2
- package/dist/components/questions/types/word-ordering/register.js +1 -46
- package/dist/components/questions/types/word-ordering/transform.js +1 -24
- package/dist/components/questions/types/write-a-short-letter/WriteAShortLetterClient.js +1 -98
- package/dist/components/questions/types/write-a-short-letter/WriteAShortLetterCreator.js +1 -320
- package/dist/components/questions/types/write-a-short-letter/index.js +1 -2
- package/dist/components/questions/types/write-a-short-letter/register.js +1 -68
- package/dist/components/questions/types/write-a-short-letter/transform.js +1 -25
- package/dist/components/questions/types/write-correct-verb-form/WriteCorrectVerbFormClient.js +1 -61
- package/dist/components/questions/types/write-correct-verb-form/WriteCorrectVerbFormCreator.js +1 -196
- package/dist/components/questions/types/write-correct-verb-form/index.js +1 -2
- package/dist/components/questions/types/write-correct-verb-form/register.js +1 -62
- package/dist/components/questions/types/write-correct-verb-form/transform.js +1 -17
- package/dist/components/questions/types/write-sentences/WriteSentencesClient.js +1 -22
- package/dist/components/questions/types/write-sentences/WriteSentencesCreator.js +1 -174
- package/dist/components/questions/types/write-sentences/index.js +1 -2
- package/dist/components/questions/types/write-sentences/register.js +1 -58
- package/dist/components/questions/types/write-sentences/transform.js +1 -28
- package/dist/components/questions/types/write-short-paragraph/WriteShortParagraphClient.js +1 -96
- package/dist/components/questions/types/write-short-paragraph/WriteShortParagraphCreator.js +1 -161
- package/dist/components/questions/types/write-short-paragraph/index.js +1 -2
- package/dist/components/questions/types/write-short-paragraph/register.js +1 -4
- package/dist/components/questions/types/write-short-paragraph/transform.js +1 -21
- package/dist/components/questions/viewer/QuestionGroupViewer.js +1 -36
- package/dist/components/questions/viewer/QuestionViewer.js +1 -446
- package/dist/components/questions/viewer/index.js +1 -2
- package/dist/components/results/ResultReviewPageContainer.js +1 -98
- package/dist/components/results/ReviewQuestionRenderer.js +1 -72
- package/dist/components/results/index.js +1 -2
- package/dist/components/results/result-review.utils.js +1 -6
- package/dist/components/results/review-data.js +1 -150
- package/dist/components/results/review-renderer.utils.js +1 -97
- package/dist/components/results/review-types.js +1 -1
- package/dist/components/themes/cambridge-yle/CambridgeYleAudioPlayer.js +1 -138
- package/dist/components/themes/cambridge-yle/CambridgeYleBottomNav.js +1 -12
- package/dist/components/themes/cambridge-yle/CambridgeYleExamLayout.js +1 -18
- package/dist/components/themes/cambridge-yle/CambridgeYleExampleText.js +1 -11
- package/dist/components/themes/cambridge-yle/CambridgeYleHeader.js +1 -11
- package/dist/components/themes/cambridge-yle/CambridgeYleInstructionBanner.js +1 -9
- package/dist/components/themes/cambridge-yle/CambridgeYleNavButton.js +1 -13
- package/dist/components/themes/cambridge-yle/CambridgeYlePartBanner.js +1 -8
- package/dist/components/themes/cambridge-yle/CambridgeYleProgressBar.js +1 -6
- package/dist/components/themes/cambridge-yle/CambridgeYleQuestionCard.js +1 -5
- package/dist/components/themes/cambridge-yle/CambridgeYleQuestionCardFullWidth.js +1 -9
- package/dist/components/themes/cambridge-yle/CambridgeYleSidebar.js +1 -41
- package/dist/components/themes/cambridge-yle/CambridgeYleTimer.js +1 -25
- package/dist/components/themes/cambridge-yle/index.js +1 -13
- package/dist/components/themes/english-certification/EcAnswerTheQuestion.js +1 -24
- package/dist/components/themes/english-certification/EcMatchByWritingAnswer.js +1 -88
- package/dist/components/themes/english-certification/EcWordFillParagraph.js +1 -55
- package/dist/components/themes/english-certification/EcWordFillStructuredForm.js +1 -156
- package/dist/components/themes/english-certification/EcWritingTask.js +1 -34
- package/dist/components/themes/english-certification/EnglishCertificationAudioPlayer.js +1 -114
- package/dist/components/themes/english-certification/EnglishCertificationBrand.js +1 -11
- package/dist/components/themes/english-certification/EnglishCertificationExamLayout.js +1 -21
- package/dist/components/themes/english-certification/EnglishCertificationFlagButton.js +1 -15
- package/dist/components/themes/english-certification/EnglishCertificationFooterBar.js +1 -19
- package/dist/components/themes/english-certification/EnglishCertificationGroupedQuestion.js +1 -148
- package/dist/components/themes/english-certification/EnglishCertificationHeader.js +1 -31
- package/dist/components/themes/english-certification/EnglishCertificationNavigationPanel.js +1 -71
- package/dist/components/themes/english-certification/EnglishCertificationOptionSelector.js +1 -33
- package/dist/components/themes/english-certification/EnglishCertificationPassagePanel.js +1 -16
- package/dist/components/themes/english-certification/EnglishCertificationQuestionCard.js +1 -22
- package/dist/components/themes/english-certification/EnglishCertificationQuestionItem.js +1 -37
- package/dist/components/themes/english-certification/EnglishCertificationQuestionRenderer.js +1 -72
- package/dist/components/themes/english-certification/EnglishCertificationReadingSection.js +1 -90
- package/dist/components/themes/english-certification/EnglishCertificationSingleQuestion.js +1 -18
- package/dist/components/themes/english-certification/EnglishCertificationSingleQuestionList.js +1 -29
- package/dist/components/themes/english-certification/english-certification-theme.js +1 -58
- package/dist/components/themes/english-certification/grouped-question-utils.js +1 -218
- package/dist/components/themes/english-certification/index.js +1 -21
- package/dist/components/themes/index.js +1 -6
- package/dist/components/themes/summer-sky/SummerSkyAudioPlayer.js +1 -170
- package/dist/components/themes/summer-sky/SummerSkyLayout.js +1 -221
- package/dist/components/themes/summer-sky/SummerSkySubmitModal.js +1 -18
- package/dist/components/themes/summer-sky/SummerSkyWaitingRoom.js +1 -194
- package/dist/components/themes/types.js +1 -1
- package/dist/components/ui/alert-dialog.js +1 -26
- package/dist/components/ui/alert.js +1 -22
- package/dist/components/ui/avatar.js +1 -12
- package/dist/components/ui/badge.js +1 -38
- package/dist/components/ui/button.js +1 -35
- package/dist/components/ui/calendar.js +1 -76
- package/dist/components/ui/card.js +1 -16
- package/dist/components/ui/carousel.js +1 -91
- package/dist/components/ui/checkbox.js +1 -9
- package/dist/components/ui/collapsible.js +1 -13
- package/dist/components/ui/command.js +1 -34
- package/dist/components/ui/confirm-dialog.js +1 -49
- package/dist/components/ui/dialog.js +1 -23
- package/dist/components/ui/drawer.js +1 -35
- package/dist/components/ui/dropdown-menu.js +1 -33
- package/dist/components/ui/file-upload.js +1 -155
- package/dist/components/ui/form.js +1 -62
- package/dist/components/ui/hover-card.js +1 -10
- package/dist/components/ui/image-preview.js +1 -29
- package/dist/components/ui/input.js +1 -8
- package/dist/components/ui/label.js +1 -9
- package/dist/components/ui/language-switcher.js +1 -15
- package/dist/components/ui/multi-select.js +1 -90
- package/dist/components/ui/points-input.js +1 -33
- package/dist/components/ui/popover.js +1 -26
- package/dist/components/ui/progress.js +1 -16
- package/dist/components/ui/radio-group.js +1 -12
- package/dist/components/ui/scroll-area.js +1 -11
- package/dist/components/ui/select.js +1 -38
- package/dist/components/ui/separator.js +1 -8
- package/dist/components/ui/sheet.js +1 -40
- package/dist/components/ui/single-select.js +1 -76
- package/dist/components/ui/skeleton.js +1 -6
- package/dist/components/ui/switch.js +1 -8
- package/dist/components/ui/table.js +1 -20
- package/dist/components/ui/tabs.js +1 -17
- package/dist/components/ui/textarea.js +1 -8
- package/dist/components/ui/tooltip.js +1 -11
- package/dist/index.js +1 -19
- package/dist/mcp/config.js +1 -32
- package/dist/mcp/http-client.js +1 -48
- package/dist/mcp/index.js +1 -29
- package/dist/mcp/tools/_helpers.js +1 -25
- package/dist/mcp/tools/exams.js +1 -439
- package/dist/mcp/tools/index.js +1 -6
- package/dist/mcp/tools/results.js +1 -19
- package/dist/shared/constants/ApiConstant.js +1 -276
- package/dist/shared/constants/QueryKeyConstant.js +1 -112
- package/dist/shared/constants/exam-level.enum.js +1 -41
- package/dist/shared/constants/landings/placement-test.constants.js +1 -431
- package/dist/shared/constants/question-skills.js +1 -267
- package/dist/shared/constants/routes.js +1 -81
- package/dist/shared/index.js +1 -7
- package/dist/shared/lib/hooks/index.js +1 -10
- package/dist/shared/lib/hooks/useAlertDialog.js +1 -57
- package/dist/shared/lib/hooks/useAudioPlayCounter.js +1 -30
- package/dist/shared/lib/hooks/useAudioPreview.js +1 -81
- package/dist/shared/lib/hooks/useAudioUpload.js +1 -24
- package/dist/shared/lib/hooks/useConfirmDialog.js +1 -89
- package/dist/shared/lib/hooks/useDebouncedCallback.js +1 -32
- package/dist/shared/lib/hooks/useDelayedAutoPlay.js +1 -12
- package/dist/shared/lib/hooks/useFileUpload.js +1 -24
- package/dist/shared/lib/hooks/useImageUrl.js +1 -5
- package/dist/shared/lib/hooks/usePresignedFileUrl.js +1 -10
- package/dist/shared/lib/hooks/useReactHookForm.js +1 -31
- package/dist/shared/lib/hooks/useToast.js +1 -62
- package/dist/shared/lib/i18n/index.js +1 -54
- package/dist/shared/lib/i18n/messages/en/admin.js +1 -2104
- package/dist/shared/lib/i18n/messages/en/auth.js +1 -86
- package/dist/shared/lib/i18n/messages/en/common.js +1 -140
- package/dist/shared/lib/i18n/messages/en/file-upload.js +1 -18
- package/dist/shared/lib/i18n/messages/en/student.js +1 -133
- package/dist/shared/lib/i18n/messages/en/terms.js +1 -37
- package/dist/shared/lib/i18n/messages/en.js +1 -15
- package/dist/shared/lib/i18n/messages/vi/admin.js +1 -2104
- package/dist/shared/lib/i18n/messages/vi/auth.js +1 -86
- package/dist/shared/lib/i18n/messages/vi/common.js +1 -140
- package/dist/shared/lib/i18n/messages/vi/file-upload.js +1 -18
- package/dist/shared/lib/i18n/messages/vi/student.js +1 -133
- package/dist/shared/lib/i18n/messages/vi/terms.js +1 -37
- package/dist/shared/lib/i18n/messages/vi.js +1 -15
- package/dist/shared/lib/i18n/types.js +1 -1
- package/dist/shared/lib/stores/examQuestionStore.js +1 -212
- package/dist/shared/lib/stores/localeStore.js +1 -10
- package/dist/shared/lib/utils/public-page.js +1 -44
- package/dist/shared/lib/utils/question-transform-types.js +1 -1
- package/dist/shared/lib/utils/question-transform.js +1 -174
- package/dist/shared/lib/utils.js +1 -168
- package/dist/shared/lib/validation/choose-correct-answer.validation.js +1 -111
- package/dist/shared/lib/validation/label-the-picture.validation.js +1 -19
- package/dist/shared/lib/validation/listen-and-choose-from-answer-group.validation.js +1 -46
- package/dist/shared/lib/validation/listen-and-choose-objects-in-scene.validation.js +1 -21
- package/dist/shared/lib/validation/listen-and-drag-objects-into-scene.validation.js +1 -34
- package/dist/shared/lib/validation/listen-and-speak-answer.validation.js +1 -40
- package/dist/shared/lib/validation/listen-and-speak-compare-images.validation.js +1 -45
- package/dist/shared/lib/validation/listen-and-speak-image-group.validation.js +1 -19
- package/dist/shared/lib/validation/listen-and-speak-info-exchange.validation.js +1 -39
- package/dist/shared/lib/validation/listen-and-speak-odd-one-out.validation.js +1 -32
- package/dist/shared/lib/validation/listen-and-speak-question-list.validation.js +1 -19
- package/dist/shared/lib/validation/listen-and-speak-with-story-images.validation.js +1 -28
- package/dist/shared/lib/validation/look-picture-choose-correct-answer.validation.js +1 -12
- package/dist/shared/lib/validation/look-picture-fill-blank-choose-answer.validation.js +1 -47
- package/dist/shared/lib/validation/read-and-color-objects.validation.js +1 -29
- package/dist/shared/lib/validation/read-displayed-content.validation.js +1 -28
- package/dist/shared/lib/validation/speaking-conversation.validation.js +1 -75
- package/dist/shared/lib/validation/speaking-describe-image.validation.js +1 -31
- package/dist/shared/lib/validation/spontaneous-qa.validation.js +1 -48
- package/dist/shared/lib/validation/word-fill-paragraph-default.validation.js +1 -38
- package/dist/shared/lib/validation/word-fill-paragraph-with-options.validation.js +1 -39
- package/dist/shared/lib/validation/word-fill-paragraph.validation.js +1 -43
- package/dist/shared/lib/validation/word-ordering.validation.js +1 -33
- package/dist/shared/types/branch.types.js +1 -6
- package/dist/shared/types/common.types.js +1 -50
- package/dist/shared/types/entities/exam-schedule.types.js +1 -5
- package/dist/shared/types/entities/exam.types.js +1 -1
- package/dist/shared/types/entities/question.types.js +1 -4
- package/dist/shared/types/entities/result.types.js +1 -4
- package/dist/shared/types/entities/user.types.js +1 -4
- package/dist/shared/types/exam-taking.types.js +1 -2
- package/dist/shared/types/history.types.js +1 -34
- package/dist/shared/types/index.js +1 -1
- package/dist/shared/types/questions/choose-correct-adjective.js +1 -4
- package/dist/shared/types/questions/choose-the-correct-answer.js +1 -9
- package/dist/shared/types/questions/fill-missing-words-in-grid.js +1 -11
- package/dist/shared/types/questions/index.js +1 -40
- package/dist/shared/types/questions/listen-and-speak-answer.js +1 -1
- package/dist/shared/types/questions/listen-and-speak-compare-images.js +1 -1
- package/dist/shared/types/questions/listen-and-speak-image-group.js +1 -1
- package/dist/shared/types/questions/listen-and-speak-info-exchange.js +1 -3
- package/dist/shared/types/questions/listen-and-speak-odd-one-out.js +1 -3
- package/dist/shared/types/questions/listen-and-speak-question-list.js +1 -1
- package/dist/shared/types/questions/listen-and-speak-with-story-images.js +1 -3
- package/dist/shared/types/questions/look-picture-choose-correct-answer.js +1 -5
- package/dist/shared/types/questions/look-picture-fill-blank-choose-answer.js +1 -9
- package/dist/shared/types/questions/look-picture-fill-word-hint.js +1 -7
- package/dist/shared/types/questions/read-displayed-content.js +1 -1
- package/dist/shared/types/questions/speaking-conversation.js +1 -7
- package/dist/shared/types/questions/speaking-describe-image.js +1 -1
- package/dist/shared/types/questions/spontaneous-qa.js +1 -1
- package/dist/shared/types/questions/write-correct-verb-form.js +1 -12
- package/dist/shared/types/role.types.js +1 -1
- package/dist/shared/types/structure-management.js +1 -28
- package/dist/shared/types/student.types.js +1 -54
- package/dist/shared/types/teacher-dashboard-rooms.js +1 -2
- package/package.json +4 -2
|
@@ -1,28 +1 @@
|
|
|
1
|
-
export const transformLabelThePicture = (
|
|
2
|
-
const answerData = question.answer;
|
|
3
|
-
if (!answerData?.imageUrl || !answerData?.labels || answerData.labels.length === 0) {
|
|
4
|
-
return { apiContent: {}, apiCorrectAnswer: {} };
|
|
5
|
-
}
|
|
6
|
-
const labels = (answerData.labels || []).map((label) => ({
|
|
7
|
-
id: label.id,
|
|
8
|
-
number: label.number,
|
|
9
|
-
correctAnswer: label.correctAnswer || '',
|
|
10
|
-
isPreFilled: label.isPreFilled || false,
|
|
11
|
-
}));
|
|
12
|
-
const answersMapping = (answerData.labels || []).reduce((acc, label) => {
|
|
13
|
-
if (label.id && label.correctAnswer && !label.isPreFilled) {
|
|
14
|
-
acc[label.id] = label.correctAnswer;
|
|
15
|
-
}
|
|
16
|
-
return acc;
|
|
17
|
-
}, {});
|
|
18
|
-
return {
|
|
19
|
-
apiContent: {
|
|
20
|
-
imageUrl: answerData.imageUrl,
|
|
21
|
-
wordBank: answerData.wordBank || [],
|
|
22
|
-
labels,
|
|
23
|
-
},
|
|
24
|
-
apiCorrectAnswer: {
|
|
25
|
-
labels: answersMapping,
|
|
26
|
-
},
|
|
27
|
-
};
|
|
28
|
-
};
|
|
1
|
+
export const transformLabelThePicture=e=>{const r=e.answer;if(!r?.imageUrl||!r?.labels||0===r.labels.length)return{apiContent:{},apiCorrectAnswer:{}};const l=(r.labels||[]).map(e=>({id:e.id,number:e.number,correctAnswer:e.correctAnswer||"",isPreFilled:e.isPreFilled||!1})),n=(r.labels||[]).reduce((e,r)=>(r.id&&r.correctAnswer&&!r.isPreFilled&&(e[r.id]=r.correctAnswer),e),{});return{apiContent:{imageUrl:r.imageUrl,wordBank:r.wordBank||[],labels:l},apiCorrectAnswer:{labels:n}}};
|
|
@@ -1,78 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
-
import { useState, useEffect } from 'react';
|
|
4
|
-
import { Check, X, Play, Pause, Volume2, ImageIcon } from 'lucide-react';
|
|
5
|
-
import { Button } from '../../../../components/ui/button';
|
|
6
|
-
import { usePresignedFileUrl, useAudioPreview } from '../../../../shared/lib/hooks';
|
|
7
|
-
import { cn } from '../../../../shared/lib/utils';
|
|
8
|
-
// ─── Sub-component: Audio player ─────────────────────────────────────────────
|
|
9
|
-
function QuestionAudioPlayer({ audioUrl }) {
|
|
10
|
-
const { previewUrl } = usePresignedFileUrl(audioUrl);
|
|
11
|
-
const { isPlaying, currentTime, duration, progressPercentage, handlePlayPause, handleProgressClick, formatTime, } = useAudioPreview(previewUrl);
|
|
12
|
-
return (_jsxs("div", { className: "flex items-center gap-3 rounded-lg bg-gradient-to-r from-indigo-50 to-purple-50 border border-indigo-200 px-4 py-2.5", children: [_jsx(Button, { variant: "outline", size: "icon", onClick: handlePlayPause, disabled: !previewUrl, className: "h-8 w-8 border-indigo-300 text-indigo-600 hover:bg-indigo-100", children: isPlaying ? _jsx(Pause, { className: "h-3.5 w-3.5" }) : _jsx(Play, { className: "h-3.5 w-3.5" }) }), _jsxs("div", { className: "text-muted-foreground min-w-[70px] text-xs", children: [formatTime(currentTime), " / ", formatTime(duration)] }), _jsx("div", { className: "bg-indigo-200 relative h-1.5 flex-1 cursor-pointer rounded-full", onClick: handleProgressClick, children: _jsx("div", { className: "bg-indigo-500 absolute top-0 left-0 h-full rounded-full transition-all", style: { width: `${progressPercentage}%` } }) }), _jsx(Volume2, { className: "h-4 w-4 text-indigo-400" })] }));
|
|
13
|
-
}
|
|
14
|
-
// ─── Sub-component: Option image ─────────────────────────────────────────────
|
|
15
|
-
function OptionImage({ imageUrl }) {
|
|
16
|
-
const { previewUrl } = usePresignedFileUrl(imageUrl);
|
|
17
|
-
if (!previewUrl) {
|
|
18
|
-
return (_jsx("div", { className: "flex h-20 w-20 flex-shrink-0 items-center justify-center rounded-lg bg-gray-100 border border-gray-200", children: _jsx(ImageIcon, { className: "h-8 w-8 text-gray-300" }) }));
|
|
19
|
-
}
|
|
20
|
-
return (_jsx("img", { src: previewUrl, alt: "Option", className: "h-20 w-20 flex-shrink-0 rounded-lg object-contain bg-gray-50 border border-gray-200" }));
|
|
21
|
-
}
|
|
22
|
-
// ─── Main Client Component ───────────────────────────────────────────────────
|
|
23
|
-
export function ListenAndChooseFromAnswerGroupClient({ questionData, onSubmit, isReviewMode = false, userAnswer, correctAnswer, explanation, }) {
|
|
24
|
-
const [selectedAnswer, setSelectedAnswer] = useState(userAnswer);
|
|
25
|
-
// Sync userAnswer when it changes
|
|
26
|
-
useEffect(() => {
|
|
27
|
-
if (userAnswer !== undefined) {
|
|
28
|
-
setSelectedAnswer(userAnswer);
|
|
29
|
-
}
|
|
30
|
-
}, [userAnswer]);
|
|
31
|
-
// Handle option selection — now by option ID
|
|
32
|
-
const handleOptionSelect = (optionId) => {
|
|
33
|
-
if (isReviewMode)
|
|
34
|
-
return;
|
|
35
|
-
setSelectedAnswer(optionId);
|
|
36
|
-
onSubmit?.(optionId);
|
|
37
|
-
};
|
|
38
|
-
// Resolve correct answer: prop > questionData
|
|
39
|
-
const resolvedCorrectAnswer = correctAnswer ?? questionData.correctAnswer;
|
|
40
|
-
// Get answer status for an option — by option ID
|
|
41
|
-
const getOptionStatus = (optionId) => {
|
|
42
|
-
const isSelected = selectedAnswer === optionId;
|
|
43
|
-
const isCorrectAnswer = isReviewMode && optionId === resolvedCorrectAnswer;
|
|
44
|
-
const isWrongSelection = isReviewMode && isSelected && !isCorrectAnswer;
|
|
45
|
-
return { isSelected, isCorrect: isCorrectAnswer, isWrong: isWrongSelection };
|
|
46
|
-
};
|
|
47
|
-
const hasAnswered = !!selectedAnswer;
|
|
48
|
-
const selectedOption = questionData.options.find((o) => o.id === selectedAnswer);
|
|
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-gradient-to-r from-orange-50 to-amber-50 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-orange-500 to-amber-600 text-white shadow-sm", 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
|
-
? '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-5 space-y-5", 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
|
-
const { isSelected, isCorrect, isWrong } = getOptionStatus(option.id);
|
|
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
|
-
? isCorrect
|
|
56
|
-
? 'border-green-400 bg-green-50 shadow-md'
|
|
57
|
-
: isWrong
|
|
58
|
-
? 'border-red-400 bg-red-50'
|
|
59
|
-
: 'border-gray-200 bg-gray-50'
|
|
60
|
-
: isSelected
|
|
61
|
-
? 'border-blue-500 bg-blue-50 shadow-md ring-2 ring-blue-100'
|
|
62
|
-
: 'border-gray-200 bg-white hover:border-gray-400 hover:shadow-sm cursor-pointer'), children: [_jsx("div", { className: cn('flex h-7 w-7 items-center justify-center rounded-lg text-sm font-bold shadow-sm', isReviewMode
|
|
63
|
-
? isCorrect
|
|
64
|
-
? 'bg-green-500 text-white'
|
|
65
|
-
: isWrong
|
|
66
|
-
? 'bg-red-500 text-white'
|
|
67
|
-
: 'bg-gray-200 text-gray-600'
|
|
68
|
-
: isSelected
|
|
69
|
-
? 'bg-blue-500 text-white'
|
|
70
|
-
: 'bg-gray-100 text-gray-600'), children: String.fromCharCode(65 + optIndex) }), _jsx(OptionImage, { imageUrl: option.imageUrl }), option.label && (_jsx("p", { className: cn('text-xs text-center font-medium', isReviewMode
|
|
71
|
-
? isCorrect ? 'text-green-700' : isWrong ? 'text-red-700' : 'text-gray-600'
|
|
72
|
-
: isSelected ? 'text-blue-700' : 'text-gray-600'), children: option.label })), isReviewMode && (isCorrect || isWrong) && (_jsx("div", { className: cn('absolute top-1 right-1 flex h-5 w-5 items-center justify-center rounded-full shadow-sm', isCorrect ? 'bg-green-500' : 'bg-red-500'), children: isCorrect ? (_jsx(Check, { className: "h-3.5 w-3.5 text-white" })) : (_jsx(X, { className: "h-3.5 w-3.5 text-white" })) })), !isReviewMode && isSelected && (_jsx("div", { className: "absolute top-1 right-1 flex h-5 w-5 items-center justify-center rounded-full bg-blue-500 shadow-sm", children: _jsx(Check, { className: "h-3.5 w-3.5 text-white" }) }))] }, option.id));
|
|
73
|
-
}) })] }), hasAnswered && selectedOption && (_jsxs("div", { className: cn('flex items-center gap-2 rounded-lg border p-3', isReviewMode
|
|
74
|
-
? isAnswerCorrect
|
|
75
|
-
? 'border-green-200 bg-green-50'
|
|
76
|
-
: 'border-red-200 bg-red-50'
|
|
77
|
-
: 'border-blue-200 bg-blue-50'), children: [_jsxs("span", { className: "text-sm font-medium text-gray-700", children: ["\u0110\u00E3 ch\u1ECDn: ", _jsx("strong", { children: selectedOption.label || selectedOption.id })] }), isReviewMode && (_jsx("span", { className: cn('text-xs font-semibold', isAnswerCorrect ? 'text-green-600' : 'text-red-600'), children: isAnswerCorrect ? '✓ Chính xác' : '✗ Không chính xác' }))] })), !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: "H\u00E3y ch\u1ECDn m\u1ED9t \u0111\u00E1p \u00E1n" })] })), isReviewMode && explanation && (_jsxs("div", { className: "rounded-lg border border-indigo-200 bg-indigo-50 p-4", children: [_jsx("p", { className: "text-sm font-semibold text-indigo-800", children: "Gi\u1EA3i th\u00EDch:" }), _jsx("p", { className: "mt-1 text-sm text-indigo-700", children: explanation })] }))] })] })] }));
|
|
78
|
-
}
|
|
1
|
+
"use client";import{jsx as e,jsxs as r,Fragment as t}from"react/jsx-runtime";import{useState as i,useEffect as n}from"react";import{Check as s,X as d,Play as a,Pause as l,Volume2 as o,ImageIcon as c}from"lucide-react";import{Button as m}from"../../../../components/ui/button";import{usePresignedFileUrl as g,useAudioPreview as h}from"../../../../shared/lib/hooks";import{cn as b}from"../../../../shared/lib/utils";function u({audioUrl:t}){const{previewUrl:i}=g(t),{isPlaying:n,currentTime:s,duration:d,progressPercentage:c,handlePlayPause:b,handleProgressClick:u,formatTime:x}=h(i);return r("div",{className:"flex items-center gap-3 rounded-lg bg-gradient-to-r from-indigo-50 to-purple-50 border border-indigo-200 px-4 py-2.5",children:[e(m,{variant:"outline",size:"icon",onClick:b,disabled:!i,className:"h-8 w-8 border-indigo-300 text-indigo-600 hover:bg-indigo-100",children:e(n?l:a,{className:"h-3.5 w-3.5"})}),r("div",{className:"text-muted-foreground min-w-[70px] text-xs",children:[x(s)," / ",x(d)]}),e("div",{className:"bg-indigo-200 relative h-1.5 flex-1 cursor-pointer rounded-full",onClick:u,children:e("div",{className:"bg-indigo-500 absolute top-0 left-0 h-full rounded-full transition-all",style:{width:`${c}%`}})}),e(o,{className:"h-4 w-4 text-indigo-400"})]})}function x({imageUrl:r}){const{previewUrl:t}=g(r);return t?e("img",{src:t,alt:"Option",className:"h-20 w-20 flex-shrink-0 rounded-lg object-contain bg-gray-50 border border-gray-200"}):e("div",{className:"flex h-20 w-20 flex-shrink-0 items-center justify-center rounded-lg bg-gray-100 border border-gray-200",children:e(c,{className:"h-8 w-8 text-gray-300"})})}export function ListenAndChooseFromAnswerGroupClient({questionData:a,onSubmit:l,isReviewMode:c=!1,userAnswer:m,correctAnswer:g,explanation:h}){const[p,f]=i(m);n(()=>{void 0!==m&&f(m)},[m]);const w=g??a.correctAnswer,N=!!p,y=a.options.find(e=>e.id===p),v=c&&p===w;return r("div",{className:"space-y-4",children:[a.isBackup&&r("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:[e("span",{children:"🔄"})," Câu hỏi dự phòng"]}),r("div",{className:"overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm",children:[r("div",{className:"flex items-center justify-between border-b border-gray-100 bg-gradient-to-r from-orange-50 to-amber-50 px-5 py-3",children:[r("div",{className:"flex items-center gap-3",children:[e("div",{className:"flex h-8 w-8 items-center justify-center rounded-lg bg-gradient-to-br from-orange-500 to-amber-600 text-white shadow-sm",children:e(o,{className:"h-4 w-4"})}),e("span",{className:"text-sm font-medium text-gray-700",children:"Nghe và chọn trong nhóm đáp án"})]}),c&&N&&e("div",{className:b("flex items-center gap-1.5 rounded-full px-3 py-1 text-xs font-semibold",v?"bg-green-100 text-green-700":"bg-red-100 text-red-700"),children:r(t,v?{children:[e(s,{className:"h-3.5 w-3.5"})," Đúng"]}:{children:[e(d,{className:"h-3.5 w-3.5"})," Sai"]})})]}),r("div",{className:"p-5 space-y-5",children:[a.audioUrl&&e(u,{audioUrl:a.audioUrl}),r("div",{className:"space-y-2",children:[e("span",{className:"text-xs font-medium text-gray-500 uppercase tracking-wider",children:"Chọn đáp án đúng"}),e("div",{className:"grid grid-cols-2 gap-3 sm:grid-cols-3",children:a.options.map((t,i)=>{const{isSelected:n,isCorrect:a,isWrong:o}=(e=>{const r=p===e,t=c&&e===w;return{isSelected:r,isCorrect:t,isWrong:c&&r&&!t}})(t.id);return r("button",{type:"button",onClick:()=>{return e=t.id,void(c||(f(e),l?.(e)));var e},disabled:c,className:b("group relative flex flex-col items-center gap-2 rounded-xl border-2 p-3 transition-all",c?a?"border-green-400 bg-green-50 shadow-md":o?"border-red-400 bg-red-50":"border-gray-200 bg-gray-50":n?"border-blue-500 bg-blue-50 shadow-md ring-2 ring-blue-100":"border-gray-200 bg-white hover:border-gray-400 hover:shadow-sm cursor-pointer"),children:[e("div",{className:b("flex h-7 w-7 items-center justify-center rounded-lg text-sm font-bold shadow-sm",c?a?"bg-green-500 text-white":o?"bg-red-500 text-white":"bg-gray-200 text-gray-600":n?"bg-blue-500 text-white":"bg-gray-100 text-gray-600"),children:String.fromCharCode(65+i)}),e(x,{imageUrl:t.imageUrl}),t.label&&e("p",{className:b("text-xs text-center font-medium",c?a?"text-green-700":o?"text-red-700":"text-gray-600":n?"text-blue-700":"text-gray-600"),children:t.label}),c&&(a||o)&&e("div",{className:b("absolute top-1 right-1 flex h-5 w-5 items-center justify-center rounded-full shadow-sm",a?"bg-green-500":"bg-red-500"),children:e(a?s:d,{className:"h-3.5 w-3.5 text-white"})}),!c&&n&&e("div",{className:"absolute top-1 right-1 flex h-5 w-5 items-center justify-center rounded-full bg-blue-500 shadow-sm",children:e(s,{className:"h-3.5 w-3.5 text-white"})})]},t.id)})})]}),N&&y&&r("div",{className:b("flex items-center gap-2 rounded-lg border p-3",c?v?"border-green-200 bg-green-50":"border-red-200 bg-red-50":"border-blue-200 bg-blue-50"),children:[r("span",{className:"text-sm font-medium text-gray-700",children:["Đã chọn: ",e("strong",{children:y.label||y.id})]}),c&&e("span",{className:b("text-xs font-semibold",v?"text-green-600":"text-red-600"),children:v?"✓ Chính xác":"✗ Không chính xác"})]}),!c&&!N&&r("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:[e("div",{className:"flex h-5 w-5 flex-shrink-0 items-center justify-center rounded-full bg-amber-400 text-white",children:e("span",{className:"text-xs font-bold",children:"!"})}),e("span",{className:"text-sm text-amber-800",children:"Hãy chọn một đáp án"})]}),c&&h&&r("div",{className:"rounded-lg border border-indigo-200 bg-indigo-50 p-4",children:[e("p",{className:"text-sm font-semibold text-indigo-800",children:"Giải thích:"}),e("p",{className:"mt-1 text-sm text-indigo-700",children:h})]})]})]})]})}
|
|
@@ -1,232 +1 @@
|
|
|
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 { Input } from '../../../../components/ui/input';
|
|
6
|
-
import { Textarea } from '../../../../components/ui/textarea';
|
|
7
|
-
import { Label } from '../../../../components/ui/label';
|
|
8
|
-
import { Card, CardContent, CardHeader, CardTitle } from '../../../../components/ui/card';
|
|
9
|
-
import { Pencil, Eye, Play, Pause, Plus, Trash2, Music, CheckCircle2, ListChecks, ImageIcon, BookOpen } from 'lucide-react';
|
|
10
|
-
import { toast } from 'sonner';
|
|
11
|
-
import { FileUpload } from '../../../../components/ui/file-upload';
|
|
12
|
-
import { ListenAndChooseFromAnswerGroupClient } from './ListenAndChooseFromAnswerGroupClient';
|
|
13
|
-
import { usePresignedFileUrl, useAudioPreview, useDebouncedCallback } from '../../../../shared/lib/hooks';
|
|
14
|
-
import { useReactHookForm } from '../../../../shared/lib/hooks/useReactHookForm';
|
|
15
|
-
import { listenAndChooseFromAnswerGroupFormSchema } from '../../../../shared/lib/validation/listen-and-choose-from-answer-group.validation';
|
|
16
|
-
import { useAnswerGroupQuestionGroup } from '../../_shared/hooks/useAnswerGroupQuestionGroup';
|
|
17
|
-
import { generateObjectId } from '../../_shared/utils/id-generator';
|
|
18
|
-
import { CreatorGuide } from '../../components/CreatorGuide';
|
|
19
|
-
// ─── Sub-component: Audio preview ────────────────────────────────────────────
|
|
20
|
-
function AudioPreview({ audioUrl }) {
|
|
21
|
-
const { previewUrl } = usePresignedFileUrl(audioUrl);
|
|
22
|
-
const { isPlaying, currentTime, duration, progressPercentage, handlePlayPause, handleProgressClick, formatTime, } = useAudioPreview(previewUrl);
|
|
23
|
-
if (!previewUrl)
|
|
24
|
-
return null;
|
|
25
|
-
return (_jsx("div", { className: "bg-muted/50 rounded-lg border p-3", children: _jsxs("div", { className: "flex items-center gap-3", children: [_jsx(Button, { variant: "outline", size: "icon", onClick: handlePlayPause, disabled: !previewUrl, className: "h-8 w-8", children: isPlaying ? _jsx(Pause, { className: "h-3.5 w-3.5" }) : _jsx(Play, { className: "h-3.5 w-3.5" }) }), _jsxs("div", { className: "text-muted-foreground min-w-[70px] text-xs", children: [formatTime(currentTime), " / ", formatTime(duration)] }), _jsx("div", { className: "bg-muted relative h-1.5 flex-1 cursor-pointer rounded-full", onClick: handleProgressClick, children: _jsx("div", { className: "bg-primary absolute top-0 left-0 h-full rounded-full transition-all", style: { width: `${progressPercentage}%` } }) })] }) }));
|
|
26
|
-
}
|
|
27
|
-
// ─── Sub-component: Option image preview ─────────────────────────────────────
|
|
28
|
-
function OptionImagePreview({ imageUrl }) {
|
|
29
|
-
const { previewUrl } = usePresignedFileUrl(imageUrl);
|
|
30
|
-
if (!previewUrl)
|
|
31
|
-
return null;
|
|
32
|
-
return (_jsx("div", { className: "mt-2 rounded-lg border border-gray-200 overflow-hidden", children: _jsx("img", { src: previewUrl, alt: "Option", className: "h-24 w-full object-contain bg-gray-50" }) }));
|
|
33
|
-
}
|
|
34
|
-
// ─── Main Creator Component ──────────────────────────────────────────────────
|
|
35
|
-
function ListenAndChooseFromAnswerGroupCreatorContent({ initialData, onSave, onCancel, onChange, onUnsavedChangesChange, validationRef, externalErrors, groups, questionIndexInPart = 0, partId = '', }) {
|
|
36
|
-
// Shared Group Data
|
|
37
|
-
const answerGroupInitialData = {
|
|
38
|
-
options: initialData?.options || [],
|
|
39
|
-
};
|
|
40
|
-
const { isGrouped, currentGroupNumber, options, setOptions, } = useAnswerGroupQuestionGroup({
|
|
41
|
-
partId,
|
|
42
|
-
groups,
|
|
43
|
-
questionIndexInPart,
|
|
44
|
-
initialData: answerGroupInitialData,
|
|
45
|
-
});
|
|
46
|
-
// Specific Question State
|
|
47
|
-
const [audioUrl, setAudioUrl] = useState(initialData?.audioUrl || '');
|
|
48
|
-
const [correctAnswer, setCorrectAnswer] = useState(initialData?.correctAnswer !== undefined && initialData?.correctAnswer !== null
|
|
49
|
-
? String(initialData.correctAnswer)
|
|
50
|
-
: '');
|
|
51
|
-
const [points, setPoints] = useState(initialData?.points);
|
|
52
|
-
const [explanation, setExplanation] = useState(initialData?.explanation || '');
|
|
53
|
-
const [isBackup, setIsBackup] = useState(initialData?.isBackup || false);
|
|
54
|
-
// Track previous initialData to detect changes
|
|
55
|
-
const previousInitialDataRef = useRef(initialData);
|
|
56
|
-
const isInitializingRef = useRef(false);
|
|
57
|
-
// UI State
|
|
58
|
-
const [isClientMode, setIsClientMode] = useState(false);
|
|
59
|
-
// Sync state from initialData when it changes
|
|
60
|
-
useEffect(() => {
|
|
61
|
-
const initialDataChanged = JSON.stringify(previousInitialDataRef.current) !== JSON.stringify(initialData);
|
|
62
|
-
if (initialDataChanged) {
|
|
63
|
-
isInitializingRef.current = true;
|
|
64
|
-
previousInitialDataRef.current = initialData;
|
|
65
|
-
if (initialData) {
|
|
66
|
-
if (initialData.audioUrl !== undefined)
|
|
67
|
-
setAudioUrl(initialData.audioUrl);
|
|
68
|
-
if (initialData.correctAnswer !== undefined && initialData.correctAnswer !== null) {
|
|
69
|
-
setCorrectAnswer(String(initialData.correctAnswer));
|
|
70
|
-
}
|
|
71
|
-
if (initialData.points !== undefined)
|
|
72
|
-
setPoints(initialData.points);
|
|
73
|
-
if (initialData.explanation !== undefined)
|
|
74
|
-
setExplanation(initialData.explanation);
|
|
75
|
-
if (initialData.isBackup !== undefined)
|
|
76
|
-
setIsBackup(initialData.isBackup);
|
|
77
|
-
}
|
|
78
|
-
else {
|
|
79
|
-
setAudioUrl('');
|
|
80
|
-
setCorrectAnswer('');
|
|
81
|
-
setPoints(undefined);
|
|
82
|
-
setExplanation('');
|
|
83
|
-
setIsBackup(false);
|
|
84
|
-
}
|
|
85
|
-
setTimeout(() => {
|
|
86
|
-
isInitializingRef.current = false;
|
|
87
|
-
}, 100);
|
|
88
|
-
}
|
|
89
|
-
}, [initialData]);
|
|
90
|
-
const onChangeRef = useRef(onChange);
|
|
91
|
-
useEffect(() => {
|
|
92
|
-
onChangeRef.current = onChange;
|
|
93
|
-
}, [onChange]);
|
|
94
|
-
const debouncedOnChange = useDebouncedCallback((data) => {
|
|
95
|
-
onChangeRef.current?.(data);
|
|
96
|
-
}, 300);
|
|
97
|
-
// Notify onChange
|
|
98
|
-
useEffect(() => {
|
|
99
|
-
if (isInitializingRef.current)
|
|
100
|
-
return;
|
|
101
|
-
debouncedOnChange({
|
|
102
|
-
options,
|
|
103
|
-
audioUrl,
|
|
104
|
-
correctAnswer,
|
|
105
|
-
points,
|
|
106
|
-
explanation,
|
|
107
|
-
isBackup,
|
|
108
|
-
groups,
|
|
109
|
-
groupNumber: currentGroupNumber,
|
|
110
|
-
});
|
|
111
|
-
}, [options, audioUrl, correctAnswer, points, explanation, isBackup, groups, currentGroupNumber, debouncedOnChange]);
|
|
112
|
-
// Validation
|
|
113
|
-
const form = useReactHookForm({
|
|
114
|
-
defaultValues: {
|
|
115
|
-
options,
|
|
116
|
-
audioUrl,
|
|
117
|
-
correctAnswer,
|
|
118
|
-
points,
|
|
119
|
-
explanation,
|
|
120
|
-
isBackup,
|
|
121
|
-
},
|
|
122
|
-
zodSchema: listenAndChooseFromAnswerGroupFormSchema,
|
|
123
|
-
mode: 'onBlur',
|
|
124
|
-
});
|
|
125
|
-
useEffect(() => {
|
|
126
|
-
if (validationRef) {
|
|
127
|
-
validationRef.current = {
|
|
128
|
-
triggerValidation: async () => {
|
|
129
|
-
form.setValue('options', options);
|
|
130
|
-
form.setValue('audioUrl', audioUrl);
|
|
131
|
-
form.setValue('correctAnswer', String(correctAnswer));
|
|
132
|
-
form.setValue('points', points);
|
|
133
|
-
form.setValue('isBackup', isBackup);
|
|
134
|
-
const isValid = await form.trigger();
|
|
135
|
-
if (!isValid) {
|
|
136
|
-
return false;
|
|
137
|
-
}
|
|
138
|
-
return true;
|
|
139
|
-
},
|
|
140
|
-
getFormData: () => ({
|
|
141
|
-
options,
|
|
142
|
-
audioUrl,
|
|
143
|
-
correctAnswer,
|
|
144
|
-
points,
|
|
145
|
-
explanation,
|
|
146
|
-
isBackup,
|
|
147
|
-
groups,
|
|
148
|
-
groupNumber: currentGroupNumber,
|
|
149
|
-
}),
|
|
150
|
-
};
|
|
151
|
-
}
|
|
152
|
-
}, [validationRef, options, audioUrl, correctAnswer, points, explanation, isBackup, groups, currentGroupNumber, form]);
|
|
153
|
-
// ─── Option Management ──────────────────────────────────────────────────────
|
|
154
|
-
const addOption = () => {
|
|
155
|
-
const newOption = {
|
|
156
|
-
id: generateObjectId(),
|
|
157
|
-
imageUrl: '',
|
|
158
|
-
label: '',
|
|
159
|
-
};
|
|
160
|
-
setOptions([...options, newOption]);
|
|
161
|
-
};
|
|
162
|
-
const updateOptionImageUrl = (id, imageUrl) => {
|
|
163
|
-
setOptions(options.map((opt) => (opt.id === id ? { ...opt, imageUrl } : opt)));
|
|
164
|
-
form.clearErrors('options');
|
|
165
|
-
};
|
|
166
|
-
const updateOptionLabel = (id, label) => {
|
|
167
|
-
setOptions(options.map((opt) => (opt.id === id ? { ...opt, label } : opt)));
|
|
168
|
-
form.clearErrors('options');
|
|
169
|
-
};
|
|
170
|
-
const removeOption = (id) => {
|
|
171
|
-
if (options.length <= 2) {
|
|
172
|
-
toast.error('Cần tối thiểu 2 đáp án!');
|
|
173
|
-
return;
|
|
174
|
-
}
|
|
175
|
-
const newOptions = options.filter((opt) => opt.id !== id);
|
|
176
|
-
setOptions(newOptions);
|
|
177
|
-
// Clear correctAnswer if the removed option was the correct one
|
|
178
|
-
if (correctAnswer === id) {
|
|
179
|
-
setCorrectAnswer('');
|
|
180
|
-
}
|
|
181
|
-
form.clearErrors('options');
|
|
182
|
-
};
|
|
183
|
-
// ─── Client Mode Preview ──────────────────────────────────────────────────
|
|
184
|
-
if (isClientMode) {
|
|
185
|
-
const questionData = {
|
|
186
|
-
options,
|
|
187
|
-
audioUrl,
|
|
188
|
-
correctAnswer,
|
|
189
|
-
points,
|
|
190
|
-
explanation,
|
|
191
|
-
isBackup,
|
|
192
|
-
};
|
|
193
|
-
return (_jsxs("div", { className: "space-y-6", children: [_jsx(Card, { children: _jsx(CardHeader, { children: _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { children: [_jsx(CardTitle, { children: "Nghe v\u00E0 ch\u1ECDn trong nh\u00F3m \u0111\u00E1p \u00E1n (Preview)" }), _jsx("p", { className: "mt-1 text-sm text-gray-500", children: "Ch\u1EBF \u0111\u1ED9 xem tr\u01B0\u1EDBc" })] }), _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(ListenAndChooseFromAnswerGroupClient, { questionData: questionData, onSubmit: () => { }, isReviewMode: true, userAnswer: correctAnswer || undefined, correctAnswer: correctAnswer || undefined, explanation: explanation })] }));
|
|
194
|
-
}
|
|
195
|
-
// ─── Creator UI ────────────────────────────────────────────────────────────
|
|
196
|
-
return (_jsxs("div", { className: "space-y-6", children: [_jsx(CreatorGuide, { colorScheme: "orange", steps: [
|
|
197
|
-
{ text: 'Upload các hình ảnh lựa chọn (options) cho nhóm — học sinh sẽ chọn đáp án đúng' },
|
|
198
|
-
{ text: 'Mỗi option cần có hình ảnh và nhãn mô tả' },
|
|
199
|
-
{ text: 'Upload audio câu hỏi cho từng câu' },
|
|
200
|
-
{ text: 'Chọn đáp án đúng (correct answer) từ danh sách options', highlight: true },
|
|
201
|
-
], tips: ['Nên có ít nhất 3-4 options để tạo sự phân biệt rõ ràng'] }), _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-orange-500 via-amber-500 to-yellow-500" }), _jsx(CardHeader, { className: "pb-4", children: _jsxs("div", { className: "flex items-center justify-between", 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-orange-500 to-amber-600 shadow-md", children: _jsx(ListChecks, { className: "h-5 w-5 text-white" }) }), _jsxs("div", { children: [_jsx(CardTitle, { className: "text-xl font-bold text-gray-900", children: "Nh\u00F3m \u0111\u00E1p \u00E1n" }), _jsxs("div", { className: "mt-1.5 inline-flex items-center gap-1.5 rounded-full bg-orange-100 px-3 py-1 text-sm font-medium text-orange-700", children: [_jsx("span", { children: "\uD83D\uDDBC\uFE0F" }), _jsxs("span", { children: ["Nh\u00F3m ", currentGroupNumber] }), _jsx("span", { className: "text-xs font-normal opacity-75 ml-1", children: "(D\u00F9ng chung)" })] })] })] }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => setIsClientMode(true), disabled: options.filter((o) => o.imageUrl).length < 2 || !audioUrl, className: "gap-2 border-orange-200 text-orange-600 hover:bg-orange-50 hover:text-orange-700", children: [_jsx(Eye, { className: "h-4 w-4" }), "Preview"] })] }) })] }), _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" }), _jsxs(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-amber-100", children: _jsx(ListChecks, { className: "h-3.5 w-3.5 text-amber-600" }) }), _jsxs(CardTitle, { className: "text-lg 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"] })] }), form.formState.errors.options?.root && (_jsx("div", { className: "px-4 pb-2", children: _jsx("p", { className: "text-sm text-red-500", children: form.formState.errors.options.root.message }) }))] }), _jsxs(CardContent, { className: "grid grid-cols-1 md:grid-cols-2 gap-4", children: [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(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 gap-2", children: [_jsx("div", { className: "flex h-6 w-6 items-center justify-center rounded-md bg-indigo-100", children: _jsx(Music, { className: "h-3.5 w-3.5 text-indigo-600" }) }), _jsx(CardTitle, { className: "text-lg font-semibold text-gray-800", children: "C\u00E2u h\u1ECFi" })] }) }), _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: _jsxs("div", { className: "flex flex-col md:flex-row gap-6", children: [_jsxs("div", { className: "flex-1", children: [_jsx(Label, { className: "text-sm font-semibold text-gray-700 mb-2 block", children: "\u0110i\u1EC3m *" }), _jsx(Input, { type: "number", min: 1, value: points ?? '', onChange: (e) => {
|
|
202
|
-
const val = e.target.value;
|
|
203
|
-
setPoints(val === '' ? undefined : parseInt(val, 10));
|
|
204
|
-
form.clearErrors('points');
|
|
205
|
-
}, placeholder: "Nh\u1EADp \u0111i\u1EC3m s\u1ED1", className: `w-40 bg-white ${form.formState.errors.points ? 'border-red-500' : ''}` }), form.formState.errors.points && (_jsx("p", { className: "mt-1 text-sm text-red-500", children: form.formState.errors.points.message }))] }), _jsx("div", { className: "flex-1 flex flex-col justify-center pt-2" })] }) }), _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" }))] })] })] }), _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" }) })] })] }));
|
|
221
|
-
}
|
|
222
|
-
// ─── Inline image preview (for correct answer selection) ─────────────────────
|
|
223
|
-
function OptionImagePreviewInline({ imageUrl }) {
|
|
224
|
-
const { previewUrl } = usePresignedFileUrl(imageUrl);
|
|
225
|
-
if (!previewUrl) {
|
|
226
|
-
return (_jsx("div", { className: "flex h-16 w-full items-center justify-center rounded-lg bg-gray-100", children: _jsx(ImageIcon, { className: "h-6 w-6 text-gray-300" }) }));
|
|
227
|
-
}
|
|
228
|
-
return (_jsx("img", { src: previewUrl, alt: "Option", className: "h-16 w-full rounded-lg object-contain bg-gray-50" }));
|
|
229
|
-
}
|
|
230
|
-
export function ListenAndChooseFromAnswerGroupCreator(props) {
|
|
231
|
-
return _jsx(ListenAndChooseFromAnswerGroupCreatorContent, { ...props });
|
|
232
|
-
}
|
|
1
|
+
"use client";import{jsx as e,jsxs as r}from"react/jsx-runtime";import{useState as t,useRef as a,useEffect as n}from"react";import{Button as i}from"../../../../components/ui/button";import{Input as o}from"../../../../components/ui/input";import{Textarea as s}from"../../../../components/ui/textarea";import{Label as l}from"../../../../components/ui/label";import{Card as c,CardContent as d,CardHeader as m,CardTitle as h}from"../../../../components/ui/card";import{Pencil as g,Eye as p,Play as u,Pause as f,Plus as b,Trash2 as x,Music as N,CheckCircle2 as v,ListChecks as w,ImageIcon as y,BookOpen as U}from"lucide-react";import{toast as C}from"sonner";import{FileUpload as S}from"../../../../components/ui/file-upload";import{ListenAndChooseFromAnswerGroupClient as k}from"./ListenAndChooseFromAnswerGroupClient";import{usePresignedFileUrl as A,useAudioPreview as j,useDebouncedCallback as z}from"../../../../shared/lib/hooks";import{useReactHookForm as B}from"../../../../shared/lib/hooks/useReactHookForm";import{listenAndChooseFromAnswerGroupFormSchema as G}from"../../../../shared/lib/validation/listen-and-choose-from-answer-group.validation";import{useAnswerGroupQuestionGroup as P}from"../../_shared/hooks/useAnswerGroupQuestionGroup";import{generateObjectId as V}from"../../_shared/utils/id-generator";import{CreatorGuide as q}from"../../components/CreatorGuide";function E({audioUrl:t}){const{previewUrl:a}=A(t),{isPlaying:n,currentTime:o,duration:s,progressPercentage:l,handlePlayPause:c,handleProgressClick:d,formatTime:m}=j(a);return a?e("div",{className:"bg-muted/50 rounded-lg border p-3",children:r("div",{className:"flex items-center gap-3",children:[e(i,{variant:"outline",size:"icon",onClick:c,disabled:!a,className:"h-8 w-8",children:e(n?f:u,{className:"h-3.5 w-3.5"})}),r("div",{className:"text-muted-foreground min-w-[70px] text-xs",children:[m(o)," / ",m(s)]}),e("div",{className:"bg-muted relative h-1.5 flex-1 cursor-pointer rounded-full",onClick:d,children:e("div",{className:"bg-primary absolute top-0 left-0 h-full rounded-full transition-all",style:{width:`${l}%`}})})]})}):null}function I({imageUrl:r}){const{previewUrl:t}=A(r);return t?e("div",{className:"mt-2 rounded-lg border border-gray-200 overflow-hidden",children:e("img",{src:t,alt:"Option",className:"h-24 w-full object-contain bg-gray-50"})}):null}function D({initialData:u,onSave:f,onCancel:A,onChange:j,onUnsavedChangesChange:D,validationRef:O,externalErrors:T,groups:M,questionIndexInPart:R=0,partId:J=""}){const L={options:u?.options||[]},{isGrouped:Q,currentGroupNumber:$,options:_,setOptions:H}=P({partId:J,groups:M,questionIndexInPart:R,initialData:L}),[K,W]=t(u?.audioUrl||""),[X,Y]=t(null!=u?.correctAnswer?String(u.correctAnswer):""),[Z,ee]=t(u?.points),[re,te]=t(u?.explanation||""),[ae,ne]=t(u?.isBackup||!1),ie=a(u),oe=a(!1),[se,le]=t(!1);n(()=>{JSON.stringify(ie.current)!==JSON.stringify(u)&&(oe.current=!0,ie.current=u,u?(void 0!==u.audioUrl&&W(u.audioUrl),void 0!==u.correctAnswer&&null!==u.correctAnswer&&Y(String(u.correctAnswer)),void 0!==u.points&&ee(u.points),void 0!==u.explanation&&te(u.explanation),void 0!==u.isBackup&&ne(u.isBackup)):(W(""),Y(""),ee(void 0),te(""),ne(!1)),setTimeout(()=>{oe.current=!1},100))},[u]);const ce=a(j);n(()=>{ce.current=j},[j]);const de=z(e=>{ce.current?.(e)},300);n(()=>{oe.current||de({options:_,audioUrl:K,correctAnswer:X,points:Z,explanation:re,isBackup:ae,groups:M,groupNumber:$})},[_,K,X,Z,re,ae,M,$,de]);const me=B({defaultValues:{options:_,audioUrl:K,correctAnswer:X,points:Z,explanation:re,isBackup:ae},zodSchema:G,mode:"onBlur"});n(()=>{O&&(O.current={triggerValidation:async()=>{me.setValue("options",_),me.setValue("audioUrl",K),me.setValue("correctAnswer",String(X)),me.setValue("points",Z),me.setValue("isBackup",ae);return!!await me.trigger()},getFormData:()=>({options:_,audioUrl:K,correctAnswer:X,points:Z,explanation:re,isBackup:ae,groups:M,groupNumber:$})})},[O,_,K,X,Z,re,ae,M,$,me]);const he=()=>{const e={id:V(),imageUrl:"",label:""};H([..._,e])};if(se){const t={options:_,audioUrl:K,correctAnswer:X,points:Z,explanation:re,isBackup:ae};return r("div",{className:"space-y-6",children:[e(c,{children:e(m,{children:r("div",{className:"flex items-center justify-between",children:[r("div",{children:[e(h,{children:"Nghe và chọn trong nhóm đáp án (Preview)"}),e("p",{className:"mt-1 text-sm text-gray-500",children:"Chế độ xem trước"})]}),r(i,{variant:"outline",size:"sm",onClick:()=>le(!1),children:[e(g,{className:"mr-2 h-4 w-4"}),"Quay lại Edit Mode"]})]})})}),e(k,{questionData:t,onSubmit:()=>{},isReviewMode:!0,userAnswer:X||void 0,correctAnswer:X||void 0,explanation:re})]})}return r("div",{className:"space-y-6",children:[e(q,{colorScheme:"orange",steps:[{text:"Upload các hình ảnh lựa chọn (options) cho nhóm — học sinh sẽ chọn đáp án đúng"},{text:"Mỗi option cần có hình ảnh và nhãn mô tả"},{text:"Upload audio câu hỏi cho từng câu"},{text:"Chọn đáp án đúng (correct answer) từ danh sách options",highlight:!0}],tips:["Nên có ít nhất 3-4 options để tạo sự phân biệt rõ ràng"]}),r(c,{className:"overflow-hidden border-0 bg-white shadow-lg shadow-blue-100/50",children:[e("div",{className:"h-1.5 bg-gradient-to-r from-orange-500 via-amber-500 to-yellow-500"}),e(m,{className:"pb-4",children:r("div",{className:"flex items-center justify-between",children:[r("div",{className:"flex items-center gap-3",children:[e("div",{className:"flex h-10 w-10 items-center justify-center rounded-xl bg-gradient-to-br from-orange-500 to-amber-600 shadow-md",children:e(w,{className:"h-5 w-5 text-white"})}),r("div",{children:[e(h,{className:"text-xl font-bold text-gray-900",children:"Nhóm đáp án"}),r("div",{className:"mt-1.5 inline-flex items-center gap-1.5 rounded-full bg-orange-100 px-3 py-1 text-sm font-medium text-orange-700",children:[e("span",{children:"🖼️"}),r("span",{children:["Nhóm ",$]}),e("span",{className:"text-xs font-normal opacity-75 ml-1",children:"(Dùng chung)"})]})]})]}),r(i,{variant:"outline",size:"sm",onClick:()=>le(!0),disabled:_.filter(e=>e.imageUrl).length<2||!K,className:"gap-2 border-orange-200 text-orange-600 hover:bg-orange-50 hover:text-orange-700",children:[e(p,{className:"h-4 w-4"}),"Preview"]})]})})]}),r(c,{className:"overflow-hidden border-0 bg-white shadow-lg shadow-amber-100/50",children:[e("div",{className:"h-1 bg-gradient-to-r from-amber-400 to-orange-400"}),r(m,{className:"pb-4",children:[r("div",{className:"flex items-center justify-between",children:[r("div",{className:"flex items-center gap-2",children:[e("div",{className:"flex h-6 w-6 items-center justify-center rounded-md bg-amber-100",children:e(w,{className:"h-3.5 w-3.5 text-amber-600"})}),r(h,{className:"text-lg font-semibold text-gray-800",children:["Nhóm đáp án (",_.length," đáp án)"]})]}),r(i,{variant:"outline",size:"sm",onClick:he,className:"gap-2 border-green-200 text-green-600 hover:bg-green-50 hover:text-green-700",children:[e(b,{className:"h-4 w-4"}),"Thêm đáp án"]})]}),me.formState.errors.options?.root&&e("div",{className:"px-4 pb-2",children:e("p",{className:"text-sm text-red-500",children:me.formState.errors.options.root.message})})]}),r(d,{className:"grid grid-cols-1 md:grid-cols-2 gap-4",children:[_.map((t,a)=>r(c,{className:"border border-gray-200",children:[e(m,{className:"pb-2 pt-3 px-4",children:r("div",{className:"flex items-center justify-between",children:[r("div",{className:"flex items-center gap-2",children:[e("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+a)}),r("span",{className:"text-sm font-semibold text-gray-700",children:["Đáp án ",String.fromCharCode(65+a)]})]}),_.length>2&&e(i,{variant:"ghost",size:"sm",onClick:()=>(e=>{if(_.length<=2)return void C.error("Cần tối thiểu 2 đáp án!");const r=_.filter(r=>r.id!==e);H(r),X===e&&Y(""),me.clearErrors("options")})(t.id),className:"h-7 w-7 p-0 text-red-500 hover:text-red-700 hover:bg-red-50",children:e(x,{className:"h-3.5 w-3.5"})})]})}),r(d,{className:"space-y-3 px-4 pb-4",children:[r("div",{children:[e(S,{id:`option-image-${t.id}`,label:"Ảnh đáp án",accept:"image/*",value:t.imageUrl,onChange:e=>{return r=t.id,a=e,H(_.map(e=>e.id===r?{...e,imageUrl:a}:e)),void me.clearErrors("options");var r,a},maxSize:5,placeholder:"Upload ảnh cho đáp án",required:!0,autoUpload:!0,prefix:"questions"}),me.formState.errors.options?.[a]?.imageUrl&&e("p",{className:"mt-1 text-sm text-red-500",children:me.formState.errors.options[a].imageUrl.message}),t.imageUrl&&e(I,{imageUrl:t.imageUrl})]}),r("div",{children:[e(l,{className:"font-medium",children:"Nhãn"}),e(o,{value:t.label||"",onChange:e=>{return r=t.id,a=e.target.value,H(_.map(e=>e.id===r?{...e,label:a}:e)),void me.clearErrors("options");var r,a},placeholder:"Nhập label / mô tả ngắn",className:"mt-1"})]})]})]},t.id)),0===_.length&&r("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:[e(y,{className:"h-10 w-10 text-amber-400 mb-2"}),e("p",{className:"text-sm text-gray-500 mb-3",children:"Chưa có đáp án nào"}),r(i,{variant:"outline",size:"sm",onClick:he,className:"gap-2",children:[e(b,{className:"h-4 w-4"}),"Thêm đáp án đầu tiên"]})]})]})]}),r(c,{className:"overflow-hidden border-0 bg-white shadow-lg shadow-indigo-100/50",children:[e("div",{className:"h-1 bg-gradient-to-r from-indigo-400 to-purple-400"}),e(m,{className:"pb-4",children:r("div",{className:"flex items-center gap-2",children:[e("div",{className:"flex h-6 w-6 items-center justify-center rounded-md bg-indigo-100",children:e(N,{className:"h-3.5 w-3.5 text-indigo-600"})}),e(h,{className:"text-lg font-semibold text-gray-800",children:"Câu hỏi"})]})}),r(d,{className:"space-y-6",children:[e("div",{className:"rounded-xl border border-gray-200 bg-gradient-to-r from-gray-50 to-slate-50 p-4",children:r("div",{className:"flex flex-col md:flex-row gap-6",children:[r("div",{className:"flex-1",children:[e(l,{className:"text-sm font-semibold text-gray-700 mb-2 block",children:"Điểm *"}),e(o,{type:"number",min:1,value:Z??"",onChange:e=>{const r=e.target.value;ee(""===r?void 0:parseInt(r,10)),me.clearErrors("points")},placeholder:"Nhập điểm số",className:"w-40 bg-white "+(me.formState.errors.points?"border-red-500":"")}),me.formState.errors.points&&e("p",{className:"mt-1 text-sm text-red-500",children:me.formState.errors.points.message})]}),e("div",{className:"flex-1 flex flex-col justify-center pt-2"})]})}),r("div",{children:[e(S,{id:"question-audio",label:"File âm thanh",accept:"audio/*",value:K,onChange:e=>{W(e),me.clearErrors("audioUrl")},maxSize:10,placeholder:"Upload file audio",required:!0,autoUpload:!0,prefix:"questions"}),me.formState.errors.audioUrl&&e("p",{className:"mt-1 text-sm text-red-500",children:me.formState.errors.audioUrl.message}),K&&e(E,{audioUrl:K})]}),r("div",{children:[e(l,{className:"text-sm font-semibold text-gray-700 mb-2 block",children:"Chọn đáp án đúng *"}),_.filter(e=>e.imageUrl).length>0?e("div",{className:"grid grid-cols-2 gap-3 sm:grid-cols-3",children:_.map((t,a)=>{if(!t.imageUrl)return null;const n=X===t.id;return r("button",{type:"button",onClick:()=>{Y(t.id),me.clearErrors("correctAnswer")},className:"relative text-left rounded-xl border-2 p-2 transition-all "+(n?"border-green-500 bg-green-50 ring-2 ring-green-200 shadow-md":"border-gray-200 hover:border-gray-400 hover:shadow-sm"),children:[r("div",{className:"flex flex-col items-center gap-2",children:[e("div",{className:"flex h-7 w-7 flex-shrink-0 items-center justify-center rounded-lg text-sm font-bold shadow-sm "+(n?"bg-green-500 text-white":"bg-gray-100 text-gray-600"),children:String.fromCharCode(65+a)}),e(F,{imageUrl:t.imageUrl}),t.label&&e("p",{className:"text-xs text-center "+(n?"text-green-700 font-medium":"text-gray-600"),children:t.label})]}),n&&e("div",{className:"absolute top-1 right-1",children:e(v,{className:"h-5 w-5 text-green-500"})})]},t.id)})}):e("p",{className:"text-sm text-amber-600 italic",children:"Vui lòng upload ảnh cho đáp án ở phần trên trước"}),me.formState.errors.correctAnswer&&e("div",{className:"mt-3 rounded-lg border border-red-200 bg-red-50 p-3",children:r("p",{className:"text-sm text-red-700",children:["⚠️ ",me.formState.errors.correctAnswer.message]})}),!me.formState.errors.correctAnswer&&!X&&_.filter(e=>e.imageUrl).length>0&&e("p",{className:"mt-1 text-xs text-amber-600",children:"Chưa chọn đáp án đúng"})]})]})]}),r(c,{className:"overflow-hidden border-0 bg-white shadow-lg shadow-amber-100/50",children:[e("div",{className:"h-1 bg-gradient-to-r from-amber-400 to-orange-400"}),e(m,{className:"pb-4",children:r("div",{className:"flex items-center gap-3",children:[e("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:e(U,{className:"h-4 w-4 text-white"})}),r("div",{children:[r("div",{className:"flex items-center gap-2",children:[e(h,{className:"text-lg font-semibold text-gray-800",children:"Giải thích"}),e("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ông bắt buộc"})]}),e("p",{className:"text-sm text-gray-500 mt-0.5",children:"Giải thích cho đáp án đúng — hiển thị sau khi học sinh hoàn thành câu hỏi"})]})]})}),e(d,{children:e(s,{value:re,onChange:e=>te(e.target.value),placeholder:"VD: Câu trả lời đúng là... vì...",rows:3,className:"min-h-[80px] border-gray-200 bg-white transition-all focus:border-amber-300 focus:ring-amber-200 resize-none"})})]})]})}function F({imageUrl:r}){const{previewUrl:t}=A(r);return t?e("img",{src:t,alt:"Option",className:"h-16 w-full rounded-lg object-contain bg-gray-50"}):e("div",{className:"flex h-16 w-full items-center justify-center rounded-lg bg-gray-100",children:e(y,{className:"h-6 w-6 text-gray-300"})})}export function ListenAndChooseFromAnswerGroupCreator(r){return e(D,{...r})}
|
|
@@ -1,2 +1 @@
|
|
|
1
|
-
export
|
|
2
|
-
export * from './ListenAndChooseFromAnswerGroupClient';
|
|
1
|
+
export*from"./ListenAndChooseFromAnswerGroupCreator";export*from"./ListenAndChooseFromAnswerGroupClient";
|
|
@@ -1,82 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
export const listenAndChooseFromAnswerGroupRegistration = {
|
|
3
|
-
component: ListenAndChooseFromAnswerGroupCreator,
|
|
4
|
-
transformInitialData: (initialData) => {
|
|
5
|
-
if (!initialData)
|
|
6
|
-
return undefined;
|
|
7
|
-
const d = initialData;
|
|
8
|
-
// Group shared data: options
|
|
9
|
-
// Use data from questionGroup.payload first (API format), fallback to answer (reverse-transformed)
|
|
10
|
-
const options = (d?.questionGroup?.payload?.options?.length > 0 && d?.questionGroup?.payload?.options) ||
|
|
11
|
-
(d?.answer?.options?.length > 0 && d?.answer?.options) ||
|
|
12
|
-
(d?.content?.options?.length > 0 && d?.content?.options) ||
|
|
13
|
-
d?.options || [];
|
|
14
|
-
// Question specific data - prioritize content (API) then answer (reverse-transformed)
|
|
15
|
-
const audioUrl = d?.content?.audioUrl || d?.answer?.audioUrl || d?.audioUrl || '';
|
|
16
|
-
// Correct answer from multiple possible locations
|
|
17
|
-
const correctAnswer = d?.answer?.correctAnswer ??
|
|
18
|
-
d?.correctAnswer?.answer ??
|
|
19
|
-
d?.content?.correctAnswer ??
|
|
20
|
-
-1;
|
|
21
|
-
// Points and explanation - top-level fields take priority
|
|
22
|
-
const points = d?.points || d?.answer?.points || 1;
|
|
23
|
-
const explanation = d?.explanation || d?.answer?.explanation || d?.content?.explanation || '';
|
|
24
|
-
const isBackup = d?.content?.isBackup || d?.isBackup || false;
|
|
25
|
-
console.log('[LISTEN_AND_CHOOSE_FROM_ANSWER_GROUP register] transformInitialData:', {
|
|
26
|
-
correctAnswer,
|
|
27
|
-
audioUrl: audioUrl ? '✓' : '✗',
|
|
28
|
-
optionsCount: options?.length || 0,
|
|
29
|
-
sources: {
|
|
30
|
-
'answer.correctAnswer': d?.answer?.correctAnswer,
|
|
31
|
-
'correctAnswer.answer': d?.correctAnswer?.answer,
|
|
32
|
-
'questionGroup.payload.options': d?.questionGroup?.payload?.options?.length,
|
|
33
|
-
}
|
|
34
|
-
});
|
|
35
|
-
return {
|
|
36
|
-
audioUrl,
|
|
37
|
-
options,
|
|
38
|
-
correctAnswer,
|
|
39
|
-
points,
|
|
40
|
-
explanation,
|
|
41
|
-
isBackup,
|
|
42
|
-
isCompleted: d?.isCompleted,
|
|
43
|
-
};
|
|
44
|
-
},
|
|
45
|
-
getExtraProps: (ctx) => ({
|
|
46
|
-
groups: ctx.groups,
|
|
47
|
-
questionIndexInPart: ctx.questionIndexInPart,
|
|
48
|
-
partId: ctx.partId,
|
|
49
|
-
onUnsavedChangesChange: ctx.onUnsavedChangesChange,
|
|
50
|
-
validationRef: ctx.validationRef,
|
|
51
|
-
}),
|
|
52
|
-
wrapOnSave: (data, ctx) => ({
|
|
53
|
-
type: ctx.questionType,
|
|
54
|
-
content: {
|
|
55
|
-
...(typeof ctx.state.content === 'object' && ctx.state.content !== null ? ctx.state.content : {}),
|
|
56
|
-
audioUrl: data.audioUrl,
|
|
57
|
-
options: data.options,
|
|
58
|
-
correctAnswer: data.correctAnswer,
|
|
59
|
-
isBackup: data.isBackup || false,
|
|
60
|
-
},
|
|
61
|
-
points: data?.points ?? ctx.state.points ?? 1,
|
|
62
|
-
level: ctx.state.level,
|
|
63
|
-
difficulty: ctx.state.difficulty,
|
|
64
|
-
skill: ctx.state.skill,
|
|
65
|
-
explanation: data?.explanation || '',
|
|
66
|
-
}),
|
|
67
|
-
wrapOnChange: (data, ctx) => ({
|
|
68
|
-
type: ctx.questionType,
|
|
69
|
-
content: {
|
|
70
|
-
...(typeof ctx.state.content === 'object' && ctx.state.content !== null ? ctx.state.content : {}),
|
|
71
|
-
audioUrl: data.audioUrl,
|
|
72
|
-
options: data.options,
|
|
73
|
-
correctAnswer: data.correctAnswer,
|
|
74
|
-
isBackup: data.isBackup || false,
|
|
75
|
-
},
|
|
76
|
-
points: data?.points ?? ctx.state.points ?? 1,
|
|
77
|
-
level: ctx.state.level,
|
|
78
|
-
difficulty: ctx.state.difficulty,
|
|
79
|
-
skill: ctx.state.skill,
|
|
80
|
-
explanation: data?.explanation || '',
|
|
81
|
-
}),
|
|
82
|
-
};
|
|
1
|
+
import{ListenAndChooseFromAnswerGroupCreator as t}from"./ListenAndChooseFromAnswerGroupCreator";export const listenAndChooseFromAnswerGroupRegistration={component:t,transformInitialData:t=>{if(!t)return;const n=t,o=n?.questionGroup?.payload?.options?.length>0&&n?.questionGroup?.payload?.options||n?.answer?.options?.length>0&&n?.answer?.options||n?.content?.options?.length>0&&n?.content?.options||n?.options||[],e=n?.content?.audioUrl||n?.answer?.audioUrl||n?.audioUrl||"",s=n?.answer?.correctAnswer??n?.correctAnswer?.answer??n?.content?.correctAnswer??-1,r=n?.points||n?.answer?.points||1,a=n?.explanation||n?.answer?.explanation||n?.content?.explanation||"",i=n?.content?.isBackup||n?.isBackup||!1;return console.log("[LISTEN_AND_CHOOSE_FROM_ANSWER_GROUP register] transformInitialData:",{correctAnswer:s,audioUrl:e?"✓":"✗",optionsCount:o?.length||0,sources:{"answer.correctAnswer":n?.answer?.correctAnswer,"correctAnswer.answer":n?.correctAnswer?.answer,"questionGroup.payload.options":n?.questionGroup?.payload?.options?.length}}),{audioUrl:e,options:o,correctAnswer:s,points:r,explanation:a,isBackup:i,isCompleted:n?.isCompleted}},getExtraProps:t=>({groups:t.groups,questionIndexInPart:t.questionIndexInPart,partId:t.partId,onUnsavedChangesChange:t.onUnsavedChangesChange,validationRef:t.validationRef}),wrapOnSave:(t,n)=>({type:n.questionType,content:{..."object"==typeof n.state.content&&null!==n.state.content?n.state.content:{},audioUrl:t.audioUrl,options:t.options,correctAnswer:t.correctAnswer,isBackup:t.isBackup||!1},points:t?.points??n.state.points??1,level:n.state.level,difficulty:n.state.difficulty,skill:n.state.skill,explanation:t?.explanation||""}),wrapOnChange:(t,n)=>({type:n.questionType,content:{..."object"==typeof n.state.content&&null!==n.state.content?n.state.content:{},audioUrl:t.audioUrl,options:t.options,correctAnswer:t.correctAnswer,isBackup:t.isBackup||!1},points:t?.points??n.state.points??1,level:n.state.level,difficulty:n.state.difficulty,skill:n.state.skill,explanation:t?.explanation||""})};
|
|
@@ -1,27 +1 @@
|
|
|
1
|
-
export const transformListenAndChooseFromAnswerGroup =
|
|
2
|
-
const answerData = question.answer;
|
|
3
|
-
// Question-specific content: only audioUrl
|
|
4
|
-
const apiContent = {};
|
|
5
|
-
if (answerData?.audioUrl) {
|
|
6
|
-
apiContent.audioUrl = answerData.audioUrl;
|
|
7
|
-
}
|
|
8
|
-
const apiCorrectAnswer = {
|
|
9
|
-
answer: answerData.correctAnswer,
|
|
10
|
-
};
|
|
11
|
-
// Group shared data: options go into questionGroup.payload
|
|
12
|
-
const answerGroupData = {
|
|
13
|
-
options: (answerData.options || []).map((opt) => ({
|
|
14
|
-
id: opt.id,
|
|
15
|
-
imageUrl: opt.imageUrl,
|
|
16
|
-
...(opt.label ? { label: opt.label } : {}),
|
|
17
|
-
})),
|
|
18
|
-
};
|
|
19
|
-
// Get group number directly from the component payload
|
|
20
|
-
const groupNumber = answerData.groupNumber || 1;
|
|
21
|
-
return {
|
|
22
|
-
apiContent,
|
|
23
|
-
apiCorrectAnswer,
|
|
24
|
-
answerGroupData,
|
|
25
|
-
groupNumber,
|
|
26
|
-
};
|
|
27
|
-
};
|
|
1
|
+
export const transformListenAndChooseFromAnswerGroup=r=>{const o=r.answer,e={};o?.audioUrl&&(e.audioUrl=o.audioUrl);return{apiContent:e,apiCorrectAnswer:{answer:o.correctAnswer},answerGroupData:{options:(o.options||[]).map(r=>({id:r.id,imageUrl:r.imageUrl,...r.label?{label:r.label}:{}}))},groupNumber:o.groupNumber||1}};
|