@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,511 +1 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useState, useRef, useEffect } from 'react';
|
|
4
|
-
import { Button } from '../../../../components/ui/button';
|
|
5
|
-
import { Label } from '../../../../components/ui/label';
|
|
6
|
-
import { Input } from '../../../../components/ui/input';
|
|
7
|
-
import { Textarea } from '../../../../components/ui/textarea';
|
|
8
|
-
import { PointsInput } from '../../../../components/ui/points-input';
|
|
9
|
-
import { Card, CardContent, CardHeader, CardTitle } from '../../../../components/ui/card';
|
|
10
|
-
import { RadioGroup } from '../../../../components/ui/radio-group';
|
|
11
|
-
import { Pencil, Eye, Plus, Trash2, BookOpen, Lightbulb, MessageSquare, CheckCircle2 } from 'lucide-react';
|
|
12
|
-
import { ReadPassageAndAnswerQuestionsClient } from './ReadPassageAndAnswerQuestionsClient';
|
|
13
|
-
import { useDebouncedCallback } from '../../../../shared/lib/hooks';
|
|
14
|
-
import { useExamQuestionStore } from '../../../../shared/lib/stores/examQuestionStore';
|
|
15
|
-
// Helper: Calculate group number for a question index based on groups config
|
|
16
|
-
function getGroupNumberForIndex(questionIndex, groups) {
|
|
17
|
-
if (!groups || groups.length === 0) {
|
|
18
|
-
return 1; // Default to group 1 if no groups config
|
|
19
|
-
}
|
|
20
|
-
let currentIndex = 0;
|
|
21
|
-
for (const group of groups) {
|
|
22
|
-
if (questionIndex < currentIndex + group.questionCount) {
|
|
23
|
-
return group.number;
|
|
24
|
-
}
|
|
25
|
-
currentIndex += group.questionCount;
|
|
26
|
-
}
|
|
27
|
-
return groups[groups.length - 1]?.number || 1;
|
|
28
|
-
}
|
|
29
|
-
/**
|
|
30
|
-
* ReadPassageAndAnswerQuestionsCreator - Single Question Mode
|
|
31
|
-
*
|
|
32
|
-
* This component handles ONE question at a time. Navigation between questions
|
|
33
|
-
* is handled by the parent ExamCreator component.
|
|
34
|
-
*
|
|
35
|
-
* Shared data within group:
|
|
36
|
-
* - passage: string (required)
|
|
37
|
-
* - passageTitle: string (optional)
|
|
38
|
-
* - instructions: string (optional)
|
|
39
|
-
*/
|
|
40
|
-
function ReadPassageAndAnswerQuestionsContent({ initialData, onSave, onSaveToQuestionBank, onCancel, onChange, onUnsavedChangesChange, externalErrors, optionCount = 4, groups, questionIndexInPart = 0, partId = '', validationRef, }) {
|
|
41
|
-
// Access exam question store to find and update shared passage for questions in same group
|
|
42
|
-
const questionsState = useExamQuestionStore((state) => state.questions);
|
|
43
|
-
const currentQuestionIndex = useExamQuestionStore((state) => state.currentQuestionIndex);
|
|
44
|
-
const upsertQuestionData = useExamQuestionStore((state) => state.upsertQuestionData);
|
|
45
|
-
const getQuestionData = useExamQuestionStore((state) => state.getQuestionData);
|
|
46
|
-
const setSharedPassageData = useExamQuestionStore((state) => state.setSharedPassageData);
|
|
47
|
-
const getSharedPassageData = useExamQuestionStore((state) => state.getSharedPassageData);
|
|
48
|
-
// Create a default single question
|
|
49
|
-
const createDefaultQuestion = () => {
|
|
50
|
-
const groupNumber = getGroupNumberForIndex(questionIndexInPart, groups);
|
|
51
|
-
return {
|
|
52
|
-
id: `sq-${Date.now()}-${Math.random()}`,
|
|
53
|
-
question: '',
|
|
54
|
-
options: Array.from({ length: optionCount }, () => ''),
|
|
55
|
-
correctAnswer: -1,
|
|
56
|
-
points: 1,
|
|
57
|
-
groupNumber,
|
|
58
|
-
};
|
|
59
|
-
};
|
|
60
|
-
// Initialize from initialData or create new
|
|
61
|
-
const initializeQuestion = () => {
|
|
62
|
-
// Check for new single question format first
|
|
63
|
-
const content = initialData?.content;
|
|
64
|
-
const answer = initialData?.answer;
|
|
65
|
-
if (content?.question || answer?.singleQuestion) {
|
|
66
|
-
const sq = answer?.singleQuestion || content;
|
|
67
|
-
const pointsFromInitialData = initialData?.points || sq?.points || 1;
|
|
68
|
-
return {
|
|
69
|
-
id: `sq-${Date.now()}-imported`,
|
|
70
|
-
question: sq?.question || '',
|
|
71
|
-
options: sq?.options || Array.from({ length: optionCount }, () => ''),
|
|
72
|
-
correctAnswer: sq?.correctAnswer ?? -1,
|
|
73
|
-
points: pointsFromInitialData,
|
|
74
|
-
groupNumber: getGroupNumberForIndex(questionIndexInPart, groups),
|
|
75
|
-
};
|
|
76
|
-
}
|
|
77
|
-
// Legacy format: singleQuestion in answer
|
|
78
|
-
if (initialData?.singleQuestion) {
|
|
79
|
-
const pointsFromInitialData = initialData?.points || initialData.singleQuestion.points || 1;
|
|
80
|
-
return {
|
|
81
|
-
...initialData.singleQuestion,
|
|
82
|
-
points: pointsFromInitialData,
|
|
83
|
-
groupNumber: getGroupNumberForIndex(questionIndexInPart, groups),
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
|
-
return createDefaultQuestion();
|
|
87
|
-
};
|
|
88
|
-
// State for current question
|
|
89
|
-
const [currentQuestion, setCurrentQuestion] = useState(initializeQuestion);
|
|
90
|
-
// Get current group number for initialization
|
|
91
|
-
const initialGroupNum = getGroupNumberForIndex(questionIndexInPart, groups);
|
|
92
|
-
// Shared passage data state
|
|
93
|
-
const [passage, setPassage] = useState(() => {
|
|
94
|
-
// Priority 1: Check shared passage from store
|
|
95
|
-
if (partId) {
|
|
96
|
-
const storedData = getSharedPassageData(partId, initialGroupNum);
|
|
97
|
-
if (storedData?.passage) {
|
|
98
|
-
return storedData.passage;
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
// Priority 2: From initialData
|
|
102
|
-
const content = initialData?.content;
|
|
103
|
-
const answer = initialData?.answer;
|
|
104
|
-
return answer?.passage || content?.passage || initialData?.passage || '';
|
|
105
|
-
});
|
|
106
|
-
const [passageTitle, setPassageTitle] = useState(() => {
|
|
107
|
-
if (partId) {
|
|
108
|
-
const storedData = getSharedPassageData(partId, initialGroupNum);
|
|
109
|
-
if (storedData?.passageTitle) {
|
|
110
|
-
return storedData.passageTitle;
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
const content = initialData?.content;
|
|
114
|
-
const answer = initialData?.answer;
|
|
115
|
-
return answer?.passageTitle || content?.passageTitle || initialData?.passageTitle || '';
|
|
116
|
-
});
|
|
117
|
-
const [instructions, setInstructions] = useState(() => {
|
|
118
|
-
if (partId) {
|
|
119
|
-
const storedData = getSharedPassageData(partId, initialGroupNum);
|
|
120
|
-
if (storedData?.instructions) {
|
|
121
|
-
return storedData.instructions;
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
const content = initialData?.content;
|
|
125
|
-
const answer = initialData?.answer;
|
|
126
|
-
return answer?.instructions || content?.instructions || initialData?.instructions || '';
|
|
127
|
-
});
|
|
128
|
-
// UI State
|
|
129
|
-
const [isClientMode, setIsClientMode] = useState(false);
|
|
130
|
-
const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false);
|
|
131
|
-
const [explanation, setExplanation] = useState(initialData?.explanation || '');
|
|
132
|
-
// Inline validation errors
|
|
133
|
-
const [validationErrors, setValidationErrors] = useState({});
|
|
134
|
-
// Store the last saved state for comparison
|
|
135
|
-
const savedStateRef = useRef('');
|
|
136
|
-
// Refs for change tracking
|
|
137
|
-
const previousInitialDataRef = useRef(initialData);
|
|
138
|
-
const previousQuestionIndexRef = useRef(questionIndexInPart);
|
|
139
|
-
const isInitializingRef = useRef(false);
|
|
140
|
-
const hasInitializedRef = useRef(false);
|
|
141
|
-
const onChangeRef = useRef(onChange);
|
|
142
|
-
const previousPayloadRef = useRef('');
|
|
143
|
-
// Current group number
|
|
144
|
-
const currentGroupNumber = currentQuestion.groupNumber || 1;
|
|
145
|
-
// Ref for onUnsavedChangesChange callback
|
|
146
|
-
const onUnsavedChangesChangeRef = useRef(onUnsavedChangesChange);
|
|
147
|
-
useEffect(() => {
|
|
148
|
-
onUnsavedChangesChangeRef.current = onUnsavedChangesChange;
|
|
149
|
-
}, [onUnsavedChangesChange]);
|
|
150
|
-
// Update onChange ref
|
|
151
|
-
useEffect(() => {
|
|
152
|
-
onChangeRef.current = onChange;
|
|
153
|
-
}, [onChange]);
|
|
154
|
-
// Debounced onChange callback
|
|
155
|
-
const debouncedOnChange = useDebouncedCallback((data) => {
|
|
156
|
-
onChangeRef.current?.(data);
|
|
157
|
-
}, 300);
|
|
158
|
-
// Helper function to create current state string for comparison
|
|
159
|
-
const getCurrentStateString = () => {
|
|
160
|
-
return JSON.stringify({
|
|
161
|
-
passage,
|
|
162
|
-
passageTitle,
|
|
163
|
-
instructions,
|
|
164
|
-
question: currentQuestion.question,
|
|
165
|
-
options: currentQuestion.options,
|
|
166
|
-
correctAnswer: currentQuestion.correctAnswer,
|
|
167
|
-
points: currentQuestion.points,
|
|
168
|
-
});
|
|
169
|
-
};
|
|
170
|
-
// Initialize saved state when questionIndexInPart changes (navigation)
|
|
171
|
-
useEffect(() => {
|
|
172
|
-
if (isInitializingRef.current)
|
|
173
|
-
return;
|
|
174
|
-
const timer = setTimeout(() => {
|
|
175
|
-
savedStateRef.current = getCurrentStateString();
|
|
176
|
-
setHasUnsavedChanges(false);
|
|
177
|
-
}, 150);
|
|
178
|
-
return () => clearTimeout(timer);
|
|
179
|
-
}, [questionIndexInPart]);
|
|
180
|
-
// Detect unsaved changes
|
|
181
|
-
useEffect(() => {
|
|
182
|
-
if (isInitializingRef.current)
|
|
183
|
-
return;
|
|
184
|
-
if (!savedStateRef.current)
|
|
185
|
-
return;
|
|
186
|
-
const currentState = getCurrentStateString();
|
|
187
|
-
const hasChanges = currentState !== savedStateRef.current;
|
|
188
|
-
if (hasChanges !== hasUnsavedChanges) {
|
|
189
|
-
setHasUnsavedChanges(hasChanges);
|
|
190
|
-
onUnsavedChangesChangeRef.current?.(hasChanges);
|
|
191
|
-
}
|
|
192
|
-
}, [passage, passageTitle, instructions, currentQuestion, hasUnsavedChanges]);
|
|
193
|
-
// Sync from initialData when navigation or first init
|
|
194
|
-
useEffect(() => {
|
|
195
|
-
const questionIndexChanged = questionIndexInPart !== previousQuestionIndexRef.current;
|
|
196
|
-
const content = initialData?.content;
|
|
197
|
-
const answer = initialData?.answer;
|
|
198
|
-
const hasNewFormatData = !!(content?.question || answer?.singleQuestion);
|
|
199
|
-
const hasValidInitialData = hasNewFormatData || !!initialData?.singleQuestion;
|
|
200
|
-
const shouldSyncForFirstInit = !hasInitializedRef.current && hasValidInitialData;
|
|
201
|
-
const shouldSyncForNavigation = questionIndexChanged;
|
|
202
|
-
if (!shouldSyncForFirstInit && !shouldSyncForNavigation) {
|
|
203
|
-
return;
|
|
204
|
-
}
|
|
205
|
-
if (shouldSyncForFirstInit || shouldSyncForNavigation) {
|
|
206
|
-
isInitializingRef.current = true;
|
|
207
|
-
previousInitialDataRef.current = initialData;
|
|
208
|
-
previousQuestionIndexRef.current = questionIndexInPart;
|
|
209
|
-
hasInitializedRef.current = true;
|
|
210
|
-
const groupNum = getGroupNumberForIndex(questionIndexInPart, groups);
|
|
211
|
-
// Initialize question
|
|
212
|
-
setCurrentQuestion({
|
|
213
|
-
...initializeQuestion(),
|
|
214
|
-
groupNumber: groupNum,
|
|
215
|
-
});
|
|
216
|
-
// Handle shared passage data
|
|
217
|
-
let passageData = {
|
|
218
|
-
passage: '',
|
|
219
|
-
passageTitle: '',
|
|
220
|
-
instructions: '',
|
|
221
|
-
};
|
|
222
|
-
// Priority 1: Check shared passage from store
|
|
223
|
-
if (partId) {
|
|
224
|
-
const storedData = getSharedPassageData(partId, groupNum);
|
|
225
|
-
if (storedData?.passage) {
|
|
226
|
-
passageData = {
|
|
227
|
-
passage: storedData.passage,
|
|
228
|
-
passageTitle: storedData.passageTitle || '',
|
|
229
|
-
instructions: storedData.instructions || '',
|
|
230
|
-
};
|
|
231
|
-
}
|
|
232
|
-
else {
|
|
233
|
-
// Priority 2: From initialData (loaded from API)
|
|
234
|
-
passageData = {
|
|
235
|
-
passage: answer?.passage || content?.passage || initialData?.passage || '',
|
|
236
|
-
passageTitle: answer?.passageTitle || content?.passageTitle || initialData?.passageTitle || '',
|
|
237
|
-
instructions: answer?.instructions || content?.instructions || initialData?.instructions || '',
|
|
238
|
-
};
|
|
239
|
-
// Save to store for other questions in the same group to access
|
|
240
|
-
if (passageData.passage) {
|
|
241
|
-
setSharedPassageData(partId, groupNum, passageData);
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
else {
|
|
246
|
-
// No partId, just use initialData
|
|
247
|
-
passageData = {
|
|
248
|
-
passage: answer?.passage || content?.passage || initialData?.passage || '',
|
|
249
|
-
passageTitle: answer?.passageTitle || content?.passageTitle || initialData?.passageTitle || '',
|
|
250
|
-
instructions: answer?.instructions || content?.instructions || initialData?.instructions || '',
|
|
251
|
-
};
|
|
252
|
-
}
|
|
253
|
-
// Set state
|
|
254
|
-
setPassage(passageData.passage);
|
|
255
|
-
setPassageTitle(passageData.passageTitle);
|
|
256
|
-
setInstructions(passageData.instructions);
|
|
257
|
-
setTimeout(() => {
|
|
258
|
-
isInitializingRef.current = false;
|
|
259
|
-
}, 100);
|
|
260
|
-
}
|
|
261
|
-
}, [initialData, groups, questionIndexInPart, partId, getSharedPassageData, setSharedPassageData]);
|
|
262
|
-
// Call onChange when data changes
|
|
263
|
-
useEffect(() => {
|
|
264
|
-
if (isInitializingRef.current)
|
|
265
|
-
return;
|
|
266
|
-
const payload = {
|
|
267
|
-
passage,
|
|
268
|
-
passageTitle,
|
|
269
|
-
instructions,
|
|
270
|
-
explanation,
|
|
271
|
-
singleQuestion: currentQuestion,
|
|
272
|
-
groups,
|
|
273
|
-
};
|
|
274
|
-
payload.points = currentQuestion.points || 1;
|
|
275
|
-
const payloadString = JSON.stringify(payload);
|
|
276
|
-
if (previousPayloadRef.current !== payloadString) {
|
|
277
|
-
previousPayloadRef.current = payloadString;
|
|
278
|
-
// Save shared passage data to store
|
|
279
|
-
if (partId && passage) {
|
|
280
|
-
setSharedPassageData(partId, currentGroupNumber, {
|
|
281
|
-
passage,
|
|
282
|
-
passageTitle,
|
|
283
|
-
instructions,
|
|
284
|
-
});
|
|
285
|
-
}
|
|
286
|
-
debouncedOnChange(payload);
|
|
287
|
-
}
|
|
288
|
-
}, [passage, passageTitle, instructions, explanation, currentQuestion, groups, partId, currentGroupNumber, setSharedPassageData, debouncedOnChange]);
|
|
289
|
-
// Update current question field
|
|
290
|
-
const updateCurrentQuestion = (field, value) => {
|
|
291
|
-
setCurrentQuestion((prev) => ({ ...prev, [field]: value }));
|
|
292
|
-
// Clear validation error
|
|
293
|
-
if (field === 'question' && validationErrors.question) {
|
|
294
|
-
setValidationErrors(prev => ({ ...prev, question: undefined }));
|
|
295
|
-
}
|
|
296
|
-
if (field === 'correctAnswer' && validationErrors.correctAnswer) {
|
|
297
|
-
setValidationErrors(prev => ({ ...prev, correctAnswer: undefined }));
|
|
298
|
-
}
|
|
299
|
-
};
|
|
300
|
-
// Add new option
|
|
301
|
-
const addOption = () => {
|
|
302
|
-
if (currentQuestion.options.length >= 10) {
|
|
303
|
-
return; // Max 10 options
|
|
304
|
-
}
|
|
305
|
-
const newOptions = [...currentQuestion.options, ''];
|
|
306
|
-
setCurrentQuestion((prev) => ({ ...prev, options: newOptions }));
|
|
307
|
-
};
|
|
308
|
-
// Delete option at index
|
|
309
|
-
const deleteOption = (index) => {
|
|
310
|
-
if (currentQuestion.options.length <= 2) {
|
|
311
|
-
return; // Minimum 2 options
|
|
312
|
-
}
|
|
313
|
-
const newOptions = currentQuestion.options.filter((_, i) => i !== index);
|
|
314
|
-
// If deleted option was the correct answer, reset correctAnswer
|
|
315
|
-
let newCorrectAnswer = currentQuestion.correctAnswer;
|
|
316
|
-
if (currentQuestion.correctAnswer === index) {
|
|
317
|
-
newCorrectAnswer = -1;
|
|
318
|
-
}
|
|
319
|
-
else if (currentQuestion.correctAnswer > index) {
|
|
320
|
-
// Shift correctAnswer index down if it's after the deleted option
|
|
321
|
-
newCorrectAnswer = currentQuestion.correctAnswer - 1;
|
|
322
|
-
}
|
|
323
|
-
setCurrentQuestion((prev) => ({ ...prev, options: newOptions, correctAnswer: newCorrectAnswer }));
|
|
324
|
-
};
|
|
325
|
-
// Update option at index
|
|
326
|
-
const updateOption = (index, value) => {
|
|
327
|
-
const newOptions = [...currentQuestion.options];
|
|
328
|
-
newOptions[index] = value;
|
|
329
|
-
setCurrentQuestion((prev) => ({ ...prev, options: newOptions }));
|
|
330
|
-
// Clear validation error
|
|
331
|
-
if (validationErrors.options?.[index]) {
|
|
332
|
-
setValidationErrors(prev => {
|
|
333
|
-
const newOptionsErrors = { ...prev.options };
|
|
334
|
-
delete newOptionsErrors[index];
|
|
335
|
-
return {
|
|
336
|
-
...prev,
|
|
337
|
-
options: Object.keys(newOptionsErrors).length > 0 ? newOptionsErrors : undefined,
|
|
338
|
-
};
|
|
339
|
-
});
|
|
340
|
-
}
|
|
341
|
-
};
|
|
342
|
-
// Validate form data
|
|
343
|
-
const validateForm = () => {
|
|
344
|
-
const errors = {};
|
|
345
|
-
if (!passage.trim()) {
|
|
346
|
-
errors.passage = 'Vui lòng nhập nội dung đoạn văn!';
|
|
347
|
-
}
|
|
348
|
-
if (!currentQuestion.question.trim()) {
|
|
349
|
-
errors.question = 'Vui lòng nhập câu hỏi!';
|
|
350
|
-
}
|
|
351
|
-
// Validate options
|
|
352
|
-
errors.options = {};
|
|
353
|
-
currentQuestion.options.forEach((opt, idx) => {
|
|
354
|
-
if (!opt.trim()) {
|
|
355
|
-
errors.options[idx] = 'Vui lòng nhập đáp án!';
|
|
356
|
-
}
|
|
357
|
-
});
|
|
358
|
-
if (Object.keys(errors.options).length === 0) {
|
|
359
|
-
delete errors.options;
|
|
360
|
-
}
|
|
361
|
-
if (currentQuestion.correctAnswer < 0) {
|
|
362
|
-
errors.correctAnswer = 'Vui lòng chọn đáp án đúng!';
|
|
363
|
-
}
|
|
364
|
-
setValidationErrors(errors);
|
|
365
|
-
if (Object.keys(errors).length > 0) {
|
|
366
|
-
return { isValid: false };
|
|
367
|
-
}
|
|
368
|
-
const saveData = {
|
|
369
|
-
passage,
|
|
370
|
-
passageTitle,
|
|
371
|
-
instructions,
|
|
372
|
-
explanation,
|
|
373
|
-
singleQuestion: currentQuestion,
|
|
374
|
-
groups,
|
|
375
|
-
};
|
|
376
|
-
saveData.points = currentQuestion.points || 1;
|
|
377
|
-
return { isValid: true, saveData };
|
|
378
|
-
};
|
|
379
|
-
// Expose validation function to parent via ref
|
|
380
|
-
useEffect(() => {
|
|
381
|
-
if (validationRef) {
|
|
382
|
-
validationRef.current = {
|
|
383
|
-
triggerValidation: async () => {
|
|
384
|
-
const { isValid } = validateForm();
|
|
385
|
-
return isValid;
|
|
386
|
-
},
|
|
387
|
-
// BUGFIX: Expose getFormData to get fresh form data at save time
|
|
388
|
-
// This bypasses the debounced onChange race condition
|
|
389
|
-
getFormData: () => {
|
|
390
|
-
const data = {
|
|
391
|
-
passage,
|
|
392
|
-
passageTitle,
|
|
393
|
-
instructions,
|
|
394
|
-
explanation,
|
|
395
|
-
singleQuestion: currentQuestion,
|
|
396
|
-
groups,
|
|
397
|
-
};
|
|
398
|
-
data.points = currentQuestion.points || 1;
|
|
399
|
-
return data;
|
|
400
|
-
},
|
|
401
|
-
};
|
|
402
|
-
}
|
|
403
|
-
}, [validationRef, passage, passageTitle, instructions, explanation, currentQuestion, groups]);
|
|
404
|
-
// Sync passage data to all questions in same group
|
|
405
|
-
const syncPassageDataToSameGroup = () => {
|
|
406
|
-
const targetGroupNumber = currentGroupNumber;
|
|
407
|
-
const passageData = { passage, passageTitle, instructions };
|
|
408
|
-
Object.entries(questionsState).forEach(([indexStr, entry]) => {
|
|
409
|
-
const questionIndex = Number(indexStr);
|
|
410
|
-
if (questionIndex === currentQuestionIndex)
|
|
411
|
-
return;
|
|
412
|
-
if (!entry?.data)
|
|
413
|
-
return;
|
|
414
|
-
if (entry.data.type !== 'READ_PASSAGE_AND_ANSWER_QUESTIONS')
|
|
415
|
-
return;
|
|
416
|
-
const answerData = entry.data.answer;
|
|
417
|
-
const sq = answerData?.singleQuestion;
|
|
418
|
-
if (sq?.groupNumber !== targetGroupNumber)
|
|
419
|
-
return;
|
|
420
|
-
const updatedAnswer = {
|
|
421
|
-
...answerData,
|
|
422
|
-
passage: passageData.passage,
|
|
423
|
-
passageTitle: passageData.passageTitle,
|
|
424
|
-
instructions: passageData.instructions,
|
|
425
|
-
};
|
|
426
|
-
upsertQuestionData(questionIndex, {
|
|
427
|
-
...entry.data,
|
|
428
|
-
answer: updatedAnswer,
|
|
429
|
-
});
|
|
430
|
-
});
|
|
431
|
-
};
|
|
432
|
-
// Handle save
|
|
433
|
-
const handleSave = () => {
|
|
434
|
-
const { isValid, saveData } = validateForm();
|
|
435
|
-
if (!isValid || !saveData)
|
|
436
|
-
return;
|
|
437
|
-
// Save shared passage data to store
|
|
438
|
-
if (partId) {
|
|
439
|
-
setSharedPassageData(partId, currentGroupNumber, {
|
|
440
|
-
passage,
|
|
441
|
-
passageTitle,
|
|
442
|
-
instructions,
|
|
443
|
-
});
|
|
444
|
-
}
|
|
445
|
-
// Sync to other questions in same group
|
|
446
|
-
syncPassageDataToSameGroup();
|
|
447
|
-
// Update saved state
|
|
448
|
-
savedStateRef.current = getCurrentStateString();
|
|
449
|
-
setHasUnsavedChanges(false);
|
|
450
|
-
onUnsavedChangesChangeRef.current?.(false);
|
|
451
|
-
onSave?.(saveData);
|
|
452
|
-
};
|
|
453
|
-
// Handle save to question bank
|
|
454
|
-
const handleSaveToQuestionBank = () => {
|
|
455
|
-
const { isValid, saveData } = validateForm();
|
|
456
|
-
if (!isValid || !saveData)
|
|
457
|
-
return;
|
|
458
|
-
if (partId) {
|
|
459
|
-
setSharedPassageData(partId, currentGroupNumber, {
|
|
460
|
-
passage,
|
|
461
|
-
passageTitle,
|
|
462
|
-
instructions,
|
|
463
|
-
});
|
|
464
|
-
}
|
|
465
|
-
syncPassageDataToSameGroup();
|
|
466
|
-
savedStateRef.current = getCurrentStateString();
|
|
467
|
-
setHasUnsavedChanges(false);
|
|
468
|
-
onUnsavedChangesChangeRef.current?.(false);
|
|
469
|
-
onSaveToQuestionBank?.(saveData);
|
|
470
|
-
};
|
|
471
|
-
// Preview mode
|
|
472
|
-
const handlePreview = () => {
|
|
473
|
-
if (!passage.trim()) {
|
|
474
|
-
setValidationErrors(prev => ({ ...prev, passage: 'Vui lòng nhập nội dung đoạn văn trước khi xem trước!' }));
|
|
475
|
-
return;
|
|
476
|
-
}
|
|
477
|
-
setIsClientMode(true);
|
|
478
|
-
};
|
|
479
|
-
// Client mode preview
|
|
480
|
-
if (isClientMode) {
|
|
481
|
-
const questionData = {
|
|
482
|
-
passage,
|
|
483
|
-
passageTitle,
|
|
484
|
-
instructions,
|
|
485
|
-
explanation,
|
|
486
|
-
singleQuestion: currentQuestion,
|
|
487
|
-
groups,
|
|
488
|
-
};
|
|
489
|
-
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: "\u0110\u1ECDc \u0111o\u1EA1n v\u0103n v\u00E0 tr\u1EA3 l\u1EDDi c\u00E2u h\u1ECFi (Preview)" }), _jsx("p", { className: "mt-1 text-sm text-gray-500", children: "Ch\u1EBF \u0111\u1ED9 xem tr\u01B0\u1EDBc" })] }), _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(ReadPassageAndAnswerQuestionsClient, { questionData: questionData, autoFillCorrectAnswers: true, isReviewMode: true, onSubmit: () => { }, explanation: explanation })] }));
|
|
490
|
-
}
|
|
491
|
-
return (_jsxs("div", { className: "space-y-6", children: [_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-purple-500 via-violet-500 to-indigo-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-purple-500 to-violet-600 shadow-md", children: _jsx(BookOpen, { className: "h-5 w-5 text-white" }) }), _jsxs("div", { children: [_jsx(CardTitle, { className: "text-xl font-bold text-gray-900", children: "\u0110\u1ECDc \u0111o\u1EA1n v\u0103n v\u00E0 tr\u1EA3 l\u1EDDi c\u00E2u h\u1ECFi" }), _jsxs("div", { className: "mt-1.5 inline-flex items-center gap-1.5 rounded-full bg-purple-100 px-3 py-1 text-sm font-medium text-purple-700", children: [_jsx("span", { children: "\uD83D\uDCDA" }), _jsxs("span", { children: ["Nh\u00F3m ", currentGroupNumber] })] })] })] }), _jsxs(Button, { variant: "outline", size: "sm", onClick: handlePreview, className: "gap-2 border-violet-200 text-violet-600 hover:bg-violet-50 hover:text-violet-700", children: [_jsx(Eye, { className: "h-4 w-4" }), "Xem tr\u01B0\u1EDBc"] })] }) }), _jsxs(CardContent, { className: "space-y-6", children: [_jsxs("div", { className: `rounded-xl border-2 p-5 transition-all ${validationErrors.passage ? 'border-red-300 bg-gradient-to-r from-red-50 to-rose-50' : 'border-amber-200 bg-gradient-to-r from-amber-50 to-yellow-50'}`, children: [_jsxs("div", { className: "mb-4 flex items-center gap-3", children: [_jsx("div", { className: `flex h-8 w-8 items-center justify-center rounded-lg ${validationErrors.passage ? 'bg-red-100' : 'bg-amber-100'}`, children: _jsx(BookOpen, { className: `h-4 w-4 ${validationErrors.passage ? 'text-red-600' : 'text-amber-600'}` }) }), _jsxs(Label, { className: `text-base font-semibold ${validationErrors.passage ? 'text-red-800' : 'text-amber-800'}`, children: ["\u0110o\u1EA1n v\u0103n \u0111\u1ECDc hi\u1EC3u ", _jsxs("span", { className: "font-normal text-amber-600", children: ["(d\u00F9ng chung cho nh\u00F3m ", currentGroupNumber, ")"] })] })] }), _jsxs("div", { className: "mb-4", children: [_jsx(Label, { htmlFor: "passageTitle", className: "text-sm font-medium text-gray-700", children: "Ti\u00EAu \u0111\u1EC1" }), _jsx(Input, { id: "passageTitle", value: passageTitle, onChange: (e) => setPassageTitle(e.target.value), placeholder: "V\u00ED d\u1EE5: Our Family Holiday in Nha Trang", className: "mt-1.5 border-gray-200 bg-white transition-all focus:border-amber-300 focus:ring-amber-200" })] }), _jsxs("div", { className: "mb-4", children: [_jsxs(Label, { htmlFor: "passage", className: "text-sm font-medium text-gray-700", children: ["N\u1ED9i dung \u0111o\u1EA1n v\u0103n ", _jsx("span", { className: "text-red-500", children: "*" })] }), _jsx(Textarea, { id: "passage", value: passage, onChange: (e) => {
|
|
492
|
-
setPassage(e.target.value);
|
|
493
|
-
if (validationErrors.passage) {
|
|
494
|
-
setValidationErrors(prev => ({ ...prev, passage: undefined }));
|
|
495
|
-
}
|
|
496
|
-
}, placeholder: "Nh\u1EADp n\u1ED9i dung \u0111o\u1EA1n v\u0103n \u0111\u1ECDc hi\u1EC3u...", className: `mt-1.5 min-h-[150px] border-gray-200 bg-white transition-all focus:border-amber-300 focus:ring-amber-200 ${validationErrors.passage ? 'border-red-400 focus:border-red-500 focus:ring-red-200' : ''}`, rows: 6 }), validationErrors.passage && (_jsx("p", { className: "mt-1.5 text-sm text-red-600", children: validationErrors.passage }))] }), _jsxs("div", { children: [_jsx(Label, { htmlFor: "instructions", className: "text-sm font-medium text-gray-700", children: "H\u01B0\u1EDBng d\u1EABn" }), _jsx(Input, { id: "instructions", value: instructions, onChange: (e) => setInstructions(e.target.value), placeholder: "V\u00ED d\u1EE5: Read the short passage and answer the question below.", className: "mt-1.5 border-gray-200 bg-white transition-all focus:border-amber-300 focus:ring-amber-200" })] })] }), externalErrors && externalErrors.length > 0 && (_jsxs("div", { className: "flex items-start gap-3 rounded-xl border border-red-200 bg-gradient-to-r from-red-50 to-rose-50 p-4", children: [_jsx("div", { className: "flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-lg bg-red-100", children: _jsx("span", { className: "text-lg", children: "\u274C" }) }), _jsxs("div", { className: "text-sm", children: [_jsx("p", { className: "font-semibold text-red-800", children: "L\u1ED7i:" }), _jsx("ul", { className: "mt-1 list-inside list-disc text-red-700", children: externalErrors.map((error, index) => (_jsx("li", { children: error }, index))) })] })] }))] })] }), _jsxs(Card, { className: "overflow-hidden border-0 bg-white 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: "bg-gradient-to-r from-indigo-50 to-purple-50 pb-4", 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-indigo-100", children: _jsx(MessageSquare, { className: "h-4 w-4 text-indigo-600" }) }), _jsx(CardTitle, { className: "text-lg font-semibold text-gray-800", children: "N\u1ED9i dung c\u00E2u h\u1ECFi" }), _jsxs("span", { className: "inline-flex items-center gap-1 rounded-full bg-purple-100 px-2.5 py-1 text-xs font-medium text-purple-700", children: ["\uD83D\uDCDA Nh\u00F3m ", currentGroupNumber] })] }) }), _jsxs(CardContent, { className: "space-y-6 pt-6", children: [_jsx("div", { className: "rounded-xl border border-gray-200 bg-gradient-to-r from-gray-50 to-slate-50 p-4", children: _jsx(PointsInput, { id: "points", value: currentQuestion.points || 1, onChange: (e) => {
|
|
497
|
-
updateCurrentQuestion('points', Number(e.target.value) || 1);
|
|
498
|
-
} }) }), _jsxs("div", { className: "space-y-2", children: [_jsxs(Label, { htmlFor: "question", className: "flex items-center gap-2 text-base font-semibold text-gray-800", children: [_jsx(MessageSquare, { className: "h-4 w-4 text-indigo-500" }), "C\u00E2u h\u1ECFi ", _jsx("span", { className: "text-red-500", children: "*" })] }), _jsx(Input, { id: "question", value: currentQuestion.question || '', onChange: (e) => updateCurrentQuestion('question', e.target.value), placeholder: "Nh\u1EADp c\u00E2u h\u1ECFi...", className: `border-gray-200 bg-white transition-all focus:border-indigo-300 focus:ring-indigo-200 ${validationErrors.question ? 'border-red-400 focus:border-red-500 focus:ring-red-200' : ''}` }), validationErrors.question && (_jsx("p", { className: "text-sm text-red-600", children: validationErrors.question }))] }), _jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-6 w-6 items-center justify-center rounded-md bg-indigo-100", children: _jsx(Lightbulb, { className: "h-3.5 w-3.5 text-indigo-600" }) }), _jsxs(Label, { className: "text-base font-semibold text-gray-800", children: ["C\u00E1c l\u1EF1a ch\u1ECDn ", _jsxs("span", { className: "text-sm font-normal text-gray-500", children: ["(", currentQuestion.options.length, " \u0111\u00E1p \u00E1n)"] })] })] }), _jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: addOption, disabled: currentQuestion.options.length >= 10, className: "gap-2 border-green-200 text-green-600 hover:bg-green-50 hover:text-green-700", children: [_jsx(Plus, { className: "h-4 w-4" }), "Th\u00EAm \u0111\u00E1p \u00E1n"] })] }), _jsx(RadioGroup, { value: currentQuestion.correctAnswer >= 0 ? currentQuestion.correctAnswer.toString() : '', onValueChange: (value) => updateCurrentQuestion('correctAnswer', parseInt(value)), children: _jsx("div", { className: "space-y-3", children: currentQuestion.options.map((option, optIndex) => {
|
|
499
|
-
const optionError = validationErrors.options?.[optIndex];
|
|
500
|
-
const isCorrect = currentQuestion.correctAnswer === optIndex;
|
|
501
|
-
const optionLetter = String.fromCharCode(65 + optIndex);
|
|
502
|
-
return (_jsx("div", { className: `group relative rounded-xl border-2 p-4 transition-all duration-200 ${isCorrect
|
|
503
|
-
? 'border-green-300 bg-gradient-to-r from-green-50 to-emerald-50 shadow-md shadow-green-100'
|
|
504
|
-
: 'border-gray-200 bg-white hover:border-gray-300 hover:shadow-sm'}`, children: _jsxs("div", { className: "flex items-start gap-4", children: [_jsx("div", { className: `flex h-10 w-10 flex-shrink-0 cursor-pointer items-center justify-center rounded-xl font-bold transition-all ${isCorrect
|
|
505
|
-
? 'bg-gradient-to-br from-green-500 to-emerald-600 text-white shadow-md'
|
|
506
|
-
: 'bg-gray-100 text-gray-600 hover:bg-indigo-100 hover:text-indigo-600'}`, onClick: () => updateCurrentQuestion('correctAnswer', optIndex), children: optionLetter }), _jsxs("div", { className: "flex-1 space-y-2", children: [_jsx(Input, { id: `option-input-${optIndex}`, value: option, onChange: (e) => updateOption(optIndex, e.target.value), placeholder: `Nhập đáp án ${optionLetter}...`, className: `border-gray-200 bg-white transition-all focus:border-indigo-300 focus:ring-indigo-200 ${optionError ? 'border-red-400 focus:border-red-500 focus:ring-red-200' : ''}` }), optionError && (_jsx("p", { className: "text-sm text-red-600", children: optionError })), isCorrect && (_jsxs("span", { className: "inline-flex items-center gap-1.5 rounded-full bg-green-100 px-3 py-1 text-xs font-semibold text-green-700", children: [_jsx(CheckCircle2, { className: "h-3.5 w-3.5" }), "\u0110\u00E1p \u00E1n \u0111\u00FAng"] }))] }), _jsxs("div", { className: "flex items-center gap-2", children: [!isCorrect && (_jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => updateCurrentQuestion('correctAnswer', optIndex), className: "text-gray-400 hover:text-green-600", title: "Ch\u1ECDn l\u00E0m \u0111\u00E1p \u00E1n \u0111\u00FAng", children: _jsx(CheckCircle2, { className: "h-4 w-4" }) })), currentQuestion.options.length > 2 && (_jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => deleteOption(optIndex), className: "text-red-500 hover:bg-red-50 hover:text-red-600", children: _jsx(Trash2, { className: "h-4 w-4" }) }))] })] }) }, optIndex));
|
|
507
|
-
}) }) }), currentQuestion.correctAnswer < 0 && (_jsxs("div", { className: "flex items-start gap-3 rounded-xl border border-amber-200 bg-gradient-to-r from-amber-50 to-yellow-50 p-4", children: [_jsx("div", { className: "flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-lg bg-amber-100", children: _jsx("span", { className: "text-lg", children: "\u26A0\uFE0F" }) }), _jsxs("div", { className: "text-sm text-amber-800", children: [_jsx("p", { className: "font-semibold", children: "Ch\u01B0a ch\u1ECDn \u0111\u00E1p \u00E1n \u0111\u00FAng" }), _jsx("p", { className: "mt-0.5 text-amber-700", children: "Click v\u00E0o \u00F4 ch\u1EEF c\u00E1i ho\u1EB7c icon \u2713 \u0111\u1EC3 ch\u1ECDn \u0111\u00E1p \u00E1n \u0111\u00FAng" })] })] })), validationErrors.correctAnswer && (_jsx("div", { className: "rounded-xl border border-red-200 bg-red-50 p-4", children: _jsx("p", { className: "text-sm text-red-600", children: validationErrors.correctAnswer }) }))] })] })] }), _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, { id: "explanation", value: explanation, onChange: (e) => setExplanation(e.target.value), placeholder: "VD: C\u00E2u tr\u1EA3 l\u1EDDi \u0111\u00FAng l\u00E0... v\u00EC...", rows: 3, className: "min-h-[80px] border-gray-200 bg-white transition-all focus:border-amber-300 focus:ring-amber-200 resize-none" }) })] })] }));
|
|
508
|
-
}
|
|
509
|
-
export function ReadPassageAndAnswerQuestionsCreator(props) {
|
|
510
|
-
return _jsx(ReadPassageAndAnswerQuestionsContent, { ...props });
|
|
511
|
-
}
|
|
1
|
+
"use client";import{jsx as e,jsxs as t}from"react/jsx-runtime";import{useState as s,useRef as r,useEffect as n}from"react";import{Button as i}from"../../../../components/ui/button";import{Label as a}from"../../../../components/ui/label";import{Input as o}from"../../../../components/ui/input";import{Textarea as l}from"../../../../components/ui/textarea";import{PointsInput as c}from"../../../../components/ui/points-input";import{Card as d,CardContent as g,CardHeader as m,CardTitle as u}from"../../../../components/ui/card";import{RadioGroup as p}from"../../../../components/ui/radio-group";import{Pencil as h,Eye as b,Plus as f,Trash2 as x,BookOpen as N,Lightbulb as v,MessageSquare as w,CheckCircle2 as y}from"lucide-react";import{ReadPassageAndAnswerQuestionsClient as C}from"./ReadPassageAndAnswerQuestionsClient";import{useDebouncedCallback as T}from"../../../../shared/lib/hooks";import{useExamQuestionStore as A}from"../../../../shared/lib/stores/examQuestionStore";function q(e,t){if(!t||0===t.length)return 1;let s=0;for(const r of t){if(e<s+r.questionCount)return r.number;s+=r.questionCount}return t[t.length-1]?.number||1}function Q({initialData:Q,onSave:j,onSaveToQuestionBank:k,onCancel:V,onChange:D,onUnsavedChangesChange:S,externalErrors:F,optionCount:z=4,groups:O,questionIndexInPart:P=0,partId:I="",validationRef:R}){A(e=>e.questions),A(e=>e.currentQuestionIndex),A(e=>e.upsertQuestionData),A(e=>e.getQuestionData);const $=A(e=>e.setSharedPassageData),E=A(e=>e.getSharedPassageData),M=()=>{const e=Q?.content,t=Q?.answer;if(e?.question||t?.singleQuestion){const s=t?.singleQuestion||e,r=Q?.points||s?.points||1;return{id:`sq-${Date.now()}-imported`,question:s?.question||"",options:s?.options||Array.from({length:z},()=>""),correctAnswer:s?.correctAnswer??-1,points:r,groupNumber:q(P,O)}}if(Q?.singleQuestion){const e=Q?.points||Q.singleQuestion.points||1;return{...Q.singleQuestion,points:e,groupNumber:q(P,O)}}return(()=>{const e=q(P,O);return{id:`sq-${Date.now()}-${Math.random()}`,question:"",options:Array.from({length:z},()=>""),correctAnswer:-1,points:1,groupNumber:e}})()},[G,H]=s(M),J=q(P,O),[B,K]=s(()=>{if(I){const e=E(I,J);if(e?.passage)return e.passage}const e=Q?.content,t=Q?.answer;return t?.passage||e?.passage||Q?.passage||""}),[L,U]=s(()=>{if(I){const e=E(I,J);if(e?.passageTitle)return e.passageTitle}const e=Q?.content,t=Q?.answer;return t?.passageTitle||e?.passageTitle||Q?.passageTitle||""}),[X,W]=s(()=>{if(I){const e=E(I,J);if(e?.instructions)return e.instructions}const e=Q?.content,t=Q?.answer;return t?.instructions||e?.instructions||Q?.instructions||""}),[Y,Z]=s(!1),[_,ee]=s(!1),[te,se]=s(Q?.explanation||""),[re,ne]=s({}),ie=r(""),ae=r(Q),oe=r(P),le=r(!1),ce=r(!1),de=r(D),ge=r(""),me=G.groupNumber||1,ue=r(S);n(()=>{ue.current=S},[S]),n(()=>{de.current=D},[D]);const pe=T(e=>{de.current?.(e)},300),he=()=>JSON.stringify({passage:B,passageTitle:L,instructions:X,question:G.question,options:G.options,correctAnswer:G.correctAnswer,points:G.points});n(()=>{if(le.current)return;const e=setTimeout(()=>{ie.current=he(),ee(!1)},150);return()=>clearTimeout(e)},[P]),n(()=>{if(le.current)return;if(!ie.current)return;const e=he()!==ie.current;e!==_&&(ee(e),ue.current?.(e))},[B,L,X,G,_]),n(()=>{const e=P!==oe.current,t=Q?.content,s=Q?.answer,r=!(!t?.question&&!s?.singleQuestion)||!!Q?.singleQuestion,n=!ce.current&&r;if((n||e)&&(n||e)){le.current=!0,ae.current=Q,oe.current=P,ce.current=!0;const e=q(P,O);H({...M(),groupNumber:e});let r={passage:"",passageTitle:"",instructions:""};if(I){const n=E(I,e);n?.passage?r={passage:n.passage,passageTitle:n.passageTitle||"",instructions:n.instructions||""}:(r={passage:s?.passage||t?.passage||Q?.passage||"",passageTitle:s?.passageTitle||t?.passageTitle||Q?.passageTitle||"",instructions:s?.instructions||t?.instructions||Q?.instructions||""},r.passage&&$(I,e,r))}else r={passage:s?.passage||t?.passage||Q?.passage||"",passageTitle:s?.passageTitle||t?.passageTitle||Q?.passageTitle||"",instructions:s?.instructions||t?.instructions||Q?.instructions||""};K(r.passage),U(r.passageTitle),W(r.instructions),setTimeout(()=>{le.current=!1},100)}},[Q,O,P,I,E,$]),n(()=>{if(le.current)return;const e={passage:B,passageTitle:L,instructions:X,explanation:te,singleQuestion:G,groups:O};e.points=G.points||1;const t=JSON.stringify(e);ge.current!==t&&(ge.current=t,I&&B&&$(I,me,{passage:B,passageTitle:L,instructions:X}),pe(e))},[B,L,X,te,G,O,I,me,$,pe]);const be=(e,t)=>{H(s=>({...s,[e]:t})),"question"===e&&re.question&&ne(e=>({...e,question:void 0})),"correctAnswer"===e&&re.correctAnswer&&ne(e=>({...e,correctAnswer:void 0}))},fe=()=>{const e={};if(B.trim()||(e.passage="Vui lòng nhập nội dung đoạn văn!"),G.question.trim()||(e.question="Vui lòng nhập câu hỏi!"),e.options={},G.options.forEach((t,s)=>{t.trim()||(e.options[s]="Vui lòng nhập đáp án!")}),0===Object.keys(e.options).length&&delete e.options,G.correctAnswer<0&&(e.correctAnswer="Vui lòng chọn đáp án đúng!"),ne(e),Object.keys(e).length>0)return{isValid:!1};const t={passage:B,passageTitle:L,instructions:X,explanation:te,singleQuestion:G,groups:O};return t.points=G.points||1,{isValid:!0,saveData:t}};n(()=>{R&&(R.current={triggerValidation:async()=>{const{isValid:e}=fe();return e},getFormData:()=>{const e={passage:B,passageTitle:L,instructions:X,explanation:te,singleQuestion:G,groups:O};return e.points=G.points||1,e}})},[R,B,L,X,te,G,O]);if(Y){const s={passage:B,passageTitle:L,instructions:X,explanation:te,singleQuestion:G,groups:O};return t("div",{className:"space-y-6",children:[e(d,{children:e(m,{children:t("div",{className:"flex items-center justify-between",children:[t("div",{children:[e(u,{children:"Đọc đoạn văn và trả lời câu hỏi (Preview)"}),e("p",{className:"mt-1 text-sm text-gray-500",children:"Chế độ xem trước"})]}),t(i,{variant:"outline",size:"sm",onClick:()=>Z(!1),children:[e(h,{className:"mr-2 h-4 w-4"}),"Quay lại Edit Mode"]})]})})}),e(C,{questionData:s,autoFillCorrectAnswers:!0,isReviewMode:!0,onSubmit:()=>{},explanation:te})]})}return t("div",{className:"space-y-6",children:[t(d,{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-purple-500 via-violet-500 to-indigo-500"}),e(m,{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-purple-500 to-violet-600 shadow-md",children:e(N,{className:"h-5 w-5 text-white"})}),t("div",{children:[e(u,{className:"text-xl font-bold text-gray-900",children:"Đọc đoạn văn và trả lời câu hỏi"}),t("div",{className:"mt-1.5 inline-flex items-center gap-1.5 rounded-full bg-purple-100 px-3 py-1 text-sm font-medium text-purple-700",children:[e("span",{children:"📚"}),t("span",{children:["Nhóm ",me]})]})]})]}),t(i,{variant:"outline",size:"sm",onClick:()=>{B.trim()?Z(!0):ne(e=>({...e,passage:"Vui lòng nhập nội dung đoạn văn trước khi xem trước!"}))},className:"gap-2 border-violet-200 text-violet-600 hover:bg-violet-50 hover:text-violet-700",children:[e(b,{className:"h-4 w-4"}),"Xem trước"]})]})}),t(g,{className:"space-y-6",children:[t("div",{className:"rounded-xl border-2 p-5 transition-all "+(re.passage?"border-red-300 bg-gradient-to-r from-red-50 to-rose-50":"border-amber-200 bg-gradient-to-r from-amber-50 to-yellow-50"),children:[t("div",{className:"mb-4 flex items-center gap-3",children:[e("div",{className:"flex h-8 w-8 items-center justify-center rounded-lg "+(re.passage?"bg-red-100":"bg-amber-100"),children:e(N,{className:"h-4 w-4 "+(re.passage?"text-red-600":"text-amber-600")})}),t(a,{className:"text-base font-semibold "+(re.passage?"text-red-800":"text-amber-800"),children:["Đoạn văn đọc hiểu ",t("span",{className:"font-normal text-amber-600",children:["(dùng chung cho nhóm ",me,")"]})]})]}),t("div",{className:"mb-4",children:[e(a,{htmlFor:"passageTitle",className:"text-sm font-medium text-gray-700",children:"Tiêu đề"}),e(o,{id:"passageTitle",value:L,onChange:e=>U(e.target.value),placeholder:"Ví dụ: Our Family Holiday in Nha Trang",className:"mt-1.5 border-gray-200 bg-white transition-all focus:border-amber-300 focus:ring-amber-200"})]}),t("div",{className:"mb-4",children:[t(a,{htmlFor:"passage",className:"text-sm font-medium text-gray-700",children:["Nội dung đoạn văn ",e("span",{className:"text-red-500",children:"*"})]}),e(l,{id:"passage",value:B,onChange:e=>{K(e.target.value),re.passage&&ne(e=>({...e,passage:void 0}))},placeholder:"Nhập nội dung đoạn văn đọc hiểu...",className:"mt-1.5 min-h-[150px] border-gray-200 bg-white transition-all focus:border-amber-300 focus:ring-amber-200 "+(re.passage?"border-red-400 focus:border-red-500 focus:ring-red-200":""),rows:6}),re.passage&&e("p",{className:"mt-1.5 text-sm text-red-600",children:re.passage})]}),t("div",{children:[e(a,{htmlFor:"instructions",className:"text-sm font-medium text-gray-700",children:"Hướng dẫn"}),e(o,{id:"instructions",value:X,onChange:e=>W(e.target.value),placeholder:"Ví dụ: Read the short passage and answer the question below.",className:"mt-1.5 border-gray-200 bg-white transition-all focus:border-amber-300 focus:ring-amber-200"})]})]}),F&&F.length>0&&t("div",{className:"flex items-start gap-3 rounded-xl border border-red-200 bg-gradient-to-r from-red-50 to-rose-50 p-4",children:[e("div",{className:"flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-lg bg-red-100",children:e("span",{className:"text-lg",children:"❌"})}),t("div",{className:"text-sm",children:[e("p",{className:"font-semibold text-red-800",children:"Lỗi:"}),e("ul",{className:"mt-1 list-inside list-disc text-red-700",children:F.map((t,s)=>e("li",{children:t},s))})]})]})]})]}),t(d,{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(m,{className:"bg-gradient-to-r from-indigo-50 to-purple-50 pb-4",children:t("div",{className:"flex items-center gap-3",children:[e("div",{className:"flex h-8 w-8 items-center justify-center rounded-lg bg-indigo-100",children:e(w,{className:"h-4 w-4 text-indigo-600"})}),e(u,{className:"text-lg font-semibold text-gray-800",children:"Nội dung câu hỏi"}),t("span",{className:"inline-flex items-center gap-1 rounded-full bg-purple-100 px-2.5 py-1 text-xs font-medium text-purple-700",children:["📚 Nhóm ",me]})]})}),t(g,{className:"space-y-6 pt-6",children:[e("div",{className:"rounded-xl border border-gray-200 bg-gradient-to-r from-gray-50 to-slate-50 p-4",children:e(c,{id:"points",value:G.points||1,onChange:e=>{be("points",Number(e.target.value)||1)}})}),t("div",{className:"space-y-2",children:[t(a,{htmlFor:"question",className:"flex items-center gap-2 text-base font-semibold text-gray-800",children:[e(w,{className:"h-4 w-4 text-indigo-500"}),"Câu hỏi ",e("span",{className:"text-red-500",children:"*"})]}),e(o,{id:"question",value:G.question||"",onChange:e=>be("question",e.target.value),placeholder:"Nhập câu hỏi...",className:"border-gray-200 bg-white transition-all focus:border-indigo-300 focus:ring-indigo-200 "+(re.question?"border-red-400 focus:border-red-500 focus:ring-red-200":"")}),re.question&&e("p",{className:"text-sm text-red-600",children:re.question})]}),t("div",{className:"space-y-4",children:[t("div",{className:"flex items-center justify-between",children:[t("div",{className:"flex items-center gap-2",children:[e("div",{className:"flex h-6 w-6 items-center justify-center rounded-md bg-indigo-100",children:e(v,{className:"h-3.5 w-3.5 text-indigo-600"})}),t(a,{className:"text-base font-semibold text-gray-800",children:["Các lựa chọn ",t("span",{className:"text-sm font-normal text-gray-500",children:["(",G.options.length," đáp án)"]})]})]}),t(i,{type:"button",variant:"outline",size:"sm",onClick:()=>{if(G.options.length>=10)return;const e=[...G.options,""];H(t=>({...t,options:e}))},disabled:G.options.length>=10,className:"gap-2 border-green-200 text-green-600 hover:bg-green-50 hover:text-green-700",children:[e(f,{className:"h-4 w-4"}),"Thêm đáp án"]})]}),e(p,{value:G.correctAnswer>=0?G.correctAnswer.toString():"",onValueChange:e=>be("correctAnswer",parseInt(e)),children:e("div",{className:"space-y-3",children:G.options.map((s,r)=>{const n=re.options?.[r],a=G.correctAnswer===r,l=String.fromCharCode(65+r);return e("div",{className:"group relative rounded-xl border-2 p-4 transition-all duration-200 "+(a?"border-green-300 bg-gradient-to-r from-green-50 to-emerald-50 shadow-md shadow-green-100":"border-gray-200 bg-white hover:border-gray-300 hover:shadow-sm"),children:t("div",{className:"flex items-start gap-4",children:[e("div",{className:"flex h-10 w-10 flex-shrink-0 cursor-pointer items-center justify-center rounded-xl font-bold transition-all "+(a?"bg-gradient-to-br from-green-500 to-emerald-600 text-white shadow-md":"bg-gray-100 text-gray-600 hover:bg-indigo-100 hover:text-indigo-600"),onClick:()=>be("correctAnswer",r),children:l}),t("div",{className:"flex-1 space-y-2",children:[e(o,{id:`option-input-${r}`,value:s,onChange:e=>((e,t)=>{const s=[...G.options];s[e]=t,H(e=>({...e,options:s})),re.options?.[e]&&ne(t=>{const s={...t.options};return delete s[e],{...t,options:Object.keys(s).length>0?s:void 0}})})(r,e.target.value),placeholder:`Nhập đáp án ${l}...`,className:"border-gray-200 bg-white transition-all focus:border-indigo-300 focus:ring-indigo-200 "+(n?"border-red-400 focus:border-red-500 focus:ring-red-200":"")}),n&&e("p",{className:"text-sm text-red-600",children:n}),a&&t("span",{className:"inline-flex items-center gap-1.5 rounded-full bg-green-100 px-3 py-1 text-xs font-semibold text-green-700",children:[e(y,{className:"h-3.5 w-3.5"}),"Đáp án đúng"]})]}),t("div",{className:"flex items-center gap-2",children:[!a&&e(i,{type:"button",variant:"ghost",size:"sm",onClick:()=>be("correctAnswer",r),className:"text-gray-400 hover:text-green-600",title:"Chọn làm đáp án đúng",children:e(y,{className:"h-4 w-4"})}),G.options.length>2&&e(i,{type:"button",variant:"ghost",size:"sm",onClick:()=>(e=>{if(G.options.length<=2)return;const t=G.options.filter((t,s)=>s!==e);let s=G.correctAnswer;G.correctAnswer===e?s=-1:G.correctAnswer>e&&(s=G.correctAnswer-1),H(e=>({...e,options:t,correctAnswer:s}))})(r),className:"text-red-500 hover:bg-red-50 hover:text-red-600",children:e(x,{className:"h-4 w-4"})})]})]})},r)})})}),G.correctAnswer<0&&t("div",{className:"flex items-start gap-3 rounded-xl border border-amber-200 bg-gradient-to-r from-amber-50 to-yellow-50 p-4",children:[e("div",{className:"flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-lg bg-amber-100",children:e("span",{className:"text-lg",children:"⚠️"})}),t("div",{className:"text-sm text-amber-800",children:[e("p",{className:"font-semibold",children:"Chưa chọn đáp án đúng"}),e("p",{className:"mt-0.5 text-amber-700",children:"Click vào ô chữ cái hoặc icon ✓ để chọn đáp án đúng"})]})]}),re.correctAnswer&&e("div",{className:"rounded-xl border border-red-200 bg-red-50 p-4",children:e("p",{className:"text-sm text-red-600",children:re.correctAnswer})})]})]})]}),t(d,{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(m,{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(N,{className:"h-4 w-4 text-white"})}),t("div",{children:[t("div",{className:"flex items-center gap-2",children:[e(u,{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(g,{children:e(l,{id:"explanation",value:te,onChange:e=>se(e.target.value),placeholder:"VD: Câu trả lời đúng là... vì...",rows:3,className:"min-h-[80px] border-gray-200 bg-white transition-all focus:border-amber-300 focus:ring-amber-200 resize-none"})})]})]})}export function ReadPassageAndAnswerQuestionsCreator(t){return e(Q,{...t})}
|
|
@@ -1,2 +1 @@
|
|
|
1
|
-
export
|
|
2
|
-
export * from './ReadPassageAndAnswerQuestionsClient';
|
|
1
|
+
export*from"./ReadPassageAndAnswerQuestionsCreator";export*from"./ReadPassageAndAnswerQuestionsClient";
|
|
@@ -1,58 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
export const readPassageAndAnswerQuestionsRegistration = {
|
|
3
|
-
component: ReadPassageAndAnswerQuestionsCreator,
|
|
4
|
-
transformInitialData: (initialData) => {
|
|
5
|
-
const content = initialData?.content || {};
|
|
6
|
-
const answer = initialData?.answer || {};
|
|
7
|
-
const explanation = initialData?.explanation || answer?.explanation || '';
|
|
8
|
-
const apiPoints = initialData?.total_points
|
|
9
|
-
? parseFloat(initialData.total_points)
|
|
10
|
-
: (initialData?.points || 1);
|
|
11
|
-
const creatorData = {
|
|
12
|
-
passage: answer?.passage || content?.passage || '',
|
|
13
|
-
passageTitle: answer?.passageTitle || content?.passageTitle || '',
|
|
14
|
-
instructions: answer?.instructions || content?.instructions || '',
|
|
15
|
-
explanation,
|
|
16
|
-
points: apiPoints,
|
|
17
|
-
};
|
|
18
|
-
// singleQuestion format
|
|
19
|
-
if (answer?.singleQuestion || content?.question) {
|
|
20
|
-
creatorData.singleQuestion = answer?.singleQuestion || {
|
|
21
|
-
id: `sq-${Date.now()}-imported`,
|
|
22
|
-
question: content?.question || '',
|
|
23
|
-
options: content?.options || [],
|
|
24
|
-
correctAnswer: answer?.correctAnswer ?? content?.correctAnswer ?? -1,
|
|
25
|
-
points: apiPoints,
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
|
-
return creatorData;
|
|
29
|
-
},
|
|
30
|
-
getExtraProps: (ctx) => ({
|
|
31
|
-
optionCount: ctx.optionCount || 4,
|
|
32
|
-
groups: ctx.groups,
|
|
33
|
-
questionIndexInPart: ctx.questionIndexInPart,
|
|
34
|
-
partId: ctx.partId,
|
|
35
|
-
validationRef: ctx.validationRef,
|
|
36
|
-
onUnsavedChangesChange: ctx.onUnsavedChangesChange,
|
|
37
|
-
}),
|
|
38
|
-
wrapOnSave: (data, ctx) => ({
|
|
39
|
-
type: ctx.questionType,
|
|
40
|
-
content: ctx.state.content,
|
|
41
|
-
points: data?.points ?? ctx.state.points ?? 1,
|
|
42
|
-
level: ctx.state.level,
|
|
43
|
-
difficulty: ctx.state.difficulty,
|
|
44
|
-
skill: ctx.state.skill,
|
|
45
|
-
answer: data,
|
|
46
|
-
explanation: data?.explanation || '',
|
|
47
|
-
}),
|
|
48
|
-
wrapOnChange: (data, ctx) => ({
|
|
49
|
-
type: ctx.questionType,
|
|
50
|
-
content: ctx.state.content,
|
|
51
|
-
points: data?.points ?? ctx.state.points ?? 1,
|
|
52
|
-
level: ctx.state.level,
|
|
53
|
-
difficulty: ctx.state.difficulty,
|
|
54
|
-
skill: ctx.state.skill,
|
|
55
|
-
answer: data,
|
|
56
|
-
explanation: data?.explanation || '',
|
|
57
|
-
}),
|
|
58
|
-
};
|
|
1
|
+
import{ReadPassageAndAnswerQuestionsCreator as t}from"./ReadPassageAndAnswerQuestionsCreator";export const readPassageAndAnswerQuestionsRegistration={component:t,transformInitialData:t=>{const n=t?.content||{},e=t?.answer||{},s=t?.explanation||e?.explanation||"",a=t?.total_points?parseFloat(t.total_points):t?.points||1,o={passage:e?.passage||n?.passage||"",passageTitle:e?.passageTitle||n?.passageTitle||"",instructions:e?.instructions||n?.instructions||"",explanation:s,points:a};return(e?.singleQuestion||n?.question)&&(o.singleQuestion=e?.singleQuestion||{id:`sq-${Date.now()}-imported`,question:n?.question||"",options:n?.options||[],correctAnswer:e?.correctAnswer??n?.correctAnswer??-1,points:a}),o},getExtraProps:t=>({optionCount:t.optionCount||4,groups:t.groups,questionIndexInPart:t.questionIndexInPart,partId:t.partId,validationRef:t.validationRef,onUnsavedChangesChange:t.onUnsavedChangesChange}),wrapOnSave:(t,n)=>({type:n.questionType,content:n.state.content,points:t?.points??n.state.points??1,level:n.state.level,difficulty:n.state.difficulty,skill:n.state.skill,answer:t,explanation:t?.explanation||""}),wrapOnChange:(t,n)=>({type:n.questionType,content:n.state.content,points:t?.points??n.state.points??1,level:n.state.level,difficulty:n.state.difficulty,skill:n.state.skill,answer:t,explanation:t?.explanation||""})};
|