@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,87 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
export const listenAndSpeakInfoExchangeRegistration = {
|
|
3
|
-
component: ListenAndSpeakInfoExchangeCreator,
|
|
4
|
-
wrapOnSave: (data, ctx) => ({
|
|
5
|
-
type: ctx.questionType,
|
|
6
|
-
points: data?.points ?? ctx.state.points ?? 1,
|
|
7
|
-
level: ctx.state.level,
|
|
8
|
-
difficulty: ctx.state.difficulty,
|
|
9
|
-
skill: ctx.state.skill,
|
|
10
|
-
explanation: data?.explanation || '',
|
|
11
|
-
content: {
|
|
12
|
-
...(typeof ctx.state.content === 'object' && ctx.state.content !== null ? ctx.state.content : {}),
|
|
13
|
-
audioUrl: data.audioUrl,
|
|
14
|
-
},
|
|
15
|
-
answer: {
|
|
16
|
-
expectedAnswers: data.expectedAnswers || [],
|
|
17
|
-
},
|
|
18
|
-
...(data.groupNumber && {
|
|
19
|
-
questionGroup: {
|
|
20
|
-
payload: {
|
|
21
|
-
title: data.title || '',
|
|
22
|
-
imageUrl: data.imageUrl || '',
|
|
23
|
-
candidateFields: data.candidateFields || data.fields || [],
|
|
24
|
-
examinerFields: data.examinerFields || [],
|
|
25
|
-
},
|
|
26
|
-
groupNumber: data.groupNumber,
|
|
27
|
-
},
|
|
28
|
-
}),
|
|
29
|
-
}),
|
|
30
|
-
wrapOnChange: (data, ctx) => ({
|
|
31
|
-
type: ctx.questionType,
|
|
32
|
-
points: data?.points ?? ctx.state.points ?? 1,
|
|
33
|
-
level: ctx.state.level,
|
|
34
|
-
difficulty: ctx.state.difficulty,
|
|
35
|
-
skill: ctx.state.skill,
|
|
36
|
-
explanation: data?.explanation || '',
|
|
37
|
-
content: {
|
|
38
|
-
...(typeof ctx.state.content === 'object' && ctx.state.content !== null ? ctx.state.content : {}),
|
|
39
|
-
audioUrl: data.audioUrl,
|
|
40
|
-
},
|
|
41
|
-
answer: {
|
|
42
|
-
expectedAnswers: data.expectedAnswers || [],
|
|
43
|
-
},
|
|
44
|
-
...(data.groupNumber && {
|
|
45
|
-
questionGroup: {
|
|
46
|
-
payload: {
|
|
47
|
-
title: data.title || '',
|
|
48
|
-
imageUrl: data.imageUrl || '',
|
|
49
|
-
candidateFields: data.candidateFields || data.fields || [],
|
|
50
|
-
examinerFields: data.examinerFields || [],
|
|
51
|
-
},
|
|
52
|
-
groupNumber: data.groupNumber,
|
|
53
|
-
},
|
|
54
|
-
}),
|
|
55
|
-
}),
|
|
56
|
-
transformInitialData: (data) => {
|
|
57
|
-
const content = data?.content;
|
|
58
|
-
const answer = data?.answer || data?.correctAnswer || data?.correct_answer;
|
|
59
|
-
const groupPayload = data?.questionGroup?.payload || {};
|
|
60
|
-
const points = data?.total_points
|
|
61
|
-
? parseFloat(data.total_points)
|
|
62
|
-
: (data?.points || content?.points || answer?.points || 1);
|
|
63
|
-
const explanation = data?.explanation || '';
|
|
64
|
-
const legacyFields = groupPayload.fields || [];
|
|
65
|
-
return {
|
|
66
|
-
// Group data
|
|
67
|
-
title: groupPayload.title || '',
|
|
68
|
-
imageUrl: groupPayload.imageUrl || '',
|
|
69
|
-
fields: legacyFields,
|
|
70
|
-
candidateFields: groupPayload.candidateFields || legacyFields,
|
|
71
|
-
examinerFields: groupPayload.examinerFields || [],
|
|
72
|
-
// Per-question
|
|
73
|
-
audioUrl: content?.audioUrl || '',
|
|
74
|
-
expectedAnswers: answer?.expectedAnswers || [],
|
|
75
|
-
explanation,
|
|
76
|
-
points,
|
|
77
|
-
};
|
|
78
|
-
},
|
|
79
|
-
getExtraProps: (ctx) => ({
|
|
80
|
-
onUnsavedChangesChange: ctx.onUnsavedChangesChange,
|
|
81
|
-
validationRef: ctx.validationRef,
|
|
82
|
-
groups: ctx.groups,
|
|
83
|
-
questionIndexInPart: ctx.questionIndexInPart,
|
|
84
|
-
partId: ctx.partId,
|
|
85
|
-
viewMode: ctx.viewMode,
|
|
86
|
-
}),
|
|
87
|
-
};
|
|
1
|
+
import{ListenAndSpeakInfoExchangeCreator as e}from"./ListenAndSpeakInfoExchangeCreator";export const listenAndSpeakInfoExchangeRegistration={component:e,wrapOnSave:(e,t)=>({type:t.questionType,points:e?.points??t.state.points??1,level:t.state.level,difficulty:t.state.difficulty,skill:t.state.skill,explanation:e?.explanation||"",content:{..."object"==typeof t.state.content&&null!==t.state.content?t.state.content:{},audioUrl:e.audioUrl},answer:{expectedAnswers:e.expectedAnswers||[]},...e.groupNumber&&{questionGroup:{payload:{title:e.title||"",imageUrl:e.imageUrl||"",candidateFields:e.candidateFields||e.fields||[],examinerFields:e.examinerFields||[]},groupNumber:e.groupNumber}}}),wrapOnChange:(e,t)=>({type:t.questionType,points:e?.points??t.state.points??1,level:t.state.level,difficulty:t.state.difficulty,skill:t.state.skill,explanation:e?.explanation||"",content:{..."object"==typeof t.state.content&&null!==t.state.content?t.state.content:{},audioUrl:e.audioUrl},answer:{expectedAnswers:e.expectedAnswers||[]},...e.groupNumber&&{questionGroup:{payload:{title:e.title||"",imageUrl:e.imageUrl||"",candidateFields:e.candidateFields||e.fields||[],examinerFields:e.examinerFields||[]},groupNumber:e.groupNumber}}}),transformInitialData:e=>{const t=e?.content,n=e?.answer||e?.correctAnswer||e?.correct_answer,a=e?.questionGroup?.payload||{},i=e?.total_points?parseFloat(e.total_points):e?.points||t?.points||n?.points||1,s=e?.explanation||"",o=a.fields||[];return{title:a.title||"",imageUrl:a.imageUrl||"",fields:o,candidateFields:a.candidateFields||o,examinerFields:a.examinerFields||[],audioUrl:t?.audioUrl||"",expectedAnswers:n?.expectedAnswers||[],explanation:s,points:i}},getExtraProps:e=>({onUnsavedChangesChange:e.onUnsavedChangesChange,validationRef:e.validationRef,groups:e.groups,questionIndexInPart:e.questionIndexInPart,partId:e.partId,viewMode:e.viewMode})};
|
|
@@ -1,32 +1 @@
|
|
|
1
|
-
export const transformListenAndSpeakInfoExchange =
|
|
2
|
-
const contentData = question.content;
|
|
3
|
-
const answerData = question.answer;
|
|
4
|
-
const questionGroup = question.questionGroup;
|
|
5
|
-
const expectedAnswers = answerData?.expectedAnswers || [];
|
|
6
|
-
const apiContent = {
|
|
7
|
-
audioUrl: contentData?.audioUrl || answerData?.audioUrl || '',
|
|
8
|
-
};
|
|
9
|
-
const result = {
|
|
10
|
-
apiContent,
|
|
11
|
-
apiCorrectAnswer: {
|
|
12
|
-
expectedAnswers: expectedAnswers,
|
|
13
|
-
},
|
|
14
|
-
};
|
|
15
|
-
// Handle group data for info exchange fields
|
|
16
|
-
const groupPayload = questionGroup?.payload || {};
|
|
17
|
-
const title = answerData?.title || groupPayload.title || '';
|
|
18
|
-
const imageUrl = answerData?.imageUrl || groupPayload.imageUrl || '';
|
|
19
|
-
const legacyFields = answerData?.fields || groupPayload.fields || [];
|
|
20
|
-
const candidateFields = answerData?.candidateFields || groupPayload.candidateFields || legacyFields;
|
|
21
|
-
const examinerFields = answerData?.examinerFields || groupPayload.examinerFields || [];
|
|
22
|
-
if (title || imageUrl || candidateFields.length > 0 || examinerFields.length > 0) {
|
|
23
|
-
result.infoExchangeGroupData = {
|
|
24
|
-
title,
|
|
25
|
-
imageUrl,
|
|
26
|
-
candidateFields,
|
|
27
|
-
examinerFields,
|
|
28
|
-
};
|
|
29
|
-
result.groupNumber = answerData?.groupNumber || questionGroup?.groupNumber || 1;
|
|
30
|
-
}
|
|
31
|
-
return result;
|
|
32
|
-
};
|
|
1
|
+
export const transformListenAndSpeakInfoExchange=e=>{const i=e.content,r=e.answer,n=e.questionGroup,a=r?.expectedAnswers||[],t={apiContent:{audioUrl:i?.audioUrl||r?.audioUrl||""},apiCorrectAnswer:{expectedAnswers:a}},d=n?.payload||{},l=r?.title||d.title||"",o=r?.imageUrl||d.imageUrl||"",s=r?.fields||d.fields||[],u=r?.candidateFields||d.candidateFields||s,p=r?.examinerFields||d.examinerFields||[];return(l||o||u.length>0||p.length>0)&&(t.infoExchangeGroupData={title:l,imageUrl:o,candidateFields:u,examinerFields:p},t.groupNumber=r?.groupNumber||n?.groupNumber||1),t};
|
|
@@ -1,26 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
-
import { Mic, Volume2, CheckCircle2, Play, Pause, ImageIcon, Info, CircleOff } from 'lucide-react';
|
|
4
|
-
import { Button } from '../../../../components/ui/button';
|
|
5
|
-
import { usePresignedFileUrl, useAudioPreview } from '../../../../shared/lib/hooks';
|
|
6
|
-
// ─── Sub-component: Custom Audio Player ──────────────────────────────────────
|
|
7
|
-
function QuestionAudioPlayer({ audioUrl }) {
|
|
8
|
-
const { previewUrl } = usePresignedFileUrl(audioUrl);
|
|
9
|
-
const { isPlaying, currentTime, duration, progressPercentage, handlePlayPause, handleProgressClick, formatTime, } = useAudioPreview(previewUrl);
|
|
10
|
-
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" })] }));
|
|
11
|
-
}
|
|
12
|
-
// ─── Sub-component: Presigned image preview ──────────────────────────────────
|
|
13
|
-
function OddOneOutImage({ imageUrl, index }) {
|
|
14
|
-
const { previewUrl } = usePresignedFileUrl(imageUrl);
|
|
15
|
-
if (!previewUrl) {
|
|
16
|
-
return (_jsx("div", { className: "w-full h-full flex items-center justify-center bg-gray-100 text-gray-400", children: _jsx(ImageIcon, { className: "h-8 w-8" }) }));
|
|
17
|
-
}
|
|
18
|
-
return (_jsx("img", { src: previewUrl, alt: `Image ${index + 1}`, className: "w-full h-full object-cover" }));
|
|
19
|
-
}
|
|
20
|
-
// ─── Main Client Component ───────────────────────────────────────────────────
|
|
21
|
-
export function ListenAndSpeakOddOneOutClient({ questionData, isReviewMode = false, }) {
|
|
22
|
-
const { audioUrl, images, oddOneOutIndex, expectedAnswers = [], explanation } = questionData;
|
|
23
|
-
return (_jsxs("div", { className: "space-y-4", children: [questionData.isBackup && (_jsxs("div", { className: "inline-flex self-center items-center gap-1.5 px-3 py-1.5 rounded-md bg-amber-50 text-amber-700 border border-amber-200 text-xs font-bold shadow-sm", children: [_jsx("span", { children: "\uD83D\uDD04" }), " C\u00E2u h\u1ECFi d\u1EF1 ph\u00F2ng"] })), _jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm", children: [_jsx("div", { className: "flex items-center justify-between border-b border-gray-100 bg-gradient-to-r from-indigo-50 to-blue-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-indigo-500 to-blue-600 text-white shadow-sm", children: _jsx(CircleOff, { className: "h-4 w-4" }) }), _jsx("span", { className: "text-sm font-medium text-gray-700", children: "Nghe v\u00E0 n\u00F3i (T\u00ECm h\u00ECnh kh\u00E1c lo\u1EA1i)" })] }) }), _jsxs("div", { className: "p-5 space-y-5", children: [images && images.length > 0 && (_jsx("div", { className: "bg-gradient-to-br from-amber-50/80 to-orange-50/60 rounded-2xl p-6 border border-amber-100/80 shadow-sm space-y-4", children: images.length === 4 ? (_jsxs(_Fragment, { children: [_jsx("div", { className: "grid grid-cols-3 gap-4", children: images.slice(0, 3).map((imgUrl, index) => (_jsxs("div", { className: "space-y-1", children: [_jsxs("div", { className: "flex items-center gap-1.5 mb-1", children: [_jsx("span", { className: `flex h-6 w-6 items-center justify-center rounded-md text-xs font-bold ${isReviewMode && oddOneOutIndex === index ? 'bg-red-200 text-red-800' : 'bg-amber-200 text-amber-800'}`, children: index + 1 }), isReviewMode && oddOneOutIndex === index && (_jsxs("span", { className: "inline-flex items-center gap-1 rounded-full bg-red-100 border border-red-200 px-1.5 py-0.5 text-[10px] font-bold text-red-700", children: [_jsx(CircleOff, { className: "h-2.5 w-2.5" }), "Kh\u00E1c lo\u1EA1i"] }))] }), _jsx("div", { className: `relative aspect-square rounded-xl overflow-hidden border-2 bg-white shadow-sm ${isReviewMode && oddOneOutIndex === index ? 'border-red-400 ring-2 ring-red-200' : 'border-gray-200'}`, children: imgUrl ? (_jsx(OddOneOutImage, { imageUrl: imgUrl, index: index })) : (_jsx("div", { className: "w-full h-full flex items-center justify-center text-gray-400 text-sm", children: _jsx(ImageIcon, { className: "h-6 w-6" }) })) })] }, index))) }), _jsx("div", { className: "flex justify-center", children: _jsx("div", { className: "w-1/3", children: _jsxs("div", { className: "space-y-1", children: [_jsxs("div", { className: "flex items-center gap-1.5 mb-1", children: [_jsx("span", { className: `flex h-6 w-6 items-center justify-center rounded-md text-xs font-bold ${isReviewMode && oddOneOutIndex === 3 ? 'bg-red-200 text-red-800' : 'bg-amber-200 text-amber-800'}`, children: "4" }), isReviewMode && oddOneOutIndex === 3 && (_jsxs("span", { className: "inline-flex items-center gap-1 rounded-full bg-red-100 border border-red-200 px-1.5 py-0.5 text-[10px] font-bold text-red-700", children: [_jsx(CircleOff, { className: "h-2.5 w-2.5" }), "Kh\u00E1c lo\u1EA1i"] }))] }), _jsx("div", { className: `relative aspect-square rounded-xl overflow-hidden border-2 bg-white shadow-sm ${isReviewMode && oddOneOutIndex === 3 ? 'border-red-400 ring-2 ring-red-200' : 'border-gray-200'}`, children: images[3] ? (_jsx(OddOneOutImage, { imageUrl: images[3], index: 3 })) : (_jsx("div", { className: "w-full h-full flex items-center justify-center text-gray-400 text-sm", children: _jsx(ImageIcon, { className: "h-6 w-6" }) })) })] }) }) })] })) : (
|
|
24
|
-
/* Fallback: equal grid for other counts */
|
|
25
|
-
_jsx("div", { className: "grid grid-cols-2 gap-4", children: images.map((imgUrl, index) => (_jsxs("div", { className: "space-y-1", children: [_jsxs("div", { className: "flex items-center gap-1.5 mb-1", children: [_jsx("span", { className: `flex h-6 w-6 items-center justify-center rounded-md text-xs font-bold ${isReviewMode && oddOneOutIndex === index ? 'bg-red-200 text-red-800' : 'bg-amber-200 text-amber-800'}`, children: index + 1 }), isReviewMode && oddOneOutIndex === index && (_jsxs("span", { className: "inline-flex items-center gap-1 rounded-full bg-red-100 border border-red-200 px-1.5 py-0.5 text-[10px] font-bold text-red-700", children: [_jsx(CircleOff, { className: "h-2.5 w-2.5" }), "Kh\u00E1c lo\u1EA1i"] }))] }), _jsx("div", { className: `relative aspect-square rounded-xl overflow-hidden border-2 bg-white shadow-sm ${isReviewMode && oddOneOutIndex === index ? 'border-red-400 ring-2 ring-red-200' : 'border-gray-200'}`, children: imgUrl ? (_jsx(OddOneOutImage, { imageUrl: imgUrl, index: index })) : (_jsx("div", { className: "w-full h-full flex items-center justify-center text-gray-400 text-sm", children: _jsx(ImageIcon, { className: "h-6 w-6" }) })) })] }, index))) })) })), audioUrl ? (_jsx(QuestionAudioPlayer, { audioUrl: audioUrl })) : (_jsxs("div", { className: "flex items-center gap-2 rounded-lg border border-gray-200 bg-gray-50 px-4 py-3", children: [_jsx(Volume2, { className: "h-4 w-4 text-gray-400" }), _jsx("span", { className: "text-xs text-gray-400", children: "Ch\u01B0a c\u00F3 audio c\u00E2u h\u1ECFi" })] })), _jsxs("div", { className: "flex items-center gap-3 rounded-lg border border-dashed border-gray-300 bg-gray-50/80 px-4 py-3", children: [_jsx("div", { className: "flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-full bg-indigo-50 ring-2 ring-indigo-100/50", children: _jsx(Mic, { className: "w-4 h-4 text-indigo-400" }) }), _jsx("p", { className: "text-xs text-gray-400", children: "H\u1ECDc sinh s\u1EBD nh\u1EA5n n\u00FAt ghi \u00E2m v\u00E0 n\u00F3i c\u00E2u tr\u1EA3 l\u1EDDi t\u1EA1i \u0111\u00E2y" })] }), isReviewMode && expectedAnswers.length > 0 && (_jsxs("div", { className: "rounded-xl border border-green-200 bg-green-50/80 p-4", children: [_jsxs("div", { className: "flex items-center gap-2 mb-3", children: [_jsx(CheckCircle2, { className: "h-4 w-4 text-green-600" }), _jsx("h4", { className: "text-sm font-semibold text-green-800", children: "C\u00E1c \u0111\u00E1p \u00E1n mong mu\u1ED1n" })] }), _jsx("div", { className: "space-y-2", children: expectedAnswers.map((answer, index) => (_jsxs("div", { className: "flex items-center gap-2.5 rounded-lg bg-white/70 border border-green-100 px-3 py-2", children: [_jsx("span", { className: "flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-full bg-green-100 text-green-700 text-xs font-bold", children: index + 1 }), _jsx("span", { className: "text-sm text-green-800", children: answer })] }, index))) })] })), isReviewMode && explanation && (_jsxs("div", { className: "rounded-xl border border-indigo-200 bg-indigo-50 p-4", children: [_jsxs("div", { className: "flex items-center gap-2 mb-2", children: [_jsx(Info, { className: "h-4 w-4 text-indigo-600" }), _jsx("p", { className: "text-sm font-semibold text-indigo-800", children: "Gi\u1EA3i th\u00EDch" })] }), _jsx("p", { className: "text-sm text-indigo-700 leading-relaxed", children: explanation })] }))] })] })] }));
|
|
26
|
-
}
|
|
1
|
+
"use client";import{jsx as e,jsxs as r,Fragment as d}from"react/jsx-runtime";import{Mic as i,Volume2 as s,CheckCircle2 as l,Play as a,Pause as t,ImageIcon as n,Info as c,CircleOff as o}from"lucide-react";import{Button as m}from"../../../../components/ui/button";import{usePresignedFileUrl as g,useAudioPreview as h}from"../../../../shared/lib/hooks";function x({audioUrl:d}){const{previewUrl:i}=g(d),{isPlaying:l,currentTime:n,duration:c,progressPercentage:o,handlePlayPause:x,handleProgressClick:b,formatTime:u}=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:x,disabled:!i,className:"h-8 w-8 border-indigo-300 text-indigo-600 hover:bg-indigo-100",children:e(l?t:a,{className:"h-3.5 w-3.5"})}),r("div",{className:"text-muted-foreground min-w-[70px] text-xs",children:[u(n)," / ",u(c)]}),e("div",{className:"bg-indigo-200 relative h-1.5 flex-1 cursor-pointer rounded-full",onClick:b,children:e("div",{className:"bg-indigo-500 absolute top-0 left-0 h-full rounded-full transition-all",style:{width:`${o}%`}})}),e(s,{className:"h-4 w-4 text-indigo-400"})]})}function b({imageUrl:r,index:d}){const{previewUrl:i}=g(r);return i?e("img",{src:i,alt:`Image ${d+1}`,className:"w-full h-full object-cover"}):e("div",{className:"w-full h-full flex items-center justify-center bg-gray-100 text-gray-400",children:e(n,{className:"h-8 w-8"})})}export function ListenAndSpeakOddOneOutClient({questionData:a,isReviewMode:t=!1}){const{audioUrl:m,images:g,oddOneOutIndex:h,expectedAnswers:u=[],explanation:f}=a;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:[e("div",{className:"flex items-center justify-between border-b border-gray-100 bg-gradient-to-r from-indigo-50 to-blue-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-indigo-500 to-blue-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à nói (Tìm hình khác loại)"})]})}),r("div",{className:"p-5 space-y-5",children:[g&&g.length>0&&e("div",{className:"bg-gradient-to-br from-amber-50/80 to-orange-50/60 rounded-2xl p-6 border border-amber-100/80 shadow-sm space-y-4",children:4===g.length?r(d,{children:[e("div",{className:"grid grid-cols-3 gap-4",children:g.slice(0,3).map((d,i)=>r("div",{className:"space-y-1",children:[r("div",{className:"flex items-center gap-1.5 mb-1",children:[e("span",{className:"flex h-6 w-6 items-center justify-center rounded-md text-xs font-bold "+(t&&h===i?"bg-red-200 text-red-800":"bg-amber-200 text-amber-800"),children:i+1}),t&&h===i&&r("span",{className:"inline-flex items-center gap-1 rounded-full bg-red-100 border border-red-200 px-1.5 py-0.5 text-[10px] font-bold text-red-700",children:[e(o,{className:"h-2.5 w-2.5"}),"Khác loại"]})]}),e("div",{className:"relative aspect-square rounded-xl overflow-hidden border-2 bg-white shadow-sm "+(t&&h===i?"border-red-400 ring-2 ring-red-200":"border-gray-200"),children:d?e(b,{imageUrl:d,index:i}):e("div",{className:"w-full h-full flex items-center justify-center text-gray-400 text-sm",children:e(n,{className:"h-6 w-6"})})})]},i))}),e("div",{className:"flex justify-center",children:e("div",{className:"w-1/3",children:r("div",{className:"space-y-1",children:[r("div",{className:"flex items-center gap-1.5 mb-1",children:[e("span",{className:"flex h-6 w-6 items-center justify-center rounded-md text-xs font-bold "+(t&&3===h?"bg-red-200 text-red-800":"bg-amber-200 text-amber-800"),children:"4"}),t&&3===h&&r("span",{className:"inline-flex items-center gap-1 rounded-full bg-red-100 border border-red-200 px-1.5 py-0.5 text-[10px] font-bold text-red-700",children:[e(o,{className:"h-2.5 w-2.5"}),"Khác loại"]})]}),e("div",{className:"relative aspect-square rounded-xl overflow-hidden border-2 bg-white shadow-sm "+(t&&3===h?"border-red-400 ring-2 ring-red-200":"border-gray-200"),children:g[3]?e(b,{imageUrl:g[3],index:3}):e("div",{className:"w-full h-full flex items-center justify-center text-gray-400 text-sm",children:e(n,{className:"h-6 w-6"})})})]})})})]}):e("div",{className:"grid grid-cols-2 gap-4",children:g.map((d,i)=>r("div",{className:"space-y-1",children:[r("div",{className:"flex items-center gap-1.5 mb-1",children:[e("span",{className:"flex h-6 w-6 items-center justify-center rounded-md text-xs font-bold "+(t&&h===i?"bg-red-200 text-red-800":"bg-amber-200 text-amber-800"),children:i+1}),t&&h===i&&r("span",{className:"inline-flex items-center gap-1 rounded-full bg-red-100 border border-red-200 px-1.5 py-0.5 text-[10px] font-bold text-red-700",children:[e(o,{className:"h-2.5 w-2.5"}),"Khác loại"]})]}),e("div",{className:"relative aspect-square rounded-xl overflow-hidden border-2 bg-white shadow-sm "+(t&&h===i?"border-red-400 ring-2 ring-red-200":"border-gray-200"),children:d?e(b,{imageUrl:d,index:i}):e("div",{className:"w-full h-full flex items-center justify-center text-gray-400 text-sm",children:e(n,{className:"h-6 w-6"})})})]},i))})}),m?e(x,{audioUrl:m}):r("div",{className:"flex items-center gap-2 rounded-lg border border-gray-200 bg-gray-50 px-4 py-3",children:[e(s,{className:"h-4 w-4 text-gray-400"}),e("span",{className:"text-xs text-gray-400",children:"Chưa có audio câu hỏi"})]}),r("div",{className:"flex items-center gap-3 rounded-lg border border-dashed border-gray-300 bg-gray-50/80 px-4 py-3",children:[e("div",{className:"flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-full bg-indigo-50 ring-2 ring-indigo-100/50",children:e(i,{className:"w-4 h-4 text-indigo-400"})}),e("p",{className:"text-xs text-gray-400",children:"Học sinh sẽ nhấn nút ghi âm và nói câu trả lời tại đây"})]}),t&&u.length>0&&r("div",{className:"rounded-xl border border-green-200 bg-green-50/80 p-4",children:[r("div",{className:"flex items-center gap-2 mb-3",children:[e(l,{className:"h-4 w-4 text-green-600"}),e("h4",{className:"text-sm font-semibold text-green-800",children:"Các đáp án mong muốn"})]}),e("div",{className:"space-y-2",children:u.map((d,i)=>r("div",{className:"flex items-center gap-2.5 rounded-lg bg-white/70 border border-green-100 px-3 py-2",children:[e("span",{className:"flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-full bg-green-100 text-green-700 text-xs font-bold",children:i+1}),e("span",{className:"text-sm text-green-800",children:d})]},i))})]}),t&&f&&r("div",{className:"rounded-xl border border-indigo-200 bg-indigo-50 p-4",children:[r("div",{className:"flex items-center gap-2 mb-2",children:[e(c,{className:"h-4 w-4 text-indigo-600"}),e("p",{className:"text-sm font-semibold text-indigo-800",children:"Giải thích"})]}),e("p",{className:"text-sm text-indigo-700 leading-relaxed",children:f})]})]})]})]})}
|
|
@@ -1,247 +1 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useEffect, useRef, useState } from 'react';
|
|
4
|
-
import { Card, CardContent, CardHeader, CardTitle } from '../../../../components/ui/card';
|
|
5
|
-
import { Label } from '../../../../components/ui/label';
|
|
6
|
-
import { Button } from '../../../../components/ui/button';
|
|
7
|
-
import { Input } from '../../../../components/ui/input';
|
|
8
|
-
import { Textarea } from '../../../../components/ui/textarea';
|
|
9
|
-
import { Form, FormControl, FormField, FormItem, FormMessage } from '../../../../components/ui/form';
|
|
10
|
-
import { FileUpload } from '../../../../components/ui/file-upload';
|
|
11
|
-
import { PointsInput } from '../../../../components/ui/points-input';
|
|
12
|
-
// Hooks
|
|
13
|
-
import { useReactHookForm } from '../../../../shared/lib/hooks/useReactHookForm';
|
|
14
|
-
import { useDebouncedCallback, usePresignedFileUrl } from '../../../../shared/lib/hooks';
|
|
15
|
-
import { listenAndSpeakOddOneOutSchema, } from '../../../../shared/lib/validation/listen-and-speak-odd-one-out.validation';
|
|
16
|
-
// Icons
|
|
17
|
-
import { Plus, Trash2, Volume2, ImageIcon, MessageSquareText, HelpCircle, BookOpen, Eye, Pencil, Info, ListChecks, CircleOff, Check } from 'lucide-react';
|
|
18
|
-
import { CreatorGuide } from '../../components/CreatorGuide';
|
|
19
|
-
import { ListenAndSpeakOddOneOutClient } from './ListenAndSpeakOddOneOutClient';
|
|
20
|
-
const REQUIRED_IMAGE_COUNT = 4;
|
|
21
|
-
// ─── Sub-component: Image preview with presigned URL ─────────────────────────
|
|
22
|
-
function ImagePreview({ imageUrl, index, isOddOneOut }) {
|
|
23
|
-
const { previewUrl } = usePresignedFileUrl(imageUrl);
|
|
24
|
-
if (!previewUrl)
|
|
25
|
-
return null;
|
|
26
|
-
return (_jsxs("div", { className: "relative rounded-xl border border-gray-200 overflow-hidden bg-white shadow-sm group/img", children: [_jsx("img", { src: previewUrl, alt: `Image ${index + 1}`, className: "h-36 w-full object-contain bg-gray-50 p-2" }), isOddOneOut && (_jsx("div", { className: "absolute inset-0 bg-red-500/10 ring-2 ring-inset ring-red-400 rounded-xl" }))] }));
|
|
27
|
-
}
|
|
28
|
-
// ─── Main Creator Component ──────────────────────────────────────────────────
|
|
29
|
-
export function ListenAndSpeakOddOneOutCreator({ initialData, onChange, onUnsavedChangesChange, validationRef, }) {
|
|
30
|
-
// ---------------------------------------------------------------
|
|
31
|
-
// Form setup with Zod validation
|
|
32
|
-
// ---------------------------------------------------------------
|
|
33
|
-
const apiData = initialData;
|
|
34
|
-
const initImages = apiData?.images && apiData.images.length > 0
|
|
35
|
-
? apiData.images
|
|
36
|
-
: Array(REQUIRED_IMAGE_COUNT).fill('');
|
|
37
|
-
const form = useReactHookForm({
|
|
38
|
-
zodSchema: listenAndSpeakOddOneOutSchema,
|
|
39
|
-
defaultValues: {
|
|
40
|
-
audioUrl: apiData?.audioUrl || '',
|
|
41
|
-
images: initImages.length >= REQUIRED_IMAGE_COUNT
|
|
42
|
-
? initImages.slice(0, REQUIRED_IMAGE_COUNT)
|
|
43
|
-
: [...initImages, ...Array(REQUIRED_IMAGE_COUNT - initImages.length).fill('')],
|
|
44
|
-
oddOneOutIndex: apiData?.oddOneOutIndex ?? -1,
|
|
45
|
-
expectedAnswers: apiData?.expectedAnswers?.length > 0 ? apiData.expectedAnswers : [''],
|
|
46
|
-
points: apiData?.points ?? 1,
|
|
47
|
-
explanation: apiData?.explanation || '',
|
|
48
|
-
isBackup: apiData?.isBackup || false,
|
|
49
|
-
},
|
|
50
|
-
});
|
|
51
|
-
const { control, watch, setValue, trigger, clearErrors } = form;
|
|
52
|
-
const watchedAudioUrl = watch('audioUrl');
|
|
53
|
-
const watchedImages = watch('images');
|
|
54
|
-
const watchedOddOneOutIndex = watch('oddOneOutIndex');
|
|
55
|
-
const watchedExpectedAnswers = watch('expectedAnswers');
|
|
56
|
-
const watchedPoints = watch('points');
|
|
57
|
-
const watchedExplanation = watch('explanation');
|
|
58
|
-
const watchedIsBackup = watch('isBackup');
|
|
59
|
-
// ---------------------------------------------------------------
|
|
60
|
-
// Echo-detection refs
|
|
61
|
-
// ---------------------------------------------------------------
|
|
62
|
-
const previousInitialDataRef = useRef(initialData);
|
|
63
|
-
const isInitializingRef = useRef(false);
|
|
64
|
-
const onChangeRef = useRef(onChange);
|
|
65
|
-
const previousPayloadRef = useRef('');
|
|
66
|
-
useEffect(() => {
|
|
67
|
-
onChangeRef.current = onChange;
|
|
68
|
-
}, [onChange]);
|
|
69
|
-
// Debounced onChange
|
|
70
|
-
const debouncedOnChange = useDebouncedCallback((data) => {
|
|
71
|
-
onChangeRef.current?.(data);
|
|
72
|
-
}, 300);
|
|
73
|
-
// ---------------------------------------------------------------
|
|
74
|
-
// Connect to external validation trigger
|
|
75
|
-
// ---------------------------------------------------------------
|
|
76
|
-
useEffect(() => {
|
|
77
|
-
if (validationRef) {
|
|
78
|
-
validationRef.current = {
|
|
79
|
-
triggerValidation: async () => {
|
|
80
|
-
return await trigger();
|
|
81
|
-
},
|
|
82
|
-
getFormData: () => form.getValues(),
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
}, [validationRef, form, trigger]);
|
|
86
|
-
// ---------------------------------------------------------------
|
|
87
|
-
// Sync form when initialData props change (echo-detection)
|
|
88
|
-
// ---------------------------------------------------------------
|
|
89
|
-
useEffect(() => {
|
|
90
|
-
const normalizedInitialData = {
|
|
91
|
-
audioUrl: apiData?.audioUrl || '',
|
|
92
|
-
images: apiData?.images?.length >= REQUIRED_IMAGE_COUNT
|
|
93
|
-
? apiData.images.slice(0, REQUIRED_IMAGE_COUNT)
|
|
94
|
-
: [...(apiData?.images || []), ...Array(REQUIRED_IMAGE_COUNT - (apiData?.images?.length || 0)).fill('')],
|
|
95
|
-
oddOneOutIndex: apiData?.oddOneOutIndex ?? -1,
|
|
96
|
-
expectedAnswers: apiData?.expectedAnswers?.length > 0 ? apiData.expectedAnswers : [''],
|
|
97
|
-
points: apiData?.points ?? 1,
|
|
98
|
-
explanation: apiData?.explanation || '',
|
|
99
|
-
isBackup: apiData?.isBackup || false,
|
|
100
|
-
};
|
|
101
|
-
const initialDataChanged = JSON.stringify(previousInitialDataRef.current) !== JSON.stringify(initialData);
|
|
102
|
-
if (!initialDataChanged)
|
|
103
|
-
return;
|
|
104
|
-
previousInitialDataRef.current = initialData;
|
|
105
|
-
const currentFormData = {
|
|
106
|
-
audioUrl: watchedAudioUrl,
|
|
107
|
-
images: watchedImages,
|
|
108
|
-
oddOneOutIndex: watchedOddOneOutIndex,
|
|
109
|
-
expectedAnswers: watchedExpectedAnswers,
|
|
110
|
-
points: watchedPoints,
|
|
111
|
-
explanation: watchedExplanation,
|
|
112
|
-
isBackup: watchedIsBackup,
|
|
113
|
-
};
|
|
114
|
-
const isEchoFromOnChange = currentFormData.points === normalizedInitialData.points &&
|
|
115
|
-
currentFormData.explanation === normalizedInitialData.explanation &&
|
|
116
|
-
currentFormData.audioUrl === normalizedInitialData.audioUrl &&
|
|
117
|
-
currentFormData.oddOneOutIndex === normalizedInitialData.oddOneOutIndex &&
|
|
118
|
-
JSON.stringify(currentFormData.images) === JSON.stringify(normalizedInitialData.images) &&
|
|
119
|
-
JSON.stringify(currentFormData.expectedAnswers) === JSON.stringify(normalizedInitialData.expectedAnswers);
|
|
120
|
-
const wasJustSaved = initialData?.isCompleted === true;
|
|
121
|
-
if (isEchoFromOnChange && wasJustSaved)
|
|
122
|
-
return;
|
|
123
|
-
const shouldReset = !isEchoFromOnChange &&
|
|
124
|
-
(normalizedInitialData.audioUrl !== currentFormData.audioUrl ||
|
|
125
|
-
normalizedInitialData.oddOneOutIndex !== currentFormData.oddOneOutIndex ||
|
|
126
|
-
JSON.stringify(normalizedInitialData.images) !== JSON.stringify(currentFormData.images) ||
|
|
127
|
-
JSON.stringify(normalizedInitialData.expectedAnswers) !== JSON.stringify(currentFormData.expectedAnswers));
|
|
128
|
-
if (shouldReset) {
|
|
129
|
-
isInitializingRef.current = true;
|
|
130
|
-
form.reset(normalizedInitialData);
|
|
131
|
-
setTimeout(() => {
|
|
132
|
-
isInitializingRef.current = false;
|
|
133
|
-
}, 150);
|
|
134
|
-
}
|
|
135
|
-
}, [initialData, form, watchedAudioUrl, watchedImages, watchedOddOneOutIndex, watchedExpectedAnswers, watchedExplanation, watchedPoints, watchedIsBackup]);
|
|
136
|
-
// ---------------------------------------------------------------
|
|
137
|
-
// Fire onChange when form values change
|
|
138
|
-
// ---------------------------------------------------------------
|
|
139
|
-
const watchedImagesString = JSON.stringify(watchedImages);
|
|
140
|
-
const watchedExpectedAnswersString = JSON.stringify(watchedExpectedAnswers);
|
|
141
|
-
useEffect(() => {
|
|
142
|
-
if (isInitializingRef.current) {
|
|
143
|
-
const currentPayload = JSON.stringify({
|
|
144
|
-
audioUrl: watchedAudioUrl,
|
|
145
|
-
images: watchedImages,
|
|
146
|
-
oddOneOutIndex: watchedOddOneOutIndex,
|
|
147
|
-
expectedAnswers: watchedExpectedAnswers,
|
|
148
|
-
points: watchedPoints,
|
|
149
|
-
explanation: watchedExplanation,
|
|
150
|
-
isBackup: watchedIsBackup,
|
|
151
|
-
});
|
|
152
|
-
previousPayloadRef.current = currentPayload;
|
|
153
|
-
return;
|
|
154
|
-
}
|
|
155
|
-
const payload = {
|
|
156
|
-
audioUrl: watchedAudioUrl,
|
|
157
|
-
images: watchedImages,
|
|
158
|
-
oddOneOutIndex: watchedOddOneOutIndex,
|
|
159
|
-
expectedAnswers: watchedExpectedAnswers,
|
|
160
|
-
points: watchedPoints,
|
|
161
|
-
explanation: watchedExplanation,
|
|
162
|
-
isBackup: watchedIsBackup,
|
|
163
|
-
};
|
|
164
|
-
const payloadString = JSON.stringify(payload);
|
|
165
|
-
if (previousPayloadRef.current !== payloadString) {
|
|
166
|
-
previousPayloadRef.current = payloadString;
|
|
167
|
-
debouncedOnChange(payload);
|
|
168
|
-
}
|
|
169
|
-
}, [
|
|
170
|
-
watchedAudioUrl,
|
|
171
|
-
watchedImagesString,
|
|
172
|
-
watchedOddOneOutIndex,
|
|
173
|
-
watchedExpectedAnswersString,
|
|
174
|
-
watchedPoints,
|
|
175
|
-
watchedExplanation,
|
|
176
|
-
watchedIsBackup,
|
|
177
|
-
debouncedOnChange,
|
|
178
|
-
]);
|
|
179
|
-
// Track unsaved changes
|
|
180
|
-
useEffect(() => {
|
|
181
|
-
if (onUnsavedChangesChange) {
|
|
182
|
-
onUnsavedChangesChange(form.formState.isDirty);
|
|
183
|
-
}
|
|
184
|
-
}, [form.formState.isDirty, onUnsavedChangesChange]);
|
|
185
|
-
// ---------------------------------------------------------------
|
|
186
|
-
// Handlers
|
|
187
|
-
// ---------------------------------------------------------------
|
|
188
|
-
const handleImageChange = (index, url) => {
|
|
189
|
-
const newImages = [...watchedImages];
|
|
190
|
-
newImages[index] = url;
|
|
191
|
-
form.setValue('images', newImages, { shouldValidate: true, shouldDirty: true });
|
|
192
|
-
};
|
|
193
|
-
const clearImage = (index) => handleImageChange(index, '');
|
|
194
|
-
const addExpectedAnswer = () => {
|
|
195
|
-
const currentAnswers = form.getValues('expectedAnswers');
|
|
196
|
-
form.setValue('expectedAnswers', [...currentAnswers, ''], { shouldValidate: true, shouldDirty: true });
|
|
197
|
-
};
|
|
198
|
-
const handleExpectedAnswerChange = (index, value) => {
|
|
199
|
-
const newAnswers = [...watchedExpectedAnswers];
|
|
200
|
-
newAnswers[index] = value;
|
|
201
|
-
form.setValue('expectedAnswers', newAnswers, { shouldValidate: true, shouldDirty: true });
|
|
202
|
-
};
|
|
203
|
-
const removeExpectedAnswer = (index) => {
|
|
204
|
-
if (watchedExpectedAnswers.length > 1) {
|
|
205
|
-
const newAnswers = watchedExpectedAnswers.filter((_, i) => i !== index);
|
|
206
|
-
form.setValue('expectedAnswers', newAnswers, { shouldValidate: true, shouldDirty: true });
|
|
207
|
-
}
|
|
208
|
-
};
|
|
209
|
-
const uploadedCount = watchedImages.filter(img => img.trim() !== '').length;
|
|
210
|
-
const [isClientMode, setIsClientMode] = useState(false);
|
|
211
|
-
// ─── Preview Mode ────────────────────────────────────────────────
|
|
212
|
-
if (isClientMode) {
|
|
213
|
-
return (_jsxs("div", { className: "space-y-6", children: [_jsx(Card, { children: _jsx(CardHeader, { children: _jsxs("div", { className: "flex items-center justify-between", children: [_jsx(CardTitle, { className: "text-lg font-bold", children: "\uD83D\uDC41\uFE0F Ch\u1EBF \u0111\u1ED9 xem tr\u01B0\u1EDBc (Preview)" }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => setIsClientMode(false), children: [_jsx(Pencil, { className: "mr-2 h-4 w-4" }), "Quay l\u1EA1i ch\u1EC9nh s\u1EEDa"] })] }) }) }), _jsx(ListenAndSpeakOddOneOutClient, { questionData: {
|
|
214
|
-
audioUrl: watchedAudioUrl,
|
|
215
|
-
images: watchedImages,
|
|
216
|
-
oddOneOutIndex: watchedOddOneOutIndex,
|
|
217
|
-
expectedAnswers: watchedExpectedAnswers,
|
|
218
|
-
points: watchedPoints,
|
|
219
|
-
explanation: watchedExplanation,
|
|
220
|
-
} })] }));
|
|
221
|
-
}
|
|
222
|
-
return (_jsxs("div", { className: "space-y-6", children: [_jsx(CreatorGuide, { colorScheme: "blue", steps: [
|
|
223
|
-
{ text: 'Upload đúng 4 hình ảnh — 3 hình cùng loại và 1 hình khác loại' },
|
|
224
|
-
{ text: 'Upload audio câu hỏi cho mỗi câu — giám khảo hỏi hình nào khác loại và tại sao' },
|
|
225
|
-
{ text: 'Nhập đáp án mong muốn: tên hình khác loại + lý do', highlight: true },
|
|
226
|
-
{ text: 'Thêm giải thích chi tiết vì sao hình đó khác loại' },
|
|
227
|
-
], tips: ['Sự khác biệt nên rõ ràng về chủ đề hoặc danh mục, không phải về hình thức'] }), _jsxs(Card, { className: "overflow-hidden border-0 bg-white shadow-lg shadow-blue-100/50", children: [_jsx("div", { className: "h-1 bg-gradient-to-r from-blue-500 via-indigo-500 to-purple-500" }), _jsx(CardHeader, { className: "pb-4", children: _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-gradient-to-br from-blue-500 to-indigo-600 shadow-sm", children: _jsx(ListChecks, { className: "h-4 w-4 text-white" }) }), _jsxs("div", { children: [_jsx(CardTitle, { className: "text-lg font-bold text-gray-900", children: "Odd One Out \u2014 H\u00ECnh n\u00E0o kh\u00E1c lo\u1EA1i?" }), _jsx("p", { className: "text-sm text-gray-500", children: "H\u1ECDc sinh nh\u00ECn 4 h\u00ECnh v\u00E0 ch\u1EC9 ra h\u00ECnh kh\u00E1c lo\u1EA1i, gi\u1EA3i th\u00EDch l\u00FD do" })] })] }), _jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: () => setIsClientMode(true), disabled: !watchedAudioUrl, className: "gap-2 border-blue-200 text-blue-600 hover:bg-blue-50 disabled:opacity-50", children: [_jsx(Eye, { className: "h-4 w-4" }), "Preview"] })] }) })] }), _jsxs(Form, { ...form, children: [_jsxs(Card, { className: "overflow-hidden border-0 bg-white shadow-lg shadow-amber-100/50", children: [_jsx("div", { className: "h-1.5 bg-gradient-to-r from-amber-500 via-orange-500 to-red-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 shrink-0 items-center justify-center rounded-xl bg-gradient-to-br from-amber-500 to-orange-600 shadow-md", children: _jsx(ImageIcon, { className: "h-5 w-5 text-white" }) }), _jsxs("div", { children: [_jsx(CardTitle, { className: "text-lg font-bold text-gray-900", children: "4 H\u00ECnh \u1EA3nh" }), _jsx("p", { className: "text-sm text-gray-500", children: "Upload \u0111\u00FAng 4 h\u00ECnh \u2014 1 h\u00ECnh s\u1EBD l\u00E0 h\u00ECnh kh\u00E1c lo\u1EA1i" })] })] }), _jsxs("span", { className: `inline-flex items-center rounded-full px-3 py-1 text-xs font-bold ${uploadedCount === 4
|
|
228
|
-
? 'bg-green-100 text-green-700 border border-green-200'
|
|
229
|
-
: 'bg-orange-100 text-orange-600 border border-orange-200'}`, children: [uploadedCount, "/", REQUIRED_IMAGE_COUNT, " \u1EA3nh"] })] }) }), _jsx(CardContent, { children: _jsx(FormField, { control: control, name: "images", render: () => (_jsxs(FormItem, { children: [_jsx("div", { className: "grid grid-cols-2 gap-5", children: Array.from({ length: REQUIRED_IMAGE_COUNT }).map((_, index) => {
|
|
230
|
-
const imgUrl = watchedImages[index] || '';
|
|
231
|
-
const isSelected = watchedOddOneOutIndex === index;
|
|
232
|
-
return (_jsxs(Card, { className: `relative border-2 transition-all duration-300 overflow-hidden ${isSelected
|
|
233
|
-
? 'border-red-400 bg-gradient-to-b from-red-50/60 to-white shadow-lg shadow-red-100/60 ring-1 ring-red-200'
|
|
234
|
-
: 'border-gray-200 hover:border-gray-300 hover:shadow-md'}`, children: [isSelected && (_jsx("div", { className: "absolute top-0 left-0 right-0 h-1 bg-gradient-to-r from-red-400 via-rose-500 to-red-400" })), _jsx(CardHeader, { className: "pb-2 pt-3 px-4", children: _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("span", { className: "text-sm font-semibold text-gray-600 flex items-center gap-2", children: [_jsx("span", { className: `flex h-6 w-6 items-center justify-center rounded-md text-xs font-bold transition-colors duration-200 ${isSelected ? 'bg-red-100 text-red-700' : 'bg-amber-100 text-amber-700'}`, children: index + 1 }), "\u1EA2nh ", index + 1, isSelected && (_jsxs("span", { className: "inline-flex items-center gap-1 rounded-full bg-red-100 border border-red-200 px-2 py-0.5 text-xs font-bold text-red-700 animate-in fade-in slide-in-from-left-1 duration-200", children: [_jsx(CircleOff, { className: "h-3 w-3" }), "Kh\u00E1c lo\u1EA1i"] }))] }), _jsx("div", { className: "flex items-center gap-1", children: imgUrl && (_jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => clearImage(index), className: "h-7 w-7 rounded-full text-red-400 hover:text-red-600 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: [imgUrl ? (_jsx(ImagePreview, { imageUrl: imgUrl, index: index, isOddOneOut: isSelected })) : (_jsx(FileUpload, { id: `odd-one-out-img-${index}`, label: "", accept: "image/*", value: "", onChange: (url) => handleImageChange(index, url), placeholder: `Upload ảnh ${index + 1}`, maxSize: 5, autoUpload: true, prefix: "questions/odd-one-out" })), form.formState.errors?.images?.[index] && (_jsxs("p", { className: "text-sm font-medium text-destructive", children: ["Vui l\u00F2ng t\u1EA3i l\u00EAn \u1EA3nh ", index + 1] })), _jsxs("button", { type: "button", onClick: () => {
|
|
235
|
-
form.setValue('oddOneOutIndex', index, { shouldValidate: true, shouldDirty: true });
|
|
236
|
-
clearErrors('oddOneOutIndex');
|
|
237
|
-
}, className: `group/toggle w-full flex items-center gap-3 rounded-lg px-3 py-2.5 text-left transition-all duration-200 cursor-pointer ${isSelected
|
|
238
|
-
? 'bg-red-50 border border-red-200'
|
|
239
|
-
: 'bg-gray-50 border border-gray-200 hover:bg-red-50/50 hover:border-red-200/60'}`, children: [_jsx("div", { className: `flex h-5 w-5 shrink-0 items-center justify-center rounded-full border-2 transition-all duration-200 ${isSelected
|
|
240
|
-
? 'border-red-500 bg-red-500 shadow-sm shadow-red-200'
|
|
241
|
-
: 'border-gray-300 bg-white group-hover/toggle:border-red-300'}`, children: isSelected && (_jsx(Check, { className: "h-3 w-3 text-white", strokeWidth: 3 })) }), _jsx("div", { className: "flex-1 min-w-0", children: _jsx("span", { className: `text-xs font-semibold transition-colors duration-200 ${isSelected
|
|
242
|
-
? 'text-red-700'
|
|
243
|
-
: 'text-gray-500 group-hover/toggle:text-red-600'}`, children: isSelected ? 'Đã đánh dấu là hình khác loại' : 'Đánh dấu là hình khác loại' }) }), _jsx(CircleOff, { className: `h-3.5 w-3.5 shrink-0 transition-colors duration-200 ${isSelected
|
|
244
|
-
? 'text-red-400'
|
|
245
|
-
: 'text-gray-300 group-hover/toggle:text-red-300'}` })] })] })] }, index));
|
|
246
|
-
}) }), form.formState.errors?.images?.message && (_jsx(FormMessage, {})), form.formState.errors?.oddOneOutIndex?.message && (_jsxs("p", { className: "mt-3 text-sm font-medium text-destructive flex items-center gap-2", children: [_jsx(CircleOff, { className: "h-4 w-4" }), form.formState.errors.oddOneOutIndex.message] }))] })) }) })] }), _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-500 via-purple-500 to-pink-500" }), _jsx(CardHeader, { className: "pb-4", children: _jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-gradient-to-br from-indigo-500 to-purple-600 shadow-sm", children: _jsx(HelpCircle, { className: "h-4 w-4 text-white" }) }), _jsxs("div", { children: [_jsx(CardTitle, { className: "text-lg font-bold text-gray-900", children: "C\u00E2u h\u1ECFi: H\u00ECnh n\u00E0o kh\u00E1c lo\u1EA1i?" }), _jsx("p", { className: "text-sm text-gray-500", children: "H\u1ECDc sinh nghe c\u00E2u h\u1ECFi, nh\u00ECn 4 h\u00ECnh v\u00E0 n\u00F3i h\u00ECnh n\u00E0o kh\u00E1c lo\u1EA1i + l\u00FD do" })] })] }) }), _jsxs(CardContent, { className: "space-y-6", children: [_jsx("div", { className: "rounded-xl border border-gray-200 bg-gray-50 p-4", children: _jsxs("div", { className: "flex flex-col md:flex-row gap-6", children: [_jsx("div", { className: "flex-1", children: _jsx(PointsInput, { ...form.register('points', { valueAsNumber: true }), error: form.formState.errors?.points?.message }) }), _jsx("div", { className: "flex-1 flex flex-col justify-center pt-2" })] }) }), _jsxs("div", { className: "space-y-2", children: [_jsxs(Label, { className: "flex items-center gap-2 text-sm font-semibold text-gray-800", children: [_jsx(Volume2, { className: "h-4 w-4 text-indigo-500" }), "File Audio c\u00E2u h\u1ECFi ", _jsx("span", { className: "text-red-500", children: "*" })] }), _jsx(FormField, { control: control, name: "audioUrl", render: ({ field }) => (_jsxs(FormItem, { children: [_jsx(FormControl, { children: _jsx(FileUpload, { id: "audio-odd-one-out", label: "", accept: "audio/*", value: field.value, onChange: (url) => { field.onChange(url); clearErrors('audioUrl'); }, maxSize: 10, placeholder: "T\u1EA3i l\u00EAn file \u00E2m thanh (mp3, wav...)", autoUpload: true, prefix: "listen-and-speak" }) }), _jsx(FormMessage, {})] })) })] })] })] }), _jsxs(Card, { className: "overflow-hidden border-0 bg-white shadow-lg shadow-green-100/50", children: [_jsx("div", { className: "h-1 bg-gradient-to-r from-green-400 to-emerald-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-9 w-9 shrink-0 items-center justify-center rounded-lg bg-gradient-to-br from-green-500 to-emerald-600 shadow-sm", children: _jsx(MessageSquareText, { className: "h-4 w-4 text-white" }) }), _jsxs("div", { children: [_jsx(CardTitle, { className: "text-lg font-bold text-gray-900", children: "\u0110\u00E1p \u00E1n mong mu\u1ED1n" }), _jsxs("p", { className: "text-sm text-gray-500", children: [watchedExpectedAnswers.filter(a => a.trim()).length, " \u0111\u00E1p \u00E1n \u0111\u00E3 nh\u1EADp"] })] })] }), _jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: addExpectedAnswer, className: "gap-2 border-green-200 text-green-600 hover:bg-green-50", children: [_jsx(Plus, { className: "h-4 w-4" }), "Th\u00EAm \u0111\u00E1p \u00E1n"] })] }) }), _jsxs(CardContent, { className: "space-y-4", children: [_jsxs("div", { className: "flex items-start gap-2 rounded-lg border border-blue-100 bg-blue-50/60 px-3 py-2 text-xs text-blue-700", children: [_jsx(Info, { className: "mt-0.5 h-3.5 w-3.5 shrink-0" }), _jsx("span", { children: "H\u1ECDc sinh ph\u1EA3i ch\u1EC9 ra h\u00ECnh kh\u00E1c lo\u1EA1i V\u00C0 gi\u1EA3i th\u00EDch l\u00FD do t\u1EA1i sao." })] }), _jsx(FormField, { control: control, name: "expectedAnswers", render: () => (_jsxs(FormItem, { children: [_jsx("div", { className: "space-y-3", children: watchedExpectedAnswers.map((answer, index) => (_jsxs("div", { className: "flex items-start gap-3 group/answer", children: [_jsx("div", { className: "flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-lg bg-green-100 text-green-700 text-sm font-bold mt-1", children: index + 1 }), _jsxs("div", { className: "flex-1", children: [_jsx(Input, { placeholder: `VD: ${index === 0 ? 'The beach — because the others are classrooms.' : 'It\'s the odd one out because...'}`, value: answer || '', onChange: (e) => handleExpectedAnswerChange(index, e.target.value), className: "border-gray-200 focus:border-green-300 focus:ring-green-200" }), form.formState.errors?.expectedAnswers?.[index]?.message && (_jsx("p", { className: "mt-1 text-sm text-red-500", children: form.formState.errors.expectedAnswers[index]?.message }))] }), watchedExpectedAnswers.length > 1 && (_jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => removeExpectedAnswer(index), className: "mt-0.5 h-8 w-8 rounded-full text-gray-400 hover:text-red-600 hover:bg-red-50 opacity-0 group-hover/answer:opacity-100 transition-opacity", "aria-label": `Xóa đáp án ${index + 1}`, children: _jsx(Trash2, { className: "h-4 w-4" }) }))] }, index))) }), form.formState.errors?.expectedAnswers?.message && (_jsx(FormMessage, {}))] })) })] })] }), _jsxs(Card, { className: "overflow-hidden border-0 bg-white shadow-lg shadow-amber-100/50", children: [_jsx("div", { className: "h-1 bg-gradient-to-r from-amber-400 to-orange-400" }), _jsx(CardHeader, { className: "pb-4", children: _jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "flex h-9 w-9 items-center justify-center rounded-lg bg-gradient-to-br from-amber-500 to-orange-500 shadow-sm", children: _jsx(BookOpen, { className: "h-4 w-4 text-white" }) }), _jsxs("div", { children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(CardTitle, { className: "text-lg font-semibold text-gray-800", children: "Gi\u1EA3i th\u00EDch" }), _jsx("span", { className: "inline-flex items-center rounded-full border border-amber-200 bg-amber-50 px-2.5 py-0.5 text-xs font-medium text-amber-600", children: "Kh\u00F4ng b\u1EAFt bu\u1ED9c" })] }), _jsx("p", { className: "text-sm text-gray-500 mt-0.5", children: "Gi\u1EA3i th\u00EDch cho \u0111\u00E1p \u00E1n \u0111\u00FAng \u2014 hi\u1EC3n th\u1ECB sau khi h\u1ECDc sinh ho\u00E0n th\u00E0nh c\u00E2u h\u1ECFi" })] })] }) }), _jsx(CardContent, { children: _jsx(Textarea, { ...form.register('explanation'), placeholder: "VD: C\u00E2u tr\u1EA3 l\u1EDDi \u0111\u00FAng l\u00E0... v\u00EC...", className: "min-h-[80px] border-gray-200 bg-white transition-all focus:border-amber-300 focus:ring-amber-200 resize-none", rows: 3 }) })] })] })] }));
|
|
247
|
-
}
|
|
1
|
+
"use client";import{jsx as e,jsxs as r}from"react/jsx-runtime";import{useEffect as t,useRef as n,useState as i}from"react";import{Card as s,CardContent as a,CardHeader as o,CardTitle as d}from"../../../../components/ui/card";import{Label as l}from"../../../../components/ui/label";import{Button as c}from"../../../../components/ui/button";import{Input as h}from"../../../../components/ui/input";import{Textarea as m}from"../../../../components/ui/textarea";import{Form as g,FormControl as u,FormField as p,FormItem as x,FormMessage as f}from"../../../../components/ui/form";import{FileUpload as b}from"../../../../components/ui/file-upload";import{PointsInput as N}from"../../../../components/ui/points-input";import{useReactHookForm as w}from"../../../../shared/lib/hooks/useReactHookForm";import{useDebouncedCallback as v,usePresignedFileUrl as y}from"../../../../shared/lib/hooks";import{listenAndSpeakOddOneOutSchema as O}from"../../../../shared/lib/validation/listen-and-speak-odd-one-out.validation";import{Plus as k,Trash2 as A,Volume2 as S,ImageIcon as U,MessageSquareText as C,HelpCircle as I,BookOpen as V,Eye as j,Pencil as J,Info as D,ListChecks as z,CircleOff as B,Check as H}from"lucide-react";import{CreatorGuide as T}from"../../components/CreatorGuide";import{ListenAndSpeakOddOneOutClient as $}from"./ListenAndSpeakOddOneOutClient";function F({imageUrl:t,index:n,isOddOneOut:i}){const{previewUrl:s}=y(t);return s?r("div",{className:"relative rounded-xl border border-gray-200 overflow-hidden bg-white shadow-sm group/img",children:[e("img",{src:s,alt:`Image ${n+1}`,className:"h-36 w-full object-contain bg-gray-50 p-2"}),i&&e("div",{className:"absolute inset-0 bg-red-500/10 ring-2 ring-inset ring-red-400 rounded-xl"})]}):null}export function ListenAndSpeakOddOneOutCreator({initialData:y,onChange:G,onUnsavedChangesChange:q,validationRef:K}){const L=y,P=L?.images&&L.images.length>0?L.images:Array(4).fill(""),R=w({zodSchema:O,defaultValues:{audioUrl:L?.audioUrl||"",images:P.length>=4?P.slice(0,4):[...P,...Array(4-P.length).fill("")],oddOneOutIndex:L?.oddOneOutIndex??-1,expectedAnswers:L?.expectedAnswers?.length>0?L.expectedAnswers:[""],points:L?.points??1,explanation:L?.explanation||"",isBackup:L?.isBackup||!1}}),{control:E,watch:Q,setValue:W,trigger:X,clearErrors:M}=R,Y=Q("audioUrl"),Z=Q("images"),_=Q("oddOneOutIndex"),ee=Q("expectedAnswers"),re=Q("points"),te=Q("explanation"),ne=Q("isBackup"),ie=n(y),se=n(!1),ae=n(G),oe=n("");t(()=>{ae.current=G},[G]);const de=v(e=>{ae.current?.(e)},300);t(()=>{K&&(K.current={triggerValidation:async()=>await X(),getFormData:()=>R.getValues()})},[K,R,X]),t(()=>{const e={audioUrl:L?.audioUrl||"",images:L?.images?.length>=4?L.images.slice(0,4):[...L?.images||[],...Array(4-(L?.images?.length||0)).fill("")],oddOneOutIndex:L?.oddOneOutIndex??-1,expectedAnswers:L?.expectedAnswers?.length>0?L.expectedAnswers:[""],points:L?.points??1,explanation:L?.explanation||"",isBackup:L?.isBackup||!1};if(!(JSON.stringify(ie.current)!==JSON.stringify(y)))return;ie.current=y;const r={audioUrl:Y,images:Z,oddOneOutIndex:_,expectedAnswers:ee,points:re,explanation:te,isBackup:ne},t=r.points===e.points&&r.explanation===e.explanation&&r.audioUrl===e.audioUrl&&r.oddOneOutIndex===e.oddOneOutIndex&&JSON.stringify(r.images)===JSON.stringify(e.images)&&JSON.stringify(r.expectedAnswers)===JSON.stringify(e.expectedAnswers);if(t&&!0===y?.isCompleted)return;!t&&(e.audioUrl!==r.audioUrl||e.oddOneOutIndex!==r.oddOneOutIndex||JSON.stringify(e.images)!==JSON.stringify(r.images)||JSON.stringify(e.expectedAnswers)!==JSON.stringify(r.expectedAnswers))&&(se.current=!0,R.reset(e),setTimeout(()=>{se.current=!1},150))},[y,R,Y,Z,_,ee,te,re,ne]);const le=JSON.stringify(Z),ce=JSON.stringify(ee);t(()=>{if(se.current){const e=JSON.stringify({audioUrl:Y,images:Z,oddOneOutIndex:_,expectedAnswers:ee,points:re,explanation:te,isBackup:ne});return void(oe.current=e)}const e={audioUrl:Y,images:Z,oddOneOutIndex:_,expectedAnswers:ee,points:re,explanation:te,isBackup:ne},r=JSON.stringify(e);oe.current!==r&&(oe.current=r,de(e))},[Y,le,_,ce,re,te,ne,de]),t(()=>{q&&q(R.formState.isDirty)},[R.formState.isDirty,q]);const he=(e,r)=>{const t=[...Z];t[e]=r,R.setValue("images",t,{shouldValidate:!0,shouldDirty:!0})},me=Z.filter(e=>""!==e.trim()).length,[ge,ue]=i(!1);return r("div",ge?{className:"space-y-6",children:[e(s,{children:e(o,{children:r("div",{className:"flex items-center justify-between",children:[e(d,{className:"text-lg font-bold",children:"👁️ Chế độ xem trước (Preview)"}),r(c,{variant:"outline",size:"sm",onClick:()=>ue(!1),children:[e(J,{className:"mr-2 h-4 w-4"}),"Quay lại chỉnh sửa"]})]})})}),e($,{questionData:{audioUrl:Y,images:Z,oddOneOutIndex:_,expectedAnswers:ee,points:re,explanation:te}})]}:{className:"space-y-6",children:[e(T,{colorScheme:"blue",steps:[{text:"Upload đúng 4 hình ảnh — 3 hình cùng loại và 1 hình khác loại"},{text:"Upload audio câu hỏi cho mỗi câu — giám khảo hỏi hình nào khác loại và tại sao"},{text:"Nhập đáp án mong muốn: tên hình khác loại + lý do",highlight:!0},{text:"Thêm giải thích chi tiết vì sao hình đó khác loại"}],tips:["Sự khác biệt nên rõ ràng về chủ đề hoặc danh mục, không phải về hình thức"]}),r(s,{className:"overflow-hidden border-0 bg-white shadow-lg shadow-blue-100/50",children:[e("div",{className:"h-1 bg-gradient-to-r from-blue-500 via-indigo-500 to-purple-500"}),e(o,{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-9 w-9 shrink-0 items-center justify-center rounded-lg bg-gradient-to-br from-blue-500 to-indigo-600 shadow-sm",children:e(z,{className:"h-4 w-4 text-white"})}),r("div",{children:[e(d,{className:"text-lg font-bold text-gray-900",children:"Odd One Out — Hình nào khác loại?"}),e("p",{className:"text-sm text-gray-500",children:"Học sinh nhìn 4 hình và chỉ ra hình khác loại, giải thích lý do"})]})]}),r(c,{type:"button",variant:"outline",size:"sm",onClick:()=>ue(!0),disabled:!Y,className:"gap-2 border-blue-200 text-blue-600 hover:bg-blue-50 disabled:opacity-50",children:[e(j,{className:"h-4 w-4"}),"Preview"]})]})})]}),r(g,{...R,children:[r(s,{className:"overflow-hidden border-0 bg-white shadow-lg shadow-amber-100/50",children:[e("div",{className:"h-1.5 bg-gradient-to-r from-amber-500 via-orange-500 to-red-500"}),e(o,{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 shrink-0 items-center justify-center rounded-xl bg-gradient-to-br from-amber-500 to-orange-600 shadow-md",children:e(U,{className:"h-5 w-5 text-white"})}),r("div",{children:[e(d,{className:"text-lg font-bold text-gray-900",children:"4 Hình ảnh"}),e("p",{className:"text-sm text-gray-500",children:"Upload đúng 4 hình — 1 hình sẽ là hình khác loại"})]})]}),r("span",{className:"inline-flex items-center rounded-full px-3 py-1 text-xs font-bold "+(4===me?"bg-green-100 text-green-700 border border-green-200":"bg-orange-100 text-orange-600 border border-orange-200"),children:[me,"/",4," ảnh"]})]})}),e(a,{children:e(p,{control:E,name:"images",render:()=>r(x,{children:[e("div",{className:"grid grid-cols-2 gap-5",children:Array.from({length:4}).map((t,n)=>{const i=Z[n]||"",d=_===n;return r(s,{className:"relative border-2 transition-all duration-300 overflow-hidden "+(d?"border-red-400 bg-gradient-to-b from-red-50/60 to-white shadow-lg shadow-red-100/60 ring-1 ring-red-200":"border-gray-200 hover:border-gray-300 hover:shadow-md"),children:[d&&e("div",{className:"absolute top-0 left-0 right-0 h-1 bg-gradient-to-r from-red-400 via-rose-500 to-red-400"}),e(o,{className:"pb-2 pt-3 px-4",children:r("div",{className:"flex items-center justify-between",children:[r("span",{className:"text-sm font-semibold text-gray-600 flex items-center gap-2",children:[e("span",{className:"flex h-6 w-6 items-center justify-center rounded-md text-xs font-bold transition-colors duration-200 "+(d?"bg-red-100 text-red-700":"bg-amber-100 text-amber-700"),children:n+1}),"Ảnh ",n+1,d&&r("span",{className:"inline-flex items-center gap-1 rounded-full bg-red-100 border border-red-200 px-2 py-0.5 text-xs font-bold text-red-700 animate-in fade-in slide-in-from-left-1 duration-200",children:[e(B,{className:"h-3 w-3"}),"Khác loại"]})]}),e("div",{className:"flex items-center gap-1",children:i&&e(c,{type:"button",variant:"ghost",size:"sm",onClick:()=>(e=>he(e,""))(n),className:"h-7 w-7 rounded-full text-red-400 hover:text-red-600 hover:bg-red-50",children:e(A,{className:"h-3.5 w-3.5"})})})]})}),r(a,{className:"space-y-3 px-4 pb-4",children:[i?e(F,{imageUrl:i,index:n,isOddOneOut:d}):e(b,{id:`odd-one-out-img-${n}`,label:"",accept:"image/*",value:"",onChange:e=>he(n,e),placeholder:`Upload ảnh ${n+1}`,maxSize:5,autoUpload:!0,prefix:"questions/odd-one-out"}),R.formState.errors?.images?.[n]&&r("p",{className:"text-sm font-medium text-destructive",children:["Vui lòng tải lên ảnh ",n+1]}),r("button",{type:"button",onClick:()=>{R.setValue("oddOneOutIndex",n,{shouldValidate:!0,shouldDirty:!0}),M("oddOneOutIndex")},className:"group/toggle w-full flex items-center gap-3 rounded-lg px-3 py-2.5 text-left transition-all duration-200 cursor-pointer "+(d?"bg-red-50 border border-red-200":"bg-gray-50 border border-gray-200 hover:bg-red-50/50 hover:border-red-200/60"),children:[e("div",{className:"flex h-5 w-5 shrink-0 items-center justify-center rounded-full border-2 transition-all duration-200 "+(d?"border-red-500 bg-red-500 shadow-sm shadow-red-200":"border-gray-300 bg-white group-hover/toggle:border-red-300"),children:d&&e(H,{className:"h-3 w-3 text-white",strokeWidth:3})}),e("div",{className:"flex-1 min-w-0",children:e("span",{className:"text-xs font-semibold transition-colors duration-200 "+(d?"text-red-700":"text-gray-500 group-hover/toggle:text-red-600"),children:d?"Đã đánh dấu là hình khác loại":"Đánh dấu là hình khác loại"})}),e(B,{className:"h-3.5 w-3.5 shrink-0 transition-colors duration-200 "+(d?"text-red-400":"text-gray-300 group-hover/toggle:text-red-300")})]})]})]},n)})}),R.formState.errors?.images?.message&&e(f,{}),R.formState.errors?.oddOneOutIndex?.message&&r("p",{className:"mt-3 text-sm font-medium text-destructive flex items-center gap-2",children:[e(B,{className:"h-4 w-4"}),R.formState.errors.oddOneOutIndex.message]})]})})})]}),r(s,{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-500 via-purple-500 to-pink-500"}),e(o,{className:"pb-4",children:r("div",{className:"flex items-center gap-3",children:[e("div",{className:"flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-gradient-to-br from-indigo-500 to-purple-600 shadow-sm",children:e(I,{className:"h-4 w-4 text-white"})}),r("div",{children:[e(d,{className:"text-lg font-bold text-gray-900",children:"Câu hỏi: Hình nào khác loại?"}),e("p",{className:"text-sm text-gray-500",children:"Học sinh nghe câu hỏi, nhìn 4 hình và nói hình nào khác loại + lý do"})]})]})}),r(a,{className:"space-y-6",children:[e("div",{className:"rounded-xl border border-gray-200 bg-gray-50 p-4",children:r("div",{className:"flex flex-col md:flex-row gap-6",children:[e("div",{className:"flex-1",children:e(N,{...R.register("points",{valueAsNumber:!0}),error:R.formState.errors?.points?.message})}),e("div",{className:"flex-1 flex flex-col justify-center pt-2"})]})}),r("div",{className:"space-y-2",children:[r(l,{className:"flex items-center gap-2 text-sm font-semibold text-gray-800",children:[e(S,{className:"h-4 w-4 text-indigo-500"}),"File Audio câu hỏi ",e("span",{className:"text-red-500",children:"*"})]}),e(p,{control:E,name:"audioUrl",render:({field:t})=>r(x,{children:[e(u,{children:e(b,{id:"audio-odd-one-out",label:"",accept:"audio/*",value:t.value,onChange:e=>{t.onChange(e),M("audioUrl")},maxSize:10,placeholder:"Tải lên file âm thanh (mp3, wav...)",autoUpload:!0,prefix:"listen-and-speak"})}),e(f,{})]})})]})]})]}),r(s,{className:"overflow-hidden border-0 bg-white shadow-lg shadow-green-100/50",children:[e("div",{className:"h-1 bg-gradient-to-r from-green-400 to-emerald-500"}),e(o,{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-9 w-9 shrink-0 items-center justify-center rounded-lg bg-gradient-to-br from-green-500 to-emerald-600 shadow-sm",children:e(C,{className:"h-4 w-4 text-white"})}),r("div",{children:[e(d,{className:"text-lg font-bold text-gray-900",children:"Đáp án mong muốn"}),r("p",{className:"text-sm text-gray-500",children:[ee.filter(e=>e.trim()).length," đáp án đã nhập"]})]})]}),r(c,{type:"button",variant:"outline",size:"sm",onClick:()=>{const e=R.getValues("expectedAnswers");R.setValue("expectedAnswers",[...e,""],{shouldValidate:!0,shouldDirty:!0})},className:"gap-2 border-green-200 text-green-600 hover:bg-green-50",children:[e(k,{className:"h-4 w-4"}),"Thêm đáp án"]})]})}),r(a,{className:"space-y-4",children:[r("div",{className:"flex items-start gap-2 rounded-lg border border-blue-100 bg-blue-50/60 px-3 py-2 text-xs text-blue-700",children:[e(D,{className:"mt-0.5 h-3.5 w-3.5 shrink-0"}),e("span",{children:"Học sinh phải chỉ ra hình khác loại VÀ giải thích lý do tại sao."})]}),e(p,{control:E,name:"expectedAnswers",render:()=>r(x,{children:[e("div",{className:"space-y-3",children:ee.map((t,n)=>r("div",{className:"flex items-start gap-3 group/answer",children:[e("div",{className:"flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-lg bg-green-100 text-green-700 text-sm font-bold mt-1",children:n+1}),r("div",{className:"flex-1",children:[e(h,{placeholder:"VD: "+(0===n?"The beach — because the others are classrooms.":"It's the odd one out because..."),value:t||"",onChange:e=>((e,r)=>{const t=[...ee];t[e]=r,R.setValue("expectedAnswers",t,{shouldValidate:!0,shouldDirty:!0})})(n,e.target.value),className:"border-gray-200 focus:border-green-300 focus:ring-green-200"}),R.formState.errors?.expectedAnswers?.[n]?.message&&e("p",{className:"mt-1 text-sm text-red-500",children:R.formState.errors.expectedAnswers[n]?.message})]}),ee.length>1&&e(c,{type:"button",variant:"ghost",size:"sm",onClick:()=>(e=>{if(ee.length>1){const r=ee.filter((r,t)=>t!==e);R.setValue("expectedAnswers",r,{shouldValidate:!0,shouldDirty:!0})}})(n),className:"mt-0.5 h-8 w-8 rounded-full text-gray-400 hover:text-red-600 hover:bg-red-50 opacity-0 group-hover/answer:opacity-100 transition-opacity","aria-label":`Xóa đáp án ${n+1}`,children:e(A,{className:"h-4 w-4"})})]},n))}),R.formState.errors?.expectedAnswers?.message&&e(f,{})]})})]})]}),r(s,{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(o,{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(V,{className:"h-4 w-4 text-white"})}),r("div",{children:[r("div",{className:"flex items-center gap-2",children:[e(d,{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(a,{children:e(m,{...R.register("explanation"),placeholder:"VD: Câu trả lời đúng là... vì...",className:"min-h-[80px] border-gray-200 bg-white transition-all focus:border-amber-300 focus:ring-amber-200 resize-none",rows:3})})]})]})]})}
|
|
@@ -1,59 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
export const listenAndSpeakOddOneOutRegistration = {
|
|
3
|
-
component: ListenAndSpeakOddOneOutCreator,
|
|
4
|
-
wrapOnSave: (data, ctx) => ({
|
|
5
|
-
type: ctx.questionType,
|
|
6
|
-
content: {
|
|
7
|
-
audioUrl: data.audioUrl,
|
|
8
|
-
images: data.images || [],
|
|
9
|
-
isBackup: data.isBackup || false,
|
|
10
|
-
},
|
|
11
|
-
points: data?.points ?? ctx.state.points ?? 1,
|
|
12
|
-
level: ctx.state.level,
|
|
13
|
-
difficulty: ctx.state.difficulty,
|
|
14
|
-
skill: ctx.state.skill,
|
|
15
|
-
answer: {
|
|
16
|
-
expectedAnswers: data.expectedAnswers || [],
|
|
17
|
-
oddOneOutIndex: data.oddOneOutIndex ?? -1,
|
|
18
|
-
},
|
|
19
|
-
explanation: data?.explanation || '',
|
|
20
|
-
}),
|
|
21
|
-
wrapOnChange: (data, ctx) => ({
|
|
22
|
-
type: ctx.questionType,
|
|
23
|
-
content: {
|
|
24
|
-
audioUrl: data.audioUrl,
|
|
25
|
-
images: data.images || [],
|
|
26
|
-
isBackup: data.isBackup || false,
|
|
27
|
-
},
|
|
28
|
-
points: data?.points ?? ctx.state.points ?? 1,
|
|
29
|
-
level: ctx.state.level,
|
|
30
|
-
difficulty: ctx.state.difficulty,
|
|
31
|
-
skill: ctx.state.skill,
|
|
32
|
-
answer: {
|
|
33
|
-
expectedAnswers: data.expectedAnswers || [],
|
|
34
|
-
oddOneOutIndex: data.oddOneOutIndex ?? -1,
|
|
35
|
-
},
|
|
36
|
-
explanation: data?.explanation || '',
|
|
37
|
-
}),
|
|
38
|
-
transformInitialData: (data) => {
|
|
39
|
-
const content = data?.content;
|
|
40
|
-
const answer = data?.answer || data?.correctAnswer || data?.correct_answer;
|
|
41
|
-
const points = data?.total_points
|
|
42
|
-
? parseFloat(data.total_points)
|
|
43
|
-
: (data?.points || content?.points || answer?.points || 1);
|
|
44
|
-
const explanation = data?.explanation || content?.explanation || answer?.explanation || '';
|
|
45
|
-
return {
|
|
46
|
-
audioUrl: content?.audioUrl || answer?.audioUrl || '',
|
|
47
|
-
images: content?.images || answer?.images || ['', '', '', ''],
|
|
48
|
-
oddOneOutIndex: answer?.oddOneOutIndex ?? content?.oddOneOutIndex ?? -1,
|
|
49
|
-
expectedAnswers: answer?.expectedAnswers || [],
|
|
50
|
-
explanation,
|
|
51
|
-
points,
|
|
52
|
-
isBackup: content?.isBackup || false,
|
|
53
|
-
};
|
|
54
|
-
},
|
|
55
|
-
getExtraProps: (ctx) => ({
|
|
56
|
-
onUnsavedChangesChange: ctx.onUnsavedChangesChange,
|
|
57
|
-
validationRef: ctx.validationRef,
|
|
58
|
-
}),
|
|
59
|
-
};
|
|
1
|
+
import{ListenAndSpeakOddOneOutCreator as e}from"./ListenAndSpeakOddOneOutCreator";export const listenAndSpeakOddOneOutRegistration={component:e,wrapOnSave:(e,t)=>({type:t.questionType,content:{audioUrl:e.audioUrl,images:e.images||[],isBackup:e.isBackup||!1},points:e?.points??t.state.points??1,level:t.state.level,difficulty:t.state.difficulty,skill:t.state.skill,answer:{expectedAnswers:e.expectedAnswers||[],oddOneOutIndex:e.oddOneOutIndex??-1},explanation:e?.explanation||""}),wrapOnChange:(e,t)=>({type:t.questionType,content:{audioUrl:e.audioUrl,images:e.images||[],isBackup:e.isBackup||!1},points:e?.points??t.state.points??1,level:t.state.level,difficulty:t.state.difficulty,skill:t.state.skill,answer:{expectedAnswers:e.expectedAnswers||[],oddOneOutIndex:e.oddOneOutIndex??-1},explanation:e?.explanation||""}),transformInitialData:e=>{const t=e?.content,n=e?.answer||e?.correctAnswer||e?.correct_answer,a=e?.total_points?parseFloat(e.total_points):e?.points||t?.points||n?.points||1,s=e?.explanation||t?.explanation||n?.explanation||"";return{audioUrl:t?.audioUrl||n?.audioUrl||"",images:t?.images||n?.images||["","","",""],oddOneOutIndex:n?.oddOneOutIndex??t?.oddOneOutIndex??-1,expectedAnswers:n?.expectedAnswers||[],explanation:s,points:a,isBackup:t?.isBackup||!1}},getExtraProps:e=>({onUnsavedChangesChange:e.onUnsavedChangesChange,validationRef:e.validationRef})};
|
|
@@ -1,18 +1 @@
|
|
|
1
|
-
export const transformListenAndSpeakOddOneOut
|
|
2
|
-
const contentData = question.content;
|
|
3
|
-
const answerData = question.answer;
|
|
4
|
-
const expectedAnswers = answerData?.expectedAnswers || [];
|
|
5
|
-
const oddOneOutIndex = answerData?.oddOneOutIndex ?? contentData?.oddOneOutIndex ?? -1;
|
|
6
|
-
const apiContent = {
|
|
7
|
-
audioUrl: contentData?.audioUrl || answerData?.audioUrl || '',
|
|
8
|
-
images: contentData?.images || answerData?.images || [],
|
|
9
|
-
isBackup: contentData?.isBackup || answerData?.isBackup || false,
|
|
10
|
-
};
|
|
11
|
-
return {
|
|
12
|
-
apiContent,
|
|
13
|
-
apiCorrectAnswer: {
|
|
14
|
-
expectedAnswers: expectedAnswers,
|
|
15
|
-
oddOneOutIndex: oddOneOutIndex,
|
|
16
|
-
},
|
|
17
|
-
};
|
|
18
|
-
};
|
|
1
|
+
export const transformListenAndSpeakOddOneOut=e=>{const n=e.content,d=e.answer,t=d?.expectedAnswers||[],s=d?.oddOneOutIndex??n?.oddOneOutIndex??-1;return{apiContent:{audioUrl:n?.audioUrl||d?.audioUrl||"",images:n?.images||d?.images||[],isBackup:n?.isBackup||d?.isBackup||!1},apiCorrectAnswer:{expectedAnswers:t,oddOneOutIndex:s}}};
|