@tinyweb_dev/oe-exam-sdk 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/exam-create.js +1 -179
- package/dist/api/exam-create.types.js +1 -1
- package/dist/api/exam-questions.js +1 -98
- package/dist/api/exam-questions.types.js +1 -1
- package/dist/api/exam-taking.js +1 -73
- package/dist/api/exam-taking.types.js +1 -1
- package/dist/api/index.js +1 -6
- package/dist/api/result-review.js +1 -50
- package/dist/api/result-review.types.js +1 -1
- package/dist/components/CueCardEditor.js +1 -58
- package/dist/components/common/ResolvedImage.js +1 -15
- package/dist/components/exams/CreateExamInfoForm.js +1 -35
- package/dist/components/exams/CreateExamPageContainer.js +1 -103
- package/dist/components/exams/ExamCreator.js +1 -89
- package/dist/components/exams/ExamPartTabs.js +1 -40
- package/dist/components/exams/ExamQuestionGrid.js +1 -18
- package/dist/components/exams/ExamQuestionsPageContainer.js +1 -90
- package/dist/components/exams/PartAudioUploader.js +1 -9
- package/dist/components/exams/TemplateSelector.js +1 -66
- package/dist/components/exams/api.js +1 -3
- package/dist/components/exams/create-exam.validation.js +1 -18
- package/dist/components/exams/exam-template.utils.js +1 -18
- package/dist/components/exams/index.js +1 -10
- package/dist/components/exams/take/ExamTakingPageContainer.js +1 -153
- package/dist/components/exams/take/GenericQuestionRenderer.js +1 -14
- package/dist/components/exams/take/exam-taking.utils.js +1 -88
- package/dist/components/exams/take/hooks/useSpeakingRecorder.js +1 -24
- package/dist/components/exams/take/index.js +1 -2
- package/dist/components/exams/take/types.js +1 -1
- package/dist/components/index.js +1 -42
- package/dist/components/questions/_shared/config/question-types.config.js +1 -333
- package/dist/components/questions/_shared/constants/color-palette.js +1 -21
- package/dist/components/questions/_shared/hooks/useAnswerGroupQuestionGroup.js +1 -105
- package/dist/components/questions/_shared/hooks/useBasicQuestionGroup.js +1 -222
- package/dist/components/questions/_shared/hooks/useCompareImagesGroup.js +1 -140
- package/dist/components/questions/_shared/hooks/useImageQuestionGroup.js +1 -123
- package/dist/components/questions/_shared/hooks/useInfoExchangeGroup.js +1 -50
- package/dist/components/questions/_shared/hooks/useQuestionFormState.js +1 -54
- package/dist/components/questions/_shared/hooks/useQuestionInitialization.js +1 -123
- package/dist/components/questions/_shared/hooks/useQuestionListGroup.js +1 -121
- package/dist/components/questions/_shared/hooks/useQuestionOnChange.js +1 -70
- package/dist/components/questions/_shared/hooks/useSceneQuestionGroup.js +1 -194
- package/dist/components/questions/_shared/index.js +1 -7
- package/dist/components/questions/_shared/types/answer-the-question.type.js +1 -1
- package/dist/components/questions/_shared/types/arrange-letters-into-words.type.js +1 -1
- package/dist/components/questions/_shared/types/choose-correct-adjective.type.js +1 -3
- package/dist/components/questions/_shared/types/color-region.config.js +1 -1
- package/dist/components/questions/_shared/types/fill-in-blank.type.js +1 -1
- package/dist/components/questions/_shared/types/fill-missing-words-in-grid.type.js +1 -1
- package/dist/components/questions/_shared/types/gn-speaking-interview.type.js +1 -1
- package/dist/components/questions/_shared/types/index.js +1 -12
- package/dist/components/questions/_shared/types/label-the-picture.type.js +1 -1
- package/dist/components/questions/_shared/types/listen-and-choose-from-answer-group.type.js +1 -1
- package/dist/components/questions/_shared/types/listen-and-choose-objects-in-scene.type.js +1 -1
- package/dist/components/questions/_shared/types/listen-and-drag-objects-into-scene.type.js +1 -1
- package/dist/components/questions/_shared/types/listen-and-speak-answer.type.js +1 -1
- package/dist/components/questions/_shared/types/listen-and-speak-compare-images.type.js +1 -1
- package/dist/components/questions/_shared/types/listen-and-speak-image-group.type.js +1 -1
- package/dist/components/questions/_shared/types/listen-and-speak-info-exchange.type.js +1 -1
- package/dist/components/questions/_shared/types/listen-and-speak-odd-one-out.type.js +1 -3
- package/dist/components/questions/_shared/types/listen-and-speak-question-list.type.js +1 -1
- package/dist/components/questions/_shared/types/listen-and-speak-with-story-images.type.js +1 -3
- package/dist/components/questions/_shared/types/listen-and-tick-answer.type.js +1 -1
- package/dist/components/questions/_shared/types/listening-drawline.type.js +1 -1
- package/dist/components/questions/_shared/types/look-and-choose-answer.type.js +1 -1
- package/dist/components/questions/_shared/types/look-picture-fill-word-hint.type.js +1 -1
- package/dist/components/questions/_shared/types/match-by-writing-answer.type.js +1 -1
- package/dist/components/questions/_shared/types/multiple-choice.type.js +1 -1
- package/dist/components/questions/_shared/types/question-group.type.js +1 -11
- package/dist/components/questions/_shared/types/read-and-color-objects.type.js +1 -1
- package/dist/components/questions/_shared/types/read-displayed-content.type.js +1 -1
- package/dist/components/questions/_shared/types/read-passage-and-answer-questions.type.js +1 -1
- package/dist/components/questions/_shared/types/speaking-describe-image.type.js +1 -1
- package/dist/components/questions/_shared/types/spontaneous-qa.type.js +1 -1
- package/dist/components/questions/_shared/types/word-fill-paragraph-with-options.type.js +1 -1
- package/dist/components/questions/_shared/types/word-fill-paragraph.type.js +1 -1
- package/dist/components/questions/_shared/types/word-fill-structured-form.type.js +1 -1
- package/dist/components/questions/_shared/types/word-ordering.type.js +1 -1
- package/dist/components/questions/_shared/types/write-a-short-letter.type.js +1 -1
- package/dist/components/questions/_shared/types/write-correct-verb-form.type.js +1 -3
- package/dist/components/questions/_shared/types/write-sentences.type.js +1 -1
- package/dist/components/questions/_shared/types/write-short-paragraph.type.js +1 -1
- package/dist/components/questions/_shared/utils/answer-builder.js +1 -79
- package/dist/components/questions/_shared/utils/createColorRegionRegistration.js +1 -39
- package/dist/components/questions/_shared/utils/id-generator.js +1 -3
- package/dist/components/questions/_shared/utils/question-validation.js +1 -21
- package/dist/components/questions/components/ColorNode.js +1 -85
- package/dist/components/questions/components/ColorRegionClient.js +1 -545
- package/dist/components/questions/components/ColorRegionCreator.js +1 -420
- package/dist/components/questions/components/ColorRegionNode.js +1 -59
- package/dist/components/questions/components/CreatorGuide.js +1 -103
- package/dist/components/questions/components/NameNode.js +1 -26
- package/dist/components/questions/components/QuestionBankOption.js +1 -46
- package/dist/components/questions/components/QuestionBasicInfoForm.js +1 -9
- package/dist/components/questions/components/QuestionCommonFields.js +1 -33
- package/dist/components/questions/components/QuestionSearchDropdown.js +1 -258
- package/dist/components/questions/components/QuestionTypeSelector.js +1 -32
- package/dist/components/questions/components/QuestionTypeSpecificForms.js +1 -6
- package/dist/components/questions/components/RegionNode.js +1 -94
- package/dist/components/questions/components/index.js +1 -14
- package/dist/components/questions/creator/QuestionCreator.js +1 -90
- package/dist/components/questions/creator/QuestionGroupCreator.js +1 -119
- package/dist/components/questions/creator/dedicated-component-router.js +1 -79
- package/dist/components/questions/creator/index.js +1 -3
- package/dist/components/questions/creator/question-type-registry.js +1 -80
- package/dist/components/questions/groups/ArticlesGroupCard.js +1 -121
- package/dist/components/questions/groups/BasicQuestionGroupCard.js +1 -50
- package/dist/components/questions/groups/DocumentGroupCard.js +1 -114
- package/dist/components/questions/groups/MatchAnswerGroupCard.js +1 -25
- package/dist/components/questions/index.js +1 -10
- package/dist/components/questions/types/answer-the-question/AnswerTheQuestionClient.js +1 -37
- package/dist/components/questions/types/answer-the-question/AnswerTheQuestionCreator.js +1 -351
- package/dist/components/questions/types/answer-the-question/register.js +1 -67
- package/dist/components/questions/types/answer-the-question/transform.js +1 -36
- package/dist/components/questions/types/arrange-letters-into-words/ArrangeLettersIntoWordsClient.js +1 -141
- package/dist/components/questions/types/arrange-letters-into-words/ArrangeLettersIntoWordsCreator.js +1 -118
- package/dist/components/questions/types/arrange-letters-into-words/index.js +1 -2
- package/dist/components/questions/types/arrange-letters-into-words/register.js +1 -4
- package/dist/components/questions/types/arrange-letters-into-words/transform.js +1 -24
- package/dist/components/questions/types/choose-correct-adjective/ChooseCorrectAdjectiveClient.js +1 -64
- package/dist/components/questions/types/choose-correct-adjective/ChooseCorrectAdjectiveCreator.js +1 -169
- package/dist/components/questions/types/choose-correct-adjective/index.js +1 -2
- package/dist/components/questions/types/choose-correct-adjective/register.js +1 -55
- package/dist/components/questions/types/choose-correct-adjective/transform.js +1 -16
- package/dist/components/questions/types/choose-the-correct-answer/ChooseTheCorrectAnswerClient.js +1 -131
- package/dist/components/questions/types/choose-the-correct-answer/ChooseTheCorrectAnswerCreator.js +1 -581
- package/dist/components/questions/types/choose-the-correct-answer/index.js +1 -2
- package/dist/components/questions/types/choose-the-correct-answer/register.js +1 -101
- package/dist/components/questions/types/choose-the-correct-answer/transform.js +1 -43
- package/dist/components/questions/types/fill-in-blank/FillInBlankClient.js +1 -275
- package/dist/components/questions/types/fill-in-blank/FillInBlankCreator.js +1 -448
- package/dist/components/questions/types/fill-in-blank/index.js +1 -2
- package/dist/components/questions/types/fill-in-blank/register.js +1 -138
- package/dist/components/questions/types/fill-in-blank/transform.js +1 -59
- package/dist/components/questions/types/fill-missing-words-in-grid/FillMissingWordsInGridClient.js +1 -117
- package/dist/components/questions/types/fill-missing-words-in-grid/FillMissingWordsInGridCreator.js +1 -671
- package/dist/components/questions/types/fill-missing-words-in-grid/index.js +1 -2
- package/dist/components/questions/types/fill-missing-words-in-grid/register.js +1 -37
- package/dist/components/questions/types/fill-missing-words-in-grid/transform.js +1 -15
- package/dist/components/questions/types/gn-speaking-interview/GNSpeakingInterviewCreator.js +1 -262
- package/dist/components/questions/types/gn-speaking-interview/register.js +1 -121
- package/dist/components/questions/types/gn-speaking-interview/transform.js +1 -48
- package/dist/components/questions/types/image-object-matching/ImageObjectMatchingClient.js +1 -16
- package/dist/components/questions/types/image-object-matching/ImageObjectMatchingCreator.js +1 -27
- package/dist/components/questions/types/image-object-matching/index.js +1 -2
- package/dist/components/questions/types/image-object-matching/register.js +1 -3
- package/dist/components/questions/types/image-object-matching/transform.js +1 -2
- package/dist/components/questions/types/label-the-picture/LabelThePictureClient.js +1 -102
- package/dist/components/questions/types/label-the-picture/LabelThePictureCreator.js +1 -344
- package/dist/components/questions/types/label-the-picture/index.js +1 -2
- package/dist/components/questions/types/label-the-picture/register.js +1 -54
- package/dist/components/questions/types/label-the-picture/transform.js +1 -28
- package/dist/components/questions/types/listen-and-choose-from-answer-group/ListenAndChooseFromAnswerGroupClient.js +1 -78
- package/dist/components/questions/types/listen-and-choose-from-answer-group/ListenAndChooseFromAnswerGroupCreator.js +1 -232
- package/dist/components/questions/types/listen-and-choose-from-answer-group/index.js +1 -2
- package/dist/components/questions/types/listen-and-choose-from-answer-group/register.js +1 -82
- package/dist/components/questions/types/listen-and-choose-from-answer-group/transform.js +1 -27
- package/dist/components/questions/types/listen-and-choose-objects-in-scene/ListenAndChooseObjectsInSceneClient.js +1 -70
- package/dist/components/questions/types/listen-and-choose-objects-in-scene/ListenAndChooseObjectsInSceneCreator.js +1 -386
- package/dist/components/questions/types/listen-and-choose-objects-in-scene/index.js +1 -2
- package/dist/components/questions/types/listen-and-choose-objects-in-scene/register.js +1 -100
- package/dist/components/questions/types/listen-and-choose-objects-in-scene/transform.js +1 -33
- package/dist/components/questions/types/listen-and-drag-objects-into-scene/ListenAndDragObjectsIntoSceneClient.js +1 -106
- package/dist/components/questions/types/listen-and-drag-objects-into-scene/ListenAndDragObjectsIntoSceneCreator.js +1 -457
- package/dist/components/questions/types/listen-and-drag-objects-into-scene/index.js +1 -2
- package/dist/components/questions/types/listen-and-drag-objects-into-scene/register.js +1 -110
- package/dist/components/questions/types/listen-and-drag-objects-into-scene/transform.js +1 -35
- package/dist/components/questions/types/listen-and-speak-answer/ArrowIndicator.js +1 -8
- package/dist/components/questions/types/listen-and-speak-answer/ListenAndSpeakAnswerClient.js +1 -22
- package/dist/components/questions/types/listen-and-speak-answer/ListenAndSpeakAnswerCreator.js +1 -291
- package/dist/components/questions/types/listen-and-speak-answer/index.js +1 -2
- package/dist/components/questions/types/listen-and-speak-answer/register.js +1 -84
- package/dist/components/questions/types/listen-and-speak-answer/transform.js +1 -27
- package/dist/components/questions/types/listen-and-speak-compare-images/ListenAndSpeakCompareImagesClient.js +1 -32
- package/dist/components/questions/types/listen-and-speak-compare-images/ListenAndSpeakCompareImagesCreator.js +1 -295
- package/dist/components/questions/types/listen-and-speak-compare-images/register.js +1 -79
- package/dist/components/questions/types/listen-and-speak-compare-images/transform.js +1 -27
- package/dist/components/questions/types/listen-and-speak-image-group/ListenAndSpeakImageGroupClient.js +1 -34
- package/dist/components/questions/types/listen-and-speak-image-group/ListenAndSpeakImageGroupCreator.js +1 -285
- package/dist/components/questions/types/listen-and-speak-image-group/register.js +1 -81
- package/dist/components/questions/types/listen-and-speak-image-group/transform.js +1 -26
- package/dist/components/questions/types/listen-and-speak-info-exchange/ListenAndSpeakInfoExchangeClient.js +1 -38
- package/dist/components/questions/types/listen-and-speak-info-exchange/ListenAndSpeakInfoExchangeCreator.js +1 -385
- package/dist/components/questions/types/listen-and-speak-info-exchange/register.js +1 -87
- package/dist/components/questions/types/listen-and-speak-info-exchange/transform.js +1 -32
- package/dist/components/questions/types/listen-and-speak-odd-one-out/ListenAndSpeakOddOneOutClient.js +1 -26
- package/dist/components/questions/types/listen-and-speak-odd-one-out/ListenAndSpeakOddOneOutCreator.js +1 -247
- package/dist/components/questions/types/listen-and-speak-odd-one-out/register.js +1 -59
- package/dist/components/questions/types/listen-and-speak-odd-one-out/transform.js +1 -18
- package/dist/components/questions/types/listen-and-speak-question-list/ListenAndSpeakQuestionListClient.js +1 -26
- package/dist/components/questions/types/listen-and-speak-question-list/ListenAndSpeakQuestionListCreator.js +1 -256
- package/dist/components/questions/types/listen-and-speak-question-list/register.js +1 -81
- package/dist/components/questions/types/listen-and-speak-question-list/transform.js +1 -26
- package/dist/components/questions/types/listen-and-speak-with-story-images/ListenAndSpeakWithStoryImagesClient.js +1 -26
- package/dist/components/questions/types/listen-and-speak-with-story-images/ListenAndSpeakWithStoryImagesCreator.js +1 -230
- package/dist/components/questions/types/listen-and-speak-with-story-images/register.js +1 -56
- package/dist/components/questions/types/listen-and-speak-with-story-images/transform.js +1 -16
- package/dist/components/questions/types/look-picture-choose-correct-answer/LookPictureChooseCorrectAnswerClient.js +1 -72
- package/dist/components/questions/types/look-picture-choose-correct-answer/LookPictureChooseCorrectAnswerCreator.js +1 -230
- package/dist/components/questions/types/look-picture-choose-correct-answer/index.js +1 -1
- package/dist/components/questions/types/look-picture-choose-correct-answer/register.js +1 -66
- package/dist/components/questions/types/look-picture-choose-correct-answer/transform.js +1 -42
- package/dist/components/questions/types/look-picture-fill-blank-choose-answer/LookPictureFillBlankChooseAnswerClient.js +1 -96
- package/dist/components/questions/types/look-picture-fill-blank-choose-answer/LookPictureFillBlankChooseAnswerCreator.js +1 -238
- package/dist/components/questions/types/look-picture-fill-blank-choose-answer/index.js +1 -2
- package/dist/components/questions/types/look-picture-fill-blank-choose-answer/register.js +1 -76
- package/dist/components/questions/types/look-picture-fill-blank-choose-answer/transform.js +1 -19
- package/dist/components/questions/types/look-picture-fill-word-hint/LookPictureFillWordHintClient.js +1 -134
- package/dist/components/questions/types/look-picture-fill-word-hint/LookPictureFillWordHintCreator.js +1 -617
- package/dist/components/questions/types/look-picture-fill-word-hint/index.js +1 -2
- package/dist/components/questions/types/look-picture-fill-word-hint/register.js +1 -82
- package/dist/components/questions/types/look-picture-fill-word-hint/transform.js +1 -29
- package/dist/components/questions/types/match-by-writing-answer/MatchByWritingAnswerClient.js +1 -38
- package/dist/components/questions/types/match-by-writing-answer/MatchByWritingAnswerCreator.js +1 -359
- package/dist/components/questions/types/match-by-writing-answer/index.js +1 -3
- package/dist/components/questions/types/match-by-writing-answer/register.js +1 -58
- package/dist/components/questions/types/match-by-writing-answer/transform.js +1 -61
- package/dist/components/questions/types/read-and-color-objects/ReadAndColorObjectsClient.js +1 -16
- package/dist/components/questions/types/read-and-color-objects/ReadAndColorObjectsCreator.js +1 -23
- package/dist/components/questions/types/read-and-color-objects/index.js +1 -2
- package/dist/components/questions/types/read-and-color-objects/register.js +1 -3
- package/dist/components/questions/types/read-and-color-objects/transform.js +1 -36
- package/dist/components/questions/types/read-displayed-content/ReadDisplayedContentCreator.js +1 -125
- package/dist/components/questions/types/read-displayed-content/register.js +1 -64
- package/dist/components/questions/types/read-displayed-content/transform.js +1 -12
- package/dist/components/questions/types/read-passage-and-answer-questions/ReadPassageAndAnswerQuestionsClient.js +1 -64
- package/dist/components/questions/types/read-passage-and-answer-questions/ReadPassageAndAnswerQuestionsCreator.js +1 -511
- package/dist/components/questions/types/read-passage-and-answer-questions/index.js +1 -2
- package/dist/components/questions/types/read-passage-and-answer-questions/register.js +1 -58
- package/dist/components/questions/types/read-passage-and-answer-questions/transform.js +1 -28
- package/dist/components/questions/types/speaking-conversation/SpeakingConversationCreator.js +1 -373
- package/dist/components/questions/types/speaking-conversation/SpeakingConversationPreviewClient.js +1 -41
- package/dist/components/questions/types/speaking-conversation/register.js +1 -110
- package/dist/components/questions/types/speaking-conversation/transform.js +1 -34
- package/dist/components/questions/types/speaking-cue-card/SpeakingCueCardCreator.js +1 -74
- package/dist/components/questions/types/speaking-cue-card/register.js +1 -52
- package/dist/components/questions/types/speaking-cue-card/transform.js +1 -12
- package/dist/components/questions/types/speaking-describe-image/SpeakingDescribeImageClient.js +1 -10
- package/dist/components/questions/types/speaking-describe-image/SpeakingDescribeImageCreator.js +1 -161
- package/dist/components/questions/types/speaking-describe-image/index.js +1 -2
- package/dist/components/questions/types/speaking-describe-image/register.js +1 -69
- package/dist/components/questions/types/speaking-describe-image/transform.js +1 -16
- package/dist/components/questions/types/spontaneous-qa/SpontaneousQACreator.js +1 -234
- package/dist/components/questions/types/spontaneous-qa/register.js +1 -93
- package/dist/components/questions/types/spontaneous-qa/transform.js +1 -34
- package/dist/components/questions/types/true-false/TrueFalseClient.js +1 -75
- package/dist/components/questions/types/true-false/TrueFalseCreator.js +1 -244
- package/dist/components/questions/types/true-false/index.js +1 -2
- package/dist/components/questions/types/true-false/register.js +1 -77
- package/dist/components/questions/types/true-false/transform.js +1 -32
- package/dist/components/questions/types/word-fill-paragraph/WordFillParagraphClient.js +1 -233
- package/dist/components/questions/types/word-fill-paragraph/WordFillParagraphCreator.js +1 -631
- package/dist/components/questions/types/word-fill-paragraph/WordFillParagraphWrapper.js +1 -28
- package/dist/components/questions/types/word-fill-paragraph/index.js +1 -2
- package/dist/components/questions/types/word-fill-paragraph/register.js +1 -136
- package/dist/components/questions/types/word-fill-paragraph/transform.js +1 -56
- package/dist/components/questions/types/word-fill-structured-form/WordFillStructuredFormClient.js +1 -138
- package/dist/components/questions/types/word-fill-structured-form/WordFillStructuredFormCreator.js +1 -261
- package/dist/components/questions/types/word-fill-structured-form/index.js +1 -2
- package/dist/components/questions/types/word-fill-structured-form/register.js +1 -67
- package/dist/components/questions/types/word-fill-structured-form/transform.js +1 -40
- package/dist/components/questions/types/word-ordering/WordOrderingClient.js +1 -84
- package/dist/components/questions/types/word-ordering/WordOrderingCreator.js +1 -192
- package/dist/components/questions/types/word-ordering/index.js +1 -2
- package/dist/components/questions/types/word-ordering/register.js +1 -46
- package/dist/components/questions/types/word-ordering/transform.js +1 -24
- package/dist/components/questions/types/write-a-short-letter/WriteAShortLetterClient.js +1 -98
- package/dist/components/questions/types/write-a-short-letter/WriteAShortLetterCreator.js +1 -320
- package/dist/components/questions/types/write-a-short-letter/index.js +1 -2
- package/dist/components/questions/types/write-a-short-letter/register.js +1 -68
- package/dist/components/questions/types/write-a-short-letter/transform.js +1 -25
- package/dist/components/questions/types/write-correct-verb-form/WriteCorrectVerbFormClient.js +1 -61
- package/dist/components/questions/types/write-correct-verb-form/WriteCorrectVerbFormCreator.js +1 -196
- package/dist/components/questions/types/write-correct-verb-form/index.js +1 -2
- package/dist/components/questions/types/write-correct-verb-form/register.js +1 -62
- package/dist/components/questions/types/write-correct-verb-form/transform.js +1 -17
- package/dist/components/questions/types/write-sentences/WriteSentencesClient.js +1 -22
- package/dist/components/questions/types/write-sentences/WriteSentencesCreator.js +1 -174
- package/dist/components/questions/types/write-sentences/index.js +1 -2
- package/dist/components/questions/types/write-sentences/register.js +1 -58
- package/dist/components/questions/types/write-sentences/transform.js +1 -28
- package/dist/components/questions/types/write-short-paragraph/WriteShortParagraphClient.js +1 -96
- package/dist/components/questions/types/write-short-paragraph/WriteShortParagraphCreator.js +1 -161
- package/dist/components/questions/types/write-short-paragraph/index.js +1 -2
- package/dist/components/questions/types/write-short-paragraph/register.js +1 -4
- package/dist/components/questions/types/write-short-paragraph/transform.js +1 -21
- package/dist/components/questions/viewer/QuestionGroupViewer.js +1 -36
- package/dist/components/questions/viewer/QuestionViewer.js +1 -446
- package/dist/components/questions/viewer/index.js +1 -2
- package/dist/components/results/ResultReviewPageContainer.js +1 -98
- package/dist/components/results/ReviewQuestionRenderer.js +1 -72
- package/dist/components/results/index.js +1 -2
- package/dist/components/results/result-review.utils.js +1 -6
- package/dist/components/results/review-data.js +1 -150
- package/dist/components/results/review-renderer.utils.js +1 -97
- package/dist/components/results/review-types.js +1 -1
- package/dist/components/themes/cambridge-yle/CambridgeYleAudioPlayer.js +1 -138
- package/dist/components/themes/cambridge-yle/CambridgeYleBottomNav.js +1 -12
- package/dist/components/themes/cambridge-yle/CambridgeYleExamLayout.js +1 -18
- package/dist/components/themes/cambridge-yle/CambridgeYleExampleText.js +1 -11
- package/dist/components/themes/cambridge-yle/CambridgeYleHeader.js +1 -11
- package/dist/components/themes/cambridge-yle/CambridgeYleInstructionBanner.js +1 -9
- package/dist/components/themes/cambridge-yle/CambridgeYleNavButton.js +1 -13
- package/dist/components/themes/cambridge-yle/CambridgeYlePartBanner.js +1 -8
- package/dist/components/themes/cambridge-yle/CambridgeYleProgressBar.js +1 -6
- package/dist/components/themes/cambridge-yle/CambridgeYleQuestionCard.js +1 -5
- package/dist/components/themes/cambridge-yle/CambridgeYleQuestionCardFullWidth.js +1 -9
- package/dist/components/themes/cambridge-yle/CambridgeYleSidebar.js +1 -41
- package/dist/components/themes/cambridge-yle/CambridgeYleTimer.js +1 -25
- package/dist/components/themes/cambridge-yle/index.js +1 -13
- package/dist/components/themes/english-certification/EcAnswerTheQuestion.js +1 -24
- package/dist/components/themes/english-certification/EcMatchByWritingAnswer.js +1 -88
- package/dist/components/themes/english-certification/EcWordFillParagraph.js +1 -55
- package/dist/components/themes/english-certification/EcWordFillStructuredForm.js +1 -156
- package/dist/components/themes/english-certification/EcWritingTask.js +1 -34
- package/dist/components/themes/english-certification/EnglishCertificationAudioPlayer.js +1 -114
- package/dist/components/themes/english-certification/EnglishCertificationBrand.js +1 -11
- package/dist/components/themes/english-certification/EnglishCertificationExamLayout.js +1 -21
- package/dist/components/themes/english-certification/EnglishCertificationFlagButton.js +1 -15
- package/dist/components/themes/english-certification/EnglishCertificationFooterBar.js +1 -19
- package/dist/components/themes/english-certification/EnglishCertificationGroupedQuestion.js +1 -148
- package/dist/components/themes/english-certification/EnglishCertificationHeader.js +1 -31
- package/dist/components/themes/english-certification/EnglishCertificationNavigationPanel.js +1 -71
- package/dist/components/themes/english-certification/EnglishCertificationOptionSelector.js +1 -33
- package/dist/components/themes/english-certification/EnglishCertificationPassagePanel.js +1 -16
- package/dist/components/themes/english-certification/EnglishCertificationQuestionCard.js +1 -22
- package/dist/components/themes/english-certification/EnglishCertificationQuestionItem.js +1 -37
- package/dist/components/themes/english-certification/EnglishCertificationQuestionRenderer.js +1 -72
- package/dist/components/themes/english-certification/EnglishCertificationReadingSection.js +1 -90
- package/dist/components/themes/english-certification/EnglishCertificationSingleQuestion.js +1 -18
- package/dist/components/themes/english-certification/EnglishCertificationSingleQuestionList.js +1 -29
- package/dist/components/themes/english-certification/english-certification-theme.js +1 -58
- package/dist/components/themes/english-certification/grouped-question-utils.js +1 -218
- package/dist/components/themes/english-certification/index.js +1 -21
- package/dist/components/themes/index.js +1 -6
- package/dist/components/themes/summer-sky/SummerSkyAudioPlayer.js +1 -170
- package/dist/components/themes/summer-sky/SummerSkyLayout.js +1 -221
- package/dist/components/themes/summer-sky/SummerSkySubmitModal.js +1 -18
- package/dist/components/themes/summer-sky/SummerSkyWaitingRoom.js +1 -194
- package/dist/components/themes/types.js +1 -1
- package/dist/components/ui/alert-dialog.js +1 -26
- package/dist/components/ui/alert.js +1 -22
- package/dist/components/ui/avatar.js +1 -12
- package/dist/components/ui/badge.js +1 -38
- package/dist/components/ui/button.js +1 -35
- package/dist/components/ui/calendar.js +1 -76
- package/dist/components/ui/card.js +1 -16
- package/dist/components/ui/carousel.js +1 -91
- package/dist/components/ui/checkbox.js +1 -9
- package/dist/components/ui/collapsible.js +1 -13
- package/dist/components/ui/command.js +1 -34
- package/dist/components/ui/confirm-dialog.js +1 -49
- package/dist/components/ui/dialog.js +1 -23
- package/dist/components/ui/drawer.js +1 -35
- package/dist/components/ui/dropdown-menu.js +1 -33
- package/dist/components/ui/file-upload.js +1 -155
- package/dist/components/ui/form.js +1 -62
- package/dist/components/ui/hover-card.js +1 -10
- package/dist/components/ui/image-preview.js +1 -29
- package/dist/components/ui/input.js +1 -8
- package/dist/components/ui/label.js +1 -9
- package/dist/components/ui/language-switcher.js +1 -15
- package/dist/components/ui/multi-select.js +1 -90
- package/dist/components/ui/points-input.js +1 -33
- package/dist/components/ui/popover.js +1 -26
- package/dist/components/ui/progress.js +1 -16
- package/dist/components/ui/radio-group.js +1 -12
- package/dist/components/ui/scroll-area.js +1 -11
- package/dist/components/ui/select.js +1 -38
- package/dist/components/ui/separator.js +1 -8
- package/dist/components/ui/sheet.js +1 -40
- package/dist/components/ui/single-select.js +1 -76
- package/dist/components/ui/skeleton.js +1 -6
- package/dist/components/ui/switch.js +1 -8
- package/dist/components/ui/table.js +1 -20
- package/dist/components/ui/tabs.js +1 -17
- package/dist/components/ui/textarea.js +1 -8
- package/dist/components/ui/tooltip.js +1 -11
- package/dist/index.js +1 -19
- package/dist/mcp/config.js +1 -32
- package/dist/mcp/http-client.js +1 -48
- package/dist/mcp/index.js +1 -29
- package/dist/mcp/tools/_helpers.js +1 -25
- package/dist/mcp/tools/exams.js +1 -439
- package/dist/mcp/tools/index.js +1 -6
- package/dist/mcp/tools/results.js +1 -19
- package/dist/shared/constants/ApiConstant.js +1 -276
- package/dist/shared/constants/QueryKeyConstant.js +1 -112
- package/dist/shared/constants/exam-level.enum.js +1 -41
- package/dist/shared/constants/landings/placement-test.constants.js +1 -431
- package/dist/shared/constants/question-skills.js +1 -267
- package/dist/shared/constants/routes.js +1 -81
- package/dist/shared/index.js +1 -7
- package/dist/shared/lib/hooks/index.js +1 -10
- package/dist/shared/lib/hooks/useAlertDialog.js +1 -57
- package/dist/shared/lib/hooks/useAudioPlayCounter.js +1 -30
- package/dist/shared/lib/hooks/useAudioPreview.js +1 -81
- package/dist/shared/lib/hooks/useAudioUpload.js +1 -24
- package/dist/shared/lib/hooks/useConfirmDialog.js +1 -89
- package/dist/shared/lib/hooks/useDebouncedCallback.js +1 -32
- package/dist/shared/lib/hooks/useDelayedAutoPlay.js +1 -12
- package/dist/shared/lib/hooks/useFileUpload.js +1 -24
- package/dist/shared/lib/hooks/useImageUrl.js +1 -5
- package/dist/shared/lib/hooks/usePresignedFileUrl.js +1 -10
- package/dist/shared/lib/hooks/useReactHookForm.js +1 -31
- package/dist/shared/lib/hooks/useToast.js +1 -62
- package/dist/shared/lib/i18n/index.js +1 -54
- package/dist/shared/lib/i18n/messages/en/admin.js +1 -2104
- package/dist/shared/lib/i18n/messages/en/auth.js +1 -86
- package/dist/shared/lib/i18n/messages/en/common.js +1 -140
- package/dist/shared/lib/i18n/messages/en/file-upload.js +1 -18
- package/dist/shared/lib/i18n/messages/en/student.js +1 -133
- package/dist/shared/lib/i18n/messages/en/terms.js +1 -37
- package/dist/shared/lib/i18n/messages/en.js +1 -15
- package/dist/shared/lib/i18n/messages/vi/admin.js +1 -2104
- package/dist/shared/lib/i18n/messages/vi/auth.js +1 -86
- package/dist/shared/lib/i18n/messages/vi/common.js +1 -140
- package/dist/shared/lib/i18n/messages/vi/file-upload.js +1 -18
- package/dist/shared/lib/i18n/messages/vi/student.js +1 -133
- package/dist/shared/lib/i18n/messages/vi/terms.js +1 -37
- package/dist/shared/lib/i18n/messages/vi.js +1 -15
- package/dist/shared/lib/i18n/types.js +1 -1
- package/dist/shared/lib/stores/examQuestionStore.js +1 -212
- package/dist/shared/lib/stores/localeStore.js +1 -10
- package/dist/shared/lib/utils/public-page.js +1 -44
- package/dist/shared/lib/utils/question-transform-types.js +1 -1
- package/dist/shared/lib/utils/question-transform.js +1 -174
- package/dist/shared/lib/utils.js +1 -168
- package/dist/shared/lib/validation/choose-correct-answer.validation.js +1 -111
- package/dist/shared/lib/validation/label-the-picture.validation.js +1 -19
- package/dist/shared/lib/validation/listen-and-choose-from-answer-group.validation.js +1 -46
- package/dist/shared/lib/validation/listen-and-choose-objects-in-scene.validation.js +1 -21
- package/dist/shared/lib/validation/listen-and-drag-objects-into-scene.validation.js +1 -34
- package/dist/shared/lib/validation/listen-and-speak-answer.validation.js +1 -40
- package/dist/shared/lib/validation/listen-and-speak-compare-images.validation.js +1 -45
- package/dist/shared/lib/validation/listen-and-speak-image-group.validation.js +1 -19
- package/dist/shared/lib/validation/listen-and-speak-info-exchange.validation.js +1 -39
- package/dist/shared/lib/validation/listen-and-speak-odd-one-out.validation.js +1 -32
- package/dist/shared/lib/validation/listen-and-speak-question-list.validation.js +1 -19
- package/dist/shared/lib/validation/listen-and-speak-with-story-images.validation.js +1 -28
- package/dist/shared/lib/validation/look-picture-choose-correct-answer.validation.js +1 -12
- package/dist/shared/lib/validation/look-picture-fill-blank-choose-answer.validation.js +1 -47
- package/dist/shared/lib/validation/read-and-color-objects.validation.js +1 -29
- package/dist/shared/lib/validation/read-displayed-content.validation.js +1 -28
- package/dist/shared/lib/validation/speaking-conversation.validation.js +1 -75
- package/dist/shared/lib/validation/speaking-describe-image.validation.js +1 -31
- package/dist/shared/lib/validation/spontaneous-qa.validation.js +1 -48
- package/dist/shared/lib/validation/word-fill-paragraph-default.validation.js +1 -38
- package/dist/shared/lib/validation/word-fill-paragraph-with-options.validation.js +1 -39
- package/dist/shared/lib/validation/word-fill-paragraph.validation.js +1 -43
- package/dist/shared/lib/validation/word-ordering.validation.js +1 -33
- package/dist/shared/types/branch.types.js +1 -6
- package/dist/shared/types/common.types.js +1 -50
- package/dist/shared/types/entities/exam-schedule.types.js +1 -5
- package/dist/shared/types/entities/exam.types.js +1 -1
- package/dist/shared/types/entities/question.types.js +1 -4
- package/dist/shared/types/entities/result.types.js +1 -4
- package/dist/shared/types/entities/user.types.js +1 -4
- package/dist/shared/types/exam-taking.types.js +1 -2
- package/dist/shared/types/history.types.js +1 -34
- package/dist/shared/types/index.js +1 -1
- package/dist/shared/types/questions/choose-correct-adjective.js +1 -4
- package/dist/shared/types/questions/choose-the-correct-answer.js +1 -9
- package/dist/shared/types/questions/fill-missing-words-in-grid.js +1 -11
- package/dist/shared/types/questions/index.js +1 -40
- package/dist/shared/types/questions/listen-and-speak-answer.js +1 -1
- package/dist/shared/types/questions/listen-and-speak-compare-images.js +1 -1
- package/dist/shared/types/questions/listen-and-speak-image-group.js +1 -1
- package/dist/shared/types/questions/listen-and-speak-info-exchange.js +1 -3
- package/dist/shared/types/questions/listen-and-speak-odd-one-out.js +1 -3
- package/dist/shared/types/questions/listen-and-speak-question-list.js +1 -1
- package/dist/shared/types/questions/listen-and-speak-with-story-images.js +1 -3
- package/dist/shared/types/questions/look-picture-choose-correct-answer.js +1 -5
- package/dist/shared/types/questions/look-picture-fill-blank-choose-answer.js +1 -9
- package/dist/shared/types/questions/look-picture-fill-word-hint.js +1 -7
- package/dist/shared/types/questions/read-displayed-content.js +1 -1
- package/dist/shared/types/questions/speaking-conversation.js +1 -7
- package/dist/shared/types/questions/speaking-describe-image.js +1 -1
- package/dist/shared/types/questions/spontaneous-qa.js +1 -1
- package/dist/shared/types/questions/write-correct-verb-form.js +1 -12
- package/dist/shared/types/role.types.js +1 -1
- package/dist/shared/types/structure-management.js +1 -28
- package/dist/shared/types/student.types.js +1 -54
- package/dist/shared/types/teacher-dashboard-rooms.js +1 -2
- package/package.json +4 -2
|
@@ -1,16 +1 @@
|
|
|
1
|
-
export const transformSpeakingDescribeImage
|
|
2
|
-
const contentData = question.content;
|
|
3
|
-
const answerData = question.answer;
|
|
4
|
-
const expectedAnswers = answerData?.expectedAnswers || [];
|
|
5
|
-
const apiContent = {
|
|
6
|
-
question: contentData?.question || '',
|
|
7
|
-
imageUrl: contentData?.imageUrl || answerData?.imageUrl || '',
|
|
8
|
-
};
|
|
9
|
-
const result = {
|
|
10
|
-
apiContent,
|
|
11
|
-
apiCorrectAnswer: {
|
|
12
|
-
expectedAnswers: expectedAnswers,
|
|
13
|
-
},
|
|
14
|
-
};
|
|
15
|
-
return result;
|
|
16
|
-
};
|
|
1
|
+
export const transformSpeakingDescribeImage=e=>{const n=e.content,r=e.answer,t=r?.expectedAnswers||[];return{apiContent:{question:n?.question||"",imageUrl:n?.imageUrl||r?.imageUrl||""},apiCorrectAnswer:{expectedAnswers:t}}};
|
|
@@ -1,234 +1 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useRef, useEffect, useState } from 'react';
|
|
4
|
-
import { useParams } from 'next/navigation';
|
|
5
|
-
import { Button } from '../../../../components/ui/button';
|
|
6
|
-
import { Label } from '../../../../components/ui/label';
|
|
7
|
-
import { Input } from '../../../../components/ui/input';
|
|
8
|
-
import { Textarea } from '../../../../components/ui/textarea';
|
|
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 { Mic, Type, Volume2, BookOpen, Info, Loader2, Layers } from 'lucide-react';
|
|
13
|
-
import { toast } from 'sonner';
|
|
14
|
-
import { FileUpload } from '../../../../components/ui/file-upload';
|
|
15
|
-
import { CreatorGuide } from '../../components/CreatorGuide';
|
|
16
|
-
import { spontaneousQAFormSchema, } from '../../../../shared/lib/validation/spontaneous-qa.validation';
|
|
17
|
-
export function SpontaneousQACreator({ initialData, onSave, onChange, externalErrors, validationRef, }) {
|
|
18
|
-
const apiData = initialData;
|
|
19
|
-
const params = useParams();
|
|
20
|
-
const examRouteId = params?.examId || params?.id; // Try to get examId from route
|
|
21
|
-
// Backward compat: convert old sampleAnswer (string) / sampleAnswers to expectedAnswers (array)
|
|
22
|
-
const initSampleAnswers = (() => {
|
|
23
|
-
const newArr = apiData?.expectedAnswers || apiData?.correctAnswer?.expectedAnswers;
|
|
24
|
-
if (Array.isArray(newArr) && newArr.length > 0)
|
|
25
|
-
return newArr;
|
|
26
|
-
const arr = apiData?.sampleAnswers || apiData?.content?.sampleAnswers;
|
|
27
|
-
if (Array.isArray(arr) && arr.length > 0)
|
|
28
|
-
return arr;
|
|
29
|
-
const single = apiData?.sampleAnswer || apiData?.correctAnswer?.sampleAnswer;
|
|
30
|
-
if (single)
|
|
31
|
-
return [single];
|
|
32
|
-
return [''];
|
|
33
|
-
})();
|
|
34
|
-
const form = useReactHookForm({
|
|
35
|
-
defaultValues: {
|
|
36
|
-
inputType: apiData?.inputType || apiData?.content?.inputType || 'TEXT',
|
|
37
|
-
audioUrl: apiData?.audioUrl || apiData?.content?.audioUrl || '',
|
|
38
|
-
questionText: apiData?.questionText || apiData?.content?.questionText || '',
|
|
39
|
-
sampleAnswers: initSampleAnswers,
|
|
40
|
-
explanation: apiData?.explanation || '',
|
|
41
|
-
points: apiData?.points ?? 5,
|
|
42
|
-
},
|
|
43
|
-
zodSchema: spontaneousQAFormSchema,
|
|
44
|
-
mode: 'onBlur',
|
|
45
|
-
});
|
|
46
|
-
const previousInitialDataRef = useRef(initialData);
|
|
47
|
-
const isInitializingRef = useRef(false);
|
|
48
|
-
const onChangeRef = useRef(onChange);
|
|
49
|
-
const previousPayloadRef = useRef('');
|
|
50
|
-
const [isGeneratingAudio, setIsGeneratingAudio] = useState(false);
|
|
51
|
-
const [previewAudioUrl, setPreviewAudioUrl] = useState(null);
|
|
52
|
-
const [lastGeneratedText, setLastGeneratedText] = useState(apiData?.questionText || apiData?.content?.questionText || '');
|
|
53
|
-
useEffect(() => {
|
|
54
|
-
onChangeRef.current = onChange;
|
|
55
|
-
}, [onChange]);
|
|
56
|
-
const debouncedOnChange = useDebouncedCallback((data) => {
|
|
57
|
-
onChangeRef.current?.(data);
|
|
58
|
-
}, 300);
|
|
59
|
-
const watchedInputType = form.watch('inputType');
|
|
60
|
-
const watchedAudioUrl = form.watch('audioUrl');
|
|
61
|
-
const watchedQuestionText = form.watch('questionText');
|
|
62
|
-
const watchedSampleAnswers = form.watch('sampleAnswers') || [''];
|
|
63
|
-
const watchedExplanation = form.watch('explanation');
|
|
64
|
-
const watchedPoints = form.watch('points');
|
|
65
|
-
const { previewUrl: presignedAudioUrl } = usePresignedFileUrl(watchedAudioUrl || '');
|
|
66
|
-
const displayAudioUrl = previewAudioUrl || presignedAudioUrl;
|
|
67
|
-
// Expose validation ref for external trigger
|
|
68
|
-
useEffect(() => {
|
|
69
|
-
if (validationRef) {
|
|
70
|
-
validationRef.current = {
|
|
71
|
-
triggerValidation: async () => {
|
|
72
|
-
return await form.trigger();
|
|
73
|
-
},
|
|
74
|
-
getFormData: () => ({
|
|
75
|
-
inputType: watchedInputType,
|
|
76
|
-
audioUrl: watchedAudioUrl,
|
|
77
|
-
questionText: watchedQuestionText,
|
|
78
|
-
sampleAnswers: watchedSampleAnswers,
|
|
79
|
-
explanation: watchedExplanation,
|
|
80
|
-
points: watchedPoints,
|
|
81
|
-
}),
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
}, [validationRef, form, watchedInputType, watchedAudioUrl, watchedQuestionText, watchedSampleAnswers, watchedExplanation, watchedPoints]);
|
|
85
|
-
// Reset form when initialData changes externally
|
|
86
|
-
useEffect(() => {
|
|
87
|
-
const apiD = initialData;
|
|
88
|
-
const resetSampleAnswers = (() => {
|
|
89
|
-
const newArr = apiD?.expectedAnswers || apiD?.correctAnswer?.expectedAnswers;
|
|
90
|
-
if (Array.isArray(newArr) && newArr.length > 0)
|
|
91
|
-
return newArr;
|
|
92
|
-
const arr = apiD?.sampleAnswers || apiD?.content?.sampleAnswers;
|
|
93
|
-
if (Array.isArray(arr) && arr.length > 0)
|
|
94
|
-
return arr;
|
|
95
|
-
const single = apiD?.sampleAnswer || apiD?.correctAnswer?.sampleAnswer;
|
|
96
|
-
if (single)
|
|
97
|
-
return [single];
|
|
98
|
-
return [''];
|
|
99
|
-
})();
|
|
100
|
-
const normalizedInitialData = {
|
|
101
|
-
inputType: apiD?.inputType || apiD?.content?.inputType || 'TEXT',
|
|
102
|
-
audioUrl: apiD?.audioUrl || apiD?.content?.audioUrl || '',
|
|
103
|
-
questionText: apiD?.questionText || apiD?.content?.questionText || '',
|
|
104
|
-
sampleAnswers: resetSampleAnswers,
|
|
105
|
-
explanation: apiD?.explanation || '',
|
|
106
|
-
points: apiD?.points ?? 5,
|
|
107
|
-
};
|
|
108
|
-
const initialDataChanged = JSON.stringify(previousInitialDataRef.current) !== JSON.stringify(initialData);
|
|
109
|
-
if (!initialDataChanged) {
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
112
|
-
previousInitialDataRef.current = initialData;
|
|
113
|
-
const currentFormData = {
|
|
114
|
-
inputType: watchedInputType,
|
|
115
|
-
audioUrl: watchedAudioUrl,
|
|
116
|
-
questionText: watchedQuestionText,
|
|
117
|
-
sampleAnswers: watchedSampleAnswers,
|
|
118
|
-
explanation: watchedExplanation,
|
|
119
|
-
points: watchedPoints,
|
|
120
|
-
};
|
|
121
|
-
const isEchoFromOnChange = currentFormData.points === normalizedInitialData.points &&
|
|
122
|
-
currentFormData.explanation === normalizedInitialData.explanation &&
|
|
123
|
-
currentFormData.inputType === normalizedInitialData.inputType &&
|
|
124
|
-
currentFormData.audioUrl === normalizedInitialData.audioUrl &&
|
|
125
|
-
currentFormData.questionText === normalizedInitialData.questionText &&
|
|
126
|
-
JSON.stringify(currentFormData.sampleAnswers) === JSON.stringify(normalizedInitialData.sampleAnswers);
|
|
127
|
-
const wasJustSaved = initialData?.isCompleted === true;
|
|
128
|
-
if (isEchoFromOnChange && wasJustSaved) {
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
131
|
-
const shouldReset = !isEchoFromOnChange;
|
|
132
|
-
if (shouldReset) {
|
|
133
|
-
isInitializingRef.current = true;
|
|
134
|
-
form.reset(normalizedInitialData);
|
|
135
|
-
setLastGeneratedText(normalizedInitialData.questionText);
|
|
136
|
-
setTimeout(() => {
|
|
137
|
-
isInitializingRef.current = false;
|
|
138
|
-
}, 150);
|
|
139
|
-
}
|
|
140
|
-
}, [initialData, form, watchedInputType, watchedAudioUrl, watchedQuestionText, watchedSampleAnswers, watchedExplanation, watchedPoints]);
|
|
141
|
-
// Debounced onChange
|
|
142
|
-
useEffect(() => {
|
|
143
|
-
if (isInitializingRef.current) {
|
|
144
|
-
const currentPayload = JSON.stringify({
|
|
145
|
-
inputType: watchedInputType,
|
|
146
|
-
audioUrl: watchedAudioUrl,
|
|
147
|
-
questionText: watchedQuestionText,
|
|
148
|
-
sampleAnswers: watchedSampleAnswers,
|
|
149
|
-
explanation: watchedExplanation,
|
|
150
|
-
points: watchedPoints,
|
|
151
|
-
});
|
|
152
|
-
previousPayloadRef.current = currentPayload;
|
|
153
|
-
return;
|
|
154
|
-
}
|
|
155
|
-
const payload = {
|
|
156
|
-
inputType: watchedInputType,
|
|
157
|
-
audioUrl: watchedAudioUrl,
|
|
158
|
-
questionText: watchedQuestionText,
|
|
159
|
-
sampleAnswers: watchedSampleAnswers,
|
|
160
|
-
explanation: watchedExplanation,
|
|
161
|
-
points: watchedPoints,
|
|
162
|
-
};
|
|
163
|
-
const payloadString = JSON.stringify(payload);
|
|
164
|
-
if (previousPayloadRef.current !== payloadString) {
|
|
165
|
-
previousPayloadRef.current = payloadString;
|
|
166
|
-
debouncedOnChange(payload);
|
|
167
|
-
}
|
|
168
|
-
}, [
|
|
169
|
-
watchedInputType,
|
|
170
|
-
watchedAudioUrl,
|
|
171
|
-
watchedQuestionText,
|
|
172
|
-
watchedSampleAnswers,
|
|
173
|
-
watchedExplanation,
|
|
174
|
-
watchedPoints,
|
|
175
|
-
debouncedOnChange,
|
|
176
|
-
]);
|
|
177
|
-
const handleSave = form.handleSubmit((data) => {
|
|
178
|
-
onSave?.({
|
|
179
|
-
...data,
|
|
180
|
-
});
|
|
181
|
-
});
|
|
182
|
-
// Handlers for dynamic sample answers
|
|
183
|
-
const handleAddSampleAnswer = () => {
|
|
184
|
-
const current = watchedSampleAnswers;
|
|
185
|
-
form.setValue('sampleAnswers', [...current, ''], { shouldValidate: false });
|
|
186
|
-
};
|
|
187
|
-
const handleRemoveSampleAnswer = (index) => {
|
|
188
|
-
const current = [...watchedSampleAnswers];
|
|
189
|
-
current.splice(index, 1);
|
|
190
|
-
// Always keep at least one entry
|
|
191
|
-
if (current.length === 0) {
|
|
192
|
-
current.push('');
|
|
193
|
-
}
|
|
194
|
-
form.setValue('sampleAnswers', current, { shouldValidate: true });
|
|
195
|
-
};
|
|
196
|
-
const handleSampleAnswerChange = (index, value) => {
|
|
197
|
-
const current = [...watchedSampleAnswers];
|
|
198
|
-
current[index] = value;
|
|
199
|
-
form.setValue('sampleAnswers', current, { shouldValidate: true });
|
|
200
|
-
};
|
|
201
|
-
const handleGenerateAudio = async () => {
|
|
202
|
-
const text = form.getValues('questionText');
|
|
203
|
-
if (!text || text.trim() === '') {
|
|
204
|
-
toast.error('Vui lòng nhập nội dung câu hỏi trước khi tạo audio!');
|
|
205
|
-
return;
|
|
206
|
-
}
|
|
207
|
-
try {
|
|
208
|
-
setIsGeneratingAudio(true);
|
|
209
|
-
toast.error('TTS chưa được tích hợp trong component SDK');
|
|
210
|
-
return;
|
|
211
|
-
}
|
|
212
|
-
catch (error) {
|
|
213
|
-
toast.error(error?.response?.data?.message || 'Lỗi khi tạo audio');
|
|
214
|
-
}
|
|
215
|
-
finally {
|
|
216
|
-
setIsGeneratingAudio(false);
|
|
217
|
-
}
|
|
218
|
-
};
|
|
219
|
-
return (_jsxs("div", { className: "space-y-6", children: [_jsx(CreatorGuide, { colorScheme: "blue", steps: [
|
|
220
|
-
{ text: 'Chọn hình thức nhập câu hỏi: Upload Audio hoặc Nhập văn bản (Text → TTS)' },
|
|
221
|
-
{ text: 'Nếu chọn Audio: upload file audio câu hỏi. Nếu chọn Text: nhập nội dung câu hỏi', highlight: true },
|
|
222
|
-
{ text: 'Nhập đáp án mẫu (tùy chọn) — AI sẽ dựa vào đáp án mẫu để chấm điểm. Có thể thêm nhiều đáp án mẫu.' },
|
|
223
|
-
{ text: 'Thêm giải thích (tùy chọn)' },
|
|
224
|
-
], tips: [
|
|
225
|
-
'Học sinh chỉ nghe câu hỏi qua audio, KHÔNG nhìn thấy text câu hỏi.',
|
|
226
|
-
'Nếu chọn "Nhập văn bản", hệ thống sẽ tự động chuyển đổi thành giọng nói (TTS).',
|
|
227
|
-
] }), _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-blue-100 text-blue-700", children: _jsx(Layers, { className: "h-4 w-4" }) }), _jsx(CardTitle, { className: "text-lg font-semibold text-slate-800", children: "C\u1EA5u h\u00ECnh N\u1ED9i dung C\u00E2u h\u1ECFi & \u0110\u00E1p \u00E1n" })] }) }), _jsxs(CardContent, { className: "p-0", children: [_jsxs("div", { className: "p-6 space-y-8", children: [_jsxs("div", { className: "grid grid-cols-1 gap-6 md:grid-cols-2", children: [_jsxs("div", { className: "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", ...form.register('points', { valueAsNumber: true }), className: "w-24 border-slate-200 focus:border-blue-400 focus:ring-blue-100" }), form.hasError('points') && (_jsx("p", { className: "text-sm text-red-600", children: form.getFieldError('points') }))] }), _jsxs("div", { className: "space-y-3", children: [_jsx(Label, { className: "text-sm font-semibold text-slate-700 block", children: "H\u00ECnh th\u1EE9c nh\u1EADp c\u00E2u h\u1ECFi" }), _jsxs("div", { className: "flex w-fit rounded-lg border border-slate-200 bg-slate-100 p-1", children: [_jsxs("button", { type: "button", onClick: () => form.setValue('inputType', 'AUDIO', { shouldValidate: true }), className: `flex items-center gap-2 rounded-md px-4 py-1.5 text-sm font-medium transition-all ${watchedInputType === 'AUDIO'
|
|
228
|
-
? 'bg-white text-blue-700 shadow-sm'
|
|
229
|
-
: 'text-slate-500 hover:bg-slate-200/50 hover:text-slate-700'}`, children: [_jsx(Mic, { className: "h-3.5 w-3.5" }), " Upload Audio"] }), _jsxs("button", { type: "button", onClick: () => form.setValue('inputType', 'TEXT', { shouldValidate: true }), className: `flex items-center gap-2 rounded-md px-4 py-1.5 text-sm font-medium transition-all ${watchedInputType === 'TEXT'
|
|
230
|
-
? 'bg-white text-emerald-700 shadow-sm'
|
|
231
|
-
: 'text-slate-500 hover:bg-slate-200/50 hover:text-slate-700'}`, children: [_jsx(Type, { className: "h-3.5 w-3.5" }), " Nh\u1EADp v\u0103n b\u1EA3n (TTS)"] })] }), form.hasError('inputType') && (_jsx("p", { className: "text-sm text-red-600 mt-1", children: form.getFieldError('inputType') }))] })] }), _jsx("hr", { className: "border-slate-100" }), _jsx("div", { className: "space-y-4", children: _jsx("div", { className: "max-w-3xl", children: watchedInputType === 'AUDIO' ? (_jsxs("div", { className: "space-y-2 animate-in fade-in zoom-in-95 duration-200", children: [_jsx(Label, { className: "text-sm font-medium text-slate-700", children: "File audio c\u00E2u h\u1ECFi" }), _jsx(FileUpload, { id: "audioUrl", label: "", accept: "audio/*", value: watchedAudioUrl || '', onChange: (url) => {
|
|
232
|
-
form.setValue('audioUrl', url, { shouldValidate: true });
|
|
233
|
-
}, maxSize: 20, placeholder: "T\u1EA3i l\u00EAn file audio (MP3, WAV...)", autoUpload: true, prefix: "speaking/spontaneous-qa" }), form.hasError('audioUrl') && (_jsx("p", { className: "text-sm text-red-600 mt-1", children: form.getFieldError('audioUrl') })), _jsxs("div", { className: "flex items-start gap-2 rounded-lg border border-amber-100 bg-amber-50/60 px-3 py-2 mt-2", children: [_jsx(Info, { className: "h-4 w-4 text-amber-500 mt-0.5 flex-shrink-0" }), _jsx("p", { className: "text-xs text-amber-700", children: "Upload file audio ch\u1EE9a c\u00E2u h\u1ECFi. H\u1ECDc sinh s\u1EBD nghe audio n\u00E0y v\u00E0 tr\u1EA3 l\u1EDDi t\u1EF1 do." })] })] })) : (_jsxs("div", { className: "space-y-3 animate-in fade-in zoom-in-95 duration-200", children: [_jsx(Label, { className: "text-sm font-medium text-slate-700", children: "N\u1ED9i dung c\u00E2u h\u1ECFi (Text \u2192 TTS)" }), _jsx(Input, { ...form.register('questionText'), placeholder: "VD: What is your favorite food and why?", className: "border-slate-200 focus:border-blue-400 focus:ring-blue-100" }), form.hasError('questionText') && (_jsx("p", { className: "text-sm text-red-600 mt-1", children: form.getFieldError('questionText') })), _jsxs("div", { className: "flex items-start gap-2 rounded-lg border border-amber-100 bg-amber-50/60 px-3 py-2", children: [_jsx(Info, { className: "h-4 w-4 text-amber-500 mt-0.5 flex-shrink-0" }), _jsx("p", { className: "text-xs text-amber-700", children: "H\u1EC7 th\u1ED1ng s\u1EBD t\u1EF1 \u0111\u1ED9ng chuy\u1EC3n v\u0103n b\u1EA3n th\u00E0nh gi\u1ECDng n\u00F3i (TTS). Click n\u00FAt \"T\u1EA1o Audio\" \u0111\u1EC3 h\u1EC7 th\u1ED1ng x\u1EED l\u00FD." })] }), _jsxs("div", { className: "flex items-center gap-4 mt-2", children: [_jsxs(Button, { type: "button", variant: "default", size: "sm", onClick: handleGenerateAudio, disabled: isGeneratingAudio || !watchedQuestionText || watchedQuestionText === lastGeneratedText, className: "gap-2 rounded-none bg-gradient-to-r from-blue-600 to-indigo-600 text-white font-bold shadow-md shadow-blue-500/30 hover:shadow-lg hover:shadow-blue-500/40 hover:-translate-y-0.5 transition-all w-fit disabled:opacity-50 disabled:cursor-not-allowed disabled:transform-none", children: [isGeneratingAudio ? (_jsx(Loader2, { className: "h-3.5 w-3.5 animate-spin" })) : (_jsx(Volume2, { className: "h-3.5 w-3.5" })), "T\u1EA1o Audio"] }), displayAudioUrl && (_jsx("audio", { controls: true, src: displayAudioUrl, className: "h-8 max-w-[250px]" }))] })] })) }) })] }), 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))) })] }) }) }))] })] }), _jsxs(Card, { className: "border border-slate-200 bg-slate-50/50 shadow-sm", children: [_jsx(CardHeader, { className: "pb-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-6 w-6 items-center justify-center rounded-md bg-slate-200/50 text-slate-600", children: _jsx(BookOpen, { className: "h-3.5 w-3.5" }) }), _jsx(CardTitle, { className: "text-base font-semibold text-slate-800", children: "Gi\u1EA3i th\u00EDch chi ti\u1EBFt" })] }), _jsx("span", { className: "text-xs font-medium text-slate-500 uppercase tracking-wider", children: "T\u00F9y ch\u1ECDn" })] }) }), _jsx(CardContent, { children: _jsx(Textarea, { value: watchedExplanation || '', onChange: (e) => form.setValue('explanation', e.target.value, { shouldValidate: true }), placeholder: "Gi\u1EA3i th\u00EDch th\u00EAm v\u1EC1 c\u00E2u tr\u1EA3 l\u1EDDi k\u1EF3 v\u1ECDng, d\u00F9ng khi review k\u1EBFt qu\u1EA3...", rows: 3, className: "resize-none border-slate-200 bg-white focus:border-slate-300 focus:ring-slate-100" }) })] })] }));
|
|
234
|
-
}
|
|
1
|
+
"use client";import{jsx as e,jsxs as t}from"react/jsx-runtime";import{useRef as s,useEffect as n,useState as a}from"react";import{useParams as i}from"next/navigation";import{Button as r}from"../../../../components/ui/button";import{Label as o}from"../../../../components/ui/label";import{Input as l}from"../../../../components/ui/input";import{Textarea as c}from"../../../../components/ui/textarea";import{Card as d,CardContent as m,CardHeader as u,CardTitle as h}from"../../../../components/ui/card";import{useDebouncedCallback as p,usePresignedFileUrl as x}from"../../../../shared/lib/hooks";import{useReactHookForm as g}from"../../../../shared/lib/hooks/useReactHookForm";import{Mic as b,Type as f,Volume2 as N,BookOpen as w,Info as T,Loader2 as y,Layers as v}from"lucide-react";import{toast as A}from"sonner";import{FileUpload as U}from"../../../../components/ui/file-upload";import{CreatorGuide as q}from"../../components/CreatorGuide";import{spontaneousQAFormSchema as S}from"../../../../shared/lib/validation/spontaneous-qa.validation";export function SpontaneousQACreator({initialData:k,onSave:C,onChange:V,externalErrors:E,validationRef:O}){const F=k,z=(i(),(()=>{const e=F?.expectedAnswers||F?.correctAnswer?.expectedAnswers;if(Array.isArray(e)&&e.length>0)return e;const t=F?.sampleAnswers||F?.content?.sampleAnswers;if(Array.isArray(t)&&t.length>0)return t;const s=F?.sampleAnswer||F?.correctAnswer?.sampleAnswer;return s?[s]:[""]})()),D=g({defaultValues:{inputType:F?.inputType||F?.content?.inputType||"TEXT",audioUrl:F?.audioUrl||F?.content?.audioUrl||"",questionText:F?.questionText||F?.content?.questionText||"",sampleAnswers:z,explanation:F?.explanation||"",points:F?.points??5},zodSchema:S,mode:"onBlur"}),j=s(k),H=s(!1),J=s(V),G=s(""),[I,X]=a(!1),[K,R]=a(null),[W,B]=a(F?.questionText||F?.content?.questionText||"");n(()=>{J.current=V},[V]);const L=p(e=>{J.current?.(e)},300),M=D.watch("inputType"),P=D.watch("audioUrl"),Q=D.watch("questionText"),Y=D.watch("sampleAnswers")||[""],Z=D.watch("explanation"),$=D.watch("points"),{previewUrl:_}=x(P||""),ee=K||_;n(()=>{O&&(O.current={triggerValidation:async()=>await D.trigger(),getFormData:()=>({inputType:M,audioUrl:P,questionText:Q,sampleAnswers:Y,explanation:Z,points:$})})},[O,D,M,P,Q,Y,Z,$]),n(()=>{const e=k,t=(()=>{const t=e?.expectedAnswers||e?.correctAnswer?.expectedAnswers;if(Array.isArray(t)&&t.length>0)return t;const s=e?.sampleAnswers||e?.content?.sampleAnswers;if(Array.isArray(s)&&s.length>0)return s;const n=e?.sampleAnswer||e?.correctAnswer?.sampleAnswer;return n?[n]:[""]})(),s={inputType:e?.inputType||e?.content?.inputType||"TEXT",audioUrl:e?.audioUrl||e?.content?.audioUrl||"",questionText:e?.questionText||e?.content?.questionText||"",sampleAnswers:t,explanation:e?.explanation||"",points:e?.points??5};if(!(JSON.stringify(j.current)!==JSON.stringify(k)))return;j.current=k;const n={inputType:M,audioUrl:P,questionText:Q,sampleAnswers:Y,explanation:Z,points:$},a=n.points===s.points&&n.explanation===s.explanation&&n.inputType===s.inputType&&n.audioUrl===s.audioUrl&&n.questionText===s.questionText&&JSON.stringify(n.sampleAnswers)===JSON.stringify(s.sampleAnswers);if(a&&!0===k?.isCompleted)return;!a&&(H.current=!0,D.reset(s),B(s.questionText),setTimeout(()=>{H.current=!1},150))},[k,D,M,P,Q,Y,Z,$]),n(()=>{if(H.current){const e=JSON.stringify({inputType:M,audioUrl:P,questionText:Q,sampleAnswers:Y,explanation:Z,points:$});return void(G.current=e)}const e={inputType:M,audioUrl:P,questionText:Q,sampleAnswers:Y,explanation:Z,points:$},t=JSON.stringify(e);G.current!==t&&(G.current=t,L(e))},[M,P,Q,Y,Z,$,L]);D.handleSubmit(e=>{C?.({...e})});return t("div",{className:"space-y-6",children:[e(q,{colorScheme:"blue",steps:[{text:"Chọn hình thức nhập câu hỏi: Upload Audio hoặc Nhập văn bản (Text → TTS)"},{text:"Nếu chọn Audio: upload file audio câu hỏi. Nếu chọn Text: nhập nội dung câu hỏi",highlight:!0},{text:"Nhập đáp án mẫu (tùy chọn) — AI sẽ dựa vào đáp án mẫu để chấm điểm. Có thể thêm nhiều đáp án mẫu."},{text:"Thêm giải thích (tùy chọn)"}],tips:["Học sinh chỉ nghe câu hỏi qua audio, KHÔNG nhìn thấy text câu hỏi.",'Nếu chọn "Nhập văn bản", hệ thống sẽ tự động chuyển đổi thành giọng nói (TTS).']}),t(d,{className:"overflow-hidden border border-slate-200 bg-white shadow-sm",children:[e(u,{className:"border-b border-slate-100 bg-slate-50/50 pb-4",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-blue-100 text-blue-700",children:e(v,{className:"h-4 w-4"})}),e(h,{className:"text-lg font-semibold text-slate-800",children:"Cấu hình Nội dung Câu hỏi & Đáp án"})]})}),t(m,{className:"p-0",children:[t("div",{className:"p-6 space-y-8",children:[t("div",{className:"grid grid-cols-1 gap-6 md:grid-cols-2",children:[t("div",{className:"space-y-1.5 animate-in fade-in zoom-in-95 duration-200",children:[t(o,{htmlFor:"points",className:"text-sm font-semibold text-slate-700",children:["Điểm số ",e("span",{className:"text-red-500",children:"*"})]}),e(l,{id:"points",type:"number",step:"1",min:"1",...D.register("points",{valueAsNumber:!0}),className:"w-24 border-slate-200 focus:border-blue-400 focus:ring-blue-100"}),D.hasError("points")&&e("p",{className:"text-sm text-red-600",children:D.getFieldError("points")})]}),t("div",{className:"space-y-3",children:[e(o,{className:"text-sm font-semibold text-slate-700 block",children:"Hình thức nhập câu hỏi"}),t("div",{className:"flex w-fit rounded-lg border border-slate-200 bg-slate-100 p-1",children:[t("button",{type:"button",onClick:()=>D.setValue("inputType","AUDIO",{shouldValidate:!0}),className:"flex items-center gap-2 rounded-md px-4 py-1.5 text-sm font-medium transition-all "+("AUDIO"===M?"bg-white text-blue-700 shadow-sm":"text-slate-500 hover:bg-slate-200/50 hover:text-slate-700"),children:[e(b,{className:"h-3.5 w-3.5"})," Upload Audio"]}),t("button",{type:"button",onClick:()=>D.setValue("inputType","TEXT",{shouldValidate:!0}),className:"flex items-center gap-2 rounded-md px-4 py-1.5 text-sm font-medium transition-all "+("TEXT"===M?"bg-white text-emerald-700 shadow-sm":"text-slate-500 hover:bg-slate-200/50 hover:text-slate-700"),children:[e(f,{className:"h-3.5 w-3.5"})," Nhập văn bản (TTS)"]})]}),D.hasError("inputType")&&e("p",{className:"text-sm text-red-600 mt-1",children:D.getFieldError("inputType")})]})]}),e("hr",{className:"border-slate-100"}),e("div",{className:"space-y-4",children:e("div",{className:"max-w-3xl",children:t("div","AUDIO"===M?{className:"space-y-2 animate-in fade-in zoom-in-95 duration-200",children:[e(o,{className:"text-sm font-medium text-slate-700",children:"File audio câu hỏi"}),e(U,{id:"audioUrl",label:"",accept:"audio/*",value:P||"",onChange:e=>{D.setValue("audioUrl",e,{shouldValidate:!0})},maxSize:20,placeholder:"Tải lên file audio (MP3, WAV...)",autoUpload:!0,prefix:"speaking/spontaneous-qa"}),D.hasError("audioUrl")&&e("p",{className:"text-sm text-red-600 mt-1",children:D.getFieldError("audioUrl")}),t("div",{className:"flex items-start gap-2 rounded-lg border border-amber-100 bg-amber-50/60 px-3 py-2 mt-2",children:[e(T,{className:"h-4 w-4 text-amber-500 mt-0.5 flex-shrink-0"}),e("p",{className:"text-xs text-amber-700",children:"Upload file audio chứa câu hỏi. Học sinh sẽ nghe audio này và trả lời tự do."})]})]}:{className:"space-y-3 animate-in fade-in zoom-in-95 duration-200",children:[e(o,{className:"text-sm font-medium text-slate-700",children:"Nội dung câu hỏi (Text → TTS)"}),e(l,{...D.register("questionText"),placeholder:"VD: What is your favorite food and why?",className:"border-slate-200 focus:border-blue-400 focus:ring-blue-100"}),D.hasError("questionText")&&e("p",{className:"text-sm text-red-600 mt-1",children:D.getFieldError("questionText")}),t("div",{className:"flex items-start gap-2 rounded-lg border border-amber-100 bg-amber-50/60 px-3 py-2",children:[e(T,{className:"h-4 w-4 text-amber-500 mt-0.5 flex-shrink-0"}),e("p",{className:"text-xs text-amber-700",children:'Hệ thống sẽ tự động chuyển văn bản thành giọng nói (TTS). Click nút "Tạo Audio" để hệ thống xử lý.'})]}),t("div",{className:"flex items-center gap-4 mt-2",children:[t(r,{type:"button",variant:"default",size:"sm",onClick:async()=>{const e=D.getValues("questionText");if(e&&""!==e.trim())try{return X(!0),void A.error("TTS chưa được tích hợp trong component SDK")}catch(e){A.error(e?.response?.data?.message||"Lỗi khi tạo audio")}finally{X(!1)}else A.error("Vui lòng nhập nội dung câu hỏi trước khi tạo audio!")},disabled:I||!Q||Q===W,className:"gap-2 rounded-none bg-gradient-to-r from-blue-600 to-indigo-600 text-white font-bold shadow-md shadow-blue-500/30 hover:shadow-lg hover:shadow-blue-500/40 hover:-translate-y-0.5 transition-all w-fit disabled:opacity-50 disabled:cursor-not-allowed disabled:transform-none",children:[I?e(y,{className:"h-3.5 w-3.5 animate-spin"}):e(N,{className:"h-3.5 w-3.5"}),"Tạo Audio"]}),ee&&e("audio",{controls:!0,src:ee,className:"h-8 max-w-[250px]"})]})]})})})]}),E&&E.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:t("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:E.map((t,s)=>e("li",{children:t},s))})]})})})]})]}),t(d,{className:"border border-slate-200 bg-slate-50/50 shadow-sm",children:[e(u,{className:"pb-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-6 w-6 items-center justify-center rounded-md bg-slate-200/50 text-slate-600",children:e(w,{className:"h-3.5 w-3.5"})}),e(h,{className:"text-base font-semibold text-slate-800",children:"Giải thích chi tiết"})]}),e("span",{className:"text-xs font-medium text-slate-500 uppercase tracking-wider",children:"Tùy chọn"})]})}),e(m,{children:e(c,{value:Z||"",onChange:e=>D.setValue("explanation",e.target.value,{shouldValidate:!0}),placeholder:"Giải thích thêm về câu trả lời kỳ vọng, dùng khi review kết quả...",rows:3,className:"resize-none border-slate-200 bg-white focus:border-slate-300 focus:ring-slate-100"})})]})]})}
|
|
@@ -1,93 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
export const spontaneousQARegistration = {
|
|
3
|
-
component: SpontaneousQACreator,
|
|
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 || 5);
|
|
11
|
-
const explanation = initialData?.explanation || content?.explanation || answer?.explanation || '';
|
|
12
|
-
// Backward compat: convert old sampleAnswer/sampleAnswers to expectedAnswers (array)
|
|
13
|
-
const normalizeSampleAnswers = () => {
|
|
14
|
-
const newArr = answer?.expectedAnswers;
|
|
15
|
-
if (Array.isArray(newArr) && newArr.length > 0)
|
|
16
|
-
return newArr;
|
|
17
|
-
const arr = answer?.sampleAnswers;
|
|
18
|
-
if (Array.isArray(arr) && arr.length > 0)
|
|
19
|
-
return arr;
|
|
20
|
-
const single = answer?.sampleAnswer;
|
|
21
|
-
if (single)
|
|
22
|
-
return [single];
|
|
23
|
-
return [''];
|
|
24
|
-
};
|
|
25
|
-
if (content && typeof content === 'object' && content.inputType !== undefined) {
|
|
26
|
-
creatorData = {
|
|
27
|
-
inputType: content.inputType || 'TEXT',
|
|
28
|
-
audioUrl: content.audioUrl || '',
|
|
29
|
-
questionText: content.questionText || '',
|
|
30
|
-
sampleAnswers: normalizeSampleAnswers(),
|
|
31
|
-
explanation,
|
|
32
|
-
points,
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
else if (answer && typeof answer === 'object') {
|
|
36
|
-
creatorData = {
|
|
37
|
-
...answer,
|
|
38
|
-
inputType: answer.inputType || content?.inputType || 'TEXT',
|
|
39
|
-
audioUrl: answer.audioUrl || content?.audioUrl || '',
|
|
40
|
-
questionText: answer.questionText || content?.questionText || '',
|
|
41
|
-
sampleAnswers: normalizeSampleAnswers(),
|
|
42
|
-
explanation,
|
|
43
|
-
points,
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
return creatorData;
|
|
47
|
-
},
|
|
48
|
-
getExtraProps: (ctx) => ({
|
|
49
|
-
onUnsavedChangesChange: ctx.onUnsavedChangesChange,
|
|
50
|
-
validationRef: ctx.validationRef,
|
|
51
|
-
}),
|
|
52
|
-
wrapOnSave: (data, ctx) => {
|
|
53
|
-
// Filter out empty sample answers
|
|
54
|
-
const expectedAnswers = (data.sampleAnswers || []).filter((s) => s && s.trim() !== '');
|
|
55
|
-
return {
|
|
56
|
-
type: ctx.questionType,
|
|
57
|
-
content: {
|
|
58
|
-
...(typeof ctx.state.content === 'object' && ctx.state.content !== null ? ctx.state.content : {}),
|
|
59
|
-
inputType: data.inputType,
|
|
60
|
-
audioUrl: data.audioUrl || '',
|
|
61
|
-
questionText: data.inputType === 'TEXT' ? data.questionText : undefined,
|
|
62
|
-
},
|
|
63
|
-
points: data?.points ?? ctx.state.points ?? 5,
|
|
64
|
-
level: ctx.state.level,
|
|
65
|
-
difficulty: ctx.state.difficulty,
|
|
66
|
-
skill: ctx.state.skill,
|
|
67
|
-
answer: {
|
|
68
|
-
expectedAnswers,
|
|
69
|
-
},
|
|
70
|
-
explanation: data?.explanation || '',
|
|
71
|
-
};
|
|
72
|
-
},
|
|
73
|
-
wrapOnChange: (data, ctx) => {
|
|
74
|
-
const expectedAnswers = data.sampleAnswers || [''];
|
|
75
|
-
return {
|
|
76
|
-
type: ctx.questionType,
|
|
77
|
-
content: {
|
|
78
|
-
...(typeof ctx.state.content === 'object' && ctx.state.content !== null ? ctx.state.content : {}),
|
|
79
|
-
inputType: data.inputType,
|
|
80
|
-
audioUrl: data.audioUrl || '',
|
|
81
|
-
questionText: data.inputType === 'TEXT' ? data.questionText : undefined,
|
|
82
|
-
},
|
|
83
|
-
points: data?.points ?? ctx.state.points ?? 5,
|
|
84
|
-
level: ctx.state.level,
|
|
85
|
-
difficulty: ctx.state.difficulty,
|
|
86
|
-
skill: ctx.state.skill,
|
|
87
|
-
answer: {
|
|
88
|
-
expectedAnswers,
|
|
89
|
-
},
|
|
90
|
-
explanation: data?.explanation || '',
|
|
91
|
-
};
|
|
92
|
-
},
|
|
93
|
-
};
|
|
1
|
+
import{SpontaneousQACreator as t}from"./SpontaneousQACreator";export const spontaneousQARegistration={component:t,transformInitialData:t=>{let e=t?.answer;const n=t?.content,o=t?.answer||t?.correctAnswer||t?.correct_answer,s=t?.total_points?parseFloat(t.total_points):t?.points||n?.points||o?.points||5,i=t?.explanation||n?.explanation||o?.explanation||"",a=()=>{const t=o?.expectedAnswers;if(Array.isArray(t)&&t.length>0)return t;const e=o?.sampleAnswers;if(Array.isArray(e)&&e.length>0)return e;const n=o?.sampleAnswer;return n?[n]:[""]};return n&&"object"==typeof n&&void 0!==n.inputType?e={inputType:n.inputType||"TEXT",audioUrl:n.audioUrl||"",questionText:n.questionText||"",sampleAnswers:a(),explanation:i,points:s}:o&&"object"==typeof o&&(e={...o,inputType:o.inputType||n?.inputType||"TEXT",audioUrl:o.audioUrl||n?.audioUrl||"",questionText:o.questionText||n?.questionText||"",sampleAnswers:a(),explanation:i,points:s}),e},getExtraProps:t=>({onUnsavedChangesChange:t.onUnsavedChangesChange,validationRef:t.validationRef}),wrapOnSave:(t,e)=>{const n=(t.sampleAnswers||[]).filter(t=>t&&""!==t.trim());return{type:e.questionType,content:{..."object"==typeof e.state.content&&null!==e.state.content?e.state.content:{},inputType:t.inputType,audioUrl:t.audioUrl||"",questionText:"TEXT"===t.inputType?t.questionText:void 0},points:t?.points??e.state.points??5,level:e.state.level,difficulty:e.state.difficulty,skill:e.state.skill,answer:{expectedAnswers:n},explanation:t?.explanation||""}},wrapOnChange:(t,e)=>{const n=t.sampleAnswers||[""];return{type:e.questionType,content:{..."object"==typeof e.state.content&&null!==e.state.content?e.state.content:{},inputType:t.inputType,audioUrl:t.audioUrl||"",questionText:"TEXT"===t.inputType?t.questionText:void 0},points:t?.points??e.state.points??5,level:e.state.level,difficulty:e.state.difficulty,skill:e.state.skill,answer:{expectedAnswers:n},explanation:t?.explanation||""}}};
|
|
@@ -1,34 +1 @@
|
|
|
1
|
-
export const transformSpontaneousQA = (
|
|
2
|
-
const contentData = question.content;
|
|
3
|
-
const answerData = question.answer;
|
|
4
|
-
const inputType = contentData?.inputType || 'TEXT';
|
|
5
|
-
const apiContent = {
|
|
6
|
-
inputType,
|
|
7
|
-
// Always include audioUrl — populated when AUDIO, empty string when TEXT
|
|
8
|
-
audioUrl: contentData?.audioUrl || '',
|
|
9
|
-
};
|
|
10
|
-
// Include questionText when inputType is TEXT
|
|
11
|
-
if (inputType === 'TEXT' && contentData?.questionText) {
|
|
12
|
-
apiContent.questionText = contentData.questionText;
|
|
13
|
-
}
|
|
14
|
-
// Normalize: support both expectedAnswers, sampleAnswers (array) and legacy sampleAnswer (string)
|
|
15
|
-
const expectedAnswers = (() => {
|
|
16
|
-
const newArr = answerData?.expectedAnswers;
|
|
17
|
-
if (Array.isArray(newArr))
|
|
18
|
-
return newArr.filter((s) => s && s.trim() !== '');
|
|
19
|
-
const arr = answerData?.sampleAnswers;
|
|
20
|
-
if (Array.isArray(arr))
|
|
21
|
-
return arr.filter((s) => s && s.trim() !== '');
|
|
22
|
-
const single = answerData?.sampleAnswer;
|
|
23
|
-
if (single && single.trim() !== '')
|
|
24
|
-
return [single];
|
|
25
|
-
return [];
|
|
26
|
-
})();
|
|
27
|
-
const apiCorrectAnswer = {
|
|
28
|
-
expectedAnswers,
|
|
29
|
-
};
|
|
30
|
-
return {
|
|
31
|
-
apiContent,
|
|
32
|
-
apiCorrectAnswer,
|
|
33
|
-
};
|
|
34
|
-
};
|
|
1
|
+
export const transformSpontaneousQA=r=>{const e=r.content,t=r.answer,n=e?.inputType||"TEXT",s={inputType:n,audioUrl:e?.audioUrl||""};"TEXT"===n&&e?.questionText&&(s.questionText=e.questionText);return{apiContent:s,apiCorrectAnswer:{expectedAnswers:(()=>{const r=t?.expectedAnswers;if(Array.isArray(r))return r.filter(r=>r&&""!==r.trim());const e=t?.sampleAnswers;if(Array.isArray(e))return e.filter(r=>r&&""!==r.trim());const n=t?.sampleAnswer;return n&&""!==n.trim()?[n]:[]})()}}};
|
|
@@ -1,75 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useState, useEffect } from 'react';
|
|
4
|
-
import { Check, X, CircleHelp } from 'lucide-react';
|
|
5
|
-
import { usePresignedFileUrl } from '../../../../shared/lib/hooks';
|
|
6
|
-
import { cn } from '../../../../shared/lib/utils';
|
|
7
|
-
export function TrueFalseClient({ questionData, onSubmit, isReviewMode = false, userAnswer, }) {
|
|
8
|
-
const [selectedAnswer, setSelectedAnswer] = useState(userAnswer !== undefined ? userAnswer : undefined);
|
|
9
|
-
// Presigned URL for image
|
|
10
|
-
const { previewUrl: imagePreviewUrl, isLoading } = usePresignedFileUrl(questionData.imageUrl || '');
|
|
11
|
-
// Sync userAnswer
|
|
12
|
-
useEffect(() => {
|
|
13
|
-
if (userAnswer !== undefined) {
|
|
14
|
-
setSelectedAnswer(userAnswer);
|
|
15
|
-
}
|
|
16
|
-
}, [userAnswer]);
|
|
17
|
-
// Handle answer change
|
|
18
|
-
const handleAnswerChange = (value) => {
|
|
19
|
-
if (isReviewMode)
|
|
20
|
-
return;
|
|
21
|
-
setSelectedAnswer(value);
|
|
22
|
-
onSubmit?.(value);
|
|
23
|
-
};
|
|
24
|
-
const hasAnswered = selectedAnswer !== undefined;
|
|
25
|
-
const isAnswerCorrect = isReviewMode && selectedAnswer === questionData.answer;
|
|
26
|
-
return (_jsx("div", { className: "space-y-4", children: _jsxs("div", { className: "overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-gray-100 bg-gradient-to-r from-gray-50 to-white 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-emerald-500 to-teal-600 text-white shadow-sm", children: _jsx(CircleHelp, { className: "h-4 w-4" }) }), _jsx("span", { className: "text-sm font-medium text-gray-700", children: "\u0110\u00FAng / Sai" })] }), isReviewMode && hasAnswered && (_jsxs("div", { className: cn('flex items-center gap-1.5 rounded-full px-3 py-1 text-xs font-semibold', isAnswerCorrect
|
|
27
|
-
? 'bg-green-100 text-green-700'
|
|
28
|
-
: 'bg-red-100 text-red-700'), children: [isAnswerCorrect ? (_jsx(Check, { className: "h-3.5 w-3.5" })) : (_jsx(X, { className: "h-3.5 w-3.5" })), isAnswerCorrect ? 'Đúng' : 'Sai'] }))] }), _jsxs("div", { className: "space-y-5 p-5", children: [_jsx("div", { className: "rounded-lg border border-blue-100 bg-gradient-to-r from-blue-50 to-indigo-50 p-4", children: _jsx("h3", { className: "text-base font-semibold leading-relaxed text-gray-800", children: questionData.question || 'Chưa nhập câu hỏi' }) }), (imagePreviewUrl || questionData.imageUrl) && (_jsxs("div", { className: "overflow-hidden rounded-lg border border-gray-200 bg-gradient-to-br from-slate-50 to-gray-100", children: [_jsx("div", { className: "border-b border-gray-100 px-3 py-1.5", children: _jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx("div", { className: "h-2 w-2 rounded-full bg-red-400" }), _jsx("div", { className: "h-2 w-2 rounded-full bg-yellow-400" }), _jsx("div", { className: "h-2 w-2 rounded-full bg-green-400" }), _jsx("span", { className: "ml-2 text-xs font-medium text-gray-500", children: "H\u00ECnh \u1EA3nh" })] }) }), _jsx("div", { className: "flex items-center justify-center p-4", children: isLoading ? (_jsx("div", { className: "flex h-48 items-center justify-center", children: _jsx("div", { className: "h-8 w-8 animate-spin rounded-full border-4 border-gray-200 border-t-blue-500" }) })) : (
|
|
29
|
-
/* eslint-disable-next-line @next/next/no-img-element */
|
|
30
|
-
_jsx("img", { src: imagePreviewUrl || questionData.imageUrl, alt: "Question illustration", className: "max-h-56 w-auto rounded-md object-contain" })) })] })), _jsxs("div", { className: "space-y-2.5", children: [_jsx("span", { className: "text-xs font-medium uppercase tracking-wider text-gray-500", children: "Ch\u1ECDn \u0111\u00E1p \u00E1n" }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("button", { type: "button", onClick: () => handleAnswerChange(true), disabled: isReviewMode, className: cn('group flex items-center justify-center gap-3 rounded-xl border-2 p-4 transition-all', isReviewMode
|
|
31
|
-
? questionData.answer === true
|
|
32
|
-
? 'border-green-400 bg-green-50'
|
|
33
|
-
: selectedAnswer === true
|
|
34
|
-
? 'border-red-400 bg-red-50'
|
|
35
|
-
: 'border-gray-200 bg-gray-50'
|
|
36
|
-
: selectedAnswer === true
|
|
37
|
-
? 'border-emerald-500 bg-emerald-50 shadow-sm ring-2 ring-emerald-100'
|
|
38
|
-
: 'border-gray-200 bg-white hover:border-emerald-300 hover:bg-emerald-50/50 hover:shadow-sm'), children: [_jsx("div", { className: cn('flex h-9 w-9 items-center justify-center rounded-full text-sm font-bold transition-all', isReviewMode
|
|
39
|
-
? questionData.answer === true
|
|
40
|
-
? 'bg-green-500 text-white'
|
|
41
|
-
: selectedAnswer === true
|
|
42
|
-
? 'bg-red-500 text-white'
|
|
43
|
-
: 'bg-gray-200 text-gray-600'
|
|
44
|
-
: selectedAnswer === true
|
|
45
|
-
? 'bg-emerald-500 text-white shadow-sm'
|
|
46
|
-
: 'bg-gray-100 text-gray-500 group-hover:bg-emerald-100 group-hover:text-emerald-600'), children: _jsx(Check, { className: "h-4 w-4" }) }), _jsx("span", { className: cn('text-base font-semibold', isReviewMode && questionData.answer === true
|
|
47
|
-
? 'text-green-800'
|
|
48
|
-
: isReviewMode && selectedAnswer === true
|
|
49
|
-
? 'text-red-800'
|
|
50
|
-
: selectedAnswer === true
|
|
51
|
-
? 'text-emerald-700'
|
|
52
|
-
: 'text-gray-600'), children: "\u0110\u00FAng (True)" }), isReviewMode && (questionData.answer === true || selectedAnswer === true) && (_jsx("div", { className: cn('flex h-6 w-6 items-center justify-center rounded-full', questionData.answer === true ? 'bg-green-500' : 'bg-red-500'), children: questionData.answer === true ? (_jsx(Check, { className: "h-4 w-4 text-white" })) : (_jsx(X, { className: "h-4 w-4 text-white" })) }))] }), _jsxs("button", { type: "button", onClick: () => handleAnswerChange(false), disabled: isReviewMode, className: cn('group flex items-center justify-center gap-3 rounded-xl border-2 p-4 transition-all', isReviewMode
|
|
53
|
-
? questionData.answer === false
|
|
54
|
-
? 'border-green-400 bg-green-50'
|
|
55
|
-
: selectedAnswer === false
|
|
56
|
-
? 'border-red-400 bg-red-50'
|
|
57
|
-
: 'border-gray-200 bg-gray-50'
|
|
58
|
-
: selectedAnswer === false
|
|
59
|
-
? 'border-red-500 bg-red-50 shadow-sm ring-2 ring-red-100'
|
|
60
|
-
: 'border-gray-200 bg-white hover:border-red-300 hover:bg-red-50/50 hover:shadow-sm'), children: [_jsx("div", { className: cn('flex h-9 w-9 items-center justify-center rounded-full text-sm font-bold transition-all', isReviewMode
|
|
61
|
-
? questionData.answer === false
|
|
62
|
-
? 'bg-green-500 text-white'
|
|
63
|
-
: selectedAnswer === false
|
|
64
|
-
? 'bg-red-500 text-white'
|
|
65
|
-
: 'bg-gray-200 text-gray-600'
|
|
66
|
-
: selectedAnswer === false
|
|
67
|
-
? 'bg-red-500 text-white shadow-sm'
|
|
68
|
-
: 'bg-gray-100 text-gray-500 group-hover:bg-red-100 group-hover:text-red-600'), children: _jsx(X, { className: "h-4 w-4" }) }), _jsx("span", { className: cn('text-base font-semibold', isReviewMode && questionData.answer === false
|
|
69
|
-
? 'text-green-800'
|
|
70
|
-
: isReviewMode && selectedAnswer === false
|
|
71
|
-
? 'text-red-800'
|
|
72
|
-
: selectedAnswer === false
|
|
73
|
-
? 'text-red-700'
|
|
74
|
-
: 'text-gray-600'), children: "Sai (False)" }), isReviewMode && (questionData.answer === false || selectedAnswer === false) && (_jsx("div", { className: cn('flex h-6 w-6 items-center justify-center rounded-full', questionData.answer === false ? 'bg-green-500' : 'bg-red-500'), children: questionData.answer === false ? (_jsx(Check, { className: "h-4 w-4 text-white" })) : (_jsx(X, { className: "h-4 w-4 text-white" })) }))] })] })] }), !isReviewMode && !hasAnswered && (_jsxs("div", { className: "flex items-center gap-2 rounded-lg border border-amber-200 bg-gradient-to-r from-amber-50 to-yellow-50 p-3", children: [_jsx("div", { className: "flex h-5 w-5 flex-shrink-0 items-center justify-center rounded-full bg-amber-400 text-white", children: _jsx("span", { className: "text-xs font-bold", children: "!" }) }), _jsx("span", { className: "text-sm text-amber-800", children: "Vui l\u00F2ng ch\u1ECDn \u0111\u00E1p \u00E1n" })] })), isReviewMode && questionData.explanation && (_jsxs("div", { className: "rounded-lg border border-blue-200 bg-blue-50 p-4", children: [_jsx("p", { className: "text-sm font-semibold text-blue-800", children: "Gi\u1EA3i th\u00EDch:" }), _jsx("p", { className: "mt-1 text-sm text-blue-700", children: questionData.explanation })] }))] })] }) }));
|
|
75
|
-
}
|
|
1
|
+
"use client";import{jsx as e,jsxs as r}from"react/jsx-runtime";import{useState as t,useEffect as a}from"react";import{Check as s,X as d,CircleHelp as l}from"lucide-react";import{usePresignedFileUrl as n}from"../../../../shared/lib/hooks";import{cn as i}from"../../../../shared/lib/utils";export function TrueFalseClient({questionData:o,onSubmit:c,isReviewMode:m=!1,userAnswer:g}){const[b,h]=t(void 0!==g?g:void 0),{previewUrl:x,isLoading:u}=n(o.imageUrl||"");a(()=>{void 0!==g&&h(g)},[g]);const w=e=>{m||(h(e),c?.(e))},f=void 0!==b,p=m&&b===o.answer;return e("div",{className:"space-y-4",children:r("div",{className:"overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm",children:[r("div",{className:"flex items-center justify-between border-b border-gray-100 bg-gradient-to-r from-gray-50 to-white 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-emerald-500 to-teal-600 text-white shadow-sm",children:e(l,{className:"h-4 w-4"})}),e("span",{className:"text-sm font-medium text-gray-700",children:"Đúng / Sai"})]}),m&&f&&r("div",{className:i("flex items-center gap-1.5 rounded-full px-3 py-1 text-xs font-semibold",p?"bg-green-100 text-green-700":"bg-red-100 text-red-700"),children:[e(p?s:d,{className:"h-3.5 w-3.5"}),p?"Đúng":"Sai"]})]}),r("div",{className:"space-y-5 p-5",children:[e("div",{className:"rounded-lg border border-blue-100 bg-gradient-to-r from-blue-50 to-indigo-50 p-4",children:e("h3",{className:"text-base font-semibold leading-relaxed text-gray-800",children:o.question||"Chưa nhập câu hỏi"})}),(x||o.imageUrl)&&r("div",{className:"overflow-hidden rounded-lg border border-gray-200 bg-gradient-to-br from-slate-50 to-gray-100",children:[e("div",{className:"border-b border-gray-100 px-3 py-1.5",children:r("div",{className:"flex items-center gap-1.5",children:[e("div",{className:"h-2 w-2 rounded-full bg-red-400"}),e("div",{className:"h-2 w-2 rounded-full bg-yellow-400"}),e("div",{className:"h-2 w-2 rounded-full bg-green-400"}),e("span",{className:"ml-2 text-xs font-medium text-gray-500",children:"Hình ảnh"})]})}),e("div",{className:"flex items-center justify-center p-4",children:u?e("div",{className:"flex h-48 items-center justify-center",children:e("div",{className:"h-8 w-8 animate-spin rounded-full border-4 border-gray-200 border-t-blue-500"})}):e("img",{src:x||o.imageUrl,alt:"Question illustration",className:"max-h-56 w-auto rounded-md object-contain"})})]}),r("div",{className:"space-y-2.5",children:[e("span",{className:"text-xs font-medium uppercase tracking-wider text-gray-500",children:"Chọn đáp án"}),r("div",{className:"grid grid-cols-2 gap-4",children:[r("button",{type:"button",onClick:()=>w(!0),disabled:m,className:i("group flex items-center justify-center gap-3 rounded-xl border-2 p-4 transition-all",m?!0===o.answer?"border-green-400 bg-green-50":!0===b?"border-red-400 bg-red-50":"border-gray-200 bg-gray-50":!0===b?"border-emerald-500 bg-emerald-50 shadow-sm ring-2 ring-emerald-100":"border-gray-200 bg-white hover:border-emerald-300 hover:bg-emerald-50/50 hover:shadow-sm"),children:[e("div",{className:i("flex h-9 w-9 items-center justify-center rounded-full text-sm font-bold transition-all",m?!0===o.answer?"bg-green-500 text-white":!0===b?"bg-red-500 text-white":"bg-gray-200 text-gray-600":!0===b?"bg-emerald-500 text-white shadow-sm":"bg-gray-100 text-gray-500 group-hover:bg-emerald-100 group-hover:text-emerald-600"),children:e(s,{className:"h-4 w-4"})}),e("span",{className:i("text-base font-semibold",m&&!0===o.answer?"text-green-800":m&&!0===b?"text-red-800":!0===b?"text-emerald-700":"text-gray-600"),children:"Đúng (True)"}),m&&(!0===o.answer||!0===b)&&e("div",{className:i("flex h-6 w-6 items-center justify-center rounded-full",!0===o.answer?"bg-green-500":"bg-red-500"),children:!0===o.answer?e(s,{className:"h-4 w-4 text-white"}):e(d,{className:"h-4 w-4 text-white"})})]}),r("button",{type:"button",onClick:()=>w(!1),disabled:m,className:i("group flex items-center justify-center gap-3 rounded-xl border-2 p-4 transition-all",m?!1===o.answer?"border-green-400 bg-green-50":!1===b?"border-red-400 bg-red-50":"border-gray-200 bg-gray-50":!1===b?"border-red-500 bg-red-50 shadow-sm ring-2 ring-red-100":"border-gray-200 bg-white hover:border-red-300 hover:bg-red-50/50 hover:shadow-sm"),children:[e("div",{className:i("flex h-9 w-9 items-center justify-center rounded-full text-sm font-bold transition-all",m?!1===o.answer?"bg-green-500 text-white":!1===b?"bg-red-500 text-white":"bg-gray-200 text-gray-600":!1===b?"bg-red-500 text-white shadow-sm":"bg-gray-100 text-gray-500 group-hover:bg-red-100 group-hover:text-red-600"),children:e(d,{className:"h-4 w-4"})}),e("span",{className:i("text-base font-semibold",m&&!1===o.answer?"text-green-800":m&&!1===b?"text-red-800":!1===b?"text-red-700":"text-gray-600"),children:"Sai (False)"}),m&&(!1===o.answer||!1===b)&&e("div",{className:i("flex h-6 w-6 items-center justify-center rounded-full",!1===o.answer?"bg-green-500":"bg-red-500"),children:!1===o.answer?e(s,{className:"h-4 w-4 text-white"}):e(d,{className:"h-4 w-4 text-white"})})]})]})]}),!m&&!f&&r("div",{className:"flex items-center gap-2 rounded-lg border border-amber-200 bg-gradient-to-r from-amber-50 to-yellow-50 p-3",children:[e("div",{className:"flex h-5 w-5 flex-shrink-0 items-center justify-center rounded-full bg-amber-400 text-white",children:e("span",{className:"text-xs font-bold",children:"!"})}),e("span",{className:"text-sm text-amber-800",children:"Vui lòng chọn đáp án"})]}),m&&o.explanation&&r("div",{className:"rounded-lg border border-blue-200 bg-blue-50 p-4",children:[e("p",{className:"text-sm font-semibold text-blue-800",children:"Giải thích:"}),e("p",{className:"mt-1 text-sm text-blue-700",children:o.explanation})]})]})]})})}
|