@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,448 +1 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { ImagePreview } from '../../../../components/ui/image-preview';
|
|
4
|
-
import { useState, useRef, useEffect, useMemo } from 'react';
|
|
5
|
-
import { Button } from '../../../../components/ui/button';
|
|
6
|
-
import { Label } from '../../../../components/ui/label';
|
|
7
|
-
import { Input } from '../../../../components/ui/input';
|
|
8
|
-
import { Textarea } from '../../../../components/ui/textarea';
|
|
9
|
-
import { PointsInput } from '../../../../components/ui/points-input';
|
|
10
|
-
import { Card, CardContent, CardHeader, CardTitle } from '../../../../components/ui/card';
|
|
11
|
-
import { Switch } from '../../../../components/ui/switch';
|
|
12
|
-
import { Pencil, Eye, Lightbulb, Type, MessageSquare, ToggleLeft, Plus, Trash2, ListChecks, ImageIcon, BookOpen } from 'lucide-react';
|
|
13
|
-
import { FillInBlankClient } from './FillInBlankClient';
|
|
14
|
-
import { useDebouncedCallback, usePresignedFileUrl } from '../../../../shared/lib/hooks';
|
|
15
|
-
import { FileUpload } from '../../../../components/ui/file-upload';
|
|
16
|
-
import { BasicQuestionGroupCard } from '../../groups/BasicQuestionGroupCard';
|
|
17
|
-
import { useBasicQuestionGroup } from '../../_shared/hooks/useBasicQuestionGroup';
|
|
18
|
-
// Helper: Extract placeholder indices from question text
|
|
19
|
-
// e.g., "He {0} 10 years old and she {1} 12." -> [0, 1]
|
|
20
|
-
function extractPlaceholderIndices(question) {
|
|
21
|
-
const regex = /\{(\d+)\}/g;
|
|
22
|
-
const indices = [];
|
|
23
|
-
let match;
|
|
24
|
-
while ((match = regex.exec(question)) !== null) {
|
|
25
|
-
const index = parseInt(match[1], 10);
|
|
26
|
-
if (!indices.includes(index)) {
|
|
27
|
-
indices.push(index);
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
return indices.sort((a, b) => a - b);
|
|
31
|
-
}
|
|
32
|
-
// Helper: Get the maximum placeholder index
|
|
33
|
-
function getMaxPlaceholderIndex(question) {
|
|
34
|
-
const indices = extractPlaceholderIndices(question);
|
|
35
|
-
return indices.length > 0 ? Math.max(...indices) : -1;
|
|
36
|
-
}
|
|
37
|
-
// Helper: Determine which group a question belongs to based on its index in the part
|
|
38
|
-
function getGroupNumberForIndex(questionIndexInPart, groups) {
|
|
39
|
-
if (!groups || groups.length === 0 || questionIndexInPart === undefined)
|
|
40
|
-
return 1;
|
|
41
|
-
let cumulative = 0;
|
|
42
|
-
for (const group of groups) {
|
|
43
|
-
cumulative += group.questionCount;
|
|
44
|
-
if (questionIndexInPart < cumulative) {
|
|
45
|
-
return group.number;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
return groups[groups.length - 1]?.number ?? 1;
|
|
49
|
-
}
|
|
50
|
-
function FillInBlankCreatorContent({ initialData, onSave, onCancel, onChange, externalErrors, onUnsavedChangesChange, validationRef, groups, questionIndexInPart, partId, headerExtra, }) {
|
|
51
|
-
// ============ GROUP (shared title / content / image) ============
|
|
52
|
-
const { isGrouped, currentGroupNumber, groupTitle, groupSubtitle, groupContent, imageUrl, setGroupTitle, setGroupSubtitle, setGroupContent, setImageUrl, } = useBasicQuestionGroup({
|
|
53
|
-
partId,
|
|
54
|
-
groups,
|
|
55
|
-
questionIndexInPart,
|
|
56
|
-
initialData: {
|
|
57
|
-
title: (() => {
|
|
58
|
-
if (initialData?.content?.passageTitle)
|
|
59
|
-
return initialData.content.passageTitle;
|
|
60
|
-
const qgPayload = initialData?.questionGroup?.payload;
|
|
61
|
-
if (qgPayload?.title)
|
|
62
|
-
return qgPayload.title;
|
|
63
|
-
if (initialData?.title)
|
|
64
|
-
return initialData.title;
|
|
65
|
-
return '';
|
|
66
|
-
})(),
|
|
67
|
-
subtitle: (() => {
|
|
68
|
-
if (initialData?.content?.passageSubtitle)
|
|
69
|
-
return initialData.content.passageSubtitle;
|
|
70
|
-
const qgPayload = initialData?.questionGroup?.payload;
|
|
71
|
-
if (qgPayload?.subtitle)
|
|
72
|
-
return qgPayload.subtitle;
|
|
73
|
-
return '';
|
|
74
|
-
})(),
|
|
75
|
-
content: (() => {
|
|
76
|
-
if (initialData?.content?.passage)
|
|
77
|
-
return initialData.content.passage;
|
|
78
|
-
const qgPayload = initialData?.questionGroup?.payload;
|
|
79
|
-
if (qgPayload?.content)
|
|
80
|
-
return qgPayload.content;
|
|
81
|
-
if (initialData?.groupContent)
|
|
82
|
-
return initialData.groupContent;
|
|
83
|
-
return '';
|
|
84
|
-
})(),
|
|
85
|
-
imageUrl: (() => {
|
|
86
|
-
if (initialData?.content?.imageUrl)
|
|
87
|
-
return initialData.content.imageUrl;
|
|
88
|
-
const qgPayload = initialData?.questionGroup?.payload;
|
|
89
|
-
if (qgPayload?.imageUrl)
|
|
90
|
-
return qgPayload.imageUrl;
|
|
91
|
-
if (initialData?.imageUrl)
|
|
92
|
-
return initialData.imageUrl;
|
|
93
|
-
return '';
|
|
94
|
-
})(),
|
|
95
|
-
},
|
|
96
|
-
});
|
|
97
|
-
// ============ STATE ============
|
|
98
|
-
// Support NEW API format (content.question + correctAnswer.answers) and legacy formats
|
|
99
|
-
const getInitialQuestion = () => {
|
|
100
|
-
// NEW API format: content.question
|
|
101
|
-
if (initialData?.content?.question)
|
|
102
|
-
return initialData.content.question;
|
|
103
|
-
// Legacy format: question at root level
|
|
104
|
-
if (initialData?.question)
|
|
105
|
-
return initialData.question;
|
|
106
|
-
// Very old format: blanks[].text
|
|
107
|
-
if (initialData?.blanks?.[0]?.text)
|
|
108
|
-
return initialData.blanks[0].text;
|
|
109
|
-
return '';
|
|
110
|
-
};
|
|
111
|
-
const getInitialAnswers = () => {
|
|
112
|
-
// NEW API format: correctAnswer.answers
|
|
113
|
-
if (initialData?.correctAnswer?.answers && Array.isArray(initialData.correctAnswer.answers)) {
|
|
114
|
-
return initialData.correctAnswer.answers;
|
|
115
|
-
}
|
|
116
|
-
// Legacy format: answers at root level
|
|
117
|
-
if (initialData?.answers && initialData.answers.length > 0) {
|
|
118
|
-
return initialData.answers;
|
|
119
|
-
}
|
|
120
|
-
// Very old format: blanks[].correctAnswer
|
|
121
|
-
if (initialData?.blanks?.[0]?.correctAnswer) {
|
|
122
|
-
return [initialData.blanks[0].correctAnswer];
|
|
123
|
-
}
|
|
124
|
-
return [];
|
|
125
|
-
};
|
|
126
|
-
const getInitialCaseSensitive = () => {
|
|
127
|
-
// NEW API format: correctAnswer.caseSensitive
|
|
128
|
-
if (initialData?.correctAnswer?.caseSensitive !== undefined) {
|
|
129
|
-
return initialData.correctAnswer.caseSensitive;
|
|
130
|
-
}
|
|
131
|
-
// Legacy format: caseSensitive at root level
|
|
132
|
-
return initialData?.caseSensitive || false;
|
|
133
|
-
};
|
|
134
|
-
const getInitialShowHints = () => {
|
|
135
|
-
// NEW API format: content.showHints
|
|
136
|
-
if (initialData?.content?.showHints !== undefined) {
|
|
137
|
-
return Boolean(initialData.content.showHints);
|
|
138
|
-
}
|
|
139
|
-
// Flat format from answer or root level
|
|
140
|
-
if (initialData?.showHints !== undefined) {
|
|
141
|
-
return Boolean(initialData.showHints);
|
|
142
|
-
}
|
|
143
|
-
// Default: false
|
|
144
|
-
return false;
|
|
145
|
-
};
|
|
146
|
-
const getInitialHints = () => {
|
|
147
|
-
// NEW API format: content.hints
|
|
148
|
-
if (Array.isArray(initialData?.content?.hints)) {
|
|
149
|
-
return initialData.content.hints;
|
|
150
|
-
}
|
|
151
|
-
// Flat format from answer or root level
|
|
152
|
-
if (Array.isArray(initialData?.hints)) {
|
|
153
|
-
return initialData.hints;
|
|
154
|
-
}
|
|
155
|
-
// Default: empty array
|
|
156
|
-
return [];
|
|
157
|
-
};
|
|
158
|
-
const [question, setQuestion] = useState(getInitialQuestion());
|
|
159
|
-
const [answers, setAnswers] = useState(getInitialAnswers());
|
|
160
|
-
const [caseSensitive, setCaseSensitive] = useState(getInitialCaseSensitive());
|
|
161
|
-
const [explanation, setExplanation] = useState(initialData?.explanation || '');
|
|
162
|
-
const [points, setPoints] = useState(initialData?.points || 1);
|
|
163
|
-
const [showHints, setShowHints] = useState(getInitialShowHints());
|
|
164
|
-
const [hints, setHints] = useState(getInitialHints());
|
|
165
|
-
const [newHint, setNewHint] = useState('');
|
|
166
|
-
const [isExample, setIsExample] = useState(initialData?.isExample || false);
|
|
167
|
-
// UI State
|
|
168
|
-
const [isClientMode, setIsClientMode] = useState(false);
|
|
169
|
-
const [imagePreviewUrl, setImagePreviewUrl] = useState('');
|
|
170
|
-
// Presigned URL for per-question image preview (non-grouped only)
|
|
171
|
-
const { previewUrl: generatedPreviewUrl } = usePresignedFileUrl(imageUrl);
|
|
172
|
-
const displayPreviewUrl = imagePreviewUrl || generatedPreviewUrl || '';
|
|
173
|
-
// Inline validation errors
|
|
174
|
-
const [validationErrors, setValidationErrors] = useState({});
|
|
175
|
-
// Refs for initialization tracking
|
|
176
|
-
const previousInitialDataRef = useRef(initialData);
|
|
177
|
-
const isInitializingRef = useRef(false);
|
|
178
|
-
const onChangeRef = useRef(onChange);
|
|
179
|
-
const previousPayloadRef = useRef('');
|
|
180
|
-
// NOTE: Status tracking is now handled entirely by ExamCreator via localStorage (exam_question_status_{id})
|
|
181
|
-
// The onChange callback triggers handleQuestionDraftChange in ExamCreator which updates localStorage
|
|
182
|
-
// Update refs
|
|
183
|
-
useEffect(() => {
|
|
184
|
-
onChangeRef.current = onChange;
|
|
185
|
-
}, [onChange]);
|
|
186
|
-
// Debounced onChange callback
|
|
187
|
-
const debouncedOnChange = useDebouncedCallback((data) => {
|
|
188
|
-
onChangeRef.current?.(data);
|
|
189
|
-
}, 300);
|
|
190
|
-
// Extract placeholder indices from question
|
|
191
|
-
const placeholderIndices = useMemo(() => extractPlaceholderIndices(question), [question]);
|
|
192
|
-
const maxIndex = useMemo(() => getMaxPlaceholderIndex(question), [question]);
|
|
193
|
-
// Ensure answers array has correct length when placeholders change
|
|
194
|
-
useEffect(() => {
|
|
195
|
-
if (maxIndex >= 0) {
|
|
196
|
-
const requiredLength = maxIndex + 1;
|
|
197
|
-
if (answers.length < requiredLength) {
|
|
198
|
-
// Extend array with empty strings
|
|
199
|
-
const newAnswers = [...answers];
|
|
200
|
-
while (newAnswers.length < requiredLength) {
|
|
201
|
-
newAnswers.push('');
|
|
202
|
-
}
|
|
203
|
-
setAnswers(newAnswers);
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
}, [maxIndex, answers.length]);
|
|
207
|
-
// Expose validation function via ref
|
|
208
|
-
useEffect(() => {
|
|
209
|
-
if (validationRef) {
|
|
210
|
-
validationRef.current = {
|
|
211
|
-
triggerValidation: async () => {
|
|
212
|
-
return validateForm();
|
|
213
|
-
},
|
|
214
|
-
// BUGFIX: Expose getFormData to get fresh form data at save time
|
|
215
|
-
// This bypasses the debounced onChange race condition
|
|
216
|
-
getFormData: () => ({
|
|
217
|
-
question,
|
|
218
|
-
answers,
|
|
219
|
-
caseSensitive,
|
|
220
|
-
explanation,
|
|
221
|
-
points,
|
|
222
|
-
imageUrl,
|
|
223
|
-
showHints,
|
|
224
|
-
hints: showHints ? hints : [],
|
|
225
|
-
isExample,
|
|
226
|
-
title: groupTitle,
|
|
227
|
-
subtitle: groupSubtitle,
|
|
228
|
-
groupContent,
|
|
229
|
-
groupNumber: currentGroupNumber,
|
|
230
|
-
groups,
|
|
231
|
-
}),
|
|
232
|
-
};
|
|
233
|
-
}
|
|
234
|
-
}, [validationRef, question, answers, caseSensitive, explanation, points, imageUrl, showHints, hints, groupTitle, groupSubtitle, groupContent, currentGroupNumber, groups]);
|
|
235
|
-
// ============ SYNC INITIAL DATA ============
|
|
236
|
-
useEffect(() => {
|
|
237
|
-
const initialDataChanged = JSON.stringify(previousInitialDataRef.current) !== JSON.stringify(initialData);
|
|
238
|
-
if (initialDataChanged) {
|
|
239
|
-
isInitializingRef.current = true;
|
|
240
|
-
previousInitialDataRef.current = initialData;
|
|
241
|
-
if (initialData) {
|
|
242
|
-
setQuestion(getInitialQuestion());
|
|
243
|
-
setAnswers(getInitialAnswers());
|
|
244
|
-
setCaseSensitive(getInitialCaseSensitive());
|
|
245
|
-
// Explanation can be at root level or in initialData
|
|
246
|
-
setExplanation(initialData.explanation || '');
|
|
247
|
-
// Points can be at root level
|
|
248
|
-
setPoints(initialData.points || 1);
|
|
249
|
-
setShowHints(getInitialShowHints());
|
|
250
|
-
setHints(getInitialHints());
|
|
251
|
-
setIsExample(initialData.isExample || false);
|
|
252
|
-
}
|
|
253
|
-
else {
|
|
254
|
-
setQuestion('');
|
|
255
|
-
setAnswers([]);
|
|
256
|
-
setCaseSensitive(false);
|
|
257
|
-
setExplanation('');
|
|
258
|
-
setPoints(1);
|
|
259
|
-
setShowHints(false);
|
|
260
|
-
setHints([]);
|
|
261
|
-
setIsExample(false);
|
|
262
|
-
}
|
|
263
|
-
setTimeout(() => {
|
|
264
|
-
isInitializingRef.current = false;
|
|
265
|
-
}, 150);
|
|
266
|
-
}
|
|
267
|
-
}, [initialData]);
|
|
268
|
-
// ============ ON CHANGE CALLBACK ============
|
|
269
|
-
useEffect(() => {
|
|
270
|
-
if (isInitializingRef.current) {
|
|
271
|
-
const currentPayload = JSON.stringify({
|
|
272
|
-
question,
|
|
273
|
-
answers,
|
|
274
|
-
caseSensitive,
|
|
275
|
-
explanation,
|
|
276
|
-
points,
|
|
277
|
-
imageUrl,
|
|
278
|
-
showHints,
|
|
279
|
-
hints,
|
|
280
|
-
isExample,
|
|
281
|
-
title: groupTitle,
|
|
282
|
-
subtitle: groupSubtitle,
|
|
283
|
-
groupContent,
|
|
284
|
-
groupNumber: currentGroupNumber,
|
|
285
|
-
groups,
|
|
286
|
-
});
|
|
287
|
-
previousPayloadRef.current = currentPayload;
|
|
288
|
-
return;
|
|
289
|
-
}
|
|
290
|
-
const payload = {
|
|
291
|
-
question,
|
|
292
|
-
answers,
|
|
293
|
-
caseSensitive,
|
|
294
|
-
explanation,
|
|
295
|
-
points,
|
|
296
|
-
imageUrl,
|
|
297
|
-
showHints,
|
|
298
|
-
hints: showHints ? hints : [],
|
|
299
|
-
isExample,
|
|
300
|
-
title: groupTitle,
|
|
301
|
-
subtitle: groupSubtitle,
|
|
302
|
-
groupContent,
|
|
303
|
-
groupNumber: currentGroupNumber,
|
|
304
|
-
groups,
|
|
305
|
-
};
|
|
306
|
-
const payloadString = JSON.stringify(payload);
|
|
307
|
-
if (previousPayloadRef.current !== payloadString) {
|
|
308
|
-
previousPayloadRef.current = payloadString;
|
|
309
|
-
debouncedOnChange(payload);
|
|
310
|
-
}
|
|
311
|
-
}, [question, answers, caseSensitive, explanation, points, imageUrl, showHints, hints, isExample, groupTitle, groupSubtitle, groupContent, currentGroupNumber, groups]);
|
|
312
|
-
// ============ VALIDATION ============
|
|
313
|
-
const validateForm = () => {
|
|
314
|
-
const errors = {};
|
|
315
|
-
if (!question.trim()) {
|
|
316
|
-
errors.question = 'Vui lòng nhập câu hỏi!';
|
|
317
|
-
}
|
|
318
|
-
else if (placeholderIndices.length === 0) {
|
|
319
|
-
errors.question = 'Câu hỏi cần có ít nhất một chỗ trống {0}!';
|
|
320
|
-
}
|
|
321
|
-
// Check if placeholder indices are sequential starting from 0
|
|
322
|
-
const expectedIndices = Array.from({ length: placeholderIndices.length }, (_, i) => i);
|
|
323
|
-
const hasGaps = !placeholderIndices.every((idx, i) => idx === expectedIndices[i]);
|
|
324
|
-
if (hasGaps && placeholderIndices.length > 0) {
|
|
325
|
-
errors.question = 'Các chỗ trống phải đánh số liên tục từ {0}!';
|
|
326
|
-
}
|
|
327
|
-
// Validate each answer - use placeholderIndices or default [0]
|
|
328
|
-
errors.answers = {};
|
|
329
|
-
const indicesToValidate = placeholderIndices.length > 0 ? placeholderIndices : [0];
|
|
330
|
-
indicesToValidate.forEach((idx) => {
|
|
331
|
-
if (!answers[idx] || !answers[idx].trim()) {
|
|
332
|
-
errors.answers[idx] = 'Vui lòng nhập đáp án!';
|
|
333
|
-
}
|
|
334
|
-
});
|
|
335
|
-
// Clean up empty answers object
|
|
336
|
-
if (Object.keys(errors.answers).length === 0) {
|
|
337
|
-
delete errors.answers;
|
|
338
|
-
}
|
|
339
|
-
setValidationErrors(errors);
|
|
340
|
-
return !errors.question && !errors.answers;
|
|
341
|
-
};
|
|
342
|
-
// Handle save
|
|
343
|
-
const handleSave = () => {
|
|
344
|
-
if (!validateForm()) {
|
|
345
|
-
return;
|
|
346
|
-
}
|
|
347
|
-
// NOTE: Save status is now managed by ExamCreator via localStorage
|
|
348
|
-
const saveData = {
|
|
349
|
-
question,
|
|
350
|
-
answers,
|
|
351
|
-
caseSensitive,
|
|
352
|
-
explanation,
|
|
353
|
-
points,
|
|
354
|
-
imageUrl,
|
|
355
|
-
showHints,
|
|
356
|
-
hints: showHints ? hints : [],
|
|
357
|
-
isExample,
|
|
358
|
-
title: groupTitle,
|
|
359
|
-
subtitle: groupSubtitle,
|
|
360
|
-
groupContent,
|
|
361
|
-
groupNumber: currentGroupNumber,
|
|
362
|
-
groups,
|
|
363
|
-
};
|
|
364
|
-
onSave?.(saveData);
|
|
365
|
-
};
|
|
366
|
-
// Update answer at specific index
|
|
367
|
-
const updateAnswer = (index, value) => {
|
|
368
|
-
const newAnswers = [...answers];
|
|
369
|
-
// Ensure array is long enough
|
|
370
|
-
while (newAnswers.length <= index) {
|
|
371
|
-
newAnswers.push('');
|
|
372
|
-
}
|
|
373
|
-
newAnswers[index] = value;
|
|
374
|
-
setAnswers(newAnswers);
|
|
375
|
-
// Clear validation error for this answer
|
|
376
|
-
if (validationErrors.answers?.[index]) {
|
|
377
|
-
setValidationErrors(prev => {
|
|
378
|
-
const newAnswerErrors = { ...prev.answers };
|
|
379
|
-
delete newAnswerErrors[index];
|
|
380
|
-
return {
|
|
381
|
-
...prev,
|
|
382
|
-
answers: Object.keys(newAnswerErrors).length > 0 ? newAnswerErrors : undefined,
|
|
383
|
-
};
|
|
384
|
-
});
|
|
385
|
-
}
|
|
386
|
-
};
|
|
387
|
-
// Generate preview text with answers filled in
|
|
388
|
-
const getPreviewText = () => {
|
|
389
|
-
let preview = question;
|
|
390
|
-
placeholderIndices.forEach((idx) => {
|
|
391
|
-
const answer = answers[idx] || '_____';
|
|
392
|
-
preview = preview.replace(`{${idx}}`, `[${answer}]`);
|
|
393
|
-
});
|
|
394
|
-
return preview;
|
|
395
|
-
};
|
|
396
|
-
// ============ CLIENT MODE PREVIEW ============
|
|
397
|
-
if (isClientMode) {
|
|
398
|
-
const questionData = {
|
|
399
|
-
question,
|
|
400
|
-
answers,
|
|
401
|
-
caseSensitive,
|
|
402
|
-
explanation,
|
|
403
|
-
imageUrl: isGrouped ? '' : (displayPreviewUrl || imageUrl),
|
|
404
|
-
showHints,
|
|
405
|
-
hints: showHints ? hints : [],
|
|
406
|
-
isExample,
|
|
407
|
-
// Group fields for Client preview
|
|
408
|
-
isGroup: isGrouped,
|
|
409
|
-
groupTitle: isGrouped ? groupTitle : '',
|
|
410
|
-
groupSubtitle: isGrouped ? groupSubtitle : '',
|
|
411
|
-
groupContent: isGrouped ? groupContent : '',
|
|
412
|
-
groupImageUrl: isGrouped ? (displayPreviewUrl || imageUrl) : '',
|
|
413
|
-
groupNumber: isGrouped ? currentGroupNumber : undefined,
|
|
414
|
-
};
|
|
415
|
-
return (_jsxs("div", { className: "space-y-4", children: [_jsx(Card, { children: _jsx(CardHeader, { children: _jsxs("div", { className: "flex items-center justify-between", children: [_jsx(CardTitle, { children: "Fill In Blank (Preview)" }), _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(FillInBlankClient, { questionData: questionData, autoFillCorrectAnswers: true, isReviewMode: false, explanation: explanation })] }));
|
|
416
|
-
}
|
|
417
|
-
// ============ CREATOR UI ============
|
|
418
|
-
return (_jsxs("div", { className: "space-y-4", children: [isGrouped && (_jsx(BasicQuestionGroupCard, { groupNumber: currentGroupNumber, title: groupTitle, subtitle: groupSubtitle, content: groupContent, imageUrl: imageUrl, onTitleChange: setGroupTitle, onSubtitleChange: setGroupSubtitle, onContentChange: setGroupContent, onImageChange: setImageUrl, uploadIdPrefix: `fill-in-blank-${partId ?? 'group'}` })), _jsxs(Card, { className: "overflow-hidden border-0 bg-white shadow-lg shadow-blue-100/50", children: [_jsx(CardHeader, { className: "px-4 py-3", children: _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-8 w-8 items-center justify-center rounded-lg bg-gradient-to-br from-blue-500 to-indigo-600 shadow-sm", children: _jsx(Type, { className: "h-4 w-4 text-white" }) }), _jsxs("div", { children: [_jsx(CardTitle, { className: "text-base font-bold text-gray-900", children: "\u0110i\u1EC1n v\u00E0o ch\u1ED7 tr\u1ED1ng" }), _jsx("p", { className: "text-xs text-gray-500", children: "H\u1ED7 tr\u1EE3 nhi\u1EC1u ch\u1ED7 tr\u1ED1ng trong m\u1ED9t c\u00E2u h\u1ECFi" })] })] }), _jsxs("div", { className: "flex items-center gap-2", children: [headerExtra, _jsxs(Button, { variant: "outline", size: "sm", onClick: () => setIsClientMode(true), disabled: !question?.trim() || placeholderIndices.length === 0, className: "gap-2 border-indigo-200 text-indigo-600 hover:bg-indigo-50 hover:text-indigo-700", children: [_jsx(Eye, { className: "h-4 w-4" }), "Xem tr\u01B0\u1EDBc"] })] })] }) }), _jsxs(CardContent, { className: "space-y-4 px-4 pb-4", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-6", children: [_jsx(PointsInput, { defaultValue: points, onChange: (e) => setPoints(Number(e.target.value) || 1) }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Switch, { id: "show-suggested-words", checked: showHints, onCheckedChange: setShowHints }), _jsx(Label, { htmlFor: "show-suggested-words", className: "cursor-pointer text-sm font-medium text-gray-700", children: "Hi\u1EC3n th\u1ECB t\u1EEB g\u1EE3i \u00FD" })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Switch, { id: "isExample", checked: isExample, onCheckedChange: setIsExample }), _jsx(Label, { htmlFor: "isExample", className: "cursor-pointer text-sm font-medium text-gray-700", children: "\u0110\u00E1nh d\u1EA5u l\u00E0 c\u00E2u h\u1ECFi Example" })] })] }), _jsx("div", { className: "rounded-xl border border-blue-200 bg-gradient-to-r from-blue-50 to-indigo-50 p-5 shadow-sm", children: _jsxs("div", { className: "flex items-start gap-3", children: [_jsx("div", { className: "flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-lg bg-blue-100", children: _jsx(Lightbulb, { className: "h-4 w-4 text-blue-600" }) }), _jsxs("div", { className: "text-sm text-blue-900", children: [_jsx("p", { className: "font-semibold text-blue-800", children: "H\u01B0\u1EDBng d\u1EABn t\u1EA1o c\u00E2u h\u1ECFi" }), _jsxs("ul", { className: "mt-2 space-y-1.5 text-blue-700", children: [_jsxs("li", { className: "flex items-center gap-2", children: [_jsx("span", { className: "h-1.5 w-1.5 rounded-full bg-blue-400" }), "S\u1EED d\u1EE5ng ", _jsx("code", { className: "mx-1 rounded bg-blue-100 px-1.5 py-0.5 font-mono text-blue-800", children: '{0}' }), ", ", _jsx("code", { className: "mx-1 rounded bg-blue-100 px-1.5 py-0.5 font-mono text-blue-800", children: '{1}' }), ", ", _jsx("code", { className: "mx-1 rounded bg-blue-100 px-1.5 py-0.5 font-mono text-blue-800", children: '{2}' }), "... \u0111\u1EC3 \u0111\u00E1nh d\u1EA5u c\u00E1c ch\u1ED7 tr\u1ED1ng"] }), _jsxs("li", { className: "flex items-center gap-2", children: [_jsx("span", { className: "h-1.5 w-1.5 rounded-full bg-blue-400" }), "H\u1EC7 th\u1ED1ng s\u1EBD t\u1EF1 \u0111\u1ED9ng t\u1EA1o \u00F4 nh\u1EADp \u0111\u00E1p \u00E1n cho m\u1ED7i ch\u1ED7 tr\u1ED1ng"] }), _jsxs("li", { className: "flex items-center gap-2", children: [_jsx("span", { className: "h-1.5 w-1.5 rounded-full bg-blue-400" }), "V\u00ED d\u1EE5: \u201CHe ", '{0}', " 10 years old and she ", '{1}', " 12.\u201D"] })] })] })] }) }), !isGrouped && (_jsxs("div", { className: "space-y-2", children: [_jsxs(Label, { className: "flex items-center gap-2 text-base font-semibold text-gray-800", children: [_jsx(ImageIcon, { className: "h-4 w-4 text-indigo-500" }), "H\u00ECnh \u1EA3nh ", _jsx("span", { className: "text-sm font-normal text-gray-500", children: "(T\u00F9y ch\u1ECDn)" })] }), _jsx(FileUpload, { id: "fill-in-blank-image", label: "", accept: "image/*", value: imageUrl || '', onChange: (url) => {
|
|
419
|
-
setImageUrl(url);
|
|
420
|
-
}, onPresignedUrlChange: setImagePreviewUrl, maxSize: 5, placeholder: "Upload \u1EA3nh ho\u1EB7c paste URL", autoUpload: true, prefix: "questions" }), displayPreviewUrl && (_jsx(ImagePreview, { src: displayPreviewUrl, alt: "Preview", className: "mt-2 h-48 w-full max-w-md rounded-lg border border-gray-200" }))] })), _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(Textarea, { id: "question", value: question, onChange: (e) => {
|
|
421
|
-
setQuestion(e.target.value);
|
|
422
|
-
if (validationErrors.question) {
|
|
423
|
-
setValidationErrors(prev => ({ ...prev, question: undefined }));
|
|
424
|
-
}
|
|
425
|
-
}, placeholder: "V\u00ED d\u1EE5: He {0} 10 years old and she {1} 12.", rows: 3, className: `min-h-[100px] 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 })), placeholderIndices.length > 0 && (_jsx("div", { className: "flex items-center gap-2 rounded-lg bg-green-50 px-3 py-2", children: _jsxs("span", { className: "text-sm text-green-700", children: ["\u2713 \u0110\u00E3 ph\u00E1t hi\u1EC7n ", _jsx("strong", { children: placeholderIndices.length }), " ch\u1ED7 tr\u1ED1ng: ", placeholderIndices.map(i => (_jsx("code", { className: "mx-0.5 rounded bg-green-100 px-1.5 py-0.5 font-mono text-green-800", children: `{${i}}` }, i)))] }) }))] }), _jsxs("div", { className: "space-y-4", 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(Type, { className: "h-3.5 w-3.5 text-indigo-600" }) }), _jsxs(Label, { className: "text-base font-semibold text-gray-800", children: ["\u0110\u00E1p \u00E1n cho c\u00E1c ch\u1ED7 tr\u1ED1ng ", _jsx("span", { className: "text-red-500", children: "*" })] })] }), _jsxs("div", { className: "grid gap-6 lg:grid-cols-2", children: [_jsx("div", { className: "space-y-3", children: (placeholderIndices.length > 0 ? placeholderIndices : [0]).map((idx) => (_jsxs("div", { className: `rounded-xl border-2 p-4 transition-all ${answers[idx]
|
|
426
|
-
? 'border-green-200 bg-gradient-to-r from-green-50 to-emerald-50'
|
|
427
|
-
: 'border-gray-200 bg-white'}`, children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: `flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-xl font-mono font-bold transition-all ${answers[idx]
|
|
428
|
-
? 'bg-gradient-to-br from-green-500 to-emerald-600 text-white shadow-md'
|
|
429
|
-
: 'bg-indigo-100 text-indigo-700'}`, children: `{${idx}}` }), _jsx(Input, { value: answers[idx] || '', onChange: (e) => updateAnswer(idx, e.target.value), placeholder: `Đáp án cho {${idx}}`, className: `border-gray-200 bg-white transition-all focus:border-indigo-300 focus:ring-indigo-200 ${validationErrors.answers?.[idx] ? 'border-red-400 focus:border-red-500 focus:ring-red-200' : ''}` })] }), validationErrors.answers?.[idx] && (_jsx("p", { className: "mt-2 ml-13 text-sm text-red-600", children: validationErrors.answers[idx] }))] }, idx))) }), showHints && (_jsxs("div", { className: "space-y-4 rounded-xl border-2 border-purple-200 bg-gradient-to-r from-purple-50 to-indigo-50 p-4", 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-purple-100", children: _jsx(ListChecks, { className: "h-3.5 w-3.5 text-purple-600" }) }), _jsxs(Label, { className: "text-base font-semibold text-gray-800", children: ["Danh s\u00E1ch t\u1EEB g\u1EE3i \u00FD ", _jsxs("span", { className: "text-sm font-normal text-gray-500", children: ["(", hints.length, " t\u1EEB)"] })] })] }), _jsxs("div", { className: "flex gap-2", children: [_jsx(Input, { value: newHint, onChange: (e) => setNewHint(e.target.value), placeholder: "Nh\u1EADp t\u1EEB g\u1EE3i \u00FD m\u1EDBi...", className: "flex-1 border-gray-200 bg-white", onKeyDown: (e) => {
|
|
430
|
-
if (e.key === 'Enter' && newHint.trim()) {
|
|
431
|
-
e.preventDefault();
|
|
432
|
-
if (!hints.includes(newHint.trim())) {
|
|
433
|
-
setHints([...hints, newHint.trim()]);
|
|
434
|
-
}
|
|
435
|
-
setNewHint('');
|
|
436
|
-
}
|
|
437
|
-
} }), _jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: () => {
|
|
438
|
-
if (newHint.trim() && !hints.includes(newHint.trim())) {
|
|
439
|
-
setHints([...hints, newHint.trim()]);
|
|
440
|
-
setNewHint('');
|
|
441
|
-
}
|
|
442
|
-
}, disabled: !newHint.trim(), 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"] })] }), hints.length > 0 ? (_jsx("div", { className: "flex flex-wrap gap-2", children: hints.map((word, index) => (_jsxs("div", { className: "flex items-center gap-2 rounded-lg border border-purple-200 bg-white px-3 py-2 shadow-sm transition-all hover:border-purple-300 hover:shadow", children: [_jsx("span", { className: "text-sm font-medium text-gray-700", children: word }), _jsx("button", { type: "button", onClick: () => {
|
|
443
|
-
setHints(hints.filter((_, i) => i !== index));
|
|
444
|
-
}, className: "rounded-full p-0.5 text-gray-400 transition-colors hover:bg-red-100 hover:text-red-500", children: _jsx(Trash2, { className: "h-3.5 w-3.5" }) })] }, index))) })) : (_jsx("p", { className: "text-center text-sm text-gray-500 italic", children: "Ch\u01B0a c\u00F3 t\u1EEB g\u1EE3i \u00FD n\u00E0o. Th\u00EAm t\u1EEB \u0111\u1EC3 hi\u1EC3n th\u1ECB cho h\u1ECDc sinh." }))] }))] })] }), question && (_jsxs("div", { className: "rounded-xl border border-gray-200 bg-gradient-to-r from-gray-50 to-slate-50 p-4", children: [_jsxs("div", { className: "mb-2 flex items-center gap-2 text-xs font-semibold text-gray-600", children: [_jsx(Eye, { className: "h-3.5 w-3.5" }), "Xem tr\u01B0\u1EDBc c\u00E2u ho\u00E0n ch\u1EC9nh"] }), _jsx("p", { className: "text-sm text-gray-700", children: getPreviewText() })] })), _jsxs("div", { className: "flex items-center justify-between rounded-xl border-2 border-gray-200 bg-white p-4 transition-all hover:border-gray-300", 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-gray-100", children: _jsx(ToggleLeft, { className: "h-4 w-4 text-gray-600" }) }), _jsxs("div", { className: "space-y-0.5", children: [_jsx(Label, { htmlFor: "case-sensitive", className: "font-semibold text-gray-800", children: "Ph\u00E2n bi\u1EC7t hoa/th\u01B0\u1EDDng" }), _jsx("p", { className: "text-sm text-gray-500", children: "B\u1EADt n\u1EBFu \u0111\u00E1p \u00E1n c\u1EA7n ph\u00E2n bi\u1EC7t ch\u1EEF hoa/th\u01B0\u1EDDng" })] })] }), _jsx(Switch, { id: "case-sensitive", checked: caseSensitive, onCheckedChange: setCaseSensitive })] }), 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-amber-100/50", children: [_jsx(CardHeader, { className: "px-4 py-3", children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-7 w-7 items-center justify-center rounded-lg bg-gradient-to-br from-amber-500 to-orange-500 shadow-sm", children: _jsx(BookOpen, { className: "h-3.5 w-3.5 text-white" }) }), _jsxs("div", { children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(CardTitle, { className: "text-base font-semibold text-gray-800", children: "Gi\u1EA3i th\u00EDch" }), _jsx("span", { className: "inline-flex items-center rounded-full border border-amber-200 bg-amber-50 px-2.5 py-0.5 text-xs font-medium text-amber-600", children: "Kh\u00F4ng b\u1EAFt bu\u1ED9c" })] }), _jsx("p", { className: "text-xs text-gray-500 mt-0.5", children: "Gi\u1EA3i th\u00EDch 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, { className: "px-4 pb-4", 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" }) })] })] }));
|
|
445
|
-
}
|
|
446
|
-
export function FillInBlankCreator(props) {
|
|
447
|
-
return _jsx(FillInBlankCreatorContent, { ...props });
|
|
448
|
-
}
|
|
1
|
+
"use client";import{jsx as e,jsxs as t}from"react/jsx-runtime";import{ImagePreview as n}from"../../../../components/ui/image-preview";import{useState as r,useRef as s,useEffect as i,useMemo as a}from"react";import{Button as l}from"../../../../components/ui/button";import{Label as o}from"../../../../components/ui/label";import{Input as c}from"../../../../components/ui/input";import{Textarea as d}from"../../../../components/ui/textarea";import{PointsInput as m}from"../../../../components/ui/points-input";import{Card as h,CardContent as g,CardHeader as u,CardTitle as p}from"../../../../components/ui/card";import{Switch as x}from"../../../../components/ui/switch";import{Pencil as b,Eye as f,Lightbulb as N,Type as w,MessageSquare as v,ToggleLeft as y,Plus as C,Trash2 as k,ListChecks as q,ImageIcon as U,BookOpen as S}from"lucide-react";import{FillInBlankClient as j}from"./FillInBlankClient";import{useDebouncedCallback as A,usePresignedFileUrl as E}from"../../../../shared/lib/hooks";import{FileUpload as H}from"../../../../components/ui/file-upload";import{BasicQuestionGroupCard as G}from"../../groups/BasicQuestionGroupCard";import{useBasicQuestionGroup as I}from"../../_shared/hooks/useBasicQuestionGroup";function T(e){const t=/\{(\d+)\}/g,n=[];let r;for(;null!==(r=t.exec(e));){const e=parseInt(r[1],10);n.includes(e)||n.push(e)}return n.sort((e,t)=>e-t)}function F({initialData:F,onSave:B,onCancel:D,onChange:P,externalErrors:V,onUnsavedChangesChange:O,validationRef:$,groups:_,questionIndexInPart:z,partId:J,headerExtra:M}){const{isGrouped:Q,currentGroupNumber:R,groupTitle:K,groupSubtitle:L,groupContent:X,imageUrl:W,setGroupTitle:Y,setGroupSubtitle:Z,setGroupContent:ee,setImageUrl:te}=I({partId:J,groups:_,questionIndexInPart:z,initialData:{title:(()=>{if(F?.content?.passageTitle)return F.content.passageTitle;const e=F?.questionGroup?.payload;return e?.title?e.title:F?.title?F.title:""})(),subtitle:(()=>{if(F?.content?.passageSubtitle)return F.content.passageSubtitle;const e=F?.questionGroup?.payload;return e?.subtitle?e.subtitle:""})(),content:(()=>{if(F?.content?.passage)return F.content.passage;const e=F?.questionGroup?.payload;return e?.content?e.content:F?.groupContent?F.groupContent:""})(),imageUrl:(()=>{if(F?.content?.imageUrl)return F.content.imageUrl;const e=F?.questionGroup?.payload;return e?.imageUrl?e.imageUrl:F?.imageUrl?F.imageUrl:""})()}}),ne=()=>F?.content?.question?F.content.question:F?.question?F.question:F?.blanks?.[0]?.text?F.blanks[0].text:"",re=()=>F?.correctAnswer?.answers&&Array.isArray(F.correctAnswer.answers)?F.correctAnswer.answers:F?.answers&&F.answers.length>0?F.answers:F?.blanks?.[0]?.correctAnswer?[F.blanks[0].correctAnswer]:[],se=()=>void 0!==F?.correctAnswer?.caseSensitive?F.correctAnswer.caseSensitive:F?.caseSensitive||!1,ie=()=>void 0!==F?.content?.showHints?Boolean(F.content.showHints):void 0!==F?.showHints&&Boolean(F.showHints),ae=()=>Array.isArray(F?.content?.hints)?F.content.hints:Array.isArray(F?.hints)?F.hints:[],[le,oe]=r(ne()),[ce,de]=r(re()),[me,he]=r(se()),[ge,ue]=r(F?.explanation||""),[pe,xe]=r(F?.points||1),[be,fe]=r(ie()),[Ne,we]=r(ae()),[ve,ye]=r(""),[Ce,ke]=r(F?.isExample||!1),[qe,Ue]=r(!1),[Se,je]=r(""),{previewUrl:Ae}=E(W),Ee=Se||Ae||"",[He,Ge]=r({}),Ie=s(F),Te=s(!1),Fe=s(P),Be=s("");i(()=>{Fe.current=P},[P]);const De=A(e=>{Fe.current?.(e)},300),Pe=a(()=>T(le),[le]),Ve=a(()=>function(e){const t=T(e);return t.length>0?Math.max(...t):-1}(le),[le]);i(()=>{if(Ve>=0){const e=Ve+1;if(ce.length<e){const t=[...ce];for(;t.length<e;)t.push("");de(t)}}},[Ve,ce.length]),i(()=>{$&&($.current={triggerValidation:async()=>Oe(),getFormData:()=>({question:le,answers:ce,caseSensitive:me,explanation:ge,points:pe,imageUrl:W,showHints:be,hints:be?Ne:[],isExample:Ce,title:K,subtitle:L,groupContent:X,groupNumber:R,groups:_})})},[$,le,ce,me,ge,pe,W,be,Ne,K,L,X,R,_]),i(()=>{JSON.stringify(Ie.current)!==JSON.stringify(F)&&(Te.current=!0,Ie.current=F,F?(oe(ne()),de(re()),he(se()),ue(F.explanation||""),xe(F.points||1),fe(ie()),we(ae()),ke(F.isExample||!1)):(oe(""),de([]),he(!1),ue(""),xe(1),fe(!1),we([]),ke(!1)),setTimeout(()=>{Te.current=!1},150))},[F]),i(()=>{if(Te.current){const e=JSON.stringify({question:le,answers:ce,caseSensitive:me,explanation:ge,points:pe,imageUrl:W,showHints:be,hints:Ne,isExample:Ce,title:K,subtitle:L,groupContent:X,groupNumber:R,groups:_});return void(Be.current=e)}const e={question:le,answers:ce,caseSensitive:me,explanation:ge,points:pe,imageUrl:W,showHints:be,hints:be?Ne:[],isExample:Ce,title:K,subtitle:L,groupContent:X,groupNumber:R,groups:_},t=JSON.stringify(e);Be.current!==t&&(Be.current=t,De(e))},[le,ce,me,ge,pe,W,be,Ne,Ce,K,L,X,R,_]);const Oe=()=>{const e={};le.trim()?0===Pe.length&&(e.question="Câu hỏi cần có ít nhất một chỗ trống {0}!"):e.question="Vui lòng nhập câu hỏi!";const t=Array.from({length:Pe.length},(e,t)=>t);!Pe.every((e,n)=>e===t[n])&&Pe.length>0&&(e.question="Các chỗ trống phải đánh số liên tục từ {0}!"),e.answers={};return(Pe.length>0?Pe:[0]).forEach(t=>{ce[t]&&ce[t].trim()||(e.answers[t]="Vui lòng nhập đáp án!")}),0===Object.keys(e.answers).length&&delete e.answers,Ge(e),!e.question&&!e.answers};if(qe){const n={question:le,answers:ce,caseSensitive:me,explanation:ge,imageUrl:Q?"":Ee||W,showHints:be,hints:be?Ne:[],isExample:Ce,isGroup:Q,groupTitle:Q?K:"",groupSubtitle:Q?L:"",groupContent:Q?X:"",groupImageUrl:Q?Ee||W:"",groupNumber:Q?R:void 0};return t("div",{className:"space-y-4",children:[e(h,{children:e(u,{children:t("div",{className:"flex items-center justify-between",children:[e(p,{children:"Fill In Blank (Preview)"}),t(l,{variant:"outline",size:"sm",onClick:()=>Ue(!1),children:[e(b,{className:"mr-2 h-4 w-4"}),"Quay lại Edit Mode"]})]})})}),e(j,{questionData:n,autoFillCorrectAnswers:!0,isReviewMode:!1,explanation:ge})]})}return t("div",{className:"space-y-4",children:[Q&&e(G,{groupNumber:R,title:K,subtitle:L,content:X,imageUrl:W,onTitleChange:Y,onSubtitleChange:Z,onContentChange:ee,onImageChange:te,uploadIdPrefix:`fill-in-blank-${J??"group"}`}),t(h,{className:"overflow-hidden border-0 bg-white shadow-lg shadow-blue-100/50",children:[e(u,{className:"px-4 py-3",children:t("div",{className:"flex items-center justify-between",children:[t("div",{className:"flex items-center gap-2",children:[e("div",{className:"flex h-8 w-8 items-center justify-center rounded-lg bg-gradient-to-br from-blue-500 to-indigo-600 shadow-sm",children:e(w,{className:"h-4 w-4 text-white"})}),t("div",{children:[e(p,{className:"text-base font-bold text-gray-900",children:"Điền vào chỗ trống"}),e("p",{className:"text-xs text-gray-500",children:"Hỗ trợ nhiều chỗ trống trong một câu hỏi"})]})]}),t("div",{className:"flex items-center gap-2",children:[M,t(l,{variant:"outline",size:"sm",onClick:()=>Ue(!0),disabled:!le?.trim()||0===Pe.length,className:"gap-2 border-indigo-200 text-indigo-600 hover:bg-indigo-50 hover:text-indigo-700",children:[e(f,{className:"h-4 w-4"}),"Xem trước"]})]})]})}),t(g,{className:"space-y-4 px-4 pb-4",children:[t("div",{className:"flex flex-wrap items-center gap-6",children:[e(m,{defaultValue:pe,onChange:e=>xe(Number(e.target.value)||1)}),t("div",{className:"flex items-center gap-2",children:[e(x,{id:"show-suggested-words",checked:be,onCheckedChange:fe}),e(o,{htmlFor:"show-suggested-words",className:"cursor-pointer text-sm font-medium text-gray-700",children:"Hiển thị từ gợi ý"})]}),t("div",{className:"flex items-center gap-2",children:[e(x,{id:"isExample",checked:Ce,onCheckedChange:ke}),e(o,{htmlFor:"isExample",className:"cursor-pointer text-sm font-medium text-gray-700",children:"Đánh dấu là câu hỏi Example"})]})]}),e("div",{className:"rounded-xl border border-blue-200 bg-gradient-to-r from-blue-50 to-indigo-50 p-5 shadow-sm",children:t("div",{className:"flex items-start gap-3",children:[e("div",{className:"flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-lg bg-blue-100",children:e(N,{className:"h-4 w-4 text-blue-600"})}),t("div",{className:"text-sm text-blue-900",children:[e("p",{className:"font-semibold text-blue-800",children:"Hướng dẫn tạo câu hỏi"}),t("ul",{className:"mt-2 space-y-1.5 text-blue-700",children:[t("li",{className:"flex items-center gap-2",children:[e("span",{className:"h-1.5 w-1.5 rounded-full bg-blue-400"}),"Sử dụng ",e("code",{className:"mx-1 rounded bg-blue-100 px-1.5 py-0.5 font-mono text-blue-800",children:"{0}"}),", ",e("code",{className:"mx-1 rounded bg-blue-100 px-1.5 py-0.5 font-mono text-blue-800",children:"{1}"}),", ",e("code",{className:"mx-1 rounded bg-blue-100 px-1.5 py-0.5 font-mono text-blue-800",children:"{2}"}),"... để đánh dấu các chỗ trống"]}),t("li",{className:"flex items-center gap-2",children:[e("span",{className:"h-1.5 w-1.5 rounded-full bg-blue-400"}),"Hệ thống sẽ tự động tạo ô nhập đáp án cho mỗi chỗ trống"]}),t("li",{className:"flex items-center gap-2",children:[e("span",{className:"h-1.5 w-1.5 rounded-full bg-blue-400"}),"Ví dụ: “He ","{0}"," 10 years old and she ","{1}"," 12.”"]})]})]})]})}),!Q&&t("div",{className:"space-y-2",children:[t(o,{className:"flex items-center gap-2 text-base font-semibold text-gray-800",children:[e(U,{className:"h-4 w-4 text-indigo-500"}),"Hình ảnh ",e("span",{className:"text-sm font-normal text-gray-500",children:"(Tùy chọn)"})]}),e(H,{id:"fill-in-blank-image",label:"",accept:"image/*",value:W||"",onChange:e=>{te(e)},onPresignedUrlChange:je,maxSize:5,placeholder:"Upload ảnh hoặc paste URL",autoUpload:!0,prefix:"questions"}),Ee&&e(n,{src:Ee,alt:"Preview",className:"mt-2 h-48 w-full max-w-md rounded-lg border border-gray-200"})]}),t("div",{className:"space-y-2",children:[t(o,{htmlFor:"question",className:"flex items-center gap-2 text-base font-semibold text-gray-800",children:[e(v,{className:"h-4 w-4 text-indigo-500"}),"Câu hỏi ",e("span",{className:"text-red-500",children:"*"})]}),e(d,{id:"question",value:le,onChange:e=>{oe(e.target.value),He.question&&Ge(e=>({...e,question:void 0}))},placeholder:"Ví dụ: He {0} 10 years old and she {1} 12.",rows:3,className:"min-h-[100px] border-gray-200 bg-white transition-all focus:border-indigo-300 focus:ring-indigo-200 "+(He.question?"border-red-400 focus:border-red-500 focus:ring-red-200":"")}),He.question&&e("p",{className:"text-sm text-red-600",children:He.question}),Pe.length>0&&e("div",{className:"flex items-center gap-2 rounded-lg bg-green-50 px-3 py-2",children:t("span",{className:"text-sm text-green-700",children:["✓ Đã phát hiện ",e("strong",{children:Pe.length})," chỗ trống: ",Pe.map(t=>e("code",{className:"mx-0.5 rounded bg-green-100 px-1.5 py-0.5 font-mono text-green-800",children:`{${t}}`},t))]})})]}),t("div",{className:"space-y-4",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(w,{className:"h-3.5 w-3.5 text-indigo-600"})}),t(o,{className:"text-base font-semibold text-gray-800",children:["Đáp án cho các chỗ trống ",e("span",{className:"text-red-500",children:"*"})]})]}),t("div",{className:"grid gap-6 lg:grid-cols-2",children:[e("div",{className:"space-y-3",children:(Pe.length>0?Pe:[0]).map(n=>t("div",{className:"rounded-xl border-2 p-4 transition-all "+(ce[n]?"border-green-200 bg-gradient-to-r from-green-50 to-emerald-50":"border-gray-200 bg-white"),children:[t("div",{className:"flex items-center gap-3",children:[e("div",{className:"flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-xl font-mono font-bold transition-all "+(ce[n]?"bg-gradient-to-br from-green-500 to-emerald-600 text-white shadow-md":"bg-indigo-100 text-indigo-700"),children:`{${n}}`}),e(c,{value:ce[n]||"",onChange:e=>((e,t)=>{const n=[...ce];for(;n.length<=e;)n.push("");n[e]=t,de(n),He.answers?.[e]&&Ge(t=>{const n={...t.answers};return delete n[e],{...t,answers:Object.keys(n).length>0?n:void 0}})})(n,e.target.value),placeholder:`Đáp án cho {${n}}`,className:"border-gray-200 bg-white transition-all focus:border-indigo-300 focus:ring-indigo-200 "+(He.answers?.[n]?"border-red-400 focus:border-red-500 focus:ring-red-200":"")})]}),He.answers?.[n]&&e("p",{className:"mt-2 ml-13 text-sm text-red-600",children:He.answers[n]})]},n))}),be&&t("div",{className:"space-y-4 rounded-xl border-2 border-purple-200 bg-gradient-to-r from-purple-50 to-indigo-50 p-4",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-purple-100",children:e(q,{className:"h-3.5 w-3.5 text-purple-600"})}),t(o,{className:"text-base font-semibold text-gray-800",children:["Danh sách từ gợi ý ",t("span",{className:"text-sm font-normal text-gray-500",children:["(",Ne.length," từ)"]})]})]}),t("div",{className:"flex gap-2",children:[e(c,{value:ve,onChange:e=>ye(e.target.value),placeholder:"Nhập từ gợi ý mới...",className:"flex-1 border-gray-200 bg-white",onKeyDown:e=>{"Enter"===e.key&&ve.trim()&&(e.preventDefault(),Ne.includes(ve.trim())||we([...Ne,ve.trim()]),ye(""))}}),t(l,{type:"button",variant:"outline",size:"sm",onClick:()=>{ve.trim()&&!Ne.includes(ve.trim())&&(we([...Ne,ve.trim()]),ye(""))},disabled:!ve.trim(),className:"gap-2 border-green-200 text-green-600 hover:bg-green-50 hover:text-green-700",children:[e(C,{className:"h-4 w-4"}),"Thêm"]})]}),Ne.length>0?e("div",{className:"flex flex-wrap gap-2",children:Ne.map((n,r)=>t("div",{className:"flex items-center gap-2 rounded-lg border border-purple-200 bg-white px-3 py-2 shadow-sm transition-all hover:border-purple-300 hover:shadow",children:[e("span",{className:"text-sm font-medium text-gray-700",children:n}),e("button",{type:"button",onClick:()=>{we(Ne.filter((e,t)=>t!==r))},className:"rounded-full p-0.5 text-gray-400 transition-colors hover:bg-red-100 hover:text-red-500",children:e(k,{className:"h-3.5 w-3.5"})})]},r))}):e("p",{className:"text-center text-sm text-gray-500 italic",children:"Chưa có từ gợi ý nào. Thêm từ để hiển thị cho học sinh."})]})]})]}),le&&t("div",{className:"rounded-xl border border-gray-200 bg-gradient-to-r from-gray-50 to-slate-50 p-4",children:[t("div",{className:"mb-2 flex items-center gap-2 text-xs font-semibold text-gray-600",children:[e(f,{className:"h-3.5 w-3.5"}),"Xem trước câu hoàn chỉnh"]}),e("p",{className:"text-sm text-gray-700",children:(()=>{let e=le;return Pe.forEach(t=>{const n=ce[t]||"_____";e=e.replace(`{${t}}`,`[${n}]`)}),e})()})]}),t("div",{className:"flex items-center justify-between rounded-xl border-2 border-gray-200 bg-white p-4 transition-all hover:border-gray-300",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-gray-100",children:e(y,{className:"h-4 w-4 text-gray-600"})}),t("div",{className:"space-y-0.5",children:[e(o,{htmlFor:"case-sensitive",className:"font-semibold text-gray-800",children:"Phân biệt hoa/thường"}),e("p",{className:"text-sm text-gray-500",children:"Bật nếu đáp án cần phân biệt chữ hoa/thường"})]})]}),e(x,{id:"case-sensitive",checked:me,onCheckedChange:he})]}),V&&V.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:V.map((t,n)=>e("li",{children:t},n))})]})]})]})]}),t(h,{className:"overflow-hidden border-0 bg-white shadow-lg shadow-amber-100/50",children:[e(u,{className:"px-4 py-3",children:t("div",{className:"flex items-center gap-2",children:[e("div",{className:"flex h-7 w-7 items-center justify-center rounded-lg bg-gradient-to-br from-amber-500 to-orange-500 shadow-sm",children:e(S,{className:"h-3.5 w-3.5 text-white"})}),t("div",{children:[t("div",{className:"flex items-center gap-2",children:[e(p,{className:"text-base font-semibold text-gray-800",children:"Giải thích"}),e("span",{className:"inline-flex items-center rounded-full border border-amber-200 bg-amber-50 px-2.5 py-0.5 text-xs font-medium text-amber-600",children:"Không bắt buộc"})]}),e("p",{className:"text-xs text-gray-500 mt-0.5",children:"Giải thích cho đáp án đúng — hiển thị sau khi học sinh hoàn thành câu hỏi"})]})]})}),e(g,{className:"px-4 pb-4",children:e(d,{id:"explanation",value:ge,onChange:e=>ue(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 FillInBlankCreator(t){return e(F,{...t})}
|
|
@@ -1,2 +1 @@
|
|
|
1
|
-
export
|
|
2
|
-
export * from './FillInBlankClient';
|
|
1
|
+
export*from"./FillInBlankCreator";export*from"./FillInBlankClient";
|