@tinyweb_dev/oe-exam-sdk 0.1.1 → 0.1.3
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.d.ts +1 -1
- package/dist/api/exam-questions.js +1 -98
- package/dist/api/exam-questions.types.d.ts +45 -1
- 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/CompletionDialog.d.ts +8 -0
- package/dist/components/exams/CompletionDialog.js +1 -0
- package/dist/components/exams/CreateExamInfoForm.d.ts +4 -1
- package/dist/components/exams/CreateExamInfoForm.js +1 -35
- package/dist/components/exams/CreateExamPageContainer.d.ts +3 -0
- package/dist/components/exams/CreateExamPageContainer.js +1 -103
- package/dist/components/exams/ExamCreator.d.ts +9 -3
- package/dist/components/exams/ExamCreator.js +1 -89
- package/dist/components/exams/ExamPartTabs.d.ts +1 -1
- package/dist/components/exams/ExamPartTabs.js +1 -40
- package/dist/components/exams/ExamPreviewDialog.d.ts +20 -0
- package/dist/components/exams/ExamPreviewDialog.js +1 -0
- package/dist/components/exams/ExamQuestionGrid.d.ts +1 -1
- package/dist/components/exams/ExamQuestionGrid.js +1 -18
- package/dist/components/exams/ExamQuestionsPageContainer.d.ts +3 -0
- package/dist/components/exams/ExamQuestionsPageContainer.js +1 -90
- package/dist/components/exams/ExamViewDialog.d.ts +24 -0
- package/dist/components/exams/ExamViewDialog.js +1 -0
- package/dist/components/exams/PartAudioUploader.js +1 -9
- package/dist/components/exams/TemplateSelector.js +1 -66
- package/dist/components/exams/UnsavedChangesDialog.d.ts +8 -0
- package/dist/components/exams/UnsavedChangesDialog.js +1 -0
- package/dist/components/exams/api.d.ts +1 -1
- package/dist/components/exams/api.js +1 -3
- package/dist/components/exams/create-exam.texts.d.ts +58 -0
- package/dist/components/exams/create-exam.texts.js +1 -0
- package/dist/components/exams/create-exam.validation.js +1 -18
- package/dist/components/exams/exam-question-status.d.ts +9 -0
- package/dist/components/exams/exam-question-status.js +1 -0
- package/dist/components/exams/exam-template.utils.js +1 -18
- package/dist/components/exams/index.d.ts +11 -1
- 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.d.ts +3 -3
- 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.d.ts +3 -0
- 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-reverse-transform.d.ts +6 -0
- package/dist/shared/lib/utils/question-reverse-transform.js +1 -0
- package/dist/shared/lib/utils/question-transform-types.js +1 -1
- package/dist/shared/lib/utils/question-transform-validation.d.ts +6 -0
- package/dist/shared/lib/utils/question-transform-validation.js +1 -0
- package/dist/shared/lib/utils/question-transform.d.ts +6 -2
- 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 +12 -3
|
@@ -1,110 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
export const speakingConversationRegistration = {
|
|
3
|
-
component: SpeakingConversationCreator,
|
|
4
|
-
transformInitialData: (initialData) => {
|
|
5
|
-
const content = initialData?.content;
|
|
6
|
-
const answer = initialData?.answer || initialData?.correctAnswer || initialData?.correct_answer;
|
|
7
|
-
const groupPayload = initialData?.questionGroup?.payload || initialData?.groupPayload || {};
|
|
8
|
-
const points = initialData?.total_points
|
|
9
|
-
? parseFloat(initialData.total_points)
|
|
10
|
-
: initialData?.points ?? content?.points ?? answer?.points ?? 5;
|
|
11
|
-
const explanation = initialData?.explanation || content?.explanation || answer?.explanation || '';
|
|
12
|
-
const expectedAnswers = (() => {
|
|
13
|
-
const arr = answer?.expectedAnswers;
|
|
14
|
-
if (Array.isArray(arr) && arr.length > 0)
|
|
15
|
-
return arr;
|
|
16
|
-
return [''];
|
|
17
|
-
})();
|
|
18
|
-
return {
|
|
19
|
-
// Per-question content
|
|
20
|
-
responder: content?.responder ?? 'STUDENT',
|
|
21
|
-
interlocutorAudioUrl: content?.interlocutorAudioUrl ?? '',
|
|
22
|
-
partnerAudioUrl: content?.partnerAudioUrl ?? '',
|
|
23
|
-
questionText: content?.questionText ?? '',
|
|
24
|
-
isBackup: content?.isBackup ?? false,
|
|
25
|
-
// Expected answers
|
|
26
|
-
expectedAnswers,
|
|
27
|
-
// Explanation & points
|
|
28
|
-
explanation,
|
|
29
|
-
points,
|
|
30
|
-
// Group payload (shared across questions in group).
|
|
31
|
-
// Prefer groupPayload.images when non-empty; fall back to answer.images which is
|
|
32
|
-
// always written by wrapOnChange/wrapOnSave. This covers the case where the store
|
|
33
|
-
// retains a stale questionGroup.payload.images=[] from the initial API load before
|
|
34
|
-
// an image was first saved (Fix for phase image not persisting after save).
|
|
35
|
-
partnerName: groupPayload?.partnerName ?? '',
|
|
36
|
-
partnerAvatarUrl: groupPayload?.partnerAvatarUrl ?? '',
|
|
37
|
-
images: groupPayload?.images?.length > 0 ? groupPayload.images : (answer?.images ?? []),
|
|
38
|
-
};
|
|
39
|
-
},
|
|
40
|
-
getExtraProps: (ctx) => ({
|
|
41
|
-
onUnsavedChangesChange: ctx.onUnsavedChangesChange,
|
|
42
|
-
validationRef: ctx.validationRef,
|
|
43
|
-
groups: ctx.groups,
|
|
44
|
-
questionIndexInPart: ctx.questionIndexInPart,
|
|
45
|
-
partId: ctx.partId,
|
|
46
|
-
}),
|
|
47
|
-
wrapOnSave: (data, ctx) => {
|
|
48
|
-
const isPartner = data.responder === 'PARTNER';
|
|
49
|
-
// Filter empty expected answers
|
|
50
|
-
const expectedAnswers = (data.expectedAnswers || []).filter((s) => s && s.trim() !== '');
|
|
51
|
-
return {
|
|
52
|
-
type: ctx.questionType,
|
|
53
|
-
content: {
|
|
54
|
-
...(typeof ctx.state.content === 'object' && ctx.state.content !== null
|
|
55
|
-
? ctx.state.content
|
|
56
|
-
: {}),
|
|
57
|
-
responder: data.responder,
|
|
58
|
-
interlocutorAudioUrl: data.interlocutorAudioUrl || undefined,
|
|
59
|
-
partnerAudioUrl: data.partnerAudioUrl || undefined,
|
|
60
|
-
questionText: data.questionText || undefined,
|
|
61
|
-
isBackup: data.isBackup || false,
|
|
62
|
-
},
|
|
63
|
-
// CRITICAL: auto-set is_example and points for PARTNER questions
|
|
64
|
-
is_example: isPartner ? true : false,
|
|
65
|
-
points: isPartner ? 0 : (data.points ?? ctx.state.points ?? 5),
|
|
66
|
-
level: ctx.state.level,
|
|
67
|
-
difficulty: ctx.state.difficulty,
|
|
68
|
-
skill: ctx.state.skill,
|
|
69
|
-
answer: {
|
|
70
|
-
expectedAnswers,
|
|
71
|
-
// Group data stored in answer so transform handler can access it
|
|
72
|
-
partnerName: data.partnerName || undefined,
|
|
73
|
-
partnerAvatarUrl: data.partnerAvatarUrl || undefined,
|
|
74
|
-
images: (data.images || []).filter((img) => img.url && img.url.trim() !== ''),
|
|
75
|
-
groupNumber: data.groupNumber,
|
|
76
|
-
},
|
|
77
|
-
explanation: data.explanation || '',
|
|
78
|
-
};
|
|
79
|
-
},
|
|
80
|
-
wrapOnChange: (data, ctx) => {
|
|
81
|
-
const isPartner = data.responder === 'PARTNER';
|
|
82
|
-
const expectedAnswers = data.expectedAnswers || [''];
|
|
83
|
-
return {
|
|
84
|
-
type: ctx.questionType,
|
|
85
|
-
content: {
|
|
86
|
-
...(typeof ctx.state.content === 'object' && ctx.state.content !== null
|
|
87
|
-
? ctx.state.content
|
|
88
|
-
: {}),
|
|
89
|
-
responder: data.responder,
|
|
90
|
-
interlocutorAudioUrl: data.interlocutorAudioUrl || undefined,
|
|
91
|
-
partnerAudioUrl: data.partnerAudioUrl || undefined,
|
|
92
|
-
questionText: data.questionText || undefined,
|
|
93
|
-
isBackup: data.isBackup || false,
|
|
94
|
-
},
|
|
95
|
-
is_example: isPartner ? true : false,
|
|
96
|
-
points: isPartner ? 0 : (data.points ?? ctx.state.points ?? 5),
|
|
97
|
-
level: ctx.state.level,
|
|
98
|
-
difficulty: ctx.state.difficulty,
|
|
99
|
-
skill: ctx.state.skill,
|
|
100
|
-
answer: {
|
|
101
|
-
expectedAnswers,
|
|
102
|
-
partnerName: data.partnerName || undefined,
|
|
103
|
-
partnerAvatarUrl: data.partnerAvatarUrl || undefined,
|
|
104
|
-
images: (data.images || []).filter((img) => img.url && img.url.trim() !== ''),
|
|
105
|
-
groupNumber: data.groupNumber,
|
|
106
|
-
},
|
|
107
|
-
explanation: data.explanation || '',
|
|
108
|
-
};
|
|
109
|
-
},
|
|
110
|
-
};
|
|
1
|
+
import{SpeakingConversationCreator as e}from"./SpeakingConversationCreator";export const speakingConversationRegistration={component:e,transformInitialData:e=>{const t=e?.content,r=e?.answer||e?.correctAnswer||e?.correct_answer,n=e?.questionGroup?.payload||e?.groupPayload||{},a=e?.total_points?parseFloat(e.total_points):e?.points??t?.points??r?.points??5,o=e?.explanation||t?.explanation||r?.explanation||"",i=(()=>{const e=r?.expectedAnswers;return Array.isArray(e)&&e.length>0?e:[""]})();return{responder:t?.responder??"STUDENT",interlocutorAudioUrl:t?.interlocutorAudioUrl??"",partnerAudioUrl:t?.partnerAudioUrl??"",questionText:t?.questionText??"",isBackup:t?.isBackup??!1,expectedAnswers:i,explanation:o,points:a,partnerName:n?.partnerName??"",partnerAvatarUrl:n?.partnerAvatarUrl??"",images:n?.images?.length>0?n.images:r?.images??[]}},getExtraProps:e=>({onUnsavedChangesChange:e.onUnsavedChangesChange,validationRef:e.validationRef,groups:e.groups,questionIndexInPart:e.questionIndexInPart,partId:e.partId}),wrapOnSave:(e,t)=>{const r="PARTNER"===e.responder,n=(e.expectedAnswers||[]).filter(e=>e&&""!==e.trim());return{type:t.questionType,content:{..."object"==typeof t.state.content&&null!==t.state.content?t.state.content:{},responder:e.responder,interlocutorAudioUrl:e.interlocutorAudioUrl||void 0,partnerAudioUrl:e.partnerAudioUrl||void 0,questionText:e.questionText||void 0,isBackup:e.isBackup||!1},is_example:!!r,points:r?0:e.points??t.state.points??5,level:t.state.level,difficulty:t.state.difficulty,skill:t.state.skill,answer:{expectedAnswers:n,partnerName:e.partnerName||void 0,partnerAvatarUrl:e.partnerAvatarUrl||void 0,images:(e.images||[]).filter(e=>e.url&&""!==e.url.trim()),groupNumber:e.groupNumber},explanation:e.explanation||""}},wrapOnChange:(e,t)=>{const r="PARTNER"===e.responder,n=e.expectedAnswers||[""];return{type:t.questionType,content:{..."object"==typeof t.state.content&&null!==t.state.content?t.state.content:{},responder:e.responder,interlocutorAudioUrl:e.interlocutorAudioUrl||void 0,partnerAudioUrl:e.partnerAudioUrl||void 0,questionText:e.questionText||void 0,isBackup:e.isBackup||!1},is_example:!!r,points:r?0:e.points??t.state.points??5,level:t.state.level,difficulty:t.state.difficulty,skill:t.state.skill,answer:{expectedAnswers:n,partnerName:e.partnerName||void 0,partnerAvatarUrl:e.partnerAvatarUrl||void 0,images:(e.images||[]).filter(e=>e.url&&""!==e.url.trim()),groupNumber:e.groupNumber},explanation:e.explanation||""}}};
|
|
@@ -1,34 +1 @@
|
|
|
1
|
-
export const transformSpeakingConversation = (
|
|
2
|
-
const contentData = question.content;
|
|
3
|
-
const answerData = question.answer;
|
|
4
|
-
const apiContent = {
|
|
5
|
-
responder: contentData?.responder || 'STUDENT',
|
|
6
|
-
...(contentData?.interlocutorAudioUrl && { interlocutorAudioUrl: contentData.interlocutorAudioUrl }),
|
|
7
|
-
...(contentData?.partnerAudioUrl && { partnerAudioUrl: contentData.partnerAudioUrl }),
|
|
8
|
-
...(contentData?.questionText && { questionText: contentData.questionText }),
|
|
9
|
-
isBackup: contentData?.isBackup || false,
|
|
10
|
-
};
|
|
11
|
-
const expectedAnswers = (() => {
|
|
12
|
-
const arr = answerData?.expectedAnswers;
|
|
13
|
-
if (Array.isArray(arr))
|
|
14
|
-
return arr.filter((s) => s && s.trim() !== '');
|
|
15
|
-
return [];
|
|
16
|
-
})();
|
|
17
|
-
const apiCorrectAnswer = {
|
|
18
|
-
expectedAnswers,
|
|
19
|
-
};
|
|
20
|
-
const isPartner = contentData?.responder === 'PARTNER';
|
|
21
|
-
return {
|
|
22
|
-
apiContent,
|
|
23
|
-
apiCorrectAnswer,
|
|
24
|
-
isExample: isPartner,
|
|
25
|
-
...(isPartner && { totalPoints: 0 }),
|
|
26
|
-
groupNumber: answerData?.groupNumber || 1,
|
|
27
|
-
// Always emit group data — SPEAKING_CONVERSATION questions are always grouped
|
|
28
|
-
conversationGroupData: {
|
|
29
|
-
partnerName: answerData?.partnerName,
|
|
30
|
-
partnerAvatarUrl: answerData?.partnerAvatarUrl,
|
|
31
|
-
images: answerData?.images || [],
|
|
32
|
-
},
|
|
33
|
-
};
|
|
34
|
-
};
|
|
1
|
+
export const transformSpeakingConversation=r=>{const e=r.content,t=r.answer,n={responder:e?.responder||"STUDENT",...e?.interlocutorAudioUrl&&{interlocutorAudioUrl:e.interlocutorAudioUrl},...e?.partnerAudioUrl&&{partnerAudioUrl:e.partnerAudioUrl},...e?.questionText&&{questionText:e.questionText},isBackup:e?.isBackup||!1},o={expectedAnswers:(()=>{const r=t?.expectedAnswers;return Array.isArray(r)?r.filter(r=>r&&""!==r.trim()):[]})()},a="PARTNER"===e?.responder;return{apiContent:n,apiCorrectAnswer:o,isExample:a,...a&&{totalPoints:0},groupNumber:t?.groupNumber||1,conversationGroupData:{partnerName:t?.partnerName,partnerAvatarUrl:t?.partnerAvatarUrl,images:t?.images||[]}}};
|
|
@@ -1,74 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useState, useEffect, useRef, useCallback } from 'react';
|
|
4
|
-
import { Card, CardContent, CardHeader, CardTitle } from '../../../../components/ui/card';
|
|
5
|
-
import { Input } from '../../../../components/ui/input';
|
|
6
|
-
import { Label } from '../../../../components/ui/label';
|
|
7
|
-
import { CueCardEditor } from '../../../../components/CueCardEditor';
|
|
8
|
-
import { CreatorGuide } from '../../components/CreatorGuide';
|
|
9
|
-
import { CreditCard } from 'lucide-react';
|
|
10
|
-
export function SpeakingCueCardCreator({ initialData, onChange, onUnsavedChangesChange, externalErrors, validationRef, }) {
|
|
11
|
-
const [htmlContent, setHtmlContent] = useState(initialData?.cueCardHtml || '');
|
|
12
|
-
const [points, setPoints] = useState(initialData?.points ?? 5);
|
|
13
|
-
const isInitializingRef = useRef(true);
|
|
14
|
-
const previousInitialDataRef = useRef(initialData);
|
|
15
|
-
const previousPayloadRef = useRef(null);
|
|
16
|
-
const onChangeRef = useRef(onChange);
|
|
17
|
-
useEffect(() => {
|
|
18
|
-
onChangeRef.current = onChange;
|
|
19
|
-
}, [onChange]);
|
|
20
|
-
// Set initializing to false after first mount
|
|
21
|
-
useEffect(() => {
|
|
22
|
-
const timer = setTimeout(() => {
|
|
23
|
-
isInitializingRef.current = false;
|
|
24
|
-
}, 150);
|
|
25
|
-
return () => clearTimeout(timer);
|
|
26
|
-
}, []);
|
|
27
|
-
// Reset form when initialData changes externally
|
|
28
|
-
useEffect(() => {
|
|
29
|
-
const initialDataChanged = JSON.stringify(previousInitialDataRef.current) !== JSON.stringify(initialData);
|
|
30
|
-
if (!initialDataChanged)
|
|
31
|
-
return;
|
|
32
|
-
previousInitialDataRef.current = initialData;
|
|
33
|
-
isInitializingRef.current = true;
|
|
34
|
-
setHtmlContent(initialData?.cueCardHtml || '');
|
|
35
|
-
setPoints(initialData?.points ?? 5);
|
|
36
|
-
setTimeout(() => {
|
|
37
|
-
isInitializingRef.current = false;
|
|
38
|
-
}, 150);
|
|
39
|
-
}, [initialData]);
|
|
40
|
-
const buildPayload = useCallback(() => ({
|
|
41
|
-
cueCardHtml: htmlContent,
|
|
42
|
-
points,
|
|
43
|
-
}), [htmlContent, points]);
|
|
44
|
-
useEffect(() => {
|
|
45
|
-
if (isInitializingRef.current)
|
|
46
|
-
return;
|
|
47
|
-
const payload = buildPayload();
|
|
48
|
-
const payloadString = JSON.stringify(payload);
|
|
49
|
-
if (previousPayloadRef.current !== payloadString) {
|
|
50
|
-
previousPayloadRef.current = payloadString;
|
|
51
|
-
onChangeRef.current?.(payload);
|
|
52
|
-
onUnsavedChangesChange?.(true);
|
|
53
|
-
}
|
|
54
|
-
}, [buildPayload, onUnsavedChangesChange]);
|
|
55
|
-
// Handle validation from parent
|
|
56
|
-
useEffect(() => {
|
|
57
|
-
if (validationRef) {
|
|
58
|
-
validationRef.current = {
|
|
59
|
-
triggerValidation: async () => {
|
|
60
|
-
if (!htmlContent || htmlContent.trim() === '') {
|
|
61
|
-
return false;
|
|
62
|
-
}
|
|
63
|
-
return true;
|
|
64
|
-
},
|
|
65
|
-
getFormData: () => buildPayload(),
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
}, [validationRef, htmlContent, points, buildPayload]);
|
|
69
|
-
return (_jsxs("div", { className: "space-y-6", children: [_jsx(CreatorGuide, { colorScheme: "purple", steps: [
|
|
70
|
-
{ text: 'Nhập nội dung Cue Card bao gồm chủ đề và các gợi ý (bullet points)', highlight: true },
|
|
71
|
-
], tips: [
|
|
72
|
-
'Cue Card nên có 1 chủ đề chính và 3-4 gợi ý (bullet points) theo format IELTS Part 2.',
|
|
73
|
-
] }), _jsxs(Card, { className: "overflow-hidden border border-slate-200 bg-white shadow-sm", children: [_jsx(CardHeader, { className: "border-b border-slate-100 bg-slate-50/50 pb-4", 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-purple-100 text-purple-700", children: _jsx(CreditCard, { className: "h-4 w-4" }) }), _jsx(CardTitle, { className: "text-lg font-semibold text-slate-800", children: "C\u00E2u h\u1ECFi Th\u1EBB g\u1EE3i \u00FD (Cue Card)" })] }) }), _jsxs(CardContent, { className: "p-0", children: [_jsxs("div", { className: "p-6 space-y-8", children: [_jsxs("div", { className: "w-full xl:w-1/3 space-y-1.5 animate-in fade-in zoom-in-95 duration-200", children: [_jsxs(Label, { htmlFor: "points", className: "text-sm font-semibold text-slate-700", children: ["\u0110i\u1EC3m s\u1ED1 ", _jsx("span", { className: "text-red-500", children: "*" })] }), _jsx(Input, { id: "points", type: "number", step: "1", min: "1", value: points, onChange: (e) => setPoints(parseInt(e.target.value) || 0), className: "w-24 border-slate-200 focus:border-blue-400 focus:ring-blue-100" })] }), _jsx("hr", { className: "border-slate-100" }), _jsx("div", { className: "space-y-3", children: _jsx(CueCardEditor, { value: htmlContent, onChange: setHtmlContent, label: _jsxs(Label, { className: "text-sm font-semibold text-slate-700", children: ["N\u1ED9i dung Cue Card ", _jsx("span", { className: "text-red-500", children: "*" })] }) }) })] }), externalErrors && externalErrors.length > 0 && (_jsx("div", { className: "border-t border-red-100 bg-red-50 p-6", children: _jsx("div", { className: "flex items-start gap-3", 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))) })] }) }) }))] })] })] }));
|
|
74
|
-
}
|
|
1
|
+
"use client";import{jsx as e,jsxs as r}from"react/jsx-runtime";import{useState as t,useEffect as s,useRef as a,useCallback as i}from"react";import{Card as n,CardContent as l,CardHeader as c,CardTitle as o}from"../../../../components/ui/card";import{Input as d}from"../../../../components/ui/input";import{Label as m}from"../../../../components/ui/label";import{CueCardEditor as u}from"../../../../components/CueCardEditor";import{CreatorGuide as p}from"../../components/CreatorGuide";import{CreditCard as h}from"lucide-react";export function SpeakingCueCardCreator({initialData:g,onChange:f,onUnsavedChangesChange:b,externalErrors:N,validationRef:C}){const[x,v]=t(g?.cueCardHtml||""),[y,w]=t(g?.points??5),S=a(!0),T=a(g),j=a(null),E=a(f);s(()=>{E.current=f},[f]),s(()=>{const e=setTimeout(()=>{S.current=!1},150);return()=>clearTimeout(e)},[]),s(()=>{JSON.stringify(T.current)!==JSON.stringify(g)&&(T.current=g,S.current=!0,v(g?.cueCardHtml||""),w(g?.points??5),setTimeout(()=>{S.current=!1},150))},[g]);const H=i(()=>({cueCardHtml:x,points:y}),[x,y]);return s(()=>{if(S.current)return;const e=H(),r=JSON.stringify(e);j.current!==r&&(j.current=r,E.current?.(e),b?.(!0))},[H,b]),s(()=>{C&&(C.current={triggerValidation:async()=>!(!x||""===x.trim()),getFormData:()=>H()})},[C,x,y,H]),r("div",{className:"space-y-6",children:[e(p,{colorScheme:"purple",steps:[{text:"Nhập nội dung Cue Card bao gồm chủ đề và các gợi ý (bullet points)",highlight:!0}],tips:["Cue Card nên có 1 chủ đề chính và 3-4 gợi ý (bullet points) theo format IELTS Part 2."]}),r(n,{className:"overflow-hidden border border-slate-200 bg-white shadow-sm",children:[e(c,{className:"border-b border-slate-100 bg-slate-50/50 pb-4",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-purple-100 text-purple-700",children:e(h,{className:"h-4 w-4"})}),e(o,{className:"text-lg font-semibold text-slate-800",children:"Câu hỏi Thẻ gợi ý (Cue Card)"})]})}),r(l,{className:"p-0",children:[r("div",{className:"p-6 space-y-8",children:[r("div",{className:"w-full xl:w-1/3 space-y-1.5 animate-in fade-in zoom-in-95 duration-200",children:[r(m,{htmlFor:"points",className:"text-sm font-semibold text-slate-700",children:["Điểm số ",e("span",{className:"text-red-500",children:"*"})]}),e(d,{id:"points",type:"number",step:"1",min:"1",value:y,onChange:e=>w(parseInt(e.target.value)||0),className:"w-24 border-slate-200 focus:border-blue-400 focus:ring-blue-100"})]}),e("hr",{className:"border-slate-100"}),e("div",{className:"space-y-3",children:e(u,{value:x,onChange:v,label:r(m,{className:"text-sm font-semibold text-slate-700",children:["Nội dung Cue Card ",e("span",{className:"text-red-500",children:"*"})]})})})]}),N&&N.length>0&&e("div",{className:"border-t border-red-100 bg-red-50 p-6",children:e("div",{className:"flex items-start gap-3",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:N.map((r,t)=>e("li",{children:r},t))})]})})})]})]})]})}
|
|
@@ -1,52 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
export const speakingCueCardRegistration = {
|
|
3
|
-
component: SpeakingCueCardCreator,
|
|
4
|
-
transformInitialData: (initialData) => {
|
|
5
|
-
const creatorData = initialData?.answer || {};
|
|
6
|
-
const content = initialData?.content;
|
|
7
|
-
const points = initialData?.total_points
|
|
8
|
-
? parseFloat(initialData.total_points)
|
|
9
|
-
: (initialData?.points || content?.points || creatorData?.points || 5);
|
|
10
|
-
return {
|
|
11
|
-
...creatorData,
|
|
12
|
-
cueCardHtml: content?.cueCardHtml || '',
|
|
13
|
-
points,
|
|
14
|
-
};
|
|
15
|
-
},
|
|
16
|
-
getExtraProps: (ctx) => ({
|
|
17
|
-
validationRef: ctx.validationRef,
|
|
18
|
-
onUnsavedChangesChange: ctx.onUnsavedChangesChange,
|
|
19
|
-
}),
|
|
20
|
-
wrapOnSave: (data, ctx) => {
|
|
21
|
-
const existingContent = typeof ctx.state.content === 'object' && ctx.state.content !== null ? ctx.state.content : {};
|
|
22
|
-
return {
|
|
23
|
-
type: ctx.questionType,
|
|
24
|
-
content: {
|
|
25
|
-
...existingContent,
|
|
26
|
-
cueCardHtml: data.cueCardHtml,
|
|
27
|
-
},
|
|
28
|
-
points: data.points ?? ctx.state.points ?? 5,
|
|
29
|
-
level: ctx.state.level,
|
|
30
|
-
difficulty: ctx.state.difficulty,
|
|
31
|
-
skill: ctx.state.skill,
|
|
32
|
-
answer: {},
|
|
33
|
-
explanation: '',
|
|
34
|
-
};
|
|
35
|
-
},
|
|
36
|
-
wrapOnChange: (data, ctx) => {
|
|
37
|
-
const existingContent = typeof ctx.state.content === 'object' && ctx.state.content !== null ? ctx.state.content : {};
|
|
38
|
-
return {
|
|
39
|
-
type: ctx.questionType,
|
|
40
|
-
content: {
|
|
41
|
-
...existingContent,
|
|
42
|
-
cueCardHtml: data.cueCardHtml,
|
|
43
|
-
},
|
|
44
|
-
points: data.points ?? ctx.state.points ?? 5,
|
|
45
|
-
level: ctx.state.level,
|
|
46
|
-
difficulty: ctx.state.difficulty,
|
|
47
|
-
skill: ctx.state.skill,
|
|
48
|
-
answer: {},
|
|
49
|
-
explanation: '',
|
|
50
|
-
};
|
|
51
|
-
},
|
|
52
|
-
};
|
|
1
|
+
import{SpeakingCueCardCreator as t}from"./SpeakingCueCardCreator";export const speakingCueCardRegistration={component:t,transformInitialData:t=>{const e=t?.answer||{},n=t?.content,a=t?.total_points?parseFloat(t.total_points):t?.points||n?.points||e?.points||5;return{...e,cueCardHtml:n?.cueCardHtml||"",points:a}},getExtraProps:t=>({validationRef:t.validationRef,onUnsavedChangesChange:t.onUnsavedChangesChange}),wrapOnSave:(t,e)=>{const n="object"==typeof e.state.content&&null!==e.state.content?e.state.content:{};return{type:e.questionType,content:{...n,cueCardHtml:t.cueCardHtml},points:t.points??e.state.points??5,level:e.state.level,difficulty:e.state.difficulty,skill:e.state.skill,answer:{},explanation:""}},wrapOnChange:(t,e)=>{const n="object"==typeof e.state.content&&null!==e.state.content?e.state.content:{};return{type:e.questionType,content:{...n,cueCardHtml:t.cueCardHtml},points:t.points??e.state.points??5,level:e.state.level,difficulty:e.state.difficulty,skill:e.state.skill,answer:{},explanation:""}}};
|
|
@@ -1,12 +1 @@
|
|
|
1
|
-
export const transformSpeakingCueCard
|
|
2
|
-
const contentData = question.content;
|
|
3
|
-
const apiContent = {
|
|
4
|
-
cueCardHtml: contentData?.cueCardHtml || '',
|
|
5
|
-
};
|
|
6
|
-
// SPEAKING_CUE_CARD has no correct answer — it's evaluated by examiner
|
|
7
|
-
const apiCorrectAnswer = {};
|
|
8
|
-
return {
|
|
9
|
-
apiContent,
|
|
10
|
-
apiCorrectAnswer,
|
|
11
|
-
};
|
|
12
|
-
};
|
|
1
|
+
export const transformSpeakingCueCard=t=>{const r=t.content;return{apiContent:{cueCardHtml:r?.cueCardHtml||""},apiCorrectAnswer:{}}};
|
package/dist/components/questions/types/speaking-describe-image/SpeakingDescribeImageClient.js
CHANGED
|
@@ -1,10 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { Mic, CheckCircle2, Info, ImageIcon, FileQuestion } from 'lucide-react';
|
|
4
|
-
import { usePresignedFileUrl } from '../../../../shared/lib/hooks';
|
|
5
|
-
export function SpeakingDescribeImageClient({ questionData, isReviewMode = false, }) {
|
|
6
|
-
const { previewUrl: imageUrl } = usePresignedFileUrl(questionData.imageUrl || '');
|
|
7
|
-
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(Mic, { className: "h-4 w-4" }) }), _jsx("span", { className: "text-sm font-medium text-gray-700", children: "M\u00F4 t\u1EA3 h\u00ECnh \u1EA3nh (Speaking)" })] }) }), _jsxs("div", { className: "p-5 space-y-5", children: [questionData.question && (_jsxs("div", { className: "flex gap-3", children: [_jsx(FileQuestion, { className: "h-5 w-5 text-indigo-500 shrink-0 mt-0.5" }), _jsx("div", { className: "text-base font-semibold text-gray-800", children: questionData.question })] })), imageUrl ? (_jsx("div", { className: "w-full max-w-3xl mx-auto rounded-xl overflow-hidden shadow-sm border border-gray-200 bg-gray-50 flex justify-center", children: _jsx("img", { src: imageUrl, alt: "Scene", className: "w-[80%] max-h-[400px] object-contain flex-shrink-0" }) })) : (_jsxs("div", { className: "flex flex-col items-center justify-center py-10 rounded-xl border border-dashed border-gray-300 bg-gray-50", children: [_jsx(ImageIcon, { className: "h-8 w-8 text-gray-400 mb-2" }), _jsx("span", { className: "text-sm text-gray-500", children: "Ch\u01B0a t\u1EA3i h\u00ECnh \u1EA3nh m\u00F4 t\u1EA3" })] })), _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: isReviewMode && questionData.audioUrl
|
|
8
|
-
? "Audio ghi âm của học sinh (đã lưu)"
|
|
9
|
-
: "Học sinh sẽ nhìn hình ảnh và nhấn nút micro để mô tả" })] }), isReviewMode && questionData.expectedAnswers && questionData.expectedAnswers.length > 0 && (_jsxs("div", { className: "rounded-xl border border-green-200 bg-green-50/80 p-4 mt-6", 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 t\u1EF1 \u0111\u1ED9ng (Tham chi\u1EBFu AI)" })] }), _jsx("div", { className: "space-y-2", children: questionData.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 && questionData.explanation && (_jsxs("div", { className: "rounded-xl border border-amber-200 bg-amber-50 p-4 mt-2", children: [_jsxs("div", { className: "flex items-center gap-2 mb-2", children: [_jsx(Info, { className: "h-4 w-4 text-amber-600" }), _jsx("p", { className: "text-sm font-semibold text-amber-800", children: "Gi\u1EA3i th\u00EDch gi\u00E1o vi\u00EAn" })] }), _jsx("p", { className: "text-sm text-amber-700 leading-relaxed whitespace-pre-wrap", children: questionData.explanation })] }))] })] })] }));
|
|
10
|
-
}
|
|
1
|
+
"use client";import{jsx as e,jsxs as r}from"react/jsx-runtime";import{Mic as s,CheckCircle2 as a,Info as t,ImageIcon as n,FileQuestion as i}from"lucide-react";import{usePresignedFileUrl as d}from"../../../../shared/lib/hooks";export function SpeakingDescribeImageClient({questionData:l,isReviewMode:c=!1}){const{previewUrl:m}=d(l.imageUrl||"");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:"Mô tả hình ảnh (Speaking)"})]})}),r("div",{className:"p-5 space-y-5",children:[l.question&&r("div",{className:"flex gap-3",children:[e(i,{className:"h-5 w-5 text-indigo-500 shrink-0 mt-0.5"}),e("div",{className:"text-base font-semibold text-gray-800",children:l.question})]}),m?e("div",{className:"w-full max-w-3xl mx-auto rounded-xl overflow-hidden shadow-sm border border-gray-200 bg-gray-50 flex justify-center",children:e("img",{src:m,alt:"Scene",className:"w-[80%] max-h-[400px] object-contain flex-shrink-0"})}):r("div",{className:"flex flex-col items-center justify-center py-10 rounded-xl border border-dashed border-gray-300 bg-gray-50",children:[e(n,{className:"h-8 w-8 text-gray-400 mb-2"}),e("span",{className:"text-sm text-gray-500",children:"Chưa tải hình ảnh mô tả"})]}),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(s,{className:"w-4 h-4 text-indigo-400"})}),e("p",{className:"text-xs text-gray-400",children:c&&l.audioUrl?"Audio ghi âm của học sinh (đã lưu)":"Học sinh sẽ nhìn hình ảnh và nhấn nút micro để mô tả"})]}),c&&l.expectedAnswers&&l.expectedAnswers.length>0&&r("div",{className:"rounded-xl border border-green-200 bg-green-50/80 p-4 mt-6",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 tự động (Tham chiếu AI)"})]}),e("div",{className:"space-y-2",children:l.expectedAnswers.map((s,a)=>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:a+1}),e("span",{className:"text-sm text-green-800",children:s})]},a))})]}),c&&l.explanation&&r("div",{className:"rounded-xl border border-amber-200 bg-amber-50 p-4 mt-2",children:[r("div",{className:"flex items-center gap-2 mb-2",children:[e(t,{className:"h-4 w-4 text-amber-600"}),e("p",{className:"text-sm font-semibold text-amber-800",children:"Giải thích giáo viên"})]}),e("p",{className:"text-sm text-amber-700 leading-relaxed whitespace-pre-wrap",children:l.explanation})]})]})]})]})}
|
package/dist/components/questions/types/speaking-describe-image/SpeakingDescribeImageCreator.js
CHANGED
|
@@ -1,161 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useRef, useEffect } from 'react';
|
|
4
|
-
import { Button } from '../../../../components/ui/button';
|
|
5
|
-
import { Label } from '../../../../components/ui/label';
|
|
6
|
-
import { Input } from '../../../../components/ui/input';
|
|
7
|
-
import { Textarea } from '../../../../components/ui/textarea';
|
|
8
|
-
import { PointsInput } from '../../../../components/ui/points-input';
|
|
9
|
-
import { Card, CardContent, CardHeader, CardTitle } from '../../../../components/ui/card';
|
|
10
|
-
import { useDebouncedCallback, usePresignedFileUrl } from '../../../../shared/lib/hooks';
|
|
11
|
-
import { useReactHookForm } from '../../../../shared/lib/hooks/useReactHookForm';
|
|
12
|
-
import { Plus, Trash2, ImageIcon, MessageSquareText, BookOpen } from 'lucide-react';
|
|
13
|
-
import { FileUpload } from '../../../../components/ui/file-upload';
|
|
14
|
-
import { CreatorGuide } from '../../components/CreatorGuide';
|
|
15
|
-
import { speakingDescribeImageFormSchema, } from '../../../../shared/lib/validation/speaking-describe-image.validation';
|
|
16
|
-
export function SpeakingDescribeImageCreator({ initialData, onSave, onChange, externalErrors, validationRef, }) {
|
|
17
|
-
const apiData = initialData;
|
|
18
|
-
const form = useReactHookForm({
|
|
19
|
-
defaultValues: {
|
|
20
|
-
question: apiData?.question || apiData?.content?.question || '',
|
|
21
|
-
imageUrl: apiData?.imageUrl || apiData?.content?.imageUrl || '',
|
|
22
|
-
expectedAnswers: (apiData?.expectedAnswers || apiData?.correctAnswer?.expectedAnswers)?.length > 0
|
|
23
|
-
? (apiData?.expectedAnswers || apiData?.correctAnswer?.expectedAnswers)
|
|
24
|
-
: [''],
|
|
25
|
-
explanation: apiData?.explanation || '',
|
|
26
|
-
points: apiData?.points ?? 5,
|
|
27
|
-
},
|
|
28
|
-
zodSchema: speakingDescribeImageFormSchema,
|
|
29
|
-
mode: 'onBlur',
|
|
30
|
-
});
|
|
31
|
-
const previousInitialDataRef = useRef(initialData);
|
|
32
|
-
const isInitializingRef = useRef(false);
|
|
33
|
-
const onChangeRef = useRef(onChange);
|
|
34
|
-
const previousPayloadRef = useRef('');
|
|
35
|
-
useEffect(() => {
|
|
36
|
-
onChangeRef.current = onChange;
|
|
37
|
-
}, [onChange]);
|
|
38
|
-
const debouncedOnChange = useDebouncedCallback((data) => {
|
|
39
|
-
onChangeRef.current?.(data);
|
|
40
|
-
}, 300);
|
|
41
|
-
const watchedQuestion = form.watch('question');
|
|
42
|
-
const watchedImageUrl = form.watch('imageUrl');
|
|
43
|
-
const watchedExpectedAnswers = form.watch('expectedAnswers');
|
|
44
|
-
const watchedExplanation = form.watch('explanation');
|
|
45
|
-
const watchedPoints = form.watch('points');
|
|
46
|
-
// Preview uploaded image
|
|
47
|
-
const { previewUrl: imagePreviewUrl } = usePresignedFileUrl(watchedImageUrl || '');
|
|
48
|
-
useEffect(() => {
|
|
49
|
-
if (validationRef) {
|
|
50
|
-
validationRef.current = {
|
|
51
|
-
triggerValidation: async () => {
|
|
52
|
-
return await form.trigger();
|
|
53
|
-
},
|
|
54
|
-
getFormData: () => ({
|
|
55
|
-
question: watchedQuestion,
|
|
56
|
-
imageUrl: watchedImageUrl,
|
|
57
|
-
expectedAnswers: watchedExpectedAnswers,
|
|
58
|
-
explanation: watchedExplanation,
|
|
59
|
-
points: watchedPoints,
|
|
60
|
-
}),
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
}, [validationRef, form, watchedQuestion, watchedImageUrl, watchedExpectedAnswers, watchedExplanation, watchedPoints]);
|
|
64
|
-
useEffect(() => {
|
|
65
|
-
const apiD = initialData;
|
|
66
|
-
const normalizedInitialData = {
|
|
67
|
-
question: apiD?.question || apiD?.content?.question || '',
|
|
68
|
-
imageUrl: apiD?.imageUrl || apiD?.content?.imageUrl || '',
|
|
69
|
-
expectedAnswers: (apiD?.expectedAnswers || apiD?.correctAnswer?.expectedAnswers)?.length > 0
|
|
70
|
-
? (apiD?.expectedAnswers || apiD?.correctAnswer?.expectedAnswers)
|
|
71
|
-
: [''],
|
|
72
|
-
explanation: apiD?.explanation || '',
|
|
73
|
-
points: apiD?.points ?? 5,
|
|
74
|
-
};
|
|
75
|
-
const initialDataChanged = JSON.stringify(previousInitialDataRef.current) !== JSON.stringify(initialData);
|
|
76
|
-
if (!initialDataChanged) {
|
|
77
|
-
return;
|
|
78
|
-
}
|
|
79
|
-
previousInitialDataRef.current = initialData;
|
|
80
|
-
const currentFormData = {
|
|
81
|
-
question: watchedQuestion,
|
|
82
|
-
imageUrl: watchedImageUrl,
|
|
83
|
-
expectedAnswers: watchedExpectedAnswers,
|
|
84
|
-
explanation: watchedExplanation,
|
|
85
|
-
points: watchedPoints,
|
|
86
|
-
};
|
|
87
|
-
const isEchoFromOnChange = currentFormData.points === normalizedInitialData.points &&
|
|
88
|
-
currentFormData.explanation === normalizedInitialData.explanation &&
|
|
89
|
-
currentFormData.question === normalizedInitialData.question &&
|
|
90
|
-
currentFormData.imageUrl === normalizedInitialData.imageUrl &&
|
|
91
|
-
JSON.stringify(currentFormData.expectedAnswers) === JSON.stringify(normalizedInitialData.expectedAnswers);
|
|
92
|
-
const wasJustSaved = initialData?.isCompleted === true;
|
|
93
|
-
if (isEchoFromOnChange && wasJustSaved) {
|
|
94
|
-
return;
|
|
95
|
-
}
|
|
96
|
-
const shouldReset = !isEchoFromOnChange;
|
|
97
|
-
if (shouldReset) {
|
|
98
|
-
isInitializingRef.current = true;
|
|
99
|
-
form.reset(normalizedInitialData);
|
|
100
|
-
setTimeout(() => {
|
|
101
|
-
isInitializingRef.current = false;
|
|
102
|
-
}, 150);
|
|
103
|
-
}
|
|
104
|
-
}, [initialData, form, watchedQuestion, watchedImageUrl, watchedExpectedAnswers, watchedExplanation, watchedPoints]);
|
|
105
|
-
const watchedExpectedAnswersString = JSON.stringify(watchedExpectedAnswers);
|
|
106
|
-
useEffect(() => {
|
|
107
|
-
if (isInitializingRef.current) {
|
|
108
|
-
const currentPayload = JSON.stringify({
|
|
109
|
-
question: watchedQuestion,
|
|
110
|
-
imageUrl: watchedImageUrl,
|
|
111
|
-
expectedAnswers: watchedExpectedAnswers,
|
|
112
|
-
explanation: watchedExplanation,
|
|
113
|
-
points: watchedPoints,
|
|
114
|
-
});
|
|
115
|
-
previousPayloadRef.current = currentPayload;
|
|
116
|
-
return;
|
|
117
|
-
}
|
|
118
|
-
const payload = {
|
|
119
|
-
question: watchedQuestion,
|
|
120
|
-
imageUrl: watchedImageUrl,
|
|
121
|
-
expectedAnswers: watchedExpectedAnswers,
|
|
122
|
-
explanation: watchedExplanation,
|
|
123
|
-
points: watchedPoints,
|
|
124
|
-
};
|
|
125
|
-
const payloadString = JSON.stringify(payload);
|
|
126
|
-
if (previousPayloadRef.current !== payloadString) {
|
|
127
|
-
previousPayloadRef.current = payloadString;
|
|
128
|
-
debouncedOnChange(payload);
|
|
129
|
-
}
|
|
130
|
-
}, [
|
|
131
|
-
watchedQuestion,
|
|
132
|
-
watchedImageUrl,
|
|
133
|
-
watchedExpectedAnswersString,
|
|
134
|
-
watchedExplanation,
|
|
135
|
-
watchedPoints,
|
|
136
|
-
debouncedOnChange,
|
|
137
|
-
]);
|
|
138
|
-
const handleSave = form.handleSubmit((data) => {
|
|
139
|
-
onSave?.({
|
|
140
|
-
...data,
|
|
141
|
-
});
|
|
142
|
-
});
|
|
143
|
-
return (_jsxs("div", { className: "space-y-4", children: [_jsx(CreatorGuide, { colorScheme: "blue", steps: [
|
|
144
|
-
{ text: 'Nhập câu hỏi yêu cầu mô tả (VD: Mô tả những hành động đang diễn ra trong hình)' },
|
|
145
|
-
{ text: 'Upload hình ảnh học sinh cần mô tả', highlight: true },
|
|
146
|
-
{ text: 'Nhập các ý, đáp án, hoặc keyword kỳ vọng học sinh sẽ nói để hệ thống AI dựa vào đó chấm điểm' },
|
|
147
|
-
{ text: 'Thêm giải thích (tùy chọn)' },
|
|
148
|
-
], tips: ['Hãy nhập các expected answers càng chi tiết càng tốt để AI có thể bắt text và đối chiếu trơn tru!'] }), _jsxs(Card, { className: "overflow-hidden border-0 bg-white", children: [_jsx(CardHeader, { className: "px-4 py-3", children: _jsx("div", { className: "flex items-center justify-between", children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-8 w-8 items-center justify-center rounded-lg bg-gray-100", children: _jsx(ImageIcon, { className: "h-4 w-4 text-gray-600" }) }), _jsxs("div", { children: [_jsx(CardTitle, { className: "text-base font-bold text-gray-900", children: "N\u1ED9i dung c\u00E2u h\u1ECFi" }), _jsx("p", { className: "text-xs text-gray-500", children: "Nh\u1EADp c\u00E2u h\u1ECFi v\u00E0 upload h\u00ECnh \u1EA3nh c\u1EA7n m\u00F4 t\u1EA3" })] })] }) }) }), _jsxs(CardContent, { className: "space-y-3 px-4 pb-4", children: [_jsx("div", { className: "flex items-center gap-6", 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(MessageSquareText, { className: "h-4 w-4 text-blue-500" }), "C\u00E2u h\u1ECFi", _jsx("span", { className: "text-xs font-normal text-gray-400", children: "(Kh\u00F4ng b\u1EAFt bu\u1ED9c)" })] }), _jsx(Input, { value: watchedQuestion || '', onChange: (e) => form.setValue('question', e.target.value, { shouldValidate: true }), placeholder: "VD: What is this?", className: "border-gray-200 bg-white transition-all focus:border-blue-300 focus:ring-blue-200" })] }), _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" }), "H\u00ECnh \u1EA3nh c\u1EA7n m\u00F4 t\u1EA3"] }), _jsx(FileUpload, { id: "imageUrl", label: "", accept: "image/*", value: watchedImageUrl, onChange: (url) => {
|
|
149
|
-
form.setValue('imageUrl', url, { shouldValidate: true });
|
|
150
|
-
}, maxSize: 10, placeholder: "Upload h\u00ECnh \u1EA3nh", autoUpload: true, prefix: "speaking/describe-image" }), form.hasError('imageUrl') && (_jsx("p", { className: "text-sm text-red-600", children: form.getFieldError('imageUrl') }))] }), imagePreviewUrl && (_jsx("div", { className: "mt-2 rounded-lg overflow-hidden border border-gray-200 flex justify-center bg-gray-100", children: _jsx("img", { src: imagePreviewUrl, alt: "Preview", className: "max-h-[300px] object-contain" }) })), externalErrors && externalErrors.length > 0 && (_jsxs("div", { className: "flex items-start gap-3 rounded-xl border border-red-200 bg-gradient-to-r from-red-50 to-rose-50 p-4", children: [_jsx("div", { className: "flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-lg bg-red-100", children: _jsx("span", { className: "text-lg", children: "\u274C" }) }), _jsxs("div", { className: "text-sm", children: [_jsx("p", { className: "font-semibold text-red-800", children: "L\u1ED7i:" }), _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", children: [_jsx(CardHeader, { className: "px-4 py-3", children: _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-8 w-8 items-center justify-center rounded-lg bg-gray-100", children: _jsx(MessageSquareText, { className: "h-4 w-4 text-gray-600" }) }), _jsxs("div", { children: [_jsxs(CardTitle, { className: "text-base font-bold text-gray-900", children: ["\u0110\u00E1p \u00E1n / Keywords expected ", _jsxs("span", { className: "text-xs font-normal text-gray-500", children: ["(", watchedExpectedAnswers.filter((a) => a.trim()).length, " \u0111\u00E1p \u00E1n)"] })] }), _jsx("p", { className: "text-xs text-gray-500", children: "Nh\u1EADp c\u00E1c \u00FD ho\u1EB7c t\u1EEB kh\u00F3a m\u00E0 h\u1ECDc sinh c\u1EA7n n\u00F3i" })] })] }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => {
|
|
151
|
-
const currentAnswers = form.getValues('expectedAnswers');
|
|
152
|
-
form.setValue('expectedAnswers', [...currentAnswers, '']);
|
|
153
|
-
}, 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 d\u00F2ng"] })] }) }), _jsx(CardContent, { className: "space-y-3 px-4 pb-4", children: _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) => {
|
|
154
|
-
const newAnswers = [...watchedExpectedAnswers];
|
|
155
|
-
newAnswers[index] = e.target.value;
|
|
156
|
-
form.setValue('expectedAnswers', newAnswers, { shouldValidate: true });
|
|
157
|
-
}, placeholder: `Ví dụ: The boy is kicking a football.`, className: "border-gray-200 bg-white transition-all 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: () => {
|
|
158
|
-
const newAnswers = watchedExpectedAnswers.filter((_, i) => i !== index);
|
|
159
|
-
form.setValue('expectedAnswers', newAnswers, { shouldValidate: true });
|
|
160
|
-
}, 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", children: [_jsx(CardHeader, { className: "px-4 py-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-amber-500 to-orange-500 shadow-sm", children: _jsx(BookOpen, { className: "h-3.5 w-3.5 text-white" }) }), _jsxs("div", { children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(CardTitle, { className: "text-base 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 py-0.5 text-xs font-medium text-amber-600", children: "Kh\u00F4ng b\u1EAFt bu\u1ED9c" })] }), _jsx("p", { className: "text-xs text-gray-500 mt-0.5", children: "Gi\u1EA3i th\u00EDch th\u00EAm v\u1EC1 b\u1EE9c \u1EA3nh \u0111\u1EC3 gi\u00FAp h\u1ECDc sinh hi\u1EC3u r\u00F5 h\u01A1n khi review k\u1EBFt qu\u1EA3." })] })] }) }), _jsx(CardContent, { className: "px-4 pb-4", children: _jsx(Textarea, { value: watchedExplanation, onChange: (e) => form.setValue('explanation', e.target.value, { shouldValidate: true }), placeholder: "VD: C\u00E2u n\u00E0y h\u1ECDc sinh c\u1EA7n ph\u1EA3i nh\u1EADn bi\u1EBFt \u0111\u01B0\u1EE3c h\u00E0nh \u0111\u1ED9ng...", rows: 3, className: "min-h-[80px] border-gray-200 bg-white transition-all focus:border-amber-300 focus:ring-amber-200 resize-none" }) })] })] }));
|
|
161
|
-
}
|
|
1
|
+
"use client";import{jsx as e,jsxs as t}from"react/jsx-runtime";import{useRef as r,useEffect as s}from"react";import{Button as n}from"../../../../components/ui/button";import{Label as a}from"../../../../components/ui/label";import{Input as i}from"../../../../components/ui/input";import{Textarea as c}from"../../../../components/ui/textarea";import{PointsInput as l}from"../../../../components/ui/points-input";import{Card as o,CardContent as d,CardHeader as m,CardTitle as h}from"../../../../components/ui/card";import{useDebouncedCallback as p,usePresignedFileUrl as g}from"../../../../shared/lib/hooks";import{useReactHookForm as x}from"../../../../shared/lib/hooks/useReactHookForm";import{Plus as u,Trash2 as f,ImageIcon as b,MessageSquareText as N,BookOpen as w}from"lucide-react";import{FileUpload as v}from"../../../../components/ui/file-upload";import{CreatorGuide as y}from"../../components/CreatorGuide";import{speakingDescribeImageFormSchema as A}from"../../../../shared/lib/validation/speaking-describe-image.validation";export function SpeakingDescribeImageCreator({initialData:U,onSave:S,onChange:V,externalErrors:k,validationRef:q}){const j=U,C=x({defaultValues:{question:j?.question||j?.content?.question||"",imageUrl:j?.imageUrl||j?.content?.imageUrl||"",expectedAnswers:(j?.expectedAnswers||j?.correctAnswer?.expectedAnswers)?.length>0?j?.expectedAnswers||j?.correctAnswer?.expectedAnswers:[""],explanation:j?.explanation||"",points:j?.points??5},zodSchema:A,mode:"onBlur"}),J=r(U),O=r(!1),D=r(V),z=r("");s(()=>{D.current=V},[V]);const E=p(e=>{D.current?.(e)},300),F=C.watch("question"),T=C.watch("imageUrl"),G=C.watch("expectedAnswers"),H=C.watch("explanation"),I=C.watch("points"),{previewUrl:K}=g(T||"");s(()=>{q&&(q.current={triggerValidation:async()=>await C.trigger(),getFormData:()=>({question:F,imageUrl:T,expectedAnswers:G,explanation:H,points:I})})},[q,C,F,T,G,H,I]),s(()=>{const e=U,t={question:e?.question||e?.content?.question||"",imageUrl:e?.imageUrl||e?.content?.imageUrl||"",expectedAnswers:(e?.expectedAnswers||e?.correctAnswer?.expectedAnswers)?.length>0?e?.expectedAnswers||e?.correctAnswer?.expectedAnswers:[""],explanation:e?.explanation||"",points:e?.points??5};if(!(JSON.stringify(J.current)!==JSON.stringify(U)))return;J.current=U;const r={question:F,imageUrl:T,expectedAnswers:G,explanation:H,points:I},s=r.points===t.points&&r.explanation===t.explanation&&r.question===t.question&&r.imageUrl===t.imageUrl&&JSON.stringify(r.expectedAnswers)===JSON.stringify(t.expectedAnswers);if(s&&!0===U?.isCompleted)return;!s&&(O.current=!0,C.reset(t),setTimeout(()=>{O.current=!1},150))},[U,C,F,T,G,H,I]);const R=JSON.stringify(G);s(()=>{if(O.current){const e=JSON.stringify({question:F,imageUrl:T,expectedAnswers:G,explanation:H,points:I});return void(z.current=e)}const e={question:F,imageUrl:T,expectedAnswers:G,explanation:H,points:I},t=JSON.stringify(e);z.current!==t&&(z.current=t,E(e))},[F,T,R,H,I,E]);C.handleSubmit(e=>{S?.({...e})});return t("div",{className:"space-y-4",children:[e(y,{colorScheme:"blue",steps:[{text:"Nhập câu hỏi yêu cầu mô tả (VD: Mô tả những hành động đang diễn ra trong hình)"},{text:"Upload hình ảnh học sinh cần mô tả",highlight:!0},{text:"Nhập các ý, đáp án, hoặc keyword kỳ vọng học sinh sẽ nói để hệ thống AI dựa vào đó chấm điểm"},{text:"Thêm giải thích (tùy chọn)"}],tips:["Hãy nhập các expected answers càng chi tiết càng tốt để AI có thể bắt text và đối chiếu trơn tru!"]}),t(o,{className:"overflow-hidden border-0 bg-white",children:[e(m,{className:"px-4 py-3",children:e("div",{className:"flex items-center justify-between",children:t("div",{className:"flex items-center gap-2",children:[e("div",{className:"flex h-8 w-8 items-center justify-center rounded-lg bg-gray-100",children:e(b,{className:"h-4 w-4 text-gray-600"})}),t("div",{children:[e(h,{className:"text-base font-bold text-gray-900",children:"Nội dung câu hỏi"}),e("p",{className:"text-xs text-gray-500",children:"Nhập câu hỏi và upload hình ảnh cần mô tả"})]})]})})}),t(d,{className:"space-y-3 px-4 pb-4",children:[e("div",{className:"flex items-center gap-6",children:e(l,{...C.register("points",{valueAsNumber:!0}),error:C.hasError("points")?C.getFieldError("points"):void 0})}),t("div",{className:"space-y-2",children:[t(a,{className:"flex items-center gap-2 text-base font-semibold text-gray-800",children:[e(N,{className:"h-4 w-4 text-blue-500"}),"Câu hỏi",e("span",{className:"text-xs font-normal text-gray-400",children:"(Không bắt buộc)"})]}),e(i,{value:F||"",onChange:e=>C.setValue("question",e.target.value,{shouldValidate:!0}),placeholder:"VD: What is this?",className:"border-gray-200 bg-white transition-all focus:border-blue-300 focus:ring-blue-200"})]}),t("div",{className:"space-y-2",children:[t(a,{className:"flex items-center gap-2 text-base font-semibold text-gray-800",children:[e(b,{className:"h-4 w-4 text-indigo-500"}),"Hình ảnh cần mô tả"]}),e(v,{id:"imageUrl",label:"",accept:"image/*",value:T,onChange:e=>{C.setValue("imageUrl",e,{shouldValidate:!0})},maxSize:10,placeholder:"Upload hình ảnh",autoUpload:!0,prefix:"speaking/describe-image"}),C.hasError("imageUrl")&&e("p",{className:"text-sm text-red-600",children:C.getFieldError("imageUrl")})]}),K&&e("div",{className:"mt-2 rounded-lg overflow-hidden border border-gray-200 flex justify-center bg-gray-100",children:e("img",{src:K,alt:"Preview",className:"max-h-[300px] object-contain"})}),k&&k.length>0&&t("div",{className:"flex items-start gap-3 rounded-xl border border-red-200 bg-gradient-to-r from-red-50 to-rose-50 p-4",children:[e("div",{className:"flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-lg bg-red-100",children:e("span",{className:"text-lg",children:"❌"})}),t("div",{className:"text-sm",children:[e("p",{className:"font-semibold text-red-800",children:"Lỗi:"}),e("ul",{className:"mt-1 list-inside list-disc text-red-700",children:k.map((t,r)=>e("li",{children:t},r))})]})]})]})]}),t(o,{className:"overflow-hidden border-0 bg-white",children:[e(m,{className:"px-4 py-3",children:t("div",{className:"flex items-center justify-between",children:[t("div",{className:"flex items-center gap-2",children:[e("div",{className:"flex h-8 w-8 items-center justify-center rounded-lg bg-gray-100",children:e(N,{className:"h-4 w-4 text-gray-600"})}),t("div",{children:[t(h,{className:"text-base font-bold text-gray-900",children:["Đáp án / Keywords expected ",t("span",{className:"text-xs font-normal text-gray-500",children:["(",G.filter(e=>e.trim()).length," đáp án)"]})]}),e("p",{className:"text-xs text-gray-500",children:"Nhập các ý hoặc từ khóa mà học sinh cần nói"})]})]}),t(n,{variant:"outline",size:"sm",onClick:()=>{const e=C.getValues("expectedAnswers");C.setValue("expectedAnswers",[...e,""])},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 dòng"]})]})}),e(d,{className:"space-y-3 px-4 pb-4",children:t("div",{className:"space-y-3",children:[G.map((r,s)=>t("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:s+1}),t("div",{className:"flex-1",children:[e(i,{value:r||"",onChange:e=>{const t=[...G];t[s]=e.target.value,C.setValue("expectedAnswers",t,{shouldValidate:!0})},placeholder:"Ví dụ: The boy is kicking a football.",className:"border-gray-200 bg-white transition-all focus:border-green-300 focus:ring-green-200"}),C.formState.errors?.expectedAnswers?.[s]?.message&&e("p",{className:"mt-1 text-sm text-red-500",children:C.formState.errors.expectedAnswers[s]?.message})]}),G.length>1&&e(n,{variant:"ghost",size:"sm",onClick:()=>{const e=G.filter((e,t)=>t!==s);C.setValue("expectedAnswers",e,{shouldValidate:!0})},className:"text-red-400 hover:text-red-600 hover:bg-red-50 opacity-0 group-hover/answer:opacity-100 transition-opacity",children:e(f,{className:"h-4 w-4"})})]},s)),C.formState.errors?.expectedAnswers&&!Array.isArray(C.formState.errors.expectedAnswers)&&e("p",{className:"text-sm text-red-500",children:C.formState.errors.expectedAnswers.message})]})})]}),t(o,{className:"overflow-hidden border-0 bg-white",children:[e(m,{className:"px-4 py-3",children:t("div",{className:"flex items-center gap-2",children:[e("div",{className:"flex h-7 w-7 items-center justify-center rounded-lg bg-gradient-to-br from-amber-500 to-orange-500 shadow-sm",children:e(w,{className:"h-3.5 w-3.5 text-white"})}),t("div",{children:[t("div",{className:"flex items-center gap-2",children:[e(h,{className:"text-base 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 py-0.5 text-xs font-medium text-amber-600",children:"Không bắt buộc"})]}),e("p",{className:"text-xs text-gray-500 mt-0.5",children:"Giải thích thêm về bức ảnh để giúp học sinh hiểu rõ hơn khi review kết quả."})]})]})}),e(d,{className:"px-4 pb-4",children:e(c,{value:H,onChange:e=>C.setValue("explanation",e.target.value,{shouldValidate:!0}),placeholder:"VD: Câu này học sinh cần phải nhận biết được hành động...",rows:3,className:"min-h-[80px] border-gray-200 bg-white transition-all focus:border-amber-300 focus:ring-amber-200 resize-none"})})]})]})}
|
|
@@ -1,2 +1 @@
|
|
|
1
|
-
export
|
|
2
|
-
export * from './SpeakingDescribeImageClient';
|
|
1
|
+
export*from"./SpeakingDescribeImageCreator";export*from"./SpeakingDescribeImageClient";
|
|
@@ -1,69 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
export const speakingDescribeImageRegistration = {
|
|
3
|
-
component: SpeakingDescribeImageCreator,
|
|
4
|
-
transformInitialData: (initialData) => {
|
|
5
|
-
let creatorData = initialData?.answer;
|
|
6
|
-
const content = initialData?.content;
|
|
7
|
-
const answer = initialData?.answer || initialData?.correctAnswer || initialData?.correct_answer;
|
|
8
|
-
const points = initialData?.total_points
|
|
9
|
-
? parseFloat(initialData.total_points)
|
|
10
|
-
: (initialData?.points || content?.points || answer?.points || 1);
|
|
11
|
-
const explanation = initialData?.explanation || content?.explanation || answer?.explanation || '';
|
|
12
|
-
if (content && typeof content === 'object' && content.imageUrl !== undefined) {
|
|
13
|
-
const expectedAnswers = answer?.expectedAnswers || [];
|
|
14
|
-
creatorData = {
|
|
15
|
-
question: content.question || '',
|
|
16
|
-
imageUrl: content.imageUrl || '',
|
|
17
|
-
expectedAnswers: expectedAnswers,
|
|
18
|
-
explanation,
|
|
19
|
-
points,
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
else if (answer && typeof answer === 'object' && answer.expectedAnswers !== undefined) {
|
|
23
|
-
creatorData = {
|
|
24
|
-
...answer,
|
|
25
|
-
question: answer.question || content?.question || '',
|
|
26
|
-
imageUrl: answer.imageUrl || content?.imageUrl || '',
|
|
27
|
-
explanation,
|
|
28
|
-
points,
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
return creatorData;
|
|
32
|
-
},
|
|
33
|
-
getExtraProps: (ctx) => ({
|
|
34
|
-
onUnsavedChangesChange: ctx.onUnsavedChangesChange,
|
|
35
|
-
validationRef: ctx.validationRef,
|
|
36
|
-
}),
|
|
37
|
-
wrapOnSave: (data, ctx) => ({
|
|
38
|
-
type: ctx.questionType,
|
|
39
|
-
content: {
|
|
40
|
-
...(typeof ctx.state.content === 'object' && ctx.state.content !== null ? ctx.state.content : {}),
|
|
41
|
-
question: data.question,
|
|
42
|
-
imageUrl: data.imageUrl,
|
|
43
|
-
},
|
|
44
|
-
points: data?.points ?? ctx.state.points ?? 1,
|
|
45
|
-
level: ctx.state.level,
|
|
46
|
-
difficulty: ctx.state.difficulty,
|
|
47
|
-
skill: ctx.state.skill,
|
|
48
|
-
answer: {
|
|
49
|
-
expectedAnswers: data.expectedAnswers,
|
|
50
|
-
},
|
|
51
|
-
explanation: data?.explanation || '',
|
|
52
|
-
}),
|
|
53
|
-
wrapOnChange: (data, ctx) => ({
|
|
54
|
-
type: ctx.questionType,
|
|
55
|
-
content: {
|
|
56
|
-
...(typeof ctx.state.content === 'object' && ctx.state.content !== null ? ctx.state.content : {}),
|
|
57
|
-
question: data.question,
|
|
58
|
-
imageUrl: data.imageUrl,
|
|
59
|
-
},
|
|
60
|
-
points: data?.points ?? ctx.state.points ?? 1,
|
|
61
|
-
level: ctx.state.level,
|
|
62
|
-
difficulty: ctx.state.difficulty,
|
|
63
|
-
skill: ctx.state.skill,
|
|
64
|
-
answer: {
|
|
65
|
-
expectedAnswers: data.expectedAnswers,
|
|
66
|
-
},
|
|
67
|
-
explanation: data?.explanation || '',
|
|
68
|
-
}),
|
|
69
|
-
};
|
|
1
|
+
import{SpeakingDescribeImageCreator as e}from"./SpeakingDescribeImageCreator";export const speakingDescribeImageRegistration={component:e,transformInitialData:e=>{let t=e?.answer;const n=e?.content,s=e?.answer||e?.correctAnswer||e?.correct_answer,a=e?.total_points?parseFloat(e.total_points):e?.points||n?.points||s?.points||1,o=e?.explanation||n?.explanation||s?.explanation||"";if(n&&"object"==typeof n&&void 0!==n.imageUrl){const e=s?.expectedAnswers||[];t={question:n.question||"",imageUrl:n.imageUrl||"",expectedAnswers:e,explanation:o,points:a}}else s&&"object"==typeof s&&void 0!==s.expectedAnswers&&(t={...s,question:s.question||n?.question||"",imageUrl:s.imageUrl||n?.imageUrl||"",explanation:o,points:a});return t},getExtraProps:e=>({onUnsavedChangesChange:e.onUnsavedChangesChange,validationRef:e.validationRef}),wrapOnSave:(e,t)=>({type:t.questionType,content:{..."object"==typeof t.state.content&&null!==t.state.content?t.state.content:{},question:e.question,imageUrl:e.imageUrl},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:{},question:e.question,imageUrl:e.imageUrl},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||""})};
|