@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,79 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
export const ListenAndSpeakCompareImagesRegistration = {
|
|
3
|
-
component: ListenAndSpeakCompareImagesCreator,
|
|
4
|
-
wrapOnSave: (data, ctx) => {
|
|
5
|
-
return {
|
|
6
|
-
type: ctx.questionType,
|
|
7
|
-
points: data?.points ?? ctx.state.points ?? 1,
|
|
8
|
-
level: ctx.state.level,
|
|
9
|
-
difficulty: ctx.state.difficulty,
|
|
10
|
-
skill: ctx.state.skill,
|
|
11
|
-
explanation: data.explanation || '',
|
|
12
|
-
content: {
|
|
13
|
-
audioUrl: data.audioUrl,
|
|
14
|
-
isBackup: data.isBackup || false,
|
|
15
|
-
},
|
|
16
|
-
answer: {
|
|
17
|
-
expectedAnswers: data.expectedAnswers,
|
|
18
|
-
},
|
|
19
|
-
...(ctx.groupNumber && { questionGroup: { payload: { candidateImageUrl: data.candidateImageUrl || '', examinerImageUrl: data.examinerImageUrl || '' }, groupNumber: ctx.groupNumber } })
|
|
20
|
-
};
|
|
21
|
-
},
|
|
22
|
-
getExtraProps: (ctx) => ({
|
|
23
|
-
onUnsavedChangesChange: ctx.onUnsavedChangesChange,
|
|
24
|
-
validationRef: ctx.validationRef,
|
|
25
|
-
groups: ctx.groups,
|
|
26
|
-
questionIndexInPart: ctx.questionIndexInPart,
|
|
27
|
-
partId: ctx.partId,
|
|
28
|
-
}),
|
|
29
|
-
wrapOnChange: (data, ctx) => {
|
|
30
|
-
return {
|
|
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
|
-
audioUrl: data.audioUrl,
|
|
39
|
-
isBackup: data.isBackup || false,
|
|
40
|
-
},
|
|
41
|
-
correct_answer: {
|
|
42
|
-
expectedAnswers: data.expectedAnswers,
|
|
43
|
-
},
|
|
44
|
-
...(ctx.groupNumber && { questionGroup: { payload: { candidateImageUrl: data.candidateImageUrl || '', examinerImageUrl: data.examinerImageUrl || '' }, groupNumber: ctx.groupNumber } })
|
|
45
|
-
};
|
|
46
|
-
},
|
|
47
|
-
transformInitialData: (data) => {
|
|
48
|
-
let creatorData = data?.answer;
|
|
49
|
-
const content = data?.content;
|
|
50
|
-
const answer = data?.answer || data?.correctAnswer || data?.correct_answer;
|
|
51
|
-
const points = data?.total_points
|
|
52
|
-
? parseFloat(data.total_points)
|
|
53
|
-
: (data?.points || content?.points || answer?.points || 1);
|
|
54
|
-
const explanation = data?.explanation || content?.explanation || answer?.explanation || '';
|
|
55
|
-
if (content && typeof content === 'object' && content.audioUrl !== undefined) {
|
|
56
|
-
creatorData = {
|
|
57
|
-
audioUrl: content.audioUrl || '',
|
|
58
|
-
expectedAnswers: answer?.expectedAnswers || [],
|
|
59
|
-
candidateImageUrl: content.candidateImageUrl || data?.questionGroup?.payload?.candidateImageUrl || '',
|
|
60
|
-
examinerImageUrl: content.examinerImageUrl || data?.questionGroup?.payload?.examinerImageUrl || '',
|
|
61
|
-
explanation,
|
|
62
|
-
points,
|
|
63
|
-
isBackup: content.isBackup || false,
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
else if (answer && typeof answer === 'object' && answer.expectedAnswers !== undefined) {
|
|
67
|
-
creatorData = {
|
|
68
|
-
...answer,
|
|
69
|
-
audioUrl: answer.audioUrl || content?.audioUrl || '',
|
|
70
|
-
candidateImageUrl: answer.candidateImageUrl || data?.questionGroup?.payload?.candidateImageUrl || '',
|
|
71
|
-
examinerImageUrl: answer.examinerImageUrl || data?.questionGroup?.payload?.examinerImageUrl || '',
|
|
72
|
-
explanation,
|
|
73
|
-
points,
|
|
74
|
-
isBackup: content?.isBackup || false,
|
|
75
|
-
};
|
|
76
|
-
}
|
|
77
|
-
return creatorData || data;
|
|
78
|
-
},
|
|
79
|
-
};
|
|
1
|
+
import{ListenAndSpeakCompareImagesCreator as e}from"./ListenAndSpeakCompareImagesCreator";export const ListenAndSpeakCompareImagesRegistration={component:e,wrapOnSave:(e,a)=>({type:a.questionType,points:e?.points??a.state.points??1,level:a.state.level,difficulty:a.state.difficulty,skill:a.state.skill,explanation:e.explanation||"",content:{audioUrl:e.audioUrl,isBackup:e.isBackup||!1},answer:{expectedAnswers:e.expectedAnswers},...a.groupNumber&&{questionGroup:{payload:{candidateImageUrl:e.candidateImageUrl||"",examinerImageUrl:e.examinerImageUrl||""},groupNumber:a.groupNumber}}}),getExtraProps:e=>({onUnsavedChangesChange:e.onUnsavedChangesChange,validationRef:e.validationRef,groups:e.groups,questionIndexInPart:e.questionIndexInPart,partId:e.partId}),wrapOnChange:(e,a)=>({type:a.questionType,points:e?.points??a.state.points??1,level:a.state.level,difficulty:a.state.difficulty,skill:a.state.skill,explanation:e.explanation||"",content:{audioUrl:e.audioUrl,isBackup:e.isBackup||!1},correct_answer:{expectedAnswers:e.expectedAnswers},...a.groupNumber&&{questionGroup:{payload:{candidateImageUrl:e.candidateImageUrl||"",examinerImageUrl:e.examinerImageUrl||""},groupNumber:a.groupNumber}}}),transformInitialData:e=>{let a=e?.answer;const t=e?.content,n=e?.answer||e?.correctAnswer||e?.correct_answer,r=e?.total_points?parseFloat(e.total_points):e?.points||t?.points||n?.points||1,i=e?.explanation||t?.explanation||n?.explanation||"";return t&&"object"==typeof t&&void 0!==t.audioUrl?a={audioUrl:t.audioUrl||"",expectedAnswers:n?.expectedAnswers||[],candidateImageUrl:t.candidateImageUrl||e?.questionGroup?.payload?.candidateImageUrl||"",examinerImageUrl:t.examinerImageUrl||e?.questionGroup?.payload?.examinerImageUrl||"",explanation:i,points:r,isBackup:t.isBackup||!1}:n&&"object"==typeof n&&void 0!==n.expectedAnswers&&(a={...n,audioUrl:n.audioUrl||t?.audioUrl||"",candidateImageUrl:n.candidateImageUrl||e?.questionGroup?.payload?.candidateImageUrl||"",examinerImageUrl:n.examinerImageUrl||e?.questionGroup?.payload?.examinerImageUrl||"",explanation:i,points:r,isBackup:t?.isBackup||!1}),a||e}};
|
|
@@ -1,27 +1 @@
|
|
|
1
|
-
export const transformListenAndSpeakCompareImages =
|
|
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
|
-
isBackup: contentData?.isBackup || answerData?.isBackup || false,
|
|
9
|
-
};
|
|
10
|
-
const result = {
|
|
11
|
-
apiContent,
|
|
12
|
-
apiCorrectAnswer: {
|
|
13
|
-
expectedAnswers: expectedAnswers,
|
|
14
|
-
},
|
|
15
|
-
};
|
|
16
|
-
// Handle group data for candidate/examiner images
|
|
17
|
-
const candidateImageUrl = answerData?.candidateImageUrl || questionGroup?.payload?.candidateImageUrl || '';
|
|
18
|
-
const examinerImageUrl = answerData?.examinerImageUrl || questionGroup?.payload?.examinerImageUrl || '';
|
|
19
|
-
if (candidateImageUrl || examinerImageUrl) {
|
|
20
|
-
result.compareImagesGroupData = {
|
|
21
|
-
candidateImageUrl,
|
|
22
|
-
examinerImageUrl,
|
|
23
|
-
};
|
|
24
|
-
result.groupNumber = answerData?.groupNumber || questionGroup?.groupNumber || 1;
|
|
25
|
-
}
|
|
26
|
-
return result;
|
|
27
|
-
};
|
|
1
|
+
export const transformListenAndSpeakCompareImages=e=>{const a=e.content,r=e.answer,n=e.questionGroup,o=r?.expectedAnswers||[],p={apiContent:{audioUrl:a?.audioUrl||r?.audioUrl||"",isBackup:a?.isBackup||r?.isBackup||!1},apiCorrectAnswer:{expectedAnswers:o}},t=r?.candidateImageUrl||n?.payload?.candidateImageUrl||"",m=r?.examinerImageUrl||n?.payload?.examinerImageUrl||"";return(t||m)&&(p.compareImagesGroupData={candidateImageUrl:t,examinerImageUrl:m},p.groupNumber=r?.groupNumber||n?.groupNumber||1),p};
|
|
@@ -1,34 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { Mic, Volume2, CheckCircle2, Images, Play, Pause, ImageIcon, Info } from 'lucide-react';
|
|
4
|
-
import { Button } from '../../../../components/ui/button';
|
|
5
|
-
import { usePresignedFileUrl, useAudioPreview } from '../../../../shared/lib/hooks';
|
|
6
|
-
import { cn } from '../../../../shared/lib/utils';
|
|
7
|
-
// ─── Sub-component: Custom Audio Player ──────────────────────────────────────
|
|
8
|
-
function QuestionAudioPlayer({ audioUrl }) {
|
|
9
|
-
const { previewUrl } = usePresignedFileUrl(audioUrl);
|
|
10
|
-
const { isPlaying, currentTime, duration, progressPercentage, handlePlayPause, handleProgressClick, formatTime, } = useAudioPreview(previewUrl);
|
|
11
|
-
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" })] }));
|
|
12
|
-
}
|
|
13
|
-
// ─── Sub-component: Presigned Image ──────────────────────────────────────────
|
|
14
|
-
function ImageWithPresigned({ url, alt }) {
|
|
15
|
-
const { previewUrl } = usePresignedFileUrl(url);
|
|
16
|
-
return (_jsx("img", { src: previewUrl || url, alt: alt, className: "max-w-full max-h-full object-contain p-2" }));
|
|
17
|
-
}
|
|
18
|
-
// ─── Main Client Component ───────────────────────────────────────────────────
|
|
19
|
-
export function ListenAndSpeakImageGroupClient({ questionData, isReviewMode = false, }) {
|
|
20
|
-
const { audioUrl, activeImageId, groupImages = [], expectedAnswers = [], explanation } = questionData;
|
|
21
|
-
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(Images, { className: "h-4 w-4" }) }), _jsx("span", { className: "text-sm font-medium text-gray-700", children: "Nghe v\u00E0 n\u00F3i (Nh\u00F3m h\u00ECnh \u1EA3nh)" })] }) }), _jsxs("div", { className: "p-5 space-y-5", children: [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" })] })), groupImages.length > 0 ? (_jsx("div", { className: "rounded-xl border border-gray-100 bg-gradient-to-b from-gray-50/80 to-white p-5", children: _jsx("div", { className: "flex flex-row justify-center gap-4 sm:gap-5 flex-wrap", children: groupImages.map((img, index) => {
|
|
22
|
-
const isActive = img.id === activeImageId;
|
|
23
|
-
const letter = String.fromCharCode(65 + index);
|
|
24
|
-
return (_jsxs("div", { className: cn('flex flex-col items-center gap-2 transition-transform duration-300', isActive && 'scale-[1.03]'), children: [_jsxs("div", { className: cn('relative w-28 h-28 sm:w-36 sm:h-36 rounded-xl overflow-hidden flex items-center justify-center bg-white transition-all duration-300', isActive
|
|
25
|
-
? 'border-[3px] border-blue-400 shadow-lg shadow-blue-100/70 ring-2 ring-blue-200/40'
|
|
26
|
-
: 'border-2 border-gray-200 shadow-sm hover:shadow-md hover:border-gray-300'), children: [img.url ? (_jsx(ImageWithPresigned, { url: img.url, alt: img.label || `Image ${index + 1}` })) : (_jsx("div", { className: "w-full h-full bg-gray-50 flex items-center justify-center text-gray-300", children: _jsx(ImageIcon, { className: "h-10 w-10" }) })), _jsx("div", { className: cn('absolute top-1.5 left-1.5 flex h-6 w-6 items-center justify-center rounded-lg text-xs font-bold shadow-sm', isActive
|
|
27
|
-
? 'bg-blue-500 text-white'
|
|
28
|
-
: 'bg-white/90 text-gray-500 border border-gray-200'), children: letter }), isActive && (_jsx("div", { className: "absolute top-1.5 right-1.5 w-5 h-5 rounded-full bg-blue-500 flex items-center justify-center shadow-sm", children: _jsx(CheckCircle2, { className: "h-3.5 w-3.5 text-white" }) }))] }), _jsxs("div", { className: "flex flex-col items-center gap-0.5", children: [_jsx("span", { className: cn('text-sm font-semibold px-2.5 py-0.5 rounded-md transition-colors', isActive
|
|
29
|
-
? 'text-blue-700 bg-blue-50'
|
|
30
|
-
: 'text-gray-600'), children: img.label || `Hình ${index + 1}` }), isActive && (_jsx("span", { className: "text-[10px] font-medium text-blue-500 animate-pulse", children: "\u0110ang h\u1ECFi" }))] })] }, img.id));
|
|
31
|
-
}) }) })) : (
|
|
32
|
-
/* Empty state for no images */
|
|
33
|
-
_jsxs("div", { className: "flex flex-col items-center justify-center py-10 px-4 rounded-xl border-2 border-dashed border-gray-200 bg-gray-50/50", children: [_jsx(ImageIcon, { className: "h-12 w-12 text-gray-300 mb-3" }), _jsx("p", { className: "text-sm font-medium text-gray-500", children: "Ch\u01B0a c\u00F3 h\u00ECnh \u1EA3nh n\u00E0o" }), _jsx("p", { className: "text-xs text-gray-400 mt-1", children: "H\u00ECnh \u1EA3nh s\u1EBD hi\u1EC3n th\u1ECB t\u1EA1i \u0111\u00E2y" })] })), _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 })] }))] })] })] }));
|
|
34
|
-
}
|
|
1
|
+
"use client";import{jsx as e,jsxs as r}from"react/jsx-runtime";import{Mic as t,Volume2 as i,CheckCircle2 as a,Images as s,Play as l,Pause as n,ImageIcon as d,Info as o}from"lucide-react";import{Button as c}from"../../../../components/ui/button";import{usePresignedFileUrl as m,useAudioPreview as h}from"../../../../shared/lib/hooks";import{cn as g}from"../../../../shared/lib/utils";function x({audioUrl:t}){const{previewUrl:a}=m(t),{isPlaying:s,currentTime:d,duration:o,progressPercentage:g,handlePlayPause:x,handleProgressClick:u,formatTime:b}=h(a);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(c,{variant:"outline",size:"icon",onClick:x,disabled:!a,className:"h-8 w-8 border-indigo-300 text-indigo-600 hover:bg-indigo-100",children:e(s?n:l,{className:"h-3.5 w-3.5"})}),r("div",{className:"text-muted-foreground min-w-[70px] text-xs",children:[b(d)," / ",b(o)]}),e("div",{className:"bg-indigo-200 relative h-1.5 flex-1 cursor-pointer rounded-full",onClick:u,children:e("div",{className:"bg-indigo-500 absolute top-0 left-0 h-full rounded-full transition-all",style:{width:`${g}%`}})}),e(i,{className:"h-4 w-4 text-indigo-400"})]})}function u({url:r,alt:t}){const{previewUrl:i}=m(r);return e("img",{src:i||r,alt:t,className:"max-w-full max-h-full object-contain p-2"})}export function ListenAndSpeakImageGroupClient({questionData:l,isReviewMode:n=!1}){const{audioUrl:c,activeImageId:m,groupImages:h=[],expectedAnswers:b=[],explanation:p}=l;return r("div",{className:"space-y-4",children:[l.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(s,{className:"h-4 w-4"})}),e("span",{className:"text-sm font-medium text-gray-700",children:"Nghe và nói (Nhóm hình ảnh)"})]})}),r("div",{className:"p-5 space-y-5",children:[c?e(x,{audioUrl:c}):r("div",{className:"flex items-center gap-2 rounded-lg border border-gray-200 bg-gray-50 px-4 py-3",children:[e(i,{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"})]}),h.length>0?e("div",{className:"rounded-xl border border-gray-100 bg-gradient-to-b from-gray-50/80 to-white p-5",children:e("div",{className:"flex flex-row justify-center gap-4 sm:gap-5 flex-wrap",children:h.map((t,i)=>{const s=t.id===m,l=String.fromCharCode(65+i);return r("div",{className:g("flex flex-col items-center gap-2 transition-transform duration-300",s&&"scale-[1.03]"),children:[r("div",{className:g("relative w-28 h-28 sm:w-36 sm:h-36 rounded-xl overflow-hidden flex items-center justify-center bg-white transition-all duration-300",s?"border-[3px] border-blue-400 shadow-lg shadow-blue-100/70 ring-2 ring-blue-200/40":"border-2 border-gray-200 shadow-sm hover:shadow-md hover:border-gray-300"),children:[t.url?e(u,{url:t.url,alt:t.label||`Image ${i+1}`}):e("div",{className:"w-full h-full bg-gray-50 flex items-center justify-center text-gray-300",children:e(d,{className:"h-10 w-10"})}),e("div",{className:g("absolute top-1.5 left-1.5 flex h-6 w-6 items-center justify-center rounded-lg text-xs font-bold shadow-sm",s?"bg-blue-500 text-white":"bg-white/90 text-gray-500 border border-gray-200"),children:l}),s&&e("div",{className:"absolute top-1.5 right-1.5 w-5 h-5 rounded-full bg-blue-500 flex items-center justify-center shadow-sm",children:e(a,{className:"h-3.5 w-3.5 text-white"})})]}),r("div",{className:"flex flex-col items-center gap-0.5",children:[e("span",{className:g("text-sm font-semibold px-2.5 py-0.5 rounded-md transition-colors",s?"text-blue-700 bg-blue-50":"text-gray-600"),children:t.label||`Hình ${i+1}`}),s&&e("span",{className:"text-[10px] font-medium text-blue-500 animate-pulse",children:"Đang hỏi"})]})]},t.id)})})}):r("div",{className:"flex flex-col items-center justify-center py-10 px-4 rounded-xl border-2 border-dashed border-gray-200 bg-gray-50/50",children:[e(d,{className:"h-12 w-12 text-gray-300 mb-3"}),e("p",{className:"text-sm font-medium text-gray-500",children:"Chưa có hình ảnh nào"}),e("p",{className:"text-xs text-gray-400 mt-1",children:"Hình ảnh sẽ hiển thị tại đây"})]}),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(t,{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"})]}),n&&b.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(a,{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:b.map((t,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:t})]},i))})]}),n&&p&&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(o,{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:p})]})]})]})]})}
|
|
@@ -1,285 +1 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useEffect, useMemo, 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 { Plus, Trash2, Pencil, Eye, Mic, Volume2, ImageIcon, Image as ImageIcon2, CheckCircle2, MessageSquareText, Info, ListChecks, BookOpen } from 'lucide-react';
|
|
10
|
-
import { useReactHookForm } from '../../../../shared/lib/hooks/useReactHookForm';
|
|
11
|
-
import { useDebouncedCallback, usePresignedFileUrl } from '../../../../shared/lib/hooks';
|
|
12
|
-
import { FileUpload } from '../../../../components/ui/file-upload';
|
|
13
|
-
import { PointsInput } from '../../../../components/ui/points-input';
|
|
14
|
-
// Stores & hooks
|
|
15
|
-
import { useImageQuestionGroup } from '../../_shared/hooks/useImageQuestionGroup';
|
|
16
|
-
import { generateObjectId } from '../../_shared/utils/id-generator';
|
|
17
|
-
// Client component
|
|
18
|
-
import { ListenAndSpeakImageGroupClient } from './ListenAndSpeakImageGroupClient';
|
|
19
|
-
// Validation & Types
|
|
20
|
-
import { listenAndSpeakImageGroupFormSchema } from '../../../../shared/lib/validation/listen-and-speak-image-group.validation';
|
|
21
|
-
import { CreatorGuide } from '../../components/CreatorGuide';
|
|
22
|
-
// ─── Sub-component: Image preview with presigned URL ─────────────────────────
|
|
23
|
-
function ImagePreview({ imageUrl }) {
|
|
24
|
-
const { previewUrl } = usePresignedFileUrl(imageUrl);
|
|
25
|
-
if (!previewUrl)
|
|
26
|
-
return null;
|
|
27
|
-
return (_jsx("div", { className: "mt-2 rounded-lg border border-gray-200 overflow-hidden", children: _jsx("img", { src: previewUrl, alt: "Preview", className: "h-24 w-full object-contain bg-gray-50" }) }));
|
|
28
|
-
}
|
|
29
|
-
// ─── Sub-component: Inline image preview (for active image selection) ────────
|
|
30
|
-
function ImagePreviewInline({ imageUrl }) {
|
|
31
|
-
const { previewUrl } = usePresignedFileUrl(imageUrl);
|
|
32
|
-
if (!previewUrl) {
|
|
33
|
-
return (_jsx("div", { className: "flex h-16 w-full items-center justify-center rounded-lg bg-gray-100", children: _jsx(ImageIcon2, { className: "h-6 w-6 text-gray-300" }) }));
|
|
34
|
-
}
|
|
35
|
-
return (_jsx("img", { src: previewUrl, alt: "Preview", className: "h-16 w-full rounded-lg object-contain bg-gray-50" }));
|
|
36
|
-
}
|
|
37
|
-
export function ListenAndSpeakImageGroupCreator({ initialData, onSave, onCancel, onChange, externalErrors, onUnsavedChangesChange, validationRef, groups, questionIndexInPart, partId, }) {
|
|
38
|
-
// 1. Group level data (Shared images)
|
|
39
|
-
const { isGrouped, currentGroupNumber, images, setImages } = useImageQuestionGroup({
|
|
40
|
-
partId,
|
|
41
|
-
groups,
|
|
42
|
-
questionIndexInPart,
|
|
43
|
-
initialData: {
|
|
44
|
-
images: initialData?.groupImages || [],
|
|
45
|
-
},
|
|
46
|
-
});
|
|
47
|
-
const isFirstInGroup = useMemo(() => {
|
|
48
|
-
if (!groups || !groups.length || questionIndexInPart === undefined)
|
|
49
|
-
return true;
|
|
50
|
-
let cumulative = 0;
|
|
51
|
-
for (const group of groups) {
|
|
52
|
-
if (questionIndexInPart === cumulative)
|
|
53
|
-
return true;
|
|
54
|
-
cumulative += group.questionCount;
|
|
55
|
-
}
|
|
56
|
-
return false;
|
|
57
|
-
}, [groups, questionIndexInPart]);
|
|
58
|
-
// 2. Question level form data (Audio, ActiveImageId, ExpectedAnswers)
|
|
59
|
-
const apiData = initialData;
|
|
60
|
-
const form = useReactHookForm({
|
|
61
|
-
zodSchema: listenAndSpeakImageGroupFormSchema,
|
|
62
|
-
defaultValues: {
|
|
63
|
-
audioUrl: apiData?.audioUrl || '',
|
|
64
|
-
expectedAnswers: apiData?.expectedAnswers?.length > 0 ? apiData.expectedAnswers : [''],
|
|
65
|
-
explanation: apiData?.explanation || '',
|
|
66
|
-
points: apiData?.points ?? 1,
|
|
67
|
-
activeImageId: apiData?.activeImageId || '',
|
|
68
|
-
isBackup: apiData?.isBackup || false,
|
|
69
|
-
},
|
|
70
|
-
mode: 'onBlur',
|
|
71
|
-
});
|
|
72
|
-
const [isClientMode, setIsClientMode] = useState(false);
|
|
73
|
-
const watchedAudioUrl = form.watch('audioUrl');
|
|
74
|
-
const watchedExpectedAnswers = form.watch('expectedAnswers');
|
|
75
|
-
const watchedActiveImageId = form.watch('activeImageId');
|
|
76
|
-
const watchedExplanation = form.watch('explanation');
|
|
77
|
-
const watchedPoints = form.watch('points');
|
|
78
|
-
const watchedIsBackup = form.watch('isBackup');
|
|
79
|
-
// Presigned URL for audio playback
|
|
80
|
-
const { previewUrl: audioPreviewUrl } = usePresignedFileUrl(watchedAudioUrl || '');
|
|
81
|
-
// ---------------------------------------------------------------
|
|
82
|
-
// Refs to prevent infinite loops (following ListenAndSpeakAnswerCreator pattern)
|
|
83
|
-
// ---------------------------------------------------------------
|
|
84
|
-
const previousInitialDataRef = useRef(initialData);
|
|
85
|
-
const isInitializingRef = useRef(false);
|
|
86
|
-
const onChangeRef = useRef(onChange);
|
|
87
|
-
const previousPayloadRef = useRef('');
|
|
88
|
-
// Keep onChange ref up to date without triggering re-renders
|
|
89
|
-
useEffect(() => {
|
|
90
|
-
onChangeRef.current = onChange;
|
|
91
|
-
}, [onChange]);
|
|
92
|
-
// Debounced onChange that uses the ref (avoids stale closure & dependency issues)
|
|
93
|
-
const debouncedOnChange = useDebouncedCallback((data) => {
|
|
94
|
-
onChangeRef.current?.(data);
|
|
95
|
-
}, 300);
|
|
96
|
-
// ---------------------------------------------------------------
|
|
97
|
-
// Connect to external validation trigger
|
|
98
|
-
// ---------------------------------------------------------------
|
|
99
|
-
useEffect(() => {
|
|
100
|
-
if (validationRef) {
|
|
101
|
-
validationRef.current = {
|
|
102
|
-
triggerValidation: async () => {
|
|
103
|
-
return await form.trigger();
|
|
104
|
-
},
|
|
105
|
-
getFormData: () => ({
|
|
106
|
-
audioUrl: watchedAudioUrl,
|
|
107
|
-
expectedAnswers: watchedExpectedAnswers,
|
|
108
|
-
explanation: watchedExplanation,
|
|
109
|
-
points: watchedPoints,
|
|
110
|
-
activeImageId: watchedActiveImageId,
|
|
111
|
-
isBackup: watchedIsBackup,
|
|
112
|
-
groupImages: images,
|
|
113
|
-
groupNumber: isGrouped ? currentGroupNumber : undefined,
|
|
114
|
-
}),
|
|
115
|
-
};
|
|
116
|
-
}
|
|
117
|
-
}, [validationRef, form, watchedAudioUrl, watchedExpectedAnswers, watchedExplanation, watchedPoints, watchedActiveImageId, watchedIsBackup, images, isGrouped, currentGroupNumber]);
|
|
118
|
-
// ---------------------------------------------------------------
|
|
119
|
-
// Sync form values if initialData props change
|
|
120
|
-
// ---------------------------------------------------------------
|
|
121
|
-
useEffect(() => {
|
|
122
|
-
const normalizedInitialData = {
|
|
123
|
-
audioUrl: apiData?.audioUrl || '',
|
|
124
|
-
expectedAnswers: apiData?.expectedAnswers?.length > 0 ? apiData.expectedAnswers : [''],
|
|
125
|
-
explanation: apiData?.explanation || '',
|
|
126
|
-
points: apiData?.points ?? 1,
|
|
127
|
-
activeImageId: apiData?.activeImageId || '',
|
|
128
|
-
isBackup: apiData?.isBackup || false,
|
|
129
|
-
};
|
|
130
|
-
const initialDataChanged = JSON.stringify(previousInitialDataRef.current) !== JSON.stringify(initialData);
|
|
131
|
-
if (!initialDataChanged) {
|
|
132
|
-
return;
|
|
133
|
-
}
|
|
134
|
-
previousInitialDataRef.current = initialData;
|
|
135
|
-
const currentFormData = {
|
|
136
|
-
audioUrl: watchedAudioUrl,
|
|
137
|
-
expectedAnswers: watchedExpectedAnswers,
|
|
138
|
-
explanation: watchedExplanation,
|
|
139
|
-
points: watchedPoints,
|
|
140
|
-
activeImageId: watchedActiveImageId,
|
|
141
|
-
isBackup: watchedIsBackup,
|
|
142
|
-
};
|
|
143
|
-
// Detect if the new initialData is just an echo of what we already sent via onChange
|
|
144
|
-
const isEchoFromOnChange = currentFormData.points === normalizedInitialData.points &&
|
|
145
|
-
currentFormData.explanation === normalizedInitialData.explanation &&
|
|
146
|
-
currentFormData.audioUrl === normalizedInitialData.audioUrl &&
|
|
147
|
-
currentFormData.activeImageId === normalizedInitialData.activeImageId &&
|
|
148
|
-
JSON.stringify(currentFormData.expectedAnswers) === JSON.stringify(normalizedInitialData.expectedAnswers);
|
|
149
|
-
const wasJustSaved = initialData?.isCompleted === true;
|
|
150
|
-
if (isEchoFromOnChange && wasJustSaved) {
|
|
151
|
-
return;
|
|
152
|
-
}
|
|
153
|
-
// Only reset if the incoming data is genuinely different from current form
|
|
154
|
-
const shouldReset = !isEchoFromOnChange &&
|
|
155
|
-
(normalizedInitialData.audioUrl !== currentFormData.audioUrl ||
|
|
156
|
-
normalizedInitialData.activeImageId !== currentFormData.activeImageId ||
|
|
157
|
-
JSON.stringify(normalizedInitialData.expectedAnswers) !== JSON.stringify(currentFormData.expectedAnswers));
|
|
158
|
-
if (shouldReset) {
|
|
159
|
-
isInitializingRef.current = true;
|
|
160
|
-
form.reset(normalizedInitialData);
|
|
161
|
-
setTimeout(() => {
|
|
162
|
-
isInitializingRef.current = false;
|
|
163
|
-
}, 150);
|
|
164
|
-
}
|
|
165
|
-
}, [initialData, form, watchedAudioUrl, watchedExpectedAnswers, watchedExplanation, watchedPoints, watchedActiveImageId, watchedIsBackup]);
|
|
166
|
-
// ---------------------------------------------------------------
|
|
167
|
-
// Fire onChange when form values change
|
|
168
|
-
// ---------------------------------------------------------------
|
|
169
|
-
const watchedExpectedAnswersString = JSON.stringify(watchedExpectedAnswers);
|
|
170
|
-
const imagesString = JSON.stringify(images);
|
|
171
|
-
useEffect(() => {
|
|
172
|
-
// Skip onChange during form reset (initialization)
|
|
173
|
-
if (isInitializingRef.current) {
|
|
174
|
-
const currentPayload = JSON.stringify({
|
|
175
|
-
audioUrl: watchedAudioUrl,
|
|
176
|
-
expectedAnswers: watchedExpectedAnswers,
|
|
177
|
-
explanation: watchedExplanation,
|
|
178
|
-
points: watchedPoints,
|
|
179
|
-
activeImageId: watchedActiveImageId,
|
|
180
|
-
isBackup: watchedIsBackup,
|
|
181
|
-
groupImages: images,
|
|
182
|
-
groupNumber: isGrouped ? currentGroupNumber : undefined,
|
|
183
|
-
});
|
|
184
|
-
previousPayloadRef.current = currentPayload;
|
|
185
|
-
return;
|
|
186
|
-
}
|
|
187
|
-
const payload = {
|
|
188
|
-
audioUrl: watchedAudioUrl,
|
|
189
|
-
expectedAnswers: watchedExpectedAnswers,
|
|
190
|
-
explanation: watchedExplanation,
|
|
191
|
-
points: watchedPoints,
|
|
192
|
-
activeImageId: watchedActiveImageId,
|
|
193
|
-
isBackup: watchedIsBackup,
|
|
194
|
-
groupImages: images,
|
|
195
|
-
groupNumber: isGrouped ? currentGroupNumber : undefined,
|
|
196
|
-
};
|
|
197
|
-
const payloadString = JSON.stringify(payload);
|
|
198
|
-
// Only fire onChange if data actually changed (deduplication)
|
|
199
|
-
if (previousPayloadRef.current !== payloadString) {
|
|
200
|
-
previousPayloadRef.current = payloadString;
|
|
201
|
-
debouncedOnChange(payload);
|
|
202
|
-
}
|
|
203
|
-
}, [
|
|
204
|
-
watchedAudioUrl,
|
|
205
|
-
watchedExpectedAnswersString,
|
|
206
|
-
watchedExplanation,
|
|
207
|
-
watchedPoints,
|
|
208
|
-
watchedActiveImageId,
|
|
209
|
-
watchedIsBackup,
|
|
210
|
-
imagesString,
|
|
211
|
-
debouncedOnChange,
|
|
212
|
-
]);
|
|
213
|
-
// Track unsaved changes
|
|
214
|
-
useEffect(() => {
|
|
215
|
-
if (onUnsavedChangesChange) {
|
|
216
|
-
onUnsavedChangesChange(form.formState.isDirty);
|
|
217
|
-
}
|
|
218
|
-
}, [form.formState.isDirty, onUnsavedChangesChange]);
|
|
219
|
-
// ---------------------------------------------------------------
|
|
220
|
-
// Handlers
|
|
221
|
-
// ---------------------------------------------------------------
|
|
222
|
-
const addExpectedAnswer = () => {
|
|
223
|
-
const currentAnswers = form.getValues('expectedAnswers');
|
|
224
|
-
form.setValue('expectedAnswers', [...currentAnswers, ''], { shouldValidate: true, shouldDirty: true });
|
|
225
|
-
};
|
|
226
|
-
const updateExpectedAnswer = (index, value) => {
|
|
227
|
-
const newAnswers = [...watchedExpectedAnswers];
|
|
228
|
-
newAnswers[index] = value;
|
|
229
|
-
form.setValue('expectedAnswers', newAnswers, { shouldValidate: true, shouldDirty: true });
|
|
230
|
-
};
|
|
231
|
-
const removeExpectedAnswer = (index) => {
|
|
232
|
-
if (watchedExpectedAnswers.length > 1) {
|
|
233
|
-
const newAnswers = watchedExpectedAnswers.filter((_, i) => i !== index);
|
|
234
|
-
form.setValue('expectedAnswers', newAnswers, { shouldValidate: true, shouldDirty: true });
|
|
235
|
-
}
|
|
236
|
-
};
|
|
237
|
-
const handleAddImage = () => {
|
|
238
|
-
setImages([...images, { id: generateObjectId(), url: '', label: String(images.length + 1) }]);
|
|
239
|
-
};
|
|
240
|
-
const handleRemoveImage = (index) => {
|
|
241
|
-
setImages(images.filter((_, i) => i !== index));
|
|
242
|
-
// Reset activeImageId if the removed image was selected
|
|
243
|
-
if (images[index].id === watchedActiveImageId) {
|
|
244
|
-
form.setValue('activeImageId', '', { shouldValidate: true, shouldDirty: true });
|
|
245
|
-
}
|
|
246
|
-
};
|
|
247
|
-
const handleUpdateImage = (index, field, value) => {
|
|
248
|
-
const newImages = [...images];
|
|
249
|
-
newImages[index] = { ...newImages[index], [field]: value };
|
|
250
|
-
setImages(newImages);
|
|
251
|
-
};
|
|
252
|
-
// ─── Client Mode Preview ──────────────────────────────────────────────────
|
|
253
|
-
if (isClientMode) {
|
|
254
|
-
const questionData = {
|
|
255
|
-
audioUrl: watchedAudioUrl,
|
|
256
|
-
expectedAnswers: watchedExpectedAnswers,
|
|
257
|
-
explanation: watchedExplanation,
|
|
258
|
-
points: watchedPoints,
|
|
259
|
-
activeImageId: watchedActiveImageId,
|
|
260
|
-
isBackup: watchedIsBackup,
|
|
261
|
-
groupImages: images,
|
|
262
|
-
groupNumber: isGrouped ? currentGroupNumber : undefined,
|
|
263
|
-
};
|
|
264
|
-
return (_jsxs("div", { className: "space-y-6", children: [_jsx(Card, { children: _jsx(CardHeader, { children: _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { children: [_jsx(CardTitle, { children: "Nghe v\u00E0 tr\u1EA3 l\u1EDDi theo nh\u00F3m \u1EA3nh (Preview)" }), _jsx("p", { className: "mt-1 text-sm text-gray-500", children: "Preview mode" })] }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => setIsClientMode(false), children: [_jsx(Pencil, { className: "mr-2 h-4 w-4" }), "Quay l\u1EA1i Edit Mode"] })] }) }) }), _jsx(ListenAndSpeakImageGroupClient, { questionData: questionData, isReviewMode: true })] }));
|
|
265
|
-
}
|
|
266
|
-
// ─── Creator UI ────────────────────────────────────────────────────────────
|
|
267
|
-
return (_jsxs("div", { className: "space-y-6", children: [_jsx(CreatorGuide, { colorScheme: "emerald", steps: [
|
|
268
|
-
{ text: 'Thêm hình ảnh vào nhóm — mỗi hình cần có URL và nhãn mô tả' },
|
|
269
|
-
{ text: 'Hình ảnh dùng chung cho tất cả câu hỏi trong nhóm (câu hỏi đầu tiên sẽ quản lý)' },
|
|
270
|
-
{ text: 'Upload audio câu hỏi cho từng câu — giám khảo sẽ hỏi về từng hình' },
|
|
271
|
-
{ text: 'Chọn hình ảnh đang được hỏi (active image) cho mỗi câu', highlight: true },
|
|
272
|
-
{ text: 'Nhập các đáp án mong muốn và giải thích' },
|
|
273
|
-
], tips: ['Sắp xếp hình ảnh theo thứ tự logic, gắn nhãn rõ ràng'] }), _jsxs(Card, { className: "overflow-hidden border-0 bg-white shadow-lg shadow-emerald-100/50", children: [_jsx("div", { className: "h-1.5 bg-gradient-to-r from-emerald-500 via-teal-500 to-cyan-500" }), _jsx(CardHeader, { className: "pb-4", children: _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "flex h-10 w-10 items-center justify-center rounded-xl bg-gradient-to-br from-emerald-500 to-teal-600 shadow-md", children: _jsx(ListChecks, { className: "h-5 w-5 text-white" }) }), _jsxs("div", { children: [_jsx(CardTitle, { className: "text-xl font-bold text-gray-900", children: "Nh\u00F3m \u0111\u00E1p \u00E1n" }), _jsxs("div", { className: "mt-1.5 inline-flex items-center gap-1.5 rounded-full bg-emerald-100 px-3 py-1 text-sm font-medium text-emerald-700", children: [_jsx("span", { children: "\uD83D\uDDBC\uFE0F" }), _jsxs("span", { children: ["Nh\u00F3m ", currentGroupNumber] }), _jsx("span", { className: "text-xs font-normal opacity-75 ml-1", children: "(D\u00F9ng chung)" })] })] })] }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => setIsClientMode(true), disabled: !watchedAudioUrl || watchedExpectedAnswers.filter((a) => a.trim()).length === 0, className: "gap-2 border-emerald-200 text-emerald-600 hover:bg-emerald-50 hover:text-emerald-700", children: [_jsx(Eye, { className: "h-4 w-4" }), "Preview"] })] }) })] }), _jsxs(Card, { className: "overflow-hidden border-0 bg-white shadow-lg shadow-emerald-100/50", children: [_jsx("div", { className: "h-1 bg-gradient-to-r from-emerald-400 to-teal-400" }), _jsx(CardHeader, { className: "pb-4", children: _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-6 w-6 items-center justify-center rounded-md bg-emerald-100", children: _jsx(ImageIcon2, { className: "h-3.5 w-3.5 text-emerald-600" }) }), _jsx(CardTitle, { className: "text-lg font-semibold text-gray-800", children: "Nh\u00F3m H\u00ECnh \u1EA2nh" })] }), images.length < 4 && (_jsxs(Button, { variant: "outline", size: "sm", onClick: handleAddImage, className: "gap-2 border-green-200 text-green-600 hover:bg-green-50 hover:text-green-700", children: [_jsx(Plus, { className: "h-4 w-4" }), "Th\u00EAm h\u00ECnh \u1EA3nh"] }))] }) }), _jsxs(CardContent, { className: "space-y-4", children: [_jsx("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4", children: images.map((img, index) => (_jsxs("div", { className: "rounded-xl border border-gray-200 bg-gray-50 p-4", children: [_jsxs("div", { className: "flex items-center justify-between mb-3", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-7 w-7 items-center justify-center rounded-lg bg-gradient-to-br from-blue-500 to-indigo-600 text-white text-sm font-bold shadow-sm", children: String.fromCharCode(65 + index) }), _jsxs("span", { className: "text-sm font-semibold text-gray-700", children: ["H\u00ECnh \u1EA3nh ", String.fromCharCode(65 + index)] })] }), images.length > 1 && (_jsx(Button, { variant: "ghost", size: "sm", onClick: () => handleRemoveImage(index), className: "h-7 w-7 p-0 text-red-500 hover:text-red-700 hover:bg-red-50", children: _jsx(Trash2, { className: "h-3.5 w-3.5" }) }))] }), _jsx(FileUpload, { id: `group-img-${img.id}`, label: "", accept: "image/*", value: img.url, onChange: (url) => handleUpdateImage(index, 'url', url), maxSize: 5, placeholder: "Upload h\u00ECnh \u1EA3nh", autoUpload: true, prefix: "questions/images" }), img.url && _jsx(ImagePreview, { imageUrl: img.url }), _jsxs("div", { className: "mt-3", children: [_jsx(Label, { className: "text-xs font-medium text-gray-600", children: "Nh\u00E3n" }), _jsx(Input, { value: img.label, onChange: (e) => handleUpdateImage(index, 'label', e.target.value), placeholder: "Nh\u1EADp nh\u00E3n / m\u00F4 t\u1EA3 ng\u1EAFn", className: "mt-1 border-gray-200" })] })] }, img.id))) }), images.length === 0 && (_jsxs("div", { className: "flex flex-col items-center justify-center rounded-lg border-2 border-dashed border-indigo-300 p-8 bg-indigo-50/50", children: [_jsx(ImageIcon2, { className: "h-10 w-10 text-indigo-400 mb-2" }), _jsx("p", { className: "text-sm text-gray-500 mb-3", children: "Ch\u01B0a c\u00F3 h\u00ECnh \u1EA3nh n\u00E0o" }), _jsxs(Button, { variant: "outline", size: "sm", onClick: handleAddImage, className: "gap-2", children: [_jsx(Plus, { className: "h-4 w-4" }), "Th\u00EAm h\u00ECnh \u1EA3nh \u0111\u1EA7u ti\u00EAn"] })] }))] })] }), _jsxs(Card, { className: "overflow-hidden border-0 bg-white shadow-lg shadow-indigo-100/50", children: [_jsx("div", { className: "h-1.5 bg-gradient-to-r from-indigo-500 to-blue-500" }), _jsx(CardHeader, { className: "pb-4", children: _jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "flex h-10 w-10 items-center justify-center rounded-xl bg-gradient-to-br from-indigo-500 to-blue-600 shadow-md", children: _jsx(Mic, { className: "h-5 w-5 text-white" }) }), _jsxs("div", { children: [_jsx(CardTitle, { className: "text-xl font-bold text-gray-900", children: "Nghe v\u00E0 tr\u1EA3 l\u1EDDi" }), _jsx("p", { className: "text-sm text-gray-500", children: "H\u1ECDc sinh nghe \u00E2m thanh v\u00E0 thu \u00E2m c\u00E2u tr\u1EA3 l\u1EDDi cho b\u1EE9c \u1EA3nh \u0111\u01B0\u1EE3c ch\u1ECDn" })] })] }) }), _jsxs(CardContent, { className: "space-y-6", children: [_jsx("div", { className: "rounded-xl border border-gray-200 bg-gray-50 p-4", children: _jsx("div", { className: "w-full md:w-1/2", children: _jsx(PointsInput, { ...form.register('points', { valueAsNumber: true }), error: form.hasError('points') ? form.getFieldError('points') : undefined }) }) }), _jsxs("div", { className: "space-y-2", children: [_jsxs(Label, { className: "flex items-center gap-2 text-base font-semibold text-gray-800", children: [_jsx(ImageIcon, { className: "h-4 w-4 text-indigo-500" }), "Ch\u1ECDn h\u00ECnh \u1EA3nh \u0111ang \u0111\u01B0\u1EE3c h\u1ECFi"] }), images.filter((o) => o.url).length > 0 ? (_jsx("div", { className: "grid grid-cols-2 gap-3 sm:grid-cols-4", children: images.map((img, index) => {
|
|
274
|
-
if (!img.url)
|
|
275
|
-
return null;
|
|
276
|
-
const isSelected = watchedActiveImageId === img.id;
|
|
277
|
-
return (_jsxs("button", { type: "button", onClick: () => {
|
|
278
|
-
form.setValue('activeImageId', img.id, { shouldValidate: true, shouldDirty: true });
|
|
279
|
-
}, className: `relative text-left rounded-xl border-2 p-2 transition-all ${isSelected
|
|
280
|
-
? 'border-indigo-500 bg-indigo-50 ring-2 ring-indigo-200 shadow-md'
|
|
281
|
-
: 'border-gray-200 hover:border-gray-400 hover:shadow-sm'}`, children: [_jsxs("div", { className: "flex flex-col items-center gap-2", children: [_jsx("div", { className: `flex h-7 w-7 flex-shrink-0 items-center justify-center rounded-lg text-sm font-bold shadow-sm ${isSelected
|
|
282
|
-
? 'bg-indigo-500 text-white'
|
|
283
|
-
: 'bg-gray-100 text-gray-600'}`, children: String.fromCharCode(65 + index) }), _jsx(ImagePreviewInline, { imageUrl: img.url }), img.label && (_jsx("p", { className: `text-xs text-center ${isSelected ? 'text-indigo-700 font-medium' : 'text-gray-600'}`, children: img.label }))] }), isSelected && (_jsx("div", { className: "absolute top-1 right-1", children: _jsx(CheckCircle2, { className: "h-5 w-5 text-indigo-500" }) }))] }, img.id));
|
|
284
|
-
}) })) : (_jsx("p", { className: "text-sm text-amber-600 italic", children: "Vui l\u00F2ng upload \u1EA3nh cho h\u00ECnh \u1EA3nh \u1EDF ph\u1EA7n tr\u00EAn tr\u01B0\u1EDBc" })), form.hasError('activeImageId') && (_jsx("div", { className: "mt-2 rounded-lg border border-red-200 bg-red-50 p-3", children: _jsxs("p", { className: "text-sm text-red-700", children: ["\u26A0\uFE0F ", form.getFieldError('activeImageId')] }) })), !form.hasError('activeImageId') && !watchedActiveImageId && images.filter((o) => o.url).length > 0 && (_jsx("p", { className: "mt-1 text-xs text-amber-600", children: "Ch\u01B0a ch\u1ECDn h\u00ECnh \u1EA3nh \u0111ang \u0111\u01B0\u1EE3c h\u1ECFi" }))] }), _jsxs("div", { className: "space-y-2", children: [_jsxs(Label, { className: "flex items-center gap-2 text-base font-semibold text-gray-800", children: [_jsx(Volume2, { className: "h-4 w-4 text-indigo-500" }), "Audio c\u00E2u h\u1ECFi"] }), _jsx(FileUpload, { id: "question-audio", label: "", accept: "audio/*", value: watchedAudioUrl, onChange: (url) => form.setValue('audioUrl', url, { shouldValidate: true, shouldDirty: true }), maxSize: 20, placeholder: "T\u1EA3i l\u00EAn file \u00E2m thanh (mp3, wav...)", autoUpload: true, prefix: "questions/audio" }), form.hasError('audioUrl') && (_jsx("p", { className: "text-sm text-red-600", children: form.getFieldError('audioUrl') })), audioPreviewUrl && (_jsx("audio", { controls: true, className: "mt-2 w-full", src: audioPreviewUrl, children: "Tr\u00ECnh duy\u1EC7t kh\u00F4ng h\u1ED7 tr\u1EE3 audio." }))] }), externalErrors && externalErrors.length > 0 && (_jsx("div", { className: "flex items-start gap-3 rounded-xl border border-red-200 bg-red-50 p-4", children: _jsxs("div", { className: "text-sm", children: [_jsx("p", { className: "font-semibold text-red-800", children: "C\u1EA7n ph\u1EA3i s\u1EEDa c\u00E1c l\u1ED7i sau:" }), _jsx("ul", { className: "mt-1 list-inside list-disc text-red-700", children: externalErrors.map((error, index) => (_jsx("li", { children: error }, index))) })] }) }))] })] }), _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 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" }) }), _jsx("div", { children: _jsxs(CardTitle, { className: "text-lg font-semibold text-gray-800", children: ["\u0110\u00E1p \u00E1n mong mu\u1ED1n (", watchedExpectedAnswers.filter((a) => a.trim()).length, ")"] }) })] }), _jsxs(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.5", children: [_jsx(Info, { className: "h-4 w-4 text-blue-500 mt-0.5 flex-shrink-0" }), _jsxs("div", { className: "text-xs text-blue-700 space-y-1", children: [_jsx("p", { children: "Nh\u1EADp c\u00E1c c\u00E2u tr\u1EA3 l\u1EDDi mong mu\u1ED1n. C\u00F3 th\u1EC3 th\u00EAm nhi\u1EC1u \u0111\u00E1p \u00E1n n\u1EBFu c\u00F3 nhi\u1EC1u c\u00E1ch tr\u1EA3 l\u1EDDi \u0111\u00FAng." }), _jsxs("p", { children: [_jsx("span", { className: "font-semibold", children: "\u2022 Ch\u1EA5m th\u1EE7 c\u00F4ng:" }), " Gi\u00E1o vi\u00EAn d\u00F9ng c\u00E1c \u0111\u00E1p \u00E1n n\u00E0y l\u00E0m tham kh\u1EA3o \u0111\u1EC3 ch\u1EA5m \u0111i\u1EC3m."] }), _jsxs("p", { children: [_jsx("span", { className: "font-semibold", children: "\u2022 Ch\u1EA5m t\u1EF1 \u0111\u1ED9ng (AI):" }), " H\u1EC7 th\u1ED1ng d\u00F9ng AI so s\u00E1nh gi\u1ECDng n\u00F3i c\u1EE7a h\u1ECDc sinh v\u1EDBi c\u00E1c \u0111\u00E1p \u00E1n \u0111\u1EC3 t\u1EF1 \u0111\u1ED9ng ch\u1EA5m \u0111i\u1EC3m."] })] })] }), _jsxs("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-9 w-9 flex-shrink-0 items-center justify-center rounded-lg bg-green-100 text-green-700 font-semibold shadow-sm text-sm", children: index + 1 }), _jsxs("div", { className: "flex-1", children: [_jsx(Input, { value: answer || '', onChange: (e) => updateExpectedAnswer(index, e.target.value), placeholder: `Ví dụ: ${index === 0 ? 'A kite' : 'It is a kite'}`, 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, { variant: "ghost", size: "sm", onClick: () => removeExpectedAnswer(index), className: "text-red-400 hover:text-red-600 hover:bg-red-50 opacity-0 group-hover/answer:opacity-100 transition-opacity", children: _jsx(Trash2, { className: "h-4 w-4" }) }))] }, index))), form.formState.errors?.expectedAnswers && !Array.isArray(form.formState.errors.expectedAnswers) && (_jsx("p", { className: "text-sm text-red-500", children: form.formState.errors.expectedAnswers.message }))] })] })] }), _jsxs(Card, { className: "overflow-hidden border-0 bg-white shadow-lg shadow-amber-100/50", children: [_jsx("div", { className: "h-1 bg-gradient-to-r from-amber-400 to-orange-400" }), _jsx(CardHeader, { className: "pb-4", children: _jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "flex h-9 w-9 items-center justify-center rounded-lg bg-gradient-to-br from-amber-500 to-orange-500 shadow-sm", children: _jsx(BookOpen, { className: "h-4 w-4 text-white" }) }), _jsxs("div", { children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(CardTitle, { className: "text-lg font-semibold text-gray-800", children: "Gi\u1EA3i th\u00EDch" }), _jsx("span", { className: "inline-flex items-center rounded-full border border-amber-200 bg-amber-50 px-2.5 py-0.5 text-xs font-medium text-amber-600", children: "Kh\u00F4ng b\u1EAFt bu\u1ED9c" })] }), _jsx("p", { className: "text-sm text-gray-500 mt-0.5", children: "Gi\u1EA3i th\u00EDch cho \u0111\u00E1p \u00E1n \u0111\u00FAng \u2014 hi\u1EC3n th\u1ECB sau khi h\u1ECDc sinh ho\u00E0n th\u00E0nh c\u00E2u h\u1ECFi" })] })] }) }), _jsx(CardContent, { children: _jsx(Textarea, { value: watchedExplanation, onChange: (e) => form.setValue('explanation', e.target.value, { shouldValidate: true }), placeholder: "VD: C\u00E2u tr\u1EA3 l\u1EDDi \u0111\u00FAng l\u00E0... v\u00EC...", rows: 3, className: "min-h-[80px] border-gray-200 bg-white transition-all focus:border-amber-300 focus:ring-amber-200 resize-none" }) })] })] }));
|
|
285
|
-
}
|
|
1
|
+
"use client";import{jsx as e,jsxs as r}from"react/jsx-runtime";import{useEffect as t,useMemo as a,useRef as i,useState as s}from"react";import{Card as n,CardContent as l,CardHeader as d,CardTitle as o}from"../../../../components/ui/card";import{Label as c}from"../../../../components/ui/label";import{Button as m}from"../../../../components/ui/button";import{Input as h}from"../../../../components/ui/input";import{Textarea as g}from"../../../../components/ui/textarea";import{Plus as u,Trash2 as p,Pencil as x,Eye as f,Mic as b,Volume2 as N,ImageIcon as v,Image as w,CheckCircle2 as y,MessageSquareText as I,Info as C,ListChecks as k,BookOpen as A}from"lucide-react";import{useReactHookForm as U}from"../../../../shared/lib/hooks/useReactHookForm";import{useDebouncedCallback as S,usePresignedFileUrl as j}from"../../../../shared/lib/hooks";import{FileUpload as V}from"../../../../components/ui/file-upload";import{PointsInput as D}from"../../../../components/ui/points-input";import{useImageQuestionGroup as z}from"../../_shared/hooks/useImageQuestionGroup";import{generateObjectId as B}from"../../_shared/utils/id-generator";import{ListenAndSpeakImageGroupClient as J}from"./ListenAndSpeakImageGroupClient";import{listenAndSpeakImageGroupFormSchema as O}from"../../../../shared/lib/validation/listen-and-speak-image-group.validation";import{CreatorGuide as E}from"../../components/CreatorGuide";function G({imageUrl:r}){const{previewUrl:t}=j(r);return t?e("div",{className:"mt-2 rounded-lg border border-gray-200 overflow-hidden",children:e("img",{src:t,alt:"Preview",className:"h-24 w-full object-contain bg-gray-50"})}):null}function q({imageUrl:r}){const{previewUrl:t}=j(r);return t?e("img",{src:t,alt:"Preview",className:"h-16 w-full rounded-lg object-contain bg-gray-50"}):e("div",{className:"flex h-16 w-full items-center justify-center rounded-lg bg-gray-100",children:e(w,{className:"h-6 w-6 text-gray-300"})})}export function ListenAndSpeakImageGroupCreator({initialData:P,onSave:T,onCancel:H,onChange:F,externalErrors:R,onUnsavedChangesChange:L,validationRef:M,groups:Q,questionIndexInPart:_,partId:K}){const{isGrouped:$,currentGroupNumber:W,images:X,setImages:Y}=z({partId:K,groups:Q,questionIndexInPart:_,initialData:{images:P?.groupImages||[]}}),Z=(a(()=>{if(!Q||!Q.length||void 0===_)return!0;let e=0;for(const r of Q){if(_===e)return!0;e+=r.questionCount}return!1},[Q,_]),P),ee=U({zodSchema:O,defaultValues:{audioUrl:Z?.audioUrl||"",expectedAnswers:Z?.expectedAnswers?.length>0?Z.expectedAnswers:[""],explanation:Z?.explanation||"",points:Z?.points??1,activeImageId:Z?.activeImageId||"",isBackup:Z?.isBackup||!1},mode:"onBlur"}),[re,te]=s(!1),ae=ee.watch("audioUrl"),ie=ee.watch("expectedAnswers"),se=ee.watch("activeImageId"),ne=ee.watch("explanation"),le=ee.watch("points"),de=ee.watch("isBackup"),{previewUrl:oe}=j(ae||""),ce=i(P),me=i(!1),he=i(F),ge=i("");t(()=>{he.current=F},[F]);const ue=S(e=>{he.current?.(e)},300);t(()=>{M&&(M.current={triggerValidation:async()=>await ee.trigger(),getFormData:()=>({audioUrl:ae,expectedAnswers:ie,explanation:ne,points:le,activeImageId:se,isBackup:de,groupImages:X,groupNumber:$?W:void 0})})},[M,ee,ae,ie,ne,le,se,de,X,$,W]),t(()=>{const e={audioUrl:Z?.audioUrl||"",expectedAnswers:Z?.expectedAnswers?.length>0?Z.expectedAnswers:[""],explanation:Z?.explanation||"",points:Z?.points??1,activeImageId:Z?.activeImageId||"",isBackup:Z?.isBackup||!1};if(!(JSON.stringify(ce.current)!==JSON.stringify(P)))return;ce.current=P;const r={audioUrl:ae,expectedAnswers:ie,explanation:ne,points:le,activeImageId:se,isBackup:de},t=r.points===e.points&&r.explanation===e.explanation&&r.audioUrl===e.audioUrl&&r.activeImageId===e.activeImageId&&JSON.stringify(r.expectedAnswers)===JSON.stringify(e.expectedAnswers);if(t&&!0===P?.isCompleted)return;!t&&(e.audioUrl!==r.audioUrl||e.activeImageId!==r.activeImageId||JSON.stringify(e.expectedAnswers)!==JSON.stringify(r.expectedAnswers))&&(me.current=!0,ee.reset(e),setTimeout(()=>{me.current=!1},150))},[P,ee,ae,ie,ne,le,se,de]);const pe=JSON.stringify(ie),xe=JSON.stringify(X);t(()=>{if(me.current){const e=JSON.stringify({audioUrl:ae,expectedAnswers:ie,explanation:ne,points:le,activeImageId:se,isBackup:de,groupImages:X,groupNumber:$?W:void 0});return void(ge.current=e)}const e={audioUrl:ae,expectedAnswers:ie,explanation:ne,points:le,activeImageId:se,isBackup:de,groupImages:X,groupNumber:$?W:void 0},r=JSON.stringify(e);ge.current!==r&&(ge.current=r,ue(e))},[ae,pe,ne,le,se,de,xe,ue]),t(()=>{L&&L(ee.formState.isDirty)},[ee.formState.isDirty,L]);const fe=()=>{Y([...X,{id:B(),url:"",label:String(X.length+1)}])},be=(e,r,t)=>{const a=[...X];a[e]={...a[e],[r]:t},Y(a)};if(re){const t={audioUrl:ae,expectedAnswers:ie,explanation:ne,points:le,activeImageId:se,isBackup:de,groupImages:X,groupNumber:$?W:void 0};return r("div",{className:"space-y-6",children:[e(n,{children:e(d,{children:r("div",{className:"flex items-center justify-between",children:[r("div",{children:[e(o,{children:"Nghe và trả lời theo nhóm ảnh (Preview)"}),e("p",{className:"mt-1 text-sm text-gray-500",children:"Preview mode"})]}),r(m,{variant:"outline",size:"sm",onClick:()=>te(!1),children:[e(x,{className:"mr-2 h-4 w-4"}),"Quay lại Edit Mode"]})]})})}),e(J,{questionData:t,isReviewMode:!0})]})}return r("div",{className:"space-y-6",children:[e(E,{colorScheme:"emerald",steps:[{text:"Thêm hình ảnh vào nhóm — mỗi hình cần có URL và nhãn mô tả"},{text:"Hình ảnh dùng chung cho tất cả câu hỏi trong nhóm (câu hỏi đầu tiên sẽ quản lý)"},{text:"Upload audio câu hỏi cho từng câu — giám khảo sẽ hỏi về từng hình"},{text:"Chọn hình ảnh đang được hỏi (active image) cho mỗi câu",highlight:!0},{text:"Nhập các đáp án mong muốn và giải thích"}],tips:["Sắp xếp hình ảnh theo thứ tự logic, gắn nhãn rõ ràng"]}),r(n,{className:"overflow-hidden border-0 bg-white shadow-lg shadow-emerald-100/50",children:[e("div",{className:"h-1.5 bg-gradient-to-r from-emerald-500 via-teal-500 to-cyan-500"}),e(d,{className:"pb-4",children:r("div",{className:"flex items-center justify-between",children:[r("div",{className:"flex items-center gap-3",children:[e("div",{className:"flex h-10 w-10 items-center justify-center rounded-xl bg-gradient-to-br from-emerald-500 to-teal-600 shadow-md",children:e(k,{className:"h-5 w-5 text-white"})}),r("div",{children:[e(o,{className:"text-xl font-bold text-gray-900",children:"Nhóm đáp án"}),r("div",{className:"mt-1.5 inline-flex items-center gap-1.5 rounded-full bg-emerald-100 px-3 py-1 text-sm font-medium text-emerald-700",children:[e("span",{children:"🖼️"}),r("span",{children:["Nhóm ",W]}),e("span",{className:"text-xs font-normal opacity-75 ml-1",children:"(Dùng chung)"})]})]})]}),r(m,{variant:"outline",size:"sm",onClick:()=>te(!0),disabled:!ae||0===ie.filter(e=>e.trim()).length,className:"gap-2 border-emerald-200 text-emerald-600 hover:bg-emerald-50 hover:text-emerald-700",children:[e(f,{className:"h-4 w-4"}),"Preview"]})]})})]}),r(n,{className:"overflow-hidden border-0 bg-white shadow-lg shadow-emerald-100/50",children:[e("div",{className:"h-1 bg-gradient-to-r from-emerald-400 to-teal-400"}),e(d,{className:"pb-4",children:r("div",{className:"flex items-center justify-between",children:[r("div",{className:"flex items-center gap-2",children:[e("div",{className:"flex h-6 w-6 items-center justify-center rounded-md bg-emerald-100",children:e(w,{className:"h-3.5 w-3.5 text-emerald-600"})}),e(o,{className:"text-lg font-semibold text-gray-800",children:"Nhóm Hình Ảnh"})]}),X.length<4&&r(m,{variant:"outline",size:"sm",onClick:fe,className:"gap-2 border-green-200 text-green-600 hover:bg-green-50 hover:text-green-700",children:[e(u,{className:"h-4 w-4"}),"Thêm hình ảnh"]})]})}),r(l,{className:"space-y-4",children:[e("div",{className:"grid grid-cols-1 md:grid-cols-2 gap-4",children:X.map((t,a)=>r("div",{className:"rounded-xl border border-gray-200 bg-gray-50 p-4",children:[r("div",{className:"flex items-center justify-between mb-3",children:[r("div",{className:"flex items-center gap-2",children:[e("div",{className:"flex h-7 w-7 items-center justify-center rounded-lg bg-gradient-to-br from-blue-500 to-indigo-600 text-white text-sm font-bold shadow-sm",children:String.fromCharCode(65+a)}),r("span",{className:"text-sm font-semibold text-gray-700",children:["Hình ảnh ",String.fromCharCode(65+a)]})]}),X.length>1&&e(m,{variant:"ghost",size:"sm",onClick:()=>(e=>{Y(X.filter((r,t)=>t!==e)),X[e].id===se&&ee.setValue("activeImageId","",{shouldValidate:!0,shouldDirty:!0})})(a),className:"h-7 w-7 p-0 text-red-500 hover:text-red-700 hover:bg-red-50",children:e(p,{className:"h-3.5 w-3.5"})})]}),e(V,{id:`group-img-${t.id}`,label:"",accept:"image/*",value:t.url,onChange:e=>be(a,"url",e),maxSize:5,placeholder:"Upload hình ảnh",autoUpload:!0,prefix:"questions/images"}),t.url&&e(G,{imageUrl:t.url}),r("div",{className:"mt-3",children:[e(c,{className:"text-xs font-medium text-gray-600",children:"Nhãn"}),e(h,{value:t.label,onChange:e=>be(a,"label",e.target.value),placeholder:"Nhập nhãn / mô tả ngắn",className:"mt-1 border-gray-200"})]})]},t.id))}),0===X.length&&r("div",{className:"flex flex-col items-center justify-center rounded-lg border-2 border-dashed border-indigo-300 p-8 bg-indigo-50/50",children:[e(w,{className:"h-10 w-10 text-indigo-400 mb-2"}),e("p",{className:"text-sm text-gray-500 mb-3",children:"Chưa có hình ảnh nào"}),r(m,{variant:"outline",size:"sm",onClick:fe,className:"gap-2",children:[e(u,{className:"h-4 w-4"}),"Thêm hình ảnh đầu tiên"]})]})]})]}),r(n,{className:"overflow-hidden border-0 bg-white shadow-lg shadow-indigo-100/50",children:[e("div",{className:"h-1.5 bg-gradient-to-r from-indigo-500 to-blue-500"}),e(d,{className:"pb-4",children:r("div",{className:"flex items-center gap-3",children:[e("div",{className:"flex h-10 w-10 items-center justify-center rounded-xl bg-gradient-to-br from-indigo-500 to-blue-600 shadow-md",children:e(b,{className:"h-5 w-5 text-white"})}),r("div",{children:[e(o,{className:"text-xl font-bold text-gray-900",children:"Nghe và trả lời"}),e("p",{className:"text-sm text-gray-500",children:"Học sinh nghe âm thanh và thu âm câu trả lời cho bức ảnh được chọn"})]})]})}),r(l,{className:"space-y-6",children:[e("div",{className:"rounded-xl border border-gray-200 bg-gray-50 p-4",children:e("div",{className:"w-full md:w-1/2",children:e(D,{...ee.register("points",{valueAsNumber:!0}),error:ee.hasError("points")?ee.getFieldError("points"):void 0})})}),r("div",{className:"space-y-2",children:[r(c,{className:"flex items-center gap-2 text-base font-semibold text-gray-800",children:[e(v,{className:"h-4 w-4 text-indigo-500"}),"Chọn hình ảnh đang được hỏi"]}),X.filter(e=>e.url).length>0?e("div",{className:"grid grid-cols-2 gap-3 sm:grid-cols-4",children:X.map((t,a)=>{if(!t.url)return null;const i=se===t.id;return r("button",{type:"button",onClick:()=>{ee.setValue("activeImageId",t.id,{shouldValidate:!0,shouldDirty:!0})},className:"relative text-left rounded-xl border-2 p-2 transition-all "+(i?"border-indigo-500 bg-indigo-50 ring-2 ring-indigo-200 shadow-md":"border-gray-200 hover:border-gray-400 hover:shadow-sm"),children:[r("div",{className:"flex flex-col items-center gap-2",children:[e("div",{className:"flex h-7 w-7 flex-shrink-0 items-center justify-center rounded-lg text-sm font-bold shadow-sm "+(i?"bg-indigo-500 text-white":"bg-gray-100 text-gray-600"),children:String.fromCharCode(65+a)}),e(q,{imageUrl:t.url}),t.label&&e("p",{className:"text-xs text-center "+(i?"text-indigo-700 font-medium":"text-gray-600"),children:t.label})]}),i&&e("div",{className:"absolute top-1 right-1",children:e(y,{className:"h-5 w-5 text-indigo-500"})})]},t.id)})}):e("p",{className:"text-sm text-amber-600 italic",children:"Vui lòng upload ảnh cho hình ảnh ở phần trên trước"}),ee.hasError("activeImageId")&&e("div",{className:"mt-2 rounded-lg border border-red-200 bg-red-50 p-3",children:r("p",{className:"text-sm text-red-700",children:["⚠️ ",ee.getFieldError("activeImageId")]})}),!ee.hasError("activeImageId")&&!se&&X.filter(e=>e.url).length>0&&e("p",{className:"mt-1 text-xs text-amber-600",children:"Chưa chọn hình ảnh đang được hỏi"})]}),r("div",{className:"space-y-2",children:[r(c,{className:"flex items-center gap-2 text-base font-semibold text-gray-800",children:[e(N,{className:"h-4 w-4 text-indigo-500"}),"Audio câu hỏi"]}),e(V,{id:"question-audio",label:"",accept:"audio/*",value:ae,onChange:e=>ee.setValue("audioUrl",e,{shouldValidate:!0,shouldDirty:!0}),maxSize:20,placeholder:"Tải lên file âm thanh (mp3, wav...)",autoUpload:!0,prefix:"questions/audio"}),ee.hasError("audioUrl")&&e("p",{className:"text-sm text-red-600",children:ee.getFieldError("audioUrl")}),oe&&e("audio",{controls:!0,className:"mt-2 w-full",src:oe,children:"Trình duyệt không hỗ trợ audio."})]}),R&&R.length>0&&e("div",{className:"flex items-start gap-3 rounded-xl border border-red-200 bg-red-50 p-4",children:r("div",{className:"text-sm",children:[e("p",{className:"font-semibold text-red-800",children:"Cần phải sửa các lỗi sau:"}),e("ul",{className:"mt-1 list-inside list-disc text-red-700",children:R.map((r,t)=>e("li",{children:r},t))})]})})]})]}),r(n,{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(d,{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 items-center justify-center rounded-lg bg-gradient-to-br from-green-500 to-emerald-600 shadow-sm",children:e(I,{className:"h-4 w-4 text-white"})}),e("div",{children:r(o,{className:"text-lg font-semibold text-gray-800",children:["Đáp án mong muốn (",ie.filter(e=>e.trim()).length,")"]})})]}),r(m,{variant:"outline",size:"sm",onClick:()=>{const e=ee.getValues("expectedAnswers");ee.setValue("expectedAnswers",[...e,""],{shouldValidate:!0,shouldDirty:!0})},className:"gap-2 border-green-200 text-green-600 hover:bg-green-50",children:[e(u,{className:"h-4 w-4"}),"Thêm đáp án"]})]})}),r(l,{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.5",children:[e(C,{className:"h-4 w-4 text-blue-500 mt-0.5 flex-shrink-0"}),r("div",{className:"text-xs text-blue-700 space-y-1",children:[e("p",{children:"Nhập các câu trả lời mong muốn. Có thể thêm nhiều đáp án nếu có nhiều cách trả lời đúng."}),r("p",{children:[e("span",{className:"font-semibold",children:"• Chấm thủ công:"})," Giáo viên dùng các đáp án này làm tham khảo để chấm điểm."]}),r("p",{children:[e("span",{className:"font-semibold",children:"• Chấm tự động (AI):"})," Hệ thống dùng AI so sánh giọng nói của học sinh với các đáp án để tự động chấm điểm."]})]})]}),r("div",{className:"space-y-3",children:[ie.map((t,a)=>r("div",{className:"flex items-start gap-3 group/answer",children:[e("div",{className:"flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-lg bg-green-100 text-green-700 font-semibold shadow-sm text-sm",children:a+1}),r("div",{className:"flex-1",children:[e(h,{value:t||"",onChange:e=>((e,r)=>{const t=[...ie];t[e]=r,ee.setValue("expectedAnswers",t,{shouldValidate:!0,shouldDirty:!0})})(a,e.target.value),placeholder:"Ví dụ: "+(0===a?"A kite":"It is a kite"),className:"border-gray-200 focus:border-green-300 focus:ring-green-200"}),ee.formState.errors?.expectedAnswers?.[a]?.message&&e("p",{className:"mt-1 text-sm text-red-500",children:ee.formState.errors.expectedAnswers[a]?.message})]}),ie.length>1&&e(m,{variant:"ghost",size:"sm",onClick:()=>(e=>{if(ie.length>1){const r=ie.filter((r,t)=>t!==e);ee.setValue("expectedAnswers",r,{shouldValidate:!0,shouldDirty:!0})}})(a),className:"text-red-400 hover:text-red-600 hover:bg-red-50 opacity-0 group-hover/answer:opacity-100 transition-opacity",children:e(p,{className:"h-4 w-4"})})]},a)),ee.formState.errors?.expectedAnswers&&!Array.isArray(ee.formState.errors.expectedAnswers)&&e("p",{className:"text-sm text-red-500",children:ee.formState.errors.expectedAnswers.message})]})]})]}),r(n,{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(d,{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(A,{className:"h-4 w-4 text-white"})}),r("div",{children:[r("div",{className:"flex items-center gap-2",children:[e(o,{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(l,{children:e(g,{value:ne,onChange:e=>ee.setValue("explanation",e.target.value,{shouldValidate:!0}),placeholder:"VD: Câu trả lời đúng là... vì...",rows:3,className:"min-h-[80px] border-gray-200 bg-white transition-all focus:border-amber-300 focus:ring-amber-200 resize-none"})})]})]})}
|
|
@@ -1,81 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
export const listenAndSpeakImageGroupRegistration = {
|
|
3
|
-
component: ListenAndSpeakImageGroupCreator,
|
|
4
|
-
transformInitialData: (apiData) => {
|
|
5
|
-
const { content, answer, questionGroup, explanation, points } = apiData;
|
|
6
|
-
let creatorData = {
|
|
7
|
-
audioUrl: '',
|
|
8
|
-
expectedAnswers: [''],
|
|
9
|
-
};
|
|
10
|
-
if (content && typeof content === 'object') {
|
|
11
|
-
creatorData = {
|
|
12
|
-
audioUrl: content.audioUrl || '',
|
|
13
|
-
activeImageId: content.activeImageId || '',
|
|
14
|
-
expectedAnswers: answer?.expectedAnswers || [],
|
|
15
|
-
groupImages: answer?.groupImages || content.groupImages || questionGroup?.payload?.images || [],
|
|
16
|
-
groupNumber: answer?.groupNumber || content.groupNumber || questionGroup?.groupNumber || 1,
|
|
17
|
-
explanation,
|
|
18
|
-
points: points || 1,
|
|
19
|
-
isBackup: content.isBackup || false,
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
else if (answer && typeof answer === 'object') {
|
|
23
|
-
creatorData = {
|
|
24
|
-
...answer,
|
|
25
|
-
audioUrl: answer.audioUrl || content?.audioUrl || '',
|
|
26
|
-
activeImageId: answer.activeImageId || content?.activeImageId || '',
|
|
27
|
-
groupImages: answer.groupImages || content?.groupImages || questionGroup?.payload?.images || [],
|
|
28
|
-
groupNumber: answer.groupNumber || content?.groupNumber || questionGroup?.groupNumber || 1,
|
|
29
|
-
explanation,
|
|
30
|
-
points: points || 1,
|
|
31
|
-
isBackup: content?.isBackup || false,
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
return creatorData;
|
|
35
|
-
},
|
|
36
|
-
getExtraProps: (ctx) => ({
|
|
37
|
-
onUnsavedChangesChange: ctx.onUnsavedChangesChange,
|
|
38
|
-
validationRef: ctx.validationRef,
|
|
39
|
-
groups: ctx.groups,
|
|
40
|
-
questionIndexInPart: ctx.questionIndexInPart,
|
|
41
|
-
partId: ctx.partId,
|
|
42
|
-
}),
|
|
43
|
-
wrapOnSave: (data, ctx) => ({
|
|
44
|
-
type: ctx.questionType,
|
|
45
|
-
content: {
|
|
46
|
-
...(typeof ctx.state.content === 'object' && ctx.state.content !== null ? ctx.state.content : {}),
|
|
47
|
-
audioUrl: data.audioUrl,
|
|
48
|
-
activeImageId: data.activeImageId,
|
|
49
|
-
groupImages: data.groupImages,
|
|
50
|
-
groupNumber: data.groupNumber,
|
|
51
|
-
isBackup: data.isBackup || false,
|
|
52
|
-
},
|
|
53
|
-
points: data?.points ?? ctx.state.points ?? 1,
|
|
54
|
-
level: ctx.state.level,
|
|
55
|
-
difficulty: ctx.state.difficulty,
|
|
56
|
-
skill: ctx.state.skill,
|
|
57
|
-
answer: {
|
|
58
|
-
expectedAnswers: data.expectedAnswers,
|
|
59
|
-
},
|
|
60
|
-
explanation: data?.explanation || '',
|
|
61
|
-
}),
|
|
62
|
-
wrapOnChange: (data, ctx) => ({
|
|
63
|
-
type: ctx.questionType,
|
|
64
|
-
content: {
|
|
65
|
-
...(typeof ctx.state.content === 'object' && ctx.state.content !== null ? ctx.state.content : {}),
|
|
66
|
-
audioUrl: data.audioUrl,
|
|
67
|
-
activeImageId: data.activeImageId,
|
|
68
|
-
groupImages: data.groupImages,
|
|
69
|
-
groupNumber: data.groupNumber,
|
|
70
|
-
isBackup: data.isBackup || false,
|
|
71
|
-
},
|
|
72
|
-
points: data?.points ?? ctx.state.points ?? 1,
|
|
73
|
-
level: ctx.state.level,
|
|
74
|
-
difficulty: ctx.state.difficulty,
|
|
75
|
-
skill: ctx.state.skill,
|
|
76
|
-
answer: {
|
|
77
|
-
expectedAnswers: data.expectedAnswers,
|
|
78
|
-
},
|
|
79
|
-
explanation: data?.explanation || '',
|
|
80
|
-
}),
|
|
81
|
-
};
|
|
1
|
+
import{ListenAndSpeakImageGroupCreator as e}from"./ListenAndSpeakImageGroupCreator";export const listenAndSpeakImageGroupRegistration={component:e,transformInitialData:e=>{const{content:t,answer:a,questionGroup:o,explanation:n,points:s}=e;let p={audioUrl:"",expectedAnswers:[""]};return t&&"object"==typeof t?p={audioUrl:t.audioUrl||"",activeImageId:t.activeImageId||"",expectedAnswers:a?.expectedAnswers||[],groupImages:a?.groupImages||t.groupImages||o?.payload?.images||[],groupNumber:a?.groupNumber||t.groupNumber||o?.groupNumber||1,explanation:n,points:s||1,isBackup:t.isBackup||!1}:a&&"object"==typeof a&&(p={...a,audioUrl:a.audioUrl||t?.audioUrl||"",activeImageId:a.activeImageId||t?.activeImageId||"",groupImages:a.groupImages||t?.groupImages||o?.payload?.images||[],groupNumber:a.groupNumber||t?.groupNumber||o?.groupNumber||1,explanation:n,points:s||1,isBackup:t?.isBackup||!1}),p},getExtraProps:e=>({onUnsavedChangesChange:e.onUnsavedChangesChange,validationRef:e.validationRef,groups:e.groups,questionIndexInPart:e.questionIndexInPart,partId:e.partId}),wrapOnSave:(e,t)=>({type:t.questionType,content:{..."object"==typeof t.state.content&&null!==t.state.content?t.state.content:{},audioUrl:e.audioUrl,activeImageId:e.activeImageId,groupImages:e.groupImages,groupNumber:e.groupNumber,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},explanation:e?.explanation||""}),wrapOnChange:(e,t)=>({type:t.questionType,content:{..."object"==typeof t.state.content&&null!==t.state.content?t.state.content:{},audioUrl:e.audioUrl,activeImageId:e.activeImageId,groupImages:e.groupImages,groupNumber:e.groupNumber,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},explanation:e?.explanation||""})};
|