@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,230 +1 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useEffect, useRef, useState } from 'react';
|
|
4
|
-
import { Card, CardContent, CardHeader, CardTitle } from '../../../../components/ui/card';
|
|
5
|
-
import { Label } from '../../../../components/ui/label';
|
|
6
|
-
import { Button } from '../../../../components/ui/button';
|
|
7
|
-
import { Input } from '../../../../components/ui/input';
|
|
8
|
-
import { Textarea } from '../../../../components/ui/textarea';
|
|
9
|
-
import { Form, FormControl, FormField, FormItem, FormMessage } from '../../../../components/ui/form';
|
|
10
|
-
import { FileUpload } from '../../../../components/ui/file-upload';
|
|
11
|
-
import { PointsInput } from '../../../../components/ui/points-input';
|
|
12
|
-
// Hooks
|
|
13
|
-
import { useReactHookForm } from '../../../../shared/lib/hooks/useReactHookForm';
|
|
14
|
-
import { useDebouncedCallback, usePresignedFileUrl } from '../../../../shared/lib/hooks';
|
|
15
|
-
import { listenAndSpeakWithStoryImagesSchema, } from '../../../../shared/lib/validation/listen-and-speak-with-story-images.validation';
|
|
16
|
-
// Icons
|
|
17
|
-
import { Plus, Trash2, Volume2, ImageIcon, MessageSquareText, Mic, BookOpen, Eye, Pencil, Info, ListChecks } from 'lucide-react';
|
|
18
|
-
import { CreatorGuide } from '../../components/CreatorGuide';
|
|
19
|
-
import { ListenAndSpeakWithStoryImagesClient } from './ListenAndSpeakWithStoryImagesClient';
|
|
20
|
-
// ─── Sub-component: Image preview with presigned URL ─────────────────────────
|
|
21
|
-
function ImagePreview({ imageUrl, index }) {
|
|
22
|
-
const { previewUrl } = usePresignedFileUrl(imageUrl);
|
|
23
|
-
if (!previewUrl)
|
|
24
|
-
return null;
|
|
25
|
-
return (_jsx("div", { className: "relative rounded-xl border border-gray-200 overflow-hidden bg-white shadow-sm", children: _jsx("img", { src: previewUrl, alt: `Story image ${index + 1}`, className: "h-44 w-full object-contain bg-gray-50 p-2" }) }));
|
|
26
|
-
}
|
|
27
|
-
// ─── Main Creator Component ──────────────────────────────────────────────────
|
|
28
|
-
export function ListenAndSpeakWithStoryImagesCreator({ initialData, onChange, onUnsavedChangesChange, validationRef, }) {
|
|
29
|
-
// ---------------------------------------------------------------
|
|
30
|
-
// Form setup with Zod validation
|
|
31
|
-
// ---------------------------------------------------------------
|
|
32
|
-
const apiData = initialData;
|
|
33
|
-
const form = useReactHookForm({
|
|
34
|
-
zodSchema: listenAndSpeakWithStoryImagesSchema,
|
|
35
|
-
defaultValues: {
|
|
36
|
-
audioUrl: apiData?.audioUrl || '',
|
|
37
|
-
images: apiData?.images?.length > 0 ? apiData.images : [''],
|
|
38
|
-
expectedAnswers: apiData?.expectedAnswers?.length > 0 ? apiData.expectedAnswers : [''],
|
|
39
|
-
points: apiData?.points ?? 1,
|
|
40
|
-
explanation: apiData?.explanation || '',
|
|
41
|
-
isBackup: apiData?.isBackup || false,
|
|
42
|
-
},
|
|
43
|
-
});
|
|
44
|
-
const { control, watch, trigger, clearErrors } = form;
|
|
45
|
-
const watchedAudioUrl = watch('audioUrl');
|
|
46
|
-
const watchedImages = watch('images');
|
|
47
|
-
const watchedExpectedAnswers = watch('expectedAnswers');
|
|
48
|
-
const watchedPoints = watch('points');
|
|
49
|
-
const watchedExplanation = watch('explanation');
|
|
50
|
-
const watchedIsBackup = watch('isBackup');
|
|
51
|
-
// ---------------------------------------------------------------
|
|
52
|
-
// Echo-detection refs
|
|
53
|
-
// ---------------------------------------------------------------
|
|
54
|
-
const previousInitialDataRef = useRef(initialData);
|
|
55
|
-
const isInitializingRef = useRef(false);
|
|
56
|
-
const onChangeRef = useRef(onChange);
|
|
57
|
-
const previousPayloadRef = useRef('');
|
|
58
|
-
useEffect(() => {
|
|
59
|
-
onChangeRef.current = onChange;
|
|
60
|
-
}, [onChange]);
|
|
61
|
-
// Debounced onChange
|
|
62
|
-
const debouncedOnChange = useDebouncedCallback((data) => {
|
|
63
|
-
onChangeRef.current?.(data);
|
|
64
|
-
}, 300);
|
|
65
|
-
// ---------------------------------------------------------------
|
|
66
|
-
// Connect to external validation trigger
|
|
67
|
-
// ---------------------------------------------------------------
|
|
68
|
-
useEffect(() => {
|
|
69
|
-
if (validationRef) {
|
|
70
|
-
validationRef.current = {
|
|
71
|
-
triggerValidation: async () => {
|
|
72
|
-
return await trigger();
|
|
73
|
-
},
|
|
74
|
-
getFormData: () => form.getValues(),
|
|
75
|
-
};
|
|
76
|
-
}
|
|
77
|
-
}, [validationRef, form, trigger]);
|
|
78
|
-
// ---------------------------------------------------------------
|
|
79
|
-
// Sync form when initialData props change (echo-detection)
|
|
80
|
-
// ---------------------------------------------------------------
|
|
81
|
-
useEffect(() => {
|
|
82
|
-
const normalizedInitialData = {
|
|
83
|
-
audioUrl: apiData?.audioUrl || '',
|
|
84
|
-
images: apiData?.images?.length > 0 ? apiData.images : [''],
|
|
85
|
-
expectedAnswers: apiData?.expectedAnswers?.length > 0 ? apiData.expectedAnswers : [''],
|
|
86
|
-
points: apiData?.points ?? 1,
|
|
87
|
-
explanation: apiData?.explanation || '',
|
|
88
|
-
isBackup: apiData?.isBackup || false,
|
|
89
|
-
};
|
|
90
|
-
const initialDataChanged = JSON.stringify(previousInitialDataRef.current) !== JSON.stringify(initialData);
|
|
91
|
-
if (!initialDataChanged)
|
|
92
|
-
return;
|
|
93
|
-
previousInitialDataRef.current = initialData;
|
|
94
|
-
const currentFormData = {
|
|
95
|
-
audioUrl: watchedAudioUrl,
|
|
96
|
-
images: watchedImages,
|
|
97
|
-
expectedAnswers: watchedExpectedAnswers,
|
|
98
|
-
points: watchedPoints,
|
|
99
|
-
explanation: watchedExplanation,
|
|
100
|
-
isBackup: watchedIsBackup,
|
|
101
|
-
};
|
|
102
|
-
const isEchoFromOnChange = currentFormData.points === normalizedInitialData.points &&
|
|
103
|
-
currentFormData.explanation === normalizedInitialData.explanation &&
|
|
104
|
-
currentFormData.audioUrl === normalizedInitialData.audioUrl &&
|
|
105
|
-
JSON.stringify(currentFormData.images) === JSON.stringify(normalizedInitialData.images) &&
|
|
106
|
-
JSON.stringify(currentFormData.expectedAnswers) === JSON.stringify(normalizedInitialData.expectedAnswers);
|
|
107
|
-
const wasJustSaved = initialData?.isCompleted === true;
|
|
108
|
-
if (isEchoFromOnChange && wasJustSaved)
|
|
109
|
-
return;
|
|
110
|
-
const shouldReset = !isEchoFromOnChange &&
|
|
111
|
-
(normalizedInitialData.audioUrl !== currentFormData.audioUrl ||
|
|
112
|
-
JSON.stringify(normalizedInitialData.images) !== JSON.stringify(currentFormData.images) ||
|
|
113
|
-
JSON.stringify(normalizedInitialData.expectedAnswers) !== JSON.stringify(currentFormData.expectedAnswers));
|
|
114
|
-
if (shouldReset) {
|
|
115
|
-
isInitializingRef.current = true;
|
|
116
|
-
form.reset(normalizedInitialData);
|
|
117
|
-
setTimeout(() => {
|
|
118
|
-
isInitializingRef.current = false;
|
|
119
|
-
}, 150);
|
|
120
|
-
}
|
|
121
|
-
}, [initialData, form, watchedAudioUrl, watchedImages, watchedExpectedAnswers, watchedExplanation, watchedPoints, watchedIsBackup]);
|
|
122
|
-
// ---------------------------------------------------------------
|
|
123
|
-
// Fire onChange when form values change
|
|
124
|
-
// ---------------------------------------------------------------
|
|
125
|
-
const watchedImagesString = JSON.stringify(watchedImages);
|
|
126
|
-
const watchedExpectedAnswersString = JSON.stringify(watchedExpectedAnswers);
|
|
127
|
-
useEffect(() => {
|
|
128
|
-
if (isInitializingRef.current) {
|
|
129
|
-
const currentPayload = JSON.stringify({
|
|
130
|
-
audioUrl: watchedAudioUrl,
|
|
131
|
-
images: watchedImages,
|
|
132
|
-
expectedAnswers: watchedExpectedAnswers,
|
|
133
|
-
points: watchedPoints,
|
|
134
|
-
explanation: watchedExplanation,
|
|
135
|
-
isBackup: watchedIsBackup,
|
|
136
|
-
});
|
|
137
|
-
previousPayloadRef.current = currentPayload;
|
|
138
|
-
return;
|
|
139
|
-
}
|
|
140
|
-
const payload = {
|
|
141
|
-
audioUrl: watchedAudioUrl,
|
|
142
|
-
images: watchedImages,
|
|
143
|
-
expectedAnswers: watchedExpectedAnswers,
|
|
144
|
-
points: watchedPoints,
|
|
145
|
-
explanation: watchedExplanation,
|
|
146
|
-
isBackup: watchedIsBackup,
|
|
147
|
-
};
|
|
148
|
-
const payloadString = JSON.stringify(payload);
|
|
149
|
-
if (previousPayloadRef.current !== payloadString) {
|
|
150
|
-
previousPayloadRef.current = payloadString;
|
|
151
|
-
debouncedOnChange(payload);
|
|
152
|
-
}
|
|
153
|
-
}, [
|
|
154
|
-
watchedAudioUrl,
|
|
155
|
-
watchedImagesString,
|
|
156
|
-
watchedExpectedAnswersString,
|
|
157
|
-
watchedPoints,
|
|
158
|
-
watchedExplanation,
|
|
159
|
-
watchedIsBackup,
|
|
160
|
-
debouncedOnChange,
|
|
161
|
-
]);
|
|
162
|
-
// Track unsaved changes
|
|
163
|
-
useEffect(() => {
|
|
164
|
-
if (onUnsavedChangesChange) {
|
|
165
|
-
onUnsavedChangesChange(form.formState.isDirty);
|
|
166
|
-
}
|
|
167
|
-
}, [form.formState.isDirty, onUnsavedChangesChange]);
|
|
168
|
-
// ---------------------------------------------------------------
|
|
169
|
-
// Handlers — Images (dynamic count)
|
|
170
|
-
// ---------------------------------------------------------------
|
|
171
|
-
const handleImageChange = (index, url) => {
|
|
172
|
-
const newImages = [...watchedImages];
|
|
173
|
-
newImages[index] = url;
|
|
174
|
-
form.setValue('images', newImages, { shouldValidate: true, shouldDirty: true });
|
|
175
|
-
};
|
|
176
|
-
const addImage = () => {
|
|
177
|
-
const currentImages = form.getValues('images');
|
|
178
|
-
form.setValue('images', [...currentImages, ''], { shouldValidate: true, shouldDirty: true });
|
|
179
|
-
};
|
|
180
|
-
const removeImage = (index) => {
|
|
181
|
-
if (watchedImages.length > 1) {
|
|
182
|
-
const newImages = watchedImages.filter((_, i) => i !== index);
|
|
183
|
-
form.setValue('images', newImages, { shouldValidate: true, shouldDirty: true });
|
|
184
|
-
}
|
|
185
|
-
};
|
|
186
|
-
const clearImage = (index) => handleImageChange(index, '');
|
|
187
|
-
// ---------------------------------------------------------------
|
|
188
|
-
// Handlers — Expected Answers
|
|
189
|
-
// ---------------------------------------------------------------
|
|
190
|
-
const addExpectedAnswer = () => {
|
|
191
|
-
const currentAnswers = form.getValues('expectedAnswers');
|
|
192
|
-
form.setValue('expectedAnswers', [...currentAnswers, ''], { shouldValidate: true, shouldDirty: true });
|
|
193
|
-
};
|
|
194
|
-
const handleExpectedAnswerChange = (index, value) => {
|
|
195
|
-
const newAnswers = [...watchedExpectedAnswers];
|
|
196
|
-
newAnswers[index] = value;
|
|
197
|
-
form.setValue('expectedAnswers', newAnswers, { shouldValidate: true, shouldDirty: true });
|
|
198
|
-
};
|
|
199
|
-
const removeExpectedAnswer = (index) => {
|
|
200
|
-
if (watchedExpectedAnswers.length > 1) {
|
|
201
|
-
const newAnswers = watchedExpectedAnswers.filter((_, i) => i !== index);
|
|
202
|
-
form.setValue('expectedAnswers', newAnswers, { shouldValidate: true, shouldDirty: true });
|
|
203
|
-
}
|
|
204
|
-
};
|
|
205
|
-
const uploadedCount = watchedImages.filter(img => img.trim() !== '').length;
|
|
206
|
-
// ---------------------------------------------------------------
|
|
207
|
-
// Preview mode
|
|
208
|
-
// ---------------------------------------------------------------
|
|
209
|
-
const [isClientMode, setIsClientMode] = useState(false);
|
|
210
|
-
if (isClientMode) {
|
|
211
|
-
const questionData = {
|
|
212
|
-
audioUrl: watchedAudioUrl,
|
|
213
|
-
images: watchedImages,
|
|
214
|
-
expectedAnswers: watchedExpectedAnswers,
|
|
215
|
-
explanation: watchedExplanation,
|
|
216
|
-
points: watchedPoints,
|
|
217
|
-
isBackup: watchedIsBackup,
|
|
218
|
-
};
|
|
219
|
-
return (_jsxs("div", { className: "space-y-6", children: [_jsx(Card, { children: _jsx(CardHeader, { children: _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { children: [_jsx(CardTitle, { children: "Nghe v\u00E0 n\u00F3i \u2014 K\u1EC3 c\u00E2u chuy\u1EC7n (Preview)" }), _jsx("p", { className: "mt-1 text-sm text-gray-500", children: "Preview mode" })] }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => setIsClientMode(false), children: [_jsx(Pencil, { className: "mr-2 h-4 w-4" }), "Quay l\u1EA1i Edit Mode"] })] }) }) }), _jsx(ListenAndSpeakWithStoryImagesClient, { questionData: questionData, isReviewMode: true })] }));
|
|
220
|
-
}
|
|
221
|
-
return (_jsxs("div", { className: "space-y-6", children: [_jsx(CreatorGuide, { colorScheme: "blue", steps: [
|
|
222
|
-
{ text: 'Upload các hình ảnh câu chuyện theo thứ tự — học sinh sẽ kể lại câu chuyện dựa trên hình' },
|
|
223
|
-
{ text: 'Có thể thêm hoặc xóa hình ảnh linh hoạt' },
|
|
224
|
-
{ text: 'Upload audio câu hỏi cho từng câu — giám khảo sẽ hỏi về nội dung câu chuyện' },
|
|
225
|
-
{ text: 'Nhập các đáp án mong muốn cho mỗi câu hỏi', highlight: true },
|
|
226
|
-
{ text: 'Thêm giải thích nếu cần' },
|
|
227
|
-
], tips: ['Hình ảnh nên sắp xếp theo trình tự thời gian rõ ràng, mỗi hình thể hiện một sự kiện'] }), _jsxs(Card, { className: "overflow-hidden border-0 bg-white shadow-lg shadow-blue-100/50", children: [_jsx("div", { className: "h-1.5 bg-gradient-to-r from-blue-500 via-indigo-500 to-purple-500" }), _jsx(CardHeader, { className: "pb-4", children: _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "flex h-10 w-10 items-center justify-center rounded-xl bg-gradient-to-br from-blue-500 to-indigo-600 shadow-md", children: _jsx(ListChecks, { className: "h-5 w-5 text-white" }) }), _jsxs("div", { children: [_jsx(CardTitle, { className: "text-xl font-bold text-gray-900", children: "K\u1EC3 c\u00E2u chuy\u1EC7n theo h\u00ECnh" }), _jsx("p", { className: "text-sm text-gray-500 mt-0.5", children: "H\u1ECDc sinh nh\u00ECn h\u00ECnh \u1EA3nh v\u00E0 k\u1EC3 l\u1EA1i c\u00E2u chuy\u1EC7n" })] })] }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => setIsClientMode(true), disabled: !watchedAudioUrl || watchedExpectedAnswers.filter((a) => a.trim()).length === 0, className: "gap-2 border-blue-200 text-blue-600 hover:bg-blue-50 hover:text-blue-700", children: [_jsx(Eye, { className: "h-4 w-4" }), "Preview"] })] }) })] }), _jsxs(Form, { ...form, children: [_jsxs(Card, { className: "overflow-hidden border-0 bg-white shadow-lg shadow-purple-100/50", children: [_jsx("div", { className: "h-1.5 bg-gradient-to-r from-purple-500 via-fuchsia-500 to-pink-500" }), _jsx(CardHeader, { className: "pb-4", children: _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "flex h-10 w-10 shrink-0 items-center justify-center rounded-xl bg-gradient-to-br from-purple-500 to-fuchsia-600 shadow-md", children: _jsx(BookOpen, { className: "h-5 w-5 text-white" }) }), _jsxs("div", { children: [_jsx(CardTitle, { className: "text-lg font-bold text-gray-900", children: "H\u00ECnh \u1EA3nh c\u00E2u chuy\u1EC7n" }), _jsx("p", { className: "text-sm text-gray-500", children: "Th\u00EAm h\u00ECnh \u1EA3nh theo th\u1EE9 t\u1EF1 \u2014 H\u1ECDc sinh s\u1EBD nh\u00ECn h\u00ECnh v\u00E0 k\u1EC3 l\u1EA1i c\u00E2u chuy\u1EC7n" })] })] }), _jsxs("div", { className: "flex items-center gap-3", children: [_jsxs("span", { className: `inline-flex items-center rounded-full px-3 py-1 text-xs font-bold ${uploadedCount > 0
|
|
228
|
-
? 'bg-green-100 text-green-700 border border-green-200'
|
|
229
|
-
: 'bg-orange-100 text-orange-600 border border-orange-200'}`, children: [uploadedCount, " \u1EA3nh"] }), _jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: addImage, className: "gap-2 border-purple-200 text-purple-600 hover:bg-purple-50", children: [_jsx(Plus, { className: "h-4 w-4" }), "Th\u00EAm \u1EA3nh"] })] })] }) }), _jsx(CardContent, { children: _jsx(FormField, { control: control, name: "images", render: () => (_jsxs(FormItem, { children: [_jsx("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4", children: watchedImages.map((imgUrl, index) => (_jsxs(Card, { className: "border border-gray-200", children: [_jsx(CardHeader, { className: "pb-2 pt-3 px-4", children: _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("span", { className: "text-sm font-semibold text-gray-600 flex items-center gap-2", children: [_jsx("span", { className: "flex h-6 w-6 items-center justify-center rounded-md bg-purple-100 text-purple-700 text-xs font-bold", children: index + 1 }), "\u1EA2nh ", index + 1] }), _jsxs("div", { className: "flex items-center gap-1", children: [imgUrl && (_jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => clearImage(index), className: "h-7 w-7 rounded-full text-orange-400 hover:text-orange-600 hover:bg-orange-50", title: "X\u00F3a \u1EA3nh", children: _jsx(ImageIcon, { className: "h-3.5 w-3.5" }) })), watchedImages.length > 1 && (_jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => removeImage(index), className: "h-7 w-7 rounded-full text-red-400 hover:text-red-600 hover:bg-red-50", title: "X\u00F3a slot", children: _jsx(Trash2, { className: "h-3.5 w-3.5" }) }))] })] }) }), _jsxs(CardContent, { className: "space-y-2 px-4 pb-4", children: [imgUrl ? (_jsx(ImagePreview, { imageUrl: imgUrl, index: index })) : (_jsx(FileUpload, { id: `story-img-${index}`, label: "", accept: "image/*", value: "", onChange: (url) => handleImageChange(index, url), placeholder: `Upload ảnh ${index + 1}`, maxSize: 5, autoUpload: true, prefix: "questions/story-images" })), form.formState.errors?.images?.[index] && (_jsxs("p", { className: "text-sm font-medium text-destructive", children: ["Vui l\u00F2ng t\u1EA3i l\u00EAn \u1EA3nh ", index + 1] }))] })] }, index))) }), form.formState.errors?.images?.message && (_jsx(FormMessage, {}))] })) }) })] }), _jsxs(Card, { className: "overflow-hidden border-0 bg-white shadow-lg shadow-indigo-100/50", children: [_jsx("div", { className: "h-1 bg-gradient-to-r from-indigo-400 to-purple-400" }), _jsx(CardHeader, { className: "pb-4", children: _jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-gradient-to-br from-indigo-500 to-purple-600 shadow-sm", children: _jsx(Mic, { className: "h-4 w-4 text-white" }) }), _jsxs("div", { children: [_jsx(CardTitle, { className: "text-lg font-semibold text-gray-800", children: "C\u00E2u h\u1ECFi: K\u1EC3 c\u00E2u chuy\u1EC7n" }), _jsx("p", { className: "text-sm text-gray-500", children: "H\u1ECDc sinh nghe audio, nh\u00ECn c\u00E1c h\u00ECnh \u1EA3nh v\u00E0 m\u00F4 t\u1EA3 c\u00E2u chuy\u1EC7n b\u1EB1ng l\u1EDDi" })] })] }) }), _jsxs(CardContent, { className: "space-y-6", children: [_jsx("div", { className: "rounded-xl border border-gray-200 bg-gray-50 p-4", children: _jsxs("div", { className: "flex flex-col md:flex-row gap-6", children: [_jsx("div", { className: "flex-1", children: _jsx(PointsInput, { ...form.register('points', { valueAsNumber: true }), error: form.formState.errors?.points?.message }) }), _jsx("div", { className: "flex-1 flex flex-col justify-center pt-2" })] }) }), _jsxs("div", { className: "space-y-2", children: [_jsxs(Label, { className: "flex items-center gap-2 text-sm font-semibold text-gray-800", children: [_jsx(Volume2, { className: "h-4 w-4 text-indigo-500" }), "File Audio c\u00E2u h\u1ECFi ", _jsx("span", { className: "text-red-500", children: "*" })] }), _jsx(FormField, { control: control, name: "audioUrl", render: ({ field }) => (_jsxs(FormItem, { children: [_jsx(FormControl, { children: _jsx(FileUpload, { id: "audio-story-images", label: "", accept: "audio/*", value: field.value, onChange: (url) => { field.onChange(url); clearErrors('audioUrl'); }, maxSize: 10, placeholder: "T\u1EA3i l\u00EAn file \u00E2m thanh (mp3, wav...)", autoUpload: true, prefix: "listen-and-speak" }) }), _jsx(FormMessage, {})] })) })] })] })] }), _jsxs(Card, { className: "overflow-hidden border-0 bg-white shadow-lg shadow-green-100/50", children: [_jsx("div", { className: "h-1 bg-gradient-to-r from-green-400 to-emerald-500" }), _jsx(CardHeader, { className: "pb-4", children: _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "flex h-9 w-9 items-center justify-center rounded-lg bg-gradient-to-br from-green-500 to-emerald-600 shadow-sm", children: _jsx(MessageSquareText, { className: "h-4 w-4 text-white" }) }), _jsx("div", { children: _jsxs(CardTitle, { className: "text-lg font-semibold text-gray-800", children: ["\u0110\u00E1p \u00E1n mong mu\u1ED1n (", watchedExpectedAnswers.filter((a) => a.trim()).length, ")"] }) })] }), _jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: addExpectedAnswer, className: "gap-2 border-green-200 text-green-600 hover:bg-green-50", children: [_jsx(Plus, { className: "h-4 w-4" }), "Th\u00EAm \u0111\u00E1p \u00E1n"] })] }) }), _jsxs(CardContent, { className: "space-y-4", children: [_jsxs("div", { className: "flex items-start gap-2 rounded-lg border border-blue-100 bg-blue-50/60 px-3 py-2.5", children: [_jsx(Info, { className: "h-4 w-4 text-blue-500 mt-0.5 flex-shrink-0" }), _jsxs("div", { className: "text-xs text-blue-700 space-y-1", children: [_jsx("p", { children: "Nh\u1EADp c\u00E1c c\u00E2u tr\u1EA3 l\u1EDDi mong mu\u1ED1n. C\u00F3 th\u1EC3 th\u00EAm nhi\u1EC1u \u0111\u00E1p \u00E1n n\u1EBFu c\u00F3 nhi\u1EC1u c\u00E1ch tr\u1EA3 l\u1EDDi \u0111\u00FAng." }), _jsxs("p", { children: [_jsx("span", { className: "font-semibold", children: "\u2022 Ch\u1EA5m th\u1EE7 c\u00F4ng:" }), " Gi\u00E1o vi\u00EAn d\u00F9ng c\u00E1c \u0111\u00E1p \u00E1n n\u00E0y l\u00E0m tham kh\u1EA3o \u0111\u1EC3 ch\u1EA5m \u0111i\u1EC3m."] }), _jsxs("p", { children: [_jsx("span", { className: "font-semibold", children: "\u2022 Ch\u1EA5m t\u1EF1 \u0111\u1ED9ng (AI):" }), " H\u1EC7 th\u1ED1ng d\u00F9ng AI so s\u00E1nh gi\u1ECDng n\u00F3i c\u1EE7a h\u1ECDc sinh v\u1EDBi c\u00E1c \u0111\u00E1p \u00E1n \u0111\u1EC3 t\u1EF1 \u0111\u1ED9ng ch\u1EA5m \u0111i\u1EC3m."] })] })] }), _jsxs("div", { className: "space-y-3", children: [watchedExpectedAnswers.map((answer, index) => (_jsxs("div", { className: "flex items-start gap-3 group/answer", children: [_jsx("div", { className: "flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-lg bg-green-100 text-green-700 font-semibold shadow-sm text-sm", children: index + 1 }), _jsxs("div", { className: "flex-1", children: [_jsx(Input, { placeholder: `VD: ${index === 0 ? 'First, the boy went to the park.' : 'Then he saw a dog and played with it.'}`, value: answer || '', onChange: (e) => handleExpectedAnswerChange(index, e.target.value), className: "border-gray-200 focus:border-green-300 focus:ring-green-200" }), form.formState.errors?.expectedAnswers?.[index]?.message && (_jsx("p", { className: "mt-1 text-sm text-red-500", children: form.formState.errors.expectedAnswers[index]?.message }))] }), watchedExpectedAnswers.length > 1 && (_jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => removeExpectedAnswer(index), className: "text-red-400 hover:text-red-600 hover:bg-red-50 opacity-0 group-hover/answer:opacity-100 transition-opacity", children: _jsx(Trash2, { className: "h-4 w-4" }) }))] }, index))), form.formState.errors?.expectedAnswers && !Array.isArray(form.formState.errors.expectedAnswers) && (_jsx("p", { className: "text-sm text-red-500", children: form.formState.errors.expectedAnswers.message }))] })] })] }), _jsxs(Card, { className: "overflow-hidden border-0 bg-white shadow-lg shadow-amber-100/50", children: [_jsx("div", { className: "h-1 bg-gradient-to-r from-amber-400 to-orange-400" }), _jsx(CardHeader, { className: "pb-4", children: _jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "flex h-9 w-9 items-center justify-center rounded-lg bg-gradient-to-br from-amber-500 to-orange-500 shadow-sm", children: _jsx(BookOpen, { className: "h-4 w-4 text-white" }) }), _jsxs("div", { children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(CardTitle, { className: "text-lg font-semibold text-gray-800", children: "Gi\u1EA3i th\u00EDch" }), _jsx("span", { className: "inline-flex items-center rounded-full border border-amber-200 bg-amber-50 px-2.5 py-0.5 text-xs font-medium text-amber-600", children: "Kh\u00F4ng b\u1EAFt bu\u1ED9c" })] }), _jsx("p", { className: "text-sm text-gray-500 mt-0.5", children: "Gi\u1EA3i th\u00EDch cho \u0111\u00E1p \u00E1n \u0111\u00FAng \u2014 hi\u1EC3n th\u1ECB sau khi h\u1ECDc sinh ho\u00E0n th\u00E0nh c\u00E2u h\u1ECFi" })] })] }) }), _jsx(CardContent, { children: _jsx(Textarea, { ...form.register('explanation'), placeholder: "VD: C\u00E2u tr\u1EA3 l\u1EDDi \u0111\u00FAng l\u00E0... v\u00EC...", className: "min-h-[80px] border-gray-200 bg-white transition-all focus:border-amber-300 focus:ring-amber-200 resize-none", rows: 3 }) })] })] })] }));
|
|
230
|
-
}
|
|
1
|
+
"use client";import{jsx as e,jsxs as t}from"react/jsx-runtime";import{useEffect as r,useRef as s,useState as i}from"react";import{Card as n,CardContent as a,CardHeader as l,CardTitle as o}from"../../../../components/ui/card";import{Label as c}from"../../../../components/ui/label";import{Button as d}from"../../../../components/ui/button";import{Input as h}from"../../../../components/ui/input";import{Textarea as m}from"../../../../components/ui/textarea";import{Form as g,FormControl as p,FormField as u,FormItem as x,FormMessage as f}from"../../../../components/ui/form";import{FileUpload as b}from"../../../../components/ui/file-upload";import{PointsInput as N}from"../../../../components/ui/points-input";import{useReactHookForm as w}from"../../../../shared/lib/hooks/useReactHookForm";import{useDebouncedCallback as v,usePresignedFileUrl as y}from"../../../../shared/lib/hooks";import{listenAndSpeakWithStoryImagesSchema as k}from"../../../../shared/lib/validation/listen-and-speak-with-story-images.validation";import{Plus as S,Trash2 as A,Volume2 as C,ImageIcon as U,MessageSquareText as V,Mic as j,BookOpen as J,Eye as O,Pencil as D,Info as z,ListChecks as B}from"lucide-react";import{CreatorGuide as H}from"../../components/CreatorGuide";import{ListenAndSpeakWithStoryImagesClient as T}from"./ListenAndSpeakWithStoryImagesClient";function F({imageUrl:t,index:r}){const{previewUrl:s}=y(t);return s?e("div",{className:"relative rounded-xl border border-gray-200 overflow-hidden bg-white shadow-sm",children:e("img",{src:s,alt:`Story image ${r+1}`,className:"h-44 w-full object-contain bg-gray-50 p-2"})}):null}export function ListenAndSpeakWithStoryImagesCreator({initialData:y,onChange:G,onUnsavedChangesChange:I,validationRef:K}){const P=y,R=w({zodSchema:k,defaultValues:{audioUrl:P?.audioUrl||"",images:P?.images?.length>0?P.images:[""],expectedAnswers:P?.expectedAnswers?.length>0?P.expectedAnswers:[""],points:P?.points??1,explanation:P?.explanation||"",isBackup:P?.isBackup||!1}}),{control:$,watch:q,trigger:E,clearErrors:L}=R,M=q("audioUrl"),W=q("images"),X=q("expectedAnswers"),Q=q("points"),Y=q("explanation"),Z=q("isBackup"),_=s(y),ee=s(!1),te=s(G),re=s("");r(()=>{te.current=G},[G]);const se=v(e=>{te.current?.(e)},300);r(()=>{K&&(K.current={triggerValidation:async()=>await E(),getFormData:()=>R.getValues()})},[K,R,E]),r(()=>{const e={audioUrl:P?.audioUrl||"",images:P?.images?.length>0?P.images:[""],expectedAnswers:P?.expectedAnswers?.length>0?P.expectedAnswers:[""],points:P?.points??1,explanation:P?.explanation||"",isBackup:P?.isBackup||!1};if(!(JSON.stringify(_.current)!==JSON.stringify(y)))return;_.current=y;const t={audioUrl:M,images:W,expectedAnswers:X,points:Q,explanation:Y,isBackup:Z},r=t.points===e.points&&t.explanation===e.explanation&&t.audioUrl===e.audioUrl&&JSON.stringify(t.images)===JSON.stringify(e.images)&&JSON.stringify(t.expectedAnswers)===JSON.stringify(e.expectedAnswers);if(r&&!0===y?.isCompleted)return;!r&&(e.audioUrl!==t.audioUrl||JSON.stringify(e.images)!==JSON.stringify(t.images)||JSON.stringify(e.expectedAnswers)!==JSON.stringify(t.expectedAnswers))&&(ee.current=!0,R.reset(e),setTimeout(()=>{ee.current=!1},150))},[y,R,M,W,X,Y,Q,Z]);const ie=JSON.stringify(W),ne=JSON.stringify(X);r(()=>{if(ee.current){const e=JSON.stringify({audioUrl:M,images:W,expectedAnswers:X,points:Q,explanation:Y,isBackup:Z});return void(re.current=e)}const e={audioUrl:M,images:W,expectedAnswers:X,points:Q,explanation:Y,isBackup:Z},t=JSON.stringify(e);re.current!==t&&(re.current=t,se(e))},[M,ie,ne,Q,Y,Z,se]),r(()=>{I&&I(R.formState.isDirty)},[R.formState.isDirty,I]);const ae=(e,t)=>{const r=[...W];r[e]=t,R.setValue("images",r,{shouldValidate:!0,shouldDirty:!0})},le=W.filter(e=>""!==e.trim()).length,[oe,ce]=i(!1);if(oe){const r={audioUrl:M,images:W,expectedAnswers:X,explanation:Y,points:Q,isBackup:Z};return t("div",{className:"space-y-6",children:[e(n,{children:e(l,{children:t("div",{className:"flex items-center justify-between",children:[t("div",{children:[e(o,{children:"Nghe và nói — Kể câu chuyện (Preview)"}),e("p",{className:"mt-1 text-sm text-gray-500",children:"Preview mode"})]}),t(d,{variant:"outline",size:"sm",onClick:()=>ce(!1),children:[e(D,{className:"mr-2 h-4 w-4"}),"Quay lại Edit Mode"]})]})})}),e(T,{questionData:r,isReviewMode:!0})]})}return t("div",{className:"space-y-6",children:[e(H,{colorScheme:"blue",steps:[{text:"Upload các hình ảnh câu chuyện theo thứ tự — học sinh sẽ kể lại câu chuyện dựa trên hình"},{text:"Có thể thêm hoặc xóa hình ảnh linh hoạt"},{text:"Upload audio câu hỏi cho từng câu — giám khảo sẽ hỏi về nội dung câu chuyện"},{text:"Nhập các đáp án mong muốn cho mỗi câu hỏi",highlight:!0},{text:"Thêm giải thích nếu cần"}],tips:["Hình ảnh nên sắp xếp theo trình tự thời gian rõ ràng, mỗi hình thể hiện một sự kiện"]}),t(n,{className:"overflow-hidden border-0 bg-white shadow-lg shadow-blue-100/50",children:[e("div",{className:"h-1.5 bg-gradient-to-r from-blue-500 via-indigo-500 to-purple-500"}),e(l,{className:"pb-4",children:t("div",{className:"flex items-center justify-between",children:[t("div",{className:"flex items-center gap-3",children:[e("div",{className:"flex h-10 w-10 items-center justify-center rounded-xl bg-gradient-to-br from-blue-500 to-indigo-600 shadow-md",children:e(B,{className:"h-5 w-5 text-white"})}),t("div",{children:[e(o,{className:"text-xl font-bold text-gray-900",children:"Kể câu chuyện theo hình"}),e("p",{className:"text-sm text-gray-500 mt-0.5",children:"Học sinh nhìn hình ảnh và kể lại câu chuyện"})]})]}),t(d,{variant:"outline",size:"sm",onClick:()=>ce(!0),disabled:!M||0===X.filter(e=>e.trim()).length,className:"gap-2 border-blue-200 text-blue-600 hover:bg-blue-50 hover:text-blue-700",children:[e(O,{className:"h-4 w-4"}),"Preview"]})]})})]}),t(g,{...R,children:[t(n,{className:"overflow-hidden border-0 bg-white shadow-lg shadow-purple-100/50",children:[e("div",{className:"h-1.5 bg-gradient-to-r from-purple-500 via-fuchsia-500 to-pink-500"}),e(l,{className:"pb-4",children:t("div",{className:"flex items-center justify-between",children:[t("div",{className:"flex items-center gap-3",children:[e("div",{className:"flex h-10 w-10 shrink-0 items-center justify-center rounded-xl bg-gradient-to-br from-purple-500 to-fuchsia-600 shadow-md",children:e(J,{className:"h-5 w-5 text-white"})}),t("div",{children:[e(o,{className:"text-lg font-bold text-gray-900",children:"Hình ảnh câu chuyện"}),e("p",{className:"text-sm text-gray-500",children:"Thêm hình ảnh theo thứ tự — Học sinh sẽ nhìn hình và kể lại câu chuyện"})]})]}),t("div",{className:"flex items-center gap-3",children:[t("span",{className:"inline-flex items-center rounded-full px-3 py-1 text-xs font-bold "+(le>0?"bg-green-100 text-green-700 border border-green-200":"bg-orange-100 text-orange-600 border border-orange-200"),children:[le," ảnh"]}),t(d,{type:"button",variant:"outline",size:"sm",onClick:()=>{const e=R.getValues("images");R.setValue("images",[...e,""],{shouldValidate:!0,shouldDirty:!0})},className:"gap-2 border-purple-200 text-purple-600 hover:bg-purple-50",children:[e(S,{className:"h-4 w-4"}),"Thêm ảnh"]})]})]})}),e(a,{children:e(u,{control:$,name:"images",render:()=>t(x,{children:[e("div",{className:"grid grid-cols-1 md:grid-cols-2 gap-4",children:W.map((r,s)=>t(n,{className:"border border-gray-200",children:[e(l,{className:"pb-2 pt-3 px-4",children:t("div",{className:"flex items-center justify-between",children:[t("span",{className:"text-sm font-semibold text-gray-600 flex items-center gap-2",children:[e("span",{className:"flex h-6 w-6 items-center justify-center rounded-md bg-purple-100 text-purple-700 text-xs font-bold",children:s+1}),"Ảnh ",s+1]}),t("div",{className:"flex items-center gap-1",children:[r&&e(d,{type:"button",variant:"ghost",size:"sm",onClick:()=>(e=>ae(e,""))(s),className:"h-7 w-7 rounded-full text-orange-400 hover:text-orange-600 hover:bg-orange-50",title:"Xóa ảnh",children:e(U,{className:"h-3.5 w-3.5"})}),W.length>1&&e(d,{type:"button",variant:"ghost",size:"sm",onClick:()=>(e=>{if(W.length>1){const t=W.filter((t,r)=>r!==e);R.setValue("images",t,{shouldValidate:!0,shouldDirty:!0})}})(s),className:"h-7 w-7 rounded-full text-red-400 hover:text-red-600 hover:bg-red-50",title:"Xóa slot",children:e(A,{className:"h-3.5 w-3.5"})})]})]})}),t(a,{className:"space-y-2 px-4 pb-4",children:[r?e(F,{imageUrl:r,index:s}):e(b,{id:`story-img-${s}`,label:"",accept:"image/*",value:"",onChange:e=>ae(s,e),placeholder:`Upload ảnh ${s+1}`,maxSize:5,autoUpload:!0,prefix:"questions/story-images"}),R.formState.errors?.images?.[s]&&t("p",{className:"text-sm font-medium text-destructive",children:["Vui lòng tải lên ảnh ",s+1]})]})]},s))}),R.formState.errors?.images?.message&&e(f,{})]})})})]}),t(n,{className:"overflow-hidden border-0 bg-white shadow-lg shadow-indigo-100/50",children:[e("div",{className:"h-1 bg-gradient-to-r from-indigo-400 to-purple-400"}),e(l,{className:"pb-4",children:t("div",{className:"flex items-center gap-3",children:[e("div",{className:"flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-gradient-to-br from-indigo-500 to-purple-600 shadow-sm",children:e(j,{className:"h-4 w-4 text-white"})}),t("div",{children:[e(o,{className:"text-lg font-semibold text-gray-800",children:"Câu hỏi: Kể câu chuyện"}),e("p",{className:"text-sm text-gray-500",children:"Học sinh nghe audio, nhìn các hình ảnh và mô tả câu chuyện bằng lời"})]})]})}),t(a,{className:"space-y-6",children:[e("div",{className:"rounded-xl border border-gray-200 bg-gray-50 p-4",children:t("div",{className:"flex flex-col md:flex-row gap-6",children:[e("div",{className:"flex-1",children:e(N,{...R.register("points",{valueAsNumber:!0}),error:R.formState.errors?.points?.message})}),e("div",{className:"flex-1 flex flex-col justify-center pt-2"})]})}),t("div",{className:"space-y-2",children:[t(c,{className:"flex items-center gap-2 text-sm font-semibold text-gray-800",children:[e(C,{className:"h-4 w-4 text-indigo-500"}),"File Audio câu hỏi ",e("span",{className:"text-red-500",children:"*"})]}),e(u,{control:$,name:"audioUrl",render:({field:r})=>t(x,{children:[e(p,{children:e(b,{id:"audio-story-images",label:"",accept:"audio/*",value:r.value,onChange:e=>{r.onChange(e),L("audioUrl")},maxSize:10,placeholder:"Tải lên file âm thanh (mp3, wav...)",autoUpload:!0,prefix:"listen-and-speak"})}),e(f,{})]})})]})]})]}),t(n,{className:"overflow-hidden border-0 bg-white shadow-lg shadow-green-100/50",children:[e("div",{className:"h-1 bg-gradient-to-r from-green-400 to-emerald-500"}),e(l,{className:"pb-4",children:t("div",{className:"flex items-center justify-between",children:[t("div",{className:"flex items-center gap-3",children:[e("div",{className:"flex h-9 w-9 items-center justify-center rounded-lg bg-gradient-to-br from-green-500 to-emerald-600 shadow-sm",children:e(V,{className:"h-4 w-4 text-white"})}),e("div",{children:t(o,{className:"text-lg font-semibold text-gray-800",children:["Đáp án mong muốn (",X.filter(e=>e.trim()).length,")"]})})]}),t(d,{type:"button",variant:"outline",size:"sm",onClick:()=>{const e=R.getValues("expectedAnswers");R.setValue("expectedAnswers",[...e,""],{shouldValidate:!0,shouldDirty:!0})},className:"gap-2 border-green-200 text-green-600 hover:bg-green-50",children:[e(S,{className:"h-4 w-4"}),"Thêm đáp án"]})]})}),t(a,{className:"space-y-4",children:[t("div",{className:"flex items-start gap-2 rounded-lg border border-blue-100 bg-blue-50/60 px-3 py-2.5",children:[e(z,{className:"h-4 w-4 text-blue-500 mt-0.5 flex-shrink-0"}),t("div",{className:"text-xs text-blue-700 space-y-1",children:[e("p",{children:"Nhập các câu trả lời mong muốn. Có thể thêm nhiều đáp án nếu có nhiều cách trả lời đúng."}),t("p",{children:[e("span",{className:"font-semibold",children:"• Chấm thủ công:"})," Giáo viên dùng các đáp án này làm tham khảo để chấm điểm."]}),t("p",{children:[e("span",{className:"font-semibold",children:"• Chấm tự động (AI):"})," Hệ thống dùng AI so sánh giọng nói của học sinh với các đáp án để tự động chấm điểm."]})]})]}),t("div",{className:"space-y-3",children:[X.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(h,{placeholder:"VD: "+(0===s?"First, the boy went to the park.":"Then he saw a dog and played with it."),value:r||"",onChange:e=>((e,t)=>{const r=[...X];r[e]=t,R.setValue("expectedAnswers",r,{shouldValidate:!0,shouldDirty:!0})})(s,e.target.value),className:"border-gray-200 focus:border-green-300 focus:ring-green-200"}),R.formState.errors?.expectedAnswers?.[s]?.message&&e("p",{className:"mt-1 text-sm text-red-500",children:R.formState.errors.expectedAnswers[s]?.message})]}),X.length>1&&e(d,{type:"button",variant:"ghost",size:"sm",onClick:()=>(e=>{if(X.length>1){const t=X.filter((t,r)=>r!==e);R.setValue("expectedAnswers",t,{shouldValidate:!0,shouldDirty:!0})}})(s),className:"text-red-400 hover:text-red-600 hover:bg-red-50 opacity-0 group-hover/answer:opacity-100 transition-opacity",children:e(A,{className:"h-4 w-4"})})]},s)),R.formState.errors?.expectedAnswers&&!Array.isArray(R.formState.errors.expectedAnswers)&&e("p",{className:"text-sm text-red-500",children:R.formState.errors.expectedAnswers.message})]})]})]}),t(n,{className:"overflow-hidden border-0 bg-white shadow-lg shadow-amber-100/50",children:[e("div",{className:"h-1 bg-gradient-to-r from-amber-400 to-orange-400"}),e(l,{className:"pb-4",children:t("div",{className:"flex items-center gap-3",children:[e("div",{className:"flex h-9 w-9 items-center justify-center rounded-lg bg-gradient-to-br from-amber-500 to-orange-500 shadow-sm",children:e(J,{className:"h-4 w-4 text-white"})}),t("div",{children:[t("div",{className:"flex items-center gap-2",children:[e(o,{className:"text-lg font-semibold text-gray-800",children:"Giải thích"}),e("span",{className:"inline-flex items-center rounded-full border border-amber-200 bg-amber-50 px-2.5 py-0.5 text-xs font-medium text-amber-600",children:"Không bắt buộc"})]}),e("p",{className:"text-sm text-gray-500 mt-0.5",children:"Giải thích cho đáp án đúng — hiển thị sau khi học sinh hoàn thành câu hỏi"})]})]})}),e(a,{children:e(m,{...R.register("explanation"),placeholder:"VD: Câu trả lời đúng là... vì...",className:"min-h-[80px] border-gray-200 bg-white transition-all focus:border-amber-300 focus:ring-amber-200 resize-none",rows:3})})]})]})]})}
|
|
@@ -1,56 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
export const listenAndSpeakWithStoryImagesRegistration = {
|
|
3
|
-
component: ListenAndSpeakWithStoryImagesCreator,
|
|
4
|
-
wrapOnSave: (data, ctx) => ({
|
|
5
|
-
type: ctx.questionType,
|
|
6
|
-
content: {
|
|
7
|
-
audioUrl: data.audioUrl,
|
|
8
|
-
images: data.images || [],
|
|
9
|
-
isBackup: data.isBackup || false,
|
|
10
|
-
},
|
|
11
|
-
points: data?.points ?? ctx.state.points ?? 1,
|
|
12
|
-
level: ctx.state.level,
|
|
13
|
-
difficulty: ctx.state.difficulty,
|
|
14
|
-
skill: ctx.state.skill,
|
|
15
|
-
answer: {
|
|
16
|
-
expectedAnswers: data.expectedAnswers || [],
|
|
17
|
-
},
|
|
18
|
-
explanation: data?.explanation || '',
|
|
19
|
-
}),
|
|
20
|
-
wrapOnChange: (data, ctx) => ({
|
|
21
|
-
type: ctx.questionType,
|
|
22
|
-
content: {
|
|
23
|
-
audioUrl: data.audioUrl,
|
|
24
|
-
images: data.images || [],
|
|
25
|
-
isBackup: data.isBackup || false,
|
|
26
|
-
},
|
|
27
|
-
points: data?.points ?? ctx.state.points ?? 1,
|
|
28
|
-
level: ctx.state.level,
|
|
29
|
-
difficulty: ctx.state.difficulty,
|
|
30
|
-
skill: ctx.state.skill,
|
|
31
|
-
answer: {
|
|
32
|
-
expectedAnswers: data.expectedAnswers || [],
|
|
33
|
-
},
|
|
34
|
-
explanation: data?.explanation || '',
|
|
35
|
-
}),
|
|
36
|
-
transformInitialData: (data) => {
|
|
37
|
-
const content = data?.content;
|
|
38
|
-
const answer = data?.answer || data?.correctAnswer || data?.correct_answer;
|
|
39
|
-
const points = data?.total_points
|
|
40
|
-
? parseFloat(data.total_points)
|
|
41
|
-
: (data?.points || content?.points || answer?.points || 1);
|
|
42
|
-
const explanation = data?.explanation || content?.explanation || answer?.explanation || '';
|
|
43
|
-
return {
|
|
44
|
-
audioUrl: content?.audioUrl || answer?.audioUrl || '',
|
|
45
|
-
images: content?.images || answer?.images || [],
|
|
46
|
-
expectedAnswers: answer?.expectedAnswers || [],
|
|
47
|
-
explanation,
|
|
48
|
-
points,
|
|
49
|
-
isBackup: content?.isBackup || false,
|
|
50
|
-
};
|
|
51
|
-
},
|
|
52
|
-
getExtraProps: (ctx) => ({
|
|
53
|
-
onUnsavedChangesChange: ctx.onUnsavedChangesChange,
|
|
54
|
-
validationRef: ctx.validationRef,
|
|
55
|
-
}),
|
|
56
|
-
};
|
|
1
|
+
import{ListenAndSpeakWithStoryImagesCreator as e}from"./ListenAndSpeakWithStoryImagesCreator";export const listenAndSpeakWithStoryImagesRegistration={component:e,wrapOnSave:(e,t)=>({type:t.questionType,content:{audioUrl:e.audioUrl,images:e.images||[],isBackup:e.isBackup||!1},points:e?.points??t.state.points??1,level:t.state.level,difficulty:t.state.difficulty,skill:t.state.skill,answer:{expectedAnswers:e.expectedAnswers||[]},explanation:e?.explanation||""}),wrapOnChange:(e,t)=>({type:t.questionType,content:{audioUrl:e.audioUrl,images:e.images||[],isBackup:e.isBackup||!1},points:e?.points??t.state.points??1,level:t.state.level,difficulty:t.state.difficulty,skill:t.state.skill,answer:{expectedAnswers:e.expectedAnswers||[]},explanation:e?.explanation||""}),transformInitialData:e=>{const t=e?.content,a=e?.answer||e?.correctAnswer||e?.correct_answer,n=e?.total_points?parseFloat(e.total_points):e?.points||t?.points||a?.points||1,s=e?.explanation||t?.explanation||a?.explanation||"";return{audioUrl:t?.audioUrl||a?.audioUrl||"",images:t?.images||a?.images||[],expectedAnswers:a?.expectedAnswers||[],explanation:s,points:n,isBackup:t?.isBackup||!1}},getExtraProps:e=>({onUnsavedChangesChange:e.onUnsavedChangesChange,validationRef:e.validationRef})};
|
|
@@ -1,16 +1 @@
|
|
|
1
|
-
export const transformListenAndSpeakWithStoryImages
|
|
2
|
-
const contentData = question.content;
|
|
3
|
-
const answerData = question.answer;
|
|
4
|
-
const expectedAnswers = answerData?.expectedAnswers || [];
|
|
5
|
-
const apiContent = {
|
|
6
|
-
audioUrl: contentData?.audioUrl || answerData?.audioUrl || '',
|
|
7
|
-
images: contentData?.images || answerData?.images || [],
|
|
8
|
-
isBackup: contentData?.isBackup || answerData?.isBackup || false,
|
|
9
|
-
};
|
|
10
|
-
return {
|
|
11
|
-
apiContent,
|
|
12
|
-
apiCorrectAnswer: {
|
|
13
|
-
expectedAnswers: expectedAnswers,
|
|
14
|
-
},
|
|
15
|
-
};
|
|
16
|
-
};
|
|
1
|
+
export const transformListenAndSpeakWithStoryImages=e=>{const s=e.content,a=e.answer,r=a?.expectedAnswers||[];return{apiContent:{audioUrl:s?.audioUrl||a?.audioUrl||"",images:s?.images||a?.images||[],isBackup:s?.isBackup||a?.isBackup||!1},apiCorrectAnswer:{expectedAnswers:r}}};
|
|
@@ -1,72 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useState, useEffect } from 'react';
|
|
4
|
-
import { Check, X, Lightbulb } from 'lucide-react';
|
|
5
|
-
import { usePresignedFileUrl } from '../../../../shared/lib/hooks';
|
|
6
|
-
function SubQuestionClient({ subQuestion, index, userAnswerValue, isReviewMode, onAnswerChange, }) {
|
|
7
|
-
// Use hook at component level
|
|
8
|
-
const { previewUrl: imagePreviewUrl, isLoading } = usePresignedFileUrl(subQuestion.imageUrl || '');
|
|
9
|
-
// Find correct answer
|
|
10
|
-
const correctOption = subQuestion.options.find((opt) => opt.isCorrect);
|
|
11
|
-
const correctOptionId = correctOption?.id || '';
|
|
12
|
-
// Check if answer is correct
|
|
13
|
-
const isAnswerCorrect = userAnswerValue === correctOptionId;
|
|
14
|
-
const hasAnswered = userAnswerValue !== '';
|
|
15
|
-
return (_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-blue-500 to-indigo-600 text-sm font-bold text-white shadow-sm", children: index + 1 }), _jsx("span", { className: "text-sm font-medium text-gray-700", children: "Nh\u00ECn tranh, ch\u1ECDn \u0111\u00E1p \u00E1n \u0111\u00FAng" })] }), isReviewMode && hasAnswered && (_jsxs("div", { className: `flex items-center gap-1.5 rounded-full px-3 py-1 text-xs font-semibold ${isAnswerCorrect
|
|
16
|
-
? 'bg-green-100 text-green-700'
|
|
17
|
-
: '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: "p-5 space-y-4", children: [(imagePreviewUrl || subQuestion.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" }) })) : (
|
|
18
|
-
/* eslint-disable-next-line @next/next/no-img-element */
|
|
19
|
-
_jsx("img", { src: imagePreviewUrl || subQuestion.imageUrl, alt: "Question illustration", className: "max-h-56 w-auto object-contain", onError: (e) => {
|
|
20
|
-
const target = e.target;
|
|
21
|
-
target.onerror = null;
|
|
22
|
-
target.src = '/images/placeholder-image.svg';
|
|
23
|
-
} })) })] })), _jsx("div", { className: "space-y-2", children: subQuestion.options.map((option, optIndex) => {
|
|
24
|
-
const isSelected = userAnswerValue === option.id;
|
|
25
|
-
const isCorrectAnswer = option.isCorrect;
|
|
26
|
-
const letter = String.fromCharCode(65 + optIndex);
|
|
27
|
-
return _jsxs("button", { type: "button", onClick: () => !isReviewMode && onAnswerChange(option.id), disabled: isReviewMode, className: `group flex w-full items-center gap-3 rounded-lg border-2 p-3 text-left transition-all ${isReviewMode
|
|
28
|
-
? isCorrectAnswer
|
|
29
|
-
? 'border-green-400 bg-green-50'
|
|
30
|
-
: isSelected
|
|
31
|
-
? 'border-red-400 bg-red-50'
|
|
32
|
-
: 'border-gray-200 bg-gray-50'
|
|
33
|
-
: isSelected
|
|
34
|
-
? 'border-blue-500 bg-blue-50 shadow-sm'
|
|
35
|
-
: 'border-gray-200 bg-white hover:border-gray-300 hover:bg-gray-50 hover:shadow-sm'}`, children: [_jsx("div", { className: `flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-lg text-sm font-bold transition-all ${isReviewMode
|
|
36
|
-
? isCorrectAnswer
|
|
37
|
-
? 'bg-green-500 text-white'
|
|
38
|
-
: isSelected
|
|
39
|
-
? 'bg-red-500 text-white'
|
|
40
|
-
: 'bg-gray-200 text-gray-600'
|
|
41
|
-
: isSelected
|
|
42
|
-
? 'bg-blue-500 text-white'
|
|
43
|
-
: 'bg-gray-100 text-gray-600 group-hover:bg-gray-200'}`, children: letter }), _jsx("span", { className: `flex-1 text-sm font-medium ${isReviewMode && isCorrectAnswer
|
|
44
|
-
? 'text-green-800'
|
|
45
|
-
: isReviewMode && isSelected && !isCorrectAnswer
|
|
46
|
-
? 'text-red-800'
|
|
47
|
-
: 'text-gray-700'}`, children: option.text }), isReviewMode && (isCorrectAnswer || isSelected) && (_jsx("div", { className: `flex h-6 w-6 items-center justify-center rounded-full ${isCorrectAnswer ? 'bg-green-500' : 'bg-red-500'}`, children: isCorrectAnswer
|
|
48
|
-
? _jsx(Check, { className: "h-4 w-4 text-white" })
|
|
49
|
-
: _jsx(X, { className: "h-4 w-4 text-white" }) }))] }, option.id);
|
|
50
|
-
}) }), isReviewMode && hasAnswered && !isAnswerCorrect && (_jsxs("div", { className: "flex items-center gap-2 rounded-lg bg-green-50 p-3 border border-green-200", children: [_jsx(Check, { className: "h-4 w-4 text-green-600" }), _jsxs("span", { className: "text-sm text-green-700", children: ["\u0110\u00E1p \u00E1n \u0111\u00FAng: ", _jsx("strong", { children: correctOption?.text })] })] }))] })] }));
|
|
51
|
-
}
|
|
52
|
-
export function LookPictureChooseCorrectAnswerClient({ questionData, onSubmit, isReviewMode = false, userAnswer = {}, explanation, }) {
|
|
53
|
-
// State for answers: { subQuestionId: selectedOptionId }
|
|
54
|
-
const [answers, setAnswers] = useState(userAnswer);
|
|
55
|
-
// Sync userAnswer when it changes
|
|
56
|
-
useEffect(() => {
|
|
57
|
-
setAnswers(userAnswer);
|
|
58
|
-
}, [userAnswer]);
|
|
59
|
-
// Handle answer change for a sub-question
|
|
60
|
-
const handleAnswerChange = (subQuestionId, optionId) => {
|
|
61
|
-
if (isReviewMode)
|
|
62
|
-
return;
|
|
63
|
-
const newAnswers = {
|
|
64
|
-
...answers,
|
|
65
|
-
[subQuestionId]: optionId,
|
|
66
|
-
};
|
|
67
|
-
setAnswers(newAnswers);
|
|
68
|
-
// Auto submit if callback provided
|
|
69
|
-
onSubmit?.(newAnswers);
|
|
70
|
-
};
|
|
71
|
-
return (_jsxs("div", { className: "space-y-6", children: [questionData.subQuestions.map((subQuestion, index) => (_jsx(SubQuestionClient, { subQuestion: subQuestion, index: index, userAnswerValue: answers[subQuestion.id] || '', isReviewMode: isReviewMode, onAnswerChange: (value) => handleAnswerChange(subQuestion.id, value) }, subQuestion.id))), isReviewMode && explanation && (_jsx("div", { className: "rounded-xl bg-gradient-to-r from-blue-50 to-indigo-50 p-4 border border-blue-200", children: _jsxs("div", { className: "flex items-start gap-3", children: [_jsx("div", { className: "flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-full bg-blue-500 text-white", children: _jsx(Lightbulb, { className: "h-3.5 w-3.5" }) }), _jsxs("div", { className: "flex-1", children: [_jsx("span", { className: "text-xs font-semibold text-blue-700 uppercase tracking-wider", children: "Gi\u1EA3i th\u00EDch" }), _jsx("p", { className: "mt-1 text-sm text-gray-700 leading-relaxed", children: explanation })] })] }) }))] }));
|
|
72
|
-
}
|
|
1
|
+
"use client";import{jsx as e,jsxs as r}from"react/jsx-runtime";import{useState as t,useEffect as s}from"react";import{Check as i,X as n,Lightbulb as a}from"lucide-react";import{usePresignedFileUrl as d}from"../../../../shared/lib/hooks";function l({subQuestion:t,index:s,userAnswerValue:a,isReviewMode:l,onAnswerChange:o}){const{previewUrl:c,isLoading:g}=d(t.imageUrl||""),m=t.options.find(e=>e.isCorrect),h=a===(m?.id||""),b=""!==a;return 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-blue-500 to-indigo-600 text-sm font-bold text-white shadow-sm",children:s+1}),e("span",{className:"text-sm font-medium text-gray-700",children:"Nhìn tranh, chọn đáp án đúng"})]}),l&&b&&r("div",{className:"flex items-center gap-1.5 rounded-full px-3 py-1 text-xs font-semibold "+(h?"bg-green-100 text-green-700":"bg-red-100 text-red-700"),children:[e(h?i:n,{className:"h-3.5 w-3.5"}),h?"Đúng":"Sai"]})]}),r("div",{className:"p-5 space-y-4",children:[(c||t.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:g?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:c||t.imageUrl,alt:"Question illustration",className:"max-h-56 w-auto object-contain",onError:e=>{const r=e.target;r.onerror=null,r.src="/images/placeholder-image.svg"}})})]}),e("div",{className:"space-y-2",children:t.options.map((t,s)=>{const d=a===t.id,c=t.isCorrect,g=String.fromCharCode(65+s);return r("button",{type:"button",onClick:()=>!l&&o(t.id),disabled:l,className:"group flex w-full items-center gap-3 rounded-lg border-2 p-3 text-left transition-all "+(l?c?"border-green-400 bg-green-50":d?"border-red-400 bg-red-50":"border-gray-200 bg-gray-50":d?"border-blue-500 bg-blue-50 shadow-sm":"border-gray-200 bg-white hover:border-gray-300 hover:bg-gray-50 hover:shadow-sm"),children:[e("div",{className:"flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-lg text-sm font-bold transition-all "+(l?c?"bg-green-500 text-white":d?"bg-red-500 text-white":"bg-gray-200 text-gray-600":d?"bg-blue-500 text-white":"bg-gray-100 text-gray-600 group-hover:bg-gray-200"),children:g}),e("span",{className:"flex-1 text-sm font-medium "+(l&&c?"text-green-800":l&&d&&!c?"text-red-800":"text-gray-700"),children:t.text}),l&&(c||d)&&e("div",{className:"flex h-6 w-6 items-center justify-center rounded-full "+(c?"bg-green-500":"bg-red-500"),children:e(c?i:n,{className:"h-4 w-4 text-white"})})]},t.id)})}),l&&b&&!h&&r("div",{className:"flex items-center gap-2 rounded-lg bg-green-50 p-3 border border-green-200",children:[e(i,{className:"h-4 w-4 text-green-600"}),r("span",{className:"text-sm text-green-700",children:["Đáp án đúng: ",e("strong",{children:m?.text})]})]})]})]})}export function LookPictureChooseCorrectAnswerClient({questionData:i,onSubmit:n,isReviewMode:d=!1,userAnswer:o={},explanation:c}){const[g,m]=t(o);s(()=>{m(o)},[o]);return r("div",{className:"space-y-6",children:[i.subQuestions.map((r,t)=>e(l,{subQuestion:r,index:t,userAnswerValue:g[r.id]||"",isReviewMode:d,onAnswerChange:e=>((e,r)=>{if(d)return;const t={...g,[e]:r};m(t),n?.(t)})(r.id,e)},r.id)),d&&c&&e("div",{className:"rounded-xl bg-gradient-to-r from-blue-50 to-indigo-50 p-4 border border-blue-200",children:r("div",{className:"flex items-start gap-3",children:[e("div",{className:"flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-full bg-blue-500 text-white",children:e(a,{className:"h-3.5 w-3.5"})}),r("div",{className:"flex-1",children:[e("span",{className:"text-xs font-semibold text-blue-700 uppercase tracking-wider",children:"Giải thích"}),e("p",{className:"mt-1 text-sm text-gray-700 leading-relaxed",children:c})]})]})})]})}
|